mygithub.libinneed.workers.dev/stackitcloud/stackit-cli
Advanced tools
| ## stackit argus plans | ||
| Lists all Argus service plans | ||
| ### Synopsis | ||
| Lists all Argus service plans. | ||
| ``` | ||
| stackit argus plans [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| List all Argus service plans | ||
| $ stackit argus plans | ||
| List all Argus service plans in JSON format | ||
| $ stackit argus plans --output-format json | ||
| List up to 10 Argus service plans | ||
| $ stackit argus plans --limit 10 | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit argus plans" | ||
| --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"] | ||
| -p, --project-id string Project ID | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit argus](./stackit_argus.md) - Provides functionality for Argus | ||
| ## stackit argus | ||
| Provides functionality for Argus | ||
| ### Synopsis | ||
| Provides functionality for Argus. | ||
| ``` | ||
| stackit argus [flags] | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit argus" | ||
| ``` | ||
| ### 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"] | ||
| -p, --project-id string Project ID | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit](./stackit.md) - Manage STACKIT resources using the command line | ||
| * [stackit argus plans](./stackit_argus_plans.md) - Lists all Argus service plans | ||
| ## stackit object-storage credentials create | ||
| Creates credentials for an Object Storage credentials group | ||
| ### Synopsis | ||
| Creates credentials for an Object Storage credentials group. The credentials are only displayed upon creation, and will not be retrievable later. | ||
| ``` | ||
| stackit object-storage credentials create [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Create credentials for a credentials group with ID "xxx" | ||
| $ stackit object-storage credentials create --credentials-group-id xxx | ||
| Create credentials for a credentials group with ID "xxx", including a specific expiration date | ||
| $ stackit object-storage credentials create --credentials-group-id xxx --expire-date 2024-03-06T00:00:00.000Z | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| --credentials-group-id string Credentials Group ID | ||
| --expire-date string Expiration date for the credentials, in a date-time with the RFC3339 layout format, e.g. 2024-01-01T00:00:00Z | ||
| -h, --help Help for "stackit object-storage credentials create" | ||
| ``` | ||
| ### 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"] | ||
| -p, --project-id string Project ID | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit object-storage credentials](./stackit_object-storage_credentials.md) - Provides functionality for Object Storage credentials | ||
| ## stackit object-storage credentials delete | ||
| Deletes credentials of an Object Storage credentials group | ||
| ### Synopsis | ||
| Deletes credentials of an Object Storage credentials group | ||
| ``` | ||
| stackit object-storage credentials delete CREDENTIALS_ID [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Delete a credential with ID "xxx" of credentials group with ID "yyy" | ||
| $ stackit object-storage credentials delete xxx --credentials-group-id yyy | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| --credentials-group-id string Credentials Group ID | ||
| -h, --help Help for "stackit object-storage credentials 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"] | ||
| -p, --project-id string Project ID | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit object-storage credentials](./stackit_object-storage_credentials.md) - Provides functionality for Object Storage credentials | ||
| ## stackit object-storage credentials list | ||
| Lists all credentials for an Object Storage credentials group | ||
| ### Synopsis | ||
| Lists all credentials for an Object Storage credentials group. | ||
| ``` | ||
| stackit object-storage credentials list [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| List all credentials for a credentials group with ID "xxx" | ||
| $ stackit object-storage credentials list --credentials-group-id xxx | ||
| List all credentials for a credentials group with ID "xxx" in JSON format | ||
| $ stackit object-storage credentials list --credentials-group-id xxx --output-format json | ||
| List up to 10 credentials for a credentials group with ID "xxx" | ||
| $ stackit object-storage credentials list --credentials-group-id xxx --limit 10 | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| --credentials-group-id string Credentials Group ID | ||
| -h, --help Help for "stackit object-storage credentials 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"] | ||
| -p, --project-id string Project ID | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit object-storage credentials](./stackit_object-storage_credentials.md) - Provides functionality for Object Storage credentials | ||
| ## stackit object-storage credentials | ||
| Provides functionality for Object Storage credentials | ||
| ### Synopsis | ||
| Provides functionality for Object Storage credentials. | ||
| ``` | ||
| stackit object-storage credentials [flags] | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit object-storage credentials" | ||
| ``` | ||
| ### 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"] | ||
| -p, --project-id string Project ID | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit object-storage](./stackit_object-storage.md) - Provides functionality regarding Object Storage | ||
| * [stackit object-storage credentials create](./stackit_object-storage_credentials_create.md) - Creates credentials for an Object Storage credentials group | ||
| * [stackit object-storage credentials delete](./stackit_object-storage_credentials_delete.md) - Deletes credentials of an Object Storage credentials group | ||
| * [stackit object-storage credentials list](./stackit_object-storage_credentials_list.md) - Lists all credentials for an Object Storage credentials group | ||
| ## stackit secrets-manager user create | ||
| Creates a Secrets Manager user | ||
| ### Synopsis | ||
| Creates a Secrets Manager user. | ||
| The username and password are auto-generated and provided upon creation. The password cannot be retrieved later. | ||
| A description can be provided to identify a user. | ||
| ``` | ||
| stackit secrets-manager user create [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Create a Secrets Manager user for instance with ID "xxx" and description "yyy" | ||
| $ stackit secrets-manager user create --instance-id xxx --description yyy | ||
| Create a Secrets Manager user for instance with ID "xxx" with write access to the secrets engine | ||
| $ stackit secrets-manager user create --instance-id xxx --write | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| --description string A user chosen description to differentiate between multiple users | ||
| -h, --help Help for "stackit secrets-manager user create" | ||
| --instance-id string ID of the instance | ||
| --write User write access to the secrets engine. If unset, user is read-only | ||
| ``` | ||
| ### 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"] | ||
| -p, --project-id string Project ID | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit secrets-manager user](./stackit_secrets-manager_user.md) - Provides functionality for Secrets Manager users | ||
| ## stackit secrets-manager user delete | ||
| Deletes a Secrets Manager user | ||
| ### Synopsis | ||
| Deletes a Secrets Manager user by ID. You can get the IDs of users for an instance by running: | ||
| $ stackit secrets-manager user delete --instance-id <INSTANCE_ID> | ||
| ``` | ||
| stackit secrets-manager user delete USER_ID [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Delete a Secrets Manager user with ID "xxx" for instance with ID "yyy" | ||
| $ stackit secrets-manager user delete xxx --instance-id yyy | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit secrets-manager user delete" | ||
| --instance-id string Instance ID | ||
| ``` | ||
| ### 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"] | ||
| -p, --project-id string Project ID | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit secrets-manager user](./stackit_secrets-manager_user.md) - Provides functionality for Secrets Manager users | ||
| ## stackit secrets-manager user describe | ||
| Shows details of a Secrets Manager user | ||
| ### Synopsis | ||
| Shows details of a Secrets Manager user. | ||
| ``` | ||
| stackit secrets-manager user describe USER_ID [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Get details of a Secrets Manager user with ID "xxx" of instance with ID "yyy" | ||
| $ stackit secrets-manager user describe xxx --instance-id yyy | ||
| Get details of a Secrets Manager user with ID "xxx" of instance with ID "yyy" in table format | ||
| $ stackit secrets-manager user describe xxx --instance-id yyy --output-format pretty | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit secrets-manager user describe" | ||
| --instance-id string ID of the 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"] | ||
| -p, --project-id string Project ID | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit secrets-manager user](./stackit_secrets-manager_user.md) - Provides functionality for Secrets Manager users | ||
| ## stackit secrets-manager user list | ||
| Lists all Secrets Manager users | ||
| ### Synopsis | ||
| Lists all Secrets Manager users. | ||
| ``` | ||
| stackit secrets-manager user list [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| List all Secrets Manager users of instance with ID "xxx | ||
| $ stackit secrets-manager user list --instance-id xxx | ||
| List all Secrets Manager users in JSON format with ID "xxx | ||
| $ stackit secrets-manager user list --instance-id xxx --output-format json | ||
| List up to 10 Secrets Manager users with ID "xxx" | ||
| $ stackit secrets-manager user list --instance-id xxx --limit 10 | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit secrets-manager user list" | ||
| --instance-id string Instance ID | ||
| --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"] | ||
| -p, --project-id string Project ID | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit secrets-manager user](./stackit_secrets-manager_user.md) - Provides functionality for Secrets Manager users | ||
| ## stackit secrets-manager user update | ||
| Updates the write privileges Secrets Manager user | ||
| ### Synopsis | ||
| Updates the write privileges Secrets Manager user. | ||
| ``` | ||
| stackit secrets-manager user update USER_ID [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Enable write access of a Secrets Manager user with ID "xxx" of instance with ID "yyy" | ||
| $ stackit secrets-manager user update xxx --instance-id yyy --enable-write | ||
| Disable write access of a Secrets Manager user with ID "xxx" of instance with ID "yyy" | ||
| $ stackit secrets-manager user update xxx --instance-id yyy --disable-write | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| --disable-write Set the user to have read-only access. | ||
| --enable-write Set the user to have write access. | ||
| -h, --help Help for "stackit secrets-manager user update" | ||
| --instance-id string ID of the 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"] | ||
| -p, --project-id string Project ID | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit secrets-manager user](./stackit_secrets-manager_user.md) - Provides functionality for Secrets Manager users | ||
| ## stackit secrets-manager user | ||
| Provides functionality for Secrets Manager users | ||
| ### Synopsis | ||
| Provides functionality for Secrets Manager users. | ||
| ``` | ||
| stackit secrets-manager user [flags] | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit secrets-manager user" | ||
| ``` | ||
| ### 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"] | ||
| -p, --project-id string Project ID | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit secrets-manager](./stackit_secrets-manager.md) - Provides functionality for Secrets Manager | ||
| * [stackit secrets-manager user create](./stackit_secrets-manager_user_create.md) - Creates a Secrets Manager user | ||
| * [stackit secrets-manager user delete](./stackit_secrets-manager_user_delete.md) - Deletes a Secrets Manager user | ||
| * [stackit secrets-manager user describe](./stackit_secrets-manager_user_describe.md) - Shows details of a Secrets Manager user | ||
| * [stackit secrets-manager user list](./stackit_secrets-manager_user_list.md) - Lists all Secrets Manager users | ||
| * [stackit secrets-manager user update](./stackit_secrets-manager_user_update.md) - Updates the write privileges Secrets Manager user | ||
| package argus | ||
| import ( | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/argus/plans" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| func NewCmd() *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "argus", | ||
| Short: "Provides functionality for Argus", | ||
| Long: "Provides functionality for Argus.", | ||
| Args: args.NoArgs, | ||
| Run: utils.CmdHelp, | ||
| } | ||
| addSubcommands(cmd) | ||
| return cmd | ||
| } | ||
| func addSubcommands(cmd *cobra.Command) { | ||
| cmd.AddCommand(plans.NewCmd()) | ||
| } |
| package plans | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "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/argus" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &argus.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, | ||
| }, | ||
| Limit: utils.Ptr(int64(10)), | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *argus.ApiListPlansRequest)) argus.ApiListPlansRequest { | ||
| request := testClient.ListPlans(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) | ||
| } | ||
| model, err := parseInput(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 argus.ApiListPlansRequest | ||
| }{ | ||
| { | ||
| 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 plans | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "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/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/argus/client" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/tables" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/argus" | ||
| ) | ||
| const ( | ||
| limitFlag = "limit" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| Limit *int64 | ||
| } | ||
| func NewCmd() *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "plans", | ||
| Short: "Lists all Argus service plans", | ||
| Long: "Lists all Argus service plans.", | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `List all Argus service plans`, | ||
| "$ stackit argus plans"), | ||
| examples.NewExample( | ||
| `List all Argus service plans in JSON format`, | ||
| "$ stackit argus plans --output-format json"), | ||
| examples.NewExample( | ||
| `List up to 10 Argus service plans`, | ||
| "$ stackit argus plans --limit 10"), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("get Argus service plans: %w", err) | ||
| } | ||
| plans := *resp.Plans | ||
| if len(plans) == 0 { | ||
| projectLabel, err := projectname.GetProjectName(ctx, cmd) | ||
| if err != nil { | ||
| projectLabel = model.ProjectId | ||
| } | ||
| cmd.Printf("No plans found for project %q\n", projectLabel) | ||
| return nil | ||
| } | ||
| // Truncate output | ||
| if model.Limit != nil && len(plans) > int(*model.Limit) { | ||
| plans = plans[:*model.Limit] | ||
| } | ||
| return outputResult(cmd, model.OutputFormat, plans) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") | ||
| } | ||
| func parseInput(cmd *cobra.Command) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| limit := flags.FlagToInt64Pointer(cmd, limitFlag) | ||
| if limit != nil && *limit < 1 { | ||
| return nil, &errors.FlagValidationError{ | ||
| Flag: limitFlag, | ||
| Details: "must be greater than 0", | ||
| } | ||
| } | ||
| return &inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| Limit: limit, | ||
| }, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *argus.APIClient) argus.ApiListPlansRequest { | ||
| req := apiClient.ListPlans(ctx, model.ProjectId) | ||
| return req | ||
| } | ||
| func outputResult(cmd *cobra.Command, outputFormat string, plans []argus.Plan) error { | ||
| switch outputFormat { | ||
| case globalflags.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(plans, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal Argus plans: %w", err) | ||
| } | ||
| cmd.Println(string(details)) | ||
| return nil | ||
| default: | ||
| table := tables.NewTable() | ||
| table.SetHeader("ID", "PLAN NAME", "DESCRIPTION") | ||
| for i := range plans { | ||
| o := plans[i] | ||
| table.AddRow(*o.Id, *o.Name, *o.Description) | ||
| table.AddSeparator() | ||
| } | ||
| table.EnableAutoMergeOnColumns(1) | ||
| err := table.Display(cmd) | ||
| if err != nil { | ||
| return fmt.Errorf("render table: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
| } |
| package create | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "time" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "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/objectstorage" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &objectstorage.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testCredentialsGroupId = uuid.NewString() | ||
| var testExpirationDate = "2024-01-01T00:00:00Z" | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| credentialsGroupIdFlag: testCredentialsGroupId, | ||
| expireDateFlag: testExpirationDate, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| testExpirationDate, err := time.Parse(expirationTimeFormat, testExpirationDate) | ||
| if err != nil { | ||
| return &inputModel{} | ||
| } | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| }, | ||
| ExpireDate: utils.Ptr(testExpirationDate), | ||
| CredentialsGroupId: testCredentialsGroupId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixturePayload(mods ...func(payload *objectstorage.CreateAccessKeyPayload)) objectstorage.CreateAccessKeyPayload { | ||
| testExpirationDate, err := time.Parse(expirationTimeFormat, testExpirationDate) | ||
| if err != nil { | ||
| return objectstorage.CreateAccessKeyPayload{} | ||
| } | ||
| payload := objectstorage.CreateAccessKeyPayload{ | ||
| Expires: utils.Ptr(testExpirationDate), | ||
| } | ||
| for _, mod := range mods { | ||
| mod(&payload) | ||
| } | ||
| return payload | ||
| } | ||
| func fixtureRequest(mods ...func(request *objectstorage.ApiCreateAccessKeyRequest)) objectstorage.ApiCreateAccessKeyRequest { | ||
| request := testClient.CreateAccessKey(testCtx, testProjectId) | ||
| request = request.CreateAccessKeyPayload(fixturePayload()) | ||
| request = request.CredentialsGroup(testCredentialsGroupId) | ||
| 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: "credentials group id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, credentialsGroupIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "credentials group id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[credentialsGroupIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "credentials group id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[credentialsGroupIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "expiration date is missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, expireDateFlag) | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.ExpireDate = nil | ||
| }), | ||
| }, | ||
| { | ||
| description: "expiration date is empty", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[expireDateFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "expiration date is invalid", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[expireDateFlag] = "test" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "expiration date is invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[expireDateFlag] = "11:00 12/12/2024" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| cmd := NewCmd() | ||
| 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(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 objectstorage.ApiCreateAccessKeyRequest | ||
| }{ | ||
| { | ||
| 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 create | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "time" | ||
| objectStorageUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/utils" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/confirm" | ||
| "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/services/object-storage/client" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" | ||
| ) | ||
| const ( | ||
| expireDateFlag = "expire-date" | ||
| credentialsGroupIdFlag = "credentials-group-id" | ||
| expirationTimeFormat = time.RFC3339 | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| ExpireDate *time.Time | ||
| CredentialsGroupId string | ||
| HidePassword bool | ||
| } | ||
| func NewCmd() *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "create", | ||
| Short: "Creates credentials for an Object Storage credentials group", | ||
| Long: "Creates credentials for an Object Storage credentials group. The credentials are only displayed upon creation, and will not be retrievable later.", | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Create credentials for a credentials group with ID "xxx"`, | ||
| "$ stackit object-storage credentials create --credentials-group-id xxx"), | ||
| examples.NewExample( | ||
| `Create credentials for a credentials group with ID "xxx", including a specific expiration date`, | ||
| "$ stackit object-storage credentials create --credentials-group-id xxx --expire-date 2024-03-06T00:00:00.000Z"), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| credentialsGroupLabel, err := objectStorageUtils.GetCredentialsGroupName(ctx, apiClient, model.ProjectId, model.CredentialsGroupId) | ||
| if err != nil { | ||
| credentialsGroupLabel = model.CredentialsGroupId | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to create credentials in group %q?", credentialsGroupLabel) | ||
| err = confirm.PromptForConfirmation(cmd, prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("create Object Storage credentials: %w", err) | ||
| } | ||
| expireDate := "Never" | ||
| if resp.Expires != nil && *resp.Expires != "" { | ||
| expireDate = *resp.Expires | ||
| } | ||
| cmd.Printf("Created credentials in group %q. Credentials ID: %s\n\n", credentialsGroupLabel, *resp.KeyId) | ||
| cmd.Printf("Access Key ID: %s\n", *resp.AccessKey) | ||
| cmd.Printf("Secret Access Key: %s\n", *resp.SecretAccessKey) | ||
| cmd.Printf("Expire Date: %s\n", expireDate) | ||
| return nil | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().String(expireDateFlag, "", "Expiration date for the credentials, in a date-time with the RFC3339 layout format, e.g. 2024-01-01T00:00:00Z") | ||
| cmd.Flags().Var(flags.UUIDFlag(), credentialsGroupIdFlag, "Credentials Group ID") | ||
| err := flags.MarkFlagsRequired(cmd, credentialsGroupIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(cmd *cobra.Command) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| expireDate, err := flags.FlagToDateTimePointer(cmd, expireDateFlag, expirationTimeFormat) | ||
| if err != nil { | ||
| return nil, &errors.FlagValidationError{ | ||
| Flag: expireDateFlag, | ||
| Details: err.Error(), | ||
| } | ||
| } | ||
| return &inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| ExpireDate: expireDate, | ||
| CredentialsGroupId: flags.FlagToStringValue(cmd, credentialsGroupIdFlag), | ||
| }, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiCreateAccessKeyRequest { | ||
| req := apiClient.CreateAccessKey(ctx, model.ProjectId) | ||
| req = req.CredentialsGroup(model.CredentialsGroupId) | ||
| req = req.CreateAccessKeyPayload(objectstorage.CreateAccessKeyPayload{ | ||
| Expires: model.ExpireDate, | ||
| }) | ||
| return req | ||
| } |
| package credentials | ||
| import ( | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/credentials/create" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/credentials/delete" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/credentials/list" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| func NewCmd() *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "credentials", | ||
| Short: "Provides functionality for Object Storage credentials", | ||
| Long: "Provides functionality for Object Storage credentials.", | ||
| Args: args.NoArgs, | ||
| Run: utils.CmdHelp, | ||
| } | ||
| addSubcommands(cmd) | ||
| return cmd | ||
| } | ||
| func addSubcommands(cmd *cobra.Command) { | ||
| cmd.AddCommand(create.NewCmd()) | ||
| cmd.AddCommand(delete.NewCmd()) | ||
| cmd.AddCommand(list.NewCmd()) | ||
| } |
| package delete | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &objectstorage.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testCredentialsGroupId = uuid.NewString() | ||
| var testCredentialsId = "keyID" | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testCredentialsId, | ||
| } | ||
| 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, | ||
| credentialsGroupIdFlag: testCredentialsGroupId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| }, | ||
| CredentialsGroupId: testCredentialsGroupId, | ||
| CredentialsId: testCredentialsId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *objectstorage.ApiDeleteAccessKeyRequest)) objectstorage.ApiDeleteAccessKeyRequest { | ||
| request := testClient.DeleteAccessKey(testCtx, testProjectId, testCredentialsId) | ||
| request = request.CredentialsGroup(testCredentialsGroupId) | ||
| 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: "credentials group id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, credentialsGroupIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "credentials group id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[credentialsGroupIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "credentials group id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[credentialsGroupIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "credential id invalid 1", | ||
| argValues: []string{""}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| cmd := NewCmd() | ||
| 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(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 objectstorage.ApiDeleteAccessKeyRequest | ||
| }{ | ||
| { | ||
| 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/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/confirm" | ||
| "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/services/object-storage/client" | ||
| objectStorageUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/utils" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" | ||
| ) | ||
| const ( | ||
| credentialsIdArg = "CREDENTIALS_ID" //nolint:gosec // linter false positive | ||
| credentialsGroupIdFlag = "credentials-group-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| CredentialsGroupId string | ||
| CredentialsId string | ||
| } | ||
| func NewCmd() *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: fmt.Sprintf("delete %s", credentialsIdArg), | ||
| Short: "Deletes credentials of an Object Storage credentials group", | ||
| Long: "Deletes credentials of an Object Storage credentials group", | ||
| Args: args.SingleArg(credentialsIdArg, nil), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Delete a credential with ID "xxx" of credentials group with ID "yyy"`, | ||
| "$ stackit object-storage credentials delete xxx --credentials-group-id yyy"), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| credentialsGroupLabel, err := objectStorageUtils.GetCredentialsGroupName(ctx, apiClient, model.ProjectId, model.CredentialsGroupId) | ||
| if err != nil { | ||
| credentialsGroupLabel = model.CredentialsGroupId | ||
| } | ||
| credentialsLabel, err := objectStorageUtils.GetCredentialsName(ctx, apiClient, model.ProjectId, model.CredentialsGroupId, model.CredentialsId) | ||
| if err != nil { | ||
| credentialsLabel = model.CredentialsId | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to delete credentials %q of credentials group %q? (This cannot be undone)", credentialsLabel, credentialsGroupLabel) | ||
| err = confirm.PromptForConfirmation(cmd, prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| _, err = req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("delete Object Storage credentials: %w", err) | ||
| } | ||
| cmd.Printf("Deleted credentials %q of credentials group %q\n", credentialsLabel, credentialsGroupLabel) | ||
| return nil | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Var(flags.UUIDFlag(), credentialsGroupIdFlag, "Credentials Group ID") | ||
| err := flags.MarkFlagsRequired(cmd, credentialsGroupIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| credentialsId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| return &inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| CredentialsGroupId: flags.FlagToStringValue(cmd, credentialsGroupIdFlag), | ||
| CredentialsId: credentialsId, | ||
| }, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiDeleteAccessKeyRequest { | ||
| req := apiClient.DeleteAccessKey(ctx, model.ProjectId, model.CredentialsId) | ||
| req = req.CredentialsGroup(model.CredentialsGroupId) | ||
| return req | ||
| } |
| package list | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "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/objectstorage" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &objectstorage.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testCredentialsGroupId = uuid.NewString() | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| credentialsGroupIdFlag: testCredentialsGroupId, | ||
| limitFlag: "10", | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| }, | ||
| CredentialsGroupId: testCredentialsGroupId, | ||
| Limit: utils.Ptr(int64(10)), | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *objectstorage.ApiListAccessKeysRequest)) objectstorage.ApiListAccessKeysRequest { | ||
| request := testClient.ListAccessKeys(testCtx, testProjectId) | ||
| request = request.CredentialsGroup(testCredentialsGroupId) | ||
| 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: "credentials group id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, credentialsGroupIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "credentials group id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[credentialsGroupIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "credentials group id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[credentialsGroupIdFlag] = "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 := NewCmd() | ||
| 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(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 objectstorage.ApiListAccessKeysRequest | ||
| }{ | ||
| { | ||
| 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" | ||
| objectStorageUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/utils" | ||
| "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/services/object-storage/client" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/tables" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" | ||
| ) | ||
| const ( | ||
| limitFlag = "limit" | ||
| credentialsGroupIdFlag = "credentials-group-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| CredentialsGroupId string | ||
| Limit *int64 | ||
| } | ||
| func NewCmd() *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "list", | ||
| Short: "Lists all credentials for an Object Storage credentials group", | ||
| Long: "Lists all credentials for an Object Storage credentials group.", | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `List all credentials for a credentials group with ID "xxx"`, | ||
| "$ stackit object-storage credentials list --credentials-group-id xxx"), | ||
| examples.NewExample( | ||
| `List all credentials for a credentials group with ID "xxx" in JSON format`, | ||
| "$ stackit object-storage credentials list --credentials-group-id xxx --output-format json"), | ||
| examples.NewExample( | ||
| `List up to 10 credentials for a credentials group with ID "xxx"`, | ||
| "$ stackit object-storage credentials list --credentials-group-id xxx --limit 10"), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("list Object Storage credentials: %w", err) | ||
| } | ||
| credentials := *resp.AccessKeys | ||
| if len(credentials) == 0 { | ||
| credentialsGroupLabel, err := objectStorageUtils.GetCredentialsGroupName(ctx, apiClient, model.ProjectId, model.CredentialsGroupId) | ||
| if err != nil { | ||
| credentialsGroupLabel = model.CredentialsGroupId | ||
| } | ||
| cmd.Printf("No credentials found for credentials group %q\n", credentialsGroupLabel) | ||
| return nil | ||
| } | ||
| // Truncate output | ||
| if model.Limit != nil && len(credentials) > int(*model.Limit) { | ||
| credentials = credentials[:*model.Limit] | ||
| } | ||
| return outputResult(cmd, model.OutputFormat, credentials) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") | ||
| cmd.Flags().Var(flags.UUIDFlag(), credentialsGroupIdFlag, "Credentials Group ID") | ||
| err := flags.MarkFlagsRequired(cmd, credentialsGroupIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(cmd *cobra.Command) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| limit := flags.FlagToInt64Pointer(cmd, limitFlag) | ||
| if limit != nil && *limit < 1 { | ||
| return nil, &errors.FlagValidationError{ | ||
| Flag: limitFlag, | ||
| Details: "must be greater than 0", | ||
| } | ||
| } | ||
| return &inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| CredentialsGroupId: flags.FlagToStringValue(cmd, credentialsGroupIdFlag), | ||
| Limit: limit, | ||
| }, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiListAccessKeysRequest { | ||
| req := apiClient.ListAccessKeys(ctx, model.ProjectId) | ||
| req = req.CredentialsGroup(model.CredentialsGroupId) | ||
| return req | ||
| } | ||
| func outputResult(cmd *cobra.Command, outputFormat string, credentials []objectstorage.AccessKey) error { | ||
| switch outputFormat { | ||
| case globalflags.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(credentials, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal Object Storage credentials list: %w", err) | ||
| } | ||
| cmd.Println(string(details)) | ||
| return nil | ||
| default: | ||
| table := tables.NewTable() | ||
| table.SetHeader("CREDENTIALS ID", "ACCESS KEY ID", "EXPIRES AT") | ||
| for i := range credentials { | ||
| c := credentials[i] | ||
| expiresAt := "Never" | ||
| if c.Expires != nil { | ||
| expiresAt = *c.Expires | ||
| } | ||
| table.AddRow(*c.KeyId, *c.DisplayName, expiresAt) | ||
| } | ||
| err := table.Display(cmd) | ||
| if err != nil { | ||
| return fmt.Errorf("render table: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
| } |
| package create | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "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/secretsmanager" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &secretsmanager.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testInstanceId = uuid.NewString() | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| instanceIdFlag: testInstanceId, | ||
| descriptionFlag: "sample description", | ||
| writeFlag: "false", | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| }, | ||
| InstanceId: testInstanceId, | ||
| Description: utils.Ptr("sample description"), | ||
| Write: utils.Ptr(false), | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *secretsmanager.ApiCreateUserRequest)) secretsmanager.ApiCreateUserRequest { | ||
| request := testClient.CreateUser(testCtx, testProjectId, testInstanceId) | ||
| request = request.CreateUserPayload(secretsmanager.CreateUserPayload{ | ||
| Description: utils.Ptr("sample description"), | ||
| Write: utils.Ptr(false), | ||
| }) | ||
| 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 description specified", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, descriptionFlag) | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.Description = utils.Ptr("") | ||
| }), | ||
| }, | ||
| { | ||
| description: "no write flag given", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, writeFlag) | ||
| }), | ||
| 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: "instance id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, instanceIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[instanceIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "write set to true", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[writeFlag] = "true" | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.Write = utils.Ptr(true) | ||
| }), | ||
| }, | ||
| } | ||
| 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) | ||
| } | ||
| model, err := parseInput(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 secretsmanager.ApiCreateUserRequest | ||
| }{ | ||
| { | ||
| 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 create | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/confirm" | ||
| "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/services/secrets-manager/client" | ||
| secretsManagerUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/secrets-manager/utils" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/secretsmanager" | ||
| ) | ||
| const ( | ||
| instanceIdFlag = "instance-id" | ||
| descriptionFlag = "description" | ||
| writeFlag = "write" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| InstanceId string | ||
| Description *string | ||
| Write *bool | ||
| } | ||
| func NewCmd() *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "create", | ||
| Short: "Creates a Secrets Manager user", | ||
| Long: fmt.Sprintf("%s\n%s\n%s", | ||
| "Creates a Secrets Manager user.", | ||
| "The username and password are auto-generated and provided upon creation. The password cannot be retrieved later.", | ||
| "A description can be provided to identify a user.", | ||
| ), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Create a Secrets Manager user for instance with ID "xxx" and description "yyy"`, | ||
| "$ stackit secrets-manager user create --instance-id xxx --description yyy"), | ||
| examples.NewExample( | ||
| `Create a Secrets Manager user for instance with ID "xxx" with write access to the secrets engine`, | ||
| "$ stackit secrets-manager user create --instance-id xxx --write"), | ||
| ), | ||
| Args: args.NoArgs, | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| instanceLabel, err := secretsManagerUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId) | ||
| if err != nil { | ||
| instanceLabel = model.InstanceId | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to create a user for instance %q?", instanceLabel) | ||
| err = confirm.PromptForConfirmation(cmd, prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("create Secrets Manager user: %w", err) | ||
| } | ||
| cmd.Printf("Created user for instance %q. User ID: %s\n\n", instanceLabel, *resp.Id) | ||
| cmd.Printf("Username: %s\n", *resp.Username) | ||
| cmd.Printf("Password: %s\n", *resp.Password) | ||
| cmd.Printf("Description: %s\n", *resp.Description) | ||
| cmd.Printf("Write Access: %t\n", *resp.Write) | ||
| return nil | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "ID of the instance") | ||
| cmd.Flags().String(descriptionFlag, "", "A user chosen description to differentiate between multiple users") | ||
| cmd.Flags().Bool(writeFlag, false, "User write access to the secrets engine. If unset, user is read-only") | ||
| err := flags.MarkFlagsRequired(cmd, instanceIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(cmd *cobra.Command) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| return &inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| InstanceId: flags.FlagToStringValue(cmd, instanceIdFlag), | ||
| Description: utils.Ptr(flags.FlagToStringValue(cmd, descriptionFlag)), | ||
| Write: utils.Ptr(flags.FlagToBoolValue(cmd, writeFlag)), | ||
| }, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) secretsmanager.ApiCreateUserRequest { | ||
| req := apiClient.CreateUser(ctx, model.ProjectId, model.InstanceId) | ||
| req = req.CreateUserPayload(secretsmanager.CreateUserPayload{ | ||
| Description: model.Description, | ||
| Write: model.Write, | ||
| }) | ||
| return req | ||
| } |
| package delete | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/secretsmanager" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &secretsmanager.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testInstanceId = uuid.NewString() | ||
| var testUserId = uuid.NewString() | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testUserId, | ||
| } | ||
| 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, | ||
| instanceIdFlag: testInstanceId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| }, | ||
| InstanceId: testInstanceId, | ||
| UserId: testUserId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *secretsmanager.ApiDeleteUserRequest)) secretsmanager.ApiDeleteUserRequest { | ||
| request := testClient.DeleteUser(testCtx, testProjectId, testInstanceId, testUserId) | ||
| 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, | ||
| }, | ||
| { | ||
| description: "user id invalid 1", | ||
| argValues: []string{""}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "user id invalid 2", | ||
| argValues: []string{"invalid-uuid"}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| cmd := NewCmd() | ||
| 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(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 secretsmanager.ApiDeleteUserRequest | ||
| }{ | ||
| { | ||
| 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/confirm" | ||
| "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/services/secrets-manager/client" | ||
| secretsManagerUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/secrets-manager/utils" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/secretsmanager" | ||
| ) | ||
| const ( | ||
| userIdArg = "USER_ID" | ||
| instanceIdFlag = "instance-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| InstanceId string | ||
| UserId string | ||
| } | ||
| func NewCmd() *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: fmt.Sprintf("delete %s", userIdArg), | ||
| Short: "Deletes a Secrets Manager user", | ||
| Long: fmt.Sprintf("%s\n%s", | ||
| "Deletes a Secrets Manager user by ID. You can get the IDs of users for an instance by running:", | ||
| " $ stackit secrets-manager user delete --instance-id <INSTANCE_ID>", | ||
| ), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Delete a Secrets Manager user with ID "xxx" for instance with ID "yyy"`, | ||
| "$ stackit secrets-manager user delete xxx --instance-id yyy"), | ||
| ), | ||
| Args: args.SingleArg(userIdArg, utils.ValidateUUID), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| instanceLabel, err := secretsManagerUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId) | ||
| if err != nil { | ||
| instanceLabel = model.InstanceId | ||
| } | ||
| userLabel, err := secretsManagerUtils.GetUserLabel(ctx, apiClient, model.ProjectId, model.InstanceId, model.UserId) | ||
| if err != nil { | ||
| userLabel = fmt.Sprintf("%q", model.UserId) | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to delete user %s of instance %q? (This cannot be undone)", userLabel, instanceLabel) | ||
| err = confirm.PromptForConfirmation(cmd, prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| err = req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("delete Secrets Manager user: %w", err) | ||
| } | ||
| cmd.Printf("Deleted user %s of instance %q\n", userLabel, instanceLabel) | ||
| return nil | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "Instance ID") | ||
| err := flags.MarkFlagsRequired(cmd, instanceIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| userId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| return &inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| InstanceId: flags.FlagToStringValue(cmd, instanceIdFlag), | ||
| UserId: userId, | ||
| }, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) secretsmanager.ApiDeleteUserRequest { | ||
| req := apiClient.DeleteUser(ctx, model.ProjectId, model.InstanceId, model.UserId) | ||
| return req | ||
| } |
| package describe | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/secretsmanager" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &secretsmanager.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testInstanceId = uuid.NewString() | ||
| var testUserId = uuid.NewString() | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testUserId, | ||
| } | ||
| 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, | ||
| instanceIdFlag: testInstanceId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| }, | ||
| InstanceId: testInstanceId, | ||
| UserId: testUserId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *secretsmanager.ApiGetUserRequest)) secretsmanager.ApiGetUserRequest { | ||
| request := testClient.GetUser(testCtx, testProjectId, testInstanceId, testUserId) | ||
| 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: fixtureArgValues(), | ||
| 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 missing", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, instanceIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[instanceIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[instanceIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "user id invalid 1", | ||
| argValues: []string{""}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "user id invalid 2", | ||
| argValues: []string{"invalid-uuid"}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| cmd := NewCmd() | ||
| 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(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 secretsmanager.ApiGetUserRequest | ||
| }{ | ||
| { | ||
| 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" | ||
| "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/services/secrets-manager/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/secretsmanager" | ||
| ) | ||
| const ( | ||
| userIdArg = "USER_ID" | ||
| instanceIdFlag = "instance-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| InstanceId string | ||
| UserId string | ||
| } | ||
| func NewCmd() *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: fmt.Sprintf("describe %s", userIdArg), | ||
| Short: "Shows details of a Secrets Manager user", | ||
| Long: "Shows details of a Secrets Manager user.", | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Get details of a Secrets Manager user with ID "xxx" of instance with ID "yyy"`, | ||
| "$ stackit secrets-manager user describe xxx --instance-id yyy"), | ||
| examples.NewExample( | ||
| `Get details of a Secrets Manager user with ID "xxx" of instance with ID "yyy" in table format`, | ||
| "$ stackit secrets-manager user describe xxx --instance-id yyy --output-format pretty"), | ||
| ), | ||
| Args: args.SingleArg(userIdArg, utils.ValidateUUID), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("get Secrets Manager user: %w", err) | ||
| } | ||
| return outputResult(cmd, model.OutputFormat, *resp) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "ID of the instance") | ||
| err := flags.MarkFlagsRequired(cmd, instanceIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| userId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| return &inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| InstanceId: flags.FlagToStringValue(cmd, instanceIdFlag), | ||
| UserId: userId, | ||
| }, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) secretsmanager.ApiGetUserRequest { | ||
| req := apiClient.GetUser(ctx, model.ProjectId, model.InstanceId, model.UserId) | ||
| return req | ||
| } | ||
| func outputResult(cmd *cobra.Command, outputFormat string, user secretsmanager.User) error { | ||
| switch outputFormat { | ||
| case globalflags.PrettyOutputFormat: | ||
| table := tables.NewTable() | ||
| table.AddRow("ID", *user.Id) | ||
| table.AddSeparator() | ||
| table.AddRow("USERNAME", *user.Username) | ||
| table.AddSeparator() | ||
| table.AddRow("DESCRIPTION", *user.Description) | ||
| if user.Password != nil && *user.Password != "" { | ||
| table.AddSeparator() | ||
| table.AddRow("PASSWORD", *user.Password) | ||
| } | ||
| table.AddSeparator() | ||
| table.AddRow("WRITE ACCESS", *user.Write) | ||
| err := table.Display(cmd) | ||
| if err != nil { | ||
| return fmt.Errorf("render table: %w", err) | ||
| } | ||
| return nil | ||
| default: | ||
| details, err := json.MarshalIndent(user, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal Secrets Manager user: %w", err) | ||
| } | ||
| cmd.Println(string(details)) | ||
| return nil | ||
| } | ||
| } |
| package list | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "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/secretsmanager" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &secretsmanager.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testInstanceId = uuid.NewString() | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| instanceIdFlag: testInstanceId, | ||
| limitFlag: "10", | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| }, | ||
| InstanceId: utils.Ptr(testInstanceId), | ||
| Limit: utils.Ptr(int64(10)), | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *secretsmanager.ApiListUsersRequest)) secretsmanager.ApiListUsersRequest { | ||
| request := testClient.ListUsers(testCtx, testProjectId, testInstanceId) | ||
| 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: "instance id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, instanceIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[instanceIdFlag] = "" | ||
| }), | ||
| 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) | ||
| } | ||
| model, err := parseInput(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 secretsmanager.ApiListUsersRequest | ||
| }{ | ||
| { | ||
| 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/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/services/secrets-manager/client" | ||
| secretsManagerUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/secrets-manager/utils" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/tables" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/secretsmanager" | ||
| ) | ||
| const ( | ||
| instanceIdFlag = "instance-id" | ||
| limitFlag = "limit" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| InstanceId *string | ||
| Limit *int64 | ||
| } | ||
| func NewCmd() *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "list", | ||
| Short: "Lists all Secrets Manager users", | ||
| Long: "Lists all Secrets Manager users.", | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `List all Secrets Manager users of instance with ID "xxx`, | ||
| "$ stackit secrets-manager user list --instance-id xxx"), | ||
| examples.NewExample( | ||
| `List all Secrets Manager users in JSON format with ID "xxx`, | ||
| "$ stackit secrets-manager user list --instance-id xxx --output-format json"), | ||
| examples.NewExample( | ||
| `List up to 10 Secrets Manager users with ID "xxx"`, | ||
| "$ stackit secrets-manager user list --instance-id xxx --limit 10"), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("get Secrets Manager users: %w", err) | ||
| } | ||
| if resp.Users == nil || len(*resp.Users) == 0 { | ||
| instanceLabel, err := secretsManagerUtils.GetInstanceName(ctx, apiClient, model.ProjectId, *model.InstanceId) | ||
| if err != nil { | ||
| instanceLabel = *model.InstanceId | ||
| } | ||
| cmd.Printf("No users found for instance %q\n", instanceLabel) | ||
| return nil | ||
| } | ||
| users := *resp.Users | ||
| // Truncate output | ||
| if model.Limit != nil && len(users) > int(*model.Limit) { | ||
| users = users[:*model.Limit] | ||
| } | ||
| return outputResult(cmd, model.OutputFormat, users) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "Instance ID") | ||
| cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") | ||
| err := flags.MarkFlagsRequired(cmd, instanceIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(cmd *cobra.Command) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| limit := flags.FlagToInt64Pointer(cmd, limitFlag) | ||
| if limit != nil && *limit < 1 { | ||
| return nil, &errors.FlagValidationError{ | ||
| Flag: limitFlag, | ||
| Details: "must be greater than 0", | ||
| } | ||
| } | ||
| return &inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| InstanceId: flags.FlagToStringPointer(cmd, instanceIdFlag), | ||
| Limit: flags.FlagToInt64Pointer(cmd, limitFlag), | ||
| }, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) secretsmanager.ApiListUsersRequest { | ||
| req := apiClient.ListUsers(ctx, model.ProjectId, *model.InstanceId) | ||
| return req | ||
| } | ||
| func outputResult(cmd *cobra.Command, outputFormat string, users []secretsmanager.User) error { | ||
| switch outputFormat { | ||
| case globalflags.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(users, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal Secrets Manager user list: %w", err) | ||
| } | ||
| cmd.Println(string(details)) | ||
| return nil | ||
| default: | ||
| table := tables.NewTable() | ||
| table.SetHeader("ID", "USERNAME", "DESCRIPTION", "WRITE ACCESS") | ||
| for i := range users { | ||
| user := users[i] | ||
| table.AddRow(*user.Id, *user.Username, *user.Description, *user.Write) | ||
| } | ||
| err := table.Display(cmd) | ||
| if err != nil { | ||
| return fmt.Errorf("render table: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
| } |
| package update | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "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/secretsmanager" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &secretsmanager.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testInstanceId = uuid.NewString() | ||
| var testUserId = uuid.NewString() | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testUserId, | ||
| } | ||
| 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, | ||
| instanceIdFlag: testInstanceId, | ||
| enableWriteFlag: "true", | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| }, | ||
| InstanceId: testInstanceId, | ||
| UserId: testUserId, | ||
| EnableWrite: utils.Ptr(true), | ||
| DisableWrite: nil, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *secretsmanager.ApiUpdateUserRequest)) secretsmanager.ApiUpdateUserRequest { | ||
| request := testClient.UpdateUser(testCtx, testProjectId, testInstanceId, testUserId) | ||
| request = request.UpdateUserPayload(secretsmanager.UpdateUserPayload{ | ||
| Write: utils.Ptr(true), | ||
| }) | ||
| 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: fixtureArgValues(), | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no arg values", | ||
| argValues: []string{}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "disable write access", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[disableWriteFlag] = "true" | ||
| delete(flagValues, enableWriteFlag) | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.EnableWrite = nil | ||
| model.DisableWrite = utils.Ptr(true) | ||
| }), | ||
| }, | ||
| { | ||
| description: "neither write flag provided", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, enableWriteFlag) | ||
| delete(flagValues, disableWriteFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "both flags provided", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[disableWriteFlag] = "true" | ||
| }), | ||
| 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 missing", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, instanceIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[instanceIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "user id invalid 1", | ||
| argValues: []string{""}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "user id invalid 2", | ||
| argValues: []string{"invalid-uuid"}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| cmd := NewCmd() | ||
| 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) | ||
| } | ||
| err = cmd.ValidateFlagGroups() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flag groups: %v", err) | ||
| } | ||
| model, err := parseInput(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 secretsmanager.ApiUpdateUserRequest | ||
| isValid bool | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| isValid: true, | ||
| }, | ||
| { | ||
| description: "disable write", | ||
| model: fixtureInputModel(func(model *inputModel) { | ||
| model.EnableWrite = nil | ||
| model.DisableWrite = utils.Ptr(true) | ||
| }), | ||
| expectedRequest: fixtureRequest().UpdateUserPayload(secretsmanager.UpdateUserPayload{ | ||
| Write: utils.Ptr(false), | ||
| }), | ||
| isValid: true, | ||
| }, | ||
| { | ||
| description: "both write flags set", | ||
| model: fixtureInputModel(func(model *inputModel) { | ||
| model.EnableWrite = utils.Ptr(true) | ||
| model.DisableWrite = utils.Ptr(true) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "none of the write flags set", | ||
| model: fixtureInputModel(func(model *inputModel) { | ||
| model.EnableWrite = nil | ||
| model.DisableWrite = nil | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request, err := buildRequest(testCtx, tt.model, testClient) | ||
| 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 update | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/confirm" | ||
| "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/services/secrets-manager/client" | ||
| secretsManagerUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/secrets-manager/utils" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/secretsmanager" | ||
| ) | ||
| const ( | ||
| userIdArg = "USER_ID" | ||
| instanceIdFlag = "instance-id" | ||
| enableWriteFlag = "enable-write" | ||
| disableWriteFlag = "disable-write" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| InstanceId string | ||
| UserId string | ||
| EnableWrite *bool | ||
| DisableWrite *bool | ||
| } | ||
| func NewCmd() *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: fmt.Sprintf("update %s", userIdArg), | ||
| Short: "Updates the write privileges Secrets Manager user", | ||
| Long: "Updates the write privileges Secrets Manager user.", | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Enable write access of a Secrets Manager user with ID "xxx" of instance with ID "yyy"`, | ||
| "$ stackit secrets-manager user update xxx --instance-id yyy --enable-write"), | ||
| examples.NewExample( | ||
| `Disable write access of a Secrets Manager user with ID "xxx" of instance with ID "yyy"`, | ||
| "$ stackit secrets-manager user update xxx --instance-id yyy --disable-write"), | ||
| ), | ||
| Args: args.SingleArg(userIdArg, utils.ValidateUUID), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| instanceLabel, err := secretsManagerUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId) | ||
| if err != nil { | ||
| instanceLabel = model.InstanceId | ||
| } | ||
| userLabel, err := secretsManagerUtils.GetUserLabel(ctx, apiClient, model.ProjectId, model.InstanceId, model.UserId) | ||
| if err != nil { | ||
| userLabel = fmt.Sprintf("%q", model.UserId) | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to update user %s of instance %q?", userLabel, instanceLabel) | ||
| err = confirm.PromptForConfirmation(cmd, prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req, err := buildRequest(ctx, model, apiClient) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| err = req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("update Secrets Manager user: %w", err) | ||
| } | ||
| cmd.Printf("Updated user %s of instance %q\n", userLabel, instanceLabel) | ||
| return nil | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "ID of the instance") | ||
| cmd.Flags().Bool(enableWriteFlag, false, "Set the user to have write access.") | ||
| cmd.Flags().Bool(disableWriteFlag, false, "Set the user to have read-only access.") | ||
| err := flags.MarkFlagsRequired(cmd, instanceIdFlag) | ||
| cmd.MarkFlagsMutuallyExclusive(enableWriteFlag, disableWriteFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| userId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| enableWrite := flags.FlagToBoolPointer(cmd, enableWriteFlag) | ||
| disableWrite := flags.FlagToBoolPointer(cmd, disableWriteFlag) | ||
| if enableWrite == nil && disableWrite == nil { | ||
| return nil, &errors.EmptyUpdateError{} | ||
| } | ||
| return &inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| InstanceId: flags.FlagToStringValue(cmd, instanceIdFlag), | ||
| EnableWrite: enableWrite, | ||
| DisableWrite: disableWrite, | ||
| UserId: userId, | ||
| }, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) (secretsmanager.ApiUpdateUserRequest, error) { | ||
| req := apiClient.UpdateUser(ctx, model.ProjectId, model.InstanceId, model.UserId) | ||
| var write bool | ||
| if model.EnableWrite != nil && model.DisableWrite == nil { | ||
| write = true | ||
| } else if model.DisableWrite != nil && model.EnableWrite == nil { | ||
| write = false | ||
| } else if model.DisableWrite == nil && model.EnableWrite == nil { | ||
| // Should never happen | ||
| return req, fmt.Errorf("one of %s and %s flags needs to be set", enableWriteFlag, disableWriteFlag) | ||
| } else if model.DisableWrite != nil && model.EnableWrite != nil { | ||
| // Should never happen | ||
| return req, fmt.Errorf("%s and %s flags can't be both set", enableWriteFlag, disableWriteFlag) | ||
| } | ||
| req = req.UpdateUserPayload(secretsmanager.UpdateUserPayload{ | ||
| Write: utils.Ptr(write), | ||
| }) | ||
| return req, nil | ||
| } |
| package user | ||
| import ( | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/secrets-manager/user/create" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/secrets-manager/user/delete" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/secrets-manager/user/describe" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/secrets-manager/user/list" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/secrets-manager/user/update" | ||
| ) | ||
| func NewCmd() *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "user", | ||
| Short: "Provides functionality for Secrets Manager users", | ||
| Long: "Provides functionality for Secrets Manager users.", | ||
| Args: args.NoArgs, | ||
| Run: utils.CmdHelp, | ||
| } | ||
| addSubcommands(cmd) | ||
| return cmd | ||
| } | ||
| func addSubcommands(cmd *cobra.Command) { | ||
| cmd.AddCommand(list.NewCmd()) | ||
| cmd.AddCommand(create.NewCmd()) | ||
| cmd.AddCommand(delete.NewCmd()) | ||
| cmd.AddCommand(describe.NewCmd()) | ||
| cmd.AddCommand(update.NewCmd()) | ||
| } |
| 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/spf13/cobra" | ||
| "github.com/spf13/viper" | ||
| sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/argus" | ||
| ) | ||
| func ConfigureClient(cmd *cobra.Command) (*argus.APIClient, error) { | ||
| var err error | ||
| var apiClient *argus.APIClient | ||
| var cfgOptions []sdkConfig.ConfigurationOption | ||
| authCfgOption, err := auth.AuthenticationConfig(cmd, auth.AuthorizeUser) | ||
| if err != nil { | ||
| return nil, &errors.AuthError{} | ||
| } | ||
| cfgOptions = append(cfgOptions, authCfgOption, sdkConfig.WithRegion("eu01")) | ||
| customEndpoint := viper.GetString(config.ArgusCustomEndpointKey) | ||
| if customEndpoint != "" { | ||
| cfgOptions = append(cfgOptions, sdkConfig.WithEndpoint(customEndpoint)) | ||
| } | ||
| apiClient, err = argus.NewAPIClient(cfgOptions...) | ||
| if err != nil { | ||
| return nil, &errors.AuthError{} | ||
| } | ||
| return apiClient, nil | ||
| } |
@@ -72,2 +72,3 @@ # STACKIT CLI release workflow. | ||
| - name: Publish packages to APT repo | ||
| # Temporarily not skipping prereleases to test integration with APT | ||
| # if: contains(github.ref_name, '-') == false | ||
@@ -74,0 +75,0 @@ env: |
+2
-1
@@ -91,3 +91,4 @@ before: | ||
| # if the tag has a prerelease indicator (e.g. v0.0.1-alpha1) | ||
| skip_upload: auto | ||
| # Temporarily not skipping prereleases to test integration with Homebrew | ||
| # skip_upload: auto | ||
@@ -94,0 +95,0 @@ snapcrafts: |
@@ -32,2 +32,3 @@ ## stackit config set | ||
| ``` | ||
| --argus-custom-endpoint string Argus API base URL, used in calls to this API | ||
| --authorization-custom-endpoint string Authorization API base URL, used in calls to this API | ||
@@ -34,0 +35,0 @@ --dns-custom-endpoint string DNS API base URL, used in calls to this API |
@@ -29,2 +29,3 @@ ## stackit config unset | ||
| ``` | ||
| --argus-custom-endpoint Argus API base URL. If unset, uses the default base URL | ||
| --async Configuration option to run commands asynchronously | ||
@@ -31,0 +32,0 @@ --authorization-custom-endpoint Authorization API base URL. If unset, uses the default base URL |
| ## stackit object-storage credentials-group delete | ||
| Deletes a credentials group | ||
| Deletes a credentials group that holds Object Storage access credentials | ||
| ### Synopsis | ||
| Deletes a credentials group. Only possible if there are no valid credentials (access-keys) left in the group, otherwise it will throw an error. | ||
| Deletes a credentials group that holds Object Storage access credentials. Only possible if there are no valid credentials (access-keys) left in the group, otherwise it will throw an error. | ||
@@ -9,0 +9,0 @@ ``` |
| ## stackit object-storage credentials-group list | ||
| Lists all credentials groups | ||
| Lists all credentials groups that hold Object Storage access credentials | ||
| ### Synopsis | ||
| Lists all credentials groups. | ||
| Lists all credentials groups that hold Object Storage access credentials. | ||
@@ -9,0 +9,0 @@ ``` |
@@ -32,4 +32,4 @@ ## stackit object-storage credentials-group | ||
| * [stackit object-storage credentials-group create](./stackit_object-storage_credentials-group_create.md) - Creates a credentials group to hold Object Storage access credentials | ||
| * [stackit object-storage credentials-group delete](./stackit_object-storage_credentials-group_delete.md) - Deletes a credentials group | ||
| * [stackit object-storage credentials-group list](./stackit_object-storage_credentials-group_list.md) - Lists all credentials groups | ||
| * [stackit object-storage credentials-group delete](./stackit_object-storage_credentials-group_delete.md) - Deletes a credentials group that holds Object Storage access credentials | ||
| * [stackit object-storage credentials-group list](./stackit_object-storage_credentials-group_list.md) - Lists all credentials groups that hold Object Storage access credentials | ||
@@ -32,2 +32,3 @@ ## stackit object-storage | ||
| * [stackit object-storage bucket](./stackit_object-storage_bucket.md) - Provides functionality for Object Storage buckets | ||
| * [stackit object-storage credentials](./stackit_object-storage_credentials.md) - Provides functionality for Object Storage credentials | ||
| * [stackit object-storage credentials-group](./stackit_object-storage_credentials-group.md) - Provides functionality for Object Storage credentials group | ||
@@ -34,0 +35,0 @@ * [stackit object-storage disable](./stackit_object-storage_disable.md) - Disables Object Storage for a project |
@@ -32,2 +32,3 @@ ## stackit secrets-manager | ||
| * [stackit secrets-manager instance](./stackit_secrets-manager_instance.md) - Provides functionality for Secrets Manager instances | ||
| * [stackit secrets-manager user](./stackit_secrets-manager_user.md) - Provides functionality for Secrets Manager users | ||
+1
-0
@@ -28,2 +28,3 @@ ## stackit | ||
| * [stackit argus](./stackit_argus.md) - Provides functionality for Argus | ||
| * [stackit auth](./stackit_auth.md) - Provides authentication functionality | ||
@@ -30,0 +31,0 @@ * [stackit config](./stackit_config.md) - Provides functionality for CLI configuration options |
+3
-2
@@ -6,3 +6,3 @@ module github.com/stackitcloud/stackit-cli | ||
| require ( | ||
| github.com/golang-jwt/jwt/v5 v5.2.0 | ||
| github.com/golang-jwt/jwt/v5 v5.2.1 | ||
| github.com/google/go-cmp v0.6.0 | ||
@@ -26,3 +26,3 @@ github.com/google/uuid v1.6.0 | ||
| golang.org/x/mod v0.15.0 | ||
| golang.org/x/oauth2 v0.17.0 | ||
| golang.org/x/oauth2 v0.18.0 | ||
| golang.org/x/text v0.14.0 | ||
@@ -53,2 +53,3 @@ ) | ||
| github.com/spf13/cast v1.6.0 // indirect | ||
| github.com/stackitcloud/stackit-sdk-go/services/argus v0.9.5 | ||
| github.com/stackitcloud/stackit-sdk-go/services/logme v0.10.1 | ||
@@ -55,0 +56,0 @@ github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.10.1 |
+6
-4
@@ -17,4 +17,4 @@ github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= | ||
| github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= | ||
| github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= | ||
| github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= | ||
| github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= | ||
| github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= | ||
| github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= | ||
@@ -79,2 +79,4 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= | ||
| github.com/stackitcloud/stackit-sdk-go/core v0.10.0/go.mod h1:B5dkVm2HlBRG7liBVIFNqncDb6TUHnJ7t0GsKhAFuRk= | ||
| github.com/stackitcloud/stackit-sdk-go/services/argus v0.9.5 h1:RLsA2jO9sMNpn7NYdtFyumY5Vj4n4WtBm2J2NBKlsnw= | ||
| github.com/stackitcloud/stackit-sdk-go/services/argus v0.9.5/go.mod h1:lzGbqwV0hqeX/kUvaaFTgjOJRxUlsZ911TX1YAcKwqc= | ||
| github.com/stackitcloud/stackit-sdk-go/services/authorization v0.1.1 h1:h7dCaBlbU34WSGuEXREmCdCzQafZgdXDZuairAzeuo8= | ||
@@ -133,4 +135,4 @@ github.com/stackitcloud/stackit-sdk-go/services/authorization v0.1.1/go.mod h1:V+wTIfuJRV8PiSOfMX6GCTaHWltGaLCz8ImOKeHIaIA= | ||
| golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | ||
| golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= | ||
| golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= | ||
| golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= | ||
| golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= | ||
| golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
@@ -137,0 +139,0 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
+29
-2
@@ -23,7 +23,34 @@ # Installation | ||
| We are working on distributing the CLI using a package manager for Linux. For the moment, you can either install via [Homebrew](https://brew.sh/) or refer to one of the installation methods below. | ||
| #### Debian/Ubuntu (`APT`) | ||
| The STACKIT CLI can be installed through the [`APT`](https://ubuntu.com/server/docs/package-management) package manager. | ||
| 1. Import the STACKIT public key: | ||
| ```shell | ||
| curl https://object.storage.eu01.onstackit.cloud/stackit-public-key/key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/stackit.gpg | ||
| ``` | ||
| 2. Add the STACKIT CLI package repository as a package source: | ||
| ```shell | ||
| echo "deb [signed-by=/usr/share/keyrings/stackit.gpg] https://object.storage.eu01.onstackit.cloud/stackit-cli-apt stackit main" | sudo tee -a /etc/apt/sources.list.d/stackit.list | ||
| ``` | ||
| 3. Update repository information and install the `stackit` package: | ||
| ```shell | ||
| sudo apt-get update | ||
| sudo apt-get install stackit | ||
| ``` | ||
| #### Any distribution | ||
| Alternatively, you can install via [Homebrew](https://brew.sh/) or refer to one of the installation methods below. | ||
| > We are currently working on distributing the CLI on more package managers for Linux. | ||
| ### Windows | ||
| We are working on distributing the CLI using a package manager for Windows. For the moment, please refer to one of the installation methods below. | ||
| > We are currently working on distributing the CLI on a package manager for Windows. For the moment, please refer to one of the installation methods below. | ||
@@ -30,0 +57,0 @@ ## Manual installation |
@@ -21,2 +21,3 @@ package set | ||
| argusCustomEndpointFlag = "argus-custom-endpoint" | ||
| authorizationCustomEndpointFlag = "authorization-custom-endpoint" | ||
@@ -96,2 +97,3 @@ dnsCustomEndpointFlag = "dns-custom-endpoint" | ||
| cmd.Flags().String(argusCustomEndpointFlag, "", "Argus API base URL, used in calls to this API") | ||
| cmd.Flags().String(authorizationCustomEndpointFlag, "", "Authorization API base URL, used in calls to this API") | ||
@@ -112,4 +114,6 @@ cmd.Flags().String(dnsCustomEndpointFlag, "", "DNS API base URL, used in calls to this API") | ||
| err := viper.BindPFlag(config.AuthorizationCustomEndpointKey, cmd.Flags().Lookup(authorizationCustomEndpointFlag)) | ||
| err := viper.BindPFlag(config.ArgusCustomEndpointKey, cmd.Flags().Lookup(argusCustomEndpointFlag)) | ||
| cobra.CheckErr(err) | ||
| err = viper.BindPFlag(config.AuthorizationCustomEndpointKey, cmd.Flags().Lookup(authorizationCustomEndpointFlag)) | ||
| cobra.CheckErr(err) | ||
| err = viper.BindPFlag(config.DNSCustomEndpointKey, cmd.Flags().Lookup(dnsCustomEndpointFlag)) | ||
@@ -116,0 +120,0 @@ cobra.CheckErr(err) |
@@ -15,2 +15,3 @@ package unset | ||
| argusCustomEndpointFlag: true, | ||
| authorizationCustomEndpointFlag: true, | ||
@@ -40,2 +41,3 @@ dnsCustomEndpointFlag: true, | ||
| ArgusCustomEndpoint: true, | ||
| AuthorizationCustomEndpoint: true, | ||
@@ -81,2 +83,3 @@ DNSCustomEndpoint: true, | ||
| model.ArgusCustomEndpoint = false | ||
| model.AuthorizationCustomEndpoint = false | ||
@@ -117,2 +120,12 @@ model.DNSCustomEndpoint = false | ||
| { | ||
| description: "argus custom endpoint empty", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]bool) { | ||
| flagValues[argusCustomEndpointFlag] = false | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.ArgusCustomEndpoint = false | ||
| }), | ||
| }, | ||
| { | ||
| description: "dns custom endpoint empty", | ||
@@ -119,0 +132,0 @@ flagValues: fixtureFlagValues(func(flagValues map[string]bool) { |
@@ -23,2 +23,3 @@ package unset | ||
| argusCustomEndpointFlag = "argus-custom-endpoint" | ||
| authorizationCustomEndpointFlag = "authorization-custom-endpoint" | ||
@@ -47,2 +48,3 @@ dnsCustomEndpointFlag = "dns-custom-endpoint" | ||
| ArgusCustomEndpoint bool | ||
| AuthorizationCustomEndpoint bool | ||
@@ -98,2 +100,5 @@ DNSCustomEndpoint bool | ||
| if model.ArgusCustomEndpoint { | ||
| viper.Set(config.ArgusCustomEndpointKey, "") | ||
| } | ||
| if model.AuthorizationCustomEndpoint { | ||
@@ -160,2 +165,3 @@ viper.Set(config.AuthorizationCustomEndpointKey, "") | ||
| cmd.Flags().Bool(argusCustomEndpointFlag, false, "Argus API base URL. If unset, uses the default base URL") | ||
| cmd.Flags().Bool(authorizationCustomEndpointFlag, false, "Authorization API base URL. If unset, uses the default base URL") | ||
@@ -184,2 +190,3 @@ cmd.Flags().Bool(dnsCustomEndpointFlag, false, "DNS API base URL. If unset, uses the default base URL") | ||
| SessionTimeLimit: flags.FlagToBoolValue(cmd, sessionTimeLimitFlag), | ||
| ArgusCustomEndpoint: flags.FlagToBoolValue(cmd, argusCustomEndpointFlag), | ||
| AuthorizationCustomEndpoint: flags.FlagToBoolValue(cmd, authorizationCustomEndpointFlag), | ||
@@ -186,0 +193,0 @@ DNSCustomEndpoint: flags.FlagToBoolValue(cmd, dnsCustomEndpointFlag), |
@@ -23,8 +23,8 @@ package create | ||
| var testProjectId = uuid.NewString() | ||
| var testDisplayName = "test-name" | ||
| var testCredentialsGroupName = "test-name" | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| nameFlag: testDisplayName, | ||
| projectIdFlag: testProjectId, | ||
| credentialsGroupNameFlag: testCredentialsGroupName, | ||
| } | ||
@@ -42,3 +42,3 @@ for _, mod := range mods { | ||
| }, | ||
| DisplayName: testDisplayName, | ||
| CredentialsGroupName: testCredentialsGroupName, | ||
| } | ||
@@ -53,3 +53,3 @@ for _, mod := range mods { | ||
| payload := objectstorage.CreateCredentialsGroupPayload{ | ||
| DisplayName: utils.Ptr(testDisplayName), | ||
| DisplayName: utils.Ptr(testCredentialsGroupName), | ||
| } | ||
@@ -113,3 +113,3 @@ for _, mod := range mods { | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, nameFlag) | ||
| delete(flagValues, credentialsGroupNameFlag) | ||
| }), | ||
@@ -116,0 +116,0 @@ isValid: false, |
@@ -21,3 +21,3 @@ package create | ||
| const ( | ||
| nameFlag = "name" | ||
| credentialsGroupNameFlag = "name" | ||
| ) | ||
@@ -27,3 +27,3 @@ | ||
| *globalflags.GlobalFlagModel | ||
| DisplayName string | ||
| CredentialsGroupName string | ||
| } | ||
@@ -56,3 +56,3 @@ | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to create a credentials group with name %q?", model.DisplayName) | ||
| prompt := fmt.Sprintf("Are you sure you want to create a credentials group with name %q?", model.CredentialsGroupName) | ||
| err = confirm.PromptForConfirmation(cmd, prompt) | ||
@@ -81,5 +81,5 @@ if err != nil { | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().String(nameFlag, "", "Name of the group holding credentials") | ||
| cmd.Flags().String(credentialsGroupNameFlag, "", "Name of the group holding credentials") | ||
| err := flags.MarkFlagsRequired(cmd, nameFlag) | ||
| err := flags.MarkFlagsRequired(cmd, credentialsGroupNameFlag) | ||
| cobra.CheckErr(err) | ||
@@ -95,4 +95,4 @@ } | ||
| return &inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| DisplayName: flags.FlagToStringValue(cmd, nameFlag), | ||
| GlobalFlagModel: globalFlags, | ||
| CredentialsGroupName: flags.FlagToStringValue(cmd, credentialsGroupNameFlag), | ||
| }, nil | ||
@@ -104,5 +104,5 @@ } | ||
| req = req.CreateCredentialsGroupPayload(objectstorage.CreateCredentialsGroupPayload{ | ||
| DisplayName: utils.Ptr(model.DisplayName), | ||
| DisplayName: utils.Ptr(model.CredentialsGroupName), | ||
| }) | ||
| return req | ||
| } |
@@ -32,4 +32,4 @@ package delete | ||
| Use: fmt.Sprintf("delete %s", credentialsGroupIdArg), | ||
| Short: "Deletes a credentials group", | ||
| Long: "Deletes a credentials group. Only possible if there are no valid credentials (access-keys) left in the group, otherwise it will throw an error.", | ||
| Short: "Deletes a credentials group that holds Object Storage access credentials", | ||
| Long: "Deletes a credentials group that holds Object Storage access credentials. Only possible if there are no valid credentials (access-keys) left in the group, otherwise it will throw an error.", | ||
| Args: args.SingleArg(credentialsGroupIdArg, utils.ValidateUUID), | ||
@@ -36,0 +36,0 @@ Example: examples.Build( |
@@ -32,4 +32,4 @@ package list | ||
| Use: "list", | ||
| Short: "Lists all credentials groups", | ||
| Long: "Lists all credentials groups.", | ||
| Short: "Lists all credentials groups that hold Object Storage access credentials", | ||
| Long: "Lists all credentials groups that hold Object Storage access credentials.", | ||
| Args: args.NoArgs, | ||
@@ -36,0 +36,0 @@ Example: examples.Build( |
@@ -5,2 +5,3 @@ package objectstorage | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/bucket" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/credentials" | ||
| credentialsGroup "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/credentials-group" | ||
@@ -32,2 +33,3 @@ "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/disable" | ||
| cmd.AddCommand(credentialsGroup.NewCmd()) | ||
| cmd.AddCommand(credentials.NewCmd()) | ||
| } |
@@ -9,2 +9,3 @@ package cmd | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/argus" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/auth" | ||
@@ -88,2 +89,3 @@ "github.com/stackitcloud/stackit-cli/internal/cmd/config" | ||
| func addSubcommands(cmd *cobra.Command) { | ||
| cmd.AddCommand(argus.NewCmd()) | ||
| cmd.AddCommand(auth.NewCmd()) | ||
@@ -90,0 +92,0 @@ cmd.AddCommand(config.NewCmd()) |
@@ -5,2 +5,3 @@ package secretsmanager | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/secrets-manager/instance" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/secrets-manager/user" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
@@ -26,2 +27,3 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| cmd.AddCommand(instance.NewCmd()) | ||
| cmd.AddCommand(user.NewCmd()) | ||
| } |
@@ -19,2 +19,3 @@ package config | ||
| ArgusCustomEndpointKey = "argus_custom_endpoint" | ||
| AuthorizationCustomEndpointKey = "authorization_custom_endpoint" | ||
@@ -60,2 +61,3 @@ DNSCustomEndpointKey = "dns_custom_endpoint" | ||
| ResourceManagerEndpointKey, | ||
| ArgusCustomEndpointKey, | ||
| AuthorizationCustomEndpointKey, | ||
@@ -133,2 +135,3 @@ MongoDBFlexCustomEndpointKey, | ||
| viper.SetDefault(DNSCustomEndpointKey, "") | ||
| viper.SetDefault(ArgusCustomEndpointKey, "") | ||
| viper.SetDefault(AuthorizationCustomEndpointKey, "") | ||
@@ -135,0 +138,0 @@ viper.SetDefault(MongoDBFlexCustomEndpointKey, "") |
@@ -5,8 +5,12 @@ package utils | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "net/http" | ||
| "net/http/httptest" | ||
| "testing" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/core/config" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" | ||
@@ -18,2 +22,3 @@ ) | ||
| testCredentialsGroupId = uuid.NewString() | ||
| testCredentialsId = "credentialsID" //nolint:gosec // linter false positive | ||
| ) | ||
@@ -23,2 +28,3 @@ | ||
| testCredentialsGroupName = "testGroup" | ||
| testCredentialsName = "testCredential" | ||
| ) | ||
@@ -29,2 +35,3 @@ | ||
| listCredentialsGroupsResp *objectstorage.ListCredentialsGroupsResponse | ||
| listAccessKeysReq objectstorage.ApiListAccessKeysRequest | ||
| } | ||
@@ -38,2 +45,7 @@ | ||
| } | ||
| func (m *objectStorageClientMocked) ListAccessKeys(_ context.Context, _ string) objectstorage.ApiListAccessKeysRequest { | ||
| return m.listAccessKeysReq | ||
| } | ||
| func TestGetCredentialsGroupName(t *testing.T) { | ||
@@ -150,1 +162,140 @@ tests := []struct { | ||
| } | ||
| func TestGetCredentialsName(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| listAccessKeysResp *objectstorage.ListAccessKeysResponse | ||
| expectedOutput string | ||
| getCredentialsNameFails bool | ||
| isValid bool | ||
| }{ | ||
| { | ||
| description: "base", | ||
| listAccessKeysResp: &objectstorage.ListAccessKeysResponse{ | ||
| AccessKeys: &[]objectstorage.AccessKey{ | ||
| { | ||
| KeyId: utils.Ptr(testCredentialsId), | ||
| DisplayName: utils.Ptr(testCredentialsName), | ||
| }, | ||
| }, | ||
| }, | ||
| isValid: true, | ||
| expectedOutput: testCredentialsName, | ||
| }, | ||
| { | ||
| description: "get credentials name fails", | ||
| getCredentialsNameFails: true, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "multiple credentials", | ||
| listAccessKeysResp: &objectstorage.ListAccessKeysResponse{ | ||
| AccessKeys: &[]objectstorage.AccessKey{ | ||
| { | ||
| KeyId: utils.Ptr("test-UUID"), | ||
| DisplayName: utils.Ptr("test-name"), | ||
| }, | ||
| { | ||
| KeyId: utils.Ptr(testCredentialsId), | ||
| DisplayName: utils.Ptr(testCredentialsName), | ||
| }, | ||
| }, | ||
| }, | ||
| isValid: true, | ||
| expectedOutput: testCredentialsName, | ||
| }, | ||
| { | ||
| description: "nil credentials", | ||
| listAccessKeysResp: &objectstorage.ListAccessKeysResponse{ | ||
| AccessKeys: nil, | ||
| }, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "nil credentials id", | ||
| listAccessKeysResp: &objectstorage.ListAccessKeysResponse{ | ||
| AccessKeys: &[]objectstorage.AccessKey{ | ||
| { | ||
| KeyId: nil, | ||
| }, | ||
| }, | ||
| }, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "nil credentials name", | ||
| listAccessKeysResp: &objectstorage.ListAccessKeysResponse{ | ||
| AccessKeys: &[]objectstorage.AccessKey{ | ||
| { | ||
| KeyId: utils.Ptr(testCredentialsId), | ||
| DisplayName: nil, | ||
| }, | ||
| }, | ||
| }, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "empty credentials name", | ||
| listAccessKeysResp: &objectstorage.ListAccessKeysResponse{ | ||
| AccessKeys: &[]objectstorage.AccessKey{ | ||
| { | ||
| KeyId: utils.Ptr(testCredentialsId), | ||
| DisplayName: utils.Ptr(""), | ||
| }, | ||
| }, | ||
| }, | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| mockedRespBytes, err := json.Marshal(tt.listAccessKeysResp) | ||
| if err != nil { | ||
| t.Fatalf("Failed to marshal mocked response: %v", err) | ||
| } | ||
| handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| w.Header().Set("Content-Type", "application/json") | ||
| if tt.getCredentialsNameFails { | ||
| w.WriteHeader(http.StatusBadGateway) | ||
| w.Header().Set("Content-Type", "application/json") | ||
| _, err := w.Write([]byte("{\"message\": \"Something bad happened\"")) | ||
| if err != nil { | ||
| t.Errorf("Failed to write bad response: %v", err) | ||
| } | ||
| return | ||
| } | ||
| _, err := w.Write(mockedRespBytes) | ||
| if err != nil { | ||
| t.Errorf("Failed to write response: %v", err) | ||
| } | ||
| }) | ||
| mockedServer := httptest.NewServer(handler) | ||
| defer mockedServer.Close() | ||
| client, err := objectstorage.NewAPIClient( | ||
| config.WithEndpoint(mockedServer.URL), | ||
| config.WithoutAuthentication(), | ||
| ) | ||
| if err != nil { | ||
| t.Fatalf("Failed to initialize client: %v", err) | ||
| } | ||
| output, err := GetCredentialsName(context.Background(), client, testProjectId, testCredentialsGroupId, testCredentialsId) | ||
| 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) | ||
| } | ||
| }) | ||
| } | ||
| } |
@@ -12,2 +12,3 @@ package utils | ||
| ListCredentialsGroupsExecute(ctx context.Context, projectId string) (*objectstorage.ListCredentialsGroupsResponse, error) | ||
| ListAccessKeys(ctx context.Context, projectId string) objectstorage.ApiListAccessKeysRequest | ||
| } | ||
@@ -34,1 +35,24 @@ | ||
| } | ||
| func GetCredentialsName(ctx context.Context, apiClient ObjectStorageClient, projectId, credentialsGroupId, keyId string) (string, error) { | ||
| req := apiClient.ListAccessKeys(ctx, projectId) | ||
| req = req.CredentialsGroup(credentialsGroupId) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return "", fmt.Errorf("list Object Storage credentials: %w", err) | ||
| } | ||
| credentials := resp.AccessKeys | ||
| if credentials == nil { | ||
| return "", fmt.Errorf("nil Object Storage credentials list") | ||
| } | ||
| for _, credential := range *credentials { | ||
| if credential.KeyId != nil && *credential.KeyId == keyId && credential.DisplayName != nil && *credential.DisplayName != "" { | ||
| return *credential.DisplayName, nil | ||
| } | ||
| } | ||
| return "", fmt.Errorf("could not find Object Storage credentials name") | ||
| } |
@@ -16,2 +16,3 @@ package utils | ||
| testInstanceId = uuid.NewString() | ||
| testUserId = uuid.NewString() | ||
| ) | ||
@@ -22,2 +23,3 @@ | ||
| testUserName = "user" | ||
| testDescription = "sample description" | ||
| ) | ||
@@ -28,2 +30,4 @@ | ||
| getInstanceResp *secretsmanager.Instance | ||
| getUserFails bool | ||
| getUserResp *secretsmanager.User | ||
| } | ||
@@ -38,2 +42,9 @@ | ||
| func (s *secretsManagerClientMocked) GetUserExecute(_ context.Context, _, _, _ string) (*secretsmanager.User, error) { | ||
| if s.getUserFails { | ||
| return nil, fmt.Errorf("could not get user") | ||
| } | ||
| return s.getUserResp, nil | ||
| } | ||
| func TestGetInstanceName(t *testing.T) { | ||
@@ -86,1 +97,81 @@ tests := []struct { | ||
| } | ||
| func TestGetUserDetails(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| getUserFails bool | ||
| GetUserResp *secretsmanager.User | ||
| isValid bool | ||
| expectedOutput string | ||
| }{ | ||
| { | ||
| description: "base", | ||
| GetUserResp: &secretsmanager.User{ | ||
| Username: utils.Ptr(testUserName), | ||
| Description: utils.Ptr(testDescription), | ||
| }, | ||
| isValid: true, | ||
| expectedOutput: fmt.Sprintf("%q (%s)", testUserName, testDescription), | ||
| }, | ||
| { | ||
| description: "user has no description", | ||
| GetUserResp: &secretsmanager.User{ | ||
| Username: utils.Ptr(testUserName), | ||
| }, | ||
| isValid: true, | ||
| expectedOutput: fmt.Sprintf("%q", testUserName), | ||
| }, | ||
| { | ||
| description: "user has empty description", | ||
| GetUserResp: &secretsmanager.User{ | ||
| Username: utils.Ptr(testUserName), | ||
| Description: utils.Ptr(""), | ||
| }, | ||
| isValid: true, | ||
| expectedOutput: fmt.Sprintf("%q", testUserName), | ||
| }, | ||
| { | ||
| description: "user has empty username", | ||
| GetUserResp: &secretsmanager.User{ | ||
| Username: utils.Ptr(""), | ||
| }, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "user has no username", | ||
| GetUserResp: &secretsmanager.User{ | ||
| Username: nil, | ||
| }, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "get user fails", | ||
| getUserFails: true, | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| client := &secretsManagerClientMocked{ | ||
| getUserFails: tt.getUserFails, | ||
| getUserResp: tt.GetUserResp, | ||
| } | ||
| userLabel, err := GetUserLabel(context.Background(), client, testProjectId, testInstanceId, testUserId) | ||
| 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 userLabel != tt.expectedOutput { | ||
| t.Errorf("expected user label to be %s, got %s", tt.expectedOutput, userLabel) | ||
| } | ||
| }) | ||
| } | ||
| } |
@@ -12,2 +12,3 @@ package utils | ||
| GetInstanceExecute(ctx context.Context, projectId, instanceId string) (*secretsmanager.Instance, error) | ||
| GetUserExecute(ctx context.Context, projectId string, instanceId string, userId string) (*secretsmanager.User, error) | ||
| } | ||
@@ -22,1 +23,21 @@ | ||
| } | ||
| func GetUserLabel(ctx context.Context, apiClient SecretsManagerClient, projectId, instanceId, userId string) (string, error) { | ||
| resp, err := apiClient.GetUserExecute(ctx, projectId, instanceId, userId) | ||
| if err != nil { | ||
| return "", fmt.Errorf("get Secrets Manager user: %w", err) | ||
| } | ||
| if resp.Username == nil || *resp.Username == "" { | ||
| // Should never happen, username is auto-generated | ||
| return "", fmt.Errorf("username is empty") | ||
| } | ||
| var userLabel string | ||
| if resp.Description == nil || *resp.Description == "" { | ||
| userLabel = fmt.Sprintf("%q", *resp.Username) | ||
| } else { | ||
| userLabel = fmt.Sprintf("%q (%s)", *resp.Username, *resp.Description) | ||
| } | ||
| return userLabel, nil | ||
| } |