github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/cmd/mattermost/commands/config.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package commands
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"os"
    10  	"reflect"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/mattermost/viper"
    15  	"github.com/pkg/errors"
    16  	"github.com/spf13/cobra"
    17  
    18  	"github.com/mattermost/mattermost-server/v5/config"
    19  	"github.com/mattermost/mattermost-server/v5/mlog"
    20  	"github.com/mattermost/mattermost-server/v5/model"
    21  	"github.com/mattermost/mattermost-server/v5/utils"
    22  )
    23  
    24  const noSettingsNamed = "unable to find a setting named: %s"
    25  
    26  var ConfigCmd = &cobra.Command{
    27  	Use:   "config",
    28  	Short: "Configuration",
    29  }
    30  
    31  var ValidateConfigCmd = &cobra.Command{
    32  	Use:   "validate",
    33  	Short: "Validate config file",
    34  	Long:  "If the config file is valid, this command will output a success message and have a zero exit code. If it is invalid, this command will output an error and have a non-zero exit code.",
    35  	RunE:  configValidateCmdF,
    36  }
    37  
    38  var ConfigSubpathCmd = &cobra.Command{
    39  	Use:   "subpath",
    40  	Short: "Update client asset loading to use the configured subpath",
    41  	Long:  "Update the hard-coded production client asset paths to take into account Mattermost running on a subpath.",
    42  	Example: `  config subpath
    43    config subpath --path /mattermost
    44    config subpath --path /`,
    45  	RunE: configSubpathCmdF,
    46  }
    47  
    48  var ConfigGetCmd = &cobra.Command{
    49  	Use:     "get",
    50  	Short:   "Get config setting",
    51  	Long:    "Gets the value of a config setting by its name in dot notation.",
    52  	Example: `config get SqlSettings.DriverName`,
    53  	Args:    cobra.ExactArgs(1),
    54  	RunE:    configGetCmdF,
    55  }
    56  
    57  var ConfigShowCmd = &cobra.Command{
    58  	Use:     "show",
    59  	Short:   "Writes the server configuration to STDOUT",
    60  	Long:    "Pretty-prints the server configuration and writes to STDOUT",
    61  	Example: "config show",
    62  	RunE:    configShowCmdF,
    63  }
    64  
    65  var ConfigSetCmd = &cobra.Command{
    66  	Use:     "set",
    67  	Short:   "Set config setting",
    68  	Long:    "Sets the value of a config setting by its name in dot notation. Accepts multiple values for array settings",
    69  	Example: "config set SqlSettings.DriverName mysql",
    70  	Args:    cobra.MinimumNArgs(2),
    71  	RunE:    configSetCmdF,
    72  }
    73  
    74  var MigrateConfigCmd = &cobra.Command{
    75  	Use:     "migrate [from_config] [to_config]",
    76  	Short:   "Migrate existing config between backends",
    77  	Long:    "Migrate a file-based configuration to (or from) a database-based configuration. Point the Mattermost server at the target configuration to start using it",
    78  	Example: `config migrate path/to/config.json "postgres://mmuser:mostest@localhost:5432/mattermost_test?sslmode=disable&connect_timeout=10"`,
    79  	Args:    cobra.ExactArgs(2),
    80  	RunE:    configMigrateCmdF,
    81  }
    82  
    83  var ConfigResetCmd = &cobra.Command{
    84  	Use:     "reset",
    85  	Short:   "Reset config setting",
    86  	Long:    "Resets the value of a config setting by its name in dot notation or a setting section. Accepts multiple values for array settings.",
    87  	Example: "config reset SqlSettings.DriverName LogSettings",
    88  	RunE:    configResetCmdF,
    89  }
    90  
    91  func init() {
    92  	ConfigSubpathCmd.Flags().String("path", "", "Optional subpath; defaults to value in SiteURL")
    93  	ConfigResetCmd.Flags().Bool("confirm", false, "Confirm you really want to reset all configuration settings to its default value")
    94  	ConfigShowCmd.Flags().Bool("json", false, "Output the configuration as JSON.")
    95  
    96  	ConfigCmd.AddCommand(
    97  		ValidateConfigCmd,
    98  		ConfigSubpathCmd,
    99  		ConfigGetCmd,
   100  		ConfigShowCmd,
   101  		ConfigSetCmd,
   102  		MigrateConfigCmd,
   103  		ConfigResetCmd,
   104  	)
   105  	RootCmd.AddCommand(ConfigCmd)
   106  }
   107  
   108  func configValidateCmdF(command *cobra.Command, args []string) error {
   109  	utils.TranslationsPreInit()
   110  	model.AppErrorInit(utils.T)
   111  
   112  	_, err := getConfigStore(command)
   113  	if err != nil {
   114  		return err
   115  	}
   116  
   117  	CommandPrettyPrintln("The document is valid")
   118  	return nil
   119  }
   120  
   121  func configSubpathCmdF(command *cobra.Command, args []string) error {
   122  	a, err := InitDBCommandContextCobra(command)
   123  	if err != nil {
   124  		return err
   125  	}
   126  	defer a.Srv().Shutdown()
   127  
   128  	path, err := command.Flags().GetString("path")
   129  	if err != nil {
   130  		return errors.Wrap(err, "failed reading path")
   131  	}
   132  
   133  	if path == "" {
   134  		return utils.UpdateAssetsSubpathFromConfig(a.Config())
   135  	}
   136  
   137  	if err := utils.UpdateAssetsSubpath(path); err != nil {
   138  		return errors.Wrap(err, "failed to update assets subpath")
   139  	}
   140  
   141  	return nil
   142  }
   143  
   144  func getConfigStore(command *cobra.Command) (config.Store, error) {
   145  	if err := utils.TranslationsPreInit(); err != nil {
   146  		return nil, errors.Wrap(err, "failed to initialize i18n")
   147  	}
   148  
   149  	configDSN := viper.GetString("config")
   150  
   151  	configStore, err := config.NewStore(configDSN, false)
   152  	if err != nil {
   153  		return nil, errors.Wrap(err, "failed to initialize config store")
   154  	}
   155  
   156  	return configStore, nil
   157  }
   158  
   159  func configGetCmdF(command *cobra.Command, args []string) error {
   160  	configStore, err := getConfigStore(command)
   161  	if err != nil {
   162  		return err
   163  	}
   164  
   165  	out, err := printConfigValues(configToMap(*configStore.Get()), strings.Split(args[0], "."), args[0])
   166  	if err != nil {
   167  		return err
   168  	}
   169  
   170  	fmt.Printf("%s", out)
   171  
   172  	return nil
   173  }
   174  
   175  func configShowCmdF(command *cobra.Command, args []string) error {
   176  	useJSON, err := command.Flags().GetBool("json")
   177  	if err != nil {
   178  		return errors.Wrap(err, "failed reading json parameter")
   179  	}
   180  
   181  	err = cobra.NoArgs(command, args)
   182  	if err != nil {
   183  		return err
   184  	}
   185  
   186  	configStore, err := getConfigStore(command)
   187  	if err != nil {
   188  		return err
   189  	}
   190  
   191  	config := *configStore.Get()
   192  
   193  	if useJSON {
   194  		configJSON, err := json.MarshalIndent(config, "", "    ")
   195  		if err != nil {
   196  			return errors.Wrap(err, "failed to marshal config as json")
   197  		}
   198  
   199  		fmt.Printf("%s\n", configJSON)
   200  	} else {
   201  		fmt.Printf("%s", prettyPrintStruct(config))
   202  	}
   203  
   204  	return nil
   205  }
   206  
   207  // printConfigValues function prints out the value of the configSettings working recursively or
   208  // gives an error if config setting is not in the file.
   209  func printConfigValues(configMap map[string]interface{}, configSetting []string, name string) (string, error) {
   210  	res, ok := configMap[configSetting[0]]
   211  	if !ok {
   212  		return "", fmt.Errorf("%s configuration setting is not in the file", name)
   213  	}
   214  	value := reflect.ValueOf(res)
   215  	switch value.Kind() {
   216  	case reflect.Map:
   217  		if len(configSetting) == 1 {
   218  			return printStringMap(value, 0), nil
   219  		}
   220  		return printConfigValues(res.(map[string]interface{}), configSetting[1:], name)
   221  	default:
   222  		if len(configSetting) == 1 {
   223  			return fmt.Sprintf("%s: \"%v\"\n", name, res), nil
   224  		}
   225  		return "", fmt.Errorf("%s configuration setting is not in the file", name)
   226  	}
   227  }
   228  
   229  func configSetCmdF(command *cobra.Command, args []string) error {
   230  	configStore, err := getConfigStore(command)
   231  	if err != nil {
   232  		return err
   233  	}
   234  
   235  	// args[0] -> holds the config setting that we want to change
   236  	// args[1:] -> the new value of the config setting
   237  	configSetting := args[0]
   238  	newVal := args[1:]
   239  
   240  	// create the function to update config
   241  	oldConfig := configStore.Get()
   242  	newConfig := configStore.Get()
   243  
   244  	f := updateConfigValue(configSetting, newVal, oldConfig, newConfig)
   245  	f(newConfig)
   246  
   247  	// UpdateConfig above would have already fixed these invalid locales, but we check again
   248  	// in the context of an explicit change to these parameters to avoid saving the fixed
   249  	// settings in the first place.
   250  	if changed := config.FixInvalidLocales(newConfig); changed {
   251  		return errors.New("Invalid locale configuration")
   252  	}
   253  
   254  	if _, errSet := configStore.Set(newConfig); errSet != nil {
   255  		return errors.Wrap(errSet, "failed to set config")
   256  	}
   257  
   258  	/*
   259  		Uncomment when CI unit test fail resolved.
   260  
   261  		a, errInit := InitDBCommandContextCobra(command)
   262  		if errInit == nil {
   263  			auditRec := a.MakeAuditRecord("configSet", audit.Success)
   264  			auditRec.AddMeta("setting", configSetting)
   265  			auditRec.AddMeta("new_value", newVal)
   266  			a.LogAuditRec(auditRec, nil)
   267  			a.Srv().Shutdown()
   268  		}
   269  	*/
   270  
   271  	return nil
   272  }
   273  
   274  func configMigrateCmdF(command *cobra.Command, args []string) error {
   275  	from := args[0]
   276  	to := args[1]
   277  
   278  	err := config.Migrate(from, to)
   279  
   280  	if err != nil {
   281  		return errors.Wrap(err, "failed to migrate config")
   282  	}
   283  
   284  	mlog.Info("Successfully migrated config.")
   285  
   286  	return nil
   287  }
   288  
   289  func updateConfigValue(configSetting string, newVal []string, oldConfig, newConfig *model.Config) func(*model.Config) {
   290  	return func(update *model.Config) {
   291  
   292  		// convert config to map[string]interface
   293  		configMap := configToMap(*oldConfig)
   294  
   295  		// iterate through the map and update the value or print an error and exit
   296  		err := UpdateMap(configMap, strings.Split(configSetting, "."), newVal)
   297  		if err != nil {
   298  			fmt.Printf("%s\n", err)
   299  			os.Exit(1)
   300  		}
   301  
   302  		// convert map to json
   303  		bs, err := json.Marshal(configMap)
   304  		if err != nil {
   305  			fmt.Printf("Error while marshalling map to json %s\n", err)
   306  			os.Exit(1)
   307  		}
   308  
   309  		// convert json to struct
   310  		err = json.Unmarshal(bs, newConfig)
   311  		if err != nil {
   312  			fmt.Printf("Error while unmarshalling json to struct %s\n", err)
   313  			os.Exit(1)
   314  		}
   315  
   316  		*update = *newConfig
   317  
   318  	}
   319  }
   320  
   321  func UpdateMap(configMap map[string]interface{}, configSettings []string, newVal []string) error {
   322  	res, ok := configMap[configSettings[0]]
   323  	if !ok {
   324  		return fmt.Errorf(noSettingsNamed, configSettings[0])
   325  	}
   326  
   327  	value := reflect.ValueOf(res)
   328  
   329  	switch value.Kind() {
   330  
   331  	case reflect.Map:
   332  		// we can only change the value of a particular setting, not the whole map, return error
   333  		if len(configSettings) == 1 {
   334  			return errors.New("unable to set multiple settings at once")
   335  		}
   336  		simpleMap, ok := res.(map[string]interface{})
   337  		if ok {
   338  			return UpdateMap(simpleMap, configSettings[1:], newVal)
   339  		}
   340  		mapOfTheMap, ok := res.(map[string]map[string]interface{})
   341  		if ok {
   342  			convertedMap := make(map[string]interface{})
   343  			for k, v := range mapOfTheMap {
   344  				convertedMap[k] = v
   345  			}
   346  			return UpdateMap(convertedMap, configSettings[1:], newVal)
   347  		}
   348  		pluginStateMap, ok := res.(map[string]*model.PluginState)
   349  		if ok {
   350  			convertedMap := make(map[string]interface{})
   351  			for k, v := range pluginStateMap {
   352  				convertedMap[k] = v
   353  			}
   354  			return UpdateMap(convertedMap, configSettings[1:], newVal)
   355  		}
   356  		return fmt.Errorf(noSettingsNamed, configSettings[1])
   357  
   358  	case reflect.Int:
   359  		if len(configSettings) == 1 {
   360  			val, err := strconv.Atoi(newVal[0])
   361  			if err != nil {
   362  				return err
   363  			}
   364  			configMap[configSettings[0]] = val
   365  			return nil
   366  		}
   367  		return fmt.Errorf(noSettingsNamed, configSettings[0])
   368  
   369  	case reflect.Int64:
   370  		if len(configSettings) == 1 {
   371  			val, err := strconv.Atoi(newVal[0])
   372  			if err != nil {
   373  				return err
   374  			}
   375  			configMap[configSettings[0]] = int64(val)
   376  			return nil
   377  		}
   378  		return fmt.Errorf(noSettingsNamed, configSettings[0])
   379  
   380  	case reflect.Bool:
   381  		if len(configSettings) == 1 {
   382  			val, err := strconv.ParseBool(newVal[0])
   383  			if err != nil {
   384  				return err
   385  			}
   386  			configMap[configSettings[0]] = val
   387  			return nil
   388  		}
   389  		return fmt.Errorf(noSettingsNamed, configSettings[0])
   390  
   391  	case reflect.String:
   392  		if len(configSettings) == 1 {
   393  			configMap[configSettings[0]] = newVal[0]
   394  			return nil
   395  		}
   396  		return fmt.Errorf(noSettingsNamed, configSettings[0])
   397  
   398  	case reflect.Slice:
   399  		if len(configSettings) == 1 {
   400  			configMap[configSettings[0]] = newVal
   401  			return nil
   402  		}
   403  		return fmt.Errorf(noSettingsNamed, configSettings[0])
   404  
   405  	case reflect.Ptr:
   406  		state, ok := res.(*model.PluginState)
   407  		if !ok || len(configSettings) != 2 {
   408  			return errors.New("type not supported yet")
   409  		}
   410  		val, err := strconv.ParseBool(newVal[0])
   411  		if err != nil {
   412  			return err
   413  		}
   414  		state.Enable = val
   415  		return nil
   416  
   417  	default:
   418  		return errors.New("type not supported yet")
   419  	}
   420  }
   421  
   422  func configResetCmdF(command *cobra.Command, args []string) error {
   423  	configStore, err := getConfigStore(command)
   424  	if err != nil {
   425  		return err
   426  	}
   427  
   428  	defaultConfig := &model.Config{}
   429  	defaultConfig.SetDefaults()
   430  
   431  	confirmFlag, _ := command.Flags().GetBool("confirm")
   432  	if confirmFlag {
   433  		if _, err = configStore.Set(defaultConfig); err != nil {
   434  			return errors.Wrap(err, "failed to set config")
   435  		}
   436  	}
   437  
   438  	if !confirmFlag && len(args) == 0 {
   439  		var confirmResetAll string
   440  		CommandPrettyPrintln("Are you sure you want to reset all the configuration settings?(YES/NO): ")
   441  		fmt.Scanln(&confirmResetAll)
   442  		if confirmResetAll == "YES" {
   443  			if _, err = configStore.Set(defaultConfig); err != nil {
   444  				return errors.Wrap(err, "failed to set config")
   445  			}
   446  		}
   447  	}
   448  
   449  	tempConfig := configStore.Get()
   450  	tempConfigMap := configToMap(*tempConfig)
   451  	defaultConfigMap := configToMap(*defaultConfig)
   452  	for _, arg := range args {
   453  		err = changeMap(tempConfigMap, defaultConfigMap, strings.Split(arg, "."))
   454  		if err != nil {
   455  			return errors.Wrap(err, "Failed to reset config")
   456  		}
   457  	}
   458  	bs, err := json.Marshal(tempConfigMap)
   459  	if err != nil {
   460  		fmt.Printf("Error while marshalling map to json %s\n", err)
   461  		os.Exit(1)
   462  	}
   463  	err = json.Unmarshal(bs, tempConfig)
   464  	if err != nil {
   465  		fmt.Printf("Error while unmarshalling json to struct %s\n", err)
   466  		os.Exit(1)
   467  	}
   468  	if changed := config.FixInvalidLocales(tempConfig); changed {
   469  		return errors.New("Invalid locale configuration")
   470  	}
   471  
   472  	if _, errSet := configStore.Set(tempConfig); errSet != nil {
   473  		return errors.Wrap(errSet, "failed to set config")
   474  	}
   475  
   476  	/*
   477  		Uncomment when CI unit test fail resolved.
   478  
   479  		a, errInit := InitDBCommandContextCobra(command)
   480  		if errInit == nil {
   481  			auditRec := a.MakeAuditRecord("configReset", audit.Success)
   482  			a.LogAuditRec(auditRec, nil)
   483  			a.Srv().Shutdown()
   484  		}
   485  	*/
   486  
   487  	return nil
   488  }
   489  
   490  func changeMap(oldConfigMap, defaultConfigMap map[string]interface{}, configSettings []string) error {
   491  	resOld, ok := oldConfigMap[configSettings[0]]
   492  	if !ok {
   493  		return fmt.Errorf("Unable to find a setting with that name %s", configSettings[0])
   494  	}
   495  	resDef := defaultConfigMap[configSettings[0]]
   496  	valueOld := reflect.ValueOf(resOld)
   497  
   498  	if valueOld.Kind() == reflect.Map {
   499  		if len(configSettings) == 1 {
   500  			return changeSection(resOld.(map[string]interface{}), resDef.(map[string]interface{}))
   501  		}
   502  		return changeMap(resOld.(map[string]interface{}), resDef.(map[string]interface{}), configSettings[1:])
   503  	}
   504  	if len(configSettings) == 1 {
   505  		oldConfigMap[configSettings[0]] = defaultConfigMap[configSettings[0]]
   506  		return nil
   507  	}
   508  	return fmt.Errorf("Unable to find a setting with that name %s", configSettings[0])
   509  }
   510  
   511  func changeSection(oldConfigMap, defaultConfigMap map[string]interface{}) error {
   512  	valueOld := reflect.ValueOf(oldConfigMap)
   513  	for _, key := range valueOld.MapKeys() {
   514  		oldConfigMap[key.String()] = defaultConfigMap[key.String()]
   515  	}
   516  	return nil
   517  }
   518  
   519  // configToMap converts our config into a map
   520  func configToMap(s interface{}) map[string]interface{} {
   521  	return structToMap(s)
   522  }