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