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