From 6cccb86bc18a3b57841de62cc18c23ca634c1a17 Mon Sep 17 00:00:00 2001 From: vicentepinto98 Date: Tue, 27 Feb 2024 14:49:48 +0000 Subject: [PATCH 01/48] Object storage credentials group create command --- .../credentials-group/create/create.go | 102 ++++++++++ .../credentials-group/create/create_test.go | 187 ++++++++++++++++++ .../credentials-group/credentials-group.go | 25 +++ internal/cmd/object-storage/object_storage.go | 2 + 4 files changed, 316 insertions(+) create mode 100644 internal/cmd/object-storage/credentials-group/create/create.go create mode 100644 internal/cmd/object-storage/credentials-group/create/create_test.go create mode 100644 internal/cmd/object-storage/credentials-group/credentials-group.go diff --git a/internal/cmd/object-storage/credentials-group/create/create.go b/internal/cmd/object-storage/credentials-group/create/create.go new file mode 100644 index 00000000..eb92b5f9 --- /dev/null +++ b/internal/cmd/object-storage/credentials-group/create/create.go @@ -0,0 +1,102 @@ +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/object-storage/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" +) + +const ( + displayNameFlag = "display-name" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + DisplayName string +} + +func NewCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "create", + Short: "Creates a credentials group to hold Object Storage access credentials", + Long: "Creates a credentials group to hold Object Storage access credentials", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `Create credentials group to hold Object Storage access credentials`, + "$ stackit object-storage credentials-group create --display-name example"), + ), + 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 + } + + if !model.AssumeYes { + prompt := "Are you sure you want to create a credentials group?" + 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 group: %w", err) + } + + cmd.Printf("Created credentials group. Credentials group ID: %s\n\n", *resp.CredentialsGroup.CredentialsGroupId) + cmd.Printf("URN: %s\n", *resp.CredentialsGroup.Urn) + return nil + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().String(displayNameFlag, "", "Name of the group holding credentials") + + err := flags.MarkFlagsRequired(cmd, displayNameFlag) + 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, + DisplayName: flags.FlagToStringValue(cmd, displayNameFlag), + }, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiCreateCredentialsGroupRequest { + req := apiClient.CreateCredentialsGroup(ctx, model.ProjectId) + req = req.CreateCredentialsGroupPayload(objectstorage.CreateCredentialsGroupPayload{ + DisplayName: utils.Ptr(model.DisplayName), + }) + return req +} diff --git a/internal/cmd/object-storage/credentials-group/create/create_test.go b/internal/cmd/object-storage/credentials-group/create/create_test.go new file mode 100644 index 00000000..69952556 --- /dev/null +++ b/internal/cmd/object-storage/credentials-group/create/create_test.go @@ -0,0 +1,187 @@ +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/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 testDisplayName = "test-name" + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + projectIdFlag: testProjectId, + displayNameFlag: testDisplayName, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + }, + DisplayName: testDisplayName, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixturePayload(mods ...func(payload *objectstorage.CreateCredentialsGroupPayload)) objectstorage.CreateCredentialsGroupPayload { + payload := objectstorage.CreateCredentialsGroupPayload{ + DisplayName: utils.Ptr(testDisplayName), + } + for _, mod := range mods { + mod(&payload) + } + return payload +} + +func fixtureRequest(mods ...func(request *objectstorage.ApiCreateCredentialsGroupRequest)) objectstorage.ApiCreateCredentialsGroupRequest { + request := testClient.CreateCredentialsGroup(testCtx, testProjectId) + request = request.CreateCredentialsGroupPayload(fixturePayload()) + 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: "display name missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, displayNameFlag) + }), + 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.ApiCreateCredentialsGroupRequest + }{ + { + 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) + } + }) + } +} diff --git a/internal/cmd/object-storage/credentials-group/credentials-group.go b/internal/cmd/object-storage/credentials-group/credentials-group.go new file mode 100644 index 00000000..faa9ca87 --- /dev/null +++ b/internal/cmd/object-storage/credentials-group/credentials-group.go @@ -0,0 +1,25 @@ +package credentialsgroup + +import ( + "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/credentials-group/create" + "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-group", + Short: "Provides functionality for Object Storage credentials group", + Long: "Provides functionality for Object Storage credentials group.", + Args: args.NoArgs, + Run: utils.CmdHelp, + } + addSubcommands(cmd) + return cmd +} + +func addSubcommands(cmd *cobra.Command) { + cmd.AddCommand(create.NewCmd()) +} diff --git a/internal/cmd/object-storage/object_storage.go b/internal/cmd/object-storage/object_storage.go index 7b2842f9..1b2c2e1c 100644 --- a/internal/cmd/object-storage/object_storage.go +++ b/internal/cmd/object-storage/object_storage.go @@ -1,6 +1,7 @@ package objectstorage import ( + credentialsGroup "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/credentials-group" "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/disable" "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/enable" "github.com/stackitcloud/stackit-cli/internal/pkg/args" @@ -24,4 +25,5 @@ func NewCmd() *cobra.Command { func addSubcommands(cmd *cobra.Command) { cmd.AddCommand(disable.NewCmd()) cmd.AddCommand(enable.NewCmd()) + cmd.AddCommand(credentialsGroup.NewCmd()) } From f39f9ce04479f01c14e18a3ac61a1e8ee9cb3314 Mon Sep 17 00:00:00 2001 From: vicentepinto98 Date: Wed, 28 Feb 2024 13:19:07 +0000 Subject: [PATCH 02/48] Delete credentials-group --- ...dentials-group.go => credentials_group.go} | 2 + .../credentials-group/delete/delete.go | 97 ++++++++ .../credentials-group/delete/delete_test.go | 215 ++++++++++++++++++ .../services/object-storage/utils/utils.go | 36 +++ .../object-storage/utils/utils_test.go | 132 +++++++++++ 5 files changed, 482 insertions(+) rename internal/cmd/object-storage/credentials-group/{credentials-group.go => credentials_group.go} (84%) create mode 100644 internal/cmd/object-storage/credentials-group/delete/delete.go create mode 100644 internal/cmd/object-storage/credentials-group/delete/delete_test.go create mode 100644 internal/pkg/services/object-storage/utils/utils.go create mode 100644 internal/pkg/services/object-storage/utils/utils_test.go diff --git a/internal/cmd/object-storage/credentials-group/credentials-group.go b/internal/cmd/object-storage/credentials-group/credentials_group.go similarity index 84% rename from internal/cmd/object-storage/credentials-group/credentials-group.go rename to internal/cmd/object-storage/credentials-group/credentials_group.go index faa9ca87..0b7e145a 100644 --- a/internal/cmd/object-storage/credentials-group/credentials-group.go +++ b/internal/cmd/object-storage/credentials-group/credentials_group.go @@ -2,6 +2,7 @@ package credentialsgroup import ( "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/credentials-group/create" + "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/credentials-group/delete" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" @@ -22,4 +23,5 @@ func NewCmd() *cobra.Command { func addSubcommands(cmd *cobra.Command) { cmd.AddCommand(create.NewCmd()) + cmd.AddCommand(delete.NewCmd()) } diff --git a/internal/cmd/object-storage/credentials-group/delete/delete.go b/internal/cmd/object-storage/credentials-group/delete/delete.go new file mode 100644 index 00000000..d0e7e26c --- /dev/null +++ b/internal/cmd/object-storage/credentials-group/delete/delete.go @@ -0,0 +1,97 @@ +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/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-cli/internal/pkg/utils" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" +) + +const ( + credentialsGroupIdArg = "CREDENTIALS_GROUP_ID" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + CredentialsGroupId string +} + +func NewCmd() *cobra.Command { + cmd := &cobra.Command{ + 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.", + Args: args.SingleArg(credentialsGroupIdArg, utils.ValidateUUID), + Example: examples.Build( + examples.NewExample( + `Delete a credentials group with ID "xxx"`, + "$ stackit object-storage credentials-group delete xxx"), + ), + 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 + } + + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to delete credentials group %s? (This cannot be undone)", 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 group: %w", err) + } + + cmd.Printf("Deleted credentials group %s\n", credentialsGroupLabel) + return nil + }, + } + return cmd +} + +func parseInput(cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + credentialsGroupId := inputArgs[0] + + globalFlags := globalflags.Parse(cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + return &inputModel{ + GlobalFlagModel: globalFlags, + CredentialsGroupId: credentialsGroupId, + }, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiDeleteCredentialsGroupRequest { + req := apiClient.DeleteCredentialsGroup(ctx, model.ProjectId, model.CredentialsGroupId) + return req +} diff --git a/internal/cmd/object-storage/credentials-group/delete/delete_test.go b/internal/cmd/object-storage/credentials-group/delete/delete_test.go new file mode 100644 index 00000000..fc00516a --- /dev/null +++ b/internal/cmd/object-storage/credentials-group/delete/delete_test.go @@ -0,0 +1,215 @@ +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() + +func fixtureArgValues(mods ...func(argValues []string)) []string { + argValues := []string{ + testCredentialsGroupId, + } + for _, mod := range mods { + mod(argValues) + } + return argValues +} + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + projectIdFlag: testProjectId, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + }, + CredentialsGroupId: testCredentialsGroupId, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *objectstorage.ApiDeleteCredentialsGroupRequest)) objectstorage.ApiDeleteCredentialsGroupRequest { + request := testClient.DeleteCredentialsGroup(testCtx, testProjectId, 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 invalid 1", + argValues: []string{""}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + { + description: "credentials group 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 objectstorage.ApiDeleteCredentialsGroupRequest + }{ + { + 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) + } + }) + } +} diff --git a/internal/pkg/services/object-storage/utils/utils.go b/internal/pkg/services/object-storage/utils/utils.go new file mode 100644 index 00000000..01ecba62 --- /dev/null +++ b/internal/pkg/services/object-storage/utils/utils.go @@ -0,0 +1,36 @@ +package utils + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" +) + +type ObjectStorageClient interface { + ListCredentialsGroupsExecute(ctx context.Context, projectId string) (*objectstorage.ListCredentialsGroupsResponse, error) +} + +func GetCredentialsGroupName(ctx context.Context, apiClient ObjectStorageClient, projectId, credentialsGroupId string) (string, error) { + resp, err := apiClient.ListCredentialsGroupsExecute(ctx, projectId) + if err != nil { + return "", fmt.Errorf("list Object Storage credentials groups: %w", err) + } + + credentialsGroups := resp.CredentialsGroups + if credentialsGroups == nil { + return "", fmt.Errorf("nil Object Storage credentials group list: %w", err) + } + + var name string + for _, group := range *credentialsGroups { + if group.CredentialsGroupId != nil && *group.CredentialsGroupId == credentialsGroupId && group.DisplayName != nil { + name = *group.DisplayName + } + } + + if name == "" { + return "", fmt.Errorf("could not find Object Storage credentials group name") + } + return name, nil +} diff --git a/internal/pkg/services/object-storage/utils/utils_test.go b/internal/pkg/services/object-storage/utils/utils_test.go new file mode 100644 index 00000000..e101bea3 --- /dev/null +++ b/internal/pkg/services/object-storage/utils/utils_test.go @@ -0,0 +1,132 @@ +package utils + +import ( + "context" + "fmt" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" +) + +var ( + testProjectId = uuid.NewString() + testCredentialsGroupId = uuid.NewString() +) + +const ( + testCredentialsGroupName = "testGroup" +) + +type objectStorageClientMocked struct { + listCredentialsGroupsFails bool + listCredentialsGroupsResp *objectstorage.ListCredentialsGroupsResponse +} + +func (m *objectStorageClientMocked) ListCredentialsGroupsExecute(_ context.Context, _ string) (*objectstorage.ListCredentialsGroupsResponse, error) { + if m.listCredentialsGroupsFails { + return nil, fmt.Errorf("could not list credentials groups") + } + return m.listCredentialsGroupsResp, nil +} +func TestGetCredentialsGroupName(t *testing.T) { + tests := []struct { + description string + listCredentialsGroupsFails bool + listCredentialsGroupsResp *objectstorage.ListCredentialsGroupsResponse + isValid bool + expectedOutput string + }{ + { + description: "base", + listCredentialsGroupsResp: &objectstorage.ListCredentialsGroupsResponse{ + CredentialsGroups: &[]objectstorage.CredentialsGroup{ + { + CredentialsGroupId: utils.Ptr(testCredentialsGroupId), + DisplayName: utils.Ptr(testCredentialsGroupName), + }, + }, + }, + isValid: true, + expectedOutput: testCredentialsGroupName, + }, + { + description: "list credentials groups fails", + listCredentialsGroupsFails: true, + isValid: false, + }, + { + description: "multiple credentials groups", + listCredentialsGroupsResp: &objectstorage.ListCredentialsGroupsResponse{ + CredentialsGroups: &[]objectstorage.CredentialsGroup{ + { + CredentialsGroupId: utils.Ptr("test-UUID"), + DisplayName: utils.Ptr("test-name"), + }, + { + CredentialsGroupId: utils.Ptr(testCredentialsGroupId), + DisplayName: utils.Ptr(testCredentialsGroupName), + }, + }, + }, + isValid: true, + expectedOutput: testCredentialsGroupName, + }, + { + description: "nil credentials groups", + listCredentialsGroupsResp: &objectstorage.ListCredentialsGroupsResponse{ + CredentialsGroups: nil, + }, + isValid: false, + }, + { + description: "nil credentials group id", + listCredentialsGroupsResp: &objectstorage.ListCredentialsGroupsResponse{ + CredentialsGroups: &[]objectstorage.CredentialsGroup{ + { + CredentialsGroupId: nil, + }, + }, + }, + isValid: false, + }, + { + description: "nil credentials group name", + listCredentialsGroupsResp: &objectstorage.ListCredentialsGroupsResponse{ + CredentialsGroups: &[]objectstorage.CredentialsGroup{ + { + CredentialsGroupId: utils.Ptr(testCredentialsGroupId), + DisplayName: nil, + }, + }, + }, + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + client := &objectStorageClientMocked{ + listCredentialsGroupsFails: tt.listCredentialsGroupsFails, + listCredentialsGroupsResp: tt.listCredentialsGroupsResp, + } + + output, err := GetCredentialsGroupName(context.Background(), client, testProjectId, testCredentialsGroupId) + + 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) + } + }) + } +} From ce03eaf0e75a56651fbecf92ba297f274ba4b8af Mon Sep 17 00:00:00 2001 From: vicentepinto98 Date: Wed, 28 Feb 2024 15:37:33 +0000 Subject: [PATCH 03/48] List credentials-group, modify create --- .../credentials-group/create/create.go | 10 +- .../credentials-group/create/create_test.go | 6 +- .../credentials-group/credentials_group.go | 2 + .../credentials-group/list/list.go | 134 +++++++++++++ .../credentials-group/list/list_test.go | 182 ++++++++++++++++++ 5 files changed, 326 insertions(+), 8 deletions(-) create mode 100644 internal/cmd/object-storage/credentials-group/list/list.go create mode 100644 internal/cmd/object-storage/credentials-group/list/list_test.go diff --git a/internal/cmd/object-storage/credentials-group/create/create.go b/internal/cmd/object-storage/credentials-group/create/create.go index eb92b5f9..887644a8 100644 --- a/internal/cmd/object-storage/credentials-group/create/create.go +++ b/internal/cmd/object-storage/credentials-group/create/create.go @@ -18,7 +18,7 @@ import ( ) const ( - displayNameFlag = "display-name" + nameFlag = "name" ) type inputModel struct { @@ -35,7 +35,7 @@ func NewCmd() *cobra.Command { Example: examples.Build( examples.NewExample( `Create credentials group to hold Object Storage access credentials`, - "$ stackit object-storage credentials-group create --display-name example"), + "$ stackit object-storage credentials-group create --name example"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -75,9 +75,9 @@ func NewCmd() *cobra.Command { } func configureFlags(cmd *cobra.Command) { - cmd.Flags().String(displayNameFlag, "", "Name of the group holding credentials") + cmd.Flags().String(nameFlag, "", "Name of the group holding credentials") - err := flags.MarkFlagsRequired(cmd, displayNameFlag) + err := flags.MarkFlagsRequired(cmd, nameFlag) cobra.CheckErr(err) } @@ -89,7 +89,7 @@ func parseInput(cmd *cobra.Command) (*inputModel, error) { return &inputModel{ GlobalFlagModel: globalFlags, - DisplayName: flags.FlagToStringValue(cmd, displayNameFlag), + DisplayName: flags.FlagToStringValue(cmd, nameFlag), }, nil } diff --git a/internal/cmd/object-storage/credentials-group/create/create_test.go b/internal/cmd/object-storage/credentials-group/create/create_test.go index 69952556..c48e8527 100644 --- a/internal/cmd/object-storage/credentials-group/create/create_test.go +++ b/internal/cmd/object-storage/credentials-group/create/create_test.go @@ -24,8 +24,8 @@ var testDisplayName = "test-name" func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { flagValues := map[string]string{ - projectIdFlag: testProjectId, - displayNameFlag: testDisplayName, + projectIdFlag: testProjectId, + nameFlag: testDisplayName, } for _, mod := range mods { mod(flagValues) @@ -107,7 +107,7 @@ func TestParseInput(t *testing.T) { { description: "display name missing", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, displayNameFlag) + delete(flagValues, nameFlag) }), isValid: false, }, diff --git a/internal/cmd/object-storage/credentials-group/credentials_group.go b/internal/cmd/object-storage/credentials-group/credentials_group.go index 0b7e145a..6427642e 100644 --- a/internal/cmd/object-storage/credentials-group/credentials_group.go +++ b/internal/cmd/object-storage/credentials-group/credentials_group.go @@ -3,6 +3,7 @@ package credentialsgroup import ( "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/credentials-group/create" "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/credentials-group/delete" + "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/credentials-group/list" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" @@ -24,4 +25,5 @@ func NewCmd() *cobra.Command { func addSubcommands(cmd *cobra.Command) { cmd.AddCommand(create.NewCmd()) cmd.AddCommand(delete.NewCmd()) + cmd.AddCommand(list.NewCmd()) } diff --git a/internal/cmd/object-storage/credentials-group/list/list.go b/internal/cmd/object-storage/credentials-group/list/list.go new file mode 100644 index 00000000..1dd01093 --- /dev/null +++ b/internal/cmd/object-storage/credentials-group/list/list.go @@ -0,0 +1,134 @@ +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/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" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + Limit *int64 +} + +func NewCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "Lists all credentials groups", + Long: "Lists all credentials groups.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `List all credentials groups`, + "$ stackit object-storage credentials-group list"), + examples.NewExample( + `List all credentials groups in JSON format`, + "$ stackit object-storage credentials list --output-format json"), + examples.NewExample( + `List up to 10 credentials groups`, + "$ stackit object-storage credentials list --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 groups: %w", err) + } + credentialsGroups := *resp.CredentialsGroups + if len(credentialsGroups) == 0 { + cmd.Println("No credentials groups found for your project") + return nil + } + + // Truncate output + if model.Limit != nil && len(credentialsGroups) > int(*model.Limit) { + credentialsGroups = credentialsGroups[:*model.Limit] + } + return outputResult(cmd, model.OutputFormat, credentialsGroups) + }, + } + 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 *objectstorage.APIClient) objectstorage.ApiListCredentialsGroupsRequest { + req := apiClient.ListCredentialsGroups(ctx, model.ProjectId) + return req +} + +func outputResult(cmd *cobra.Command, outputFormat string, credentialsGroups []objectstorage.CredentialsGroup) error { + switch outputFormat { + case globalflags.JSONOutputFormat: + details, err := json.MarshalIndent(credentialsGroups, "", " ") + if err != nil { + return fmt.Errorf("marshal Object Storage credentials group list: %w", err) + } + cmd.Println(string(details)) + + return nil + default: + table := tables.NewTable() + table.SetHeader("ID", "NAME", "URN") + for i := range credentialsGroups { + c := credentialsGroups[i] + table.AddRow(*c.CredentialsGroupId, *c.DisplayName, *c.Urn) + } + err := table.Display(cmd) + if err != nil { + return fmt.Errorf("render table: %w", err) + } + return nil + } +} diff --git a/internal/cmd/object-storage/credentials-group/list/list_test.go b/internal/cmd/object-storage/credentials-group/list/list_test.go new file mode 100644 index 00000000..0658184a --- /dev/null +++ b/internal/cmd/object-storage/credentials-group/list/list_test.go @@ -0,0 +1,182 @@ +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() + +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 *objectstorage.ApiListCredentialsGroupsRequest)) objectstorage.ApiListCredentialsGroupsRequest { + request := testClient.ListCredentialsGroups(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 := 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.ApiListCredentialsGroupsRequest + }{ + { + 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) + } + }) + } +} From e0d5ccf56469a62bba1742d78c586afa95b3b20e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Go=CC=88kc=CC=A7e=20Go=CC=88k=20Klingel?= Date: Fri, 1 Mar 2024 08:23:41 +0100 Subject: [PATCH 04/48] add unit test - group name empty check --- .../pkg/services/object-storage/utils/utils_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/internal/pkg/services/object-storage/utils/utils_test.go b/internal/pkg/services/object-storage/utils/utils_test.go index e101bea3..f219cc5b 100644 --- a/internal/pkg/services/object-storage/utils/utils_test.go +++ b/internal/pkg/services/object-storage/utils/utils_test.go @@ -104,6 +104,18 @@ func TestGetCredentialsGroupName(t *testing.T) { }, isValid: false, }, + { + description: "empty credentials group name", + listCredentialsGroupsResp: &objectstorage.ListCredentialsGroupsResponse{ + CredentialsGroups: &[]objectstorage.CredentialsGroup{ + { + CredentialsGroupId: utils.Ptr(testCredentialsGroupId), + DisplayName: utils.Ptr(""), + }, + }, + }, + isValid: false, + }, } for _, tt := range tests { From 6c9f80e1634714f995616d445348cb2eb95acf7f Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Fri, 1 Mar 2024 10:48:06 +0100 Subject: [PATCH 05/48] Update long description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: João Palet --- internal/cmd/object-storage/credentials-group/create/create.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials-group/create/create.go b/internal/cmd/object-storage/credentials-group/create/create.go index 887644a8..7c8cfd69 100644 --- a/internal/cmd/object-storage/credentials-group/create/create.go +++ b/internal/cmd/object-storage/credentials-group/create/create.go @@ -30,7 +30,7 @@ func NewCmd() *cobra.Command { cmd := &cobra.Command{ Use: "create", Short: "Creates a credentials group to hold Object Storage access credentials", - Long: "Creates a credentials group to hold Object Storage access credentials", + Long: "Creates a credentials group to hold Object Storage access credentials.", Args: args.NoArgs, Example: examples.Build( examples.NewExample( From 630174268f4dc05b574472ab088fc1df9e385fb6 Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Fri, 1 Mar 2024 10:51:57 +0100 Subject: [PATCH 06/48] Change text formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: João Palet --- internal/cmd/object-storage/credentials-group/delete/delete.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials-group/delete/delete.go b/internal/cmd/object-storage/credentials-group/delete/delete.go index d0e7e26c..d1397e9c 100644 --- a/internal/cmd/object-storage/credentials-group/delete/delete.go +++ b/internal/cmd/object-storage/credentials-group/delete/delete.go @@ -56,7 +56,7 @@ func NewCmd() *cobra.Command { } if !model.AssumeYes { - prompt := fmt.Sprintf("Are you sure you want to delete credentials group %s? (This cannot be undone)", credentialsGroupLabel) + prompt := fmt.Sprintf("Are you sure you want to delete credentials group %q? (This cannot be undone)", credentialsGroupLabel) err = confirm.PromptForConfirmation(cmd, prompt) if err != nil { return err From 4b93ddc7503bfc6a90cbfe4c42f2f70347cd90d9 Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Fri, 1 Mar 2024 10:52:53 +0100 Subject: [PATCH 07/48] Change text formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: João Palet --- internal/cmd/object-storage/credentials-group/delete/delete.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials-group/delete/delete.go b/internal/cmd/object-storage/credentials-group/delete/delete.go index d1397e9c..70bf2548 100644 --- a/internal/cmd/object-storage/credentials-group/delete/delete.go +++ b/internal/cmd/object-storage/credentials-group/delete/delete.go @@ -70,7 +70,7 @@ func NewCmd() *cobra.Command { return fmt.Errorf("delete Object Storage credentials group: %w", err) } - cmd.Printf("Deleted credentials group %s\n", credentialsGroupLabel) + cmd.Printf("Deleted credentials group %q\n", credentialsGroupLabel) return nil }, } From d1c31b3a75a8e981344e1ecda30a81c0cf0c52be Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Fri, 1 Mar 2024 10:55:25 +0100 Subject: [PATCH 08/48] Update command in example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: João Palet --- internal/cmd/object-storage/credentials-group/list/list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials-group/list/list.go b/internal/cmd/object-storage/credentials-group/list/list.go index 1dd01093..ab42a291 100644 --- a/internal/cmd/object-storage/credentials-group/list/list.go +++ b/internal/cmd/object-storage/credentials-group/list/list.go @@ -41,7 +41,7 @@ func NewCmd() *cobra.Command { "$ stackit object-storage credentials list --output-format json"), examples.NewExample( `List up to 10 credentials groups`, - "$ stackit object-storage credentials list --limit 10"), + "$ stackit object-storage credentials-group list --limit 10"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() From c7c0ce713bcab7f24efe6bbae2ef1e6cfcbf4476 Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Fri, 1 Mar 2024 10:56:45 +0100 Subject: [PATCH 09/48] Update command in example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: João Palet --- internal/cmd/object-storage/credentials-group/list/list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials-group/list/list.go b/internal/cmd/object-storage/credentials-group/list/list.go index ab42a291..694406de 100644 --- a/internal/cmd/object-storage/credentials-group/list/list.go +++ b/internal/cmd/object-storage/credentials-group/list/list.go @@ -38,7 +38,7 @@ func NewCmd() *cobra.Command { "$ stackit object-storage credentials-group list"), examples.NewExample( `List all credentials groups in JSON format`, - "$ stackit object-storage credentials list --output-format json"), + "$ stackit object-storage credentials-group list --output-format json"), examples.NewExample( `List up to 10 credentials groups`, "$ stackit object-storage credentials-group list --limit 10"), From c1143542673f78fd956f49fd089168b6dc3f618e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Go=CC=88kc=CC=A7e=20Go=CC=88k=20Klingel?= Date: Fri, 1 Mar 2024 11:53:53 +0100 Subject: [PATCH 10/48] add name to prompt and success messages --- .../cmd/object-storage/credentials-group/create/create.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/cmd/object-storage/credentials-group/create/create.go b/internal/cmd/object-storage/credentials-group/create/create.go index 7c8cfd69..9bb3d785 100644 --- a/internal/cmd/object-storage/credentials-group/create/create.go +++ b/internal/cmd/object-storage/credentials-group/create/create.go @@ -51,7 +51,7 @@ func NewCmd() *cobra.Command { } if !model.AssumeYes { - prompt := "Are you sure you want to create a credentials group?" + prompt := fmt.Sprintf("Are you sure you want to create a credentials group with name %q?", model.DisplayName) err = confirm.PromptForConfirmation(cmd, prompt) if err != nil { return err @@ -65,7 +65,7 @@ func NewCmd() *cobra.Command { return fmt.Errorf("create Object Storage credentials group: %w", err) } - cmd.Printf("Created credentials group. Credentials group ID: %s\n\n", *resp.CredentialsGroup.CredentialsGroupId) + cmd.Printf("Created credentials group %q. Credentials group ID: %s\n\n", *resp.CredentialsGroup.DisplayName, *resp.CredentialsGroup.CredentialsGroupId) cmd.Printf("URN: %s\n", *resp.CredentialsGroup.Urn) return nil }, From 74d3c9212c18e7dcf101fccc14ea4c700db22f3b Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Fri, 1 Mar 2024 11:56:26 +0100 Subject: [PATCH 11/48] Change condition handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: João Palet --- internal/pkg/services/object-storage/utils/utils.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/internal/pkg/services/object-storage/utils/utils.go b/internal/pkg/services/object-storage/utils/utils.go index 01ecba62..aa112458 100644 --- a/internal/pkg/services/object-storage/utils/utils.go +++ b/internal/pkg/services/object-storage/utils/utils.go @@ -22,15 +22,11 @@ func GetCredentialsGroupName(ctx context.Context, apiClient ObjectStorageClient, return "", fmt.Errorf("nil Object Storage credentials group list: %w", err) } - var name string for _, group := range *credentialsGroups { - if group.CredentialsGroupId != nil && *group.CredentialsGroupId == credentialsGroupId && group.DisplayName != nil { - name = *group.DisplayName + if group.CredentialsGroupId != nil && *group.CredentialsGroupId == credentialsGroupId && group.DisplayName != nil && *group.DisplayName != "" { + return group.DisplayName, nil } } - if name == "" { - return "", fmt.Errorf("could not find Object Storage credentials group name") - } - return name, nil + return "", fmt.Errorf("could not find Object Storage credentials group name") } From 1b6ad53ddce1ea4d60487df26073163581a844fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Go=CC=88kc=CC=A7e=20Go=CC=88k=20Klingel?= Date: Fri, 1 Mar 2024 12:02:30 +0100 Subject: [PATCH 12/48] fix return value issue --- internal/pkg/services/object-storage/utils/utils.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/pkg/services/object-storage/utils/utils.go b/internal/pkg/services/object-storage/utils/utils.go index aa112458..e2f990f2 100644 --- a/internal/pkg/services/object-storage/utils/utils.go +++ b/internal/pkg/services/object-storage/utils/utils.go @@ -24,9 +24,9 @@ func GetCredentialsGroupName(ctx context.Context, apiClient ObjectStorageClient, for _, group := range *credentialsGroups { if group.CredentialsGroupId != nil && *group.CredentialsGroupId == credentialsGroupId && group.DisplayName != nil && *group.DisplayName != "" { - return group.DisplayName, nil + return *group.DisplayName, nil } } - return "", fmt.Errorf("could not find Object Storage credentials group name") + return "", fmt.Errorf("could not find Object Storage credentials group name") } From 0f1bb18fcf066643b10e2c353ec30fffb31e289d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Go=CC=88kc=CC=A7e=20Go=CC=88k=20Klingel?= Date: Tue, 5 Mar 2024 17:40:35 +0100 Subject: [PATCH 13/48] Object storage credentials create command --- .../credentials/create/create.go | 127 +++++++++ .../credentials/create/create_test.go | 246 ++++++++++++++++++ 2 files changed, 373 insertions(+) create mode 100644 internal/cmd/object-storage/credentials/create/create.go create mode 100644 internal/cmd/object-storage/credentials/create/create_test.go diff --git a/internal/cmd/object-storage/credentials/create/create.go b/internal/cmd/object-storage/credentials/create/create.go new file mode 100644 index 00000000..46b25927 --- /dev/null +++ b/internal/cmd/object-storage/credentials/create/create.go @@ -0,0 +1,127 @@ +package create + +import ( + "context" + "fmt" + objectStorageUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/utils" + "time" + + "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 ( + expiresFlag = "expires" + credentialsGroupFlag = "credentials-group" + expirationTimeFormat = time.RFC3339 +) + +type inputModel struct { + *globalflags.GlobalFlagModel + Expires *time.Time + CredentialsGroupId string + HidePassword bool +} + +func NewCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "create", + Short: "Creates credentials for a credentials group", + Long: "Creates credentials for a credentials group.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `Create credentials for a credentials group`, + "$ stackit object-storage credentials create --credentials-group xxx"), + examples.NewExample( + `Create credentials for a credentials group, with a specific expiration date`, + "$ stackit object-storage credentials create --credentials-group xxx --expires 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) + } + + cmd.Printf("Created credentials in group %q. Credential 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) + + return nil + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().String(expiresFlag, "", "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(), credentialsGroupFlag, "Credentials Group ID") + + err := flags.MarkFlagsRequired(cmd, credentialsGroupFlag) + cobra.CheckErr(err) +} + +func parseInput(cmd *cobra.Command) (*inputModel, error) { + globalFlags := globalflags.Parse(cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + expires, err := flags.FlagToDateTimePointer(cmd, expiresFlag, expirationTimeFormat) + if err != nil { + return nil, &errors.FlagValidationError{ + Flag: expiresFlag, + Details: err.Error(), + } + } + + return &inputModel{ + GlobalFlagModel: globalFlags, + Expires: expires, + CredentialsGroupId: flags.FlagToStringValue(cmd, credentialsGroupFlag), + }, 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.Expires, + }) + return req +} diff --git a/internal/cmd/object-storage/credentials/create/create_test.go b/internal/cmd/object-storage/credentials/create/create_test.go new file mode 100644 index 00000000..bd0a8ba5 --- /dev/null +++ b/internal/cmd/object-storage/credentials/create/create_test.go @@ -0,0 +1,246 @@ +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 testCredentialGroupId = 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, + credentialsGroupFlag: testCredentialGroupId, + expiresFlag: 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, + }, + Expires: utils.Ptr(testExpirationDate), + CredentialsGroupId: testCredentialGroupId, + } + 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 { + //TODO check what can I return here + 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()) + 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, credentialsGroupFlag) + }), + isValid: false, + }, + { + description: "credentials group id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[credentialsGroupFlag] = "" + }), + isValid: false, + }, + { + description: "credentials group id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[credentialsGroupFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "expiration date is missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, expiresFlag) + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Expires = nil + }), + }, + { + description: "expiration date is empty", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[expiresFlag] = "" + }), + isValid: false, + }, + { + description: "expiration date is invalid", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[expiresFlag] = "test" + }), + isValid: false, + }, + { + description: "expiration date is invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[expiresFlag] = "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) + } + }) + } +} From 50ea661ad6633854848e94338a888b68e6d077e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Go=CC=88kc=CC=A7e=20Go=CC=88k=20Klingel?= Date: Tue, 5 Mar 2024 17:41:15 +0100 Subject: [PATCH 14/48] Object storage credentials delete command --- .../credentials/delete/delete.go | 112 +++++++++ .../credentials/delete/delete_test.go | 233 ++++++++++++++++++ .../services/object-storage/utils/utils.go | 24 ++ .../object-storage/utils/utils_test.go | 6 + 4 files changed, 375 insertions(+) create mode 100644 internal/cmd/object-storage/credentials/delete/delete.go create mode 100644 internal/cmd/object-storage/credentials/delete/delete_test.go diff --git a/internal/cmd/object-storage/credentials/delete/delete.go b/internal/cmd/object-storage/credentials/delete/delete.go new file mode 100644 index 00000000..a3cb54d7 --- /dev/null +++ b/internal/cmd/object-storage/credentials/delete/delete.go @@ -0,0 +1,112 @@ +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 ( + credentialIdArg = "CREDENTIAL_ID" + credentialsGroupFlag = "credentials-group" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + CredentialsGroupId string + CredentialId string +} + +func NewCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("delete %s", credentialIdArg), + Short: "Deletes a credential of a credentials group", + Long: "Deletes a credential of a credentials group.", + Args: args.SingleArg(credentialIdArg, 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 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.CredentialId) + if err != nil { + credentialsLabel = model.CredentialId + } + + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to delete credentials %s 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 %s of credentials group %q\n", credentialsLabel, credentialsGroupLabel) + return nil + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Var(flags.UUIDFlag(), credentialsGroupFlag, "Credentials Group ID") + + err := flags.MarkFlagsRequired(cmd, credentialsGroupFlag) + cobra.CheckErr(err) +} + +func parseInput(cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + credentialId := inputArgs[0] + + globalFlags := globalflags.Parse(cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + return &inputModel{ + GlobalFlagModel: globalFlags, + CredentialsGroupId: flags.FlagToStringValue(cmd, credentialsGroupFlag), + CredentialId: credentialId, + }, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiDeleteAccessKeyRequest { + req := apiClient.DeleteAccessKey(ctx, model.ProjectId, model.CredentialId) + req = req.CredentialsGroup(model.CredentialsGroupId) + return req +} diff --git a/internal/cmd/object-storage/credentials/delete/delete_test.go b/internal/cmd/object-storage/credentials/delete/delete_test.go new file mode 100644 index 00000000..aa5047ef --- /dev/null +++ b/internal/cmd/object-storage/credentials/delete/delete_test.go @@ -0,0 +1,233 @@ +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 testCredentialId = "keyID" + +func fixtureArgValues(mods ...func(argValues []string)) []string { + argValues := []string{ + testCredentialId, + } + 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, + credentialsGroupFlag: 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, + CredentialId: testCredentialId, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *objectstorage.ApiDeleteAccessKeyRequest)) objectstorage.ApiDeleteAccessKeyRequest { + request := testClient.DeleteAccessKey(testCtx, testProjectId, testCredentialId) + 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, credentialsGroupFlag) + }), + isValid: false, + }, + { + description: "credentials group id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[credentialsGroupFlag] = "" + }), + isValid: false, + }, + { + description: "credentials group id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[credentialsGroupFlag] = "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) + } + }) + } +} diff --git a/internal/pkg/services/object-storage/utils/utils.go b/internal/pkg/services/object-storage/utils/utils.go index e2f990f2..b4ea82a5 100644 --- a/internal/pkg/services/object-storage/utils/utils.go +++ b/internal/pkg/services/object-storage/utils/utils.go @@ -9,6 +9,7 @@ import ( type ObjectStorageClient interface { ListCredentialsGroupsExecute(ctx context.Context, projectId string) (*objectstorage.ListCredentialsGroupsResponse, error) + ListAccessKeys(ctx context.Context, projectId string) objectstorage.ApiListAccessKeysRequest } func GetCredentialsGroupName(ctx context.Context, apiClient ObjectStorageClient, projectId, credentialsGroupId string) (string, error) { @@ -30,3 +31,26 @@ func GetCredentialsGroupName(ctx context.Context, apiClient ObjectStorageClient, return "", fmt.Errorf("could not find Object Storage credentials group name") } + +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: %w", err) + } + + 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 credential name") +} diff --git a/internal/pkg/services/object-storage/utils/utils_test.go b/internal/pkg/services/object-storage/utils/utils_test.go index f219cc5b..f4f31808 100644 --- a/internal/pkg/services/object-storage/utils/utils_test.go +++ b/internal/pkg/services/object-storage/utils/utils_test.go @@ -23,6 +23,7 @@ const ( type objectStorageClientMocked struct { listCredentialsGroupsFails bool listCredentialsGroupsResp *objectstorage.ListCredentialsGroupsResponse + listAccessKeysReq objectstorage.ApiListAccessKeysRequest } func (m *objectStorageClientMocked) ListCredentialsGroupsExecute(_ context.Context, _ string) (*objectstorage.ListCredentialsGroupsResponse, error) { @@ -31,6 +32,11 @@ func (m *objectStorageClientMocked) ListCredentialsGroupsExecute(_ context.Conte } return m.listCredentialsGroupsResp, nil } + +func (m *objectStorageClientMocked) ListAccessKeys(_ context.Context, _ string) objectstorage.ApiListAccessKeysRequest { + return m.listAccessKeysReq +} + func TestGetCredentialsGroupName(t *testing.T) { tests := []struct { description string From c6f732f272a0a3b78bfcc73722792b9b69124d08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Go=CC=88kc=CC=A7e=20Go=CC=88k=20Klingel?= Date: Tue, 5 Mar 2024 17:41:43 +0100 Subject: [PATCH 15/48] Object storage credentials list command --- .../object-storage/credentials/list/list.go | 153 +++++++++++++ .../credentials/list/list_test.go | 206 ++++++++++++++++++ 2 files changed, 359 insertions(+) create mode 100644 internal/cmd/object-storage/credentials/list/list.go create mode 100644 internal/cmd/object-storage/credentials/list/list_test.go diff --git a/internal/cmd/object-storage/credentials/list/list.go b/internal/cmd/object-storage/credentials/list/list.go new file mode 100644 index 00000000..005b6eb6 --- /dev/null +++ b/internal/cmd/object-storage/credentials/list/list.go @@ -0,0 +1,153 @@ +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" + credentialsGroupFlag = "credentials-group" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + CredentialsGroupId string + Limit *int64 +} + +func NewCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "Lists all credentials for a credentials group", + Long: "Lists all credentials for a credentials group.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `List all credentials for a credentials group`, + "$ stackit object-storage credentials list --credentials-group xxx"), + examples.NewExample( + `List all credentials for a credentials group in JSON format`, + "$ stackit object-storage credentials list --credentials-group xxx --output-format json"), + examples.NewExample( + `List up to 10 credentials for a credentials group`, + "$ stackit object-storage credentials list --credentials-group 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 your 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(), credentialsGroupFlag, "Credentials Group ID") + + err := flags.MarkFlagsRequired(cmd, credentialsGroupFlag) + 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, credentialsGroupFlag), + 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("CREDENTIAL 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 + } +} diff --git a/internal/cmd/object-storage/credentials/list/list_test.go b/internal/cmd/object-storage/credentials/list/list_test.go new file mode 100644 index 00000000..de1825c2 --- /dev/null +++ b/internal/cmd/object-storage/credentials/list/list_test.go @@ -0,0 +1,206 @@ +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, + credentialsGroupFlag: 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) + 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, credentialsGroupFlag) + }), + isValid: false, + }, + { + description: "credentials group id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[credentialsGroupFlag] = "" + }), + isValid: false, + }, + { + description: "credentials group id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[credentialsGroupFlag] = "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) + } + }) + } +} From b9c262759acee3386f515f397ec5a14f92f540f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Go=CC=88kc=CC=A7e=20Go=CC=88k=20Klingel?= Date: Tue, 5 Mar 2024 17:42:18 +0100 Subject: [PATCH 16/48] Object storage credentials commands extension --- .../object-storage/credentials/credentials.go | 29 +++++++++++++++++++ internal/cmd/object-storage/object_storage.go | 2 ++ 2 files changed, 31 insertions(+) create mode 100644 internal/cmd/object-storage/credentials/credentials.go diff --git a/internal/cmd/object-storage/credentials/credentials.go b/internal/cmd/object-storage/credentials/credentials.go new file mode 100644 index 00000000..4914945e --- /dev/null +++ b/internal/cmd/object-storage/credentials/credentials.go @@ -0,0 +1,29 @@ +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()) +} diff --git a/internal/cmd/object-storage/object_storage.go b/internal/cmd/object-storage/object_storage.go index f3a8a8fc..c3a1a38f 100644 --- a/internal/cmd/object-storage/object_storage.go +++ b/internal/cmd/object-storage/object_storage.go @@ -2,6 +2,7 @@ package objectstorage import ( "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" "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/disable" "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/enable" @@ -28,4 +29,5 @@ func addSubcommands(cmd *cobra.Command) { cmd.AddCommand(disable.NewCmd()) cmd.AddCommand(enable.NewCmd()) cmd.AddCommand(credentialsGroup.NewCmd()) + cmd.AddCommand(credentials.NewCmd()) } From 4a56080101758010f1c704d988604cb3c16e9812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Go=CC=88kc=CC=A7e=20Go=CC=88k=20Klingel?= Date: Tue, 5 Mar 2024 17:55:59 +0100 Subject: [PATCH 17/48] Specify credentials group for the name variables --- .../credentials-group/create/create.go | 16 ++++++++-------- .../credentials-group/create/create_test.go | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/internal/cmd/object-storage/credentials-group/create/create.go b/internal/cmd/object-storage/credentials-group/create/create.go index 9bb3d785..f441b99d 100644 --- a/internal/cmd/object-storage/credentials-group/create/create.go +++ b/internal/cmd/object-storage/credentials-group/create/create.go @@ -18,12 +18,12 @@ import ( ) const ( - nameFlag = "name" + credentialsGroupNameFlag = "name" ) type inputModel struct { *globalflags.GlobalFlagModel - DisplayName string + CredentialsGroupName string } func NewCmd() *cobra.Command { @@ -51,7 +51,7 @@ func NewCmd() *cobra.Command { } 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) if err != nil { return err @@ -75,9 +75,9 @@ func NewCmd() *cobra.Command { } 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) } @@ -88,15 +88,15 @@ func parseInput(cmd *cobra.Command) (*inputModel, error) { } return &inputModel{ - GlobalFlagModel: globalFlags, - DisplayName: flags.FlagToStringValue(cmd, nameFlag), + GlobalFlagModel: globalFlags, + CredentialsGroupName: flags.FlagToStringValue(cmd, credentialsGroupNameFlag), }, nil } func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiCreateCredentialsGroupRequest { req := apiClient.CreateCredentialsGroup(ctx, model.ProjectId) req = req.CreateCredentialsGroupPayload(objectstorage.CreateCredentialsGroupPayload{ - DisplayName: utils.Ptr(model.DisplayName), + DisplayName: utils.Ptr(model.CredentialsGroupName), }) return req } diff --git a/internal/cmd/object-storage/credentials-group/create/create_test.go b/internal/cmd/object-storage/credentials-group/create/create_test.go index c48e8527..72e5d55e 100644 --- a/internal/cmd/object-storage/credentials-group/create/create_test.go +++ b/internal/cmd/object-storage/credentials-group/create/create_test.go @@ -20,12 +20,12 @@ type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") var testClient = &objectstorage.APIClient{} 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, } for _, mod := range mods { mod(flagValues) @@ -38,7 +38,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { GlobalFlagModel: &globalflags.GlobalFlagModel{ ProjectId: testProjectId, }, - DisplayName: testDisplayName, + CredentialsGroupName: testCredentialsGroupName, } for _, mod := range mods { mod(model) @@ -48,7 +48,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { func fixturePayload(mods ...func(payload *objectstorage.CreateCredentialsGroupPayload)) objectstorage.CreateCredentialsGroupPayload { payload := objectstorage.CreateCredentialsGroupPayload{ - DisplayName: utils.Ptr(testDisplayName), + DisplayName: utils.Ptr(testCredentialsGroupName), } for _, mod := range mods { mod(&payload) @@ -107,7 +107,7 @@ func TestParseInput(t *testing.T) { { description: "display name missing", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, nameFlag) + delete(flagValues, credentialsGroupNameFlag) }), isValid: false, }, From d71e007565c1cbdedc39ab22c64139f33d2c3fd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Go=CC=88kc=CC=A7e=20Go=CC=88k=20Klingel?= Date: Tue, 5 Mar 2024 18:22:59 +0100 Subject: [PATCH 18/48] Fix unit tests --- .../cmd/object-storage/credentials/create/create_test.go | 8 ++++---- .../cmd/object-storage/credentials/delete/delete_test.go | 1 + internal/cmd/object-storage/credentials/list/list_test.go | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/cmd/object-storage/credentials/create/create_test.go b/internal/cmd/object-storage/credentials/create/create_test.go index bd0a8ba5..567e13e9 100644 --- a/internal/cmd/object-storage/credentials/create/create_test.go +++ b/internal/cmd/object-storage/credentials/create/create_test.go @@ -21,13 +21,13 @@ type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") var testClient = &objectstorage.APIClient{} var testProjectId = uuid.NewString() -var testCredentialGroupId = 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, - credentialsGroupFlag: testCredentialGroupId, + credentialsGroupFlag: testCredentialsGroupId, expiresFlag: testExpirationDate, } for _, mod := range mods { @@ -47,7 +47,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { ProjectId: testProjectId, }, Expires: utils.Ptr(testExpirationDate), - CredentialsGroupId: testCredentialGroupId, + CredentialsGroupId: testCredentialsGroupId, } for _, mod := range mods { mod(model) @@ -58,7 +58,6 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { func fixturePayload(mods ...func(payload *objectstorage.CreateAccessKeyPayload)) objectstorage.CreateAccessKeyPayload { testExpirationDate, err := time.Parse(expirationTimeFormat, testExpirationDate) if err != nil { - //TODO check what can I return here return objectstorage.CreateAccessKeyPayload{} } payload := objectstorage.CreateAccessKeyPayload{ @@ -73,6 +72,7 @@ func fixturePayload(mods ...func(payload *objectstorage.CreateAccessKeyPayload)) 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) } diff --git a/internal/cmd/object-storage/credentials/delete/delete_test.go b/internal/cmd/object-storage/credentials/delete/delete_test.go index aa5047ef..da5b07ff 100644 --- a/internal/cmd/object-storage/credentials/delete/delete_test.go +++ b/internal/cmd/object-storage/credentials/delete/delete_test.go @@ -59,6 +59,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { func fixtureRequest(mods ...func(request *objectstorage.ApiDeleteAccessKeyRequest)) objectstorage.ApiDeleteAccessKeyRequest { request := testClient.DeleteAccessKey(testCtx, testProjectId, testCredentialId) + request = request.CredentialsGroup(testCredentialsGroupId) for _, mod := range mods { mod(&request) } diff --git a/internal/cmd/object-storage/credentials/list/list_test.go b/internal/cmd/object-storage/credentials/list/list_test.go index de1825c2..ba2d8b47 100644 --- a/internal/cmd/object-storage/credentials/list/list_test.go +++ b/internal/cmd/object-storage/credentials/list/list_test.go @@ -50,6 +50,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { func fixtureRequest(mods ...func(request *objectstorage.ApiListAccessKeysRequest)) objectstorage.ApiListAccessKeysRequest { request := testClient.ListAccessKeys(testCtx, testProjectId) + request = request.CredentialsGroup(testCredentialsGroupId) for _, mod := range mods { mod(&request) } From 337d93659793853f8ee05be76f7e3ee2bfe0ff68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Go=CC=88kc=CC=A7e=20Go=CC=88k=20Klingel?= Date: Wed, 6 Mar 2024 13:23:21 +0100 Subject: [PATCH 19/48] Add unit test for the utils --- .../object-storage/utils/utils_test.go | 149 +++++++++++++++++- 1 file changed, 147 insertions(+), 2 deletions(-) diff --git a/internal/pkg/services/object-storage/utils/utils_test.go b/internal/pkg/services/object-storage/utils/utils_test.go index f4f31808..44868289 100644 --- a/internal/pkg/services/object-storage/utils/utils_test.go +++ b/internal/pkg/services/object-storage/utils/utils_test.go @@ -2,22 +2,28 @@ package utils import ( "context" + "encoding/json" "fmt" - "testing" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "net/http" + "net/http/httptest" + "testing" "github.com/google/uuid" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/stackitcloud/stackit-sdk-go/core/config" "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" ) var ( testProjectId = uuid.NewString() testCredentialsGroupId = uuid.NewString() + testCredentialId = "credentialID" ) const ( testCredentialsGroupName = "testGroup" + testCredentialsName = "testCredential" ) type objectStorageClientMocked struct { @@ -148,3 +154,142 @@ func TestGetCredentialsGroupName(t *testing.T) { }) } } + +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(testCredentialId), + 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(testCredentialId), + 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(testCredentialId), + DisplayName: nil, + }, + }, + }, + isValid: false, + }, + { + description: "empty credentials name", + listAccessKeysResp: &objectstorage.ListAccessKeysResponse{ + AccessKeys: &[]objectstorage.AccessKey{ + { + KeyId: utils.Ptr(testCredentialId), + 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, testCredentialId) + + 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) + } + }) + } +} From 74f9d141fd53f420ac6ff51e72f913a7d878af03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Go=CC=88kc=CC=A7e=20Go=CC=88k=20Klingel?= Date: Wed, 6 Mar 2024 14:15:22 +0100 Subject: [PATCH 20/48] Fix linting issues - false positive --- internal/cmd/object-storage/credentials/delete/delete.go | 2 +- internal/pkg/services/object-storage/utils/utils_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/cmd/object-storage/credentials/delete/delete.go b/internal/cmd/object-storage/credentials/delete/delete.go index a3cb54d7..cf91c0de 100644 --- a/internal/cmd/object-storage/credentials/delete/delete.go +++ b/internal/cmd/object-storage/credentials/delete/delete.go @@ -16,7 +16,7 @@ import ( ) const ( - credentialIdArg = "CREDENTIAL_ID" + credentialIdArg = "CREDENTIAL_ID" //nolint:gosec // linter false positive credentialsGroupFlag = "credentials-group" ) diff --git a/internal/pkg/services/object-storage/utils/utils_test.go b/internal/pkg/services/object-storage/utils/utils_test.go index 44868289..41563295 100644 --- a/internal/pkg/services/object-storage/utils/utils_test.go +++ b/internal/pkg/services/object-storage/utils/utils_test.go @@ -18,7 +18,7 @@ import ( var ( testProjectId = uuid.NewString() testCredentialsGroupId = uuid.NewString() - testCredentialId = "credentialID" + testCredentialId = "credentialID" //nolint:gosec // linter false positive ) const ( From 691f8a9d05c891532dc0a97ca10c814488bbdd54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Go=CC=88kc=CC=A7e=20Go=CC=88k=20Klingel?= Date: Wed, 6 Mar 2024 15:01:14 +0100 Subject: [PATCH 21/48] Fix linting issues --- internal/cmd/object-storage/credentials/create/create.go | 3 ++- internal/cmd/object-storage/credentials/delete/delete.go | 1 + internal/cmd/object-storage/credentials/list/list.go | 1 + internal/pkg/services/object-storage/utils/utils.go | 2 +- internal/pkg/services/object-storage/utils/utils_test.go | 2 +- 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/internal/cmd/object-storage/credentials/create/create.go b/internal/cmd/object-storage/credentials/create/create.go index 46b25927..d8a37f08 100644 --- a/internal/cmd/object-storage/credentials/create/create.go +++ b/internal/cmd/object-storage/credentials/create/create.go @@ -3,9 +3,10 @@ package create import ( "context" "fmt" - objectStorageUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/utils" "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" diff --git a/internal/cmd/object-storage/credentials/delete/delete.go b/internal/cmd/object-storage/credentials/delete/delete.go index cf91c0de..26805387 100644 --- a/internal/cmd/object-storage/credentials/delete/delete.go +++ b/internal/cmd/object-storage/credentials/delete/delete.go @@ -3,6 +3,7 @@ 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" diff --git a/internal/cmd/object-storage/credentials/list/list.go b/internal/cmd/object-storage/credentials/list/list.go index 005b6eb6..011f72ef 100644 --- a/internal/cmd/object-storage/credentials/list/list.go +++ b/internal/cmd/object-storage/credentials/list/list.go @@ -4,6 +4,7 @@ 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" diff --git a/internal/pkg/services/object-storage/utils/utils.go b/internal/pkg/services/object-storage/utils/utils.go index 38e929c0..b4ea82a5 100644 --- a/internal/pkg/services/object-storage/utils/utils.go +++ b/internal/pkg/services/object-storage/utils/utils.go @@ -53,4 +53,4 @@ func GetCredentialsName(ctx context.Context, apiClient ObjectStorageClient, proj } return "", fmt.Errorf("could not find Object Storage credential name") -} \ No newline at end of file +} diff --git a/internal/pkg/services/object-storage/utils/utils_test.go b/internal/pkg/services/object-storage/utils/utils_test.go index ccbe5a95..41563295 100644 --- a/internal/pkg/services/object-storage/utils/utils_test.go +++ b/internal/pkg/services/object-storage/utils/utils_test.go @@ -292,4 +292,4 @@ func TestGetCredentialsName(t *testing.T) { } }) } -} \ No newline at end of file +} From 701361cedc4e7e15e1e3f91b9128fd64ce4d2ca0 Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Thu, 7 Mar 2024 08:03:14 +0100 Subject: [PATCH 22/48] Update create command description Co-authored-by: Vicente Pinto --- internal/cmd/object-storage/credentials/create/create.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials/create/create.go b/internal/cmd/object-storage/credentials/create/create.go index d8a37f08..1e933ec9 100644 --- a/internal/cmd/object-storage/credentials/create/create.go +++ b/internal/cmd/object-storage/credentials/create/create.go @@ -34,7 +34,7 @@ type inputModel struct { func NewCmd() *cobra.Command { cmd := &cobra.Command{ Use: "create", - Short: "Creates credentials for a credentials group", + Short: "Creates credentials for an Object Storage credentials group", Long: "Creates credentials for a credentials group.", Args: args.NoArgs, Example: examples.Build( From b0dea407e180012515847b4405f2c25752d08cb7 Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Thu, 7 Mar 2024 08:03:58 +0100 Subject: [PATCH 23/48] Update create command description Co-authored-by: Vicente Pinto --- internal/cmd/object-storage/credentials/create/create.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials/create/create.go b/internal/cmd/object-storage/credentials/create/create.go index 1e933ec9..80225289 100644 --- a/internal/cmd/object-storage/credentials/create/create.go +++ b/internal/cmd/object-storage/credentials/create/create.go @@ -35,7 +35,7 @@ func NewCmd() *cobra.Command { cmd := &cobra.Command{ Use: "create", Short: "Creates credentials for an Object Storage credentials group", - Long: "Creates credentials for a credentials group.", + Long: "Creates credentials for an Object Storage credentials group.", Args: args.NoArgs, Example: examples.Build( examples.NewExample( From 0495a2f894c539c62c8bf99909dcbe9a7307c4d0 Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Thu, 7 Mar 2024 08:04:42 +0100 Subject: [PATCH 24/48] Update create command description Co-authored-by: Vicente Pinto --- internal/cmd/object-storage/credentials/create/create.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials/create/create.go b/internal/cmd/object-storage/credentials/create/create.go index 80225289..7a3a5a2b 100644 --- a/internal/cmd/object-storage/credentials/create/create.go +++ b/internal/cmd/object-storage/credentials/create/create.go @@ -39,7 +39,7 @@ func NewCmd() *cobra.Command { Args: args.NoArgs, Example: examples.Build( examples.NewExample( - `Create credentials for a credentials group`, + `Create credentials for a credentials group with ID xxx`, "$ stackit object-storage credentials create --credentials-group xxx"), examples.NewExample( `Create credentials for a credentials group, with a specific expiration date`, From b7b151685cd4253e8d4e42842068aeba7607f684 Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Thu, 7 Mar 2024 08:06:23 +0100 Subject: [PATCH 25/48] Update create command example description Co-authored-by: Vicente Pinto --- internal/cmd/object-storage/credentials/create/create.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials/create/create.go b/internal/cmd/object-storage/credentials/create/create.go index 7a3a5a2b..73d87643 100644 --- a/internal/cmd/object-storage/credentials/create/create.go +++ b/internal/cmd/object-storage/credentials/create/create.go @@ -42,7 +42,7 @@ func NewCmd() *cobra.Command { `Create credentials for a credentials group with ID xxx`, "$ stackit object-storage credentials create --credentials-group xxx"), examples.NewExample( - `Create credentials for a credentials group, with a specific expiration date`, + `Create credentials for a credentials group with ID xxx, including a specific expiration date`, "$ stackit object-storage credentials create --credentials-group xxx --expires 2024-03-06T00:00:00.000Z"), ), RunE: func(cmd *cobra.Command, args []string) error { From dc65bd1158ca56a92309224e8a5e6038b3c0be9b Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Thu, 7 Mar 2024 08:07:30 +0100 Subject: [PATCH 26/48] Update create command output Co-authored-by: Vicente Pinto --- internal/cmd/object-storage/credentials/create/create.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials/create/create.go b/internal/cmd/object-storage/credentials/create/create.go index 73d87643..7a720219 100644 --- a/internal/cmd/object-storage/credentials/create/create.go +++ b/internal/cmd/object-storage/credentials/create/create.go @@ -78,7 +78,7 @@ func NewCmd() *cobra.Command { return fmt.Errorf("create Object Storage credentials: %w", err) } - cmd.Printf("Created credentials in group %q. Credential ID: %s\n\n", credentialsGroupLabel, *resp.KeyId) + 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) From ea0d7e7c912e23ab84ed7b33a39d87fd1598c71a Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Thu, 7 Mar 2024 08:08:29 +0100 Subject: [PATCH 27/48] Update create command credential Co-authored-by: Vicente Pinto --- internal/cmd/object-storage/credentials/delete/delete.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials/delete/delete.go b/internal/cmd/object-storage/credentials/delete/delete.go index 26805387..b2ce9b8e 100644 --- a/internal/cmd/object-storage/credentials/delete/delete.go +++ b/internal/cmd/object-storage/credentials/delete/delete.go @@ -17,7 +17,7 @@ import ( ) const ( - credentialIdArg = "CREDENTIAL_ID" //nolint:gosec // linter false positive + credentialIdArg = "CREDENTIALS_ID" //nolint:gosec // linter false positive credentialsGroupFlag = "credentials-group" ) From afe6a05ca19b63060ae87dd3f06df22ad1352903 Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Thu, 7 Mar 2024 08:09:37 +0100 Subject: [PATCH 28/48] Update delete command short description Co-authored-by: Vicente Pinto --- internal/cmd/object-storage/credentials/delete/delete.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials/delete/delete.go b/internal/cmd/object-storage/credentials/delete/delete.go index b2ce9b8e..209ef1fa 100644 --- a/internal/cmd/object-storage/credentials/delete/delete.go +++ b/internal/cmd/object-storage/credentials/delete/delete.go @@ -30,7 +30,7 @@ type inputModel struct { func NewCmd() *cobra.Command { cmd := &cobra.Command{ Use: fmt.Sprintf("delete %s", credentialIdArg), - Short: "Deletes a credential of a credentials group", + Short: "Deletes credentials of an Object Storage credentials group", Long: "Deletes a credential of a credentials group.", Args: args.SingleArg(credentialIdArg, nil), Example: examples.Build( From edbd250b9cdc9e4ec2b1f5c0860be55f03e38a0c Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Thu, 7 Mar 2024 08:10:18 +0100 Subject: [PATCH 29/48] Update delete command long description Co-authored-by: Vicente Pinto --- internal/cmd/object-storage/credentials/delete/delete.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials/delete/delete.go b/internal/cmd/object-storage/credentials/delete/delete.go index 209ef1fa..5c665ff1 100644 --- a/internal/cmd/object-storage/credentials/delete/delete.go +++ b/internal/cmd/object-storage/credentials/delete/delete.go @@ -31,7 +31,7 @@ func NewCmd() *cobra.Command { cmd := &cobra.Command{ Use: fmt.Sprintf("delete %s", credentialIdArg), Short: "Deletes credentials of an Object Storage credentials group", - Long: "Deletes a credential of a credentials group.", + Long: "Deletes credentials of an Object Storage credentials group", Args: args.SingleArg(credentialIdArg, nil), Example: examples.Build( examples.NewExample( From 656b8d72e23f39aa76adafa0c5076a15ed90d0a9 Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Thu, 7 Mar 2024 08:11:39 +0100 Subject: [PATCH 30/48] Update delete command promt message Co-authored-by: Vicente Pinto --- internal/cmd/object-storage/credentials/delete/delete.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials/delete/delete.go b/internal/cmd/object-storage/credentials/delete/delete.go index 5c665ff1..8c977ffa 100644 --- a/internal/cmd/object-storage/credentials/delete/delete.go +++ b/internal/cmd/object-storage/credentials/delete/delete.go @@ -62,7 +62,7 @@ func NewCmd() *cobra.Command { } if !model.AssumeYes { - prompt := fmt.Sprintf("Are you sure you want to delete credentials %s of credentials group %q? (This cannot be undone)", credentialsLabel, credentialsGroupLabel) + 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 From ca9b6016e76f98b6c7728eced3e19d8ab71e4ce2 Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Thu, 7 Mar 2024 08:12:20 +0100 Subject: [PATCH 31/48] Update delete command output Co-authored-by: Vicente Pinto --- internal/cmd/object-storage/credentials/delete/delete.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials/delete/delete.go b/internal/cmd/object-storage/credentials/delete/delete.go index 8c977ffa..835a4f57 100644 --- a/internal/cmd/object-storage/credentials/delete/delete.go +++ b/internal/cmd/object-storage/credentials/delete/delete.go @@ -76,7 +76,7 @@ func NewCmd() *cobra.Command { return fmt.Errorf("delete Object Storage credentials: %w", err) } - cmd.Printf("Deleted credentials %s of credentials group %q\n", credentialsLabel, credentialsGroupLabel) + cmd.Printf("Deleted credentials %q of credentials group %q\n", credentialsLabel, credentialsGroupLabel) return nil }, } From 65b59669c8209a3c849d0e47400cf445e3765b9c Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Thu, 7 Mar 2024 08:13:02 +0100 Subject: [PATCH 32/48] Update list command short description Co-authored-by: Vicente Pinto --- internal/cmd/object-storage/credentials/list/list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials/list/list.go b/internal/cmd/object-storage/credentials/list/list.go index 011f72ef..caf3755a 100644 --- a/internal/cmd/object-storage/credentials/list/list.go +++ b/internal/cmd/object-storage/credentials/list/list.go @@ -33,7 +33,7 @@ type inputModel struct { func NewCmd() *cobra.Command { cmd := &cobra.Command{ Use: "list", - Short: "Lists all credentials for a credentials group", + Short: "Lists all credentials for an Object Storage credentials group", Long: "Lists all credentials for a credentials group.", Args: args.NoArgs, Example: examples.Build( From b0c0f895072b08d89b683dda4f8b1f18c840742c Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Thu, 7 Mar 2024 08:13:37 +0100 Subject: [PATCH 33/48] Update list command long description Co-authored-by: Vicente Pinto --- internal/cmd/object-storage/credentials/list/list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials/list/list.go b/internal/cmd/object-storage/credentials/list/list.go index caf3755a..8a7f2e04 100644 --- a/internal/cmd/object-storage/credentials/list/list.go +++ b/internal/cmd/object-storage/credentials/list/list.go @@ -34,7 +34,7 @@ func NewCmd() *cobra.Command { cmd := &cobra.Command{ Use: "list", Short: "Lists all credentials for an Object Storage credentials group", - Long: "Lists all credentials for a credentials group.", + Long: "Lists all credentials for an Object Storage credentials group.", Args: args.NoArgs, Example: examples.Build( examples.NewExample( From 7af91075a3bbc4ab5036cbdef8f2bdcf6c7ca729 Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Thu, 7 Mar 2024 08:14:29 +0100 Subject: [PATCH 34/48] Update list command example description Co-authored-by: Vicente Pinto --- internal/cmd/object-storage/credentials/list/list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials/list/list.go b/internal/cmd/object-storage/credentials/list/list.go index 8a7f2e04..2f3a37da 100644 --- a/internal/cmd/object-storage/credentials/list/list.go +++ b/internal/cmd/object-storage/credentials/list/list.go @@ -38,7 +38,7 @@ func NewCmd() *cobra.Command { Args: args.NoArgs, Example: examples.Build( examples.NewExample( - `List all credentials for a credentials group`, + `List all credentials for a credentials group with ID "xxx"`, "$ stackit object-storage credentials list --credentials-group xxx"), examples.NewExample( `List all credentials for a credentials group in JSON format`, From 3f6f1ba1432a6a2cdf3af910ffeef245a2cfc1ab Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Thu, 7 Mar 2024 08:14:55 +0100 Subject: [PATCH 35/48] Update list command example description Co-authored-by: Vicente Pinto --- internal/cmd/object-storage/credentials/list/list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials/list/list.go b/internal/cmd/object-storage/credentials/list/list.go index 2f3a37da..28fe41b6 100644 --- a/internal/cmd/object-storage/credentials/list/list.go +++ b/internal/cmd/object-storage/credentials/list/list.go @@ -41,7 +41,7 @@ func NewCmd() *cobra.Command { `List all credentials for a credentials group with ID "xxx"`, "$ stackit object-storage credentials list --credentials-group xxx"), examples.NewExample( - `List all credentials for a credentials group in JSON format`, + `List all credentials for a credentials group with ID "xxx" in JSON format`, "$ stackit object-storage credentials list --credentials-group xxx --output-format json"), examples.NewExample( `List up to 10 credentials for a credentials group`, From b6272ee66a64cfec8ef32dbf1349724bdad5986d Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Thu, 7 Mar 2024 08:15:45 +0100 Subject: [PATCH 36/48] Update list command example description Co-authored-by: Vicente Pinto --- internal/cmd/object-storage/credentials/list/list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials/list/list.go b/internal/cmd/object-storage/credentials/list/list.go index 28fe41b6..bce67bb4 100644 --- a/internal/cmd/object-storage/credentials/list/list.go +++ b/internal/cmd/object-storage/credentials/list/list.go @@ -44,7 +44,7 @@ func NewCmd() *cobra.Command { `List all credentials for a credentials group with ID "xxx" in JSON format`, "$ stackit object-storage credentials list --credentials-group xxx --output-format json"), examples.NewExample( - `List up to 10 credentials for a credentials group`, + `List up to 10 credentials for a credentials group with ID "xxx"`, "$ stackit object-storage credentials list --credentials-group xxx --limit 10"), ), RunE: func(cmd *cobra.Command, args []string) error { From 933a7bc1185f8df52652a3f085889981125d07e3 Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Thu, 7 Mar 2024 08:16:11 +0100 Subject: [PATCH 37/48] Update list command message Co-authored-by: Vicente Pinto --- internal/cmd/object-storage/credentials/list/list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials/list/list.go b/internal/cmd/object-storage/credentials/list/list.go index bce67bb4..fa371f61 100644 --- a/internal/cmd/object-storage/credentials/list/list.go +++ b/internal/cmd/object-storage/credentials/list/list.go @@ -73,7 +73,7 @@ func NewCmd() *cobra.Command { credentialsGroupLabel = model.CredentialsGroupId } - cmd.Printf("No credentials found for your credentials group %q\n", credentialsGroupLabel) + cmd.Printf("No credentials found for credentials group %q\n", credentialsGroupLabel) return nil } From b1721db5ce1842b09ef25ee7578663c4fdea24ba Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Thu, 7 Mar 2024 08:16:28 +0100 Subject: [PATCH 38/48] Update list command table header Co-authored-by: Vicente Pinto --- internal/cmd/object-storage/credentials/list/list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials/list/list.go b/internal/cmd/object-storage/credentials/list/list.go index fa371f61..b5315a52 100644 --- a/internal/cmd/object-storage/credentials/list/list.go +++ b/internal/cmd/object-storage/credentials/list/list.go @@ -135,7 +135,7 @@ func outputResult(cmd *cobra.Command, outputFormat string, credentials []objects return nil default: table := tables.NewTable() - table.SetHeader("CREDENTIAL ID", "ACCESS KEY ID", "EXPIRES AT") + table.SetHeader("CREDENTIALS ID", "ACCESS KEY ID", "EXPIRES AT") for i := range credentials { c := credentials[i] From fae599c829d7a19b0557150e88e712f9d7d1be6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Go=CC=88kc=CC=A7e=20Go=CC=88k=20Klingel?= Date: Thu, 7 Mar 2024 08:18:40 +0100 Subject: [PATCH 39/48] Update flag in create command --- .../object-storage/credentials/create/create.go | 16 ++++++++-------- .../credentials/create/create_test.go | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/internal/cmd/object-storage/credentials/create/create.go b/internal/cmd/object-storage/credentials/create/create.go index 7a720219..c8b8cc53 100644 --- a/internal/cmd/object-storage/credentials/create/create.go +++ b/internal/cmd/object-storage/credentials/create/create.go @@ -19,9 +19,9 @@ import ( ) const ( - expiresFlag = "expires" - credentialsGroupFlag = "credentials-group" - expirationTimeFormat = time.RFC3339 + expiresFlag = "expires" + credentialsGroupIdFlag = "credentials-group-id" + expirationTimeFormat = time.RFC3339 ) type inputModel struct { @@ -40,10 +40,10 @@ func NewCmd() *cobra.Command { Example: examples.Build( examples.NewExample( `Create credentials for a credentials group with ID xxx`, - "$ stackit object-storage credentials create --credentials-group 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 xxx --expires 2024-03-06T00:00:00.000Z"), + "$ stackit object-storage credentials create --credentials-group-id xxx --expires 2024-03-06T00:00:00.000Z"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -91,9 +91,9 @@ func NewCmd() *cobra.Command { func configureFlags(cmd *cobra.Command) { cmd.Flags().String(expiresFlag, "", "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(), credentialsGroupFlag, "Credentials Group ID") + cmd.Flags().Var(flags.UUIDFlag(), credentialsGroupIdFlag, "Credentials Group ID") - err := flags.MarkFlagsRequired(cmd, credentialsGroupFlag) + err := flags.MarkFlagsRequired(cmd, credentialsGroupIdFlag) cobra.CheckErr(err) } @@ -114,7 +114,7 @@ func parseInput(cmd *cobra.Command) (*inputModel, error) { return &inputModel{ GlobalFlagModel: globalFlags, Expires: expires, - CredentialsGroupId: flags.FlagToStringValue(cmd, credentialsGroupFlag), + CredentialsGroupId: flags.FlagToStringValue(cmd, credentialsGroupIdFlag), }, nil } diff --git a/internal/cmd/object-storage/credentials/create/create_test.go b/internal/cmd/object-storage/credentials/create/create_test.go index 567e13e9..17102c27 100644 --- a/internal/cmd/object-storage/credentials/create/create_test.go +++ b/internal/cmd/object-storage/credentials/create/create_test.go @@ -26,9 +26,9 @@ var testExpirationDate = "2024-01-01T00:00:00Z" func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { flagValues := map[string]string{ - projectIdFlag: testProjectId, - credentialsGroupFlag: testCredentialsGroupId, - expiresFlag: testExpirationDate, + projectIdFlag: testProjectId, + credentialsGroupIdFlag: testCredentialsGroupId, + expiresFlag: testExpirationDate, } for _, mod := range mods { mod(flagValues) @@ -121,21 +121,21 @@ func TestParseInput(t *testing.T) { { description: "credentials group id missing", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, credentialsGroupFlag) + delete(flagValues, credentialsGroupIdFlag) }), isValid: false, }, { description: "credentials group id invalid 1", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[credentialsGroupFlag] = "" + flagValues[credentialsGroupIdFlag] = "" }), isValid: false, }, { description: "credentials group id invalid 2", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[credentialsGroupFlag] = "invalid-uuid" + flagValues[credentialsGroupIdFlag] = "invalid-uuid" }), isValid: false, }, From e7b76473434852bc942458aa16eba1bfe58808ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Go=CC=88kc=CC=A7e=20Go=CC=88k=20Klingel?= Date: Thu, 7 Mar 2024 08:29:59 +0100 Subject: [PATCH 40/48] Update expire date flag in create command --- .../credentials/create/create.go | 18 +++++++++--------- .../credentials/create/create_test.go | 14 +++++++------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/internal/cmd/object-storage/credentials/create/create.go b/internal/cmd/object-storage/credentials/create/create.go index c8b8cc53..93fc4525 100644 --- a/internal/cmd/object-storage/credentials/create/create.go +++ b/internal/cmd/object-storage/credentials/create/create.go @@ -19,14 +19,14 @@ import ( ) const ( - expiresFlag = "expires" + expireDateFlag = "expire-date" credentialsGroupIdFlag = "credentials-group-id" expirationTimeFormat = time.RFC3339 ) type inputModel struct { *globalflags.GlobalFlagModel - Expires *time.Time + ExpireDate *time.Time CredentialsGroupId string HidePassword bool } @@ -35,7 +35,7 @@ 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.", + Long: "Creates credentials for an Object Storage credentials group. The credentials are only displayed upon creation, and it will not be retrievable later.", Args: args.NoArgs, Example: examples.Build( examples.NewExample( @@ -43,7 +43,7 @@ func NewCmd() *cobra.Command { "$ 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 --expires 2024-03-06T00:00:00.000Z"), + "$ 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() @@ -90,7 +90,7 @@ func NewCmd() *cobra.Command { } func configureFlags(cmd *cobra.Command) { - cmd.Flags().String(expiresFlag, "", "Expiration date for the credentials, in a date-time with the RFC3339 layout format, e.g. 2024-01-01T00:00:00Z") + 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) @@ -103,17 +103,17 @@ func parseInput(cmd *cobra.Command) (*inputModel, error) { return nil, &errors.ProjectIdError{} } - expires, err := flags.FlagToDateTimePointer(cmd, expiresFlag, expirationTimeFormat) + expireDate, err := flags.FlagToDateTimePointer(cmd, expireDateFlag, expirationTimeFormat) if err != nil { return nil, &errors.FlagValidationError{ - Flag: expiresFlag, + Flag: expireDateFlag, Details: err.Error(), } } return &inputModel{ GlobalFlagModel: globalFlags, - Expires: expires, + ExpireDate: expireDate, CredentialsGroupId: flags.FlagToStringValue(cmd, credentialsGroupIdFlag), }, nil } @@ -122,7 +122,7 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstora req := apiClient.CreateAccessKey(ctx, model.ProjectId) req = req.CredentialsGroup(model.CredentialsGroupId) req = req.CreateAccessKeyPayload(objectstorage.CreateAccessKeyPayload{ - Expires: model.Expires, + Expires: model.ExpireDate, }) return req } diff --git a/internal/cmd/object-storage/credentials/create/create_test.go b/internal/cmd/object-storage/credentials/create/create_test.go index 17102c27..c9c5e1b8 100644 --- a/internal/cmd/object-storage/credentials/create/create_test.go +++ b/internal/cmd/object-storage/credentials/create/create_test.go @@ -28,7 +28,7 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st flagValues := map[string]string{ projectIdFlag: testProjectId, credentialsGroupIdFlag: testCredentialsGroupId, - expiresFlag: testExpirationDate, + expireDateFlag: testExpirationDate, } for _, mod := range mods { mod(flagValues) @@ -46,7 +46,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { GlobalFlagModel: &globalflags.GlobalFlagModel{ ProjectId: testProjectId, }, - Expires: utils.Ptr(testExpirationDate), + ExpireDate: utils.Ptr(testExpirationDate), CredentialsGroupId: testCredentialsGroupId, } for _, mod := range mods { @@ -142,31 +142,31 @@ func TestParseInput(t *testing.T) { { description: "expiration date is missing", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, expiresFlag) + delete(flagValues, expireDateFlag) }), isValid: true, expectedModel: fixtureInputModel(func(model *inputModel) { - model.Expires = nil + model.ExpireDate = nil }), }, { description: "expiration date is empty", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[expiresFlag] = "" + flagValues[expireDateFlag] = "" }), isValid: false, }, { description: "expiration date is invalid", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[expiresFlag] = "test" + flagValues[expireDateFlag] = "test" }), isValid: false, }, { description: "expiration date is invalid 2", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[expiresFlag] = "11:00 12/12/2024" + flagValues[expireDateFlag] = "11:00 12/12/2024" }), isValid: false, }, From 6afcced7c156e5ae56afd78525350c978a6da1b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Go=CC=88kc=CC=A7e=20Go=CC=88k=20Klingel?= Date: Thu, 7 Mar 2024 08:42:57 +0100 Subject: [PATCH 41/48] Print expire date in create output --- internal/cmd/object-storage/credentials/create/create.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/cmd/object-storage/credentials/create/create.go b/internal/cmd/object-storage/credentials/create/create.go index 93fc4525..afdb30a1 100644 --- a/internal/cmd/object-storage/credentials/create/create.go +++ b/internal/cmd/object-storage/credentials/create/create.go @@ -78,9 +78,15 @@ func NewCmd() *cobra.Command { 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 }, From 8ffb0d99de330d5b45671786f97cf61b1779177c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Go=CC=88kc=CC=A7e=20Go=CC=88k=20Klingel?= Date: Thu, 7 Mar 2024 09:41:11 +0100 Subject: [PATCH 42/48] Fixes for PR comments --- .../credentials/delete/delete.go | 28 +++++++++---------- .../credentials/delete/delete_test.go | 18 ++++++------ .../object-storage/credentials/list/list.go | 16 +++++------ .../credentials/list/list_test.go | 12 ++++---- .../services/object-storage/utils/utils.go | 2 +- .../object-storage/utils/utils_test.go | 12 ++++---- 6 files changed, 44 insertions(+), 44 deletions(-) diff --git a/internal/cmd/object-storage/credentials/delete/delete.go b/internal/cmd/object-storage/credentials/delete/delete.go index 835a4f57..3a029e0f 100644 --- a/internal/cmd/object-storage/credentials/delete/delete.go +++ b/internal/cmd/object-storage/credentials/delete/delete.go @@ -17,26 +17,26 @@ import ( ) const ( - credentialIdArg = "CREDENTIALS_ID" //nolint:gosec // linter false positive - credentialsGroupFlag = "credentials-group" + credentialsIdArg = "CREDENTIALS_ID" //nolint:gosec // linter false positive + credentialsGroupIdFlag = "credentials-group-id" ) type inputModel struct { *globalflags.GlobalFlagModel CredentialsGroupId string - CredentialId string + CredentialsId string } func NewCmd() *cobra.Command { cmd := &cobra.Command{ - Use: fmt.Sprintf("delete %s", credentialIdArg), + 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(credentialIdArg, nil), + 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 yyy"), + "$ stackit object-storage credentials delete xxx --credentials-group-id yyy"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -56,9 +56,9 @@ func NewCmd() *cobra.Command { credentialsGroupLabel = model.CredentialsGroupId } - credentialsLabel, err := objectStorageUtils.GetCredentialsName(ctx, apiClient, model.ProjectId, model.CredentialsGroupId, model.CredentialId) + credentialsLabel, err := objectStorageUtils.GetCredentialsName(ctx, apiClient, model.ProjectId, model.CredentialsGroupId, model.CredentialsId) if err != nil { - credentialsLabel = model.CredentialId + credentialsLabel = model.CredentialsId } if !model.AssumeYes { @@ -85,14 +85,14 @@ func NewCmd() *cobra.Command { } func configureFlags(cmd *cobra.Command) { - cmd.Flags().Var(flags.UUIDFlag(), credentialsGroupFlag, "Credentials Group ID") + cmd.Flags().Var(flags.UUIDFlag(), credentialsGroupIdFlag, "Credentials Group ID") - err := flags.MarkFlagsRequired(cmd, credentialsGroupFlag) + err := flags.MarkFlagsRequired(cmd, credentialsGroupIdFlag) cobra.CheckErr(err) } func parseInput(cmd *cobra.Command, inputArgs []string) (*inputModel, error) { - credentialId := inputArgs[0] + credentialsId := inputArgs[0] globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { @@ -101,13 +101,13 @@ func parseInput(cmd *cobra.Command, inputArgs []string) (*inputModel, error) { return &inputModel{ GlobalFlagModel: globalFlags, - CredentialsGroupId: flags.FlagToStringValue(cmd, credentialsGroupFlag), - CredentialId: credentialId, + 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.CredentialId) + req := apiClient.DeleteAccessKey(ctx, model.ProjectId, model.CredentialsId) req = req.CredentialsGroup(model.CredentialsGroupId) return req } diff --git a/internal/cmd/object-storage/credentials/delete/delete_test.go b/internal/cmd/object-storage/credentials/delete/delete_test.go index da5b07ff..c160315a 100644 --- a/internal/cmd/object-storage/credentials/delete/delete_test.go +++ b/internal/cmd/object-storage/credentials/delete/delete_test.go @@ -20,11 +20,11 @@ var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") var testClient = &objectstorage.APIClient{} var testProjectId = uuid.NewString() var testCredentialsGroupId = uuid.NewString() -var testCredentialId = "keyID" +var testCredentialsId = "keyID" func fixtureArgValues(mods ...func(argValues []string)) []string { argValues := []string{ - testCredentialId, + testCredentialsId, } for _, mod := range mods { mod(argValues) @@ -34,8 +34,8 @@ func fixtureArgValues(mods ...func(argValues []string)) []string { func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { flagValues := map[string]string{ - projectIdFlag: testProjectId, - credentialsGroupFlag: testCredentialsGroupId, + projectIdFlag: testProjectId, + credentialsGroupIdFlag: testCredentialsGroupId, } for _, mod := range mods { mod(flagValues) @@ -49,7 +49,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { ProjectId: testProjectId, }, CredentialsGroupId: testCredentialsGroupId, - CredentialId: testCredentialId, + CredentialsId: testCredentialsId, } for _, mod := range mods { mod(model) @@ -58,7 +58,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *objectstorage.ApiDeleteAccessKeyRequest)) objectstorage.ApiDeleteAccessKeyRequest { - request := testClient.DeleteAccessKey(testCtx, testProjectId, testCredentialId) + request := testClient.DeleteAccessKey(testCtx, testProjectId, testCredentialsId) request = request.CredentialsGroup(testCredentialsGroupId) for _, mod := range mods { mod(&request) @@ -126,21 +126,21 @@ func TestParseInput(t *testing.T) { { description: "credentials group id missing", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, credentialsGroupFlag) + delete(flagValues, credentialsGroupIdFlag) }), isValid: false, }, { description: "credentials group id invalid 1", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[credentialsGroupFlag] = "" + flagValues[credentialsGroupIdFlag] = "" }), isValid: false, }, { description: "credentials group id invalid 2", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[credentialsGroupFlag] = "invalid-uuid" + flagValues[credentialsGroupIdFlag] = "invalid-uuid" }), isValid: false, }, diff --git a/internal/cmd/object-storage/credentials/list/list.go b/internal/cmd/object-storage/credentials/list/list.go index b5315a52..f982bd44 100644 --- a/internal/cmd/object-storage/credentials/list/list.go +++ b/internal/cmd/object-storage/credentials/list/list.go @@ -20,8 +20,8 @@ import ( ) const ( - limitFlag = "limit" - credentialsGroupFlag = "credentials-group" + limitFlag = "limit" + credentialsGroupIdFlag = "credentials-group-id" ) type inputModel struct { @@ -39,13 +39,13 @@ func NewCmd() *cobra.Command { Example: examples.Build( examples.NewExample( `List all credentials for a credentials group with ID "xxx"`, - "$ stackit object-storage credentials list --credentials-group 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 xxx --output-format json"), + "$ 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 xxx --limit 10"), + "$ stackit object-storage credentials list --credentials-group-id xxx --limit 10"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -90,9 +90,9 @@ func NewCmd() *cobra.Command { func configureFlags(cmd *cobra.Command) { cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") - cmd.Flags().Var(flags.UUIDFlag(), credentialsGroupFlag, "Credentials Group ID") + cmd.Flags().Var(flags.UUIDFlag(), credentialsGroupIdFlag, "Credentials Group ID") - err := flags.MarkFlagsRequired(cmd, credentialsGroupFlag) + err := flags.MarkFlagsRequired(cmd, credentialsGroupIdFlag) cobra.CheckErr(err) } @@ -112,7 +112,7 @@ func parseInput(cmd *cobra.Command) (*inputModel, error) { return &inputModel{ GlobalFlagModel: globalFlags, - CredentialsGroupId: flags.FlagToStringValue(cmd, credentialsGroupFlag), + CredentialsGroupId: flags.FlagToStringValue(cmd, credentialsGroupIdFlag), Limit: limit, }, nil } diff --git a/internal/cmd/object-storage/credentials/list/list_test.go b/internal/cmd/object-storage/credentials/list/list_test.go index ba2d8b47..fa828642 100644 --- a/internal/cmd/object-storage/credentials/list/list_test.go +++ b/internal/cmd/object-storage/credentials/list/list_test.go @@ -24,9 +24,9 @@ var testCredentialsGroupId = uuid.NewString() func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { flagValues := map[string]string{ - projectIdFlag: testProjectId, - credentialsGroupFlag: testCredentialsGroupId, - limitFlag: "10", + projectIdFlag: testProjectId, + credentialsGroupIdFlag: testCredentialsGroupId, + limitFlag: "10", } for _, mod := range mods { mod(flagValues) @@ -99,21 +99,21 @@ func TestParseInput(t *testing.T) { { description: "credentials group id missing", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, credentialsGroupFlag) + delete(flagValues, credentialsGroupIdFlag) }), isValid: false, }, { description: "credentials group id invalid 1", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[credentialsGroupFlag] = "" + flagValues[credentialsGroupIdFlag] = "" }), isValid: false, }, { description: "credentials group id invalid 2", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[credentialsGroupFlag] = "invalid-uuid" + flagValues[credentialsGroupIdFlag] = "invalid-uuid" }), isValid: false, }, diff --git a/internal/pkg/services/object-storage/utils/utils.go b/internal/pkg/services/object-storage/utils/utils.go index b4ea82a5..ba3cc615 100644 --- a/internal/pkg/services/object-storage/utils/utils.go +++ b/internal/pkg/services/object-storage/utils/utils.go @@ -52,5 +52,5 @@ func GetCredentialsName(ctx context.Context, apiClient ObjectStorageClient, proj } } - return "", fmt.Errorf("could not find Object Storage credential name") + return "", fmt.Errorf("could not find Object Storage credentials name") } diff --git a/internal/pkg/services/object-storage/utils/utils_test.go b/internal/pkg/services/object-storage/utils/utils_test.go index 41563295..4fec2589 100644 --- a/internal/pkg/services/object-storage/utils/utils_test.go +++ b/internal/pkg/services/object-storage/utils/utils_test.go @@ -18,7 +18,7 @@ import ( var ( testProjectId = uuid.NewString() testCredentialsGroupId = uuid.NewString() - testCredentialId = "credentialID" //nolint:gosec // linter false positive + testCredentialsId = "credentialsID" //nolint:gosec // linter false positive ) const ( @@ -168,7 +168,7 @@ func TestGetCredentialsName(t *testing.T) { listAccessKeysResp: &objectstorage.ListAccessKeysResponse{ AccessKeys: &[]objectstorage.AccessKey{ { - KeyId: utils.Ptr(testCredentialId), + KeyId: utils.Ptr(testCredentialsId), DisplayName: utils.Ptr(testCredentialsName), }, }, @@ -190,7 +190,7 @@ func TestGetCredentialsName(t *testing.T) { DisplayName: utils.Ptr("test-name"), }, { - KeyId: utils.Ptr(testCredentialId), + KeyId: utils.Ptr(testCredentialsId), DisplayName: utils.Ptr(testCredentialsName), }, }, @@ -221,7 +221,7 @@ func TestGetCredentialsName(t *testing.T) { listAccessKeysResp: &objectstorage.ListAccessKeysResponse{ AccessKeys: &[]objectstorage.AccessKey{ { - KeyId: utils.Ptr(testCredentialId), + KeyId: utils.Ptr(testCredentialsId), DisplayName: nil, }, }, @@ -233,7 +233,7 @@ func TestGetCredentialsName(t *testing.T) { listAccessKeysResp: &objectstorage.ListAccessKeysResponse{ AccessKeys: &[]objectstorage.AccessKey{ { - KeyId: utils.Ptr(testCredentialId), + KeyId: utils.Ptr(testCredentialsId), DisplayName: utils.Ptr(""), }, }, @@ -276,7 +276,7 @@ func TestGetCredentialsName(t *testing.T) { t.Fatalf("Failed to initialize client: %v", err) } - output, err := GetCredentialsName(context.Background(), client, testProjectId, testCredentialsGroupId, testCredentialId) + output, err := GetCredentialsName(context.Background(), client, testProjectId, testCredentialsGroupId, testCredentialsId) if tt.isValid && err != nil { t.Errorf("failed on valid input") From f8a8f94a468f76d9367b8e5f5f38b6d282cded72 Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Thu, 7 Mar 2024 09:59:21 +0100 Subject: [PATCH 43/48] Update return value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: João Palet --- internal/pkg/services/object-storage/utils/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/services/object-storage/utils/utils.go b/internal/pkg/services/object-storage/utils/utils.go index ba3cc615..f1256470 100644 --- a/internal/pkg/services/object-storage/utils/utils.go +++ b/internal/pkg/services/object-storage/utils/utils.go @@ -43,7 +43,7 @@ func GetCredentialsName(ctx context.Context, apiClient ObjectStorageClient, proj credentials := resp.AccessKeys if credentials == nil { - return "", fmt.Errorf("nil Object Storage credentials list: %w", err) + return "", fmt.Errorf("nil Object Storage credentials list) } for _, credential := range *credentials { From c80a81ac1a7a37829567ae14bf340a7bc11e8e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Go=CC=88kc=CC=A7e=20Go=CC=88k=20Klingel?= Date: Thu, 7 Mar 2024 10:05:52 +0100 Subject: [PATCH 44/48] add missing quote --- internal/pkg/services/object-storage/utils/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/services/object-storage/utils/utils.go b/internal/pkg/services/object-storage/utils/utils.go index f1256470..0f744d9d 100644 --- a/internal/pkg/services/object-storage/utils/utils.go +++ b/internal/pkg/services/object-storage/utils/utils.go @@ -43,7 +43,7 @@ func GetCredentialsName(ctx context.Context, apiClient ObjectStorageClient, proj credentials := resp.AccessKeys if credentials == nil { - return "", fmt.Errorf("nil Object Storage credentials list) + return "", fmt.Errorf("nil Object Storage credentials list") } for _, credential := range *credentials { From 5abc3c1ec59752ec59cab01d0122df43a050ebc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Go=CC=88kc=CC=A7e=20Go=CC=88k=20Klingel?= Date: Thu, 7 Mar 2024 11:24:47 +0100 Subject: [PATCH 45/48] add docs --- docs/stackit_object-storage.md | 1 + docs/stackit_object-storage_credentials.md | 34 ++++++++++++++ ...ackit_object-storage_credentials_create.md | 43 +++++++++++++++++ ...ackit_object-storage_credentials_delete.md | 39 ++++++++++++++++ ...stackit_object-storage_credentials_list.md | 46 +++++++++++++++++++ docs/stackit_secrets-manager_user_create.md | 15 +++--- 6 files changed, 170 insertions(+), 8 deletions(-) create mode 100644 docs/stackit_object-storage_credentials.md create mode 100644 docs/stackit_object-storage_credentials_create.md create mode 100644 docs/stackit_object-storage_credentials_delete.md create mode 100644 docs/stackit_object-storage_credentials_list.md diff --git a/docs/stackit_object-storage.md b/docs/stackit_object-storage.md index a30996fb..d6405ba0 100644 --- a/docs/stackit_object-storage.md +++ b/docs/stackit_object-storage.md @@ -29,6 +29,7 @@ stackit object-storage [flags] * [stackit](./stackit.md) - Manage STACKIT resources using the command line * [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 * [stackit object-storage disable](./stackit_object-storage_disable.md) - Disables Object Storage for a project * [stackit object-storage enable](./stackit_object-storage_enable.md) - Enables Object Storage for a project diff --git a/docs/stackit_object-storage_credentials.md b/docs/stackit_object-storage_credentials.md new file mode 100644 index 00000000..30d360d8 --- /dev/null +++ b/docs/stackit_object-storage_credentials.md @@ -0,0 +1,34 @@ +## 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 + diff --git a/docs/stackit_object-storage_credentials_create.md b/docs/stackit_object-storage_credentials_create.md new file mode 100644 index 00000000..92575660 --- /dev/null +++ b/docs/stackit_object-storage_credentials_create.md @@ -0,0 +1,43 @@ +## 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 it 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 + diff --git a/docs/stackit_object-storage_credentials_delete.md b/docs/stackit_object-storage_credentials_delete.md new file mode 100644 index 00000000..bae6b99b --- /dev/null +++ b/docs/stackit_object-storage_credentials_delete.md @@ -0,0 +1,39 @@ +## 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 + diff --git a/docs/stackit_object-storage_credentials_list.md b/docs/stackit_object-storage_credentials_list.md new file mode 100644 index 00000000..8882936f --- /dev/null +++ b/docs/stackit_object-storage_credentials_list.md @@ -0,0 +1,46 @@ +## 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 + diff --git a/docs/stackit_secrets-manager_user_create.md b/docs/stackit_secrets-manager_user_create.md index aed85ad8..4a671d95 100644 --- a/docs/stackit_secrets-manager_user_create.md +++ b/docs/stackit_secrets-manager_user_create.md @@ -4,7 +4,9 @@ Creates a Secrets Manager user ### Synopsis -Creates a user for a Secrets Manager instance with generated username and password +Creates a Secrets Manager user. +The username and password are auto-generated and provided upon creation. +A description can be provided to identify a user. ``` stackit secrets-manager user create [flags] @@ -13,17 +15,14 @@ stackit secrets-manager user create [flags] ### Examples ``` - Create a Secrets Manager user for instance with ID "xxx" - $ stackit mongodbflex user create --instance-id xxx - Create a Secrets Manager user for instance with ID "xxx" and description "yyy" - $ stackit mongodbflex user create --instance-id xxx --description yyy + $ stackit secrets-manager user create --instance-id xxx --description yyy - Create a Secrets Manager user for instance with ID "xxx" and doesn't display the password - $ stackit mongodbflex user create --instance-id xxx --hide-password + Create a Secrets Manager user for instance with ID "xxx" and hides the generated password + $ stackit secrets-manager user create --instance-id xxx --hide-password Create a Secrets Manager user for instance with ID "xxx" with write access to the secrets engine - $ stackit mongodbflex user create --instance-id xxx --write + $ stackit secrets-manager user create --instance-id xxx --write ``` ### Options From 62288e96e6be60c2e108e2d2cc23a65c05425ca8 Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Thu, 7 Mar 2024 15:14:35 +0100 Subject: [PATCH 46/48] Update long description Co-authored-by: Vicente Pinto --- internal/cmd/object-storage/credentials/create/create.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials/create/create.go b/internal/cmd/object-storage/credentials/create/create.go index afdb30a1..dea89cf0 100644 --- a/internal/cmd/object-storage/credentials/create/create.go +++ b/internal/cmd/object-storage/credentials/create/create.go @@ -35,7 +35,7 @@ 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 it will not be retrievable later.", + 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( From 3215c7abfde82451259b29e3a7155f9f75a7321c Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Thu, 7 Mar 2024 15:15:16 +0100 Subject: [PATCH 47/48] Update example description Co-authored-by: Vicente Pinto --- internal/cmd/object-storage/credentials/create/create.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials/create/create.go b/internal/cmd/object-storage/credentials/create/create.go index dea89cf0..ae9b8a2c 100644 --- a/internal/cmd/object-storage/credentials/create/create.go +++ b/internal/cmd/object-storage/credentials/create/create.go @@ -39,7 +39,7 @@ func NewCmd() *cobra.Command { Args: args.NoArgs, Example: examples.Build( examples.NewExample( - `Create credentials for a credentials group with ID xxx`, + `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`, From 8780fad63a35c8a6c6ab6219853d0bc5e2aac069 Mon Sep 17 00:00:00 2001 From: GokceGK <161626272+GokceGK@users.noreply.github.com> Date: Thu, 7 Mar 2024 15:15:35 +0100 Subject: [PATCH 48/48] Update example description Co-authored-by: Vicente Pinto --- internal/cmd/object-storage/credentials/create/create.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/object-storage/credentials/create/create.go b/internal/cmd/object-storage/credentials/create/create.go index ae9b8a2c..7088e187 100644 --- a/internal/cmd/object-storage/credentials/create/create.go +++ b/internal/cmd/object-storage/credentials/create/create.go @@ -42,7 +42,7 @@ func NewCmd() *cobra.Command { `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`, + `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 {