diff --git a/cmd/compute.cloudserver.go b/cmd/compute.cloudserver.go index 62ca8df..a7c19af 100644 --- a/cmd/compute.cloudserver.go +++ b/cmd/compute.cloudserver.go @@ -264,6 +264,12 @@ var cloudserverCreateCmd = &cobra.Command{ return } + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + if response != nil && response.Data != nil { headers := []TableColumn{ {Header: "ID", Width: 30}, @@ -298,7 +304,11 @@ var cloudserverCreateCmd = &cobra.Command{ fmt.Sprintf("%d", hd), regionValue, } - PrintTable(headers, [][]string{row}) + if err := RenderOutput(format, response.Data, func() { + PrintTable(headers, [][]string{row}) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("Cloud server created, but no data returned.") } @@ -572,6 +582,12 @@ var cloudserverListCmd = &cobra.Command{ Use: "list", Short: "List all cloud servers", Run: func(cmd *cobra.Command, args []string) { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + projectID, err := GetProjectID(cmd) if err != nil { fmt.Printf("Error: %v\n", err) @@ -651,7 +667,11 @@ var cloudserverListCmd = &cobra.Command{ }) } - PrintTable(headers, rows) + if err := RenderOutput(format, response.Data.Values, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No cloud servers found") } diff --git a/cmd/compute.keypair.go b/cmd/compute.keypair.go index fd29f56..8f5b071 100644 --- a/cmd/compute.keypair.go +++ b/cmd/compute.keypair.go @@ -140,6 +140,11 @@ var keypairCreateCmd = &cobra.Command{ } if response != nil && response.Data != nil { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } headers := []TableColumn{ {Header: "NAME", Width: 40}, {Header: "PUBLIC_KEY", Width: 60}, @@ -157,7 +162,11 @@ var keypairCreateCmd = &cobra.Command{ }(), publicKeyValue, } - PrintTable(headers, [][]string{row}) + if err := RenderOutput(format, response.Data, func() { + PrintTable(headers, [][]string{row}) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("Keypair created, but no data returned.") } @@ -311,6 +320,12 @@ var keypairListCmd = &cobra.Command{ Use: "list", Short: "List all keypairs", Run: func(cmd *cobra.Command, args []string) { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + projectID, err := GetProjectID(cmd) if err != nil { fmt.Printf("Error: %v\n", err) @@ -384,7 +399,11 @@ var keypairListCmd = &cobra.Command{ rows = append(rows, []string{name, id, publicKey, status}) } - PrintTable(headers, rows) + if err := RenderOutput(format, response.Data.Values, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No keypairs found") } diff --git a/cmd/container.containerregistry.go b/cmd/container.containerregistry.go index 1d93246..041ddc0 100644 --- a/cmd/container.containerregistry.go +++ b/cmd/container.containerregistry.go @@ -612,6 +612,11 @@ var containerregistryListCmd = &cobra.Command{ } if response.Data != nil && len(response.Data.Values) > 0 { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } headers := []TableColumn{ {Header: "NAME", Width: 40}, {Header: "ID", Width: 30}, @@ -649,7 +654,11 @@ var containerregistryListCmd = &cobra.Command{ }) } - PrintTable(headers, rows) + if err := RenderOutput(format, response.Data.Values, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No container registries found") } diff --git a/cmd/container.kaas.go b/cmd/container.kaas.go index 52240d9..2c13178 100644 --- a/cmd/container.kaas.go +++ b/cmd/container.kaas.go @@ -284,6 +284,11 @@ var kaasCreateCmd = &cobra.Command{ } if response != nil && response.Data != nil { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } headers := []TableColumn{ {Header: "ID", Width: 30}, {Header: "NAME", Width: 40}, @@ -315,7 +320,11 @@ var kaasCreateCmd = &cobra.Command{ return "" }(), } - PrintTable(headers, [][]string{row}) + if err := RenderOutput(format, response.Data, func() { + PrintTable(headers, [][]string{row}) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("KaaS cluster created, but no data returned.") } @@ -693,6 +702,12 @@ var kaasListCmd = &cobra.Command{ Use: "list", Short: "List all KaaS clusters", Run: func(cmd *cobra.Command, args []string) { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + projectID, err := GetProjectID(cmd) if err != nil { fmt.Printf("Error: %v\n", err) @@ -753,7 +768,11 @@ var kaasListCmd = &cobra.Command{ }) } - PrintTable(headers, rows) + if err := RenderOutput(format, response.Data.Values, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No KaaS clusters found") } diff --git a/cmd/context.go b/cmd/context.go index d244671..2f7becf 100644 --- a/cmd/context.go +++ b/cmd/context.go @@ -11,14 +11,20 @@ import ( // Context represents the CLI context configuration type Context struct { - CurrentContext string `yaml:"current-context"` - Contexts map[string]CtxInfo `yaml:"contexts"` + CurrentContext string `yaml:"current-context" json:"current_context"` + Contexts map[string]CtxInfo `yaml:"contexts" json:"contexts"` } // CtxInfo represents a single context type CtxInfo struct { - ProjectID string `yaml:"project-id"` - Name string `yaml:"name,omitempty"` + ProjectID string `yaml:"project-id" json:"project_id"` + Name string `yaml:"name,omitempty" json:"name,omitempty"` +} + +// contextCurrentOutput is the structured output for the current context command +type contextCurrentOutput struct { + Name string `yaml:"name" json:"name"` + ProjectID string `yaml:"project_id" json:"project_id"` } var contextCmd = &cobra.Command{ @@ -71,6 +77,12 @@ var contextUseCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { contextName := args[0] + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + // Load context ctx, err := LoadContext() if err != nil { @@ -97,8 +109,17 @@ var contextUseCmd = &cobra.Command{ return } - fmt.Printf("Switched to context '%s'\n", contextName) - fmt.Printf("Project ID: %s\n", ctx.Contexts[contextName].ProjectID) + output := contextCurrentOutput{ + Name: contextName, + ProjectID: ctx.Contexts[contextName].ProjectID, + } + + if err := RenderOutput(format, output, func() { + fmt.Printf("Switched to context '%s'\n", contextName) + fmt.Printf("Project ID: %s\n", ctx.Contexts[contextName].ProjectID) + }); err != nil { + fmt.Println(err.Error()) + } }, } @@ -106,6 +127,12 @@ var contextListCmd = &cobra.Command{ Use: "list", Short: "List all contexts", Run: func(cmd *cobra.Command, args []string) { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + // Load context ctx, err := LoadContext() if err != nil { @@ -118,17 +145,21 @@ var contextListCmd = &cobra.Command{ return } - fmt.Println("\nContexts:") - fmt.Println("=========") - for name, info := range ctx.Contexts { - current := "" - if name == ctx.CurrentContext { - current = " *" + if err := RenderOutput(format, ctx, func() { + fmt.Println("\nContexts:") + fmt.Println("=========") + for name, info := range ctx.Contexts { + current := "" + if name == ctx.CurrentContext { + current = " *" + } + fmt.Printf("%-20s Project ID: %s%s\n", name, info.ProjectID, current) } - fmt.Printf("%-20s Project ID: %s%s\n", name, info.ProjectID, current) - } - if ctx.CurrentContext != "" { - fmt.Printf("\n* = current context\n") + if ctx.CurrentContext != "" { + fmt.Printf("\n* = current context\n") + } + }); err != nil { + fmt.Println(err.Error()) } }, } @@ -137,6 +168,12 @@ var contextCurrentCmd = &cobra.Command{ Use: "current", Short: "Show current context", Run: func(cmd *cobra.Command, args []string) { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + // Load context ctx, err := LoadContext() if err != nil || ctx.CurrentContext == "" { @@ -150,8 +187,17 @@ var contextCurrentCmd = &cobra.Command{ return } - fmt.Printf("Current context: %s\n", ctx.CurrentContext) - fmt.Printf("Project ID: %s\n", info.ProjectID) + output := contextCurrentOutput{ + Name: ctx.CurrentContext, + ProjectID: info.ProjectID, + } + + if err := RenderOutput(format, output, func() { + fmt.Printf("Current context: %s\n", ctx.CurrentContext) + fmt.Printf("Project ID: %s\n", info.ProjectID) + }); err != nil { + fmt.Println(err.Error()) + } }, } diff --git a/cmd/database.backup.go b/cmd/database.backup.go index c7b3291..9c13854 100644 --- a/cmd/database.backup.go +++ b/cmd/database.backup.go @@ -189,6 +189,11 @@ var backupCreateCmd = &cobra.Command{ } if response != nil && response.Data != nil { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } headers := []TableColumn{ {Header: "ID", Width: 30}, {Header: "NAME", Width: 40}, @@ -221,7 +226,11 @@ var backupCreateCmd = &cobra.Command{ return "" }(), } - PrintTable(headers, [][]string{row}) + if err := RenderOutput(format, response.Data, func() { + PrintTable(headers, [][]string{row}) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("Backup created, but no data returned.") } @@ -308,6 +317,12 @@ var backupListCmd = &cobra.Command{ Use: "list", Short: "List all database backups", Run: func(cmd *cobra.Command, args []string) { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + projectID, err := GetProjectID(cmd) if err != nil { fmt.Printf("Error: %v\n", err) @@ -376,7 +391,11 @@ var backupListCmd = &cobra.Command{ } rows = append(rows, row) } - PrintTable(headers, rows) + if err := RenderOutput(format, resp.Data.Values, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No backups found") } diff --git a/cmd/database.dbaas.database.go b/cmd/database.dbaas.database.go index 4c6017b..328d3fd 100644 --- a/cmd/database.dbaas.database.go +++ b/cmd/database.dbaas.database.go @@ -240,6 +240,11 @@ var dbaasDatabaseListCmd = &cobra.Command{ } if resp != nil && resp.Data != nil && len(resp.Data.Values) > 0 { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } headers := []TableColumn{ {Header: "NAME", Width: 40}, {Header: "CREATION DATE", Width: 25}, @@ -265,7 +270,11 @@ var dbaasDatabaseListCmd = &cobra.Command{ } rows = append(rows, row) } - PrintTable(headers, rows) + if err := RenderOutput(format, resp.Data.Values, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No databases found") } diff --git a/cmd/database.dbaas.go b/cmd/database.dbaas.go index a88279c..3a9a79b 100644 --- a/cmd/database.dbaas.go +++ b/cmd/database.dbaas.go @@ -156,6 +156,11 @@ var dbaasCreateCmd = &cobra.Command{ } if response != nil && response.Data != nil { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } headers := []TableColumn{ {Header: "ID", Width: 30}, {Header: "NAME", Width: 40}, @@ -202,7 +207,11 @@ var dbaasCreateCmd = &cobra.Command{ return "" }(), } - PrintTable(headers, [][]string{row}) + if err := RenderOutput(format, response.Data, func() { + PrintTable(headers, [][]string{row}) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("DBaaS instance created, but no data returned.") } @@ -303,6 +312,12 @@ var dbaasListCmd = &cobra.Command{ Use: "list", Short: "List all DBaaS instances", Run: func(cmd *cobra.Command, args []string) { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + projectID, err := GetProjectID(cmd) if err != nil { fmt.Printf("Error: %v\n", err) @@ -392,7 +407,11 @@ var dbaasListCmd = &cobra.Command{ } rows = append(rows, row) } - PrintTable(headers, rows) + if err := RenderOutput(format, resp.Data.Values, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No DBaaS instances found") } diff --git a/cmd/database.dbaas.user.go b/cmd/database.dbaas.user.go index 1809350..3937305 100644 --- a/cmd/database.dbaas.user.go +++ b/cmd/database.dbaas.user.go @@ -244,6 +244,11 @@ var dbaasUserListCmd = &cobra.Command{ } if resp != nil && resp.Data != nil && len(resp.Data.Values) > 0 { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } headers := []TableColumn{ {Header: "USERNAME", Width: 40}, {Header: "CREATION DATE", Width: 25}, @@ -269,7 +274,11 @@ var dbaasUserListCmd = &cobra.Command{ } rows = append(rows, row) } - PrintTable(headers, rows) + if err := RenderOutput(format, resp.Data.Values, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No users found") } diff --git a/cmd/management.project.go b/cmd/management.project.go index 5b0c796..0a1fcd4 100644 --- a/cmd/management.project.go +++ b/cmd/management.project.go @@ -36,6 +36,8 @@ func init() { // Add flags for project update command projectUpdateCmd.Flags().String("description", "", "New description for the project") projectUpdateCmd.Flags().StringSlice("tags", []string{}, "Tags for the project (comma-separated)") + + // Output format is provided globally via root persistent --format flag. } // completeProjectID provides completion for project IDs @@ -457,6 +459,12 @@ var projectListCmd = &cobra.Command{ Use: "list", Short: "List all projects", Run: func(cmd *cobra.Command, args []string) { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + // Get SDK client client, err := GetArubaClient() if err != nil { @@ -483,17 +491,25 @@ var projectListCmd = &cobra.Command{ return } - if response != nil && response.Data != nil && len(response.Data.Values) > 0 { - // Define table columns + var projects []types.ProjectResponse + if response != nil && response.Data != nil { + projects = response.Data.Values + } + + if err := RenderOutput(format, projects, func() { + if len(projects) == 0 { + fmt.Println("No projects found") + return + } + headers := []TableColumn{ {Header: "NAME", Width: 40}, {Header: "ID", Width: 30}, {Header: "CREATION DATE", Width: 15}, } - // Build rows var rows [][]string - for _, project := range response.Data.Values { + for _, project := range projects { name := "" if project.Metadata.Name != nil && *project.Metadata.Name != "" { name = *project.Metadata.Name @@ -504,7 +520,6 @@ var projectListCmd = &cobra.Command{ id = *project.Metadata.ID } - // Format creation date as dd-mm-yyyy creationDate := "N/A" if !project.Metadata.CreationDate.IsZero() { creationDate = project.Metadata.CreationDate.Format("02-01-2006") @@ -513,10 +528,9 @@ var projectListCmd = &cobra.Command{ rows = append(rows, []string{name, id, creationDate}) } - // Print the table PrintTable(headers, rows) - } else { - fmt.Println("No projects found") + }); err != nil { + fmt.Println(err.Error()) } }, } diff --git a/cmd/network.elasticip.go b/cmd/network.elasticip.go index ff9a6b1..47b70f3 100644 --- a/cmd/network.elasticip.go +++ b/cmd/network.elasticip.go @@ -179,6 +179,12 @@ var elasticipListCmd = &cobra.Command{ Use: "list", Short: "List all Elastic IPs", Run: func(cmd *cobra.Command, args []string) { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + // Get project ID from flag or context projectID, err := GetProjectID(cmd) if err != nil { @@ -240,7 +246,11 @@ var elasticipListCmd = &cobra.Command{ } // Print the table - PrintTable(headers, rows) + if err := RenderOutput(format, response.Data.Values, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No Elastic IPs found") } diff --git a/cmd/network.loadbalancer.go b/cmd/network.loadbalancer.go index 51a8d15..65be68a 100644 --- a/cmd/network.loadbalancer.go +++ b/cmd/network.loadbalancer.go @@ -71,6 +71,12 @@ var loadbalancerListCmd = &cobra.Command{ Use: "list", Short: "List all Load Balancers", Run: func(cmd *cobra.Command, args []string) { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + // Get project ID from flag or context projectID, err := GetProjectID(cmd) if err != nil { @@ -132,7 +138,11 @@ var loadbalancerListCmd = &cobra.Command{ } // Print the table - PrintTable(headers, rows) + if err := RenderOutput(format, response.Data.Values, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No Load Balancers found") } diff --git a/cmd/network.securitygroup.go b/cmd/network.securitygroup.go index 9edbdf4..03ad5cf 100644 --- a/cmd/network.securitygroup.go +++ b/cmd/network.securitygroup.go @@ -44,6 +44,11 @@ var securitygroupCreateCmd = &cobra.Command{ fmt.Println("Error: --name and --region are required") return } + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } projectID, err := GetProjectID(cmd) if err != nil { fmt.Printf("Error: %v\n", err) @@ -95,7 +100,11 @@ var securitygroupCreateCmd = &cobra.Command{ } }(), } - PrintTable(headers, [][]string{row}) + if err := RenderOutput(format, resp.Data, func() { + PrintTable(headers, [][]string{row}) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("Security group created, but no ID returned.") } @@ -177,6 +186,11 @@ var securitygroupListCmd = &cobra.Command{ Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { vpcID := args[0] + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } projectID, err := GetProjectID(cmd) if err != nil { fmt.Printf("Error: %v\n", err) @@ -230,7 +244,11 @@ var securitygroupListCmd = &cobra.Command{ } rows = append(rows, []string{name, id, region, status}) } - PrintTable(headers, rows) + if err := RenderOutput(format, resp.Data.Values, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No security groups found.") } @@ -321,6 +339,11 @@ var securitygroupUpdateCmd = &cobra.Command{ return } if resp != nil && resp.Data != nil { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } headers := []TableColumn{ {Header: "NAME", Width: 30}, {Header: "ID", Width: 26}, @@ -348,7 +371,11 @@ var securitygroupUpdateCmd = &cobra.Command{ return "" }(), } - PrintTable(headers, [][]string{row}) + if err := RenderOutput(format, resp.Data, func() { + PrintTable(headers, [][]string{row}) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Printf("Security group '%s' updated.\n", sgID) } diff --git a/cmd/network.securityrule.go b/cmd/network.securityrule.go index b5366ee..37965d4 100644 --- a/cmd/network.securityrule.go +++ b/cmd/network.securityrule.go @@ -116,6 +116,12 @@ var securityruleCreateCmd = &cobra.Command{ targetValue, _ := cmd.Flags().GetString("target-value") verbose, _ := cmd.Flags().GetBool("verbose") + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + // Validate required fields if name == "" { fmt.Println("Error: --name is required") @@ -235,7 +241,11 @@ var securityruleCreateCmd = &cobra.Command{ return "" }(), } - PrintTable(headers, [][]string{row}) + if err := RenderOutput(format, resp.Data, func() { + PrintTable(headers, [][]string{row}) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("Security rule created, but no ID returned.") } @@ -332,6 +342,12 @@ var securityruleListCmd = &cobra.Command{ vpcID := args[0] securityGroupID := args[1] + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + projectID, err := GetProjectID(cmd) if err != nil { fmt.Printf("Error: %v\n", err) @@ -395,7 +411,11 @@ var securityruleListCmd = &cobra.Command{ } rows = append(rows, []string{name, id, direction, protocol, port, target, status}) } - PrintTable(headers, rows) + if err := RenderOutput(format, resp.Data.Values, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No security rules found.") } @@ -569,6 +589,11 @@ var securityruleUpdateCmd = &cobra.Command{ } if resp != nil && resp.Data != nil { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } headers := []TableColumn{ {Header: "NAME", Width: 30}, {Header: "ID", Width: 26}, @@ -600,7 +625,11 @@ var securityruleUpdateCmd = &cobra.Command{ return "" }(), } - PrintTable(headers, [][]string{row}) + if err := RenderOutput(format, resp.Data, func() { + PrintTable(headers, [][]string{row}) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Printf("Security rule '%s' updated.\n", securityRuleID) } diff --git a/cmd/network.subnet.go b/cmd/network.subnet.go index 758d5f9..25bdfde 100644 --- a/cmd/network.subnet.go +++ b/cmd/network.subnet.go @@ -161,6 +161,11 @@ var subnetCreateCmd = &cobra.Command{ return } if resp != nil && resp.Data != nil && resp.Data.Metadata.ID != nil { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } headers := []TableColumn{ {Header: "NAME", Width: 30}, {Header: "ID", Width: 26}, @@ -190,7 +195,11 @@ var subnetCreateCmd = &cobra.Command{ } }(), } - PrintTable(headers, [][]string{row}) + if err := RenderOutput(format, resp.Data, func() { + PrintTable(headers, [][]string{row}) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("Subnet created, but no ID returned.") } @@ -318,6 +327,11 @@ var subnetListCmd = &cobra.Command{ return } if resp != nil && resp.Data != nil && len(resp.Data.Values) > 0 { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } headers := []TableColumn{ {Header: "NAME", Width: 30}, {Header: "ID", Width: 26}, @@ -346,7 +360,11 @@ var subnetListCmd = &cobra.Command{ } rows = append(rows, []string{name, id, region, cidr, status}) } - PrintTable(headers, rows) + if err := RenderOutput(format, resp.Data.Values, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No subnets found.") } @@ -515,6 +533,11 @@ var subnetUpdateCmd = &cobra.Command{ return } if resp != nil && resp.Data != nil { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } headers := []TableColumn{ {Header: "NAME", Width: 30}, {Header: "ID", Width: 26}, @@ -537,7 +560,11 @@ var subnetUpdateCmd = &cobra.Command{ if resp.Data.Status.State != nil { status = *resp.Data.Status.State } - PrintTable(headers, [][]string{{name, id, cidr, status}}) + if err := RenderOutput(format, resp.Data, func() { + PrintTable(headers, [][]string{{name, id, cidr, status}}) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Printf("Subnet '%s' updated.\n", subnetID) } diff --git a/cmd/network.vpc.go b/cmd/network.vpc.go index 3db3d32..56c0c03 100644 --- a/cmd/network.vpc.go +++ b/cmd/network.vpc.go @@ -420,6 +420,12 @@ var vpcListCmd = &cobra.Command{ Use: "list", Short: "List all VPCs", Run: func(cmd *cobra.Command, args []string) { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + // Get SDK client client, err := GetArubaClient() if err != nil { @@ -478,7 +484,11 @@ var vpcListCmd = &cobra.Command{ } // Print the table - PrintTable(headers, rows) + if err := RenderOutput(format, response.Data.Values, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No VPCs found") } diff --git a/cmd/network.vpcpeering.go b/cmd/network.vpcpeering.go index 7af6f81..02f0536 100644 --- a/cmd/network.vpcpeering.go +++ b/cmd/network.vpcpeering.go @@ -58,6 +58,11 @@ var vpcpeeringCreateCmd = &cobra.Command{ fmt.Println("Error: --name, --peer-vpc-id, and --region are required") return } + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } projectID, err := GetProjectID(cmd) if err != nil { fmt.Printf("Error: %v\n", err) @@ -127,7 +132,11 @@ var vpcpeeringCreateCmd = &cobra.Command{ } }(), } - PrintTable(headers, [][]string{row}) + if err := RenderOutput(format, resp.Data, func() { + PrintTable(headers, [][]string{row}) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("VPC peering created, but no ID returned.") } @@ -209,6 +218,11 @@ var vpcpeeringListCmd = &cobra.Command{ Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { vpcID := args[0] + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } projectID, err := GetProjectID(cmd) if err != nil { fmt.Printf("Error: %v\n", err) @@ -267,7 +281,11 @@ var vpcpeeringListCmd = &cobra.Command{ } rows = append(rows, []string{name, id, peerVPC, region, status}) } - PrintTable(headers, rows) + if err := RenderOutput(format, resp.Data.Values, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No VPC peerings found.") } @@ -362,6 +380,11 @@ var vpcpeeringUpdateCmd = &cobra.Command{ return } if resp != nil && resp.Data != nil { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } headers := []TableColumn{ {Header: "NAME", Width: 30}, {Header: "ID", Width: 26}, @@ -401,7 +424,11 @@ var vpcpeeringUpdateCmd = &cobra.Command{ return "" }(), } - PrintTable(headers, [][]string{row}) + if err := RenderOutput(format, resp.Data, func() { + PrintTable(headers, [][]string{row}) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Printf("VPC peering '%s' updated.\n", peeringID) } diff --git a/cmd/network.vpcpeeringroute.go b/cmd/network.vpcpeeringroute.go index a18c222..7bb3c79 100644 --- a/cmd/network.vpcpeeringroute.go +++ b/cmd/network.vpcpeeringroute.go @@ -119,6 +119,12 @@ var vpcpeeringrouteCreateCmd = &cobra.Command{ return } + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + projectID, err := GetProjectID(cmd) if err != nil { fmt.Printf("Error: %v\n", err) @@ -195,7 +201,11 @@ var vpcpeeringrouteCreateCmd = &cobra.Command{ return "" }(), } - PrintTable(headers, [][]string{row}) + if err := RenderOutput(format, resp.Data, func() { + PrintTable(headers, [][]string{row}) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("VPC peering route created, but no data returned.") } @@ -274,6 +284,12 @@ var vpcpeeringrouteListCmd = &cobra.Command{ vpcID := args[0] peeringID := args[1] + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + projectID, err := GetProjectID(cmd) if err != nil { fmt.Printf("Error: %v\n", err) @@ -322,7 +338,11 @@ var vpcpeeringrouteListCmd = &cobra.Command{ } rows = append(rows, []string{name, localNetwork, remoteNetwork, status}) } - PrintTable(headers, rows) + if err := RenderOutput(format, resp.Data.Values, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No VPC peering routes found.") } @@ -440,6 +460,11 @@ var vpcpeeringrouteUpdateCmd = &cobra.Command{ } if resp != nil && resp.Data != nil { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } headers := []TableColumn{ {Header: "NAME", Width: 30}, {Header: "LOCAL NETWORK", Width: 18}, @@ -457,7 +482,11 @@ var vpcpeeringrouteUpdateCmd = &cobra.Command{ return "" }(), } - PrintTable(headers, [][]string{row}) + if err := RenderOutput(format, resp.Data, func() { + PrintTable(headers, [][]string{row}) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Printf("VPC peering route '%s' updated.\n", routeID) } diff --git a/cmd/network.vpnroute.go b/cmd/network.vpnroute.go index ea36505..840fc25 100644 --- a/cmd/network.vpnroute.go +++ b/cmd/network.vpnroute.go @@ -107,6 +107,12 @@ var vpnrouteCreateCmd = &cobra.Command{ tags, _ := cmd.Flags().GetStringSlice("tags") verbose, _ := cmd.Flags().GetBool("verbose") + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + // Validate required fields if name == "" { fmt.Println("Error: --name is required") @@ -205,7 +211,11 @@ var vpnrouteCreateCmd = &cobra.Command{ return "" }(), } - PrintTable(headers, [][]string{row}) + if err := RenderOutput(format, resp.Data, func() { + PrintTable(headers, [][]string{row}) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("VPN route created, but no ID returned.") } @@ -295,6 +305,12 @@ var vpnrouteListCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { vpnTunnelID := args[0] + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + projectID, err := GetProjectID(cmd) if err != nil { fmt.Printf("Error: %v\n", err) @@ -351,7 +367,11 @@ var vpnrouteListCmd = &cobra.Command{ } rows = append(rows, []string{name, id, cloudSubnet, onPremSubnet, status}) } - PrintTable(headers, rows) + if err := RenderOutput(format, resp.Data.Values, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No VPN routes found.") } @@ -477,6 +497,11 @@ var vpnrouteUpdateCmd = &cobra.Command{ } if resp != nil && resp.Data != nil { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } headers := []TableColumn{ {Header: "NAME", Width: 30}, {Header: "ID", Width: 26}, @@ -506,7 +531,11 @@ var vpnrouteUpdateCmd = &cobra.Command{ return "" }(), } - PrintTable(headers, [][]string{row}) + if err := RenderOutput(format, resp.Data, func() { + PrintTable(headers, [][]string{row}) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Printf("VPN route '%s' updated.\n", routeID) } diff --git a/cmd/network.vpntunnel.go b/cmd/network.vpntunnel.go index ee13e9d..84a04dc 100644 --- a/cmd/network.vpntunnel.go +++ b/cmd/network.vpntunnel.go @@ -111,6 +111,12 @@ var vpntunnelListCmd = &cobra.Command{ Use: "list", Short: "List all VPN tunnels", Run: func(cmd *cobra.Command, args []string) { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + // Get SDK client client, err := GetArubaClient() if err != nil { @@ -171,7 +177,11 @@ var vpntunnelListCmd = &cobra.Command{ } // Print the table - PrintTable(headers, rows) + if err := RenderOutput(format, response.Data.Values, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No VPN tunnels found") } diff --git a/cmd/root.go b/cmd/root.go index 10e31f6..c785988 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,12 +1,16 @@ package cmd import ( + "bytes" "fmt" "log" "os" + "strings" "sync" "github.com/Arubacloud/sdk-go/pkg/aruba" + "github.com/drewstinnett/gout/v2" + "github.com/drewstinnett/gout/v2/formats" "github.com/spf13/cobra" ) @@ -19,6 +23,13 @@ var ( cachedDebug bool cachedBaseURL string cachedTokenIssuer string + outputFormat string +) + +const ( + FormatTable = "table" + FormatJSON = "json" + FormatYAML = "yaml" ) // rootCmd represents the base command when called without any subcommands @@ -51,6 +62,72 @@ func init() { // Add global debug flag rootCmd.PersistentFlags().BoolP("debug", "d", false, "Enable debug logging (shows HTTP requests/responses)") + AddPersistentFormatFlag(rootCmd, FormatTable) +} + +// AddPersistentFormatFlag attaches a reusable persistent --format flag to a command. +func AddPersistentFormatFlag(cmd *cobra.Command, defaultFormat string) { + if strings.TrimSpace(defaultFormat) == "" { + defaultFormat = FormatTable + } + cmd.PersistentFlags().StringVar(&outputFormat, "format", defaultFormat, "Output format (table, json, yaml)") +} + +// GetOutputFormat reads, normalizes, and validates the --format flag. +func GetOutputFormat(cmd *cobra.Command) (string, error) { + format, _ := cmd.Flags().GetString("format") + format = strings.ToLower(strings.TrimSpace(format)) + if format == "" { + format = FormatTable + } + + switch format { + case FormatTable, FormatJSON, FormatYAML: + return format, nil + default: + return "", fmt.Errorf("unknown format: %s. Supported formats: table, json, yaml", format) + } +} + +// RenderOutput prints the provided data according to the selected format. +// tablePrinter is called only when format=table. +func RenderOutput(format string, data any, tablePrinter func()) error { + switch format { + case FormatJSON: + creator, ok := formats.Formats[FormatJSON] + if !ok { + return fmt.Errorf("json formatter not registered in gout") + } + var out bytes.Buffer + g := gout.New(gout.WithWriter(&out), gout.WithFormatter(creator())) + err := g.Print(data) + if err != nil { + return fmt.Errorf("error formatting JSON output: %v", err) + } + fmt.Print(out.String()) + return nil + case FormatYAML: + creator, ok := formats.Formats[FormatYAML] + if !ok { + return fmt.Errorf("yaml formatter not registered in gout") + } + var out bytes.Buffer + g := gout.New(gout.WithWriter(&out), gout.WithFormatter(creator())) + err := g.Print(data) + if err != nil { + return fmt.Errorf("error formatting YAML output: %v", err) + } + fmt.Print(out.String()) + return nil + case FormatTable: + if tablePrinter == nil { + return fmt.Errorf("table output requires a table printer") + } + tablePrinter() + return nil + default: + return fmt.Errorf("unknown format: %s. Supported formats: table, json, yaml", format) + } } // GetArubaClient creates and returns an Aruba Cloud SDK client using stored credentials @@ -164,26 +241,48 @@ type TableColumn struct { // headers: slice of TableColumn defining each column // rows: slice of string slices, each inner slice represents a row func PrintTable(headers []TableColumn, rows [][]string) { - // Print header row - formatStr := "" - headerValues := make([]interface{}, len(headers)) - for i, col := range headers { - formatStr += fmt.Sprintf("%%-%ds ", col.Width) - headerValues[i] = col.Header + format, err := GetOutputFormat(rootCmd) + if err != nil { + fmt.Println(err.Error()) + return } - formatStr += "\n" - fmt.Printf(formatStr, headerValues...) - // Print data rows + data := make([]map[string]string, 0, len(rows)) for _, row := range rows { - rowValues := make([]interface{}, len(row)) - for i, val := range row { - // Truncate if value is too long - if len(headers) > i && len(val) > headers[i].Width { - val = val[:headers[i].Width-3] + "..." + entry := map[string]string{} + for i, col := range headers { + key := strings.ToLower(strings.ReplaceAll(strings.TrimSpace(col.Header), " ", "_")) + value := "" + if i < len(row) { + value = row[i] } - rowValues[i] = val + entry[key] = value } - fmt.Printf(formatStr, rowValues...) + data = append(data, entry) + } + + err = RenderOutput(format, data, func() { + formatStr := "" + headerValues := make([]interface{}, len(headers)) + for i, col := range headers { + formatStr += fmt.Sprintf("%%-%ds ", col.Width) + headerValues[i] = col.Header + } + formatStr += "\n" + fmt.Printf(formatStr, headerValues...) + + for _, row := range rows { + rowValues := make([]interface{}, len(row)) + for i, val := range row { + if len(headers) > i && len(val) > headers[i].Width { + val = val[:headers[i].Width-3] + "..." + } + rowValues[i] = val + } + fmt.Printf(formatStr, rowValues...) + } + }) + if err != nil { + fmt.Println(err.Error()) } } diff --git a/cmd/schedule.job.go b/cmd/schedule.job.go index 016a51b..e4d52fc 100644 --- a/cmd/schedule.job.go +++ b/cmd/schedule.job.go @@ -187,6 +187,11 @@ var jobCreateCmd = &cobra.Command{ } if response != nil && response.Data != nil { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } headers := []TableColumn{ {Header: "ID", Width: 30}, {Header: "NAME", Width: 40}, @@ -226,7 +231,11 @@ var jobCreateCmd = &cobra.Command{ return "" }(), } - PrintTable(headers, [][]string{row}) + if err := RenderOutput(format, response.Data, func() { + PrintTable(headers, [][]string{row}) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("Job created, but no data returned.") } @@ -324,6 +333,12 @@ var jobListCmd = &cobra.Command{ Use: "list", Short: "List all scheduled jobs", Run: func(cmd *cobra.Command, args []string) { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + projectID, err := GetProjectID(cmd) if err != nil { fmt.Printf("Error: %v\n", err) @@ -406,7 +421,11 @@ var jobListCmd = &cobra.Command{ } rows = append(rows, row) } - PrintTable(headers, rows) + if err := RenderOutput(format, resp.Data.Values, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No jobs found") } diff --git a/cmd/security.kms.go b/cmd/security.kms.go index 0c5352c..84cf483 100644 --- a/cmd/security.kms.go +++ b/cmd/security.kms.go @@ -144,6 +144,11 @@ var kmsCreateCmd = &cobra.Command{ } if response != nil && response.Data != nil { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } headers := []TableColumn{ {Header: "ID", Width: 30}, {Header: "NAME", Width: 40}, @@ -176,7 +181,11 @@ var kmsCreateCmd = &cobra.Command{ return "" }(), } - PrintTable(headers, [][]string{row}) + if err := RenderOutput(format, response.Data, func() { + PrintTable(headers, [][]string{row}) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("KMS created, but no data returned.") } @@ -263,6 +272,12 @@ var kmsListCmd = &cobra.Command{ Use: "list", Short: "List all KMS resources", Run: func(cmd *cobra.Command, args []string) { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + projectID, err := GetProjectID(cmd) if err != nil { fmt.Printf("Error: %v\n", err) @@ -331,7 +346,11 @@ var kmsListCmd = &cobra.Command{ } rows = append(rows, row) } - PrintTable(headers, rows) + if err := RenderOutput(format, resp.Data.Values, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No KMS resources found") } diff --git a/cmd/storage.backup.go b/cmd/storage.backup.go index d80ab30..f9c5cc9 100644 --- a/cmd/storage.backup.go +++ b/cmd/storage.backup.go @@ -229,6 +229,12 @@ var storageBackupListCmd = &cobra.Command{ Use: "list", Short: "List storage backups", Run: func(cmd *cobra.Command, args []string) { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + projectID, err := GetProjectID(cmd) if err != nil { fmt.Printf("Error: %v\n", err) @@ -278,7 +284,11 @@ var storageBackupListCmd = &cobra.Command{ rows = append(rows, []string{name, id, backupType, status}) } - PrintTable(headers, rows) + if err := RenderOutput(format, response.Data.Values, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No backups found") } diff --git a/cmd/storage.blockstorage.go b/cmd/storage.blockstorage.go index 90d97f4..4267a53 100644 --- a/cmd/storage.blockstorage.go +++ b/cmd/storage.blockstorage.go @@ -528,6 +528,12 @@ var blockstorageListCmd = &cobra.Command{ // Get flags verbose, _ := cmd.Flags().GetBool("verbose") + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + // Get SDK client client, err := GetArubaClient() if err != nil { @@ -623,8 +629,11 @@ var blockstorageListCmd = &cobra.Command{ rows = append(rows, []string{name, id, size, region, zone, volumeType, status}) } - // Print the table - PrintTable(headers, rows) + if err := RenderOutput(format, response.Data.Values, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No block storage found") } diff --git a/cmd/storage.restore.go b/cmd/storage.restore.go index 7ac61ae..a1788f6 100644 --- a/cmd/storage.restore.go +++ b/cmd/storage.restore.go @@ -239,6 +239,12 @@ var storageRestoreListCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { backupID := args[0] + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } + projectID, err := GetProjectID(cmd) if err != nil { fmt.Printf("Error: %v\n", err) @@ -285,7 +291,11 @@ var storageRestoreListCmd = &cobra.Command{ rows = append(rows, []string{name, id, status}) } - PrintTable(headers, rows) + if err := RenderOutput(format, response.Data.Values, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No restores found for this backup") } diff --git a/cmd/storage.snapshot.go b/cmd/storage.snapshot.go index 4ea3e6d..ba80066 100644 --- a/cmd/storage.snapshot.go +++ b/cmd/storage.snapshot.go @@ -476,6 +476,11 @@ var snapshotListCmd = &cobra.Command{ } if response != nil && response.Data != nil && len(response.Data.Values) > 0 { + format, err := GetOutputFormat(cmd) + if err != nil { + fmt.Println(err.Error()) + return + } // Filter snapshots by volume URI var filteredSnapshots []types.SnapshotResponse for _, snapshot := range response.Data.Values { @@ -521,7 +526,11 @@ var snapshotListCmd = &cobra.Command{ } // Print the table - PrintTable(headers, rows) + if err := RenderOutput(format, filteredSnapshots, func() { + PrintTable(headers, rows) + }); err != nil { + fmt.Println(err.Error()) + } } else { fmt.Println("No snapshots found") } diff --git a/docs/website/docs/resources/management/project.md b/docs/website/docs/resources/management/project.md index 4ea55ba..7240840 100644 --- a/docs/website/docs/resources/management/project.md +++ b/docs/website/docs/resources/management/project.md @@ -37,6 +37,9 @@ Display all projects in a table format. acloud management project list ``` +**Optional Flags:** +- `--format `: Desired output format for the list (table/json) + **Output:** ``` ID NAME CREATION DATE diff --git a/e2e/compute/cloudserver_list.json b/e2e/compute/cloudserver_list.json new file mode 100644 index 0000000..00b3bb8 --- /dev/null +++ b/e2e/compute/cloudserver_list.json @@ -0,0 +1,25 @@ +[ + { + "metadata": { + "id": "xxxxxxxxxxxxxxxxxxxxxxxx", + "name": "test-server", + "locationResponse": { + "value": "ITBG-Bergamo" + } + }, + "properties": { + "dataCenter": "itbg1-a", + "flavor": { + "id": "xxxxxxxxxxxxxxxxxxxxxxxx", + "name": "small", + "category": "compute", + "cpu": 2, + "ram": 4096, + "hd": 50 + } + }, + "status": { + "state": "Running" + } + } +] diff --git a/e2e/compute/cloudserver_list.yaml b/e2e/compute/cloudserver_list.yaml new file mode 100644 index 0000000..31dfac1 --- /dev/null +++ b/e2e/compute/cloudserver_list.yaml @@ -0,0 +1,16 @@ +- metadata: + id: xxxxxxxxxxxxxxxxxxxxxxxx + name: test-server + locationresponse: + value: ITBG-Bergamo + properties: + datacenter: itbg1-a + flavor: + id: xxxxxxxxxxxxxxxxxxxxxxxx + name: small + category: compute + cpu: 2 + ram: 4096 + hd: 50 + status: + state: Running diff --git a/e2e/compute/keypair_list.json b/e2e/compute/keypair_list.json new file mode 100644 index 0000000..1a693a8 --- /dev/null +++ b/e2e/compute/keypair_list.json @@ -0,0 +1,11 @@ +[ + { + "metadata": { + "id": "xxxxxxxxxxxxxxxxxxxxxxxx", + "name": "test-keypair" + }, + "properties": { + "value": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC... test@example.com" + } + } +] diff --git a/e2e/compute/keypair_list.yaml b/e2e/compute/keypair_list.yaml new file mode 100644 index 0000000..077cad3 --- /dev/null +++ b/e2e/compute/keypair_list.yaml @@ -0,0 +1,5 @@ +- metadata: + id: xxxxxxxxxxxxxxxxxxxxxxxx + name: test-keypair + properties: + value: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC... test@example.com diff --git a/e2e/compute/test.sh b/e2e/compute/test.sh old mode 100644 new mode 100755 index 8f633cb..ebae501 --- a/e2e/compute/test.sh +++ b/e2e/compute/test.sh @@ -48,11 +48,108 @@ extract_id() { echo "$output" | grep -oE '[a-f0-9]{24}' | tail -1 || echo "" } +# Function to check if a string is valid JSON +is_valid_json() { + local input="$1" + if command -v python3 >/dev/null 2>&1; then + echo "$input" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null && return 0 + elif command -v python >/dev/null 2>&1; then + echo "$input" | python -c "import sys,json; json.load(sys.stdin)" 2>/dev/null && return 0 + fi + return 1 +} + +# Generic --format flag test helper +# Usage: test_format_flags "Label" "no resources msg" cmd arg1 arg2... +test_format_flags() { + local label="$1" + local no_resources_msg="$2" + shift 2 + local cmd_args=("$@") + + echo -e "${BLUE}--- Testing $label --format flag ---${NC}" + + for fmt in "" table; do + local lbl="--format \"$fmt\"" + [ -z "$fmt" ] && lbl='--format "" (default)' + echo -e "${YELLOW}Testing $lbl...${NC}" + if [ -z "$fmt" ]; then + OUT=$($ACLOUD_CMD "${cmd_args[@]}" 2>&1) + else + OUT=$($ACLOUD_CMD "${cmd_args[@]}" --format "$fmt" 2>&1) + fi + EXIT=$? + if [ $EXIT -eq 0 ]; then + echo -e "${GREEN}✓ $lbl: command succeeded${NC}" + if ! is_valid_json "$OUT" || echo "$OUT" | grep -qF "$no_resources_msg"; then + echo -e "${GREEN}✓ $lbl: output is table/plain (not JSON)${NC}" + else + echo -e "${RED}✗ $lbl: output unexpectedly looks like JSON${NC}" + fi + else + echo -e "${RED}✗ $lbl: command failed (exit $EXIT)${NC}" + echo "$OUT" + fi + done + + echo -e "${YELLOW}Testing --format json...${NC}" + JSON_OUTPUT=$($ACLOUD_CMD "${cmd_args[@]}" --format json 2>&1) + JSON_EXIT=$? + if [ $JSON_EXIT -ne 0 ]; then + echo -e "${RED}✗ --format json: command failed (exit $JSON_EXIT)${NC}" + echo "$JSON_OUTPUT" + elif echo "$JSON_OUTPUT" | grep -qF "$no_resources_msg"; then + echo -e "${YELLOW}⚠ --format json: no resources — format validation skipped${NC}" + elif is_valid_json "$JSON_OUTPUT"; then + echo -e "${GREEN}✓ --format json: valid JSON${NC}" + if echo "$JSON_OUTPUT" | grep -q '"metadata"'; then + echo -e "${GREEN}✓ --format json: 'metadata' key present${NC}" + else + echo -e "${RED}✗ --format json: 'metadata' key missing${NC}" + fi + if echo "$JSON_OUTPUT" | grep -q '"properties"'; then + echo -e "${GREEN}✓ --format json: 'properties' key present${NC}" + else + echo -e "${RED}✗ --format json: 'properties' key missing${NC}" + fi + else + echo -e "${RED}✗ --format json: output is not valid JSON${NC}" + echo "$JSON_OUTPUT" + fi + + echo -e "${YELLOW}Testing --format yaml...${NC}" + YAML_OUTPUT=$($ACLOUD_CMD "${cmd_args[@]}" --format yaml 2>&1) + YAML_EXIT=$? + if [ $YAML_EXIT -ne 0 ]; then + echo -e "${RED}✗ --format yaml: command failed (exit $YAML_EXIT)${NC}" + echo "$YAML_OUTPUT" + elif echo "$YAML_OUTPUT" | grep -qF "$no_resources_msg"; then + echo -e "${YELLOW}⚠ --format yaml: no resources — format validation skipped${NC}" + elif echo "$YAML_OUTPUT" | grep -qE '^[a-zA-Z].*:|^- '; then + echo -e "${GREEN}✓ --format yaml: output looks like YAML${NC}" + if echo "$YAML_OUTPUT" | grep -q 'metadata:'; then + echo -e "${GREEN}✓ --format yaml: 'metadata' key present${NC}" + else + echo -e "${RED}✗ --format yaml: 'metadata' key missing${NC}" + fi + if echo "$YAML_OUTPUT" | grep -q 'properties:'; then + echo -e "${GREEN}✓ --format yaml: 'properties' key present${NC}" + else + echo -e "${RED}✗ --format yaml: 'properties' key missing${NC}" + fi + else + echo -e "${RED}✗ --format yaml: output does not look like YAML${NC}" + echo "$YAML_OUTPUT" + fi + + echo "" +} + # Function to test Cloud Server operations test_cloudserver() { local server_name="${RESOURCE_PREFIX}-server" local flavor="${ACLOUD_FLAVOR:-small}" - local image="${ACLOUD_IMAGE:-your-image-id}" + local image="${ACLOUD_IMAGE:-LU24-DK01}" echo -e "${BLUE}--- Testing Cloud Server Operations ---${NC}" @@ -257,6 +354,8 @@ trap cleanup EXIT # Run tests test_cloudserver test_keypair +test_format_flags "Cloud Server list" "No cloud servers found" compute cloudserver list --project-id "$PROJECT_ID" +test_format_flags "Key Pair list" "No keypairs found" compute keypair list --project-id "$PROJECT_ID" # Summary echo -e "${BLUE}=== Test Summary ===${NC}" diff --git a/e2e/container/test.sh b/e2e/container/test.sh old mode 100644 new mode 100755 index ed83b2c..fa3a63c --- a/e2e/container/test.sh +++ b/e2e/container/test.sh @@ -67,6 +67,102 @@ extract_id() { echo "$output" | grep -oE '[a-f0-9]{24}' | tail -1 || echo "" } +# Check if string is valid JSON +is_valid_json() { + local input="$1" + if command -v python3 >/dev/null 2>&1; then + echo "$input" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null && return 0 + elif command -v python >/dev/null 2>&1; then + echo "$input" | python -c "import sys,json; json.load(sys.stdin)" 2>/dev/null && return 0 + fi + return 1 +} + +# Generic format test helper: test_format_flags