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