github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/cmd/hkserver/commands/config.go (about)

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