// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package commands import ( "bytes" "encoding/json" "fmt" "os" "reflect" "strings" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" ) var ConfigCmd = &cobra.Command{ Use: "config", Short: "Configuration", } var ValidateConfigCmd = &cobra.Command{ Use: "validate", Short: "Validate config file", 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.", RunE: configValidateCmdF, } var ConfigSubpathCmd = &cobra.Command{ Use: "subpath", Short: "Update client asset loading to use the configured subpath", Long: "Update the hard-coded production client asset paths to take into account Mattermost running on a subpath.", Example: ` config subpath config subpath --path /mattermost config subpath --path /`, RunE: configSubpathCmdF, } var ConfigGetCmd = &cobra.Command{ Use: "get", Short: "Get config setting", Long: "Gets the value of a config setting by its name in dot notation.", Example: `config get SqlSettings.DriverName`, Args: cobra.ExactArgs(1), RunE: configGetCmdF, } func init() { ConfigSubpathCmd.Flags().String("path", "", "Optional subpath; defaults to value in SiteURL") ConfigCmd.AddCommand( ValidateConfigCmd, ConfigSubpathCmd, ConfigGetCmd, ) RootCmd.AddCommand(ConfigCmd) } func configValidateCmdF(command *cobra.Command, args []string) error { utils.TranslationsPreInit() model.AppErrorInit(utils.T) filePath, err := command.Flags().GetString("config") if err != nil { return err } filePath = utils.FindConfigFile(filePath) file, err := os.Open(filePath) if err != nil { return err } decoder := json.NewDecoder(file) config := model.Config{} err = decoder.Decode(&config) if err != nil { return err } if _, err := file.Stat(); err != nil { return err } if err := config.IsValid(); err != nil { return errors.New(utils.T(err.Id)) } CommandPrettyPrintln("The document is valid") return nil } func configSubpathCmdF(command *cobra.Command, args []string) error { a, err := InitDBCommandContextCobra(command) if err != nil { return err } defer a.Shutdown() path, err := command.Flags().GetString("path") if err != nil { return errors.Wrap(err, "failed reading path") } if path == "" { return utils.UpdateAssetsSubpathFromConfig(a.Config()) } if err := utils.UpdateAssetsSubpath(path); err != nil { return errors.Wrap(err, "failed to update assets subpath") } return nil } func configGetCmdF(command *cobra.Command, args []string) error { app, err := InitDBCommandContextCobra(command) if err != nil { return err } defer app.Shutdown() // create the model for config // Note: app.Config() returns a pointer, make appropriate changes config := app.Config() // get the print config setting and any error if there is out, err := printConfigValues(configToMap(*config), strings.Split(args[0], "."), args[0]) if err != nil { return err } fmt.Printf("%s", out) return nil } // printConfigValues function prints out the value of the configSettings working recursively or // gives an error if config setting is not in the file. func printConfigValues(configMap map[string]interface{}, configSetting []string, name string) (string, error) { res, ok := configMap[configSetting[0]] if !ok { return "", fmt.Errorf("%s configuration setting is not in the file", name) } value := reflect.ValueOf(res) switch value.Kind() { case reflect.Map: if len(configSetting) == 1 { return printMap(value, 0), nil } return printConfigValues(res.(map[string]interface{}), configSetting[1:], name) default: if len(configSetting) == 1 { return fmt.Sprintf("%s: \"%v\"\n", name, res), nil } return "", fmt.Errorf("%s configuration setting is not in the file", name) } } // printMap takes a reflect.Value and return a string, recursively if its a map with the given tab settings. func printMap(value reflect.Value, tabVal int) string { // our output buffer out := &bytes.Buffer{} for _, key := range value.MapKeys() { val := value.MapIndex(key) if newVal, ok := val.Interface().(map[string]interface{}); !ok { fmt.Fprintf(out, "%s", strings.Repeat("\t", tabVal)) fmt.Fprintf(out, "%v: \"%v\"\n", key.Interface(), val.Interface()) } else { fmt.Fprintf(out, "%s", strings.Repeat("\t", tabVal)) fmt.Fprintf(out, "%v:\n", key.Interface()) // going one level in, increase the tab tabVal++ fmt.Fprintf(out, "%s", printMap(reflect.ValueOf(newVal), tabVal)) // coming back one level, decrease the tab tabVal-- } } return out.String() } // configToMap converts our config into a map func configToMap(s interface{}) map[string]interface{} { return structToMap(s) } // structToMap converts a struct into a map func structToMap(t interface{}) map[string]interface{} { defer func() { if r := recover(); r != nil { mlog.Error(fmt.Sprintf("Panicked in structToMap. This should never happen. %v", r)) } }() val := reflect.ValueOf(t) if val.Kind() != reflect.Struct { return nil } out := map[string]interface{}{} for i := 0; i < val.NumField(); i++ { field := val.Field(i) var value interface{} switch field.Kind() { case reflect.Struct: value = structToMap(field.Interface()) case reflect.Ptr: indirectType := field.Elem() if indirectType.Kind() == reflect.Struct { value = structToMap(indirectType.Interface()) } else { value = indirectType.Interface() } default: value = field.Interface() } out[val.Type().Field(i).Name] = value } return out }