github.com/jfrerich/mattermost-server@v5.8.0-rc2+incompatible/cmd/mattermost/commands/config.go (about)

     1  // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package commands
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"fmt"
    10  	"os"
    11  	"reflect"
    12  	"strconv"
    13  	"strings"
    14  
    15  	"github.com/pkg/errors"
    16  	"github.com/spf13/cobra"
    17  
    18  	"github.com/mattermost/mattermost-server/mlog"
    19  	"github.com/mattermost/mattermost-server/model"
    20  	"github.com/mattermost/mattermost-server/utils"
    21  	"github.com/mattermost/mattermost-server/utils/fileutils"
    22  )
    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  func init() {
    73  	ConfigSubpathCmd.Flags().String("path", "", "Optional subpath; defaults to value in SiteURL")
    74  
    75  	ConfigCmd.AddCommand(
    76  		ValidateConfigCmd,
    77  		ConfigSubpathCmd,
    78  		ConfigGetCmd,
    79  		ConfigShowCmd,
    80  		ConfigSetCmd,
    81  	)
    82  	RootCmd.AddCommand(ConfigCmd)
    83  }
    84  
    85  func configValidateCmdF(command *cobra.Command, args []string) error {
    86  	utils.TranslationsPreInit()
    87  	model.AppErrorInit(utils.T)
    88  	filePath, err := command.Flags().GetString("config")
    89  	if err != nil {
    90  		return err
    91  	}
    92  
    93  	filePath = fileutils.FindConfigFile(filePath)
    94  
    95  	file, err := os.Open(filePath)
    96  	if err != nil {
    97  		return err
    98  	}
    99  
   100  	decoder := json.NewDecoder(file)
   101  	config := model.Config{}
   102  	err = decoder.Decode(&config)
   103  	if err != nil {
   104  		return err
   105  	}
   106  
   107  	if _, err := file.Stat(); err != nil {
   108  		return err
   109  	}
   110  
   111  	if err := config.IsValid(); err != nil {
   112  		return errors.New(utils.T(err.Id))
   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.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 configGetCmdF(command *cobra.Command, args []string) error {
   143  	app, err := InitDBCommandContextCobra(command)
   144  	if err != nil {
   145  		return err
   146  	}
   147  	defer app.Shutdown()
   148  
   149  	// create the model for config
   150  	// Note: app.Config() returns a pointer, make appropriate changes
   151  	config := app.Config()
   152  
   153  	// get the print config setting and any error if there is
   154  	out, err := printConfigValues(configToMap(*config), strings.Split(args[0], "."), args[0])
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	fmt.Printf("%s", out)
   160  
   161  	return nil
   162  }
   163  
   164  func configShowCmdF(command *cobra.Command, args []string) error {
   165  	app, err := InitDBCommandContextCobra(command)
   166  	if err != nil {
   167  		return err
   168  	}
   169  	defer app.Shutdown()
   170  
   171  	// check that no arguments are given
   172  	err = cobra.NoArgs(command, args)
   173  	if err != nil {
   174  		return err
   175  	}
   176  
   177  	// set up the config object
   178  	config := app.Config()
   179  
   180  	// pretty print
   181  	fmt.Printf("%s", prettyPrint(configToMap(*config)))
   182  	return nil
   183  }
   184  
   185  // printConfigValues function prints out the value of the configSettings working recursively or
   186  // gives an error if config setting is not in the file.
   187  func printConfigValues(configMap map[string]interface{}, configSetting []string, name string) (string, error) {
   188  
   189  	res, ok := configMap[configSetting[0]]
   190  	if !ok {
   191  		return "", fmt.Errorf("%s configuration setting is not in the file", name)
   192  	}
   193  	value := reflect.ValueOf(res)
   194  	switch value.Kind() {
   195  	case reflect.Map:
   196  		if len(configSetting) == 1 {
   197  			return printMap(value, 0), nil
   198  		}
   199  		return printConfigValues(res.(map[string]interface{}), configSetting[1:], name)
   200  	default:
   201  		if len(configSetting) == 1 {
   202  			return fmt.Sprintf("%s: \"%v\"\n", name, res), nil
   203  		}
   204  		return "", fmt.Errorf("%s configuration setting is not in the file", name)
   205  	}
   206  }
   207  
   208  // prettyPrint the map
   209  func prettyPrint(configMap map[string]interface{}) string {
   210  	value := reflect.ValueOf(configMap)
   211  	return printMap(value, 0)
   212  }
   213  
   214  // printMap takes a reflect.Value and print it out, recursively if its a map with the given tab settings.
   215  func printMap(value reflect.Value, tabVal int) string {
   216  
   217  	out := &bytes.Buffer{}
   218  
   219  	for _, key := range value.MapKeys() {
   220  		val := value.MapIndex(key)
   221  		if newVal, ok := val.Interface().(map[string]interface{}); !ok {
   222  			fmt.Fprintf(out, "%s", strings.Repeat("\t", tabVal))
   223  			fmt.Fprintf(out, "%v: \"%v\"\n", key.Interface(), val.Interface())
   224  		} else {
   225  			fmt.Fprintf(out, "%s", strings.Repeat("\t", tabVal))
   226  			fmt.Fprintf(out, "%v:\n", key.Interface())
   227  			// going one level in, increase the tab
   228  			tabVal++
   229  			fmt.Fprintf(out, "%s", printMap(reflect.ValueOf(newVal), tabVal))
   230  			// coming back one level, decrease the tab
   231  			tabVal--
   232  		}
   233  	}
   234  
   235  	return out.String()
   236  
   237  }
   238  
   239  func configSetCmdF(command *cobra.Command, args []string) error {
   240  	app, err := InitDBCommandContextCobra(command)
   241  	if err != nil {
   242  		return err
   243  	}
   244  
   245  	defer app.Shutdown()
   246  
   247  	// args[0] -> holds the config setting that we want to change
   248  	// args[1:] -> the new value of the config setting
   249  	configSetting := args[0]
   250  	newVal := args[1:]
   251  
   252  	// Update the config
   253  
   254  	// first disable the watchers
   255  	app.DisableConfigWatch()
   256  
   257  	// create the function to update config
   258  	oldConfig := app.Config()
   259  	newConfig := app.Config()
   260  	f := updateConfigValue(configSetting, newVal, oldConfig, newConfig)
   261  
   262  	// update the config
   263  	app.UpdateConfig(f)
   264  
   265  	// Verify new config
   266  	if err := newConfig.IsValid(); err != nil {
   267  		return err
   268  	}
   269  
   270  	if err := utils.ValidateLocales(app.Config()); err != nil {
   271  		return errors.New("Invalid locale configuration")
   272  	}
   273  
   274  	// make the changes persist
   275  	app.PersistConfig()
   276  
   277  	// reload config
   278  	app.ReloadConfig()
   279  
   280  	// Enable config watchers
   281  	app.EnableConfigWatch()
   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("unable to find a setting with that name %s", 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  		return UpdateMap(res.(map[string]interface{}), configSettings[1:], newVal)
   334  
   335  	case reflect.Int:
   336  		if len(configSettings) == 1 {
   337  			val, err := strconv.Atoi(newVal[0])
   338  			if err != nil {
   339  				return err
   340  			}
   341  			configMap[configSettings[0]] = val
   342  			return nil
   343  		}
   344  		return fmt.Errorf("unable to find a setting with that name %s", configSettings[0])
   345  
   346  	case reflect.Int64:
   347  		if len(configSettings) == 1 {
   348  			val, err := strconv.Atoi(newVal[0])
   349  			if err != nil {
   350  				return err
   351  			}
   352  			configMap[configSettings[0]] = int64(val)
   353  			return nil
   354  		}
   355  		return fmt.Errorf("unable to find a setting with that name %s", configSettings[0])
   356  
   357  	case reflect.Bool:
   358  		if len(configSettings) == 1 {
   359  			val, err := strconv.ParseBool(newVal[0])
   360  			if err != nil {
   361  				return err
   362  			}
   363  			configMap[configSettings[0]] = val
   364  			return nil
   365  		}
   366  		return fmt.Errorf("unable to find a setting with that name %s", configSettings[0])
   367  
   368  	case reflect.String:
   369  		if len(configSettings) == 1 {
   370  			configMap[configSettings[0]] = newVal[0]
   371  			return nil
   372  		}
   373  		return fmt.Errorf("unable to find a setting with that name %s", configSettings[0])
   374  
   375  	case reflect.Slice:
   376  		if len(configSettings) == 1 {
   377  			configMap[configSettings[0]] = newVal
   378  			return nil
   379  		}
   380  		return fmt.Errorf("unable to find a setting with that name %s", configSettings[0])
   381  
   382  	default:
   383  		return errors.New("type not supported yet")
   384  	}
   385  }
   386  
   387  // configToMap converts our config into a map
   388  func configToMap(s interface{}) map[string]interface{} {
   389  	return structToMap(s)
   390  }
   391  
   392  // structToMap converts a struct into a map
   393  func structToMap(t interface{}) map[string]interface{} {
   394  	defer func() {
   395  		if r := recover(); r != nil {
   396  			mlog.Error(fmt.Sprintf("Panicked in structToMap. This should never happen. %v", r))
   397  		}
   398  	}()
   399  
   400  	val := reflect.ValueOf(t)
   401  
   402  	if val.Kind() != reflect.Struct {
   403  		return nil
   404  	}
   405  
   406  	out := map[string]interface{}{}
   407  
   408  	for i := 0; i < val.NumField(); i++ {
   409  		field := val.Field(i)
   410  
   411  		var value interface{}
   412  
   413  		switch field.Kind() {
   414  		case reflect.Struct:
   415  			value = structToMap(field.Interface())
   416  		case reflect.Ptr:
   417  			indirectType := field.Elem()
   418  
   419  			if indirectType.Kind() == reflect.Struct {
   420  				value = structToMap(indirectType.Interface())
   421  			} else {
   422  				value = indirectType.Interface()
   423  			}
   424  		default:
   425  			value = field.Interface()
   426  		}
   427  
   428  		out[val.Type().Field(i).Name] = value
   429  	}
   430  	return out
   431  }