mygithub.libinneed.workers.dev/stackitcloud/stackit-cli
Advanced tools
| ## stackit beta sqlserverflex instance create | ||
| Creates an SQLServer Flex instance | ||
| ### Synopsis | ||
| Creates an SQLServer Flex instance. | ||
| ``` | ||
| stackit beta sqlserverflex instance create [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Create an SQLServer Flex instance with name "my-instance" and specify flavor by CPU and RAM. Other parameters are set to default values | ||
| $ stackit beta sqlserverflex instance create --name my-instance --cpu 1 --ram 4 | ||
| Create an SQLServer Flex instance with name "my-instance" and specify flavor by ID. Other parameters are set to default values | ||
| $ stackit beta sqlserverflex instance create --name my-instance --flavor-id xxx | ||
| Create an SQLServer Flex instance with name "my-instance", specify flavor by CPU and RAM, set storage size to 20 GB, and restrict access to a specific range of IP addresses. Other parameters are set to default values | ||
| $ stackit beta sqlserverflex instance create --name my-instance --cpu 1 --ram 4 --storage-size 20 --acl 1.2.3.0/24 | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| --acl strings The access control list (ACL). Must contain at least one valid subnet, for instance '0.0.0.0/0' for open access (discouraged), '1.2.3.0/24 for a public IP range of an organization, '1.2.3.4/32' for a single IP range, etc. (default []) | ||
| --backup-schedule string Backup schedule | ||
| --cpu int Number of CPUs | ||
| --edition string Edition of the SQLServer instance | ||
| --flavor-id string ID of the flavor | ||
| -h, --help Help for "stackit beta sqlserverflex instance create" | ||
| -n, --name string Instance name | ||
| --ram int Amount of RAM (in GB) | ||
| --retention-days int The days for how long the backup files should be stored before being cleaned up | ||
| --storage-class string Storage class | ||
| --storage-size int Storage size (in GB) | ||
| --version string SQLServer version | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta sqlserverflex instance](./stackit_beta_sqlserverflex_instance.md) - Provides functionality for SQLServer Flex instances | ||
| ## stackit beta sqlserverflex instance delete | ||
| Deletes an SQLServer Flex instance | ||
| ### Synopsis | ||
| Deletes an SQLServer Flex instance. | ||
| ``` | ||
| stackit beta sqlserverflex instance delete INSTANCE_ID [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Delete an SQLServer Flex instance with ID "xxx" | ||
| $ stackit beta sqlserverflex instance delete xxx | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta sqlserverflex instance delete" | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta sqlserverflex instance](./stackit_beta_sqlserverflex_instance.md) - Provides functionality for SQLServer Flex instances | ||
| ## stackit beta sqlserverflex instance describe | ||
| Shows details of an SQLServer Flex instance | ||
| ### Synopsis | ||
| Shows details of an SQLServer Flex instance. | ||
| ``` | ||
| stackit beta sqlserverflex instance describe INSTANCE_ID [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Get details of an SQLServer Flex instance with ID "xxx" | ||
| $ stackit beta sqlserverflex instance describe xxx | ||
| Get details of an SQLServer Flex instance with ID "xxx" in JSON format | ||
| $ stackit beta sqlserverflex instance describe xxx --output-format json | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta sqlserverflex instance describe" | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta sqlserverflex instance](./stackit_beta_sqlserverflex_instance.md) - Provides functionality for SQLServer Flex instances | ||
| ## stackit beta sqlserverflex instance list | ||
| Lists all SQLServer Flex instances | ||
| ### Synopsis | ||
| Lists all SQLServer Flex instances. | ||
| ``` | ||
| stackit beta sqlserverflex instance list [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| List all SQLServer Flex instances | ||
| $ stackit beta sqlserverflex instance list | ||
| List all SQLServer Flex instances in JSON format | ||
| $ stackit beta sqlserverflex instance list --output-format json | ||
| List up to 10 SQLServer Flex instances | ||
| $ stackit beta sqlserverflex instance list --limit 10 | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta sqlserverflex instance list" | ||
| --limit int Maximum number of entries to list | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta sqlserverflex instance](./stackit_beta_sqlserverflex_instance.md) - Provides functionality for SQLServer Flex instances | ||
| ## stackit beta sqlserverflex instance update | ||
| Updates an SQLServer Flex instance | ||
| ### Synopsis | ||
| Updates an SQLServer Flex instance. | ||
| ``` | ||
| stackit beta sqlserverflex instance update INSTANCE_ID [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Update the name of an SQLServer Flex instance with ID "xxx" | ||
| $ stackit beta sqlserverflex instance update xxx --name my-new-name | ||
| Update the backup schedule of an SQLServer Flex instance with ID "xxx" | ||
| $ stackit beta sqlserverflex instance update xxx --backup-schedule "30 0 * * *" | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| --acl strings Lists of IP networks in CIDR notation which are allowed to access this instance (default []) | ||
| --backup-schedule string Backup schedule | ||
| --cpu int Number of CPUs | ||
| --flavor-id string ID of the flavor | ||
| -h, --help Help for "stackit beta sqlserverflex instance update" | ||
| -n, --name string Instance name | ||
| --ram int Amount of RAM (in GB) | ||
| --version string Version | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta sqlserverflex instance](./stackit_beta_sqlserverflex_instance.md) - Provides functionality for SQLServer Flex instances | ||
| ## stackit beta sqlserverflex instance | ||
| Provides functionality for SQLServer Flex instances | ||
| ### Synopsis | ||
| Provides functionality for SQLServer Flex instances. | ||
| ``` | ||
| stackit beta sqlserverflex instance [flags] | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta sqlserverflex instance" | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta sqlserverflex](./stackit_beta_sqlserverflex.md) - Provides functionality for SQLServer Flex | ||
| * [stackit beta sqlserverflex instance create](./stackit_beta_sqlserverflex_instance_create.md) - Creates an SQLServer Flex instance | ||
| * [stackit beta sqlserverflex instance delete](./stackit_beta_sqlserverflex_instance_delete.md) - Deletes an SQLServer Flex instance | ||
| * [stackit beta sqlserverflex instance describe](./stackit_beta_sqlserverflex_instance_describe.md) - Shows details of an SQLServer Flex instance | ||
| * [stackit beta sqlserverflex instance list](./stackit_beta_sqlserverflex_instance_list.md) - Lists all SQLServer Flex instances | ||
| * [stackit beta sqlserverflex instance update](./stackit_beta_sqlserverflex_instance_update.md) - Updates an SQLServer Flex instance | ||
| ## stackit beta sqlserverflex options | ||
| Lists SQL Server Flex options | ||
| ### Synopsis | ||
| Lists SQL Server Flex options (flavors, versions and storages for a given flavor) | ||
| Pass one or more flags to filter what categories are shown. | ||
| ``` | ||
| stackit beta sqlserverflex options [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| List SQL Server Flex flavors options | ||
| $ stackit sqlserverflex options --flavors | ||
| List SQL Server Flex available versions | ||
| $ stackit sqlserverflex options --versions | ||
| List SQL Server Flex storage options for a given flavor. The flavor ID can be retrieved by running "$ stackit sqlserverflex options --flavors" | ||
| $ stackit sqlserverflex options --storages --flavor-id <FLAVOR_ID> | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| --flavor-id string The flavor ID to show storages for. Only relevant when "--storages" is passed | ||
| --flavors Lists supported flavors | ||
| -h, --help Help for "stackit beta sqlserverflex options" | ||
| --storages Lists supported storages for a given flavor | ||
| --versions Lists supported versions | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta sqlserverflex](./stackit_beta_sqlserverflex.md) - Provides functionality for SQLServer Flex | ||
| ## stackit beta sqlserverflex | ||
| Provides functionality for SQLServer Flex | ||
| ### Synopsis | ||
| Provides functionality for SQLServer Flex. | ||
| ``` | ||
| stackit beta sqlserverflex [flags] | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta sqlserverflex" | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta](./stackit_beta.md) - Contains beta STACKIT CLI commands | ||
| * [stackit beta sqlserverflex instance](./stackit_beta_sqlserverflex_instance.md) - Provides functionality for SQLServer Flex instances | ||
| * [stackit beta sqlserverflex options](./stackit_beta_sqlserverflex_options.md) - Lists SQL Server Flex options | ||
| ## stackit beta | ||
| Contains beta STACKIT CLI commands | ||
| ### Synopsis | ||
| Contains beta STACKIT CLI commands. | ||
| The commands under this group are still in a beta state, and functionality may be incomplete or have breaking changes. | ||
| ``` | ||
| stackit beta [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| See the currently available beta commands | ||
| $ stackit beta --help | ||
| Execute a beta command | ||
| $ stackit beta MY_COMMAND | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta" | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit](./stackit.md) - Manage STACKIT resources using the command line | ||
| * [stackit beta sqlserverflex](./stackit_beta_sqlserverflex.md) - Provides functionality for SQLServer Flex | ||
| package beta | ||
| import ( | ||
| "fmt" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "beta", | ||
| Short: "Contains beta STACKIT CLI commands", | ||
| Long: fmt.Sprintf("%s\n%s", | ||
| "Contains beta STACKIT CLI commands.", | ||
| "The commands under this group are still in a beta state, and functionality may be incomplete or have breaking changes."), | ||
| Args: args.NoArgs, | ||
| Run: utils.CmdHelp, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| "See the currently available beta commands", | ||
| "$ stackit beta --help"), | ||
| examples.NewExample( | ||
| "Execute a beta command", | ||
| "$ stackit beta MY_COMMAND"), | ||
| ), | ||
| } | ||
| addSubcommands(cmd, p) | ||
| return cmd | ||
| } | ||
| func addSubcommands(cmd *cobra.Command, p *print.Printer) { | ||
| cmd.AddCommand(sqlserverflex.NewCmd(p)) | ||
| } |
| package create | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &sqlserverflex.APIClient{} | ||
| type sqlServerFlexClientMocked struct { | ||
| listFlavorsFails bool | ||
| listFlavorsResp *sqlserverflex.ListFlavorsResponse | ||
| listStoragesFails bool | ||
| listStoragesResp *sqlserverflex.ListStoragesResponse | ||
| } | ||
| func (c *sqlServerFlexClientMocked) CreateInstance(ctx context.Context, projectId string) sqlserverflex.ApiCreateInstanceRequest { | ||
| return testClient.CreateInstance(ctx, projectId) | ||
| } | ||
| func (c *sqlServerFlexClientMocked) ListStoragesExecute(_ context.Context, _, _ string) (*sqlserverflex.ListStoragesResponse, error) { | ||
| if c.listFlavorsFails { | ||
| return nil, fmt.Errorf("list storages failed") | ||
| } | ||
| return c.listStoragesResp, nil | ||
| } | ||
| func (c *sqlServerFlexClientMocked) ListFlavorsExecute(_ context.Context, _ string) (*sqlserverflex.ListFlavorsResponse, error) { | ||
| if c.listFlavorsFails { | ||
| return nil, fmt.Errorf("list flavors failed") | ||
| } | ||
| return c.listFlavorsResp, nil | ||
| } | ||
| var testProjectId = uuid.NewString() | ||
| var testFlavorId = uuid.NewString() | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| instanceNameFlag: "example-name", | ||
| aclFlag: "0.0.0.0/0", | ||
| backupScheduleFlag: "0 0/6 * * *", | ||
| flavorIdFlag: testFlavorId, | ||
| storageClassFlag: "storage-class", // Non-default | ||
| storageSizeFlag: "10", | ||
| versionFlag: "6.0", | ||
| editionFlag: "developer", | ||
| retentionDaysFlag: "32", | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| InstanceName: utils.Ptr("example-name"), | ||
| ACL: utils.Ptr([]string{"0.0.0.0/0"}), | ||
| BackupSchedule: utils.Ptr("0 0/6 * * *"), | ||
| FlavorId: utils.Ptr(testFlavorId), | ||
| StorageClass: utils.Ptr("storage-class"), | ||
| StorageSize: utils.Ptr(int64(10)), | ||
| Version: utils.Ptr("6.0"), | ||
| Edition: utils.Ptr("developer"), | ||
| RetentionDays: utils.Ptr(int64(32)), | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *sqlserverflex.ApiCreateInstanceRequest)) sqlserverflex.ApiCreateInstanceRequest { | ||
| request := testClient.CreateInstance(testCtx, testProjectId) | ||
| request = request.CreateInstancePayload(fixturePayload()) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func fixturePayload(mods ...func(payload *sqlserverflex.CreateInstancePayload)) sqlserverflex.CreateInstancePayload { | ||
| payload := sqlserverflex.CreateInstancePayload{ | ||
| Name: utils.Ptr("example-name"), | ||
| Acl: &sqlserverflex.CreateInstancePayloadAcl{Items: utils.Ptr([]string{"0.0.0.0/0"})}, | ||
| BackupSchedule: utils.Ptr("0 0/6 * * *"), | ||
| FlavorId: utils.Ptr(testFlavorId), | ||
| Storage: &sqlserverflex.CreateInstancePayloadStorage{ | ||
| Class: utils.Ptr("storage-class"), | ||
| Size: utils.Ptr(int64(10)), | ||
| }, | ||
| Version: utils.Ptr("6.0"), | ||
| Options: &sqlserverflex.CreateInstancePayloadOptions{ | ||
| Edition: utils.Ptr("developer"), | ||
| RetentionDays: utils.Ptr("32"), | ||
| }, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(&payload) | ||
| } | ||
| return payload | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| flagValues map[string]string | ||
| aclValues []string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "use CPU and RAM", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[cpuFlag] = "2" | ||
| flagValues[ramFlag] = "4" | ||
| delete(flagValues, flavorIdFlag) | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.FlavorId = nil | ||
| model.CPU = utils.Ptr(int64(2)) | ||
| model.RAM = utils.Ptr(int64(4)) | ||
| }), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "invalid with flavor ID, CPU and RAM", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[cpuFlag] = "2" | ||
| flagValues[ramFlag] = "4" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "invalid with flavor ID and CPU", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[cpuFlag] = "2" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "invalid with CPU only", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[cpuFlag] = "2" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no version", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, versionFlag) | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.Version = nil | ||
| }), | ||
| }, | ||
| { | ||
| description: "repeated acl flags", | ||
| flagValues: fixtureFlagValues(), | ||
| aclValues: []string{"198.51.100.14/24", "198.51.100.14/32"}, | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.ACL = utils.Ptr( | ||
| append(*model.ACL, "198.51.100.14/24", "198.51.100.14/32"), | ||
| ) | ||
| }), | ||
| }, | ||
| { | ||
| description: "repeated acl flag with list value", | ||
| flagValues: fixtureFlagValues(), | ||
| aclValues: []string{"198.51.100.14/24,198.51.100.14/32"}, | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.ACL = utils.Ptr( | ||
| append(*model.ACL, "198.51.100.14/24", "198.51.100.14/32"), | ||
| ) | ||
| }), | ||
| }, | ||
| { | ||
| description: "no acls", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, aclFlag) | ||
| }), | ||
| aclValues: []string{}, | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.ACL = nil | ||
| }), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| for _, value := range tt.aclValues { | ||
| err := cmd.Flags().Set(aclFlag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", aclFlag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest sqlserverflex.ApiCreateInstanceRequest | ||
| listFlavorsFails bool | ||
| listFlavorsResp *sqlserverflex.ListFlavorsResponse | ||
| listStoragesFails bool | ||
| listStoragesResp *sqlserverflex.ListStoragesResponse | ||
| isValid bool | ||
| }{ | ||
| { | ||
| description: "base with flavor ID", | ||
| model: fixtureInputModel(), | ||
| isValid: true, | ||
| expectedRequest: fixtureRequest(), | ||
| listFlavorsResp: &sqlserverflex.ListFlavorsResponse{ | ||
| Flavors: &[]sqlserverflex.InstanceFlavorEntry{ | ||
| { | ||
| Id: utils.Ptr(testFlavorId), | ||
| Cpu: utils.Ptr(int64(2)), | ||
| Memory: utils.Ptr(int64(4)), | ||
| }, | ||
| }, | ||
| }, | ||
| listStoragesResp: &sqlserverflex.ListStoragesResponse{ | ||
| StorageClasses: &[]string{"storage-class"}, | ||
| StorageRange: &sqlserverflex.StorageRange{ | ||
| Min: utils.Ptr(int64(10)), | ||
| Max: utils.Ptr(int64(100)), | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| description: "with CPU and RAM", | ||
| model: fixtureInputModel( | ||
| func(model *inputModel) { | ||
| model.FlavorId = nil | ||
| model.CPU = utils.Ptr(int64(2)) | ||
| model.RAM = utils.Ptr(int64(4)) | ||
| }, | ||
| ), | ||
| isValid: true, | ||
| expectedRequest: fixtureRequest(), | ||
| listFlavorsResp: &sqlserverflex.ListFlavorsResponse{ | ||
| Flavors: &[]sqlserverflex.InstanceFlavorEntry{ | ||
| { | ||
| Id: utils.Ptr(testFlavorId), | ||
| Cpu: utils.Ptr(int64(2)), | ||
| Memory: utils.Ptr(int64(4)), | ||
| }, | ||
| { | ||
| Id: utils.Ptr("other-flavor"), | ||
| Cpu: utils.Ptr(int64(1)), | ||
| Memory: utils.Ptr(int64(8)), | ||
| }, | ||
| }, | ||
| }, | ||
| listStoragesResp: &sqlserverflex.ListStoragesResponse{ | ||
| StorageClasses: &[]string{"storage-class"}, | ||
| StorageRange: &sqlserverflex.StorageRange{ | ||
| Min: utils.Ptr(int64(10)), | ||
| Max: utils.Ptr(int64(100)), | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| description: "get flavors fails", | ||
| model: fixtureInputModel( | ||
| func(model *inputModel) { | ||
| model.FlavorId = nil | ||
| model.CPU = utils.Ptr(int64(2)) | ||
| model.RAM = utils.Ptr(int64(4)) | ||
| }, | ||
| ), | ||
| listFlavorsFails: true, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "flavor id not found", | ||
| model: fixtureInputModel( | ||
| func(model *inputModel) { | ||
| model.FlavorId = nil | ||
| model.CPU = utils.Ptr(int64(5)) | ||
| model.RAM = utils.Ptr(int64(9)) | ||
| }, | ||
| ), | ||
| listFlavorsResp: &sqlserverflex.ListFlavorsResponse{ | ||
| Flavors: &[]sqlserverflex.InstanceFlavorEntry{ | ||
| { | ||
| Id: utils.Ptr(testFlavorId), | ||
| Cpu: utils.Ptr(int64(2)), | ||
| Memory: utils.Ptr(int64(4)), | ||
| }, | ||
| { | ||
| Id: utils.Ptr("other-flavor"), | ||
| Cpu: utils.Ptr(int64(1)), | ||
| Memory: utils.Ptr(int64(8)), | ||
| }, | ||
| }, | ||
| }, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "get storages fails", | ||
| model: fixtureInputModel( | ||
| func(model *inputModel) { | ||
| model.FlavorId = nil | ||
| model.CPU = utils.Ptr(int64(2)) | ||
| model.RAM = utils.Ptr(int64(4)) | ||
| }, | ||
| ), | ||
| listFlavorsFails: true, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "invalid storage class", | ||
| model: fixtureInputModel( | ||
| func(model *inputModel) { | ||
| model.StorageClass = utils.Ptr("non-existing-class") | ||
| }, | ||
| ), | ||
| listFlavorsResp: &sqlserverflex.ListFlavorsResponse{ | ||
| Flavors: &[]sqlserverflex.InstanceFlavorEntry{ | ||
| { | ||
| Id: utils.Ptr(testFlavorId), | ||
| Cpu: utils.Ptr(int64(2)), | ||
| Memory: utils.Ptr(int64(4)), | ||
| }, | ||
| }, | ||
| }, | ||
| listStoragesResp: &sqlserverflex.ListStoragesResponse{ | ||
| StorageClasses: &[]string{"storage-class"}, | ||
| StorageRange: &sqlserverflex.StorageRange{ | ||
| Min: utils.Ptr(int64(10)), | ||
| Max: utils.Ptr(int64(100)), | ||
| }, | ||
| }, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "invalid storage size", | ||
| model: fixtureInputModel( | ||
| func(model *inputModel) { | ||
| model.StorageSize = utils.Ptr(int64(9)) | ||
| }, | ||
| ), | ||
| listFlavorsResp: &sqlserverflex.ListFlavorsResponse{ | ||
| Flavors: &[]sqlserverflex.InstanceFlavorEntry{ | ||
| { | ||
| Id: utils.Ptr(testFlavorId), | ||
| Cpu: utils.Ptr(int64(2)), | ||
| Memory: utils.Ptr(int64(4)), | ||
| }, | ||
| }, | ||
| }, | ||
| listStoragesResp: &sqlserverflex.ListStoragesResponse{ | ||
| StorageClasses: &[]string{"storage-class"}, | ||
| StorageRange: &sqlserverflex.StorageRange{ | ||
| Min: utils.Ptr(int64(10)), | ||
| Max: utils.Ptr(int64(100)), | ||
| }, | ||
| }, | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| client := &sqlServerFlexClientMocked{ | ||
| listFlavorsFails: tt.listFlavorsFails, | ||
| listFlavorsResp: tt.listFlavorsResp, | ||
| listStoragesFails: tt.listStoragesFails, | ||
| listStoragesResp: tt.listStoragesResp, | ||
| } | ||
| request, err := buildRequest(testCtx, tt.model, client) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error building request: %v", err) | ||
| } | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package create | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "errors" | ||
| "fmt" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/client" | ||
| sqlserverflexUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/utils" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/wait" | ||
| ) | ||
| const ( | ||
| instanceNameFlag = "name" | ||
| aclFlag = "acl" | ||
| backupScheduleFlag = "backup-schedule" | ||
| flavorIdFlag = "flavor-id" | ||
| cpuFlag = "cpu" | ||
| ramFlag = "ram" | ||
| storageClassFlag = "storage-class" | ||
| storageSizeFlag = "storage-size" | ||
| versionFlag = "version" | ||
| editionFlag = "edition" | ||
| retentionDaysFlag = "retention-days" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| InstanceName *string | ||
| ACL *[]string | ||
| BackupSchedule *string | ||
| FlavorId *string | ||
| CPU *int64 | ||
| RAM *int64 | ||
| StorageClass *string | ||
| StorageSize *int64 | ||
| Version *string | ||
| Edition *string | ||
| RetentionDays *int64 | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "create", | ||
| Short: "Creates an SQLServer Flex instance", | ||
| Long: "Creates an SQLServer Flex instance.", | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Create an SQLServer Flex instance with name "my-instance" and specify flavor by CPU and RAM. Other parameters are set to default values`, | ||
| `$ stackit beta sqlserverflex instance create --name my-instance --cpu 1 --ram 4`), | ||
| examples.NewExample( | ||
| `Create an SQLServer Flex instance with name "my-instance" and specify flavor by ID. Other parameters are set to default values`, | ||
| `$ stackit beta sqlserverflex instance create --name my-instance --flavor-id xxx`), | ||
| examples.NewExample( | ||
| `Create an SQLServer Flex instance with name "my-instance", specify flavor by CPU and RAM, set storage size to 20 GB, and restrict access to a specific range of IP addresses. Other parameters are set to default values`, | ||
| `$ stackit beta sqlserverflex instance create --name my-instance --cpu 1 --ram 4 --storage-size 20 --acl 1.2.3.0/24`), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| projectLabel, err := projectname.GetProjectName(ctx, p, cmd) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get project name: %v", err) | ||
| projectLabel = model.ProjectId | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to create an SQLServer Flex instance for project %q?", projectLabel) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req, err := buildRequest(ctx, model, apiClient) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("create SQLServer Flex instance: %w", err) | ||
| } | ||
| instanceId := *resp.Id | ||
| // Wait for async operation, if async mode not enabled | ||
| if !model.Async { | ||
| s := spinner.New(p) | ||
| s.Start("Creating instance") | ||
| _, err = wait.CreateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx) | ||
| if err != nil { | ||
| return fmt.Errorf("wait for SQLServer Flex instance creation: %w", err) | ||
| } | ||
| s.Stop() | ||
| } | ||
| return outputResult(p, model, projectLabel, resp) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().StringP(instanceNameFlag, "n", "", "Instance name") | ||
| cmd.Flags().Var(flags.CIDRSliceFlag(), aclFlag, "The access control list (ACL). Must contain at least one valid subnet, for instance '0.0.0.0/0' for open access (discouraged), '1.2.3.0/24 for a public IP range of an organization, '1.2.3.4/32' for a single IP range, etc.") | ||
| cmd.Flags().String(backupScheduleFlag, "", "Backup schedule") | ||
| cmd.Flags().String(flavorIdFlag, "", "ID of the flavor") | ||
| cmd.Flags().Int64(cpuFlag, 0, "Number of CPUs") | ||
| cmd.Flags().Int64(ramFlag, 0, "Amount of RAM (in GB)") | ||
| cmd.Flags().Int64(storageSizeFlag, 0, "Storage size (in GB)") | ||
| cmd.Flags().String(storageClassFlag, "", "Storage class") | ||
| cmd.Flags().String(versionFlag, "", "SQLServer version") | ||
| cmd.Flags().String(editionFlag, "", "Edition of the SQLServer instance") | ||
| cmd.Flags().Int64(retentionDaysFlag, 0, "The days for how long the backup files should be stored before being cleaned up") | ||
| err := flags.MarkFlagsRequired(cmd, instanceNameFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &cliErr.ProjectIdError{} | ||
| } | ||
| flavorId := flags.FlagToStringPointer(p, cmd, flavorIdFlag) | ||
| cpu := flags.FlagToInt64Pointer(p, cmd, cpuFlag) | ||
| ram := flags.FlagToInt64Pointer(p, cmd, ramFlag) | ||
| if flavorId == nil && (cpu == nil || ram == nil) { | ||
| return nil, &cliErr.DatabaseInputFlavorError{ | ||
| Cmd: cmd, | ||
| Service: sqlserverflexUtils.ServiceCmd, | ||
| } | ||
| } | ||
| if flavorId != nil && (cpu != nil || ram != nil) { | ||
| return nil, &cliErr.DatabaseInputFlavorError{ | ||
| Cmd: cmd, | ||
| Service: sqlserverflexUtils.ServiceCmd, | ||
| } | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| InstanceName: flags.FlagToStringPointer(p, cmd, instanceNameFlag), | ||
| ACL: flags.FlagToStringSlicePointer(p, cmd, aclFlag), | ||
| BackupSchedule: flags.FlagToStringPointer(p, cmd, backupScheduleFlag), | ||
| FlavorId: flavorId, | ||
| CPU: cpu, | ||
| RAM: ram, | ||
| StorageClass: flags.FlagToStringPointer(p, cmd, storageClassFlag), | ||
| StorageSize: flags.FlagToInt64Pointer(p, cmd, storageSizeFlag), | ||
| Version: flags.FlagToStringPointer(p, cmd, versionFlag), | ||
| Edition: flags.FlagToStringPointer(p, cmd, editionFlag), | ||
| RetentionDays: flags.FlagToInt64Pointer(p, cmd, retentionDaysFlag), | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| type sqlServerFlexClient interface { | ||
| CreateInstance(ctx context.Context, projectId string) sqlserverflex.ApiCreateInstanceRequest | ||
| ListFlavorsExecute(ctx context.Context, projectId string) (*sqlserverflex.ListFlavorsResponse, error) | ||
| ListStoragesExecute(ctx context.Context, projectId, flavorId string) (*sqlserverflex.ListStoragesResponse, error) | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient sqlServerFlexClient) (sqlserverflex.ApiCreateInstanceRequest, error) { | ||
| req := apiClient.CreateInstance(ctx, model.ProjectId) | ||
| var flavorId *string | ||
| var err error | ||
| flavors, err := apiClient.ListFlavorsExecute(ctx, model.ProjectId) | ||
| if err != nil { | ||
| return req, fmt.Errorf("get SQLServer Flex flavors: %w", err) | ||
| } | ||
| if model.FlavorId == nil { | ||
| flavorId, err = sqlserverflexUtils.LoadFlavorId(*model.CPU, *model.RAM, flavors.Flavors) | ||
| if err != nil { | ||
| var dsaInvalidPlanError *cliErr.DSAInvalidPlanError | ||
| if !errors.As(err, &dsaInvalidPlanError) { | ||
| return req, fmt.Errorf("load flavor ID: %w", err) | ||
| } | ||
| return req, err | ||
| } | ||
| } else { | ||
| err := sqlserverflexUtils.ValidateFlavorId(*model.FlavorId, flavors.Flavors) | ||
| if err != nil { | ||
| return req, err | ||
| } | ||
| flavorId = model.FlavorId | ||
| } | ||
| storages, err := apiClient.ListStoragesExecute(ctx, model.ProjectId, *flavorId) | ||
| if err != nil { | ||
| return req, fmt.Errorf("get SQLServer Flex storages: %w", err) | ||
| } | ||
| err = sqlserverflexUtils.ValidateStorage(model.StorageClass, model.StorageSize, storages, *flavorId) | ||
| if err != nil { | ||
| return req, err | ||
| } | ||
| var retentionDays *string | ||
| if model.RetentionDays != nil { | ||
| retentionDays = utils.Ptr(fmt.Sprintf("%d", *model.RetentionDays)) | ||
| } | ||
| req = req.CreateInstancePayload(sqlserverflex.CreateInstancePayload{ | ||
| Name: model.InstanceName, | ||
| Acl: &sqlserverflex.CreateInstancePayloadAcl{Items: model.ACL}, | ||
| BackupSchedule: model.BackupSchedule, | ||
| FlavorId: flavorId, | ||
| Storage: &sqlserverflex.CreateInstancePayloadStorage{ | ||
| Class: model.StorageClass, | ||
| Size: model.StorageSize, | ||
| }, | ||
| Version: model.Version, | ||
| Options: &sqlserverflex.CreateInstancePayloadOptions{ | ||
| Edition: model.Edition, | ||
| RetentionDays: retentionDays, | ||
| }, | ||
| }) | ||
| return req, nil | ||
| } | ||
| func outputResult(p *print.Printer, model *inputModel, projectLabel string, resp *sqlserverflex.CreateInstanceResponse) error { | ||
| switch model.OutputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(resp, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal SQLServerFlex instance: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal SQLServerFlex instance: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| operationState := "Created" | ||
| if model.Async { | ||
| operationState = "Triggered creation of" | ||
| } | ||
| p.Outputf("%s instance for project %q. Instance ID: %s\n", operationState, projectLabel, *resp.Id) | ||
| return nil | ||
| } | ||
| } |
| package delete | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &sqlserverflex.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testInstanceId = uuid.NewString() | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testInstanceId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(argValues) | ||
| } | ||
| return argValues | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| InstanceId: testInstanceId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *sqlserverflex.ApiDeleteInstanceRequest)) sqlserverflex.ApiDeleteInstanceRequest { | ||
| request := testClient.DeleteInstance(testCtx, testProjectId, testInstanceId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argValues []string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| argValues: []string{}, | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no arg values", | ||
| argValues: []string{}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no flag values", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id missing", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 1", | ||
| argValues: []string{""}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 2", | ||
| argValues: []string{"invalid-uuid"}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateArgs(tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating args: %v", err) | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd, tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing input: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest sqlserverflex.ApiDeleteInstanceRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package delete | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/client" | ||
| sqlserverflexUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/utils" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/wait" | ||
| ) | ||
| const ( | ||
| instanceIdArg = "INSTANCE_ID" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| InstanceId string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: fmt.Sprintf("delete %s", instanceIdArg), | ||
| Short: "Deletes an SQLServer Flex instance", | ||
| Long: "Deletes an SQLServer Flex instance.", | ||
| Args: args.SingleArg(instanceIdArg, utils.ValidateUUID), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Delete an SQLServer Flex instance with ID "xxx"`, | ||
| "$ stackit beta sqlserverflex instance delete xxx"), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| instanceLabel, err := sqlserverflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get instance name: %v", err) | ||
| instanceLabel = model.InstanceId | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to delete instance %q? (This cannot be undone)", instanceLabel) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| err = req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("delete SQLServer Flex instance: %w", err) | ||
| } | ||
| // Wait for async operation, if async mode not enabled | ||
| if !model.Async { | ||
| s := spinner.New(p) | ||
| s.Start("Deleting instance") | ||
| _, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId).WaitWithContext(ctx) | ||
| if err != nil { | ||
| return fmt.Errorf("wait for SQLServer Flex instance deletion: %w", err) | ||
| } | ||
| s.Stop() | ||
| } | ||
| operationState := "Deleted" | ||
| if model.Async { | ||
| operationState = "Triggered deletion of" | ||
| } | ||
| p.Info("%s instance %q\n", operationState, instanceLabel) | ||
| return nil | ||
| }, | ||
| } | ||
| return cmd | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| instanceId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| InstanceId: instanceId, | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *sqlserverflex.APIClient) sqlserverflex.ApiDeleteInstanceRequest { | ||
| req := apiClient.DeleteInstance(ctx, model.ProjectId, model.InstanceId) | ||
| return req | ||
| } |
| package describe | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &sqlserverflex.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testInstanceId = uuid.NewString() | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testInstanceId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(argValues) | ||
| } | ||
| return argValues | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| InstanceId: testInstanceId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *sqlserverflex.ApiGetInstanceRequest)) sqlserverflex.ApiGetInstanceRequest { | ||
| request := testClient.GetInstance(testCtx, testProjectId, testInstanceId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argValues []string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| argValues: []string{}, | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no arg values", | ||
| argValues: []string{}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no flag values", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id missing", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 1", | ||
| argValues: []string{""}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 2", | ||
| argValues: []string{"invalid-uuid"}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateArgs(tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating args: %v", err) | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd, tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing input: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest sqlserverflex.ApiGetInstanceRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package describe | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "strings" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/client" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/tables" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| ) | ||
| const ( | ||
| instanceIdArg = "INSTANCE_ID" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| InstanceId string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: fmt.Sprintf("describe %s", instanceIdArg), | ||
| Short: "Shows details of an SQLServer Flex instance", | ||
| Long: "Shows details of an SQLServer Flex instance.", | ||
| Args: args.SingleArg(instanceIdArg, utils.ValidateUUID), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Get details of an SQLServer Flex instance with ID "xxx"`, | ||
| "$ stackit beta sqlserverflex instance describe xxx"), | ||
| examples.NewExample( | ||
| `Get details of an SQLServer Flex instance with ID "xxx" in JSON format`, | ||
| "$ stackit beta sqlserverflex instance describe xxx --output-format json"), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("read SQLServer Flex instance: %w", err) | ||
| } | ||
| return outputResult(p, model.OutputFormat, resp.Item) | ||
| }, | ||
| } | ||
| return cmd | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| instanceId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| InstanceId: instanceId, | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *sqlserverflex.APIClient) sqlserverflex.ApiGetInstanceRequest { | ||
| req := apiClient.GetInstance(ctx, model.ProjectId, model.InstanceId) | ||
| return req | ||
| } | ||
| func outputResult(p *print.Printer, outputFormat string, instance *sqlserverflex.Instance) error { | ||
| switch outputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(instance, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal SQLServer Flex instance: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(instance, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal SQLServer Flex instance: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| aclsArray := *instance.Acl.Items | ||
| acls := strings.Join(aclsArray, ",") | ||
| table := tables.NewTable() | ||
| table.AddRow("ID", *instance.Id) | ||
| table.AddSeparator() | ||
| table.AddRow("NAME", *instance.Name) | ||
| table.AddSeparator() | ||
| table.AddRow("STATUS", *instance.Status) | ||
| table.AddSeparator() | ||
| table.AddRow("STORAGE SIZE (GB)", *instance.Storage.Size) | ||
| table.AddSeparator() | ||
| table.AddRow("VERSION", *instance.Version) | ||
| table.AddSeparator() | ||
| table.AddRow("BACKUP SCHEDULE (UTC)", *instance.BackupSchedule) | ||
| table.AddSeparator() | ||
| table.AddRow("ACL", acls) | ||
| table.AddSeparator() | ||
| table.AddRow("FLAVOR DESCRIPTION", *instance.Flavor.Description) | ||
| table.AddSeparator() | ||
| table.AddRow("CPU", *instance.Flavor.Cpu) | ||
| table.AddSeparator() | ||
| table.AddRow("RAM (GB)", *instance.Flavor.Memory) | ||
| table.AddSeparator() | ||
| err := table.Display(p) | ||
| if err != nil { | ||
| return fmt.Errorf("render table: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
| } |
| package instance | ||
| import ( | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance/create" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance/delete" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance/describe" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance/list" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance/update" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "instance", | ||
| Short: "Provides functionality for SQLServer Flex instances", | ||
| Long: "Provides functionality for SQLServer Flex instances.", | ||
| Args: args.NoArgs, | ||
| Run: utils.CmdHelp, | ||
| } | ||
| addSubcommands(cmd, p) | ||
| return cmd | ||
| } | ||
| func addSubcommands(cmd *cobra.Command, p *print.Printer) { | ||
| cmd.AddCommand(create.NewCmd(p)) | ||
| cmd.AddCommand(delete.NewCmd(p)) | ||
| cmd.AddCommand(describe.NewCmd(p)) | ||
| cmd.AddCommand(list.NewCmd(p)) | ||
| cmd.AddCommand(update.NewCmd(p)) | ||
| } |
| package list | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &sqlserverflex.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| limitFlag: "10", | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| Limit: utils.Ptr(int64(10)), | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *sqlserverflex.ApiListInstancesRequest)) sqlserverflex.ApiListInstancesRequest { | ||
| request := testClient.ListInstances(testCtx, testProjectId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "limit invalid", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[limitFlag] = "invalid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "limit invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[limitFlag] = "0" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| cmd := &cobra.Command{} | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| configureFlags(cmd) | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| p := print.NewPrinter() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest sqlserverflex.ApiListInstancesRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package list | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/client" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/tables" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| ) | ||
| const ( | ||
| limitFlag = "limit" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| Limit *int64 | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "list", | ||
| Short: "Lists all SQLServer Flex instances", | ||
| Long: "Lists all SQLServer Flex instances.", | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `List all SQLServer Flex instances`, | ||
| "$ stackit beta sqlserverflex instance list"), | ||
| examples.NewExample( | ||
| `List all SQLServer Flex instances in JSON format`, | ||
| "$ stackit beta sqlserverflex instance list --output-format json"), | ||
| examples.NewExample( | ||
| `List up to 10 SQLServer Flex instances`, | ||
| "$ stackit beta sqlserverflex instance list --limit 10"), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("get SQLServer Flex instances: %w", err) | ||
| } | ||
| if resp.Items == nil || len(*resp.Items) == 0 { | ||
| projectLabel, err := projectname.GetProjectName(ctx, p, cmd) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get project name: %v", err) | ||
| projectLabel = model.ProjectId | ||
| } | ||
| p.Info("No instances found for project %q\n", projectLabel) | ||
| return nil | ||
| } | ||
| instances := *resp.Items | ||
| // Truncate output | ||
| if model.Limit != nil && len(instances) > int(*model.Limit) { | ||
| instances = instances[:*model.Limit] | ||
| } | ||
| return outputResult(p, model.OutputFormat, instances) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| limit := flags.FlagToInt64Pointer(p, cmd, limitFlag) | ||
| if limit != nil && *limit < 1 { | ||
| return nil, &errors.FlagValidationError{ | ||
| Flag: limitFlag, | ||
| Details: "must be greater than 0", | ||
| } | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| Limit: limit, | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *sqlserverflex.APIClient) sqlserverflex.ApiListInstancesRequest { | ||
| req := apiClient.ListInstances(ctx, model.ProjectId) | ||
| return req | ||
| } | ||
| func outputResult(p *print.Printer, outputFormat string, instances []sqlserverflex.InstanceListInstance) error { | ||
| switch outputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(instances, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal SQLServer Flex instance list: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(instances, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal SQLServer Flex instance list: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| table := tables.NewTable() | ||
| table.SetHeader("ID", "NAME", "STATUS") | ||
| for i := range instances { | ||
| instance := instances[i] | ||
| table.AddRow(*instance.Id, *instance.Name, *instance.Status) | ||
| } | ||
| err := table.Display(p) | ||
| if err != nil { | ||
| return fmt.Errorf("render table: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
| } |
| package update | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &sqlserverflex.APIClient{} | ||
| type sqlServerFlexClientMocked struct { | ||
| listFlavorsFails bool | ||
| listFlavorsResp *sqlserverflex.ListFlavorsResponse | ||
| listStoragesFails bool | ||
| listStoragesResp *sqlserverflex.ListStoragesResponse | ||
| getInstanceFails bool | ||
| getInstanceResp *sqlserverflex.GetInstanceResponse | ||
| } | ||
| func (c *sqlServerFlexClientMocked) PartialUpdateInstance(ctx context.Context, projectId, instanceId string) sqlserverflex.ApiPartialUpdateInstanceRequest { | ||
| return testClient.PartialUpdateInstance(ctx, projectId, instanceId) | ||
| } | ||
| func (c *sqlServerFlexClientMocked) GetInstanceExecute(_ context.Context, _, _ string) (*sqlserverflex.GetInstanceResponse, error) { | ||
| if c.getInstanceFails { | ||
| return nil, fmt.Errorf("get instance failed") | ||
| } | ||
| return c.getInstanceResp, nil | ||
| } | ||
| func (c *sqlServerFlexClientMocked) ListStoragesExecute(_ context.Context, _, _ string) (*sqlserverflex.ListStoragesResponse, error) { | ||
| if c.listFlavorsFails { | ||
| return nil, fmt.Errorf("list storages failed") | ||
| } | ||
| return c.listStoragesResp, nil | ||
| } | ||
| func (c *sqlServerFlexClientMocked) ListFlavorsExecute(_ context.Context, _ string) (*sqlserverflex.ListFlavorsResponse, error) { | ||
| if c.listFlavorsFails { | ||
| return nil, fmt.Errorf("list flavors failed") | ||
| } | ||
| return c.listFlavorsResp, nil | ||
| } | ||
| var testProjectId = uuid.NewString() | ||
| var testInstanceId = uuid.NewString() | ||
| var testFlavorId = uuid.NewString() | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testInstanceId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(argValues) | ||
| } | ||
| return argValues | ||
| } | ||
| func fixtureRequiredFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureStandardFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| flavorIdFlag: testFlavorId, | ||
| instanceNameFlag: "example-name", | ||
| aclFlag: "0.0.0.0/0", | ||
| backupScheduleFlag: "0 0 * * *", | ||
| versionFlag: "5.0", | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureRequiredInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| InstanceId: testInstanceId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureStandardInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| InstanceId: testInstanceId, | ||
| FlavorId: utils.Ptr(testFlavorId), | ||
| InstanceName: utils.Ptr("example-name"), | ||
| ACL: utils.Ptr([]string{"0.0.0.0/0"}), | ||
| BackupSchedule: utils.Ptr("0 0 * * *"), | ||
| Version: utils.Ptr("5.0"), | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *sqlserverflex.ApiPartialUpdateInstanceRequest)) sqlserverflex.ApiPartialUpdateInstanceRequest { | ||
| request := testClient.PartialUpdateInstance(testCtx, testProjectId, testInstanceId) | ||
| request = request.PartialUpdateInstancePayload(sqlserverflex.PartialUpdateInstancePayload{}) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argValues []string | ||
| flagValues map[string]string | ||
| aclValues []string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "no values", | ||
| argValues: []string{}, | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no arg values", | ||
| argValues: []string{}, | ||
| flagValues: fixtureRequiredFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no flag values", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "only instance and project ids", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureRequiredFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "all values with flavor id", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureStandardFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureStandardInputModel(), | ||
| }, | ||
| { | ||
| description: "all values with cpu and ram", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureStandardFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, flavorIdFlag) | ||
| flagValues[cpuFlag] = "2" | ||
| flagValues[ramFlag] = "4" | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureStandardInputModel(func(model *inputModel) { | ||
| model.FlavorId = nil | ||
| model.CPU = utils.Ptr(int64(2)) | ||
| model.RAM = utils.Ptr(int64(4)) | ||
| }), | ||
| }, | ||
| { | ||
| description: "project id missing", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureRequiredFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureRequiredFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureRequiredFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 1", | ||
| argValues: []string{""}, | ||
| flagValues: fixtureRequiredFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 2", | ||
| argValues: []string{"invalid-uuid"}, | ||
| flagValues: fixtureRequiredFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "invalid with flavor ID, CPU and RAM", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureRequiredFlagValues(func(flagValues map[string]string) { | ||
| flagValues[flavorIdFlag] = testFlavorId | ||
| flagValues[cpuFlag] = "2" | ||
| flagValues[ramFlag] = "4" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "invalid with flavor ID and CPU", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureRequiredFlagValues(func(flagValues map[string]string) { | ||
| flagValues[flavorIdFlag] = testFlavorId | ||
| flagValues[cpuFlag] = "2" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no acl flag", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureStandardFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, aclFlag) | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureStandardInputModel(func(model *inputModel) { | ||
| model.ACL = nil | ||
| }), | ||
| }, | ||
| { | ||
| description: "repeated acl flags", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureRequiredFlagValues(), | ||
| aclValues: []string{"198.51.100.14/24", "198.51.100.14/32"}, | ||
| isValid: true, | ||
| expectedModel: fixtureRequiredInputModel(func(model *inputModel) { | ||
| model.ACL = utils.Ptr([]string{"198.51.100.14/24", "198.51.100.14/32"}) | ||
| }), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| for _, value := range tt.aclValues { | ||
| err := cmd.Flags().Set(aclFlag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", aclFlag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateArgs(tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating args: %v", err) | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd, tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest sqlserverflex.ApiPartialUpdateInstanceRequest | ||
| getInstanceFails bool | ||
| getInstanceResp *sqlserverflex.GetInstanceResponse | ||
| listFlavorsFails bool | ||
| listFlavorsResp *sqlserverflex.ListFlavorsResponse | ||
| listStoragesFails bool | ||
| listStoragesResp *sqlserverflex.ListStoragesResponse | ||
| isValid bool | ||
| }{ | ||
| { | ||
| description: "no values", | ||
| model: fixtureRequiredInputModel(), | ||
| isValid: true, | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| { | ||
| description: "update flavor from id", | ||
| model: fixtureRequiredInputModel(func(model *inputModel) { | ||
| model.FlavorId = utils.Ptr(testFlavorId) | ||
| }), | ||
| isValid: true, | ||
| listFlavorsResp: &sqlserverflex.ListFlavorsResponse{ | ||
| Flavors: &[]sqlserverflex.InstanceFlavorEntry{ | ||
| { | ||
| Id: utils.Ptr(testFlavorId), | ||
| Cpu: utils.Ptr(int64(2)), | ||
| Memory: utils.Ptr(int64(4)), | ||
| }, | ||
| }, | ||
| }, | ||
| expectedRequest: testClient.PartialUpdateInstance(testCtx, testProjectId, testInstanceId). | ||
| PartialUpdateInstancePayload(sqlserverflex.PartialUpdateInstancePayload{ | ||
| FlavorId: utils.Ptr(testFlavorId), | ||
| }), | ||
| }, | ||
| { | ||
| description: "update flavor from cpu and ram", | ||
| model: fixtureRequiredInputModel(func(model *inputModel) { | ||
| model.CPU = utils.Ptr(int64(2)) | ||
| model.RAM = utils.Ptr(int64(4)) | ||
| }), | ||
| isValid: true, | ||
| listFlavorsResp: &sqlserverflex.ListFlavorsResponse{ | ||
| Flavors: &[]sqlserverflex.InstanceFlavorEntry{ | ||
| { | ||
| Id: utils.Ptr(testFlavorId), | ||
| Cpu: utils.Ptr(int64(2)), | ||
| Memory: utils.Ptr(int64(4)), | ||
| }, | ||
| }, | ||
| }, | ||
| expectedRequest: testClient.PartialUpdateInstance(testCtx, testProjectId, testInstanceId). | ||
| PartialUpdateInstancePayload(sqlserverflex.PartialUpdateInstancePayload{ | ||
| FlavorId: utils.Ptr(testFlavorId), | ||
| }), | ||
| }, | ||
| { | ||
| description: "get flavors fails", | ||
| model: fixtureRequiredInputModel( | ||
| func(model *inputModel) { | ||
| model.CPU = utils.Ptr(int64(2)) | ||
| model.RAM = utils.Ptr(int64(4)) | ||
| }, | ||
| ), | ||
| listFlavorsFails: true, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "flavor id not found", | ||
| model: fixtureRequiredInputModel( | ||
| func(model *inputModel) { | ||
| model.CPU = utils.Ptr(int64(5)) | ||
| model.RAM = utils.Ptr(int64(9)) | ||
| }, | ||
| ), | ||
| listFlavorsResp: &sqlserverflex.ListFlavorsResponse{ | ||
| Flavors: &[]sqlserverflex.InstanceFlavorEntry{ | ||
| { | ||
| Id: utils.Ptr(testFlavorId), | ||
| Cpu: utils.Ptr(int64(2)), | ||
| Memory: utils.Ptr(int64(4)), | ||
| }, | ||
| { | ||
| Id: utils.Ptr("other-flavor"), | ||
| Cpu: utils.Ptr(int64(1)), | ||
| Memory: utils.Ptr(int64(8)), | ||
| }, | ||
| }, | ||
| }, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "get instance fails", | ||
| model: fixtureRequiredInputModel( | ||
| func(model *inputModel) { | ||
| model.FlavorId = nil | ||
| model.RAM = utils.Ptr(int64(4)) | ||
| }, | ||
| ), | ||
| getInstanceFails: true, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "get storages fails", | ||
| model: fixtureRequiredInputModel( | ||
| func(model *inputModel) { | ||
| model.FlavorId = nil | ||
| model.CPU = utils.Ptr(int64(2)) | ||
| model.RAM = utils.Ptr(int64(4)) | ||
| }, | ||
| ), | ||
| listFlavorsFails: true, | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| client := &sqlServerFlexClientMocked{ | ||
| getInstanceFails: tt.getInstanceFails, | ||
| getInstanceResp: tt.getInstanceResp, | ||
| listFlavorsFails: tt.listFlavorsFails, | ||
| listFlavorsResp: tt.listFlavorsResp, | ||
| listStoragesFails: tt.listStoragesFails, | ||
| listStoragesResp: tt.listStoragesResp, | ||
| } | ||
| request, err := buildRequest(testCtx, tt.model, client) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error building request: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package update | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "errors" | ||
| "fmt" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/client" | ||
| sqlserverflexUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/utils" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/wait" | ||
| ) | ||
| const ( | ||
| instanceIdArg = "INSTANCE_ID" | ||
| instanceNameFlag = "name" | ||
| aclFlag = "acl" | ||
| backupScheduleFlag = "backup-schedule" | ||
| flavorIdFlag = "flavor-id" | ||
| cpuFlag = "cpu" | ||
| ramFlag = "ram" | ||
| versionFlag = "version" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| InstanceId string | ||
| InstanceName *string | ||
| ACL *[]string | ||
| BackupSchedule *string | ||
| FlavorId *string | ||
| CPU *int64 | ||
| RAM *int64 | ||
| Version *string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: fmt.Sprintf("update %s", instanceIdArg), | ||
| Short: "Updates an SQLServer Flex instance", | ||
| Long: "Updates an SQLServer Flex instance.", | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Update the name of an SQLServer Flex instance with ID "xxx"`, | ||
| "$ stackit beta sqlserverflex instance update xxx --name my-new-name"), | ||
| examples.NewExample( | ||
| `Update the backup schedule of an SQLServer Flex instance with ID "xxx"`, | ||
| `$ stackit beta sqlserverflex instance update xxx --backup-schedule "30 0 * * *"`), | ||
| ), | ||
| Args: args.SingleArg(instanceIdArg, utils.ValidateUUID), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| instanceLabel, err := sqlserverflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get instance name: %v", err) | ||
| instanceLabel = model.InstanceId | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to update instance %q?", instanceLabel) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req, err := buildRequest(ctx, model, apiClient) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("update SQLServer Flex instance: %w", err) | ||
| } | ||
| instanceId := *resp.Item.Id | ||
| // Wait for async operation, if async mode not enabled | ||
| if !model.Async { | ||
| s := spinner.New(p) | ||
| s.Start("Updating instance") | ||
| _, err = wait.PartialUpdateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx) | ||
| if err != nil { | ||
| return fmt.Errorf("wait for SQLServer Flex instance update: %w", err) | ||
| } | ||
| s.Stop() | ||
| } | ||
| return outputResult(p, model, instanceLabel, resp) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().StringP(instanceNameFlag, "n", "", "Instance name") | ||
| cmd.Flags().Var(flags.CIDRSliceFlag(), aclFlag, "Lists of IP networks in CIDR notation which are allowed to access this instance") | ||
| cmd.Flags().String(backupScheduleFlag, "", "Backup schedule") | ||
| cmd.Flags().String(flavorIdFlag, "", "ID of the flavor") | ||
| cmd.Flags().Int64(cpuFlag, 0, "Number of CPUs") | ||
| cmd.Flags().Int64(ramFlag, 0, "Amount of RAM (in GB)") | ||
| cmd.Flags().String(versionFlag, "", "Version") | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| instanceId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &cliErr.ProjectIdError{} | ||
| } | ||
| instanceName := flags.FlagToStringPointer(p, cmd, instanceNameFlag) | ||
| flavorId := flags.FlagToStringPointer(p, cmd, flavorIdFlag) | ||
| cpu := flags.FlagToInt64Pointer(p, cmd, cpuFlag) | ||
| ram := flags.FlagToInt64Pointer(p, cmd, ramFlag) | ||
| acl := flags.FlagToStringSlicePointer(p, cmd, aclFlag) | ||
| backupSchedule := flags.FlagToStringPointer(p, cmd, backupScheduleFlag) | ||
| version := flags.FlagToStringPointer(p, cmd, versionFlag) | ||
| if instanceName == nil && flavorId == nil && cpu == nil && ram == nil && acl == nil && | ||
| backupSchedule == nil && version == nil { | ||
| return nil, &cliErr.EmptyUpdateError{} | ||
| } | ||
| if flavorId != nil && (cpu != nil || ram != nil) { | ||
| return nil, &cliErr.DatabaseInputFlavorError{ | ||
| Cmd: cmd, | ||
| Service: sqlserverflexUtils.ServiceCmd, | ||
| Args: inputArgs, | ||
| } | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| InstanceId: instanceId, | ||
| InstanceName: instanceName, | ||
| ACL: acl, | ||
| BackupSchedule: backupSchedule, | ||
| FlavorId: flavorId, | ||
| CPU: cpu, | ||
| RAM: ram, | ||
| Version: version, | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| type sqlServerFlexClient interface { | ||
| PartialUpdateInstance(ctx context.Context, projectId, instanceId string) sqlserverflex.ApiPartialUpdateInstanceRequest | ||
| GetInstanceExecute(ctx context.Context, projectId, instanceId string) (*sqlserverflex.GetInstanceResponse, error) | ||
| ListFlavorsExecute(ctx context.Context, projectId string) (*sqlserverflex.ListFlavorsResponse, error) | ||
| ListStoragesExecute(ctx context.Context, projectId, flavorId string) (*sqlserverflex.ListStoragesResponse, error) | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient sqlServerFlexClient) (sqlserverflex.ApiPartialUpdateInstanceRequest, error) { | ||
| req := apiClient.PartialUpdateInstance(ctx, model.ProjectId, model.InstanceId) | ||
| var flavorId *string | ||
| var err error | ||
| flavors, err := apiClient.ListFlavorsExecute(ctx, model.ProjectId) | ||
| if err != nil { | ||
| return req, fmt.Errorf("get SQLServer Flex flavors: %w", err) | ||
| } | ||
| if model.FlavorId == nil && (model.RAM != nil || model.CPU != nil) { | ||
| ram := model.RAM | ||
| cpu := model.CPU | ||
| if model.RAM == nil || model.CPU == nil { | ||
| currentInstance, err := apiClient.GetInstanceExecute(ctx, model.ProjectId, model.InstanceId) | ||
| if err != nil { | ||
| return req, fmt.Errorf("get SQLServer Flex instance: %w", err) | ||
| } | ||
| if model.RAM == nil { | ||
| ram = currentInstance.Item.Flavor.Memory | ||
| } | ||
| if model.CPU == nil { | ||
| cpu = currentInstance.Item.Flavor.Cpu | ||
| } | ||
| } | ||
| flavorId, err = sqlserverflexUtils.LoadFlavorId(*cpu, *ram, flavors.Flavors) | ||
| if err != nil { | ||
| var dsaInvalidPlanError *cliErr.DSAInvalidPlanError | ||
| if !errors.As(err, &dsaInvalidPlanError) { | ||
| return req, fmt.Errorf("load flavor ID: %w", err) | ||
| } | ||
| return req, err | ||
| } | ||
| } else if model.FlavorId != nil { | ||
| err := sqlserverflexUtils.ValidateFlavorId(*model.FlavorId, flavors.Flavors) | ||
| if err != nil { | ||
| return req, err | ||
| } | ||
| flavorId = model.FlavorId | ||
| } | ||
| var payloadAcl *sqlserverflex.CreateInstancePayloadAcl | ||
| if model.ACL != nil { | ||
| payloadAcl = &sqlserverflex.CreateInstancePayloadAcl{Items: model.ACL} | ||
| } | ||
| req = req.PartialUpdateInstancePayload(sqlserverflex.PartialUpdateInstancePayload{ | ||
| Name: model.InstanceName, | ||
| Acl: payloadAcl, | ||
| BackupSchedule: model.BackupSchedule, | ||
| FlavorId: flavorId, | ||
| Version: model.Version, | ||
| }) | ||
| return req, nil | ||
| } | ||
| func outputResult(p *print.Printer, model *inputModel, instanceLabel string, resp *sqlserverflex.UpdateInstanceResponse) error { | ||
| switch model.OutputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(resp, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal update SQLServerFlex instance: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal update SQLServerFlex instance: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| operationState := "Updated" | ||
| if model.Async { | ||
| operationState = "Triggered update of" | ||
| } | ||
| p.Info("%s instance %q\n", operationState, instanceLabel) | ||
| return nil | ||
| } | ||
| } |
| package options | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| ) | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| type sqlServerFlexClientMocked struct { | ||
| listFlavorsFails bool | ||
| listVersionsFails bool | ||
| listStoragesFails bool | ||
| listFlavorsCalled bool | ||
| listVersionsCalled bool | ||
| listStoragesCalled bool | ||
| } | ||
| func (c *sqlServerFlexClientMocked) ListFlavorsExecute(_ context.Context, _ string) (*sqlserverflex.ListFlavorsResponse, error) { | ||
| c.listFlavorsCalled = true | ||
| if c.listFlavorsFails { | ||
| return nil, fmt.Errorf("list flavors failed") | ||
| } | ||
| return utils.Ptr(sqlserverflex.ListFlavorsResponse{ | ||
| Flavors: utils.Ptr([]sqlserverflex.InstanceFlavorEntry{}), | ||
| }), nil | ||
| } | ||
| func (c *sqlServerFlexClientMocked) ListVersionsExecute(_ context.Context, _ string) (*sqlserverflex.ListVersionsResponse, error) { | ||
| c.listVersionsCalled = true | ||
| if c.listVersionsFails { | ||
| return nil, fmt.Errorf("list versions failed") | ||
| } | ||
| return utils.Ptr(sqlserverflex.ListVersionsResponse{ | ||
| Versions: utils.Ptr([]string{}), | ||
| }), nil | ||
| } | ||
| func (c *sqlServerFlexClientMocked) ListStoragesExecute(_ context.Context, _, _ string) (*sqlserverflex.ListStoragesResponse, error) { | ||
| c.listStoragesCalled = true | ||
| if c.listStoragesFails { | ||
| return nil, fmt.Errorf("list storages failed") | ||
| } | ||
| return utils.Ptr(sqlserverflex.ListStoragesResponse{ | ||
| StorageClasses: utils.Ptr([]string{}), | ||
| StorageRange: &sqlserverflex.StorageRange{ | ||
| Min: utils.Ptr(int64(10)), | ||
| Max: utils.Ptr(int64(100)), | ||
| }, | ||
| }), nil | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| flavorsFlag: "true", | ||
| versionsFlag: "true", | ||
| storagesFlag: "true", | ||
| flavorIdFlag: "2.4", | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModelAllFalse(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{Verbosity: globalflags.VerbosityDefault}, | ||
| Flavors: false, | ||
| Versions: false, | ||
| Storages: false, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureInputModelAllTrue(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{Verbosity: globalflags.VerbosityDefault}, | ||
| Flavors: true, | ||
| Versions: true, | ||
| Storages: true, | ||
| FlavorId: utils.Ptr("2.4"), | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "all values", | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModelAllTrue(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "some values 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[storagesFlag] = "false" | ||
| delete(flagValues, flavorIdFlag) | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModelAllFalse(func(model *inputModel) { | ||
| model.Flavors = true | ||
| model.Versions = true | ||
| }), | ||
| }, | ||
| { | ||
| description: "some values 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, flavorsFlag) | ||
| delete(flagValues, versionsFlag) | ||
| flagValues[storagesFlag] = "true" | ||
| flagValues[flavorIdFlag] = "2.4" | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModelAllFalse(func(model *inputModel) { | ||
| model.Storages = true | ||
| model.FlavorId = utils.Ptr("2.4") | ||
| }), | ||
| }, | ||
| { | ||
| description: "storages without flavor-id", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, flavorIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "flavor-id without storage", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, storagesFlag) | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModelAllTrue(func(model *inputModel) { | ||
| model.Storages = false | ||
| }), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildAndExecuteRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| isValid bool | ||
| listFlavorsFails bool | ||
| listVersionsFails bool | ||
| listStoragesFails bool | ||
| expectListFlavorsCalled bool | ||
| expectListVersionsCalled bool | ||
| expectListStoragesCalled bool | ||
| }{ | ||
| { | ||
| description: "all values", | ||
| model: fixtureInputModelAllTrue(), | ||
| isValid: true, | ||
| expectListFlavorsCalled: true, | ||
| expectListVersionsCalled: true, | ||
| expectListStoragesCalled: true, | ||
| }, | ||
| { | ||
| description: "no values", | ||
| model: fixtureInputModelAllFalse(), | ||
| isValid: true, | ||
| expectListFlavorsCalled: false, | ||
| expectListVersionsCalled: false, | ||
| expectListStoragesCalled: false, | ||
| }, | ||
| { | ||
| description: "only flavors", | ||
| model: fixtureInputModelAllFalse(func(model *inputModel) { model.Flavors = true }), | ||
| isValid: true, | ||
| expectListFlavorsCalled: true, | ||
| }, | ||
| { | ||
| description: "only versions", | ||
| model: fixtureInputModelAllFalse(func(model *inputModel) { model.Versions = true }), | ||
| isValid: true, | ||
| expectListVersionsCalled: true, | ||
| }, | ||
| { | ||
| description: "only storages", | ||
| model: fixtureInputModelAllFalse(func(model *inputModel) { | ||
| model.Storages = true | ||
| model.FlavorId = utils.Ptr("2.4") | ||
| }), | ||
| isValid: true, | ||
| expectListStoragesCalled: true, | ||
| }, | ||
| { | ||
| description: "list flavors fails", | ||
| model: fixtureInputModelAllTrue(), | ||
| isValid: false, | ||
| listFlavorsFails: true, | ||
| expectListFlavorsCalled: true, | ||
| expectListVersionsCalled: false, | ||
| expectListStoragesCalled: false, | ||
| }, | ||
| { | ||
| description: "list versions fails", | ||
| model: fixtureInputModelAllTrue(), | ||
| isValid: false, | ||
| listVersionsFails: true, | ||
| expectListFlavorsCalled: true, | ||
| expectListVersionsCalled: true, | ||
| expectListStoragesCalled: false, | ||
| }, | ||
| { | ||
| description: "list storages fails", | ||
| model: fixtureInputModelAllTrue(), | ||
| isValid: false, | ||
| listStoragesFails: true, | ||
| expectListFlavorsCalled: true, | ||
| expectListVersionsCalled: true, | ||
| expectListStoragesCalled: true, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := &print.Printer{} | ||
| cmd := NewCmd(p) | ||
| p.Cmd = cmd | ||
| client := &sqlServerFlexClientMocked{ | ||
| listFlavorsFails: tt.listFlavorsFails, | ||
| listVersionsFails: tt.listVersionsFails, | ||
| listStoragesFails: tt.listStoragesFails, | ||
| } | ||
| err := buildAndExecuteRequest(testCtx, p, tt.model, client) | ||
| if err != nil && tt.isValid { | ||
| t.Fatalf("error building and executing request: %v", err) | ||
| } | ||
| if err == nil && !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| if tt.expectListFlavorsCalled != client.listFlavorsCalled { | ||
| t.Fatalf("expected listFlavorsCalled to be %v, got %v", tt.expectListFlavorsCalled, client.listFlavorsCalled) | ||
| } | ||
| if tt.expectListVersionsCalled != client.listVersionsCalled { | ||
| t.Fatalf("expected listVersionsCalled to be %v, got %v", tt.expectListVersionsCalled, client.listVersionsCalled) | ||
| } | ||
| if tt.expectListStoragesCalled != client.listStoragesCalled { | ||
| t.Fatalf("expected listStoragesCalled to be %v, got %v", tt.expectListStoragesCalled, client.listStoragesCalled) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package options | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/client" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/tables" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| ) | ||
| const ( | ||
| flavorsFlag = "flavors" | ||
| versionsFlag = "versions" | ||
| storagesFlag = "storages" | ||
| flavorIdFlag = "flavor-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| Flavors bool | ||
| Versions bool | ||
| Storages bool | ||
| FlavorId *string | ||
| } | ||
| type options struct { | ||
| Flavors *[]sqlserverflex.InstanceFlavorEntry `json:"flavors,omitempty"` | ||
| Versions *[]string `json:"versions,omitempty"` | ||
| Storages *flavorStorages `json:"flavorStorages,omitempty"` | ||
| } | ||
| type flavorStorages struct { | ||
| FlavorId string `json:"flavorId"` | ||
| Storages *sqlserverflex.ListStoragesResponse `json:"storages"` | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "options", | ||
| Short: "Lists SQL Server Flex options", | ||
| Long: "Lists SQL Server Flex options (flavors, versions and storages for a given flavor)\nPass one or more flags to filter what categories are shown.", | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `List SQL Server Flex flavors options`, | ||
| "$ stackit sqlserverflex options --flavors"), | ||
| examples.NewExample( | ||
| `List SQL Server Flex available versions`, | ||
| "$ stackit sqlserverflex options --versions"), | ||
| examples.NewExample( | ||
| `List SQL Server Flex storage options for a given flavor. The flavor ID can be retrieved by running "$ stackit sqlserverflex options --flavors"`, | ||
| "$ stackit sqlserverflex options --storages --flavor-id <FLAVOR_ID>"), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Call API | ||
| err = buildAndExecuteRequest(ctx, p, model, apiClient) | ||
| if err != nil { | ||
| return fmt.Errorf("get SQL Server Flex options: %w", err) | ||
| } | ||
| return nil | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Bool(flavorsFlag, false, "Lists supported flavors") | ||
| cmd.Flags().Bool(versionsFlag, false, "Lists supported versions") | ||
| cmd.Flags().Bool(storagesFlag, false, "Lists supported storages for a given flavor") | ||
| cmd.Flags().String(flavorIdFlag, "", `The flavor ID to show storages for. Only relevant when "--storages" is passed`) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| flavors := flags.FlagToBoolValue(p, cmd, flavorsFlag) | ||
| versions := flags.FlagToBoolValue(p, cmd, versionsFlag) | ||
| storages := flags.FlagToBoolValue(p, cmd, storagesFlag) | ||
| flavorId := flags.FlagToStringPointer(p, cmd, flavorIdFlag) | ||
| if !flavors && !versions && !storages { | ||
| return nil, fmt.Errorf("%s\n\n%s", | ||
| "please specify at least one category for which to list the available options.", | ||
| "Get details on the available flags by re-running your command with the --help flag.") | ||
| } | ||
| if storages && flavorId == nil { | ||
| return nil, fmt.Errorf("%s\n\n%s\n%s", | ||
| `please specify a flavor ID to show storages for by setting the flag "--flavor-id <FLAVOR_ID>".`, | ||
| "You can get the available flavor IDs by running:", | ||
| " $ stackit sqlserverflex options --flavors") | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| Flavors: flavors, | ||
| Versions: versions, | ||
| Storages: storages, | ||
| FlavorId: flags.FlagToStringPointer(p, cmd, flavorIdFlag), | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| type sqlServerFlexOptionsClient interface { | ||
| ListFlavorsExecute(ctx context.Context, projectId string) (*sqlserverflex.ListFlavorsResponse, error) | ||
| ListVersionsExecute(ctx context.Context, projectId string) (*sqlserverflex.ListVersionsResponse, error) | ||
| ListStoragesExecute(ctx context.Context, projectId, flavorId string) (*sqlserverflex.ListStoragesResponse, error) | ||
| } | ||
| func buildAndExecuteRequest(ctx context.Context, p *print.Printer, model *inputModel, apiClient sqlServerFlexOptionsClient) error { | ||
| var flavors *sqlserverflex.ListFlavorsResponse | ||
| var versions *sqlserverflex.ListVersionsResponse | ||
| var storages *sqlserverflex.ListStoragesResponse | ||
| var err error | ||
| if model.Flavors { | ||
| flavors, err = apiClient.ListFlavorsExecute(ctx, model.ProjectId) | ||
| if err != nil { | ||
| return fmt.Errorf("get SQL Server Flex flavors: %w", err) | ||
| } | ||
| } | ||
| if model.Versions { | ||
| versions, err = apiClient.ListVersionsExecute(ctx, model.ProjectId) | ||
| if err != nil { | ||
| return fmt.Errorf("get SQL Server Flex versions: %w", err) | ||
| } | ||
| } | ||
| if model.Storages { | ||
| storages, err = apiClient.ListStoragesExecute(ctx, model.ProjectId, *model.FlavorId) | ||
| if err != nil { | ||
| return fmt.Errorf("get SQL Server Flex storages: %w", err) | ||
| } | ||
| } | ||
| return outputResult(p, model, flavors, versions, storages) | ||
| } | ||
| func outputResult(p *print.Printer, model *inputModel, flavors *sqlserverflex.ListFlavorsResponse, versions *sqlserverflex.ListVersionsResponse, storages *sqlserverflex.ListStoragesResponse) error { | ||
| options := &options{} | ||
| if flavors != nil { | ||
| options.Flavors = flavors.Flavors | ||
| } | ||
| if versions != nil { | ||
| options.Versions = versions.Versions | ||
| } | ||
| if storages != nil && model.FlavorId != nil { | ||
| options.Storages = &flavorStorages{ | ||
| FlavorId: *model.FlavorId, | ||
| Storages: storages, | ||
| } | ||
| } | ||
| switch model.OutputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(options, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal SQL Server Flex options: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(options, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal SQL Server Flex options: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| return outputResultAsTable(p, model, options) | ||
| } | ||
| } | ||
| func outputResultAsTable(p *print.Printer, model *inputModel, options *options) error { | ||
| content := "" | ||
| if model.Flavors { | ||
| content += renderFlavors(*options.Flavors) | ||
| } | ||
| if model.Versions { | ||
| content += renderVersions(*options.Versions) | ||
| } | ||
| if model.Storages { | ||
| content += renderStorages(options.Storages.Storages) | ||
| } | ||
| err := p.PagerDisplay(content) | ||
| if err != nil { | ||
| return fmt.Errorf("display output: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
| func renderFlavors(flavors []sqlserverflex.InstanceFlavorEntry) string { | ||
| if len(flavors) == 0 { | ||
| return "" | ||
| } | ||
| table := tables.NewTable() | ||
| table.SetTitle("Flavors") | ||
| table.SetHeader("ID", "CPU", "MEMORY", "DESCRIPTION", "VALID INSTANCE TYPES") | ||
| for i := range flavors { | ||
| f := flavors[i] | ||
| table.AddRow(*f.Id, *f.Cpu, *f.Memory, *f.Description, *f.Categories) | ||
| } | ||
| return table.Render() | ||
| } | ||
| func renderVersions(versions []string) string { | ||
| if len(versions) == 0 { | ||
| return "" | ||
| } | ||
| table := tables.NewTable() | ||
| table.SetTitle("Versions") | ||
| table.SetHeader("VERSION") | ||
| for i := range versions { | ||
| v := versions[i] | ||
| table.AddRow(v) | ||
| } | ||
| return table.Render() | ||
| } | ||
| func renderStorages(resp *sqlserverflex.ListStoragesResponse) string { | ||
| if resp.StorageClasses == nil || len(*resp.StorageClasses) == 0 { | ||
| return "" | ||
| } | ||
| storageClasses := *resp.StorageClasses | ||
| table := tables.NewTable() | ||
| table.SetTitle("Storages") | ||
| table.SetHeader("MINIMUM", "MAXIMUM", "STORAGE CLASS") | ||
| for i := range storageClasses { | ||
| sc := storageClasses[i] | ||
| table.AddRow(*resp.StorageRange.Min, *resp.StorageRange.Max, sc) | ||
| } | ||
| table.EnableAutoMergeOnColumns(1, 2, 3) | ||
| return table.Render() | ||
| } |
| package sqlserverflex | ||
| import ( | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/options" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "sqlserverflex", | ||
| Short: "Provides functionality for SQLServer Flex", | ||
| Long: "Provides functionality for SQLServer Flex.", | ||
| Args: args.NoArgs, | ||
| Run: utils.CmdHelp, | ||
| } | ||
| addSubcommands(cmd, p) | ||
| return cmd | ||
| } | ||
| func addSubcommands(cmd *cobra.Command, p *print.Printer) { | ||
| cmd.AddCommand(instance.NewCmd(p)) | ||
| cmd.AddCommand(options.NewCmd(p)) | ||
| } |
| package cmd | ||
| import ( | ||
| "errors" | ||
| "testing" | ||
| "github.com/spf13/cobra" | ||
| pkgErrors "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| ) | ||
| var cmd *cobra.Command | ||
| var service *cobra.Command | ||
| var resource *cobra.Command | ||
| var operation *cobra.Command | ||
| func setupCmd() { | ||
| cmd = &cobra.Command{ | ||
| Use: "stackit", | ||
| } | ||
| service = &cobra.Command{ | ||
| Use: "service", | ||
| } | ||
| resource = &cobra.Command{ | ||
| Use: "resource", | ||
| } | ||
| operation = &cobra.Command{ | ||
| Use: "operation", | ||
| } | ||
| cmd.AddCommand(service) | ||
| service.AddCommand(resource) | ||
| resource.AddCommand(operation) | ||
| } | ||
| func TestBeautifyUnknownAndMissingCommandsError(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| inputError error | ||
| command *cobra.Command | ||
| expectedMsg string | ||
| isNotUnknownFlagError bool | ||
| }{ | ||
| { | ||
| description: "root command, extra input is a flag", | ||
| inputError: errors.New("unknown flag: --something"), | ||
| command: cmd, | ||
| expectedMsg: pkgErrors.SUBCOMMAND_MISSING, | ||
| }, | ||
| { | ||
| description: "non unknown flag error, return the same", | ||
| inputError: errors.New("some error"), | ||
| command: cmd, | ||
| expectedMsg: "some error", | ||
| isNotUnknownFlagError: true, | ||
| }, | ||
| } | ||
| setupCmd() | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| actualError := beautifyUnknownAndMissingCommandsError(cmd, tt.inputError) | ||
| if tt.isNotUnknownFlagError { | ||
| if actualError.Error() != tt.expectedMsg { | ||
| t.Fatalf("expected error message to be %s, got %s", tt.expectedMsg, actualError.Error()) | ||
| } | ||
| return | ||
| } | ||
| appendedErr := pkgErrors.AppendUsageTip(errors.New(tt.expectedMsg), cmd) | ||
| if actualError.Error() != appendedErr.Error() { | ||
| t.Fatalf("expected error to be %s, got %s", appendedErr.Error(), actualError.Error()) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package config | ||
| import ( | ||
| "os" | ||
| "path/filepath" | ||
| "testing" | ||
| "github.com/spf13/viper" | ||
| ) | ||
| func TestWrite(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| folderName string | ||
| folderExists bool | ||
| }{ | ||
| { | ||
| description: "write config file", | ||
| folderName: "", | ||
| }, | ||
| { | ||
| description: "write config file to new folder", | ||
| folderName: "new-folder", | ||
| }, | ||
| { | ||
| description: "write config file to existing folder", | ||
| folderName: "existing-folder", | ||
| folderExists: true, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| configPath := filepath.Join(os.TempDir(), tt.folderName, "config.json") | ||
| viper.SetConfigFile(configPath) | ||
| folderPath = filepath.Dir(configPath) | ||
| if tt.folderExists { | ||
| err := os.MkdirAll(folderPath, os.ModePerm) | ||
| if err != nil { | ||
| t.Fatalf("expected error to be nil, got %v", err) | ||
| } | ||
| } | ||
| err := Write() | ||
| if err != nil { | ||
| t.Fatalf("expected error to be nil, got %v", err) | ||
| } | ||
| // Check if the file was created | ||
| _, err = os.Stat(configPath) | ||
| if os.IsNotExist(err) { | ||
| t.Fatalf("expected file to exist, got %v", err) | ||
| } | ||
| // Delete the file | ||
| err = os.Remove(configPath) | ||
| if err != nil { | ||
| t.Fatalf("expected error to be nil, got %v", err) | ||
| } | ||
| // Delete the folder | ||
| if tt.folderName != "" { | ||
| err = os.Remove(folderPath) | ||
| if err != nil { | ||
| t.Fatalf("expected error to be nil, got %v", err) | ||
| } | ||
| } | ||
| }) | ||
| } | ||
| } |
| package errors | ||
| import ( | ||
| "errors" | ||
| "fmt" | ||
| "testing" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| var cmd *cobra.Command | ||
| var service *cobra.Command | ||
| var resource *cobra.Command | ||
| var operation *cobra.Command | ||
| func setupCmd() { | ||
| cmd = &cobra.Command{ | ||
| Use: "stackit", | ||
| } | ||
| service = &cobra.Command{ | ||
| Use: "service", | ||
| } | ||
| resource = &cobra.Command{ | ||
| Use: "resource", | ||
| } | ||
| operation = &cobra.Command{ | ||
| Use: "operation", | ||
| } | ||
| cmd.AddCommand(service) | ||
| service.AddCommand(resource) | ||
| resource.AddCommand(operation) | ||
| } | ||
| func setupBetaCmd() { | ||
| cmd = &cobra.Command{ | ||
| Use: "stackit", | ||
| } | ||
| beta := &cobra.Command{ | ||
| Use: "beta", | ||
| } | ||
| service = &cobra.Command{ | ||
| Use: "service", | ||
| } | ||
| resource = &cobra.Command{ | ||
| Use: "resource", | ||
| } | ||
| operation = &cobra.Command{ | ||
| Use: "operation", | ||
| } | ||
| cmd.AddCommand(beta) | ||
| beta.AddCommand(service) | ||
| service.AddCommand(resource) | ||
| resource.AddCommand(operation) | ||
| } | ||
| func TestSimpleErrors(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| err error | ||
| expectedMsg string | ||
| }{ | ||
| { | ||
| description: "Test ProjectIdError", | ||
| err: &ProjectIdError{}, | ||
| expectedMsg: MISSING_PROJECT_ID, | ||
| }, | ||
| { | ||
| description: "Test EmptyUpdateError", | ||
| err: &EmptyUpdateError{}, | ||
| expectedMsg: EMPTY_UPDATE, | ||
| }, | ||
| { | ||
| description: "Test AuthError", | ||
| err: &AuthError{}, | ||
| expectedMsg: FAILED_AUTH, | ||
| }, | ||
| { | ||
| description: "Test ActivateServiceAccountError", | ||
| err: &ActivateServiceAccountError{}, | ||
| expectedMsg: FAILED_SERVICE_ACCOUNT_ACTIVATION, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| if tt.err.Error() != tt.expectedMsg { | ||
| t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, tt.err.Error()) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestArgusInputPlanError(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| args []string | ||
| expectedMsg string | ||
| }{ | ||
| { | ||
| description: "base", | ||
| args: []string{"arg1", "arg2"}, | ||
| expectedMsg: fmt.Sprintf(ARGUS_INVALID_INPUT_PLAN, "stackit service resource operation arg1 arg2", "service"), | ||
| }, | ||
| { | ||
| description: "no args", | ||
| args: []string{}, | ||
| expectedMsg: fmt.Sprintf(ARGUS_INVALID_INPUT_PLAN, "stackit service resource operation", "service"), | ||
| }, | ||
| } | ||
| setupCmd() | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| err := &ArgusInputPlanError{ | ||
| Cmd: operation, | ||
| Args: tt.args, | ||
| } | ||
| if err.Error() != tt.expectedMsg { | ||
| t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error()) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestArgusInvalidPlanError(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| details string | ||
| service string | ||
| expectedMsg string | ||
| }{ | ||
| { | ||
| description: "base", | ||
| details: "details", | ||
| service: "service", | ||
| expectedMsg: fmt.Sprintf(ARGUS_INVALID_PLAN, "details", "service"), | ||
| }, | ||
| { | ||
| description: "no details", | ||
| details: "", | ||
| service: "service", | ||
| expectedMsg: fmt.Sprintf(ARGUS_INVALID_PLAN, "", "service"), | ||
| }, | ||
| { | ||
| description: "no service", | ||
| details: "details", | ||
| service: "", | ||
| expectedMsg: fmt.Sprintf(ARGUS_INVALID_PLAN, "details", ""), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| err := &ArgusInvalidPlanError{ | ||
| Service: tt.service, | ||
| Details: tt.details, | ||
| } | ||
| if err.Error() != tt.expectedMsg { | ||
| t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error()) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestDSAInputPlanError(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| args []string | ||
| expectedMsg string | ||
| }{ | ||
| { | ||
| description: "base", | ||
| args: []string{"arg1", "arg2"}, | ||
| expectedMsg: fmt.Sprintf(DSA_INVALID_INPUT_PLAN, "stackit service resource operation arg1 arg2", "service"), | ||
| }, | ||
| { | ||
| description: "no args", | ||
| args: []string{}, | ||
| expectedMsg: fmt.Sprintf(DSA_INVALID_INPUT_PLAN, "stackit service resource operation", "service"), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| setupCmd() | ||
| err := &DSAInputPlanError{ | ||
| Cmd: operation, | ||
| Args: tt.args, | ||
| } | ||
| if err.Error() != tt.expectedMsg { | ||
| t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error()) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestDSAInvalidPlanError(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| details string | ||
| service string | ||
| expectedMsg string | ||
| }{ | ||
| { | ||
| description: "base", | ||
| details: "details", | ||
| service: "service", | ||
| expectedMsg: fmt.Sprintf(DSA_INVALID_PLAN, "details", "service"), | ||
| }, | ||
| { | ||
| description: "no details", | ||
| details: "", | ||
| service: "service", | ||
| expectedMsg: fmt.Sprintf(DSA_INVALID_PLAN, "", "service"), | ||
| }, | ||
| { | ||
| description: "no service", | ||
| details: "details", | ||
| service: "", | ||
| expectedMsg: fmt.Sprintf(DSA_INVALID_PLAN, "details", ""), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| err := &DSAInvalidPlanError{ | ||
| Service: tt.service, | ||
| Details: tt.details, | ||
| } | ||
| if err.Error() != tt.expectedMsg { | ||
| t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error()) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestDatabaseInputFlavorError(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| args []string | ||
| service string | ||
| expectedMsg string | ||
| isBetaCmd bool | ||
| }{ | ||
| { | ||
| description: "no service", | ||
| args: []string{"arg1", "arg2"}, | ||
| expectedMsg: fmt.Sprintf(DATABASE_INVALID_INPUT_FLAVOR, "stackit service resource operation arg1 arg2", "service"), | ||
| }, | ||
| { | ||
| description: "with service", | ||
| args: []string{"arg1", "arg2"}, | ||
| service: "beta service", | ||
| expectedMsg: fmt.Sprintf(DATABASE_INVALID_INPUT_FLAVOR, "stackit beta service resource operation arg1 arg2", "beta service"), | ||
| isBetaCmd: true, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| if tt.isBetaCmd { | ||
| setupBetaCmd() | ||
| } else { | ||
| setupCmd() | ||
| } | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| err := &DatabaseInputFlavorError{ | ||
| Cmd: operation, | ||
| Args: tt.args, | ||
| Service: tt.service, | ||
| } | ||
| if err.Error() != tt.expectedMsg { | ||
| t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error()) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestDatabaseInvalidFlavorError(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| details string | ||
| service string | ||
| expectedMsg string | ||
| }{ | ||
| { | ||
| description: "base", | ||
| details: "details", | ||
| service: "service", | ||
| expectedMsg: fmt.Sprintf(DATABASE_INVALID_FLAVOR, "details", "service"), | ||
| }, | ||
| { | ||
| description: "no details", | ||
| details: "", | ||
| service: "service", | ||
| expectedMsg: fmt.Sprintf(DATABASE_INVALID_FLAVOR, "", "service"), | ||
| }, | ||
| { | ||
| description: "no service", | ||
| details: "details", | ||
| service: "", | ||
| expectedMsg: fmt.Sprintf(DATABASE_INVALID_FLAVOR, "details", ""), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| err := &DatabaseInvalidFlavorError{ | ||
| Service: tt.service, | ||
| Details: tt.details, | ||
| } | ||
| if err.Error() != tt.expectedMsg { | ||
| t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error()) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestDatabaseInvalidStorageError(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| details string | ||
| service string | ||
| flavorId string | ||
| expectedMsg string | ||
| }{ | ||
| { | ||
| description: "base", | ||
| details: "details", | ||
| service: "service", | ||
| flavorId: "flavorId", | ||
| expectedMsg: fmt.Sprintf(DATABASE_INVALID_STORAGE, "details", "service", "flavorId"), | ||
| }, | ||
| { | ||
| description: "no details", | ||
| details: "", | ||
| service: "service", | ||
| flavorId: "flavorId", | ||
| expectedMsg: fmt.Sprintf(DATABASE_INVALID_STORAGE, "", "service", "flavorId"), | ||
| }, | ||
| { | ||
| description: "no service", | ||
| details: "details", | ||
| service: "", | ||
| flavorId: "flavorId", | ||
| expectedMsg: fmt.Sprintf(DATABASE_INVALID_STORAGE, "details", "", "flavorId"), | ||
| }, | ||
| { | ||
| description: "no flavorId", | ||
| details: "details", | ||
| service: "service", | ||
| flavorId: "", | ||
| expectedMsg: fmt.Sprintf(DATABASE_INVALID_STORAGE, "details", "service", ""), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| err := &DatabaseInvalidStorageError{ | ||
| Service: tt.service, | ||
| Details: tt.details, | ||
| FlavorId: tt.flavorId, | ||
| } | ||
| if err.Error() != tt.expectedMsg { | ||
| t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error()) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestFlagValidationError(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| flag string | ||
| details string | ||
| expectedMsg string | ||
| }{ | ||
| { | ||
| description: "base", | ||
| flag: "flag", | ||
| details: "details", | ||
| expectedMsg: fmt.Sprintf(FLAG_VALIDATION, "flag", "details"), | ||
| }, | ||
| { | ||
| description: "no flag", | ||
| flag: "", | ||
| details: "details", | ||
| expectedMsg: fmt.Sprintf(FLAG_VALIDATION, "", "details"), | ||
| }, | ||
| { | ||
| description: "no details", | ||
| flag: "flag", | ||
| details: "", | ||
| expectedMsg: fmt.Sprintf(FLAG_VALIDATION, "flag", ""), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| err := &FlagValidationError{ | ||
| Flag: tt.flag, | ||
| Details: tt.details, | ||
| } | ||
| if err.Error() != tt.expectedMsg { | ||
| t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error()) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestRequiredMutuallyExclusiveFlagsError(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| flags []string | ||
| expectedMsg string | ||
| }{ | ||
| { | ||
| description: "base", | ||
| flags: []string{"flag1", "flag2"}, | ||
| expectedMsg: fmt.Sprintf(REQUIRED_MUTUALLY_EXCLUSIVE_FLAGS, "flag1, flag2"), | ||
| }, | ||
| { | ||
| description: "no flags", | ||
| flags: []string{}, | ||
| expectedMsg: fmt.Sprintf(REQUIRED_MUTUALLY_EXCLUSIVE_FLAGS, ""), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| err := &RequiredMutuallyExclusiveFlagsError{ | ||
| Flags: tt.flags, | ||
| } | ||
| if err.Error() != tt.expectedMsg { | ||
| t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error()) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestArgValidationError(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| arg string | ||
| details string | ||
| expectedMsg string | ||
| }{ | ||
| { | ||
| description: "base", | ||
| arg: "arg", | ||
| details: "details", | ||
| expectedMsg: fmt.Sprintf(ARG_VALIDATION, "arg", "details"), | ||
| }, | ||
| { | ||
| description: "no arg", | ||
| arg: "", | ||
| details: "details", | ||
| expectedMsg: fmt.Sprintf(ARG_VALIDATION, "", "details"), | ||
| }, | ||
| { | ||
| description: "no details", | ||
| arg: "arg", | ||
| details: "", | ||
| expectedMsg: fmt.Sprintf(ARG_VALIDATION, "arg", ""), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| err := &ArgValidationError{ | ||
| Arg: tt.arg, | ||
| Details: tt.details, | ||
| } | ||
| if err.Error() != tt.expectedMsg { | ||
| t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error()) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestSingleArgExpectedError(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| expected string | ||
| count int | ||
| expectedMsg string | ||
| }{ | ||
| { | ||
| description: "base", | ||
| expected: "expected", | ||
| count: 1, | ||
| expectedMsg: fmt.Sprintf(ARG_MISSING, "expected"), | ||
| }, | ||
| { | ||
| description: "multiple", | ||
| expected: "expected", | ||
| count: 2, | ||
| expectedMsg: fmt.Sprintf(SINGLE_ARG_EXPECTED, "expected", 2), | ||
| }, | ||
| } | ||
| setupCmd() | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| err := &SingleArgExpectedError{ | ||
| Expected: tt.expected, | ||
| Count: tt.count, | ||
| Cmd: operation, | ||
| } | ||
| appendedErr := AppendUsageTip(errors.New(tt.expectedMsg), operation) | ||
| if err.Error() != appendedErr.Error() { | ||
| t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error()) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestSingleOptionalArgExpectedError(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| expected string | ||
| count int | ||
| expectedMsg string | ||
| }{ | ||
| { | ||
| description: "base", | ||
| expected: "expected", | ||
| count: 1, | ||
| expectedMsg: fmt.Sprintf(SINGLE_OPTIONAL_ARG_EXPECTED, "expected", 1), | ||
| }, | ||
| { | ||
| description: "multiple", | ||
| expected: "expected", | ||
| count: 2, | ||
| expectedMsg: fmt.Sprintf(SINGLE_OPTIONAL_ARG_EXPECTED, "expected", 2), | ||
| }, | ||
| { | ||
| description: "no count", | ||
| expected: "expected", | ||
| count: 0, | ||
| expectedMsg: fmt.Sprintf(SINGLE_OPTIONAL_ARG_EXPECTED, "expected", 0), | ||
| }, | ||
| } | ||
| setupCmd() | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| err := &SingleOptionalArgExpectedError{ | ||
| Expected: tt.expected, | ||
| Count: tt.count, | ||
| Cmd: operation, | ||
| } | ||
| appendedErr := AppendUsageTip(errors.New(tt.expectedMsg), operation) | ||
| if err.Error() != appendedErr.Error() { | ||
| t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error()) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestInputUnknownError(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| input string | ||
| command *cobra.Command | ||
| expectedMsg string | ||
| }{ | ||
| { | ||
| description: "extra argument, not a subcommand", | ||
| input: "extra", | ||
| command: operation, | ||
| expectedMsg: fmt.Sprintf(ARG_UNKNOWN, "extra"), | ||
| }, | ||
| { | ||
| description: "extra subcommand", | ||
| input: "extra", | ||
| command: service, | ||
| expectedMsg: fmt.Sprintf(SUBCOMMAND_UNKNOWN, "extra"), | ||
| }, | ||
| } | ||
| setupCmd() | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| err := &InputUnknownError{ | ||
| ProvidedInput: tt.input, | ||
| Cmd: tt.command, | ||
| } | ||
| appendedErr := AppendUsageTip(errors.New(tt.expectedMsg), tt.command) | ||
| if err.Error() != appendedErr.Error() { | ||
| t.Fatalf("expected error to be %s, got %s", appendedErr.Error(), err.Error()) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestSubcommandMissingError(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| expectedMsg string | ||
| }{ | ||
| { | ||
| description: "base", | ||
| expectedMsg: SUBCOMMAND_MISSING, | ||
| }, | ||
| } | ||
| setupCmd() | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| err := &SubcommandMissingError{ | ||
| Cmd: cmd, | ||
| } | ||
| appendedErr := AppendUsageTip(errors.New(tt.expectedMsg), cmd) | ||
| if err.Error() != appendedErr.Error() { | ||
| t.Fatalf("expected error to be %s, got %s", tt.expectedMsg, err.Error()) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestAppendUsageTip(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| err error | ||
| expectedError error | ||
| }{ | ||
| { | ||
| description: "base", | ||
| err: fmt.Errorf("error"), | ||
| expectedError: fmt.Errorf("%w.\n\n%s", fmt.Errorf("error"), fmt.Sprintf(USAGE_TIP, "stackit")), | ||
| }, | ||
| } | ||
| setupCmd() | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| err := AppendUsageTip(tt.err, cmd) | ||
| if err.Error() != tt.expectedError.Error() { | ||
| t.Fatalf("expected error to be %s, got %s", tt.expectedError, err.Error()) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package fileutils | ||
| import ( | ||
| "os" | ||
| "testing" | ||
| ) | ||
| const outputFilePath = "./testPayload.json" | ||
| func TestWriteToFile(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| content string | ||
| outputFile string | ||
| }{ | ||
| { | ||
| description: "write into file", | ||
| content: "Test message", | ||
| outputFile: outputFilePath, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| err := WriteToFile(tt.outputFile, tt.content) | ||
| if err != nil { | ||
| t.Fatalf("unexpected error: %s", err.Error()) | ||
| } | ||
| output, err := os.ReadFile(tt.outputFile) | ||
| if err != nil { | ||
| t.Fatalf("unexpected error: %s", err.Error()) | ||
| } | ||
| if string(output) != tt.content { | ||
| t.Errorf("unexpected output: got %q, want %q", output, tt.content) | ||
| } | ||
| }) | ||
| } | ||
| // Cleanup | ||
| err := os.RemoveAll(outputFilePath) | ||
| if err != nil { | ||
| t.Errorf("failed cleaning test data") | ||
| } | ||
| } |
| package fileutils | ||
| import ( | ||
| "fmt" | ||
| "os" | ||
| ) | ||
| func WriteToFile(outputFileName, content string) (err error) { | ||
| fo, err := os.Create(outputFileName) | ||
| if err != nil { | ||
| return fmt.Errorf("create output file: %w", err) | ||
| } | ||
| defer func() { | ||
| tempErr := fo.Close() | ||
| if tempErr != nil { | ||
| if err != nil { | ||
| err = fmt.Errorf("%w; close output file: %w", err, tempErr) | ||
| } else { | ||
| err = fmt.Errorf("close output file: %w", tempErr) | ||
| } | ||
| } | ||
| }() | ||
| _, err = fo.WriteString(content) | ||
| if err != nil { | ||
| return fmt.Errorf("write content to output file: %w", err) | ||
| } | ||
| return err | ||
| } |
| package projectname | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/google/uuid" | ||
| "github.com/spf13/cobra" | ||
| "github.com/spf13/viper" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/config" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| ) | ||
| var testProjectId = uuid.NewString() | ||
| func TestGetProjectName(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| projectName string | ||
| projectId string | ||
| isValid bool | ||
| }{ | ||
| { | ||
| description: "Project name from config", | ||
| projectName: "project-name", | ||
| projectId: testProjectId, | ||
| isValid: true, | ||
| }, | ||
| { | ||
| description: "empty project name and id", | ||
| projectName: "", | ||
| projectId: "", | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| viper.Set(config.ProjectNameKey, tt.projectName) | ||
| viper.Set(config.ProjectIdKey, tt.projectId) | ||
| defer viper.Reset() | ||
| p := print.NewPrinter() | ||
| cmd := &cobra.Command{} | ||
| projectName, err := GetProjectName(context.Background(), p, cmd) | ||
| if err != nil { | ||
| if tt.isValid { | ||
| t.Fatalf("unexpected error: %v", err) | ||
| } | ||
| return | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("expected error, got project name %q", projectName) | ||
| } | ||
| if projectName != tt.projectName { | ||
| t.Fatalf("expected project name %q, got %q", tt.projectName, projectName) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package client | ||
| import ( | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/auth" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/config" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/spf13/viper" | ||
| sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| ) | ||
| func ConfigureClient(p *print.Printer) (*sqlserverflex.APIClient, error) { | ||
| var err error | ||
| var apiClient *sqlserverflex.APIClient | ||
| var cfgOptions []sdkConfig.ConfigurationOption | ||
| authCfgOption, err := auth.AuthenticationConfig(p, auth.AuthorizeUser) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "configure authentication: %v", err) | ||
| return nil, &errors.AuthError{} | ||
| } | ||
| cfgOptions = append(cfgOptions, authCfgOption, sdkConfig.WithRegion("eu01")) | ||
| customEndpoint := viper.GetString(config.SQLServerFlexCustomEndpointKey) | ||
| if customEndpoint != "" { | ||
| cfgOptions = append(cfgOptions, sdkConfig.WithEndpoint(customEndpoint)) | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| cfgOptions = append(cfgOptions, | ||
| sdkConfig.WithMiddleware(print.RequestResponseCapturer(p, nil)), | ||
| ) | ||
| } | ||
| apiClient, err = sqlserverflex.NewAPIClient(cfgOptions...) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "create new API client: %v", err) | ||
| return nil, &errors.AuthError{} | ||
| } | ||
| return apiClient, nil | ||
| } |
| package utils | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| ) | ||
| var ( | ||
| testProjectId = uuid.NewString() | ||
| testInstanceId = uuid.NewString() | ||
| ) | ||
| const ( | ||
| testInstanceName = "instance" | ||
| testUserName = "user" | ||
| ) | ||
| type sqlServerFlexClientMocked struct { | ||
| listVersionsFails bool | ||
| listVersionsResp *sqlserverflex.ListVersionsResponse | ||
| getInstanceFails bool | ||
| getInstanceResp *sqlserverflex.GetInstanceResponse | ||
| getUserFails bool | ||
| getUserResp *sqlserverflex.GetUserResponse | ||
| listRestoreJobsFails bool | ||
| listRestoreJobsResp *sqlserverflex.ListRestoreJobsResponse | ||
| } | ||
| func (m *sqlServerFlexClientMocked) ListVersionsExecute(_ context.Context, _ string) (*sqlserverflex.ListVersionsResponse, error) { | ||
| if m.listVersionsFails { | ||
| return nil, fmt.Errorf("could not list versions") | ||
| } | ||
| return m.listVersionsResp, nil | ||
| } | ||
| func (m *sqlServerFlexClientMocked) ListRestoreJobsExecute(_ context.Context, _, _ string) (*sqlserverflex.ListRestoreJobsResponse, error) { | ||
| if m.listRestoreJobsFails { | ||
| return nil, fmt.Errorf("could not list versions") | ||
| } | ||
| return m.listRestoreJobsResp, nil | ||
| } | ||
| func (m *sqlServerFlexClientMocked) GetInstanceExecute(_ context.Context, _, _ string) (*sqlserverflex.GetInstanceResponse, error) { | ||
| if m.getInstanceFails { | ||
| return nil, fmt.Errorf("could not get instance") | ||
| } | ||
| return m.getInstanceResp, nil | ||
| } | ||
| func (m *sqlServerFlexClientMocked) GetUserExecute(_ context.Context, _, _, _ string) (*sqlserverflex.GetUserResponse, error) { | ||
| if m.getUserFails { | ||
| return nil, fmt.Errorf("could not get user") | ||
| } | ||
| return m.getUserResp, nil | ||
| } | ||
| func TestValidateStorage(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| storageClass *string | ||
| storageSize *int64 | ||
| storages *sqlserverflex.ListStoragesResponse | ||
| isValid bool | ||
| }{ | ||
| { | ||
| description: "base", | ||
| storageClass: utils.Ptr("foo"), | ||
| storageSize: utils.Ptr(int64(10)), | ||
| storages: &sqlserverflex.ListStoragesResponse{ | ||
| StorageClasses: &[]string{"bar-1", "bar-2", "foo"}, | ||
| StorageRange: &sqlserverflex.StorageRange{ | ||
| Min: utils.Ptr(int64(5)), | ||
| Max: utils.Ptr(int64(20)), | ||
| }, | ||
| }, | ||
| isValid: true, | ||
| }, | ||
| { | ||
| description: "nil response", | ||
| storageClass: utils.Ptr("foo"), | ||
| storageSize: utils.Ptr(int64(10)), | ||
| storages: nil, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "storage size out of range 1", | ||
| storageClass: utils.Ptr("foo"), | ||
| storageSize: utils.Ptr(int64(1)), | ||
| storages: &sqlserverflex.ListStoragesResponse{ | ||
| StorageClasses: &[]string{"bar-1", "bar-2", "foo"}, | ||
| StorageRange: &sqlserverflex.StorageRange{ | ||
| Min: utils.Ptr(int64(5)), | ||
| Max: utils.Ptr(int64(20)), | ||
| }, | ||
| }, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "storage size out of range 2", | ||
| storageClass: utils.Ptr("foo"), | ||
| storageSize: utils.Ptr(int64(200)), | ||
| storages: &sqlserverflex.ListStoragesResponse{ | ||
| StorageClasses: &[]string{"bar-1", "bar-2", "foo"}, | ||
| StorageRange: &sqlserverflex.StorageRange{ | ||
| Min: utils.Ptr(int64(5)), | ||
| Max: utils.Ptr(int64(20)), | ||
| }, | ||
| }, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "storage size in range limit 1", | ||
| storageClass: utils.Ptr("foo"), | ||
| storageSize: utils.Ptr(int64(5)), | ||
| storages: &sqlserverflex.ListStoragesResponse{ | ||
| StorageClasses: &[]string{"bar-1", "bar-2", "foo"}, | ||
| StorageRange: &sqlserverflex.StorageRange{ | ||
| Min: utils.Ptr(int64(5)), | ||
| Max: utils.Ptr(int64(20)), | ||
| }, | ||
| }, | ||
| isValid: true, | ||
| }, | ||
| { | ||
| description: "storage size in range limit 2", | ||
| storageClass: utils.Ptr("foo"), | ||
| storageSize: utils.Ptr(int64(20)), | ||
| storages: &sqlserverflex.ListStoragesResponse{ | ||
| StorageClasses: &[]string{"bar-1", "bar-2", "foo"}, | ||
| StorageRange: &sqlserverflex.StorageRange{ | ||
| Min: utils.Ptr(int64(5)), | ||
| Max: utils.Ptr(int64(20)), | ||
| }, | ||
| }, | ||
| isValid: true, | ||
| }, | ||
| { | ||
| description: "invalid storage", | ||
| storageClass: utils.Ptr("foo"), | ||
| storageSize: utils.Ptr(int64(10)), | ||
| storages: &sqlserverflex.ListStoragesResponse{ | ||
| StorageClasses: &[]string{"bar-1", "bar-2", "bar-3"}, | ||
| StorageRange: &sqlserverflex.StorageRange{ | ||
| Min: utils.Ptr(int64(5)), | ||
| Max: utils.Ptr(int64(20)), | ||
| }, | ||
| }, | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| err := ValidateStorage(tt.storageClass, tt.storageSize, tt.storages, "flavor-id") | ||
| if tt.isValid && err != nil { | ||
| t.Fatalf("should not have failed: %v", err) | ||
| } | ||
| if !tt.isValid && err == nil { | ||
| t.Fatalf("should have failed") | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestValidateFlavorId(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| flavorId string | ||
| flavors *[]sqlserverflex.InstanceFlavorEntry | ||
| isValid bool | ||
| }{ | ||
| { | ||
| description: "base", | ||
| flavorId: "foo", | ||
| flavors: &[]sqlserverflex.InstanceFlavorEntry{ | ||
| {Id: utils.Ptr("bar-1")}, | ||
| {Id: utils.Ptr("bar-2")}, | ||
| {Id: utils.Ptr("foo")}, | ||
| }, | ||
| isValid: true, | ||
| }, | ||
| { | ||
| description: "nil flavors", | ||
| flavorId: "foo", | ||
| flavors: nil, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no flavors", | ||
| flavorId: "foo", | ||
| flavors: &[]sqlserverflex.InstanceFlavorEntry{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "nil flavor id", | ||
| flavorId: "foo", | ||
| flavors: &[]sqlserverflex.InstanceFlavorEntry{ | ||
| {Id: utils.Ptr("bar-1")}, | ||
| {Id: nil}, | ||
| {Id: utils.Ptr("foo")}, | ||
| }, | ||
| isValid: true, | ||
| }, | ||
| { | ||
| description: "invalid flavor", | ||
| flavorId: "foo", | ||
| flavors: &[]sqlserverflex.InstanceFlavorEntry{ | ||
| {Id: utils.Ptr("bar-1")}, | ||
| {Id: utils.Ptr("bar-2")}, | ||
| {Id: utils.Ptr("bar-3")}, | ||
| }, | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| err := ValidateFlavorId(tt.flavorId, tt.flavors) | ||
| if tt.isValid && err != nil { | ||
| t.Fatalf("should not have failed: %v", err) | ||
| } | ||
| if !tt.isValid && err == nil { | ||
| t.Fatalf("should have failed") | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestLoadFlavorId(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| cpu int64 | ||
| ram int64 | ||
| flavors *[]sqlserverflex.InstanceFlavorEntry | ||
| isValid bool | ||
| expectedOutput *string | ||
| }{ | ||
| { | ||
| description: "base", | ||
| cpu: 2, | ||
| ram: 4, | ||
| flavors: &[]sqlserverflex.InstanceFlavorEntry{ | ||
| { | ||
| Id: utils.Ptr("bar-1"), | ||
| Cpu: utils.Ptr(int64(2)), | ||
| Memory: utils.Ptr(int64(2)), | ||
| }, | ||
| { | ||
| Id: utils.Ptr("bar-2"), | ||
| Cpu: utils.Ptr(int64(4)), | ||
| Memory: utils.Ptr(int64(4)), | ||
| }, | ||
| { | ||
| Id: utils.Ptr("foo"), | ||
| Cpu: utils.Ptr(int64(2)), | ||
| Memory: utils.Ptr(int64(4)), | ||
| }, | ||
| }, | ||
| isValid: true, | ||
| expectedOutput: utils.Ptr("foo"), | ||
| }, | ||
| { | ||
| description: "nil flavors", | ||
| cpu: 2, | ||
| ram: 4, | ||
| flavors: nil, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no flavors", | ||
| cpu: 2, | ||
| ram: 4, | ||
| flavors: &[]sqlserverflex.InstanceFlavorEntry{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "flavors with details missing", | ||
| cpu: 2, | ||
| ram: 4, | ||
| flavors: &[]sqlserverflex.InstanceFlavorEntry{ | ||
| { | ||
| Id: utils.Ptr("bar-1"), | ||
| Cpu: nil, | ||
| Memory: nil, | ||
| }, | ||
| { | ||
| Id: utils.Ptr("bar-2"), | ||
| Cpu: utils.Ptr(int64(4)), | ||
| Memory: utils.Ptr(int64(4)), | ||
| }, | ||
| { | ||
| Id: utils.Ptr("foo"), | ||
| Cpu: utils.Ptr(int64(2)), | ||
| Memory: utils.Ptr(int64(4)), | ||
| }, | ||
| }, | ||
| isValid: true, | ||
| expectedOutput: utils.Ptr("foo"), | ||
| }, | ||
| { | ||
| description: "match with nil id", | ||
| cpu: 2, | ||
| ram: 4, | ||
| flavors: &[]sqlserverflex.InstanceFlavorEntry{ | ||
| { | ||
| Id: utils.Ptr("bar-1"), | ||
| Cpu: utils.Ptr(int64(2)), | ||
| Memory: utils.Ptr(int64(2)), | ||
| }, | ||
| { | ||
| Id: utils.Ptr("bar-2"), | ||
| Cpu: utils.Ptr(int64(4)), | ||
| Memory: utils.Ptr(int64(4)), | ||
| }, | ||
| { | ||
| Id: nil, | ||
| Cpu: utils.Ptr(int64(2)), | ||
| Memory: utils.Ptr(int64(4)), | ||
| }, | ||
| }, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "invalid settings", | ||
| cpu: 2, | ||
| ram: 4, | ||
| flavors: &[]sqlserverflex.InstanceFlavorEntry{ | ||
| { | ||
| Id: utils.Ptr("bar-1"), | ||
| Cpu: utils.Ptr(int64(2)), | ||
| Memory: utils.Ptr(int64(2)), | ||
| }, | ||
| { | ||
| Id: utils.Ptr("bar-2"), | ||
| Cpu: utils.Ptr(int64(4)), | ||
| Memory: utils.Ptr(int64(4)), | ||
| }, | ||
| }, | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| output, err := LoadFlavorId(tt.cpu, tt.ram, tt.flavors) | ||
| if !tt.isValid { | ||
| if err == nil { | ||
| t.Fatalf("should have failed") | ||
| } | ||
| return | ||
| } | ||
| if err != nil { | ||
| t.Fatalf("should not have failed: %v", err) | ||
| } | ||
| if output == nil { | ||
| t.Fatalf("returned nil output") | ||
| } | ||
| diff := cmp.Diff(output, tt.expectedOutput) | ||
| if diff != "" { | ||
| t.Fatalf("outputs do not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestGetInstanceName(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| getInstanceFails bool | ||
| getInstanceResp *sqlserverflex.GetInstanceResponse | ||
| isValid bool | ||
| expectedOutput string | ||
| }{ | ||
| { | ||
| description: "base", | ||
| getInstanceResp: &sqlserverflex.GetInstanceResponse{ | ||
| Item: &sqlserverflex.Instance{ | ||
| Name: utils.Ptr(testInstanceName), | ||
| }, | ||
| }, | ||
| isValid: true, | ||
| expectedOutput: testInstanceName, | ||
| }, | ||
| { | ||
| description: "get instance fails", | ||
| getInstanceFails: true, | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| client := &sqlServerFlexClientMocked{ | ||
| getInstanceFails: tt.getInstanceFails, | ||
| getInstanceResp: tt.getInstanceResp, | ||
| } | ||
| output, err := GetInstanceName(context.Background(), client, testProjectId, testInstanceId) | ||
| if tt.isValid && err != nil { | ||
| t.Errorf("failed on valid input") | ||
| } | ||
| if !tt.isValid && err == nil { | ||
| t.Errorf("did not fail on invalid input") | ||
| } | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| if output != tt.expectedOutput { | ||
| t.Errorf("expected output to be %s, got %s", tt.expectedOutput, output) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package utils | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "strings" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| ) | ||
| const ( | ||
| ServiceCmd = "beta sqlserverflex" | ||
| ) | ||
| type SQLServerFlexClient interface { | ||
| ListVersionsExecute(ctx context.Context, projectId string) (*sqlserverflex.ListVersionsResponse, error) | ||
| GetInstanceExecute(ctx context.Context, projectId, instanceId string) (*sqlserverflex.GetInstanceResponse, error) | ||
| } | ||
| func ValidateFlavorId(flavorId string, flavors *[]sqlserverflex.InstanceFlavorEntry) error { | ||
| if flavors == nil { | ||
| return fmt.Errorf("nil flavors") | ||
| } | ||
| for _, f := range *flavors { | ||
| if f.Id != nil && strings.EqualFold(*f.Id, flavorId) { | ||
| return nil | ||
| } | ||
| } | ||
| return &errors.DatabaseInvalidFlavorError{ | ||
| Service: ServiceCmd, | ||
| Details: fmt.Sprintf("You provided flavor ID '%s', which is invalid.", flavorId), | ||
| } | ||
| } | ||
| func ValidateStorage(storageClass *string, storageSize *int64, storages *sqlserverflex.ListStoragesResponse, flavorId string) error { | ||
| if storages == nil { | ||
| return fmt.Errorf("nil storages") | ||
| } | ||
| if storageSize != nil { | ||
| if *storageSize < *storages.StorageRange.Min || *storageSize > *storages.StorageRange.Max { | ||
| return fmt.Errorf("%s", fmt.Sprintf("You provided storage size '%d', which is invalid. The valid range is %d-%d.", *storageSize, *storages.StorageRange.Min, *storages.StorageRange.Max)) | ||
| } | ||
| } | ||
| if storageClass == nil { | ||
| return nil | ||
| } | ||
| for _, sc := range *storages.StorageClasses { | ||
| if strings.EqualFold(*storageClass, sc) { | ||
| return nil | ||
| } | ||
| } | ||
| return &errors.DatabaseInvalidStorageError{ | ||
| Service: ServiceCmd, | ||
| Details: fmt.Sprintf("You provided storage class '%s', which is invalid.", *storageClass), | ||
| FlavorId: flavorId, | ||
| } | ||
| } | ||
| func LoadFlavorId(cpu, ram int64, flavors *[]sqlserverflex.InstanceFlavorEntry) (*string, error) { | ||
| if flavors == nil { | ||
| return nil, fmt.Errorf("nil flavors") | ||
| } | ||
| availableFlavors := "" | ||
| for _, f := range *flavors { | ||
| if f.Id == nil || f.Cpu == nil || f.Memory == nil { | ||
| continue | ||
| } | ||
| if *f.Cpu == cpu && *f.Memory == ram { | ||
| return f.Id, nil | ||
| } | ||
| availableFlavors = fmt.Sprintf("%s\n- %d CPU, %d GB RAM", availableFlavors, *f.Cpu, *f.Cpu) | ||
| } | ||
| return nil, &errors.DatabaseInvalidFlavorError{ | ||
| Service: ServiceCmd, | ||
| Details: "You provided an invalid combination for CPU and RAM.", | ||
| } | ||
| } | ||
| func GetInstanceName(ctx context.Context, apiClient SQLServerFlexClient, projectId, instanceId string) (string, error) { | ||
| resp, err := apiClient.GetInstanceExecute(ctx, projectId, instanceId) | ||
| if err != nil { | ||
| return "", fmt.Errorf("get SQLServer Flex instance: %w", err) | ||
| } | ||
| return *resp.Item.Name, nil | ||
| } |
@@ -16,5 +16,5 @@ name: Renovate | ||
| - name: Self-hosted Renovate | ||
| uses: renovatebot/github-action@v40.1.9 | ||
| uses: renovatebot/github-action@v40.1.11 | ||
| with: | ||
| configurationFile: .github/renovate.json | ||
| token: ${{ secrets.RENOVATE_TOKEN }} |
+2
-2
@@ -103,3 +103,3 @@ before: | ||
| homepage: "https://github.com/stackitcloud/stackit-cli" | ||
| description: "A command-line interface to manage STACKIT resources.\nThis CLI is in a BETA state. More services and functionality will be supported soon." | ||
| description: "A command-line interface to manage STACKIT resources.\nThis CLI is in a beta state. More services and functionality will be supported soon." | ||
| directory: Formula | ||
@@ -121,3 +121,3 @@ license: "Apache-2.0" | ||
| summary: A command-line interface to manage STACKIT resources. | ||
| description: "A command-line interface to manage STACKIT resources.\nThis CLI is in a BETA state. More services and functionality will be supported soon." | ||
| description: "A command-line interface to manage STACKIT resources.\nThis CLI is in a beta state. More services and functionality will be supported soon." | ||
| license: Apache-2.0 | ||
@@ -124,0 +124,0 @@ confinement: classic |
@@ -23,3 +23,3 @@ ## stackit argus scrape-config generate-payload | ||
| Generate a Create payload with default values, and adapt it with custom values for the different configuration options | ||
| $ stackit argus scrape-config generate-payload > ./payload.json | ||
| $ stackit argus scrape-config generate-payload --file-path ./payload.json | ||
| <Modify payload in file, if needed> | ||
@@ -29,5 +29,8 @@ $ stackit argus scrape-config create my-config --payload @./payload.json | ||
| Generate an Update payload with the values of an existing configuration named "my-config" for Argus instance xxx, and adapt it with custom values for the different configuration options | ||
| $ stackit argus scrape-config generate-payload --job-name my-config --instance-id xxx > ./payload.json | ||
| $ stackit argus scrape-config generate-payload --job-name my-config --instance-id xxx --file-path ./payload.json | ||
| <Modify payload in file> | ||
| $ stackit argus scrape-config update my-config --payload @./payload.json | ||
| Generate an Update payload with the values of an existing configuration named "my-config" for Argus instance xxx, and preview it in the terminal | ||
| $ stackit argus scrape-config generate-payload --job-name my-config --instance-id xxx | ||
| ``` | ||
@@ -38,2 +41,3 @@ | ||
| ``` | ||
| -f, --file-path string If set, writes the payload to the given file. If unset, writes the payload to the standard output | ||
| -h, --help Help for "stackit argus scrape-config generate-payload" | ||
@@ -40,0 +44,0 @@ --instance-id string Instance ID |
@@ -51,3 +51,3 @@ ## stackit auth activate-service-account | ||
| * [stackit auth](./stackit_auth.md) - Provides authentication functionality | ||
| * [stackit auth](./stackit_auth.md) - Authenticates the STACKIT CLI | ||
@@ -38,3 +38,3 @@ ## stackit auth login | ||
| * [stackit auth](./stackit_auth.md) - Provides authentication functionality | ||
| * [stackit auth](./stackit_auth.md) - Authenticates the STACKIT CLI | ||
| ## stackit auth | ||
| Provides authentication functionality | ||
| Authenticates the STACKIT CLI | ||
| ### Synopsis | ||
| Provides authentication functionality. | ||
| Authenticates in the STACKIT CLI. | ||
@@ -9,0 +9,0 @@ ``` |
@@ -50,2 +50,3 @@ ## stackit config set | ||
| --ske-custom-endpoint string SKE API base URL, used in calls to this API | ||
| --sqlserverflex-custom-endpoint string SQLServer Flex API base URL, used in calls to this API | ||
| ``` | ||
@@ -52,0 +53,0 @@ |
@@ -50,2 +50,3 @@ ## stackit config unset | ||
| --ske-custom-endpoint SKE API base URL. If unset, uses the default base URL | ||
| --sqlserverflex-custom-endpoint SQLServer Flex API base URL. If unset, uses the default base URL | ||
| --verbosity Verbosity of the CLI | ||
@@ -52,0 +53,0 @@ ``` |
| ## stackit dns record-set list | ||
| List DNS record sets | ||
| Lists DNS record sets | ||
| ### Synopsis | ||
| List DNS record sets. Successfully deleted record sets are not listed by default. | ||
| Lists DNS record sets. Successfully deleted record sets are not listed by default. | ||
@@ -9,0 +9,0 @@ ``` |
@@ -35,4 +35,4 @@ ## stackit dns record-set | ||
| * [stackit dns record-set describe](./stackit_dns_record-set_describe.md) - Shows details of a DNS record set | ||
| * [stackit dns record-set list](./stackit_dns_record-set_list.md) - List DNS record sets | ||
| * [stackit dns record-set list](./stackit_dns_record-set_list.md) - Lists DNS record sets | ||
| * [stackit dns record-set update](./stackit_dns_record-set_update.md) - Updates a DNS record set | ||
| ## stackit dns zone list | ||
| List DNS zones | ||
| Lists DNS zones | ||
| ### Synopsis | ||
| List DNS zones. Successfully deleted zones are not listed by default. | ||
| Lists DNS zones. Successfully deleted zones are not listed by default. | ||
@@ -9,0 +9,0 @@ ``` |
@@ -35,4 +35,4 @@ ## stackit dns zone | ||
| * [stackit dns zone describe](./stackit_dns_zone_describe.md) - Shows details of a DNS zone | ||
| * [stackit dns zone list](./stackit_dns_zone_list.md) - List DNS zones | ||
| * [stackit dns zone list](./stackit_dns_zone_list.md) - Lists DNS zones | ||
| * [stackit dns zone update](./stackit_dns_zone_update.md) - Updates a DNS zone | ||
@@ -18,3 +18,3 @@ ## stackit load-balancer generate-payload | ||
| Generate a payload, and adapt it with custom values for the different configuration options | ||
| $ stackit load-balancer generate-payload > ./payload.json | ||
| $ stackit load-balancer generate-payload --file-path ./payload.json | ||
| <Modify payload in file, if needed> | ||
@@ -24,5 +24,8 @@ $ stackit load-balancer create --payload @./payload.json | ||
| Generate a payload with values of an existing load balancer, and adapt it with custom values for the different configuration options | ||
| $ stackit load-balancer generate-payload --lb-name xxx > ./payload.json | ||
| $ stackit load-balancer generate-payload --lb-name xxx --file-path ./payload.json | ||
| <Modify payload in file> | ||
| $ stackit load-balancer update xxx --payload @./payload.json | ||
| Generate a payload with values of an existing load balancer, and preview it in the terminal | ||
| $ stackit load-balancer generate-payload --lb-name xxx | ||
| ``` | ||
@@ -33,4 +36,5 @@ | ||
| ``` | ||
| -h, --help Help for "stackit load-balancer generate-payload" | ||
| -n, --lb-name string If set, generates the payload with the current values of the given load balancer. If unset, generates the payload with empty values | ||
| -f, --file-path string If set, writes the payload to the given file. If unset, writes the payload to the standard output | ||
| -h, --help Help for "stackit load-balancer generate-payload" | ||
| -n, --lb-name string If set, generates the payload with the current values of the given load balancer. If unset, generates the payload with empty values | ||
| ``` | ||
@@ -37,0 +41,0 @@ |
@@ -7,4 +7,4 @@ ## stackit mongodbflex backup restore | ||
| Restores a MongoDB Flex instance from a backup of an instance or clones a MongoDB Flex instance from a point-in-time snapshot. | ||
| The backup is specified by a backup ID and the point-in-time snapshot is specified by a timestamp. | ||
| Restores a MongoDB Flex instance from a backup of an instance or clones a MongoDB Flex instance from a point-in-time backup. | ||
| The backup can be specified by a backup ID or a timestamp. | ||
| You can specify the instance to which the backup will be applied. If not specified, the backup will be applied to the same instance from which it was taken. | ||
@@ -36,3 +36,3 @@ | ||
| --instance-id string Instance ID | ||
| --timestamp string Timestamp of the snapshot to use as a source for cloning the instance in a date-time with the RFC3339 layout format, e.g. 2024-01-01T00:00:00Z | ||
| --timestamp string Timestamp to restore the instance to, in a date-time with the RFC3339 layout format, e.g. 2024-01-01T00:00:00Z | ||
| ``` | ||
@@ -39,0 +39,0 @@ |
@@ -22,4 +22,4 @@ ## stackit mongodbflex backup update-schedule | ||
| Update the retention days for snapshots of a MongoDB Flex instance with ID "xxx" to 5 days | ||
| $ stackit mongodbflex backup update-schedule --instance-id xxx --save-snapshot-days 5 | ||
| Update the retention days for backups of a MongoDB Flex instance with ID "xxx" to 5 days | ||
| $ stackit mongodbflex backup update-schedule --instance-id xxx --store-for-days 5 | ||
| ``` | ||
@@ -32,7 +32,7 @@ | ||
| --instance-id string Instance ID | ||
| --save-daily-snapshot-days int Number of days to retain daily snapshots. Should be less than or equal to the number of days of the selected weekly or monthly value. | ||
| --save-monthly-snapshot-months int Number of months to retain monthly snapshots | ||
| --save-snapshot-days int Number of days to retain snapshots. Should be less than or equal to the value of the daily backup. | ||
| --save-weekly-snapshot-weeks int Number of weeks to retain weekly snapshots. Should be less than or equal to the number of weeks of the selected monthly value. | ||
| --schedule string Backup schedule, in the cron scheduling system format e.g. '0 0 * * *' | ||
| --store-daily-backup-days int Number of days to retain daily backups. Should be less than or equal to the number of days of the selected weekly or monthly value. | ||
| --store-for-days int Number of days to retain backups. Should be less than or equal to the value of the daily backup. | ||
| --store-monthly-backups-months int Number of months to retain monthly backups | ||
| --store-weekly-backup-weeks int Number of weeks to retain weekly backups. Should be less than or equal to the number of weeks of the selected monthly value. | ||
| ``` | ||
@@ -39,0 +39,0 @@ |
@@ -31,3 +31,3 @@ ## stackit object-storage bucket | ||
| * [stackit object-storage](./stackit_object-storage.md) - Provides functionality regarding Object Storage | ||
| * [stackit object-storage](./stackit_object-storage.md) - Provides functionality for Object Storage | ||
| * [stackit object-storage bucket create](./stackit_object-storage_bucket_create.md) - Creates an Object Storage bucket | ||
@@ -34,0 +34,0 @@ * [stackit object-storage bucket delete](./stackit_object-storage_bucket_delete.md) - Deletes an Object Storage bucket |
@@ -31,3 +31,3 @@ ## stackit object-storage credentials-group | ||
| * [stackit object-storage](./stackit_object-storage.md) - Provides functionality regarding Object Storage | ||
| * [stackit object-storage](./stackit_object-storage.md) - Provides functionality for Object Storage | ||
| * [stackit object-storage credentials-group create](./stackit_object-storage_credentials-group_create.md) - Creates a credentials group to hold Object Storage access credentials | ||
@@ -34,0 +34,0 @@ * [stackit object-storage credentials-group delete](./stackit_object-storage_credentials-group_delete.md) - Deletes a credentials group that holds Object Storage access credentials |
@@ -31,3 +31,3 @@ ## stackit object-storage credentials | ||
| * [stackit object-storage](./stackit_object-storage.md) - Provides functionality regarding Object Storage | ||
| * [stackit object-storage](./stackit_object-storage.md) - Provides functionality for Object Storage | ||
| * [stackit object-storage credentials create](./stackit_object-storage_credentials_create.md) - Creates credentials for an Object Storage credentials group | ||
@@ -34,0 +34,0 @@ * [stackit object-storage credentials delete](./stackit_object-storage_credentials_delete.md) - Deletes credentials of an Object Storage credentials group |
@@ -38,3 +38,3 @@ ## stackit object-storage disable | ||
| * [stackit object-storage](./stackit_object-storage.md) - Provides functionality regarding Object Storage | ||
| * [stackit object-storage](./stackit_object-storage.md) - Provides functionality for Object Storage | ||
@@ -38,3 +38,3 @@ ## stackit object-storage enable | ||
| * [stackit object-storage](./stackit_object-storage.md) - Provides functionality regarding Object Storage | ||
| * [stackit object-storage](./stackit_object-storage.md) - Provides functionality for Object Storage | ||
| ## stackit object-storage | ||
| Provides functionality regarding Object Storage | ||
| Provides functionality for Object Storage | ||
| ### Synopsis | ||
| Provides functionality regarding Object Storage. | ||
| Provides functionality for Object Storage. | ||
@@ -9,0 +9,0 @@ ``` |
@@ -44,3 +44,3 @@ ## stackit organization member add | ||
| * [stackit organization member](./stackit_organization_member.md) - Provides functionality regarding organization members | ||
| * [stackit organization member](./stackit_organization_member.md) - Manages organization members | ||
@@ -48,3 +48,3 @@ ## stackit organization member list | ||
| * [stackit organization member](./stackit_organization_member.md) - Provides functionality regarding organization members | ||
| * [stackit organization member](./stackit_organization_member.md) - Manages organization members | ||
@@ -46,3 +46,3 @@ ## stackit organization member remove | ||
| * [stackit organization member](./stackit_organization_member.md) - Provides functionality regarding organization members | ||
| * [stackit organization member](./stackit_organization_member.md) - Manages organization members | ||
| ## stackit organization member | ||
| Provides functionality regarding organization members | ||
| Manages organization members | ||
| ### Synopsis | ||
| Provides functionality regarding organization members. | ||
| Manages organization members. | ||
@@ -31,3 +31,3 @@ ``` | ||
| * [stackit organization](./stackit_organization.md) - Provides functionality regarding organizations | ||
| * [stackit organization](./stackit_organization.md) - Manages organizations | ||
| * [stackit organization member add](./stackit_organization_member_add.md) - Adds a member to an organization | ||
@@ -34,0 +34,0 @@ * [stackit organization member list](./stackit_organization_member_list.md) - Lists members of an organization |
@@ -46,3 +46,3 @@ ## stackit organization role list | ||
| * [stackit organization role](./stackit_organization_role.md) - Provides functionality regarding organization roles | ||
| * [stackit organization role](./stackit_organization_role.md) - Manages organization roles | ||
| ## stackit organization role | ||
| Provides functionality regarding organization roles | ||
| Manages organization roles | ||
| ### Synopsis | ||
| Provides functionality regarding organization roles. | ||
| Manages organization roles. | ||
@@ -31,4 +31,4 @@ ``` | ||
| * [stackit organization](./stackit_organization.md) - Provides functionality regarding organizations | ||
| * [stackit organization](./stackit_organization.md) - Manages organizations | ||
| * [stackit organization role list](./stackit_organization_role_list.md) - Lists roles and permissions of an organization | ||
| ## stackit organization | ||
| Provides functionality regarding organizations | ||
| Manages organizations | ||
| ### Synopsis | ||
| Provides functionality regarding organizations. | ||
| Manages organizations. | ||
| An active STACKIT organization is the root element of the resource hierarchy and a prerequisite to use any STACKIT Cloud Resource / Service. | ||
@@ -33,4 +33,4 @@ | ||
| * [stackit](./stackit.md) - Manage STACKIT resources using the command line | ||
| * [stackit organization member](./stackit_organization_member.md) - Provides functionality regarding organization members | ||
| * [stackit organization role](./stackit_organization_role.md) - Provides functionality regarding organization roles | ||
| * [stackit organization member](./stackit_organization_member.md) - Manages organization members | ||
| * [stackit organization role](./stackit_organization_role.md) - Manages organization roles | ||
@@ -44,3 +44,3 @@ ## stackit project create | ||
| * [stackit project](./stackit_project.md) - Provides functionality regarding projects | ||
| * [stackit project](./stackit_project.md) - Manages projects | ||
@@ -41,3 +41,3 @@ ## stackit project delete | ||
| * [stackit project](./stackit_project.md) - Provides functionality regarding projects | ||
| * [stackit project](./stackit_project.md) - Manages projects | ||
@@ -45,3 +45,3 @@ ## stackit project describe | ||
| * [stackit project](./stackit_project.md) - Provides functionality regarding projects | ||
| * [stackit project](./stackit_project.md) - Manages projects | ||
@@ -53,3 +53,3 @@ ## stackit project list | ||
| * [stackit project](./stackit_project.md) - Provides functionality regarding projects | ||
| * [stackit project](./stackit_project.md) - Manages projects | ||
@@ -43,3 +43,3 @@ ## stackit project member add | ||
| * [stackit project member](./stackit_project_member.md) - Provides functionality regarding project members | ||
| * [stackit project member](./stackit_project_member.md) - Manages project members | ||
@@ -47,3 +47,3 @@ ## stackit project member list | ||
| * [stackit project member](./stackit_project_member.md) - Provides functionality regarding project members | ||
| * [stackit project member](./stackit_project_member.md) - Manages project members | ||
@@ -45,3 +45,3 @@ ## stackit project member remove | ||
| * [stackit project member](./stackit_project_member.md) - Provides functionality regarding project members | ||
| * [stackit project member](./stackit_project_member.md) - Manages project members | ||
| ## stackit project member | ||
| Provides functionality regarding project members | ||
| Manages project members | ||
| ### Synopsis | ||
| Provides functionality regarding project members. | ||
| Manages project members. | ||
@@ -31,3 +31,3 @@ ``` | ||
| * [stackit project](./stackit_project.md) - Provides functionality regarding projects | ||
| * [stackit project](./stackit_project.md) - Manages projects | ||
| * [stackit project member add](./stackit_project_member_add.md) - Adds a member to a project | ||
@@ -34,0 +34,0 @@ * [stackit project member list](./stackit_project_member_list.md) - Lists members of a project |
@@ -45,3 +45,3 @@ ## stackit project role list | ||
| * [stackit project role](./stackit_project_role.md) - Provides functionality regarding project roles | ||
| * [stackit project role](./stackit_project_role.md) - Manages project roles | ||
| ## stackit project role | ||
| Provides functionality regarding project roles | ||
| Manages project roles | ||
| ### Synopsis | ||
| Provides functionality regarding project roles. | ||
| Manages project roles. | ||
@@ -31,4 +31,4 @@ ``` | ||
| * [stackit project](./stackit_project.md) - Provides functionality regarding projects | ||
| * [stackit project](./stackit_project.md) - Manages projects | ||
| * [stackit project role list](./stackit_project_role_list.md) - Lists roles and permissions of a project | ||
@@ -47,3 +47,3 @@ ## stackit project update | ||
| * [stackit project](./stackit_project.md) - Provides functionality regarding projects | ||
| * [stackit project](./stackit_project.md) - Manages projects | ||
| ## stackit project | ||
| Provides functionality regarding projects | ||
| Manages projects | ||
| ### Synopsis | ||
| Provides functionality regarding projects. | ||
| Provides functionality for projects. | ||
| A project is a container for resources which is the service that you can purchase from STACKIT. | ||
@@ -37,5 +37,5 @@ | ||
| * [stackit project list](./stackit_project_list.md) - Lists STACKIT projects | ||
| * [stackit project member](./stackit_project_member.md) - Provides functionality regarding project members | ||
| * [stackit project role](./stackit_project_role.md) - Provides functionality regarding project roles | ||
| * [stackit project member](./stackit_project_member.md) - Manages project members | ||
| * [stackit project role](./stackit_project_role.md) - Manages project roles | ||
| * [stackit project update](./stackit_project_update.md) - Updates a STACKIT project | ||
@@ -49,3 +49,3 @@ ## stackit service-account key create | ||
| * [stackit service-account key](./stackit_service-account_key.md) - Provides functionality regarding service account keys | ||
| * [stackit service-account key](./stackit_service-account_key.md) - Provides functionality for service account keys | ||
@@ -39,3 +39,3 @@ ## stackit service-account key delete | ||
| * [stackit service-account key](./stackit_service-account_key.md) - Provides functionality regarding service account keys | ||
| * [stackit service-account key](./stackit_service-account_key.md) - Provides functionality for service account keys | ||
@@ -39,3 +39,3 @@ ## stackit service-account key describe | ||
| * [stackit service-account key](./stackit_service-account_key.md) - Provides functionality regarding service account keys | ||
| * [stackit service-account key](./stackit_service-account_key.md) - Provides functionality for service account keys | ||
@@ -46,3 +46,3 @@ ## stackit service-account key list | ||
| * [stackit service-account key](./stackit_service-account_key.md) - Provides functionality regarding service account keys | ||
| * [stackit service-account key](./stackit_service-account_key.md) - Provides functionality for service account keys | ||
@@ -49,3 +49,3 @@ ## stackit service-account key update | ||
| * [stackit service-account key](./stackit_service-account_key.md) - Provides functionality regarding service account keys | ||
| * [stackit service-account key](./stackit_service-account_key.md) - Provides functionality for service account keys | ||
| ## stackit service-account key | ||
| Provides functionality regarding service account keys | ||
| Provides functionality for service account keys | ||
| ### Synopsis | ||
| Provides functionality regarding service account keys. | ||
| Provides functionality for service account keys. | ||
@@ -9,0 +9,0 @@ ``` |
@@ -45,3 +45,3 @@ ## stackit service-account token create | ||
| * [stackit service-account token](./stackit_service-account_token.md) - Provides functionality regarding service account tokens | ||
| * [stackit service-account token](./stackit_service-account_token.md) - Provides functionality for service account tokens | ||
@@ -48,3 +48,3 @@ ## stackit service-account token list | ||
| * [stackit service-account token](./stackit_service-account_token.md) - Provides functionality regarding service account tokens | ||
| * [stackit service-account token](./stackit_service-account_token.md) - Provides functionality for service account tokens | ||
@@ -41,3 +41,3 @@ ## stackit service-account token revoke | ||
| * [stackit service-account token](./stackit_service-account_token.md) - Provides functionality regarding service account tokens | ||
| * [stackit service-account token](./stackit_service-account_token.md) - Provides functionality for service account tokens | ||
| ## stackit service-account token | ||
| Provides functionality regarding service account tokens | ||
| Provides functionality for service account tokens | ||
| ### Synopsis | ||
| Provides functionality regarding service account tokens. | ||
| Provides functionality for service account tokens. | ||
@@ -9,0 +9,0 @@ ``` |
@@ -35,5 +35,5 @@ ## stackit service-account | ||
| * [stackit service-account get-jwks](./stackit_service-account_get-jwks.md) - Shows the JWKS for a service account | ||
| * [stackit service-account key](./stackit_service-account_key.md) - Provides functionality regarding service account keys | ||
| * [stackit service-account key](./stackit_service-account_key.md) - Provides functionality for service account keys | ||
| * [stackit service-account list](./stackit_service-account_list.md) - Lists all service accounts | ||
| * [stackit service-account token](./stackit_service-account_token.md) - Provides functionality regarding service account tokens | ||
| * [stackit service-account token](./stackit_service-account_token.md) - Provides functionality for service account tokens | ||
@@ -18,3 +18,3 @@ ## stackit ske cluster generate-payload | ||
| Generate a payload with default values, and adapt it with custom values for the different configuration options | ||
| $ stackit ske cluster generate-payload > ./payload.json | ||
| $ stackit ske cluster generate-payload --file-path ./payload.json | ||
| <Modify payload in file, if needed> | ||
@@ -24,5 +24,8 @@ $ stackit ske cluster create my-cluster --payload @./payload.json | ||
| Generate a payload with values of a cluster, and adapt it with custom values for the different configuration options | ||
| $ stackit ske cluster generate-payload --cluster-name my-cluster > ./payload.json | ||
| $ stackit ske cluster generate-payload --cluster-name my-cluster --file-path ./payload.json | ||
| <Modify payload in file> | ||
| $ stackit ske cluster update my-cluster --payload @./payload.json | ||
| Generate a payload with values of a cluster, and preview it in the terminal | ||
| $ stackit ske cluster generate-payload --cluster-name my-cluster | ||
| ``` | ||
@@ -34,2 +37,3 @@ | ||
| -n, --cluster-name string If set, generates the payload with the current state of the given cluster. If unset, generates the payload with default values | ||
| -f, --file-path string If set, writes the payload to the given file. If unset, writes the payload to the standard output | ||
| -h, --help Help for "stackit ske cluster generate-payload" | ||
@@ -36,0 +40,0 @@ ``` |
+5
-4
@@ -30,3 +30,4 @@ ## stackit | ||
| * [stackit argus](./stackit_argus.md) - Provides functionality for Argus | ||
| * [stackit auth](./stackit_auth.md) - Provides authentication functionality | ||
| * [stackit auth](./stackit_auth.md) - Authenticates the STACKIT CLI | ||
| * [stackit beta](./stackit_beta.md) - Contains beta STACKIT CLI commands | ||
| * [stackit config](./stackit_config.md) - Provides functionality for CLI configuration options | ||
@@ -39,7 +40,7 @@ * [stackit curl](./stackit_curl.md) - Executes an authenticated HTTP request to an endpoint | ||
| * [stackit mongodbflex](./stackit_mongodbflex.md) - Provides functionality for MongoDB Flex | ||
| * [stackit object-storage](./stackit_object-storage.md) - Provides functionality regarding Object Storage | ||
| * [stackit object-storage](./stackit_object-storage.md) - Provides functionality for Object Storage | ||
| * [stackit opensearch](./stackit_opensearch.md) - Provides functionality for OpenSearch | ||
| * [stackit organization](./stackit_organization.md) - Provides functionality regarding organizations | ||
| * [stackit organization](./stackit_organization.md) - Manages organizations | ||
| * [stackit postgresflex](./stackit_postgresflex.md) - Provides functionality for PostgreSQL Flex | ||
| * [stackit project](./stackit_project.md) - Provides functionality regarding projects | ||
| * [stackit project](./stackit_project.md) - Manages projects | ||
| * [stackit rabbitmq](./stackit_rabbitmq.md) - Provides functionality for RabbitMQ | ||
@@ -46,0 +47,0 @@ * [stackit redis](./stackit_redis.md) - Provides functionality for Redis |
+8
-7
@@ -6,2 +6,3 @@ module github.com/stackitcloud/stackit-cli | ||
| require ( | ||
| github.com/fatih/color v1.14.1 | ||
| github.com/goccy/go-yaml v1.11.3 | ||
@@ -19,11 +20,12 @@ github.com/golang-jwt/jwt/v5 v5.2.1 | ||
| github.com/stackitcloud/stackit-sdk-go/core v0.12.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/authorization v0.2.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/dns v0.9.1 | ||
| github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v0.13.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/authorization v0.3.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/dns v0.10.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v0.14.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.14.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.13.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.14.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.8.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.7.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.8.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.4.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/ske v0.15.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v0.2.0 | ||
| github.com/zalando/go-keyring v0.2.4 | ||
@@ -45,3 +47,2 @@ golang.org/x/mod v0.17.0 | ||
| require ( | ||
| github.com/fatih/color v1.14.1 // indirect | ||
| github.com/mattn/go-isatty v0.0.17 // indirect | ||
@@ -78,3 +79,3 @@ golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect | ||
| github.com/spf13/cast v1.6.0 // indirect | ||
| github.com/stackitcloud/stackit-sdk-go/services/argus v0.10.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/argus v0.11.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v0.12.0 | ||
@@ -81,0 +82,0 @@ github.com/stackitcloud/stackit-sdk-go/services/logme v0.14.0 |
+14
-12
@@ -127,8 +127,8 @@ github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= | ||
| github.com/stackitcloud/stackit-sdk-go/core v0.12.0/go.mod h1:mDX1mSTsB3mP+tNBGcFNx6gH1mGBN4T+dVt+lcw7nlw= | ||
| github.com/stackitcloud/stackit-sdk-go/services/argus v0.10.0 h1:FAYOt6UBy/F2jPH2C/NnZnbjLZryJBjtM3afLVgGc4w= | ||
| github.com/stackitcloud/stackit-sdk-go/services/argus v0.10.0/go.mod h1:nVllQfYODhX1q3bgwVTLO7wHOp+8NMLiKbn3u/Dg5nU= | ||
| github.com/stackitcloud/stackit-sdk-go/services/authorization v0.2.0 h1:vdv8DiG9KN6r0UilBeNbwEWItX/HqUMm6kab3t71kpY= | ||
| github.com/stackitcloud/stackit-sdk-go/services/authorization v0.2.0/go.mod h1:1sLuXa7Qvp9f+wKWdRjyNe8B2F8JX7nSTd8fBKadri4= | ||
| github.com/stackitcloud/stackit-sdk-go/services/dns v0.9.1 h1:pj2nAJvgzFSckA56rCPdi7StXGrr06go8qejI1weNJ8= | ||
| github.com/stackitcloud/stackit-sdk-go/services/dns v0.9.1/go.mod h1:MdZcRbs19s2NLeJmSLSoqTzm9IPIQhE1ZEMpo9gePq0= | ||
| github.com/stackitcloud/stackit-sdk-go/services/argus v0.11.0 h1:JVEx/ouHB6PlwGzQa3ywyDym1HTWo3WgrxAyXprCnuM= | ||
| github.com/stackitcloud/stackit-sdk-go/services/argus v0.11.0/go.mod h1:nVllQfYODhX1q3bgwVTLO7wHOp+8NMLiKbn3u/Dg5nU= | ||
| github.com/stackitcloud/stackit-sdk-go/services/authorization v0.3.0 h1:AyzBgcbd0rCm+2+xaWqtfibjWmkKlO+U+7qxqvtKpJ8= | ||
| github.com/stackitcloud/stackit-sdk-go/services/authorization v0.3.0/go.mod h1:1sLuXa7Qvp9f+wKWdRjyNe8B2F8JX7nSTd8fBKadri4= | ||
| github.com/stackitcloud/stackit-sdk-go/services/dns v0.10.0 h1:QIZfs6nJ/l2pOweH1E+wazXnlAUtqisVbYUxWAokTbc= | ||
| github.com/stackitcloud/stackit-sdk-go/services/dns v0.10.0/go.mod h1:MdZcRbs19s2NLeJmSLSoqTzm9IPIQhE1ZEMpo9gePq0= | ||
| github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v0.12.0 h1:LAteZO46XmqTsmPw0QV8n8WiGM205pxrcqHqWznNmyY= | ||
@@ -140,4 +140,4 @@ github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v0.12.0/go.mod h1:wsO3+vXe1XiKLeCIctWAptaHQZ07Un7kmLTQ+drbj7w= | ||
| github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.14.0/go.mod h1:kPetkX9hNm9HkRyiKQL/tlgdi8frZdMP8afg0mEvQ9s= | ||
| github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v0.13.0 h1:Dhanx9aV5VRfpHg22Li07661FbRT5FR9/M6FowN08a8= | ||
| github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v0.13.0/go.mod h1:iFerEzGmkg6R13ldFUyHUWHm0ac9cS4ftTDLhP0k/dU= | ||
| github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v0.14.0 h1:FaJYVfha+atvPfFIf3h3+BFjOjeux9OBHukG1J98kq0= | ||
| github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v0.14.0/go.mod h1:iFerEzGmkg6R13ldFUyHUWHm0ac9cS4ftTDLhP0k/dU= | ||
| github.com/stackitcloud/stackit-sdk-go/services/objectstorage v0.9.0 h1:rWgy4/eCIgyA2dUuc4a30pldmS6taQDwiLqoeZmyeP8= | ||
@@ -147,4 +147,4 @@ github.com/stackitcloud/stackit-sdk-go/services/objectstorage v0.9.0/go.mod h1:dkVMJI88eJ3Xs0ZV15r4tUpgitUGJXcvrX3RL4Zq2bQ= | ||
| github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.14.0/go.mod h1:ZecMIf9oYj2DGZqWh93l97WdVaRdLl+tW5Fq3YKGwBM= | ||
| github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.13.0 h1:PGLjBZxWM7NIrH1+W1+f+/4kZEgwv9DGnXcUzOqM0M8= | ||
| github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.13.0/go.mod h1:SdrqGLCkilL6wl1+jcxmLtks2IocgIg+bsyeyYUIzR4= | ||
| github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.14.0 h1:PZAqXd8TVyTZo8qty4bM2sSoLlLG+Nc9tcpxbQhO+GY= | ||
| github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.14.0/go.mod h1:SdrqGLCkilL6wl1+jcxmLtks2IocgIg+bsyeyYUIzR4= | ||
| github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.14.0 h1:wJ+LSMrRol4wlm/ML4wvVPGwIw51VHMFwMCOtwluvKQ= | ||
@@ -156,4 +156,4 @@ github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.14.0/go.mod h1:eSgnPBknTJh7t+jVKN+xzeAh+Cg1USOlH3QCyfvG20g= | ||
| github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.8.0/go.mod h1:p16qz/pAW8b1gEhqMpIgJfutRPeDPqQLlbVGyCo3f8o= | ||
| github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.7.0 h1:1Ho+M4DyZHrwbDe1peW//x+/hegIuaUdZqbQEbPlr4k= | ||
| github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.7.0/go.mod h1:LX0Mcyr7/QP77zf7e05fHCJO38RMuTxr7nEDUDZ3oPQ= | ||
| github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.8.0 h1:pJBG455kmtbQFpCxcBfBK8wOuEnmsMv3h90LFcdj3q0= | ||
| github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.8.0/go.mod h1:LX0Mcyr7/QP77zf7e05fHCJO38RMuTxr7nEDUDZ3oPQ= | ||
| github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.4.0 h1:JB1O0E9+L50ZaO36uz7azurvUuB5JdX5s2ZXuIdb9t8= | ||
@@ -163,2 +163,4 @@ github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.4.0/go.mod h1:Ni9RBJvcaXRIrDIuQBpJcuQvCQSj27crQSyc+WM4p0c= | ||
| github.com/stackitcloud/stackit-sdk-go/services/ske v0.15.0/go.mod h1:0fFs4R7kg+gU7FNAIzzFvlCZJz6gyZ8CFhbK3eSrAwQ= | ||
| github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v0.2.0 h1:aIXxXx6u4+6C02MPb+hdItigeKeen7m+hEEG+Ej9sNs= | ||
| github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v0.2.0/go.mod h1:fQJOQMfasStZ8J9iGX0vTjyJoQtLqMXJ5Npb03QJk84= | ||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
@@ -165,0 +167,0 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= |
@@ -25,3 +25,6 @@ package generatepayload | ||
| const testJobName = "test-job-name" | ||
| const ( | ||
| testJobName = "test-job-name" | ||
| testFilePath = "example-file" | ||
| ) | ||
@@ -33,2 +36,3 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| jobNameFlag: testJobName, | ||
| filePathFlag: testFilePath, | ||
| } | ||
@@ -49,2 +53,3 @@ for _, mod := range mods { | ||
| JobName: utils.Ptr(testJobName), | ||
| FilePath: utils.Ptr(testFilePath), | ||
| } | ||
@@ -87,2 +92,12 @@ for _, mod := range mods { | ||
| { | ||
| description: "file path missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, filePathFlag) | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.FilePath = nil | ||
| }), | ||
| }, | ||
| { | ||
| description: "job name missing", | ||
@@ -89,0 +104,0 @@ flagValues: fixtureFlagValues(func(flagValues map[string]string) { |
@@ -10,2 +10,3 @@ package generatepayload | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/fileutils" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
@@ -24,2 +25,3 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| instanceIdFlag = "instance-id" | ||
| filePathFlag = "file-path" | ||
| ) | ||
@@ -31,2 +33,3 @@ | ||
| InstanceId string | ||
| FilePath *string | ||
| } | ||
@@ -50,3 +53,3 @@ | ||
| `Generate a Create payload with default values, and adapt it with custom values for the different configuration options`, | ||
| `$ stackit argus scrape-config generate-payload > ./payload.json`, | ||
| `$ stackit argus scrape-config generate-payload --file-path ./payload.json`, | ||
| `<Modify payload in file, if needed>`, | ||
@@ -56,5 +59,8 @@ `$ stackit argus scrape-config create my-config --payload @./payload.json`), | ||
| `Generate an Update payload with the values of an existing configuration named "my-config" for Argus instance xxx, and adapt it with custom values for the different configuration options`, | ||
| `$ stackit argus scrape-config generate-payload --job-name my-config --instance-id xxx > ./payload.json`, | ||
| `$ stackit argus scrape-config generate-payload --job-name my-config --instance-id xxx --file-path ./payload.json`, | ||
| `<Modify payload in file>`, | ||
| `$ stackit argus scrape-config update my-config --payload @./payload.json`), | ||
| examples.NewExample( | ||
| `Generate an Update payload with the values of an existing configuration named "my-config" for Argus instance xxx, and preview it in the terminal`, | ||
| `$ stackit argus scrape-config generate-payload --job-name my-config --instance-id xxx`), | ||
| ), | ||
@@ -76,3 +82,3 @@ RunE: func(cmd *cobra.Command, args []string) error { | ||
| createPayload := argusUtils.DefaultCreateScrapeConfigPayload | ||
| return outputCreateResult(p, &createPayload) | ||
| return outputCreateResult(p, model.FilePath, &createPayload) | ||
| } | ||
@@ -91,3 +97,3 @@ | ||
| return outputUpdateResult(p, payload) | ||
| return outputUpdateResult(p, model.FilePath, payload) | ||
| }, | ||
@@ -102,2 +108,3 @@ } | ||
| cmd.Flags().StringP(jobNameFlag, "n", "", "If set, generates an update payload with the current state of the given scrape config. If unset, generates a create payload with default values") | ||
| cmd.Flags().StringP(filePathFlag, "f", "", "If set, writes the payload to the given file. If unset, writes the payload to the standard output") | ||
| } | ||
@@ -119,2 +126,3 @@ | ||
| InstanceId: flags.FlagToStringValue(p, cmd, instanceIdFlag), | ||
| FilePath: flags.FlagToStringPointer(p, cmd, filePathFlag), | ||
| }, nil | ||
@@ -128,3 +136,3 @@ } | ||
| func outputCreateResult(p *print.Printer, payload *argus.CreateScrapeConfigPayload) error { | ||
| func outputCreateResult(p *print.Printer, filePath *string, payload *argus.CreateScrapeConfigPayload) error { | ||
| payloadBytes, err := json.MarshalIndent(*payload, "", " ") | ||
@@ -134,8 +142,16 @@ if err != nil { | ||
| } | ||
| p.Outputln(string(payloadBytes)) | ||
| if filePath != nil { | ||
| err = fileutils.WriteToFile(*filePath, string(payloadBytes)) | ||
| if err != nil { | ||
| return fmt.Errorf("write payload to the file: %w", err) | ||
| } | ||
| } else { | ||
| p.Outputln(string(payloadBytes)) | ||
| } | ||
| return nil | ||
| } | ||
| func outputUpdateResult(p *print.Printer, payload *argus.UpdateScrapeConfigPayload) error { | ||
| func outputUpdateResult(p *print.Printer, filePath *string, payload *argus.UpdateScrapeConfigPayload) error { | ||
| payloadBytes, err := json.MarshalIndent(*payload, "", " ") | ||
@@ -145,5 +161,13 @@ if err != nil { | ||
| } | ||
| p.Outputln(string(payloadBytes)) | ||
| if filePath != nil { | ||
| err = fileutils.WriteToFile(*filePath, string(payloadBytes)) | ||
| if err != nil { | ||
| return fmt.Errorf("write payload to the file: %w", err) | ||
| } | ||
| } else { | ||
| p.Outputln(string(payloadBytes)) | ||
| } | ||
| return nil | ||
| } |
@@ -6,4 +6,6 @@ package activateserviceaccount | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/auth" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/zalando/go-keyring" | ||
@@ -124,1 +126,60 @@ "github.com/google/go-cmp/cmp" | ||
| } | ||
| func TestStoreFlags(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| isValid bool | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| isValid: true, | ||
| }, | ||
| { | ||
| description: "no values", | ||
| model: &inputModel{ | ||
| ServiceAccountToken: "", | ||
| ServiceAccountKeyPath: "", | ||
| PrivateKeyPath: "", | ||
| TokenCustomEndpoint: "", | ||
| JwksCustomEndpoint: "", | ||
| }, | ||
| isValid: true, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| // Initialize an empty keyring | ||
| keyring.MockInit() | ||
| err := storeFlags(tt.model) | ||
| if !tt.isValid { | ||
| if err == nil { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| return | ||
| } | ||
| if err != nil { | ||
| t.Fatalf("store flags: %v", err) | ||
| } | ||
| value, err := auth.GetAuthField(auth.TOKEN_CUSTOM_ENDPOINT) | ||
| if err != nil { | ||
| t.Errorf("Failed to get value of auth field: %v", err) | ||
| } | ||
| if value != tt.model.TokenCustomEndpoint { | ||
| t.Errorf("Value of \"%s\" does not match: expected \"%s\", got \"%s\"", auth.TOKEN_CUSTOM_ENDPOINT, tt.model.TokenCustomEndpoint, value) | ||
| } | ||
| value, err = auth.GetAuthField(auth.JWKS_CUSTOM_ENDPOINT) | ||
| if err != nil { | ||
| t.Errorf("Failed to get value of auth field: %v", err) | ||
| } | ||
| if value != tt.model.JwksCustomEndpoint { | ||
| t.Errorf("Value of \"%s\" does not match: expected \"%s\", got \"%s\"", auth.JWKS_CUSTOM_ENDPOINT, tt.model.TokenCustomEndpoint, value) | ||
| } | ||
| }) | ||
| } | ||
| } |
@@ -16,4 +16,4 @@ package auth | ||
| Use: "auth", | ||
| Short: "Provides authentication functionality", | ||
| Long: "Provides authentication functionality.", | ||
| Short: "Authenticates the STACKIT CLI", | ||
| Long: "Authenticates in the STACKIT CLI.", | ||
| Args: args.NoArgs, | ||
@@ -20,0 +20,0 @@ Run: utils.CmdHelp, |
@@ -38,2 +38,3 @@ package set | ||
| skeCustomEndpointFlag = "ske-custom-endpoint" | ||
| sqlServerFlexCustomEndpointFlag = "sqlserverflex-custom-endpoint" | ||
| ) | ||
@@ -143,2 +144,3 @@ | ||
| cmd.Flags().String(skeCustomEndpointFlag, "", "SKE API base URL, used in calls to this API") | ||
| cmd.Flags().String(sqlServerFlexCustomEndpointFlag, "", "SQLServer Flex API base URL, used in calls to this API") | ||
@@ -165,2 +167,6 @@ err := viper.BindPFlag(config.ArgusCustomEndpointKey, cmd.Flags().Lookup(argusCustomEndpointFlag)) | ||
| cobra.CheckErr(err) | ||
| err = viper.BindPFlag(config.RabbitMQCustomEndpointKey, cmd.Flags().Lookup(rabbitMQCustomEndpointFlag)) | ||
| cobra.CheckErr(err) | ||
| err = viper.BindPFlag(config.RedisCustomEndpointKey, cmd.Flags().Lookup(redisCustomEndpointFlag)) | ||
| cobra.CheckErr(err) | ||
| err = viper.BindPFlag(config.ResourceManagerEndpointKey, cmd.Flags().Lookup(skeCustomEndpointFlag)) | ||
@@ -174,6 +180,4 @@ cobra.CheckErr(err) | ||
| cobra.CheckErr(err) | ||
| err = viper.BindPFlag(config.RedisCustomEndpointKey, cmd.Flags().Lookup(redisCustomEndpointFlag)) | ||
| err = viper.BindPFlag(config.SQLServerFlexCustomEndpointKey, cmd.Flags().Lookup(sqlServerFlexCustomEndpointFlag)) | ||
| cobra.CheckErr(err) | ||
| err = viper.BindPFlag(config.RabbitMQCustomEndpointKey, cmd.Flags().Lookup(rabbitMQCustomEndpointFlag)) | ||
| cobra.CheckErr(err) | ||
| } | ||
@@ -180,0 +184,0 @@ |
@@ -34,2 +34,3 @@ package unset | ||
| skeCustomEndpointFlag: true, | ||
| sqlServerFlexCustomEndpointFlag: true, | ||
| } | ||
@@ -64,2 +65,3 @@ for _, mod := range mods { | ||
| SKECustomEndpoint: true, | ||
| SQLServerFlexCustomEndpoint: true, | ||
| } | ||
@@ -110,2 +112,3 @@ for _, mod := range mods { | ||
| model.SKECustomEndpoint = false | ||
| model.SQLServerFlexCustomEndpoint = false | ||
| }), | ||
@@ -112,0 +115,0 @@ }, |
@@ -41,2 +41,3 @@ package unset | ||
| skeCustomEndpointFlag = "ske-custom-endpoint" | ||
| sqlServerFlexCustomEndpointFlag = "sqlserverflex-custom-endpoint" | ||
| ) | ||
@@ -67,2 +68,3 @@ | ||
| SKECustomEndpoint bool | ||
| SQLServerFlexCustomEndpoint bool | ||
| } | ||
@@ -154,2 +156,5 @@ | ||
| } | ||
| if model.SQLServerFlexCustomEndpoint { | ||
| viper.Set(config.SQLServerFlexCustomEndpointKey, "") | ||
| } | ||
@@ -190,2 +195,3 @@ err := config.Write() | ||
| cmd.Flags().Bool(skeCustomEndpointFlag, false, "SKE API base URL. If unset, uses the default base URL") | ||
| cmd.Flags().Bool(sqlServerFlexCustomEndpointFlag, false, "SQLServer Flex API base URL. If unset, uses the default base URL") | ||
| } | ||
@@ -217,2 +223,3 @@ | ||
| SKECustomEndpoint: flags.FlagToBoolValue(p, cmd, skeCustomEndpointFlag), | ||
| SQLServerFlexCustomEndpoint: flags.FlagToBoolValue(p, cmd, sqlServerFlexCustomEndpointFlag), | ||
| } | ||
@@ -219,0 +226,0 @@ |
@@ -54,4 +54,4 @@ package list | ||
| Use: "list", | ||
| Short: "List DNS record sets", | ||
| Long: `List DNS record sets. Successfully deleted record sets are not listed by default.`, | ||
| Short: "Lists DNS record sets", | ||
| Long: `Lists DNS record sets. Successfully deleted record sets are not listed by default.`, | ||
| Args: args.NoArgs, | ||
@@ -58,0 +58,0 @@ Example: examples.Build( |
@@ -52,4 +52,4 @@ package list | ||
| Use: "list", | ||
| Short: "List DNS zones", | ||
| Long: `List DNS zones. Successfully deleted zones are not listed by default.`, | ||
| Short: "Lists DNS zones", | ||
| Long: `Lists DNS zones. Successfully deleted zones are not listed by default.`, | ||
| Args: args.NoArgs, | ||
@@ -56,0 +56,0 @@ Example: examples.Build( |
@@ -26,6 +26,12 @@ package generatepayload | ||
| const ( | ||
| testLoadBalancerName = "example-name" | ||
| testFilePath = "example-file" | ||
| ) | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| loadBalancerNameFlag: "example-name", | ||
| loadBalancerNameFlag: testLoadBalancerName, | ||
| filePathFlag: testFilePath, | ||
| } | ||
@@ -44,3 +50,4 @@ for _, mod := range mods { | ||
| }, | ||
| LoadBalancerName: utils.Ptr("example-name"), | ||
| LoadBalancerName: utils.Ptr(testLoadBalancerName), | ||
| FilePath: utils.Ptr(testFilePath), | ||
| } | ||
@@ -54,3 +61,3 @@ for _, mod := range mods { | ||
| func fixtureRequest(mods ...func(request *loadbalancer.ApiGetLoadBalancerRequest)) loadbalancer.ApiGetLoadBalancerRequest { | ||
| request := testClient.GetLoadBalancer(testCtx, testProjectId, "example-name") | ||
| request := testClient.GetLoadBalancer(testCtx, testProjectId, testLoadBalancerName) | ||
| for _, mod := range mods { | ||
@@ -94,2 +101,12 @@ mod(&request) | ||
| { | ||
| description: "file path missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, filePathFlag) | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.FilePath = nil | ||
| }), | ||
| }, | ||
| { | ||
| description: "project id missing", | ||
@@ -96,0 +113,0 @@ flagValues: fixtureFlagValues(func(flagValues map[string]string) { |
@@ -11,2 +11,3 @@ package generatepayload | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/fileutils" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
@@ -24,2 +25,3 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| loadBalancerNameFlag = "lb-name" | ||
| filePathFlag = "file-path" | ||
| ) | ||
@@ -30,2 +32,3 @@ | ||
| LoadBalancerName *string | ||
| FilePath *string | ||
| } | ||
@@ -124,3 +127,3 @@ | ||
| `Generate a payload, and adapt it with custom values for the different configuration options`, | ||
| `$ stackit load-balancer generate-payload > ./payload.json`, | ||
| `$ stackit load-balancer generate-payload --file-path ./payload.json`, | ||
| `<Modify payload in file, if needed>`, | ||
@@ -130,5 +133,8 @@ `$ stackit load-balancer create --payload @./payload.json`), | ||
| `Generate a payload with values of an existing load balancer, and adapt it with custom values for the different configuration options`, | ||
| `$ stackit load-balancer generate-payload --lb-name xxx > ./payload.json`, | ||
| `$ stackit load-balancer generate-payload --lb-name xxx --file-path ./payload.json`, | ||
| `<Modify payload in file>`, | ||
| `$ stackit load-balancer update xxx --payload @./payload.json`), | ||
| examples.NewExample( | ||
| `Generate a payload with values of an existing load balancer, and preview it in the terminal`, | ||
| `$ stackit load-balancer generate-payload --lb-name xxx`), | ||
| ), | ||
@@ -150,3 +156,3 @@ RunE: func(cmd *cobra.Command, args []string) error { | ||
| createPayload := DefaultCreateLoadBalancerPayload | ||
| return outputCreateResult(p, &createPayload) | ||
| return outputCreateResult(p, model.FilePath, &createPayload) | ||
| } | ||
@@ -171,3 +177,3 @@ | ||
| } | ||
| return outputUpdateResult(p, updatePayload) | ||
| return outputUpdateResult(p, model.FilePath, updatePayload) | ||
| }, | ||
@@ -181,2 +187,3 @@ } | ||
| cmd.Flags().StringP(loadBalancerNameFlag, "n", "", "If set, generates the payload with the current values of the given load balancer. If unset, generates the payload with empty values") | ||
| cmd.Flags().StringP(filePathFlag, "f", "", "If set, writes the payload to the given file. If unset, writes the payload to the standard output") | ||
| } | ||
@@ -196,2 +203,3 @@ | ||
| LoadBalancerName: loadBalancerName, | ||
| FilePath: flags.FlagToStringPointer(p, cmd, filePathFlag), | ||
| } | ||
@@ -216,3 +224,3 @@ | ||
| func outputCreateResult(p *print.Printer, payload *loadbalancer.CreateLoadBalancerPayload) error { | ||
| func outputCreateResult(p *print.Printer, filePath *string, payload *loadbalancer.CreateLoadBalancerPayload) error { | ||
| payloadBytes, err := json.MarshalIndent(*payload, "", " ") | ||
@@ -222,8 +230,16 @@ if err != nil { | ||
| } | ||
| p.Outputln(string(payloadBytes)) | ||
| if filePath != nil { | ||
| err = fileutils.WriteToFile(*filePath, string(payloadBytes)) | ||
| if err != nil { | ||
| return fmt.Errorf("write create load balancer payload to the file: %w", err) | ||
| } | ||
| } else { | ||
| p.Outputln(string(payloadBytes)) | ||
| } | ||
| return nil | ||
| } | ||
| func outputUpdateResult(p *print.Printer, payload *loadbalancer.UpdateLoadBalancerPayload) error { | ||
| func outputUpdateResult(p *print.Printer, filePath *string, payload *loadbalancer.UpdateLoadBalancerPayload) error { | ||
| payloadBytes, err := json.MarshalIndent(*payload, "", " ") | ||
@@ -233,4 +249,12 @@ if err != nil { | ||
| } | ||
| p.Outputln(string(payloadBytes)) | ||
| if filePath != nil { | ||
| err = fileutils.WriteToFile(*filePath, string(payloadBytes)) | ||
| if err != nil { | ||
| return fmt.Errorf("write update load balancer payload to the file: %w", err) | ||
| } | ||
| } else { | ||
| p.Outputln(string(payloadBytes)) | ||
| } | ||
| return nil | ||
@@ -237,0 +261,0 @@ } |
@@ -42,4 +42,4 @@ package restore | ||
| Long: fmt.Sprintf("%s\n%s\n%s", | ||
| "Restores a MongoDB Flex instance from a backup of an instance or clones a MongoDB Flex instance from a point-in-time snapshot.", | ||
| "The backup is specified by a backup ID and the point-in-time snapshot is specified by a timestamp.", | ||
| "Restores a MongoDB Flex instance from a backup of an instance or clones a MongoDB Flex instance from a point-in-time backup.", | ||
| "The backup can be specified by a backup ID or a timestamp.", | ||
| "You can specify the instance to which the backup will be applied. If not specified, the backup will be applied to the same instance from which it was taken.", | ||
@@ -145,3 +145,3 @@ ), | ||
| cmd.Flags().String(backupIdFlag, "", "Backup ID") | ||
| cmd.Flags().String(timestampFlag, "", "Timestamp of the snapshot to use as a source for cloning the instance in a date-time with the RFC3339 layout format, e.g. 2024-01-01T00:00:00Z") | ||
| cmd.Flags().String(timestampFlag, "", "Timestamp to restore the instance to, in a date-time with the RFC3339 layout format, e.g. 2024-01-01T00:00:00Z") | ||
@@ -148,0 +148,0 @@ err := flags.MarkFlagsRequired(cmd, instanceIdFlag) |
@@ -142,3 +142,3 @@ package schedule | ||
| table := tables.NewTable() | ||
| table.AddRow("BACKUP SCHEDULE", *instance.BackupSchedule) | ||
| table.AddRow("BACKUP SCHEDULE (UTC)", *instance.BackupSchedule) | ||
| table.AddSeparator() | ||
@@ -145,0 +145,0 @@ table.AddRow("DAILY SNAPSHOT RETENTION (DAYS)", (*instance.Options)["dailySnapshotRetentionDays"]) |
@@ -24,6 +24,6 @@ package updateschedule | ||
| scheduleFlag = "schedule" | ||
| snapshotRetentionDaysFlag = "save-snapshot-days" | ||
| dailySnapshotRetentionDaysFlag = "save-daily-snapshot-days" | ||
| weeklySnapshotRetentionWeeksFlag = "save-weekly-snapshot-weeks" | ||
| monthlySnapshotRetentionMonthsFlag = "save-monthly-snapshot-months" | ||
| snapshotRetentionDaysFlag = "store-for-days" | ||
| dailySnapshotRetentionDaysFlag = "store-daily-backup-days" | ||
| weeklySnapshotRetentionWeeksFlag = "store-weekly-backup-weeks" | ||
| monthlySnapshotRetentionMonthsFlag = "store-monthly-backups-months" | ||
@@ -66,4 +66,4 @@ // Default values for the backup schedule options | ||
| examples.NewExample( | ||
| `Update the retention days for snapshots of a MongoDB Flex instance with ID "xxx" to 5 days`, | ||
| "$ stackit mongodbflex backup update-schedule --instance-id xxx --save-snapshot-days 5"), | ||
| `Update the retention days for backups of a MongoDB Flex instance with ID "xxx" to 5 days`, | ||
| "$ stackit mongodbflex backup update-schedule --instance-id xxx --store-for-days 5"), | ||
| ), | ||
@@ -126,6 +126,6 @@ | ||
| cmd.Flags().String(scheduleFlag, "", "Backup schedule, in the cron scheduling system format e.g. '0 0 * * *'") | ||
| cmd.Flags().Int64(snapshotRetentionDaysFlag, 0, "Number of days to retain snapshots. Should be less than or equal to the value of the daily backup.") | ||
| cmd.Flags().Int64(dailySnapshotRetentionDaysFlag, 0, "Number of days to retain daily snapshots. Should be less than or equal to the number of days of the selected weekly or monthly value.") | ||
| cmd.Flags().Int64(weeklySnapshotRetentionWeeksFlag, 0, "Number of weeks to retain weekly snapshots. Should be less than or equal to the number of weeks of the selected monthly value.") | ||
| cmd.Flags().Int64(monthlySnapshotRetentionMonthsFlag, 0, "Number of months to retain monthly snapshots") | ||
| cmd.Flags().Int64(snapshotRetentionDaysFlag, 0, "Number of days to retain backups. Should be less than or equal to the value of the daily backup.") | ||
| cmd.Flags().Int64(dailySnapshotRetentionDaysFlag, 0, "Number of days to retain daily backups. Should be less than or equal to the number of days of the selected weekly or monthly value.") | ||
| cmd.Flags().Int64(weeklySnapshotRetentionWeeksFlag, 0, "Number of weeks to retain weekly backups. Should be less than or equal to the number of weeks of the selected monthly value.") | ||
| cmd.Flags().Int64(monthlySnapshotRetentionMonthsFlag, 0, "Number of months to retain monthly backups") | ||
@@ -132,0 +132,0 @@ err := flags.MarkFlagsRequired(cmd, instanceIdFlag) |
@@ -137,3 +137,3 @@ package describe | ||
| table.AddSeparator() | ||
| table.AddRow("STORAGE SIZE", *instance.Storage.Size) | ||
| table.AddRow("STORAGE SIZE (GB)", *instance.Storage.Size) | ||
| table.AddSeparator() | ||
@@ -152,5 +152,5 @@ table.AddRow("VERSION", *instance.Version) | ||
| table.AddSeparator() | ||
| table.AddRow("RAM", *instance.Flavor.Memory) | ||
| table.AddRow("RAM (GB)", *instance.Flavor.Memory) | ||
| table.AddSeparator() | ||
| table.AddRow("BACKUP SCHEDULE", *instance.BackupSchedule) | ||
| table.AddRow("BACKUP SCHEDULE (UTC)", *instance.BackupSchedule) | ||
| table.AddSeparator() | ||
@@ -157,0 +157,0 @@ err = table.Display(p) |
@@ -19,4 +19,4 @@ package objectstorage | ||
| Use: "object-storage", | ||
| Short: "Provides functionality regarding Object Storage", | ||
| Long: "Provides functionality regarding Object Storage.", | ||
| Short: "Provides functionality for Object Storage", | ||
| Long: "Provides functionality for Object Storage.", | ||
| Args: args.NoArgs, | ||
@@ -23,0 +23,0 @@ Run: utils.CmdHelp, |
@@ -17,4 +17,4 @@ package member | ||
| Use: "member", | ||
| Short: "Provides functionality regarding organization members", | ||
| Long: "Provides functionality regarding organization members.", | ||
| Short: "Manages organization members", | ||
| Long: "Manages organization members.", | ||
| Args: args.NoArgs, | ||
@@ -21,0 +21,0 @@ Run: utils.CmdHelp, |
@@ -18,5 +18,5 @@ package organization | ||
| Use: "organization", | ||
| Short: "Provides functionality regarding organizations", | ||
| Short: "Manages organizations", | ||
| Long: fmt.Sprintf("%s\n%s", | ||
| "Provides functionality regarding organizations.", | ||
| "Manages organizations.", | ||
| "An active STACKIT organization is the root element of the resource hierarchy and a prerequisite to use any STACKIT Cloud Resource / Service.", | ||
@@ -23,0 +23,0 @@ ), |
@@ -15,4 +15,4 @@ package role | ||
| Use: "role", | ||
| Short: "Provides functionality regarding organization roles", | ||
| Long: "Provides functionality regarding organization roles.", | ||
| Short: "Manages organization roles", | ||
| Long: "Manages organization roles.", | ||
| Args: args.NoArgs, | ||
@@ -19,0 +19,0 @@ Run: utils.CmdHelp, |
@@ -139,3 +139,3 @@ package describe | ||
| table.AddSeparator() | ||
| table.AddRow("STORAGE SIZE", *instance.Storage.Size) | ||
| table.AddRow("STORAGE SIZE (GB)", *instance.Storage.Size) | ||
| table.AddSeparator() | ||
@@ -154,3 +154,3 @@ table.AddRow("VERSION", *instance.Version) | ||
| table.AddSeparator() | ||
| table.AddRow("RAM", *instance.Flavor.Memory) | ||
| table.AddRow("RAM (GB)", *instance.Flavor.Memory) | ||
| table.AddSeparator() | ||
@@ -157,0 +157,0 @@ table.AddRow("BACKUP SCHEDULE (UTC)", *instance.BackupSchedule) |
@@ -17,4 +17,4 @@ package member | ||
| Use: "member", | ||
| Short: "Provides functionality regarding project members", | ||
| Long: "Provides functionality regarding project members.", | ||
| Short: "Manages project members", | ||
| Long: "Manages project members.", | ||
| Args: args.NoArgs, | ||
@@ -21,0 +21,0 @@ Run: utils.CmdHelp, |
@@ -23,5 +23,5 @@ package project | ||
| Use: "project", | ||
| Short: "Provides functionality regarding projects", | ||
| Short: "Manages projects", | ||
| Long: fmt.Sprintf("%s\n%s", | ||
| "Provides functionality regarding projects.", | ||
| "Provides functionality for projects.", | ||
| "A project is a container for resources which is the service that you can purchase from STACKIT.", | ||
@@ -28,0 +28,0 @@ ), |
@@ -15,4 +15,4 @@ package role | ||
| Use: "role", | ||
| Short: "Provides functionality regarding project roles", | ||
| Long: "Provides functionality regarding project roles.", | ||
| Short: "Manages project roles", | ||
| Long: "Manages project roles.", | ||
| Args: args.NoArgs, | ||
@@ -19,0 +19,0 @@ Run: utils.CmdHelp, |
+22
-1
@@ -11,2 +11,3 @@ package cmd | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/auth" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/config" | ||
@@ -35,2 +36,3 @@ "github.com/stackitcloud/stackit-cli/internal/cmd/curl" | ||
| "github.com/fatih/color" | ||
| "github.com/spf13/cobra" | ||
@@ -65,3 +67,3 @@ "github.com/spf13/viper" | ||
| if flags.FlagToBoolValue(p, cmd, "version") { | ||
| p.Outputf("STACKIT CLI (BETA)\n") | ||
| p.Outputf("STACKIT CLI (beta)\n") | ||
@@ -94,5 +96,23 @@ parsedDate, err := time.Parse(time.RFC3339, date) | ||
| beautifyUsageTemplate(cmd) | ||
| return cmd | ||
| } | ||
| func beautifyUsageTemplate(cmd *cobra.Command) { | ||
| cobra.AddTemplateFunc("WhiteBold", color.New(color.FgHiWhite, color.Bold).SprintFunc()) | ||
| usageTemplate := cmd.UsageTemplate() | ||
| usageTemplate = strings.NewReplacer( | ||
| `Usage:`, `{{WhiteBold "USAGE"}}`, | ||
| `Examples:`, `{{WhiteBold "EXAMPLES"}}`, | ||
| `Aliases:`, `{{WhiteBold "ALIASES"}}`, | ||
| `Available Commands:`, `{{WhiteBold "AVAILABLE COMMANDS"}}`, | ||
| `Additional Commands:`, `{{WhiteBold "ADDITIONAL COMMANDS"}}`, | ||
| `Global Flags:`, `{{WhiteBold "GLOBAL FLAGS"}}`, | ||
| `Flags:`, `{{WhiteBold "FLAGS"}}`, | ||
| `Additional help topics:`, `{{WhiteBold "ADDITIONAL HELP TOPICS"}}`, | ||
| ).Replace(usageTemplate) | ||
| cmd.SetUsageTemplate(usageTemplate) | ||
| } | ||
| func configureFlags(cmd *cobra.Command) error { | ||
@@ -111,2 +131,3 @@ cmd.Flags().BoolP("version", "v", false, `Show "stackit" version`) | ||
| cmd.AddCommand(auth.NewCmd(p)) | ||
| cmd.AddCommand(beta.NewCmd(p)) | ||
| cmd.AddCommand(config.NewCmd(p)) | ||
@@ -113,0 +134,0 @@ cmd.AddCommand(curl.NewCmd(p)) |
@@ -66,3 +66,3 @@ package create | ||
| request = request.UpdateACLsPayload(secretsmanager.UpdateACLsPayload{ | ||
| Cidrs: utils.Ptr([]secretsmanager.AclUpdate{ | ||
| Cidrs: utils.Ptr([]secretsmanager.UpdateACLPayload{ | ||
| {Cidr: utils.Ptr("198.51.100.14/24")}, | ||
@@ -285,3 +285,3 @@ })}) | ||
| expectedRequest: fixtureUpdateACLsRequest().UpdateACLsPayload(secretsmanager.UpdateACLsPayload{ | ||
| Cidrs: utils.Ptr([]secretsmanager.AclUpdate{ | ||
| Cidrs: utils.Ptr([]secretsmanager.UpdateACLPayload{ | ||
| {Cidr: utils.Ptr("198.51.100.14/24")}, | ||
@@ -288,0 +288,0 @@ {Cidr: utils.Ptr("1.2.3.4/32")}, |
@@ -149,6 +149,6 @@ package create | ||
| cidrs := make([]secretsmanager.AclUpdate, len(*model.Acls)) | ||
| cidrs := make([]secretsmanager.UpdateACLPayload, len(*model.Acls)) | ||
| for i, acl := range *model.Acls { | ||
| cidrs[i] = secretsmanager.AclUpdate{Cidr: utils.Ptr(acl)} | ||
| cidrs[i] = secretsmanager.UpdateACLPayload{Cidr: utils.Ptr(acl)} | ||
| } | ||
@@ -155,0 +155,0 @@ |
@@ -113,6 +113,6 @@ package describe | ||
| func outputResult(p *print.Printer, outputFormat string, instance *secretsmanager.Instance, aclList *secretsmanager.AclList) error { | ||
| func outputResult(p *print.Printer, outputFormat string, instance *secretsmanager.Instance, aclList *secretsmanager.ListACLsResponse) error { | ||
| output := struct { | ||
| *secretsmanager.Instance | ||
| *secretsmanager.AclList | ||
| *secretsmanager.ListACLsResponse | ||
| }{instance, aclList} | ||
@@ -119,0 +119,0 @@ |
@@ -73,3 +73,3 @@ package update | ||
| request = request.UpdateACLsPayload(secretsmanager.UpdateACLsPayload{ | ||
| Cidrs: utils.Ptr([]secretsmanager.AclUpdate{ | ||
| Cidrs: utils.Ptr([]secretsmanager.UpdateACLPayload{ | ||
| {Cidr: utils.Ptr(testACL1)}, | ||
@@ -279,3 +279,3 @@ })}) | ||
| expectedRequest: fixtureRequest().UpdateACLsPayload(secretsmanager.UpdateACLsPayload{ | ||
| Cidrs: utils.Ptr([]secretsmanager.AclUpdate{ | ||
| Cidrs: utils.Ptr([]secretsmanager.UpdateACLPayload{ | ||
| {Cidr: utils.Ptr(testACL1)}, | ||
@@ -282,0 +282,0 @@ {Cidr: utils.Ptr(testACL2)}, |
@@ -127,6 +127,6 @@ package update | ||
| cidrs := []secretsmanager.AclUpdate{} | ||
| cidrs := []secretsmanager.UpdateACLPayload{} | ||
| for _, acl := range *model.Acls { | ||
| cidrs = append(cidrs, secretsmanager.AclUpdate{Cidr: utils.Ptr(acl)}) | ||
| cidrs = append(cidrs, secretsmanager.UpdateACLPayload{Cidr: utils.Ptr(acl)}) | ||
| } | ||
@@ -133,0 +133,0 @@ |
@@ -19,4 +19,4 @@ package key | ||
| Use: "key", | ||
| Short: "Provides functionality regarding service account keys", | ||
| Long: "Provides functionality regarding service account keys.", | ||
| Short: "Provides functionality for service account keys", | ||
| Long: "Provides functionality for service account keys.", | ||
| Args: args.NoArgs, | ||
@@ -23,0 +23,0 @@ Run: utils.CmdHelp, |
@@ -17,4 +17,4 @@ package token | ||
| Use: "token", | ||
| Short: "Provides functionality regarding service account tokens", | ||
| Long: "Provides functionality regarding service account tokens.", | ||
| Short: "Provides functionality for service account tokens", | ||
| Long: "Provides functionality for service account tokens.", | ||
| Args: args.NoArgs, | ||
@@ -21,0 +21,0 @@ Run: utils.CmdHelp, |
@@ -25,6 +25,12 @@ package generatepayload | ||
| const ( | ||
| testClusterName = "example-name" | ||
| testFilePath = "example-file" | ||
| ) | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| clusterNameFlag: "example-name", | ||
| clusterNameFlag: testClusterName, | ||
| filePathFlag: testFilePath, | ||
| } | ||
@@ -43,3 +49,4 @@ for _, mod := range mods { | ||
| }, | ||
| ClusterName: utils.Ptr("example-name"), | ||
| ClusterName: utils.Ptr(testClusterName), | ||
| FilePath: utils.Ptr(testFilePath), | ||
| } | ||
@@ -53,3 +60,3 @@ for _, mod := range mods { | ||
| func fixtureRequest(mods ...func(request *ske.ApiGetClusterRequest)) ske.ApiGetClusterRequest { | ||
| request := testClient.GetCluster(testCtx, testProjectId, "example-name") | ||
| request := testClient.GetCluster(testCtx, testProjectId, testClusterName) | ||
| for _, mod := range mods { | ||
@@ -93,2 +100,12 @@ mod(&request) | ||
| { | ||
| description: "file path missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, filePathFlag) | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.FilePath = nil | ||
| }), | ||
| }, | ||
| { | ||
| description: "project id missing", | ||
@@ -95,0 +112,0 @@ flagValues: fixtureFlagValues(func(flagValues map[string]string) { |
@@ -11,2 +11,3 @@ package generatepayload | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/fileutils" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
@@ -24,2 +25,3 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| clusterNameFlag = "cluster-name" | ||
| filePathFlag = "file-path" | ||
| ) | ||
@@ -30,2 +32,3 @@ | ||
| ClusterName *string | ||
| FilePath *string | ||
| } | ||
@@ -45,3 +48,3 @@ | ||
| `Generate a payload with default values, and adapt it with custom values for the different configuration options`, | ||
| `$ stackit ske cluster generate-payload > ./payload.json`, | ||
| `$ stackit ske cluster generate-payload --file-path ./payload.json`, | ||
| `<Modify payload in file, if needed>`, | ||
@@ -51,5 +54,8 @@ `$ stackit ske cluster create my-cluster --payload @./payload.json`), | ||
| `Generate a payload with values of a cluster, and adapt it with custom values for the different configuration options`, | ||
| `$ stackit ske cluster generate-payload --cluster-name my-cluster > ./payload.json`, | ||
| `$ stackit ske cluster generate-payload --cluster-name my-cluster --file-path ./payload.json`, | ||
| `<Modify payload in file>`, | ||
| `$ stackit ske cluster update my-cluster --payload @./payload.json`), | ||
| examples.NewExample( | ||
| `Generate a payload with values of a cluster, and preview it in the terminal`, | ||
| `$ stackit ske cluster generate-payload --cluster-name my-cluster`), | ||
| ), | ||
@@ -91,3 +97,3 @@ RunE: func(cmd *cobra.Command, args []string) error { | ||
| return outputResult(p, payload) | ||
| return outputResult(p, model.FilePath, payload) | ||
| }, | ||
@@ -101,2 +107,3 @@ } | ||
| cmd.Flags().StringP(clusterNameFlag, "n", "", "If set, generates the payload with the current state of the given cluster. If unset, generates the payload with default values") | ||
| cmd.Flags().StringP(filePathFlag, "f", "", "If set, writes the payload to the given file. If unset, writes the payload to the standard output") | ||
| } | ||
@@ -116,2 +123,3 @@ | ||
| ClusterName: clusterName, | ||
| FilePath: flags.FlagToStringPointer(p, cmd, filePathFlag), | ||
| } | ||
@@ -136,3 +144,3 @@ | ||
| func outputResult(p *print.Printer, payload *ske.CreateOrUpdateClusterPayload) error { | ||
| func outputResult(p *print.Printer, filePath *string, payload *ske.CreateOrUpdateClusterPayload) error { | ||
| payloadBytes, err := json.MarshalIndent(*payload, "", " ") | ||
@@ -142,5 +150,13 @@ if err != nil { | ||
| } | ||
| p.Outputln(string(payloadBytes)) | ||
| if filePath != nil { | ||
| err = fileutils.WriteToFile(*filePath, string(payloadBytes)) | ||
| if err != nil { | ||
| return fmt.Errorf("write payload to the file: %w", err) | ||
| } | ||
| } else { | ||
| p.Outputln(string(payloadBytes)) | ||
| } | ||
| return nil | ||
| } |
@@ -138,2 +138,23 @@ package options | ||
| func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { | ||
| // filter output based on the flags | ||
| if !model.AvailabilityZones { | ||
| options.AvailabilityZones = nil | ||
| } | ||
| if !model.KubernetesVersions { | ||
| options.KubernetesVersions = nil | ||
| } | ||
| if !model.MachineImages { | ||
| options.MachineImages = nil | ||
| } | ||
| if !model.MachineTypes { | ||
| options.MachineTypes = nil | ||
| } | ||
| if !model.VolumeTypes { | ||
| options.VolumeTypes = nil | ||
| } | ||
| switch model.OutputFormat { | ||
@@ -156,29 +177,21 @@ case print.JSONOutputFormat: | ||
| default: | ||
| return outputResultAsTable(p, model, options) | ||
| return outputResultAsTable(p, options) | ||
| } | ||
| } | ||
| func outputResultAsTable(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { | ||
| func outputResultAsTable(p *print.Printer, options *ske.ProviderOptions) error { | ||
| content := "" | ||
| if model.AvailabilityZones { | ||
| content += renderAvailabilityZones(options) | ||
| content += renderAvailabilityZones(options) | ||
| kubernetesVersionsRendered, err := renderKubernetesVersions(options) | ||
| if err != nil { | ||
| return fmt.Errorf("render Kubernetes versions: %w", err) | ||
| } | ||
| if model.KubernetesVersions { | ||
| kubernetesVersionsRendered, err := renderKubernetesVersions(options) | ||
| if err != nil { | ||
| return fmt.Errorf("render Kubernetes versions: %w", err) | ||
| } | ||
| content += kubernetesVersionsRendered | ||
| } | ||
| if model.MachineImages { | ||
| content += renderMachineImages(options) | ||
| } | ||
| if model.MachineTypes { | ||
| content += renderMachineTypes(options) | ||
| } | ||
| if model.VolumeTypes { | ||
| content += renderVolumeTypes(options) | ||
| } | ||
| content += kubernetesVersionsRendered | ||
| err := p.PagerDisplay(content) | ||
| content += renderMachineImages(options) | ||
| content += renderMachineTypes(options) | ||
| content += renderVolumeTypes(options) | ||
| err = p.PagerDisplay(content) | ||
| if err != nil { | ||
@@ -192,2 +205,6 @@ return fmt.Errorf("display output: %w", err) | ||
| func renderAvailabilityZones(resp *ske.ProviderOptions) string { | ||
| if resp.AvailabilityZones == nil { | ||
| return "" | ||
| } | ||
| zones := *resp.AvailabilityZones | ||
@@ -206,2 +223,6 @@ | ||
| func renderKubernetesVersions(resp *ske.ProviderOptions) (string, error) { | ||
| if resp.KubernetesVersions == nil { | ||
| return "", nil | ||
| } | ||
| versions := *resp.KubernetesVersions | ||
@@ -228,2 +249,6 @@ | ||
| func renderMachineImages(resp *ske.ProviderOptions) string { | ||
| if resp.MachineImages == nil { | ||
| return "" | ||
| } | ||
| images := *resp.MachineImages | ||
@@ -258,2 +283,6 @@ | ||
| func renderMachineTypes(resp *ske.ProviderOptions) string { | ||
| if resp.MachineTypes == nil { | ||
| return "" | ||
| } | ||
| types := *resp.MachineTypes | ||
@@ -272,2 +301,6 @@ | ||
| func renderVolumeTypes(resp *ske.ProviderOptions) string { | ||
| if resp.VolumeTypes == nil { | ||
| return "" | ||
| } | ||
| types := *resp.VolumeTypes | ||
@@ -274,0 +307,0 @@ |
@@ -36,2 +36,3 @@ package config | ||
| SKECustomEndpointKey = "ske_custom_endpoint" | ||
| SQLServerFlexCustomEndpointKey = "sqlserverflex_custom_endpoint" | ||
@@ -74,2 +75,3 @@ AsyncDefault = false | ||
| SKECustomEndpointKey, | ||
| SQLServerFlexCustomEndpointKey, | ||
| } | ||
@@ -151,2 +153,3 @@ | ||
| viper.SetDefault(SKECustomEndpointKey, "") | ||
| viper.SetDefault(SQLServerFlexCustomEndpointKey, "") | ||
| } |
@@ -197,6 +197,5 @@ package errors | ||
| type DatabaseInputFlavorError struct { | ||
| Service string | ||
| Operation string | ||
| Cmd *cobra.Command | ||
| Args []string | ||
| Service string | ||
| Cmd *cobra.Command | ||
| Args []string | ||
| } | ||
@@ -209,6 +208,9 @@ | ||
| } | ||
| // Assumes a structure of the form "stackit <service> <resource> <operation>" | ||
| service := e.Cmd.Parent().Parent().Use | ||
| return fmt.Sprintf(DATABASE_INVALID_INPUT_FLAVOR, fullCommandPath, service) | ||
| if e.Service == "" { | ||
| // Assumes a structure of the form "stackit <service> <resource> <operation>" | ||
| e.Service = e.Cmd.Parent().Parent().Use | ||
| } | ||
| return fmt.Sprintf(DATABASE_INVALID_INPUT_FLAVOR, fullCommandPath, e.Service) | ||
| } | ||
@@ -215,0 +217,0 @@ |
@@ -16,3 +16,3 @@ package print | ||
| var defaultHTTPHeaders = []string{"Accept", "Content-Type", "Content-Length", "User-Agent", "Date", "Referrer-Policy"} | ||
| var defaultHTTPHeaders = []string{"Accept", "Content-Type", "Content-Length", "User-Agent", "Date", "Referrer-Policy", "Traceparent"} | ||
@@ -19,0 +19,0 @@ // BuildDebugStrFromInputModel converts an input model to a user-friendly string representation. |
@@ -647,1 +647,53 @@ package utils | ||
| } | ||
| func TestGetInstanceType(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| numReplicas int64 | ||
| expectedOutput string | ||
| isValid bool | ||
| }{ | ||
| { | ||
| description: "single", | ||
| numReplicas: 1, | ||
| expectedOutput: "Single", | ||
| isValid: true, | ||
| }, | ||
| { | ||
| description: "replica set", | ||
| numReplicas: 3, | ||
| expectedOutput: "Replica", | ||
| isValid: true, | ||
| }, | ||
| { | ||
| description: "sharded cluster", | ||
| numReplicas: 9, | ||
| expectedOutput: "Sharded", | ||
| isValid: true, | ||
| }, | ||
| { | ||
| description: "invalid", | ||
| numReplicas: 0, | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| output, err := GetInstanceType(tt.numReplicas) | ||
| if !tt.isValid { | ||
| if err == nil { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| return | ||
| } | ||
| if err != nil { | ||
| t.Fatalf("failed on valid input: %v", err) | ||
| } | ||
| if output != tt.expectedOutput { | ||
| t.Fatalf("expected output to be %s, got %s", tt.expectedOutput, output) | ||
| } | ||
| }) | ||
| } | ||
| } |
@@ -572,1 +572,47 @@ package utils | ||
| } | ||
| func TestGetInstanceType(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| numReplicas int64 | ||
| expectedOutput string | ||
| isValid bool | ||
| }{ | ||
| { | ||
| description: "single", | ||
| numReplicas: 1, | ||
| expectedOutput: "Single", | ||
| isValid: true, | ||
| }, | ||
| { | ||
| description: "replica set", | ||
| numReplicas: 3, | ||
| expectedOutput: "Replica", | ||
| isValid: true, | ||
| }, | ||
| { | ||
| description: "invalid", | ||
| numReplicas: 0, | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| output, err := GetInstanceType(tt.numReplicas) | ||
| if !tt.isValid { | ||
| if err == nil { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| return | ||
| } | ||
| if err != nil { | ||
| t.Fatalf("failed on valid input: %v", err) | ||
| } | ||
| if output != tt.expectedOutput { | ||
| t.Fatalf("expected output to be %s, got %s", tt.expectedOutput, output) | ||
| } | ||
| }) | ||
| } | ||
| } |
@@ -613,1 +613,28 @@ package utils | ||
| } | ||
| func TestGetDefaultKubeconfigPath(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| }{ | ||
| { | ||
| description: "base", | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| output, err := GetDefaultKubeconfigPath() | ||
| if err != nil { | ||
| t.Errorf("failed on valid input") | ||
| } | ||
| userHome, err := os.UserHomeDir() | ||
| if err != nil { | ||
| t.Errorf("could not get user home directory") | ||
| } | ||
| if output != filepath.Join(userHome, ".kube", "config") { | ||
| t.Errorf("expected output to be %s, got %s", filepath.Join(userHome, ".kube", "config"), output) | ||
| } | ||
| }) | ||
| } | ||
| } |
+23
-25
@@ -52,22 +52,22 @@ [](https://goreportcard.com/report/github.com/stackitcloud/stackit-cli)  [](https://www.apache.org/licenses/LICENSE-2.0) | ||
| | Service | CLI Commands | Status | | ||
| | ---------------------------------- | ------------------------- | ----------------------- | | ||
| | Argus | `argus` | :white_check_mark: | | ||
| | Infrastructure as a Service (IaaS) | | Will be integrated soon | | ||
| | Authorization | `project`, `organization` | :white_check_mark: | | ||
| | DNS | `dns` | :white_check_mark: | | ||
| | Kubernetes Engine (SKE) | `ske` | :white_check_mark: | | ||
| | Load Balancer | `load-balancer` | :white_check_mark: | | ||
| | LogMe | `logme` | :white_check_mark: | | ||
| | MariaDB | `mariadb` | :white_check_mark: | | ||
| | MongoDB Flex | `mongodbflex` | :white_check_mark: | | ||
| | Object Storage | `object-storage` | :white_check_mark: | | ||
| | OpenSearch | `opensearch` | :white_check_mark: | | ||
| | PostgreSQL Flex | `postgresflex` | :white_check_mark: | | ||
| | RabbitMQ | `rabbitmq` | :white_check_mark: | | ||
| | Redis | `redis` | :white_check_mark: | | ||
| | Resource Manager | `project` | :white_check_mark: | | ||
| | Secrets Manager | `secrets-manager` | :white_check_mark: | | ||
| | Service Account | `service-account` | :white_check_mark: | | ||
| | SQLServer Flex | | Will be integrated soon | | ||
| | Service | CLI Commands | Status | | ||
| | ---------------------------------- | ------------------------- | ------------------------- | | ||
| | Argus | `argus` | :white_check_mark: | | ||
| | Infrastructure as a Service (IaaS) | | Will be integrated soon | | ||
| | Authorization | `project`, `organization` | :white_check_mark: | | ||
| | DNS | `dns` | :white_check_mark: | | ||
| | Kubernetes Engine (SKE) | `ske` | :white_check_mark: | | ||
| | Load Balancer | `load-balancer` | :white_check_mark: | | ||
| | LogMe | `logme` | :white_check_mark: | | ||
| | MariaDB | `mariadb` | :white_check_mark: | | ||
| | MongoDB Flex | `mongodbflex` | :white_check_mark: | | ||
| | Object Storage | `object-storage` | :white_check_mark: | | ||
| | OpenSearch | `opensearch` | :white_check_mark: | | ||
| | PostgreSQL Flex | `postgresflex` | :white_check_mark: | | ||
| | RabbitMQ | `rabbitmq` | :white_check_mark: | | ||
| | Redis | `redis` | :white_check_mark: | | ||
| | Resource Manager | `project` | :white_check_mark: | | ||
| | Secrets Manager | `secrets-manager` | :white_check_mark: | | ||
| | Service Account | `service-account` | :white_check_mark: | | ||
| | SQLServer Flex | `beta sqlserverflex` | :white_check_mark: (beta) | | ||
@@ -143,2 +143,3 @@ ## Authentication | ||
| When using `less` as a pager, STACKIT CLI will automatically pass following options | ||
| - -F, --quit-if-one-screen - Less will automatically exit if the entire file can be displayed on the first screen. | ||
@@ -150,14 +151,11 @@ - -S, --chop-long-lines - Lines longer than the screen width will be chopped rather than being folded. | ||
| > These options will not be added automatically if a custom pager is defined. | ||
| > | ||
| > | ||
| > In that case, users can define the parameters by using the specific environment variable required by the `PAGER` (if supported). | ||
| > | ||
| > For example, if user sets the `PAGER` environment variable to `less` and would like to pass some arguments, `LESS` environment variable must be used as following: | ||
| > | ||
| > export PAGER="less" | ||
| > | ||
| > | ||
| > export LESS="-R" | ||
| ## Autocompletion | ||
@@ -164,0 +162,0 @@ |