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