From dcc0b45789cc586eb1785bd8af1ac8796c7392b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Palet?= Date: Wed, 19 Jun 2024 14:40:49 +0100 Subject: [PATCH 1/8] Implement user create and reset-password --- .../cmd/beta/sqlserverflex/sqlserverflex.go | 2 + .../beta/sqlserverflex/user/create/create.go | 195 ++++++++++++++ .../sqlserverflex/user/create/create_test.go | 246 ++++++++++++++++++ .../user/reset-password/reset_password.go | 162 ++++++++++++ .../reset-password/reset_password_test.go | 245 +++++++++++++++++ internal/cmd/beta/sqlserverflex/user/user.go | 28 ++ .../pkg/services/mongodbflex/utils/utils.go | 14 +- .../pkg/services/postgresflex/utils/utils.go | 12 +- .../pkg/services/sqlserverflex/utils/utils.go | 9 + .../sqlserverflex/utils/utils_test.go | 51 ++++ 10 files changed, 951 insertions(+), 13 deletions(-) create mode 100644 internal/cmd/beta/sqlserverflex/user/create/create.go create mode 100644 internal/cmd/beta/sqlserverflex/user/create/create_test.go create mode 100644 internal/cmd/beta/sqlserverflex/user/reset-password/reset_password.go create mode 100644 internal/cmd/beta/sqlserverflex/user/reset-password/reset_password_test.go create mode 100644 internal/cmd/beta/sqlserverflex/user/user.go diff --git a/internal/cmd/beta/sqlserverflex/sqlserverflex.go b/internal/cmd/beta/sqlserverflex/sqlserverflex.go index 2cd98a979..196d7a6e1 100644 --- a/internal/cmd/beta/sqlserverflex/sqlserverflex.go +++ b/internal/cmd/beta/sqlserverflex/sqlserverflex.go @@ -3,6 +3,7 @@ package sqlserverflex import ( "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/options" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/user" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" @@ -25,4 +26,5 @@ func NewCmd(p *print.Printer) *cobra.Command { func addSubcommands(cmd *cobra.Command, p *print.Printer) { cmd.AddCommand(instance.NewCmd(p)) cmd.AddCommand(options.NewCmd(p)) + cmd.AddCommand(user.NewCmd(p)) } diff --git a/internal/cmd/beta/sqlserverflex/user/create/create.go b/internal/cmd/beta/sqlserverflex/user/create/create.go new file mode 100644 index 000000000..e74a51e09 --- /dev/null +++ b/internal/cmd/beta/sqlserverflex/user/create/create.go @@ -0,0 +1,195 @@ +package create + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/goccy/go-yaml" + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/client" + sqlserverflexUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/utils" + "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" +) + +const ( + instanceIdFlag = "instance-id" + usernameFlag = "username" + databaseFlag = "database" + roleFlag = "role" +) + +var ( + rolesDefault = []string{"read"} +) + +type inputModel struct { + *globalflags.GlobalFlagModel + + InstanceId string + Username *string + Database *string + Roles *[]string +} + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: "create", + Short: "Creates a SQLServer Flex user", + Long: fmt.Sprintf("%s\n%s\n%s\n%s\n%s", + "Creates a SQLServer Flex user login for an instance.", + "The password is only visible upon creation and cannot be retrieved later.", + "Alternatively, you can reset the password and access the new one by running:", + " $ stackit sqlserverflex user reset-password USER_ID --instance-id INSTANCE_ID", + "Please refer to https://docs.stackit.cloud/stackit/en/creating-logins-and-users-in-sqlserver-flex-instances-210862358.html for additional information.", + ), + Example: examples.Build( + examples.NewExample( + `Create a SQLServer Flex user for instance with ID "xxx" and specify the username, role and database`, + "$ stackit sqlserverflex user create --instance-id xxx --username johndoe --role my-role --database my-database"), + ), + Args: args.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(p, cmd) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(p) + if err != nil { + return err + } + + instanceLabel, err := sqlserverflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId) + if err != nil { + p.Debug(print.ErrorLevel, "get instance name: %v", err) + instanceLabel = model.InstanceId + } + + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to create a user for instance %q?", instanceLabel) + err = p.PromptForConfirmation(prompt) + if err != nil { + return err + } + } + + // Call API + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("create SQLServer Flex user: %w", err) + } + user := resp.Item + + return outputResult(p, model, instanceLabel, user) + }, + } + + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "ID of the instance") + cmd.Flags().String(usernameFlag, "", "Username of the user") + cmd.Flags().String(databaseFlag, "", "Default database for the user") + cmd.Flags().StringSlice(roleFlag, []string{}, "Roles of the user") + + err := flags.MarkFlagsRequired(cmd, instanceIdFlag, usernameFlag) + cobra.CheckErr(err) +} + +func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + InstanceId: flags.FlagToStringValue(p, cmd, instanceIdFlag), + Username: flags.FlagToStringPointer(p, cmd, usernameFlag), + Database: flags.FlagToStringPointer(p, cmd, databaseFlag), + Roles: flags.FlagToStringSlicePointer(p, cmd, roleFlag), + } + + if p.IsVerbosityDebug() { + modelStr, err := print.BuildDebugStrFromInputModel(model) + if err != nil { + p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) + } else { + p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) + } + } + + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *sqlserverflex.APIClient) sqlserverflex.ApiCreateUserRequest { + req := apiClient.CreateUser(ctx, model.ProjectId, model.InstanceId) + + var roles []sqlserverflex.Role + if model.Roles != nil { + for _, r := range *model.Roles { + roles = append(roles, sqlserverflex.Role(r)) + } + } + + req = req.CreateUserPayload(sqlserverflex.CreateUserPayload{ + Username: model.Username, + Database: model.Database, + Roles: &roles, + }) + return req +} + +func outputResult(p *print.Printer, model *inputModel, instanceLabel string, user *sqlserverflex.User) error { + switch model.OutputFormat { + case print.JSONOutputFormat: + details, err := json.MarshalIndent(user, "", " ") + if err != nil { + return fmt.Errorf("marshal SQLServer Flex user: %w", err) + } + p.Outputln(string(details)) + + return nil + case print.YAMLOutputFormat: + details, err := yaml.MarshalWithOptions(user, yaml.IndentSequence(true)) + if err != nil { + return fmt.Errorf("marshal SQLServer Flex user: %w", err) + } + p.Outputln(string(details)) + + return nil + default: + p.Outputf("Created user for instance %q. User ID: %s\n\n", instanceLabel, *user.Id) + p.Outputf("Username: %s\n", *user.Username) + p.Outputf("Password: %s\n", *user.Password) + if user.Roles != nil && len(*user.Roles) != 0 { + p.Outputf("Roles: %v\n", *user.Roles) + } + if user.Database != nil && *user.Database != "" { + p.Outputf("Database: %s\n", *user.Database) + } + if user.Host != nil && *user.Host != "" { + p.Outputf("Host: %s\n", *user.Host) + } + if user.Port != nil { + p.Outputf("Port: %d\n", *user.Port) + } + if user.Uri != nil && *user.Uri != "" { + p.Outputf("URI: %s\n", *user.Uri) + } + + return nil + } +} diff --git a/internal/cmd/beta/sqlserverflex/user/create/create_test.go b/internal/cmd/beta/sqlserverflex/user/create/create_test.go new file mode 100644 index 000000000..6d7679953 --- /dev/null +++ b/internal/cmd/beta/sqlserverflex/user/create/create_test.go @@ -0,0 +1,246 @@ +package create + +import ( + "context" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" +) + +var projectIdFlag = globalflags.ProjectIdFlag + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &sqlserverflex.APIClient{} +var testProjectId = uuid.NewString() +var testInstanceId = uuid.NewString() + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + projectIdFlag: testProjectId, + instanceIdFlag: testInstanceId, + usernameFlag: "johndoe", + databaseFlag: "default", + roleFlag: "read", + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Verbosity: globalflags.VerbosityDefault, + }, + InstanceId: testInstanceId, + Username: utils.Ptr("johndoe"), + Database: utils.Ptr("default"), + Roles: utils.Ptr([]string{"read"}), + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *sqlserverflex.ApiCreateUserRequest)) sqlserverflex.ApiCreateUserRequest { + request := testClient.CreateUser(testCtx, testProjectId, testInstanceId) + request = request.CreateUserPayload(sqlserverflex.CreateUserPayload{ + Username: utils.Ptr("johndoe"), + Database: utils.Ptr("default"), + Roles: utils.Ptr([]sqlserverflex.Role{"read"}), + }) + + 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 username specified", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, usernameFlag) + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Username = nil + }), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, projectIdFlag) + }), + isValid: false, + }, + { + description: "project id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "instance id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, instanceIdFlag) + }), + isValid: false, + }, + { + description: "instance id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[instanceIdFlag] = "" + }), + isValid: false, + }, + { + description: "database missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, databaseFlag) + }), + isValid: false, + }, + { + description: "roles missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, roleFlag) + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Roles = &rolesDefault + }), + }, + { + description: "invalid role", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[roleFlag] = "invalid-role" + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + cmd := &cobra.Command{} + err := globalflags.Configure(cmd.Flags()) + if err != nil { + t.Fatalf("configure global flags: %v", err) + } + + configureFlags(cmd) + + for flag, value := range tt.flagValues { + err := cmd.Flags().Set(flag, value) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("setting flag --%s=%s: %v", flag, value, err) + } + } + + err = cmd.ValidateRequiredFlags() + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error validating flags: %v", err) + } + + p := print.NewPrinter() + model, err := parseInput(p, cmd) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error parsing flags: %v", err) + } + + if !tt.isValid { + t.Fatalf("did not fail on invalid input") + } + diff := cmp.Diff(model, tt.expectedModel) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest sqlserverflex.ApiCreateUserRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + { + description: "no username specified", + model: fixtureInputModel(func(model *inputModel) { + model.Username = nil + }), + expectedRequest: fixtureRequest().CreateUserPayload(sqlserverflex.CreateUserPayload{ + Database: utils.Ptr("default"), + Roles: utils.Ptr([]sqlserverflex.Role{"read"}), + }), + }, + } + + 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/beta/sqlserverflex/user/reset-password/reset_password.go b/internal/cmd/beta/sqlserverflex/user/reset-password/reset_password.go new file mode 100644 index 000000000..85e08d84b --- /dev/null +++ b/internal/cmd/beta/sqlserverflex/user/reset-password/reset_password.go @@ -0,0 +1,162 @@ +package resetpassword + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/goccy/go-yaml" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/client" + sqlserverflexUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/utils" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" +) + +const ( + userIdArg = "USER_ID" + + instanceIdFlag = "instance-id" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + + InstanceId string + UserId string +} + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("reset-password %s", userIdArg), + Short: "Resets the password of a SQLServer Flex user", + Long: fmt.Sprintf("%s\ns%s", + "Resets the password of a SQLServer Flex user.", + "The new password is visible after and cannot be retrieved later.", + ), + Example: examples.Build( + examples.NewExample( + `Reset the password of a SQLServer Flex user with ID "xxx" of instance with ID "yyy"`, + "$ stackit sqlserverflex user reset-password xxx --instance-id yyy"), + ), + Args: args.SingleArg(userIdArg, nil), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(p, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(p) + if err != nil { + return err + } + + instanceLabel, err := sqlserverflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId) + if err != nil { + p.Debug(print.ErrorLevel, "get instance name: %v", err) + instanceLabel = model.InstanceId + } + + userLabel, err := sqlserverflexUtils.GetUserName(ctx, apiClient, model.ProjectId, model.InstanceId, model.UserId) + if err != nil { + p.Debug(print.ErrorLevel, "get user name: %v", err) + userLabel = model.UserId + } + + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to reset the password of user %q of instance %q? (This cannot be undone)", userLabel, instanceLabel) + err = p.PromptForConfirmation(prompt) + if err != nil { + return err + } + } + + // Call API + req := buildRequest(ctx, model, apiClient) + user, err := req.Execute() + if err != nil { + return fmt.Errorf("reset SQLServer Flex user password: %w", err) + } + + return outputResult(p, model, userLabel, instanceLabel, user.Item) + }, + } + + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "ID of the instance") + + err := flags.MarkFlagsRequired(cmd, instanceIdFlag) + cobra.CheckErr(err) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + userId := inputArgs[0] + + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + InstanceId: flags.FlagToStringValue(p, cmd, instanceIdFlag), + UserId: userId, + } + + if p.IsVerbosityDebug() { + modelStr, err := print.BuildDebugStrFromInputModel(model) + if err != nil { + p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) + } else { + p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) + } + } + + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *sqlserverflex.APIClient) sqlserverflex.ApiResetUserRequest { + req := apiClient.ResetUser(ctx, model.ProjectId, model.InstanceId, model.UserId) + return req +} + +func outputResult(p *print.Printer, model *inputModel, userLabel, instanceLabel string, user *sqlserverflex.User) error { + switch model.OutputFormat { + case print.JSONOutputFormat: + details, err := json.MarshalIndent(user, "", " ") + if err != nil { + return fmt.Errorf("marshal SQLServer Flex reset password: %w", err) + } + p.Outputln(string(details)) + + return nil + case print.YAMLOutputFormat: + details, err := yaml.MarshalWithOptions(user, yaml.IndentSequence(true)) + if err != nil { + return fmt.Errorf("marshal SQLServer Flex reset password: %w", err) + } + p.Outputln(string(details)) + + return nil + default: + p.Outputf("Reset password for user %q of instance %q\n\n", userLabel, instanceLabel) + p.Outputf("Username: %s\n", *user.Username) + p.Outputf("New password: %s\n", *user.Password) + if user.Uri != nil && *user.Uri != "" { + p.Outputf("New URI: %s\n", *user.Uri) + } + return nil + } +} diff --git a/internal/cmd/beta/sqlserverflex/user/reset-password/reset_password_test.go b/internal/cmd/beta/sqlserverflex/user/reset-password/reset_password_test.go new file mode 100644 index 000000000..e0a472ae1 --- /dev/null +++ b/internal/cmd/beta/sqlserverflex/user/reset-password/reset_password_test.go @@ -0,0 +1,245 @@ +package resetpassword + +import ( + "context" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" +) + +var projectIdFlag = globalflags.ProjectIdFlag + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &sqlserverflex.APIClient{} +var testProjectId = uuid.NewString() +var testInstanceId = uuid.NewString() +var testUserId = uuid.NewString() + +func fixtureArgValues(mods ...func(argValues []string)) []string { + argValues := []string{ + testUserId, + } + for _, mod := range mods { + mod(argValues) + } + return argValues +} + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + projectIdFlag: testProjectId, + instanceIdFlag: testInstanceId, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Verbosity: globalflags.VerbosityDefault, + }, + InstanceId: testInstanceId, + UserId: testUserId, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *sqlserverflex.ApiResetUserRequest)) sqlserverflex.ApiResetUserRequest { + request := testClient.ResetUser(testCtx, testProjectId, testInstanceId, testUserId) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + argValues: fixtureArgValues(), + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "no arg values", + argValues: []string{}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + { + description: "no flag values", + argValues: fixtureArgValues(), + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "project id missing", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, projectIdFlag) + }), + isValid: false, + }, + { + description: "project id invalid 1", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "instance id missing", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, instanceIdFlag) + }), + isValid: false, + }, + { + description: "instance id invalid 1", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[instanceIdFlag] = "" + }), + isValid: false, + }, + { + description: "instance id invalid 2", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[instanceIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "user id invalid 1", + argValues: []string{""}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + { + description: "user id invalid 2", + argValues: []string{"invalid-uuid"}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + p := print.NewPrinter() + cmd := NewCmd(p) + err := globalflags.Configure(cmd.Flags()) + if err != nil { + t.Fatalf("configure global flags: %v", err) + } + + for flag, value := range tt.flagValues { + err := cmd.Flags().Set(flag, value) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("setting flag --%s=%s: %v", flag, value, err) + } + } + + err = cmd.ValidateArgs(tt.argValues) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error validating args: %v", err) + } + + err = cmd.ValidateRequiredFlags() + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error validating flags: %v", err) + } + + model, err := parseInput(p, cmd, tt.argValues) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error parsing flags: %v", err) + } + + if !tt.isValid { + t.Fatalf("did not fail on invalid input") + } + diff := cmp.Diff(model, tt.expectedModel) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest sqlserverflex.ApiResetUserRequest + }{ + { + 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/beta/sqlserverflex/user/user.go b/internal/cmd/beta/sqlserverflex/user/user.go new file mode 100644 index 000000000..7b01d76fd --- /dev/null +++ b/internal/cmd/beta/sqlserverflex/user/user.go @@ -0,0 +1,28 @@ +package user + +import ( + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/user/create" + resetpassword "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/user/reset-password" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/spf13/cobra" +) + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: "user", + Short: "Provides functionality for SQLServer Flex users", + Long: "Provides functionality for SQLServer Flex users.", + Args: args.NoArgs, + Run: utils.CmdHelp, + } + addSubcommands(cmd, p) + return cmd +} + +func addSubcommands(cmd *cobra.Command, p *print.Printer) { + cmd.AddCommand(create.NewCmd(p)) + cmd.AddCommand(resetpassword.NewCmd(p)) +} diff --git a/internal/pkg/services/mongodbflex/utils/utils.go b/internal/pkg/services/mongodbflex/utils/utils.go index c582c8de8..2f8bbd89b 100644 --- a/internal/pkg/services/mongodbflex/utils/utils.go +++ b/internal/pkg/services/mongodbflex/utils/utils.go @@ -20,6 +20,13 @@ var instanceTypeToReplicas = map[string]int64{ "Sharded": 9, } +type MongoDBFlexClient interface { + ListVersionsExecute(ctx context.Context, projectId string) (*mongodbflex.ListVersionsResponse, error) + GetInstanceExecute(ctx context.Context, projectId, instanceId string) (*mongodbflex.GetInstanceResponse, error) + GetUserExecute(ctx context.Context, projectId, instanceId, userId string) (*mongodbflex.GetUserResponse, error) + ListRestoreJobsExecute(ctx context.Context, projectId string, instanceId string) (*mongodbflex.ListRestoreJobsResponse, error) +} + func AvailableInstanceTypes() []string { instanceTypes := make([]string, len(instanceTypeToReplicas)) i := 0 @@ -115,13 +122,6 @@ func LoadFlavorId(cpu, ram int64, flavors *[]mongodbflex.HandlersInfraFlavor) (* } } -type MongoDBFlexClient interface { - ListVersionsExecute(ctx context.Context, projectId string) (*mongodbflex.ListVersionsResponse, error) - GetInstanceExecute(ctx context.Context, projectId, instanceId string) (*mongodbflex.GetInstanceResponse, error) - GetUserExecute(ctx context.Context, projectId, instanceId, userId string) (*mongodbflex.GetUserResponse, error) - ListRestoreJobsExecute(ctx context.Context, projectId string, instanceId string) (*mongodbflex.ListRestoreJobsResponse, error) -} - func GetLatestMongoDBVersion(ctx context.Context, apiClient MongoDBFlexClient, projectId string) (string, error) { resp, err := apiClient.ListVersionsExecute(ctx, projectId) if err != nil { diff --git a/internal/pkg/services/postgresflex/utils/utils.go b/internal/pkg/services/postgresflex/utils/utils.go index 298df752e..66bc48474 100644 --- a/internal/pkg/services/postgresflex/utils/utils.go +++ b/internal/pkg/services/postgresflex/utils/utils.go @@ -18,6 +18,12 @@ var instanceTypeToReplicas = map[string]int64{ "Replica": 3, } +type PostgresFlexClient interface { + ListVersionsExecute(ctx context.Context, projectId string) (*postgresflex.ListVersionsResponse, error) + GetInstanceExecute(ctx context.Context, projectId, instanceId string) (*postgresflex.InstanceResponse, error) + GetUserExecute(ctx context.Context, projectId, instanceId, userId string) (*postgresflex.GetUserResponse, error) +} + func AvailableInstanceTypes() []string { instanceTypes := make([]string, len(instanceTypeToReplicas)) i := 0 @@ -113,12 +119,6 @@ func LoadFlavorId(cpu, ram int64, flavors *[]postgresflex.Flavor) (*string, erro } } -type PostgresFlexClient interface { - ListVersionsExecute(ctx context.Context, projectId string) (*postgresflex.ListVersionsResponse, error) - GetInstanceExecute(ctx context.Context, projectId, instanceId string) (*postgresflex.InstanceResponse, error) - GetUserExecute(ctx context.Context, projectId, instanceId, userId string) (*postgresflex.GetUserResponse, error) -} - func GetLatestPostgreSQLVersion(ctx context.Context, apiClient PostgresFlexClient, projectId string) (string, error) { resp, err := apiClient.ListVersionsExecute(ctx, projectId) if err != nil { diff --git a/internal/pkg/services/sqlserverflex/utils/utils.go b/internal/pkg/services/sqlserverflex/utils/utils.go index 2e74c879e..e2fef4071 100644 --- a/internal/pkg/services/sqlserverflex/utils/utils.go +++ b/internal/pkg/services/sqlserverflex/utils/utils.go @@ -17,6 +17,7 @@ const ( type SQLServerFlexClient interface { ListVersionsExecute(ctx context.Context, projectId string) (*sqlserverflex.ListVersionsResponse, error) GetInstanceExecute(ctx context.Context, projectId, instanceId string) (*sqlserverflex.GetInstanceResponse, error) + GetUserExecute(ctx context.Context, projectId, instanceId, userId string) (*sqlserverflex.GetUserResponse, error) } func ValidateFlavorId(flavorId string, flavors *[]sqlserverflex.InstanceFlavorEntry) error { @@ -91,3 +92,11 @@ func GetInstanceName(ctx context.Context, apiClient SQLServerFlexClient, project } return *resp.Item.Name, nil } + +func GetUserName(ctx context.Context, apiClient SQLServerFlexClient, projectId, instanceId, userId string) (string, error) { + resp, err := apiClient.GetUserExecute(ctx, projectId, instanceId, userId) + if err != nil { + return "", fmt.Errorf("get MongoDBFlex user: %w", err) + } + return *resp.Item.Username, nil +} diff --git a/internal/pkg/services/sqlserverflex/utils/utils_test.go b/internal/pkg/services/sqlserverflex/utils/utils_test.go index c944fc810..8776baf4a 100644 --- a/internal/pkg/services/sqlserverflex/utils/utils_test.go +++ b/internal/pkg/services/sqlserverflex/utils/utils_test.go @@ -15,6 +15,7 @@ import ( var ( testProjectId = uuid.NewString() testInstanceId = uuid.NewString() + testUserId = uuid.NewString() ) const ( @@ -421,3 +422,53 @@ func TestGetInstanceName(t *testing.T) { }) } } + +func TestGetUserName(t *testing.T) { + tests := []struct { + description string + getUserFails bool + getUserResp *sqlserverflex.GetUserResponse + isValid bool + expectedOutput string + }{ + { + description: "base", + getUserResp: &sqlserverflex.GetUserResponse{ + Item: &sqlserverflex.InstanceResponseUser{ + Username: utils.Ptr(testUserName), + }, + }, + isValid: true, + expectedOutput: testUserName, + }, + { + description: "get user fails", + getUserFails: true, + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + client := &sqlServerFlexClientMocked{ + getUserFails: tt.getUserFails, + getUserResp: tt.getUserResp, + } + + output, err := GetUserName(context.Background(), client, testProjectId, testInstanceId, testUserId) + + if tt.isValid && err != nil { + t.Errorf("failed on valid input") + } + if !tt.isValid && err == nil { + t.Errorf("did not fail on invalid input") + } + if !tt.isValid { + return + } + if output != tt.expectedOutput { + t.Errorf("expected output to be %s, got %s", tt.expectedOutput, output) + } + }) + } +} From 801e781b107faa70ce1ebfc28a264c3e8566c2cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Palet?= Date: Wed, 19 Jun 2024 14:41:15 +0100 Subject: [PATCH 2/8] Update docs --- docs/stackit_beta_sqlserverflex.md | 1 + docs/stackit_beta_sqlserverflex_user.md | 34 ++++++++++++++ .../stackit_beta_sqlserverflex_user_create.md | 47 +++++++++++++++++++ ..._beta_sqlserverflex_user_reset-password.md | 41 ++++++++++++++++ docs/stackit_mongodbflex_user_describe.md | 4 +- docs/stackit_postgresflex_user_describe.md | 4 +- 6 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 docs/stackit_beta_sqlserverflex_user.md create mode 100644 docs/stackit_beta_sqlserverflex_user_create.md create mode 100644 docs/stackit_beta_sqlserverflex_user_reset-password.md diff --git a/docs/stackit_beta_sqlserverflex.md b/docs/stackit_beta_sqlserverflex.md index dc6ec99d7..79a07e351 100644 --- a/docs/stackit_beta_sqlserverflex.md +++ b/docs/stackit_beta_sqlserverflex.md @@ -31,4 +31,5 @@ stackit beta sqlserverflex [flags] * [stackit beta](./stackit_beta.md) - Contains beta STACKIT CLI commands * [stackit beta sqlserverflex instance](./stackit_beta_sqlserverflex_instance.md) - Provides functionality for SQLServer Flex instances * [stackit beta sqlserverflex options](./stackit_beta_sqlserverflex_options.md) - Lists SQL Server Flex options +* [stackit beta sqlserverflex user](./stackit_beta_sqlserverflex_user.md) - Provides functionality for SQLServer Flex users diff --git a/docs/stackit_beta_sqlserverflex_user.md b/docs/stackit_beta_sqlserverflex_user.md new file mode 100644 index 000000000..f4717a6fe --- /dev/null +++ b/docs/stackit_beta_sqlserverflex_user.md @@ -0,0 +1,34 @@ +## stackit beta sqlserverflex user + +Provides functionality for SQLServer Flex users + +### Synopsis + +Provides functionality for SQLServer Flex users. + +``` +stackit beta sqlserverflex user [flags] +``` + +### Options + +``` + -h, --help Help for "stackit beta sqlserverflex user" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit beta sqlserverflex](./stackit_beta_sqlserverflex.md) - Provides functionality for SQLServer Flex +* [stackit beta sqlserverflex user create](./stackit_beta_sqlserverflex_user_create.md) - Creates a SQLServer Flex user +* [stackit beta sqlserverflex user reset-password](./stackit_beta_sqlserverflex_user_reset-password.md) - Resets the password of a SQLServer Flex user + diff --git a/docs/stackit_beta_sqlserverflex_user_create.md b/docs/stackit_beta_sqlserverflex_user_create.md new file mode 100644 index 000000000..9b30d6e51 --- /dev/null +++ b/docs/stackit_beta_sqlserverflex_user_create.md @@ -0,0 +1,47 @@ +## stackit beta sqlserverflex user create + +Creates a SQLServer Flex user + +### Synopsis + +Creates a SQLServer Flex user login for an instance. +The password is only visible upon creation and cannot be retrieved later. +Alternatively, you can reset the password and access the new one by running: + $ stackit sqlserverflex user reset-password USER_ID --instance-id INSTANCE_ID +Please refer to https://docs.stackit.cloud/stackit/en/creating-logins-and-users-in-sqlserver-flex-instances-210862358.html for additional information. + +``` +stackit beta sqlserverflex user create [flags] +``` + +### Examples + +``` + Create a SQLServer Flex user for instance with ID "xxx" and specify the username, role and database + $ stackit sqlserverflex user create --instance-id xxx --username johndoe --role my-role --database my-database +``` + +### Options + +``` + --database string Default database for the user + -h, --help Help for "stackit beta sqlserverflex user create" + --instance-id string ID of the instance + --role strings Roles of the user + --username string Username of the user +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit beta sqlserverflex user](./stackit_beta_sqlserverflex_user.md) - Provides functionality for SQLServer Flex users + diff --git a/docs/stackit_beta_sqlserverflex_user_reset-password.md b/docs/stackit_beta_sqlserverflex_user_reset-password.md new file mode 100644 index 000000000..18b380de1 --- /dev/null +++ b/docs/stackit_beta_sqlserverflex_user_reset-password.md @@ -0,0 +1,41 @@ +## stackit beta sqlserverflex user reset-password + +Resets the password of a SQLServer Flex user + +### Synopsis + +Resets the password of a SQLServer Flex user. +sThe new password is visible after and cannot be retrieved later. + +``` +stackit beta sqlserverflex user reset-password USER_ID [flags] +``` + +### Examples + +``` + Reset the password of a SQLServer Flex user with ID "xxx" of instance with ID "yyy" + $ stackit sqlserverflex user reset-password xxx --instance-id yyy +``` + +### Options + +``` + -h, --help Help for "stackit beta sqlserverflex user reset-password" + --instance-id string ID of the instance +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit beta sqlserverflex user](./stackit_beta_sqlserverflex_user.md) - Provides functionality for SQLServer Flex users + diff --git a/docs/stackit_mongodbflex_user_describe.md b/docs/stackit_mongodbflex_user_describe.md index 2295b1c84..1a0b65443 100644 --- a/docs/stackit_mongodbflex_user_describe.md +++ b/docs/stackit_mongodbflex_user_describe.md @@ -16,10 +16,10 @@ stackit mongodbflex user describe USER_ID [flags] ``` Get details of a MongoDB Flex user with ID "xxx" of instance with ID "yyy" - $ stackit mongodbflex user list xxx --instance-id yyy + $ stackit mongodbflex user describe xxx --instance-id yyy Get details of a MongoDB Flex user with ID "xxx" of instance with ID "yyy" in JSON format - $ stackit mongodbflex user list xxx --instance-id yyy --output-format json + $ stackit mongodbflex user describe xxx --instance-id yyy --output-format json ``` ### Options diff --git a/docs/stackit_postgresflex_user_describe.md b/docs/stackit_postgresflex_user_describe.md index dd1d74e8e..40e5a5bd9 100644 --- a/docs/stackit_postgresflex_user_describe.md +++ b/docs/stackit_postgresflex_user_describe.md @@ -16,10 +16,10 @@ stackit postgresflex user describe USER_ID [flags] ``` Get details of a PostgreSQL Flex user with ID "xxx" of instance with ID "yyy" - $ stackit postgresflex user list xxx --instance-id yyy + $ stackit postgresflex user describe xxx --instance-id yyy Get details of a PostgreSQL Flex user with ID "xxx" of instance with ID "yyy" in JSON format - $ stackit postgresflex user list xxx --instance-id yyy --output-format json + $ stackit postgresflex user describe xxx --instance-id yyy --output-format json ``` ### Options From 82cc5ada2cdb7886080666ea7b1bfed0b5a7e737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Palet?= Date: Wed, 19 Jun 2024 14:44:49 +0100 Subject: [PATCH 3/8] Fix error message --- internal/pkg/services/mongodbflex/utils/utils.go | 4 ++-- internal/pkg/services/sqlserverflex/utils/utils.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/pkg/services/mongodbflex/utils/utils.go b/internal/pkg/services/mongodbflex/utils/utils.go index 2f8bbd89b..ad2e07f9e 100644 --- a/internal/pkg/services/mongodbflex/utils/utils.go +++ b/internal/pkg/services/mongodbflex/utils/utils.go @@ -147,7 +147,7 @@ func GetLatestMongoDBVersion(ctx context.Context, apiClient MongoDBFlexClient, p func GetInstanceName(ctx context.Context, apiClient MongoDBFlexClient, projectId, instanceId string) (string, error) { resp, err := apiClient.GetInstanceExecute(ctx, projectId, instanceId) if err != nil { - return "", fmt.Errorf("get MongoDBFlex instance: %w", err) + return "", fmt.Errorf("get MongoDB Flex instance: %w", err) } return *resp.Item.Name, nil } @@ -155,7 +155,7 @@ func GetInstanceName(ctx context.Context, apiClient MongoDBFlexClient, projectId func GetUserName(ctx context.Context, apiClient MongoDBFlexClient, projectId, instanceId, userId string) (string, error) { resp, err := apiClient.GetUserExecute(ctx, projectId, instanceId, userId) if err != nil { - return "", fmt.Errorf("get MongoDBFlex user: %w", err) + return "", fmt.Errorf("get MongoDB Flex user: %w", err) } return *resp.Item.Username, nil } diff --git a/internal/pkg/services/sqlserverflex/utils/utils.go b/internal/pkg/services/sqlserverflex/utils/utils.go index e2fef4071..11a88fc77 100644 --- a/internal/pkg/services/sqlserverflex/utils/utils.go +++ b/internal/pkg/services/sqlserverflex/utils/utils.go @@ -96,7 +96,7 @@ func GetInstanceName(ctx context.Context, apiClient SQLServerFlexClient, project func GetUserName(ctx context.Context, apiClient SQLServerFlexClient, projectId, instanceId, userId string) (string, error) { resp, err := apiClient.GetUserExecute(ctx, projectId, instanceId, userId) if err != nil { - return "", fmt.Errorf("get MongoDBFlex user: %w", err) + return "", fmt.Errorf("get SQLServer Flex user: %w", err) } return *resp.Item.Username, nil } From 392762f0f517b470379b40ff26d26f6f9c89cf5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Palet?= Date: Wed, 19 Jun 2024 14:46:28 +0100 Subject: [PATCH 4/8] Improve descriptions --- docs/stackit_beta_sqlserverflex_user.md | 4 ++-- docs/stackit_beta_sqlserverflex_user_create.md | 6 +++--- docs/stackit_beta_sqlserverflex_user_reset-password.md | 6 +++--- internal/cmd/beta/sqlserverflex/user/create/create.go | 6 +++--- .../sqlserverflex/user/reset-password/reset_password.go | 6 +++--- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/stackit_beta_sqlserverflex_user.md b/docs/stackit_beta_sqlserverflex_user.md index f4717a6fe..2b02cbb4c 100644 --- a/docs/stackit_beta_sqlserverflex_user.md +++ b/docs/stackit_beta_sqlserverflex_user.md @@ -29,6 +29,6 @@ stackit beta sqlserverflex user [flags] ### SEE ALSO * [stackit beta sqlserverflex](./stackit_beta_sqlserverflex.md) - Provides functionality for SQLServer Flex -* [stackit beta sqlserverflex user create](./stackit_beta_sqlserverflex_user_create.md) - Creates a SQLServer Flex user -* [stackit beta sqlserverflex user reset-password](./stackit_beta_sqlserverflex_user_reset-password.md) - Resets the password of a SQLServer Flex user +* [stackit beta sqlserverflex user create](./stackit_beta_sqlserverflex_user_create.md) - Creates an SQLServer Flex user +* [stackit beta sqlserverflex user reset-password](./stackit_beta_sqlserverflex_user_reset-password.md) - Resets the password of an SQLServer Flex user diff --git a/docs/stackit_beta_sqlserverflex_user_create.md b/docs/stackit_beta_sqlserverflex_user_create.md index 9b30d6e51..2b63cc67a 100644 --- a/docs/stackit_beta_sqlserverflex_user_create.md +++ b/docs/stackit_beta_sqlserverflex_user_create.md @@ -1,10 +1,10 @@ ## stackit beta sqlserverflex user create -Creates a SQLServer Flex user +Creates an SQLServer Flex user ### Synopsis -Creates a SQLServer Flex user login for an instance. +Creates an SQLServer Flex user login for an instance. The password is only visible upon creation and cannot be retrieved later. Alternatively, you can reset the password and access the new one by running: $ stackit sqlserverflex user reset-password USER_ID --instance-id INSTANCE_ID @@ -17,7 +17,7 @@ stackit beta sqlserverflex user create [flags] ### Examples ``` - Create a SQLServer Flex user for instance with ID "xxx" and specify the username, role and database + Create an SQLServer Flex user for instance with ID "xxx" and specify the username, role and database $ stackit sqlserverflex user create --instance-id xxx --username johndoe --role my-role --database my-database ``` diff --git a/docs/stackit_beta_sqlserverflex_user_reset-password.md b/docs/stackit_beta_sqlserverflex_user_reset-password.md index 18b380de1..2e8e87637 100644 --- a/docs/stackit_beta_sqlserverflex_user_reset-password.md +++ b/docs/stackit_beta_sqlserverflex_user_reset-password.md @@ -1,10 +1,10 @@ ## stackit beta sqlserverflex user reset-password -Resets the password of a SQLServer Flex user +Resets the password of an SQLServer Flex user ### Synopsis -Resets the password of a SQLServer Flex user. +Resets the password of an SQLServer Flex user. sThe new password is visible after and cannot be retrieved later. ``` @@ -14,7 +14,7 @@ stackit beta sqlserverflex user reset-password USER_ID [flags] ### Examples ``` - Reset the password of a SQLServer Flex user with ID "xxx" of instance with ID "yyy" + Reset the password of an SQLServer Flex user with ID "xxx" of instance with ID "yyy" $ stackit sqlserverflex user reset-password xxx --instance-id yyy ``` diff --git a/internal/cmd/beta/sqlserverflex/user/create/create.go b/internal/cmd/beta/sqlserverflex/user/create/create.go index e74a51e09..c0097d46f 100644 --- a/internal/cmd/beta/sqlserverflex/user/create/create.go +++ b/internal/cmd/beta/sqlserverflex/user/create/create.go @@ -41,9 +41,9 @@ type inputModel struct { func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "create", - Short: "Creates a SQLServer Flex user", + Short: "Creates an SQLServer Flex user", Long: fmt.Sprintf("%s\n%s\n%s\n%s\n%s", - "Creates a SQLServer Flex user login for an instance.", + "Creates an SQLServer Flex user login for an instance.", "The password is only visible upon creation and cannot be retrieved later.", "Alternatively, you can reset the password and access the new one by running:", " $ stackit sqlserverflex user reset-password USER_ID --instance-id INSTANCE_ID", @@ -51,7 +51,7 @@ func NewCmd(p *print.Printer) *cobra.Command { ), Example: examples.Build( examples.NewExample( - `Create a SQLServer Flex user for instance with ID "xxx" and specify the username, role and database`, + `Create an SQLServer Flex user for instance with ID "xxx" and specify the username, role and database`, "$ stackit sqlserverflex user create --instance-id xxx --username johndoe --role my-role --database my-database"), ), Args: args.NoArgs, diff --git a/internal/cmd/beta/sqlserverflex/user/reset-password/reset_password.go b/internal/cmd/beta/sqlserverflex/user/reset-password/reset_password.go index 85e08d84b..3dfb3f1c2 100644 --- a/internal/cmd/beta/sqlserverflex/user/reset-password/reset_password.go +++ b/internal/cmd/beta/sqlserverflex/user/reset-password/reset_password.go @@ -35,14 +35,14 @@ type inputModel struct { func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: fmt.Sprintf("reset-password %s", userIdArg), - Short: "Resets the password of a SQLServer Flex user", + Short: "Resets the password of an SQLServer Flex user", Long: fmt.Sprintf("%s\ns%s", - "Resets the password of a SQLServer Flex user.", + "Resets the password of an SQLServer Flex user.", "The new password is visible after and cannot be retrieved later.", ), Example: examples.Build( examples.NewExample( - `Reset the password of a SQLServer Flex user with ID "xxx" of instance with ID "yyy"`, + `Reset the password of an SQLServer Flex user with ID "xxx" of instance with ID "yyy"`, "$ stackit sqlserverflex user reset-password xxx --instance-id yyy"), ), Args: args.SingleArg(userIdArg, nil), From 53c0e39d3265ff6d6911b6f57f2b5bd8ee7fa592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Palet?= Date: Wed, 19 Jun 2024 15:10:10 +0100 Subject: [PATCH 5/8] Fix tests --- .../beta/sqlserverflex/user/create/create.go | 4 -- .../sqlserverflex/user/create/create_test.go | 43 ++++++++----------- .../reset-password/reset_password_test.go | 12 ------ 3 files changed, 18 insertions(+), 41 deletions(-) diff --git a/internal/cmd/beta/sqlserverflex/user/create/create.go b/internal/cmd/beta/sqlserverflex/user/create/create.go index c0097d46f..6dbfce100 100644 --- a/internal/cmd/beta/sqlserverflex/user/create/create.go +++ b/internal/cmd/beta/sqlserverflex/user/create/create.go @@ -25,10 +25,6 @@ const ( roleFlag = "role" ) -var ( - rolesDefault = []string{"read"} -) - type inputModel struct { *globalflags.GlobalFlagModel diff --git a/internal/cmd/beta/sqlserverflex/user/create/create_test.go b/internal/cmd/beta/sqlserverflex/user/create/create_test.go index 6d7679953..328296613 100644 --- a/internal/cmd/beta/sqlserverflex/user/create/create_test.go +++ b/internal/cmd/beta/sqlserverflex/user/create/create_test.go @@ -88,9 +88,26 @@ func TestParseInput(t *testing.T) { flagValues: fixtureFlagValues(func(flagValues map[string]string) { delete(flagValues, usernameFlag) }), + isValid: false, + }, + { + description: "no database specified", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, databaseFlag) + }), isValid: true, expectedModel: fixtureInputModel(func(model *inputModel) { - model.Username = nil + model.Database = nil + }), + }, + { + description: "no roles specified", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, roleFlag) + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Roles = nil }), }, { @@ -133,30 +150,6 @@ func TestParseInput(t *testing.T) { }), isValid: false, }, - { - description: "database missing", - flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, databaseFlag) - }), - isValid: false, - }, - { - description: "roles missing", - flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, roleFlag) - }), - isValid: true, - expectedModel: fixtureInputModel(func(model *inputModel) { - model.Roles = &rolesDefault - }), - }, - { - description: "invalid role", - flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[roleFlag] = "invalid-role" - }), - isValid: false, - }, } for _, tt := range tests { diff --git a/internal/cmd/beta/sqlserverflex/user/reset-password/reset_password_test.go b/internal/cmd/beta/sqlserverflex/user/reset-password/reset_password_test.go index e0a472ae1..b60011db4 100644 --- a/internal/cmd/beta/sqlserverflex/user/reset-password/reset_password_test.go +++ b/internal/cmd/beta/sqlserverflex/user/reset-password/reset_password_test.go @@ -148,18 +148,6 @@ func TestParseInput(t *testing.T) { }), isValid: false, }, - { - description: "user id invalid 1", - argValues: []string{""}, - flagValues: fixtureFlagValues(), - isValid: false, - }, - { - description: "user id invalid 2", - argValues: []string{"invalid-uuid"}, - flagValues: fixtureFlagValues(), - isValid: false, - }, } for _, tt := range tests { From ad9829f3a0ba11409cb47a989788173608cafeb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Palet?= Date: Wed, 19 Jun 2024 15:22:37 +0100 Subject: [PATCH 6/8] Fixes after review --- .../cmd/beta/sqlserverflex/user/create/create.go | 15 +++++++++------ .../beta/sqlserverflex/user/create/create_test.go | 4 ++-- .../user/reset-password/reset_password.go | 4 ++-- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/internal/cmd/beta/sqlserverflex/user/create/create.go b/internal/cmd/beta/sqlserverflex/user/create/create.go index 6dbfce100..88d352a54 100644 --- a/internal/cmd/beta/sqlserverflex/user/create/create.go +++ b/internal/cmd/beta/sqlserverflex/user/create/create.go @@ -22,7 +22,7 @@ const ( instanceIdFlag = "instance-id" usernameFlag = "username" databaseFlag = "database" - roleFlag = "role" + rolesFlag = "roles" ) type inputModel struct { @@ -39,16 +39,19 @@ func NewCmd(p *print.Printer) *cobra.Command { Use: "create", Short: "Creates an SQLServer Flex user", Long: fmt.Sprintf("%s\n%s\n%s\n%s\n%s", - "Creates an SQLServer Flex user login for an instance.", + "Creates an SQLServer Flex user for an instance.", "The password is only visible upon creation and cannot be retrieved later.", "Alternatively, you can reset the password and access the new one by running:", - " $ stackit sqlserverflex user reset-password USER_ID --instance-id INSTANCE_ID", + " $ stackit beta sqlserverflex user reset-password USER_ID --instance-id INSTANCE_ID", "Please refer to https://docs.stackit.cloud/stackit/en/creating-logins-and-users-in-sqlserver-flex-instances-210862358.html for additional information.", ), Example: examples.Build( examples.NewExample( `Create an SQLServer Flex user for instance with ID "xxx" and specify the username, role and database`, - "$ stackit sqlserverflex user create --instance-id xxx --username johndoe --role my-role --database my-database"), + "$ stackit beta sqlserverflex user create --instance-id xxx --username johndoe --roles my-role --database my-database"), + examples.NewExample( + `Create an SQLServer Flex user for instance with ID "xxx", specifying multiple roles`, + `$ stackit beta sqlserverflex user create --instance-id xxx --username johndoe --roles "my-role-1,my-role-2`), ), Args: args.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { @@ -98,7 +101,7 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "ID of the instance") cmd.Flags().String(usernameFlag, "", "Username of the user") cmd.Flags().String(databaseFlag, "", "Default database for the user") - cmd.Flags().StringSlice(roleFlag, []string{}, "Roles of the user") + cmd.Flags().StringSlice(rolesFlag, []string{}, "Roles of the user") err := flags.MarkFlagsRequired(cmd, instanceIdFlag, usernameFlag) cobra.CheckErr(err) @@ -115,7 +118,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { InstanceId: flags.FlagToStringValue(p, cmd, instanceIdFlag), Username: flags.FlagToStringPointer(p, cmd, usernameFlag), Database: flags.FlagToStringPointer(p, cmd, databaseFlag), - Roles: flags.FlagToStringSlicePointer(p, cmd, roleFlag), + Roles: flags.FlagToStringSlicePointer(p, cmd, rolesFlag), } if p.IsVerbosityDebug() { diff --git a/internal/cmd/beta/sqlserverflex/user/create/create_test.go b/internal/cmd/beta/sqlserverflex/user/create/create_test.go index 328296613..2f4cad3b0 100644 --- a/internal/cmd/beta/sqlserverflex/user/create/create_test.go +++ b/internal/cmd/beta/sqlserverflex/user/create/create_test.go @@ -30,7 +30,7 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st instanceIdFlag: testInstanceId, usernameFlag: "johndoe", databaseFlag: "default", - roleFlag: "read", + rolesFlag: "read", } for _, mod := range mods { mod(flagValues) @@ -103,7 +103,7 @@ func TestParseInput(t *testing.T) { { description: "no roles specified", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, roleFlag) + delete(flagValues, rolesFlag) }), isValid: true, expectedModel: fixtureInputModel(func(model *inputModel) { diff --git a/internal/cmd/beta/sqlserverflex/user/reset-password/reset_password.go b/internal/cmd/beta/sqlserverflex/user/reset-password/reset_password.go index 3dfb3f1c2..009ec914c 100644 --- a/internal/cmd/beta/sqlserverflex/user/reset-password/reset_password.go +++ b/internal/cmd/beta/sqlserverflex/user/reset-password/reset_password.go @@ -38,12 +38,12 @@ func NewCmd(p *print.Printer) *cobra.Command { Short: "Resets the password of an SQLServer Flex user", Long: fmt.Sprintf("%s\ns%s", "Resets the password of an SQLServer Flex user.", - "The new password is visible after and cannot be retrieved later.", + "The new password is visible after resetting and cannot be retrieved later.", ), Example: examples.Build( examples.NewExample( `Reset the password of an SQLServer Flex user with ID "xxx" of instance with ID "yyy"`, - "$ stackit sqlserverflex user reset-password xxx --instance-id yyy"), + "$ stackit beta sqlserverflex user reset-password xxx --instance-id yyy"), ), Args: args.SingleArg(userIdArg, nil), RunE: func(cmd *cobra.Command, args []string) error { From aa21c2b4f2c64d4c1d0fc7a22243d5412d862eaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Palet?= Date: Wed, 19 Jun 2024 15:26:16 +0100 Subject: [PATCH 7/8] Fixes after review 2 --- internal/cmd/beta/sqlserverflex/options/options.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/cmd/beta/sqlserverflex/options/options.go b/internal/cmd/beta/sqlserverflex/options/options.go index 789af6036..1e585bb55 100644 --- a/internal/cmd/beta/sqlserverflex/options/options.go +++ b/internal/cmd/beta/sqlserverflex/options/options.go @@ -87,10 +87,10 @@ func NewCmd(p *print.Printer) *cobra.Command { `List SQL Server Flex available versions`, "$ stackit beta sqlserverflex options --versions"), examples.NewExample( - `List SQL Server Flex storage options for a given flavor. The flavor ID can be retrieved by running "$ stackit sqlserverflex options --flavors"`, + `List SQL Server Flex storage options for a given flavor. The flavor ID can be retrieved by running "$ stackit beta sqlserverflex options --flavors"`, "$ stackit beta sqlserverflex options --storages --flavor-id "), examples.NewExample( - `List SQL Server Flex user roles and database compatibilities for a given instance. The IDs of existing instances can be obtained by running "$ stackit sqlserverflex instance list"`, + `List SQL Server Flex user roles and database compatibilities for a given instance. The IDs of existing instances can be obtained by running "$ stackit beta sqlserverflex instance list"`, "$ stackit beta sqlserverflex options --user-roles --db-compatibilities --instance-id "), ), RunE: func(cmd *cobra.Command, args []string) error { From 52306d01ffd3676c7ea98c80328ad88c9a60016e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Palet?= Date: Wed, 19 Jun 2024 15:27:42 +0100 Subject: [PATCH 8/8] Update docs --- docs/stackit_beta_sqlserverflex_options.md | 4 ++-- docs/stackit_beta_sqlserverflex_user_create.md | 11 +++++++---- .../stackit_beta_sqlserverflex_user_reset-password.md | 4 ++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/stackit_beta_sqlserverflex_options.md b/docs/stackit_beta_sqlserverflex_options.md index 30d76d30c..dd4a7063b 100644 --- a/docs/stackit_beta_sqlserverflex_options.md +++ b/docs/stackit_beta_sqlserverflex_options.md @@ -20,10 +20,10 @@ stackit beta sqlserverflex options [flags] List SQL Server Flex available versions $ stackit beta sqlserverflex options --versions - List SQL Server Flex storage options for a given flavor. The flavor ID can be retrieved by running "$ stackit sqlserverflex options --flavors" + List SQL Server Flex storage options for a given flavor. The flavor ID can be retrieved by running "$ stackit beta sqlserverflex options --flavors" $ stackit beta sqlserverflex options --storages --flavor-id - List SQL Server Flex user roles and database compatibilities for a given instance. The IDs of existing instances can be obtained by running "$ stackit sqlserverflex instance list" + List SQL Server Flex user roles and database compatibilities for a given instance. The IDs of existing instances can be obtained by running "$ stackit beta sqlserverflex instance list" $ stackit beta sqlserverflex options --user-roles --db-compatibilities --instance-id ``` diff --git a/docs/stackit_beta_sqlserverflex_user_create.md b/docs/stackit_beta_sqlserverflex_user_create.md index 2b63cc67a..aa9faa987 100644 --- a/docs/stackit_beta_sqlserverflex_user_create.md +++ b/docs/stackit_beta_sqlserverflex_user_create.md @@ -4,10 +4,10 @@ Creates an SQLServer Flex user ### Synopsis -Creates an SQLServer Flex user login for an instance. +Creates an SQLServer Flex user for an instance. The password is only visible upon creation and cannot be retrieved later. Alternatively, you can reset the password and access the new one by running: - $ stackit sqlserverflex user reset-password USER_ID --instance-id INSTANCE_ID + $ stackit beta sqlserverflex user reset-password USER_ID --instance-id INSTANCE_ID Please refer to https://docs.stackit.cloud/stackit/en/creating-logins-and-users-in-sqlserver-flex-instances-210862358.html for additional information. ``` @@ -18,7 +18,10 @@ stackit beta sqlserverflex user create [flags] ``` Create an SQLServer Flex user for instance with ID "xxx" and specify the username, role and database - $ stackit sqlserverflex user create --instance-id xxx --username johndoe --role my-role --database my-database + $ stackit beta sqlserverflex user create --instance-id xxx --username johndoe --roles my-role --database my-database + + Create an SQLServer Flex user for instance with ID "xxx", specifying multiple roles + $ stackit beta sqlserverflex user create --instance-id xxx --username johndoe --roles "my-role-1,my-role-2 ``` ### Options @@ -27,7 +30,7 @@ stackit beta sqlserverflex user create [flags] --database string Default database for the user -h, --help Help for "stackit beta sqlserverflex user create" --instance-id string ID of the instance - --role strings Roles of the user + --roles strings Roles of the user --username string Username of the user ``` diff --git a/docs/stackit_beta_sqlserverflex_user_reset-password.md b/docs/stackit_beta_sqlserverflex_user_reset-password.md index 2e8e87637..757438141 100644 --- a/docs/stackit_beta_sqlserverflex_user_reset-password.md +++ b/docs/stackit_beta_sqlserverflex_user_reset-password.md @@ -5,7 +5,7 @@ Resets the password of an SQLServer Flex user ### Synopsis Resets the password of an SQLServer Flex user. -sThe new password is visible after and cannot be retrieved later. +sThe new password is visible after resetting and cannot be retrieved later. ``` stackit beta sqlserverflex user reset-password USER_ID [flags] @@ -15,7 +15,7 @@ stackit beta sqlserverflex user reset-password USER_ID [flags] ``` Reset the password of an SQLServer Flex user with ID "xxx" of instance with ID "yyy" - $ stackit sqlserverflex user reset-password xxx --instance-id yyy + $ stackit beta sqlserverflex user reset-password xxx --instance-id yyy ``` ### Options