diff --git a/cmd/scw/testdata/test-all-usage-config-delete-usage.stderr.golden b/cmd/scw/testdata/test-all-usage-config-delete-usage.stderr.golden index e1e26af007..398d0f5bdd 100644 --- a/cmd/scw/testdata/test-all-usage-config-delete-usage.stderr.golden +++ b/cmd/scw/testdata/test-all-usage-config-delete-usage.stderr.golden @@ -1,17 +1,2 @@ Allows the deletion of a profile from the config file -USAGE: - scw config delete - -AVAILABLE COMMANDS: - profile Delete a profile from the config file - -FLAGS: - -h, --help help for delete - -GLOBAL FLAGS: - -D, --debug Enable debug mode - -o, --output string Output format: json or human - -p, --profile string The config profile to use - -Use "scw config delete [command] --help" for more information about a command. diff --git a/cmd/scw/testdata/test-all-usage-config-get-usage.stderr.golden b/cmd/scw/testdata/test-all-usage-config-get-usage.stderr.golden index c302fe9384..905cd4d5be 100644 --- a/cmd/scw/testdata/test-all-usage-config-get-usage.stderr.golden +++ b/cmd/scw/testdata/test-all-usage-config-get-usage.stderr.golden @@ -1,14 +1,17 @@ -Get a line from the config file +Get a value from the config file USAGE: - scw config get + scw config get [arg=value ...] EXAMPLES: Get the default organization ID scw config get default_organization_id Get the default region of the profile 'prod' - scw config get prod.default_region + scw -p prod config get default_region + +ARGS: + key the config config key name to get (send_telemetry | access_key | secret_key | api_url | insecure | default_organization_id | default_region | default_zone) FLAGS: -h, --help help for get diff --git a/cmd/scw/testdata/test-all-usage-config-delete-profile-usage.stderr.golden b/cmd/scw/testdata/test-all-usage-config-profile-delete-usage.stderr.golden similarity index 73% rename from cmd/scw/testdata/test-all-usage-config-delete-profile-usage.stderr.golden rename to cmd/scw/testdata/test-all-usage-config-profile-delete-usage.stderr.golden index b9410180cc..9af4bb892b 100644 --- a/cmd/scw/testdata/test-all-usage-config-delete-profile-usage.stderr.golden +++ b/cmd/scw/testdata/test-all-usage-config-profile-delete-usage.stderr.golden @@ -1,13 +1,13 @@ Delete a profile from the config file USAGE: - scw config delete profile [arg=value ...] + scw config profile delete [arg=value ...] ARGS: name FLAGS: - -h, --help help for profile + -h, --help help for delete GLOBAL FLAGS: -D, --debug Enable debug mode diff --git a/cmd/scw/testdata/test-all-usage-config-unset-usage.stderr.golden b/cmd/scw/testdata/test-all-usage-config-unset-usage.stderr.golden index 46f0147dc7..400353eaa5 100644 --- a/cmd/scw/testdata/test-all-usage-config-unset-usage.stderr.golden +++ b/cmd/scw/testdata/test-all-usage-config-unset-usage.stderr.golden @@ -1,7 +1,10 @@ Unset a line from the config file USAGE: - scw config unset + scw config unset [arg=value ...] + +ARGS: + key the config config key name to unset (send_telemetry | access_key | secret_key | api_url | insecure | default_organization_id | default_region | default_zone) FLAGS: -h, --help help for unset diff --git a/internal/namespaces/config/commands.go b/internal/namespaces/config/commands.go index 2423e21f67..f5c8ef1f91 100644 --- a/internal/namespaces/config/commands.go +++ b/internal/namespaces/config/commands.go @@ -4,8 +4,8 @@ import ( "bytes" "context" "fmt" + "path" "reflect" - "strings" "github.com/fatih/color" "github.com/scaleway/scaleway-cli/internal/args" @@ -13,13 +13,16 @@ import ( "github.com/scaleway/scaleway-cli/internal/interactive" "github.com/scaleway/scaleway-cli/internal/tabwriter" "github.com/scaleway/scaleway-cli/internal/terminal" - "github.com/scaleway/scaleway-sdk-go/logger" "github.com/scaleway/scaleway-sdk-go/scw" "github.com/scaleway/scaleway-sdk-go/strcase" ) // TODO: add proper tests +const ( + sendTelemetryKey = "send_telemetry" +) + func GetCommands() *core.Commands { return core.NewCommands( configRoot(), @@ -91,12 +94,26 @@ func configRoot() *core.Command { // configGetCommand gets one or many values for the scaleway config func configGetCommand() *core.Command { + + type configGetArgs struct { + Key string + } + return &core.Command{ - Short: `Get a line from the config file`, + Short: `Get a value from the config file`, Namespace: "config", Resource: "get", AllowAnonymousClient: true, - ArgsType: reflect.TypeOf(args.RawArgs{}), + ArgsType: reflect.TypeOf(configGetArgs{}), + ArgSpecs: core.ArgSpecs{ + { + Name: "key", + Short: "the config config key name to get", + Required: true, + EnumValues: getProfileKeys(), + Positional: true, + }, + }, Examples: []*core.Example{ { Short: "Get the default organization ID", @@ -104,7 +121,7 @@ func configGetCommand() *core.Command { }, { Short: "Get the default region of the profile 'prod'", - Raw: "scw config get prod.default_region", + Raw: "scw -p prod config get default_region", }, }, SeeAlsos: []*core.SeeAlso{ @@ -114,28 +131,29 @@ func configGetCommand() *core.Command { }, }, Run: func(ctx context.Context, argsI interface{}) (i interface{}, e error) { - config, err := scw.LoadConfig() - if err != nil { - return nil, err - } - rawArgs := *(argsI.(*args.RawArgs)) - if len(rawArgs) == 0 { - return nil, notEnoughArgsForConfigGetError() - } - profileName, key, err := splitProfileKey(rawArgs.GetPositionalArgs()[0]) + config, err := scw.LoadConfigFromPath(extractConfigPath(ctx)) if err != nil { return nil, err } - profile, err := getProfile(config, profileName) - if err != nil { - return nil, err + key := argsI.(*configGetArgs).Key + + // send_telemetry is the only key that is not in a profile but in the config object directly + if key == sendTelemetryKey { + return config.SendTelemetry, nil } - value, err := getProfileValue(profile, key) - if err != nil { - return nil, err + + profileName := core.ExtractProfileName(ctx) + profile := &config.Profile + if profileName != "" { + var exist bool + profile, exist = config.Profiles[profileName] + if !exist { + return nil, unknownProfileError(profileName) + } } - return value, nil + + return getProfileValue(profile, key) }, } } @@ -167,97 +185,129 @@ The only allowed attributes are access_key, secret_key, default_organization_id, }, }, Run: func(ctx context.Context, argsI interface{}) (i interface{}, err error) { + // Validate arguments rawArgs := *(argsI.(*args.RawArgs)) - profileName, key, value, err := validateRawArgsForConfigSet(rawArgs) + key, value, err := validateRawArgsForConfigSet(rawArgs) if err != nil { return nil, err } // Execute - config, err := scw.LoadConfig() + configPath := extractConfigPath(ctx) + config, err := scw.LoadConfigFromPath(configPath) if err != nil { return nil, err } - profile, err := getProfile(config, profileName) // There can not be an error if profileName is empty - if err != nil { - // We create the profile if it doesn't exist - if config.Profiles == nil { - config.Profiles = map[string]*scw.Profile{} + + // send_telemetry is the only key that is not in a profile but in the config object directly + if key == sendTelemetryKey { + config.SendTelemetry = scw.BoolPtr(value == "true") + } else { + profileName := core.ExtractProfileName(ctx) + profile := &config.Profile + if profileName != "" { + var exist bool + profile, exist = config.Profiles[profileName] + if !exist { + if config.Profiles == nil { + config.Profiles = map[string]*scw.Profile{} + } + config.Profiles[profileName] = &scw.Profile{} + profile = config.Profiles[profileName] + } + } + + err = setProfileValue(profile, key, value) + if err != nil { + return nil, err } - config.Profiles[profileName] = &scw.Profile{} - profile = config.Profiles[profileName] - } - err = setProfileValue(profile, key, value) - if err != nil { - return nil, err } // Save - err = config.Save() + err = config.SaveTo(configPath) if err != nil { return nil, err } - // Inform success - return configSetSuccess(rawArgs[0], value), nil + return &core.SuccessResult{ + Message: fmt.Sprintf("set %v %v successfully", key, value), + }, nil }, } } // configDumpCommand unsets a value for the scaleway config func configUnsetCommand() *core.Command { + + type configUnsetArgs struct { + Key string + } + return &core.Command{ Short: `Unset a line from the config file`, Namespace: "config", Resource: "unset", AllowAnonymousClient: true, - ArgsType: reflect.TypeOf(args.RawArgs{}), + ArgsType: reflect.TypeOf(configUnsetArgs{}), + ArgSpecs: core.ArgSpecs{ + { + Name: "key", + Short: "the config config key name to unset", + Required: true, + EnumValues: getProfileKeys(), + Positional: true, + }, + }, Run: func(ctx context.Context, argsI interface{}) (i interface{}, e error) { - config, err := scw.LoadConfig() - if err != nil { - return nil, err - } - rawArgs := *(argsI.(*args.RawArgs)) - if len(rawArgs) == 0 { - return nil, notEnoughArgsForConfigUnsetError() - } - if len(rawArgs) > 1 { - return nil, tooManyArgsForConfigUnsetError() - } - profileAndKey := rawArgs[0] - profileName, key, err := splitProfileKey(profileAndKey) - if err != nil { - return nil, err - } - profile, err := getProfile(config, profileName) + configPath := extractConfigPath(ctx) + config, err := scw.LoadConfigFromPath(configPath) if err != nil { return nil, err } - logger.Debugf("conf before: %v", config) - err = unsetProfileValue(profile, key) - if err != nil { - return nil, err + key := argsI.(*configUnsetArgs).Key + + if key == sendTelemetryKey { + config.SendTelemetry = nil + } else { + profileName := core.ExtractProfileName(ctx) + profile := &config.Profile + if profileName != "" { + var exist bool + profile, exist = config.Profiles[profileName] + if !exist { + return nil, unknownProfileError(profileName) + } + } + err = unsetProfileValue(profile, key) + if err != nil { + return nil, err + } } - logger.Debugf("conf after: %v", config) - err = config.Save() + + err = config.SaveTo(configPath) if err != nil { return nil, err } - return configUnsetSuccess(profileAndKey), nil + return &core.SuccessResult{ + Message: fmt.Sprintf("unset %v successfully", key), + }, nil }, } } // configDumpCommand dumps the scaleway config func configDumpCommand() *core.Command { + + type configDumpArgs struct{} + return &core.Command{ Short: `Dump the config file`, Namespace: "config", Resource: "dump", AllowAnonymousClient: true, - ArgsType: reflect.TypeOf(args.RawArgs{}), + ArgsType: reflect.TypeOf(configDumpArgs{}), SeeAlsos: []*core.SeeAlso{ { Short: "Config management help", @@ -265,7 +315,8 @@ func configDumpCommand() *core.Command { }, }, Run: func(ctx context.Context, argsI interface{}) (i interface{}, e error) { - config, err := scw.LoadConfig() + configPath := extractConfigPath(ctx) + config, err := scw.LoadConfigFromPath(configPath) if err != nil { return nil, err } @@ -283,28 +334,31 @@ func configDeleteCommand() *core.Command { } } -type configDeleteProfileArgs struct { - Name string -} - // configDeleteProfileCommand deletes a profile from the config func configDeleteProfileCommand() *core.Command { + + type configDeleteProfileArgs struct { + Name string + } + return &core.Command{ Short: `Delete a profile from the config file`, Namespace: "config", - Resource: "delete", - Verb: "profile", + Resource: "profile", + Verb: "delete", AllowAnonymousClient: true, ArgsType: reflect.TypeOf(configDeleteProfileArgs{}), ArgSpecs: core.ArgSpecs{ { - Name: "name", - Required: true, + Name: "name", + Required: true, + Positional: true, }, }, Run: func(ctx context.Context, argsI interface{}) (i interface{}, e error) { profileName := argsI.(*configDeleteProfileArgs).Name - config, err := scw.LoadConfig() + configPath := extractConfigPath(ctx) + config, err := scw.LoadConfigFromPath(configPath) if err != nil { return nil, err } @@ -313,24 +367,29 @@ func configDeleteProfileCommand() *core.Command { } else { return nil, unknownProfileError(profileName) } - err = config.Save() + err = config.SaveTo(configPath) if err != nil { return nil, err } - return configDeleteProfileSuccess(profileName), nil + return &core.SuccessResult{ + Message: fmt.Sprintf("profile '%s' deleted successfully", profileName), + }, nil }, } } // configResetCommand resets the config func configResetCommand() *core.Command { + + type configResetArgs struct{} + return &core.Command{ Short: `Reset the config`, Namespace: "config", Resource: "reset", AllowAnonymousClient: true, - ArgsType: reflect.TypeOf(args.RawArgs{}), + ArgsType: reflect.TypeOf(configResetArgs{}), Run: func(ctx context.Context, argsI interface{}) (i interface{}, e error) { _, err := scw.LoadConfig() if err != nil { @@ -341,7 +400,9 @@ func configResetCommand() *core.Command { if err != nil { return nil, err } - return configResetSuccess(), nil + return &core.SuccessResult{ + Message: "reset config successfully", + }, nil }, } } @@ -349,44 +410,20 @@ func configResetCommand() *core.Command { // // Helper functions // - -// getProfile returns scw.Config.Profiles[profileName] or scw.Config.Profile if profileName is empty. -func getProfile(config *scw.Config, profileName string) (profile *scw.Profile, err error) { - if profileName != "" { - profile, err := config.GetProfile(profileName) - if err != nil { - return nil, err - } - return profile, nil - } - return &config.Profile, nil -} - -// splitProfileKey splits a "profile.key" string into ("profile", "key") -func splitProfileKey(arg string) (profileName string, key string, err error) { - strs := strings.Split(arg, ".") - if len(strs) == 1 { - return "", strs[0], nil - } - if len(strs) == 2 { - return strs[0], strs[1], nil +func getProfileValue(profile *scw.Profile, fieldName string) (interface{}, error) { + field, err := getProfileField(profile, fieldName) + if err != nil { + return nil, err } - return "", "", invalidProfileKeyPairError(arg) + return field.Interface(), nil } -func getProfileValue(profile *scw.Profile, fieldName string) (string, error) { - field := reflect.ValueOf(profile).Elem().FieldByName(strcase.ToPublicGoName(fieldName)) - if !field.IsValid() { - return "", invalidProfileKeyIdentifierError(fieldName) - } - if field.IsNil() { - return "", nilFieldError(fieldName) +func setProfileValue(profile *scw.Profile, fieldName string, value string) error { + field, err := getProfileField(profile, fieldName) + if err != nil { + return err } - return fmt.Sprint(field.Elem().Interface()), nil -} -func setProfileValue(profile *scw.Profile, fieldName string, value string) error { - field := reflect.ValueOf(profile).Elem().FieldByName(strcase.ToPublicGoName(fieldName)) switch kind := field.Type().Elem().Kind(); kind { case reflect.String: field.Set(reflect.ValueOf(&value)) @@ -399,7 +436,7 @@ func setProfileValue(profile *scw.Profile, fieldName string, value string) error } func unsetProfileValue(profile *scw.Profile, key string) error { - field, err := getProfileAttribute(profile, key) + field, err := getProfileField(profile, key) if err != nil { return err } @@ -407,10 +444,33 @@ func unsetProfileValue(profile *scw.Profile, key string) error { return nil } -func getProfileAttribute(profile *scw.Profile, key string) (reflect.Value, error) { +func getProfileField(profile *scw.Profile, key string) (reflect.Value, error) { field := reflect.ValueOf(profile).Elem().FieldByName(strcase.ToPublicGoName(key)) if !field.IsValid() { - return reflect.ValueOf(nil), invalidProfileAttributeError(key) + return reflect.ValueOf(nil), invalidProfileKeyError(key) } return field, nil } + +func getProfileKeys() []string { + t := reflect.TypeOf(scw.Profile{}) + keys := []string{ + "send_telemetry", + } + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + switch field.Name { + case "APIURL": + keys = append(keys, "api_url") + default: + keys = append(keys, strcase.ToSnake(t.Field(i).Name)) + } + } + return keys +} + +// This func should be removes when core implement it +func extractConfigPath(ctx context.Context) string { + homeDir := core.ExtractUserHomeDir(ctx) + return path.Join(homeDir, ".config", "scw", "config.yaml") +} diff --git a/internal/namespaces/config/commands_test.go b/internal/namespaces/config/commands_test.go new file mode 100644 index 0000000000..313e2a2daf --- /dev/null +++ b/internal/namespaces/config/commands_test.go @@ -0,0 +1,236 @@ +package config + +import ( + "os" + "path" + "testing" + + "github.com/alecthomas/assert" + "github.com/scaleway/scaleway-cli/internal/core" + "github.com/scaleway/scaleway-sdk-go/scw" + "github.com/stretchr/testify/require" +) + +func Test_ConfigGetCommand(t *testing.T) { + + t.Run("Simple", core.Test(&core.TestConfig{ + Commands: GetCommands(), + BeforeFunc: beforeFuncCreateConfigFile(&scw.Config{ + Profile: scw.Profile{ + AccessKey: scw.StringPtr("mock-access-key"), + }, + }), + Cmd: "scw config get access_key", + Check: core.TestCheckCombine( + core.TestCheckExitCode(0), + core.TestCheckGolden(), + ), + TmpHomeDir: true, + })) + + t.Run("Profile", core.Test(&core.TestConfig{ + Commands: GetCommands(), + BeforeFunc: beforeFuncCreateConfigFile(&scw.Config{ + Profile: scw.Profile{}, + Profiles: map[string]*scw.Profile{ + "test": { + AccessKey: scw.StringPtr("mock-access-key"), + }, + }, + }), + Cmd: "scw -p test config get access_key", + Check: core.TestCheckCombine( + core.TestCheckExitCode(0), + core.TestCheckGolden(), + ), + TmpHomeDir: true, + })) + + t.Run("Telemetry", core.Test(&core.TestConfig{ + Commands: GetCommands(), + BeforeFunc: beforeFuncCreateConfigFile(&scw.Config{ + SendTelemetry: scw.BoolPtr(true), + }), + Cmd: "scw -p test config get send_telemetry", + Check: core.TestCheckCombine( + core.TestCheckExitCode(0), + core.TestCheckGolden(), + ), + TmpHomeDir: true, + })) +} + +func Test_ConfigSetCommand(t *testing.T) { + + t.Run("Simple", core.Test(&core.TestConfig{ + Commands: GetCommands(), + BeforeFunc: beforeFuncCreateFullConfig(), + Cmd: "scw config set access_key mock-access-key", + Check: core.TestCheckCombine( + core.TestCheckExitCode(0), + core.TestCheckGolden(), + checkConfig(func(t *testing.T, config *scw.Config) { + assert.Equal(t, "mock-access-key", *config.AccessKey) + }), + ), + TmpHomeDir: true, + })) + + t.Run("Profile", core.Test(&core.TestConfig{ + Commands: GetCommands(), + BeforeFunc: beforeFuncCreateFullConfig(), + Cmd: "scw -p p1 config set access_key mock-access-key", + Check: core.TestCheckCombine( + core.TestCheckExitCode(0), + core.TestCheckGolden(), + checkConfig(func(t *testing.T, config *scw.Config) { + assert.Equal(t, "mock-access-key", *config.Profiles["test"].AccessKey) + }), + ), + TmpHomeDir: true, + })) + + t.Run("Telemetry", core.Test(&core.TestConfig{ + Commands: GetCommands(), + BeforeFunc: beforeFuncCreateFullConfig(), + Cmd: "scw config set send_telemetry true", + Check: core.TestCheckCombine( + core.TestCheckExitCode(0), + core.TestCheckGolden(), + checkConfig(func(t *testing.T, config *scw.Config) { + assert.Equal(t, true, *config.SendTelemetry) + }), + ), + TmpHomeDir: true, + })) +} + +func Test_ConfigUnsetCommand(t *testing.T) { + + t.Run("Simple", core.Test(&core.TestConfig{ + Commands: GetCommands(), + BeforeFunc: beforeFuncCreateFullConfig(), + Cmd: "scw config unset access_key", + Check: core.TestCheckCombine( + core.TestCheckExitCode(0), + core.TestCheckGolden(), + checkConfig(func(t *testing.T, config *scw.Config) { + assert.Nil(t, config.AccessKey) + }), + ), + TmpHomeDir: true, + })) + + t.Run("Profile", core.Test(&core.TestConfig{ + Commands: GetCommands(), + BeforeFunc: beforeFuncCreateFullConfig(), + Cmd: "scw -p p1 config unset access_key", + Check: core.TestCheckCombine( + core.TestCheckExitCode(0), + core.TestCheckGolden(), + checkConfig(func(t *testing.T, config *scw.Config) { + assert.Nil(t, config.Profiles["test"].AccessKey) + }), + ), + TmpHomeDir: true, + })) + + t.Run("Telemetry", core.Test(&core.TestConfig{ + Commands: GetCommands(), + BeforeFunc: beforeFuncCreateFullConfig(), + Cmd: "scw config unset send_telemetry", + Check: core.TestCheckCombine( + core.TestCheckExitCode(0), + core.TestCheckGolden(), + checkConfig(func(t *testing.T, config *scw.Config) { + assert.Nil(t, config.SendTelemetry) + }), + ), + TmpHomeDir: true, + })) +} + +func Test_ConfigDeleteProfileCommand(t *testing.T) { + t.Run("Simple", core.Test(&core.TestConfig{ + Commands: GetCommands(), + BeforeFunc: beforeFuncCreateFullConfig(), + Cmd: "scw config profile delete p2", + Check: core.TestCheckCombine( + core.TestCheckExitCode(0), + core.TestCheckGolden(), + checkConfig(func(t *testing.T, config *scw.Config) { + assert.Nil(t, config.Profiles["p2"]) + }), + ), + TmpHomeDir: true, + })) +} + +func Test_ConfigDumpCommand(t *testing.T) { + t.Run("Simple", core.Test(&core.TestConfig{ + Commands: GetCommands(), + BeforeFunc: beforeFuncCreateFullConfig(), + Cmd: "scw config dump", + Check: core.TestCheckCombine( + core.TestCheckExitCode(0), + core.TestCheckGolden(), + ), + TmpHomeDir: true, + })) +} + +func checkConfig(f func(t *testing.T, config *scw.Config)) core.TestCheck { + return func(t *testing.T, ctx *core.CheckFuncCtx) { + homeDir := ctx.OverrideEnv["HOME"] + config, err := scw.LoadConfigFromPath(path.Join(homeDir, ".config", "scw", "config.yaml")) + require.NoError(t, err) + f(t, config) + } +} + +func beforeFuncCreateConfigFile(c *scw.Config) core.BeforeFunc { + return func(ctx *core.BeforeFuncCtx) error { + homeDir := ctx.OverrideEnv["HOME"] + scwDir := path.Join(homeDir, ".config", "scw") + err := os.MkdirAll(scwDir, 0755) + if err != nil { + return err + } + + return c.SaveTo(path.Join(scwDir, "config.yaml")) + } +} + +func beforeFuncCreateFullConfig() core.BeforeFunc { + return beforeFuncCreateConfigFile(&scw.Config{ + Profile: scw.Profile{ + AccessKey: scw.StringPtr("mock-access-key"), + SecretKey: scw.StringPtr("mock-secret-key"), + APIURL: scw.StringPtr("mock-api-url"), + Insecure: scw.BoolPtr(true), + DefaultOrganizationID: scw.StringPtr("mock-orgaid"), + DefaultRegion: scw.StringPtr("fr-par"), + DefaultZone: scw.StringPtr("fr-par-1"), + }, + Profiles: map[string]*scw.Profile{ + "p1": { + AccessKey: scw.StringPtr("p1-mock-access-key"), + SecretKey: scw.StringPtr("p1-mock-secret-key"), + APIURL: scw.StringPtr("p1-mock-api-url"), + Insecure: scw.BoolPtr(true), + DefaultOrganizationID: scw.StringPtr("p1-mock-orgaid"), + DefaultRegion: scw.StringPtr("fr-par"), + DefaultZone: scw.StringPtr("fr-par-1"), + }, + "p2": { + AccessKey: scw.StringPtr("p2-mock-access-key"), + SecretKey: scw.StringPtr("p2-mock-secret-key"), + APIURL: scw.StringPtr("p2-mock-api-url"), + Insecure: scw.BoolPtr(true), + DefaultOrganizationID: scw.StringPtr("p2-mock-orgaid"), + DefaultRegion: scw.StringPtr("fr-par"), + DefaultZone: scw.StringPtr("fr-par-1"), + }, + }, + }) +} diff --git a/internal/namespaces/config/errors.go b/internal/namespaces/config/errors.go index 07904a5e5e..28e30dd3cd 100644 --- a/internal/namespaces/config/errors.go +++ b/internal/namespaces/config/errors.go @@ -11,16 +11,10 @@ import ( "github.com/scaleway/scaleway-cli/internal/core" ) -func defaultCliError(err error) *core.CliError { - return &core.CliError{ - Err: err, - } -} - func invalidDefaultOrganizationIDError(value string) *core.CliError { return &core.CliError{ - Err: fmt.Errorf("invalid default_organization_id '%v'", value), - Hint: "default_organization_id should be a valid UUID, formatted as: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX.", + Message: fmt.Sprintf("invalid default_organization_id '%v'", value), + Hint: "default_organization_id should be a valid UUID, formatted as: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX.", } } @@ -44,37 +38,19 @@ func invalidZoneError(value string) *core.CliError { func notEnoughArgsForConfigSetError() *core.CliError { return &core.CliError{ - Err: defaultCliError(fmt.Errorf("not enough args: enter a key and a value")), + Err: fmt.Errorf("not enough args: enter a key and a value"), } } func missingValueForConfigSetError(key string) *core.CliError { return &core.CliError{ - Err: defaultCliError(fmt.Errorf("missing value for key '%v'", key)), + Err: fmt.Errorf("missing value for key '%v'", key), } } func tooManyArgsForConfigSetError() *core.CliError { return &core.CliError{ - Err: defaultCliError(fmt.Errorf("too many args: only one value can be set at a time")), - } -} - -func notEnoughArgsForConfigGetError() *core.CliError { - return &core.CliError{ - Err: fmt.Errorf("not enough args: enter a key"), - } -} - -func notEnoughArgsForConfigUnsetError() *core.CliError { - return &core.CliError{ - Err: fmt.Errorf("not enough args: enter a key"), - } -} - -func tooManyArgsForConfigUnsetError() *core.CliError { - return &core.CliError{ - Err: fmt.Errorf("too many args: only one value can be unset at a time"), + Err: fmt.Errorf("too many args: only one value can be set at a time"), } } @@ -84,32 +60,8 @@ func unknownProfileError(profileName string) *core.CliError { } } -func invalidProfileKeyPairError(arg string) *core.CliError { - return &core.CliError{ - Err: fmt.Errorf("invalid profile/key pair identifier '%v'", arg), - } -} - -func invalidProfileKeyIdentifierError(fieldName string) *core.CliError { - return &core.CliError{ - Err: fmt.Errorf("invalid profile's key identifier '%v'", fieldName), - } -} - -func nilFieldError(fieldName string) *core.CliError { - return &core.CliError{ - Err: fmt.Errorf("field not set '%v'", fieldName), - } -} - func invalidKindForKeyError(kind reflect.Kind, fieldName string) *core.CliError { return &core.CliError{ Err: fmt.Errorf("invalid kind '%v' for key '%v'", kind, fieldName), } } - -func invalidProfileAttributeError(key string) *core.CliError { - return &core.CliError{ - Err: fmt.Errorf("invalid profile attribute: '%v'", key), - } -} diff --git a/internal/namespaces/config/success.go b/internal/namespaces/config/success.go deleted file mode 100644 index 1a976fd0cc..0000000000 --- a/internal/namespaces/config/success.go +++ /dev/null @@ -1,35 +0,0 @@ -package config - -// -// Functions in this file can only return non-nil *core.SuccessResult. -// - -import ( - "fmt" - - "github.com/scaleway/scaleway-cli/internal/core" -) - -func configSetSuccess(profileAndKey string, value string) *core.SuccessResult { - return &core.SuccessResult{ - Message: fmt.Sprintf("set %v %v successfully", profileAndKey, value), - } -} - -func configUnsetSuccess(profileAndKey string) *core.SuccessResult { - return &core.SuccessResult{ - Message: fmt.Sprintf("unset %v successfully", profileAndKey), - } -} - -func configDeleteProfileSuccess(profileName string) *core.SuccessResult { - return &core.SuccessResult{ - Message: fmt.Sprintf("profile '%s' deleted successfully", profileName), - } -} - -func configResetSuccess() *core.SuccessResult { - return &core.SuccessResult{ - Message: "reset config successfully", - } -} diff --git a/internal/namespaces/config/testdata/test-config-delete-profile-command-simple.stdout.golden b/internal/namespaces/config/testdata/test-config-delete-profile-command-simple.stdout.golden new file mode 100644 index 0000000000..179b968d13 --- /dev/null +++ b/internal/namespaces/config/testdata/test-config-delete-profile-command-simple.stdout.golden @@ -0,0 +1 @@ +✅ Profile 'p2' deleted successfully. diff --git a/internal/namespaces/config/testdata/test-config-dump-command-simple.stdout.golden b/internal/namespaces/config/testdata/test-config-dump-command-simple.stdout.golden new file mode 100644 index 0000000000..0f319c6b3c --- /dev/null +++ b/internal/namespaces/config/testdata/test-config-dump-command-simple.stdout.golden @@ -0,0 +1,24 @@ +access_key: mock-access-key +secret_key: mock-sec-xxxx-xxxx-xxxx-xxxxxxxxxxxx +insecure: true +default_organization_id: mock-orgaid +default_region: fr-par +default_zone: fr-par-1 +profiles: + p1: + access_key: p1-mock-access-key + secret_key: p1-mock--xxxx-xxxx-xxxx-xxxxxxxxxxxx + api_url: p1-mock-api-url + insecure: true + default_organization_id: p1-mock-orgaid + default_region: fr-par + default_zone: fr-par-1 + p2: + access_key: p2-mock-access-key + secret_key: p2-mock--xxxx-xxxx-xxxx-xxxxxxxxxxxx + api_url: p2-mock-api-url + insecure: true + default_organization_id: p2-mock-orgaid + default_region: fr-par + default_zone: fr-par-1 + diff --git a/internal/namespaces/config/testdata/test-config-get-command-profile.stdout.golden b/internal/namespaces/config/testdata/test-config-get-command-profile.stdout.golden new file mode 100644 index 0000000000..ccbd5969cb --- /dev/null +++ b/internal/namespaces/config/testdata/test-config-get-command-profile.stdout.golden @@ -0,0 +1 @@ +mock-access-key diff --git a/internal/namespaces/config/testdata/test-config-get-command-simple.stdout.golden b/internal/namespaces/config/testdata/test-config-get-command-simple.stdout.golden new file mode 100644 index 0000000000..ccbd5969cb --- /dev/null +++ b/internal/namespaces/config/testdata/test-config-get-command-simple.stdout.golden @@ -0,0 +1 @@ +mock-access-key diff --git a/internal/namespaces/config/testdata/test-config-get-command-telemetry.stdout.golden b/internal/namespaces/config/testdata/test-config-get-command-telemetry.stdout.golden new file mode 100644 index 0000000000..27ba77ddaf --- /dev/null +++ b/internal/namespaces/config/testdata/test-config-get-command-telemetry.stdout.golden @@ -0,0 +1 @@ +true diff --git a/internal/namespaces/config/testdata/test-config-unset-command-profile.stdout.golden b/internal/namespaces/config/testdata/test-config-unset-command-profile.stdout.golden new file mode 100644 index 0000000000..476b40a858 --- /dev/null +++ b/internal/namespaces/config/testdata/test-config-unset-command-profile.stdout.golden @@ -0,0 +1 @@ +✅ Unset access_key successfully. diff --git a/internal/namespaces/config/testdata/test-config-unset-command-simple.stdout.golden b/internal/namespaces/config/testdata/test-config-unset-command-simple.stdout.golden new file mode 100644 index 0000000000..476b40a858 --- /dev/null +++ b/internal/namespaces/config/testdata/test-config-unset-command-simple.stdout.golden @@ -0,0 +1 @@ +✅ Unset access_key successfully. diff --git a/internal/namespaces/config/testdata/test-config-unset-command-telemetry.stdout.golden b/internal/namespaces/config/testdata/test-config-unset-command-telemetry.stdout.golden new file mode 100644 index 0000000000..9da44f38c5 --- /dev/null +++ b/internal/namespaces/config/testdata/test-config-unset-command-telemetry.stdout.golden @@ -0,0 +1 @@ +✅ Unset send_telemetry successfully. diff --git a/internal/namespaces/config/validate.go b/internal/namespaces/config/validate.go index 86e88919a0..c4c8c09fa8 100644 --- a/internal/namespaces/config/validate.go +++ b/internal/namespaces/config/validate.go @@ -1,50 +1,44 @@ package config import ( - "reflect" - "github.com/scaleway/scaleway-cli/internal/args" "github.com/scaleway/scaleway-cli/internal/core" "github.com/scaleway/scaleway-sdk-go/scw" - "github.com/scaleway/scaleway-sdk-go/strcase" "github.com/scaleway/scaleway-sdk-go/validation" ) -func validateRawArgsForConfigSet(rawArgs args.RawArgs) (profile string, key string, value string, err error) { +func validateRawArgsForConfigSet(rawArgs args.RawArgs) (key string, value string, err error) { if len(rawArgs) == 0 { - return "", "", "", notEnoughArgsForConfigSetError() + return "", "", notEnoughArgsForConfigSetError() } if len(rawArgs) == 1 { - return "", "", "", missingValueForConfigSetError(rawArgs[0]) + return "", "", missingValueForConfigSetError(rawArgs[0]) } if len(rawArgs) > 2 { - return "", "", "", tooManyArgsForConfigSetError() + return "", "", tooManyArgsForConfigSetError() } - profileAndKey := rawArgs[0] + key = rawArgs[0] value = rawArgs[1] - profile, key, err = splitProfileKey(profileAndKey) - if err != nil { - return - } err = validateProfileKey(key) if err != nil { - return + return "", "", err } err = validateProfileValue(key, value) if err != nil { - return + return "", "", err } - return + return key, value, nil } func validateProfileKey(fieldName string) error { - field := reflect.ValueOf(&scw.Profile{}).Elem().FieldByName(strcase.ToPublicGoName(fieldName)) - if !field.IsValid() { - return invalidProfileKeyError(fieldName) + if fieldName == sendTelemetryKey { + return nil } - return nil + + _, err := getProfileField(&scw.Profile{}, fieldName) + return err } func validateProfileValue(fieldName string, value string) error { diff --git a/internal/printer/json.go b/internal/printer/json.go index 1f284b014f..edc53edd06 100644 --- a/internal/printer/json.go +++ b/internal/printer/json.go @@ -33,6 +33,7 @@ func (o *jsonPrinter) Print(data interface{}, opt *human.MarshalOpt) error { if err != nil { return err } + return nil } if reflect.TypeOf(data).Kind() == reflect.Slice && reflect.ValueOf(data).IsNil() {