diff --git a/cmd/mattermost/commands/config.go b/cmd/mattermost/commands/config.go index 81ed6d7236..2ca3ac7331 100644 --- a/cmd/mattermost/commands/config.go +++ b/cmd/mattermost/commands/config.go @@ -11,6 +11,7 @@ import ( "strconv" "strings" + "github.com/mattermost/viper" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -18,7 +19,6 @@ import ( "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" - "github.com/mattermost/viper" ) const noSettingsNamed = "unable to find a setting named: %s" @@ -80,8 +80,17 @@ var MigrateConfigCmd = &cobra.Command{ RunE: configMigrateCmdF, } +var ConfigResetCmd = &cobra.Command{ + Use: "reset", + Short: "Reset config setting", + Long: "Resets the value of a config setting by its name in dot notation or a setting section. Accepts multiple values for array settings.", + Example: "config reset SqlSettings.DriverName LogSettings", + RunE: configResetCmdF, +} + func init() { ConfigSubpathCmd.Flags().String("path", "", "Optional subpath; defaults to value in SiteURL") + ConfigResetCmd.Flags().Bool("confirm", false, "Confirm you really want to reset all configuration settings to its default value") ConfigShowCmd.Flags().Bool("json", false, "Output the configuration as JSON.") ConfigCmd.AddCommand( @@ -91,6 +100,7 @@ func init() { ConfigShowCmd, ConfigSetCmd, MigrateConfigCmd, + ConfigResetCmd, ) RootCmd.AddCommand(ConfigCmd) } @@ -367,6 +377,92 @@ func UpdateMap(configMap map[string]interface{}, configSettings []string, newVal } } +func configResetCmdF(command *cobra.Command, args []string) error { + configStore, err := getConfigStore(command) + if err != nil { + return err + } + + defaultConfig := &model.Config{} + defaultConfig.SetDefaults() + + confirmFlag, _ := command.Flags().GetBool("confirm") + if confirmFlag { + if _, err = configStore.Set(defaultConfig); err != nil { + return errors.Wrap(err, "failed to set config") + } + } + + if !confirmFlag && len(args) == 0 { + var confirmResetAll string + CommandPrettyPrintln("Are you sure you want to reset all the configuration settings?(YES/NO): ") + fmt.Scanln(&confirmResetAll) + if confirmResetAll == "YES" { + if _, err = configStore.Set(defaultConfig); err != nil { + return errors.Wrap(err, "failed to set config") + } + } + } + + tempConfig := configStore.Get() + tempConfigMap := configToMap(*tempConfig) + defaultConfigMap := configToMap(*defaultConfig) + for _, arg := range args { + err = changeMap(tempConfigMap, defaultConfigMap, strings.Split(arg, ".")) + if err != nil { + return errors.Wrap(err, "Failed to reset config") + } + } + bs, err := json.Marshal(tempConfigMap) + if err != nil { + fmt.Printf("Error while marshalling map to json %s\n", err) + os.Exit(1) + } + err = json.Unmarshal(bs, tempConfig) + if err != nil { + fmt.Printf("Error while unmarshalling json to struct %s\n", err) + os.Exit(1) + } + if changed := config.FixInvalidLocales(tempConfig); changed { + return errors.New("Invalid locale configuration") + } + + if _, err := configStore.Set(tempConfig); err != nil { + return errors.Wrap(err, "failed to set config") + } + + return nil +} + +func changeMap(oldConfigMap, defaultConfigMap map[string]interface{}, configSettings []string) error { + resOld, ok := oldConfigMap[configSettings[0]] + if !ok { + return fmt.Errorf("Unable to find a setting with that name %s", configSettings[0]) + } + resDef := defaultConfigMap[configSettings[0]] + valueOld := reflect.ValueOf(resOld) + + if valueOld.Kind() == reflect.Map { + if len(configSettings) == 1 { + return changeSection(resOld.(map[string]interface{}), resDef.(map[string]interface{})) + } + return changeMap(resOld.(map[string]interface{}), resDef.(map[string]interface{}), configSettings[1:]) + } + if len(configSettings) == 1 { + oldConfigMap[configSettings[0]] = defaultConfigMap[configSettings[0]] + return nil + } + return fmt.Errorf("Unable to find a setting with that name %s", configSettings[0]) +} + +func changeSection(oldConfigMap, defaultConfigMap map[string]interface{}) error { + valueOld := reflect.ValueOf(oldConfigMap) + for _, key := range valueOld.MapKeys() { + oldConfigMap[key.String()] = defaultConfigMap[key.String()] + } + return nil +} + // configToMap converts our config into a map func configToMap(s interface{}) map[string]interface{} { return structToMap(s) diff --git a/cmd/mattermost/commands/config_test.go b/cmd/mattermost/commands/config_test.go index 3268066084..d830a3b1dc 100644 --- a/cmd/mattermost/commands/config_test.go +++ b/cmd/mattermost/commands/config_test.go @@ -13,11 +13,10 @@ import ( "strings" "testing" - "github.com/mattermost/mattermost-server/config" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/mattermost/mattermost-server/config" "github.com/mattermost/mattermost-server/model" ) @@ -168,6 +167,64 @@ func TestConfigSet(t *testing.T) { }) } +func TestConfigReset(t *testing.T) { + th := Setup() + defer th.TearDown() + + t.Run("No Error when no arguments are given (reset all the configurations)", func(t *testing.T) { + assert.NoError(t, th.RunCommand(t, "config", "reset")) + }) + + t.Run("No Error when a configuration section is given", func(t *testing.T) { + assert.NoError(t, th.RunCommand(t, "config", "reset", "JobSettings")) + }) + + t.Run("No Error when a configuration setting is given", func(t *testing.T) { + assert.NoError(t, th.RunCommand(t, "config", "reset", "JobSettings.RunJobs")) + }) + + t.Run("Error when the wrong configuration section is given", func(t *testing.T) { + assert.Error(t, th.RunCommand(t, "config", "reset", "InvalidSettings")) + }) + + t.Run("Error when the wrong configuration setting is given", func(t *testing.T) { + assert.Error(t, th.RunCommand(t, "config", "reset", "JobSettings.InvalidConfiguration")) + }) + + t.Run("Success when the confirm boolean flag is given", func(t *testing.T) { + assert.NoError(t, th.RunCommand(t, "config", "set", "JobSettings.RunJobs", "false")) + assert.NoError(t, th.RunCommand(t, "config", "set", "PrivacySettings.ShowFullName", "false")) + assert.NoError(t, th.RunCommand(t, "config", "reset", "--confirm")) + output1 := th.CheckCommand(t, "config", "get", "JobSettings.RunJobs") + output2 := th.CheckCommand(t, "config", "get", "PrivacySettings.ShowFullName") + assert.Contains(t, output1, "true") + assert.Contains(t, output2, "true") + }) + + t.Run("Success when a configuration section is given", func(t *testing.T) { + assert.NoError(t, th.RunCommand(t, "config", "set", "JobSettings.RunJobs", "false")) + assert.NoError(t, th.RunCommand(t, "config", "set", "JobSettings.RunScheduler", "false")) + assert.NoError(t, th.RunCommand(t, "config", "set", "PrivacySettings.ShowFullName", "false")) + assert.NoError(t, th.RunCommand(t, "config", "reset", "JobSettings")) + output1 := th.CheckCommand(t, "config", "get", "JobSettings.RunJobs") + output2 := th.CheckCommand(t, "config", "get", "JobSettings.RunScheduler") + output3 := th.CheckCommand(t, "config", "get", "PrivacySettings.ShowFullName") + assert.Contains(t, output1, "true") + assert.Contains(t, output2, "true") + assert.Contains(t, output3, "false") + }) + + t.Run("Success when a configuration setting is given", func(t *testing.T) { + assert.NoError(t, th.RunCommand(t, "config", "set", "JobSettings.RunJobs", "false")) + assert.NoError(t, th.RunCommand(t, "config", "set", "JobSettings.RunScheduler", "false")) + assert.NoError(t, th.RunCommand(t, "config", "reset", "JobSettings.RunJobs")) + output1 := th.CheckCommand(t, "config", "get", "JobSettings.RunJobs") + output2 := th.CheckCommand(t, "config", "get", "JobSettings.RunScheduler") + assert.Contains(t, output1, "true") + assert.Contains(t, output2, "false") + }) +} + func TestConfigToMap(t *testing.T) { // This test is almost the same as TestStructToMap, but I have it here for the sake of completions cases := []struct {