diff --git a/server/cmd/mmctl/commands/config.go b/server/cmd/mmctl/commands/config.go index 60ffdf4d57..06e194c6b0 100644 --- a/server/cmd/mmctl/commands/config.go +++ b/server/cmd/mmctl/commands/config.go @@ -193,12 +193,15 @@ func setValueWithConversion(val reflect.Value, newValue interface{}) error { val.Set(reflect.ValueOf(newValue)) return nil case reflect.Slice: - if val.Type().Elem().Kind() != reflect.String { - return errors.New("unsupported type of slice") - } v := reflect.ValueOf(newValue) if v.Kind() != reflect.Slice { - return errors.New("target value is of type Array and provided value is not") + // Special case when setting a string to a byte slice + if val.Type().Elem().Kind() == reflect.Uint8 && v.Kind() == reflect.String { + s := newValue.(string) + v = reflect.ValueOf([]byte(s)) + } else { + return errors.Errorf("target value is of type %v and provided value is %v", val.Kind(), v.Kind()) + } } val.Set(v) return nil @@ -206,7 +209,7 @@ func setValueWithConversion(val reflect.Value, newValue interface{}) error { bits := val.Type().Bits() v, err := strconv.ParseInt(newValue.(string), 10, bits) if err != nil { - return fmt.Errorf("target value is of type %v and provided value is not", val.Kind()) + return fmt.Errorf("target value is of type %v and provided value is not, err: %v", val.Kind(), err) } val.SetInt(v) return nil @@ -214,7 +217,7 @@ func setValueWithConversion(val reflect.Value, newValue interface{}) error { bits := val.Type().Bits() v, err := strconv.ParseFloat(newValue.(string), bits) if err != nil { - return fmt.Errorf("target value is of type %v and provided value is not", val.Kind()) + return fmt.Errorf("target value is of type %v and provided value is not, err: %v", val.Kind(), err) } val.SetFloat(v) return nil @@ -224,12 +227,12 @@ func setValueWithConversion(val reflect.Value, newValue interface{}) error { case reflect.Bool: v, err := strconv.ParseBool(newValue.(string)) if err != nil { - return errors.New("target value is of type Bool and provided value is not") + return fmt.Errorf("target value is of type %v and provided value is not, err: %v", val.Kind(), err) } val.SetBool(v) return nil default: - return errors.New("target value type is not supported") + return errors.Errorf("value type %v is not supported", val.Kind()) } } diff --git a/server/cmd/mmctl/commands/config_test.go b/server/cmd/mmctl/commands/config_test.go index 913abb66f8..02d88f7c18 100644 --- a/server/cmd/mmctl/commands/config_test.go +++ b/server/cmd/mmctl/commands/config_test.go @@ -6,6 +6,7 @@ package commands import ( "bytes" "context" + "encoding/json" "errors" "fmt" "net/http" @@ -14,6 +15,7 @@ import ( "github.com/mattermost/mattermost/server/public/model" "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/mattermost/mattermost/server/v8/cmd/mmctl/printer" @@ -923,3 +925,80 @@ func TestCloudRestricted(t *testing.T) { require.True(t, cloudRestricted(cfg, parseConfigPath(path))) }) } + +func TestSetConfigValue(t *testing.T) { + tests := map[string]struct { + path string + config *model.Config + args []string + expectedConfig *model.Config + }{ + "bool": { + path: "LogSettings.EnableConsole", + args: []string{"true"}, + config: &model.Config{LogSettings: model.LogSettings{ + EnableConsole: model.NewBool(false), + }}, + expectedConfig: &model.Config{LogSettings: model.LogSettings{ + EnableConsole: model.NewBool(true), + }}, + }, + "string": { + path: "LogSettings.ConsoleLevel", + args: []string{"foo"}, + config: &model.Config{LogSettings: model.LogSettings{ + ConsoleLevel: model.NewString("ConsoleLevel"), + }}, + expectedConfig: &model.Config{LogSettings: model.LogSettings{ + ConsoleLevel: model.NewString("foo"), + }}, + }, + "int": { + path: "LogSettings.MaxFieldSize", + args: []string{"123"}, + config: &model.Config{LogSettings: model.LogSettings{ + MaxFieldSize: model.NewInt(0), + }}, + expectedConfig: &model.Config{LogSettings: model.LogSettings{ + MaxFieldSize: model.NewInt(123), + }}, + }, + "int64": { + path: "ServiceSettings.TLSStrictTransportMaxAge", + config: &model.Config{ServiceSettings: model.ServiceSettings{ + TLSStrictTransportMaxAge: model.NewInt64(0), + }}, + args: []string{"123"}, + expectedConfig: &model.Config{ServiceSettings: model.ServiceSettings{ + TLSStrictTransportMaxAge: model.NewInt64(123), + }}, + }, + "string slice": { + path: "SqlSettings.DataSourceReplicas", + args: []string{"abc", "def"}, + config: &model.Config{SqlSettings: model.SqlSettings{ + DataSourceReplicas: []string{}, + }}, + expectedConfig: &model.Config{SqlSettings: model.SqlSettings{ + DataSourceReplicas: []string{"abc", "def"}, + }}, + }, + "json.RawMessage": { + path: "LogSettings.AdvancedLoggingJSON", + config: &model.Config{LogSettings: model.LogSettings{ + AdvancedLoggingJSON: nil, + }}, + args: []string{`{"console1":{"Type":"console"}}`}, + expectedConfig: &model.Config{LogSettings: model.LogSettings{ + AdvancedLoggingJSON: json.RawMessage(`{"console1":{"Type":"console"}}`), + }}, + }, + } + + for name, tc := range tests { + err := setConfigValue(parseConfigPath(tc.path), tc.config, tc.args) + require.NoError(t, err) + + assert.Equal(t, tc.expectedConfig, tc.config, name) + } +}