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 }