523 строки
14 KiB
Go
523 строки
14 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package commands
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/mattermost/viper"
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/mattermost/mattermost-server/v5/config"
|
|
"github.com/mattermost/mattermost-server/v5/mlog"
|
|
"github.com/mattermost/mattermost-server/v5/model"
|
|
"github.com/mattermost/mattermost-server/v5/utils"
|
|
)
|
|
|
|
const noSettingsNamed = "unable to find a setting named: %s"
|
|
|
|
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,
|
|
}
|
|
|
|
var ConfigShowCmd = &cobra.Command{
|
|
Use: "show",
|
|
Short: "Writes the server configuration to STDOUT",
|
|
Long: "Pretty-prints the server configuration and writes to STDOUT",
|
|
Example: "config show",
|
|
RunE: configShowCmdF,
|
|
}
|
|
|
|
var ConfigSetCmd = &cobra.Command{
|
|
Use: "set",
|
|
Short: "Set config setting",
|
|
Long: "Sets the value of a config setting by its name in dot notation. Accepts multiple values for array settings",
|
|
Example: "config set SqlSettings.DriverName mysql",
|
|
Args: cobra.MinimumNArgs(2),
|
|
RunE: configSetCmdF,
|
|
}
|
|
|
|
var MigrateConfigCmd = &cobra.Command{
|
|
Use: "migrate [from_config] [to_config]",
|
|
Short: "Migrate existing config between backends",
|
|
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",
|
|
Example: `config migrate path/to/config.json "postgres://mmuser:mostest@localhost:5432/mattermost_test?sslmode=disable&connect_timeout=10"`,
|
|
Args: cobra.ExactArgs(2),
|
|
RunE: configMigrateCmdF,
|
|
}
|
|
|
|
var ConfigResetCmd = &cobra.Command{
|
|
Use: "reset",
|
|
Short: "Reset config setting",
|
|
Long: "Resets the value of a config setting by its name in dot notation or a setting section. Accepts multiple values for array settings.",
|
|
Example: "config reset SqlSettings.DriverName LogSettings",
|
|
RunE: configResetCmdF,
|
|
}
|
|
|
|
func init() {
|
|
ConfigSubpathCmd.Flags().String("path", "", "Optional subpath; defaults to value in SiteURL")
|
|
ConfigResetCmd.Flags().Bool("confirm", false, "Confirm you really want to reset all configuration settings to its default value")
|
|
ConfigShowCmd.Flags().Bool("json", false, "Output the configuration as JSON.")
|
|
|
|
ConfigCmd.AddCommand(
|
|
ValidateConfigCmd,
|
|
ConfigSubpathCmd,
|
|
ConfigGetCmd,
|
|
ConfigShowCmd,
|
|
ConfigSetCmd,
|
|
MigrateConfigCmd,
|
|
ConfigResetCmd,
|
|
)
|
|
RootCmd.AddCommand(ConfigCmd)
|
|
}
|
|
|
|
func configValidateCmdF(command *cobra.Command, args []string) error {
|
|
utils.TranslationsPreInit()
|
|
model.AppErrorInit(utils.T)
|
|
|
|
_, err := getConfigStore(command)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
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.Srv().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 getConfigStore(command *cobra.Command) (config.Store, error) {
|
|
if err := utils.TranslationsPreInit(); err != nil {
|
|
return nil, errors.Wrap(err, "failed to initialize i18n")
|
|
}
|
|
|
|
configDSN := viper.GetString("config")
|
|
|
|
configStore, err := config.NewStore(configDSN, false)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to initialize config store")
|
|
}
|
|
|
|
return configStore, nil
|
|
}
|
|
|
|
func configGetCmdF(command *cobra.Command, args []string) error {
|
|
configStore, err := getConfigStore(command)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
out, err := printConfigValues(configToMap(*configStore.Get()), strings.Split(args[0], "."), args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("%s", out)
|
|
|
|
return nil
|
|
}
|
|
|
|
func configShowCmdF(command *cobra.Command, args []string) error {
|
|
useJSON, err := command.Flags().GetBool("json")
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed reading json parameter")
|
|
}
|
|
|
|
err = cobra.NoArgs(command, args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
configStore, err := getConfigStore(command)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
config := *configStore.Get()
|
|
|
|
if useJSON {
|
|
configJSON, err := json.MarshalIndent(config, "", " ")
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to marshal config as json")
|
|
}
|
|
|
|
fmt.Printf("%s\n", configJSON)
|
|
} else {
|
|
fmt.Printf("%s", prettyPrintStruct(config))
|
|
}
|
|
|
|
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 printStringMap(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)
|
|
}
|
|
}
|
|
|
|
func configSetCmdF(command *cobra.Command, args []string) error {
|
|
configStore, err := getConfigStore(command)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// args[0] -> holds the config setting that we want to change
|
|
// args[1:] -> the new value of the config setting
|
|
configSetting := args[0]
|
|
newVal := args[1:]
|
|
|
|
// create the function to update config
|
|
oldConfig := configStore.Get()
|
|
newConfig := configStore.Get()
|
|
|
|
f := updateConfigValue(configSetting, newVal, oldConfig, newConfig)
|
|
f(newConfig)
|
|
|
|
// UpdateConfig above would have already fixed these invalid locales, but we check again
|
|
// in the context of an explicit change to these parameters to avoid saving the fixed
|
|
// settings in the first place.
|
|
if changed := config.FixInvalidLocales(newConfig); changed {
|
|
return errors.New("Invalid locale configuration")
|
|
}
|
|
|
|
if _, errSet := configStore.Set(newConfig); errSet != nil {
|
|
return errors.Wrap(errSet, "failed to set config")
|
|
}
|
|
|
|
/*
|
|
Uncomment when CI unit test fail resolved.
|
|
|
|
a, errInit := InitDBCommandContextCobra(command)
|
|
if errInit == nil {
|
|
auditRec := a.MakeAuditRecord("configSet", audit.Success)
|
|
auditRec.AddMeta("setting", configSetting)
|
|
auditRec.AddMeta("new_value", newVal)
|
|
a.LogAuditRec(auditRec, nil)
|
|
a.Srv().Shutdown()
|
|
}
|
|
*/
|
|
|
|
return nil
|
|
}
|
|
|
|
func configMigrateCmdF(command *cobra.Command, args []string) error {
|
|
from := args[0]
|
|
to := args[1]
|
|
|
|
err := config.Migrate(from, to)
|
|
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to migrate config")
|
|
}
|
|
|
|
mlog.Info("Successfully migrated config.")
|
|
|
|
return nil
|
|
}
|
|
|
|
func updateConfigValue(configSetting string, newVal []string, oldConfig, newConfig *model.Config) func(*model.Config) {
|
|
return func(update *model.Config) {
|
|
|
|
// convert config to map[string]interface
|
|
configMap := configToMap(*oldConfig)
|
|
|
|
// iterate through the map and update the value or print an error and exit
|
|
err := UpdateMap(configMap, strings.Split(configSetting, "."), newVal)
|
|
if err != nil {
|
|
fmt.Printf("%s\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// convert map to json
|
|
bs, err := json.Marshal(configMap)
|
|
if err != nil {
|
|
fmt.Printf("Error while marshalling map to json %s\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// convert json to struct
|
|
err = json.Unmarshal(bs, newConfig)
|
|
if err != nil {
|
|
fmt.Printf("Error while unmarshalling json to struct %s\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
*update = *newConfig
|
|
|
|
}
|
|
}
|
|
|
|
func UpdateMap(configMap map[string]interface{}, configSettings []string, newVal []string) error {
|
|
res, ok := configMap[configSettings[0]]
|
|
if !ok {
|
|
return fmt.Errorf(noSettingsNamed, configSettings[0])
|
|
}
|
|
|
|
value := reflect.ValueOf(res)
|
|
|
|
switch value.Kind() {
|
|
|
|
case reflect.Map:
|
|
// we can only change the value of a particular setting, not the whole map, return error
|
|
if len(configSettings) == 1 {
|
|
return errors.New("unable to set multiple settings at once")
|
|
}
|
|
simpleMap, ok := res.(map[string]interface{})
|
|
if ok {
|
|
return UpdateMap(simpleMap, configSettings[1:], newVal)
|
|
}
|
|
mapOfTheMap, ok := res.(map[string]map[string]interface{})
|
|
if ok {
|
|
convertedMap := make(map[string]interface{})
|
|
for k, v := range mapOfTheMap {
|
|
convertedMap[k] = v
|
|
}
|
|
return UpdateMap(convertedMap, configSettings[1:], newVal)
|
|
}
|
|
pluginStateMap, ok := res.(map[string]*model.PluginState)
|
|
if ok {
|
|
convertedMap := make(map[string]interface{})
|
|
for k, v := range pluginStateMap {
|
|
convertedMap[k] = v
|
|
}
|
|
return UpdateMap(convertedMap, configSettings[1:], newVal)
|
|
}
|
|
return fmt.Errorf(noSettingsNamed, configSettings[1])
|
|
|
|
case reflect.Int:
|
|
if len(configSettings) == 1 {
|
|
val, err := strconv.Atoi(newVal[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
configMap[configSettings[0]] = val
|
|
return nil
|
|
}
|
|
return fmt.Errorf(noSettingsNamed, configSettings[0])
|
|
|
|
case reflect.Int64:
|
|
if len(configSettings) == 1 {
|
|
val, err := strconv.Atoi(newVal[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
configMap[configSettings[0]] = int64(val)
|
|
return nil
|
|
}
|
|
return fmt.Errorf(noSettingsNamed, configSettings[0])
|
|
|
|
case reflect.Bool:
|
|
if len(configSettings) == 1 {
|
|
val, err := strconv.ParseBool(newVal[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
configMap[configSettings[0]] = val
|
|
return nil
|
|
}
|
|
return fmt.Errorf(noSettingsNamed, configSettings[0])
|
|
|
|
case reflect.String:
|
|
if len(configSettings) == 1 {
|
|
configMap[configSettings[0]] = newVal[0]
|
|
return nil
|
|
}
|
|
return fmt.Errorf(noSettingsNamed, configSettings[0])
|
|
|
|
case reflect.Slice:
|
|
if len(configSettings) == 1 {
|
|
configMap[configSettings[0]] = newVal
|
|
return nil
|
|
}
|
|
return fmt.Errorf(noSettingsNamed, configSettings[0])
|
|
|
|
case reflect.Ptr:
|
|
state, ok := res.(*model.PluginState)
|
|
if !ok || len(configSettings) != 2 {
|
|
return errors.New("type not supported yet")
|
|
}
|
|
val, err := strconv.ParseBool(newVal[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
state.Enable = val
|
|
return nil
|
|
|
|
default:
|
|
return errors.New("type not supported yet")
|
|
}
|
|
}
|
|
|
|
func configResetCmdF(command *cobra.Command, args []string) error {
|
|
configStore, err := getConfigStore(command)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defaultConfig := &model.Config{}
|
|
defaultConfig.SetDefaults()
|
|
|
|
confirmFlag, _ := command.Flags().GetBool("confirm")
|
|
if confirmFlag {
|
|
if _, err = configStore.Set(defaultConfig); err != nil {
|
|
return errors.Wrap(err, "failed to set config")
|
|
}
|
|
}
|
|
|
|
if !confirmFlag && len(args) == 0 {
|
|
var confirmResetAll string
|
|
CommandPrettyPrintln("Are you sure you want to reset all the configuration settings?(YES/NO): ")
|
|
fmt.Scanln(&confirmResetAll)
|
|
if confirmResetAll == "YES" {
|
|
if _, err = configStore.Set(defaultConfig); err != nil {
|
|
return errors.Wrap(err, "failed to set config")
|
|
}
|
|
}
|
|
}
|
|
|
|
tempConfig := configStore.Get()
|
|
tempConfigMap := configToMap(*tempConfig)
|
|
defaultConfigMap := configToMap(*defaultConfig)
|
|
for _, arg := range args {
|
|
err = changeMap(tempConfigMap, defaultConfigMap, strings.Split(arg, "."))
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed to reset config")
|
|
}
|
|
}
|
|
bs, err := json.Marshal(tempConfigMap)
|
|
if err != nil {
|
|
fmt.Printf("Error while marshalling map to json %s\n", err)
|
|
os.Exit(1)
|
|
}
|
|
err = json.Unmarshal(bs, tempConfig)
|
|
if err != nil {
|
|
fmt.Printf("Error while unmarshalling json to struct %s\n", err)
|
|
os.Exit(1)
|
|
}
|
|
if changed := config.FixInvalidLocales(tempConfig); changed {
|
|
return errors.New("Invalid locale configuration")
|
|
}
|
|
|
|
if _, errSet := configStore.Set(tempConfig); errSet != nil {
|
|
return errors.Wrap(errSet, "failed to set config")
|
|
}
|
|
|
|
/*
|
|
Uncomment when CI unit test fail resolved.
|
|
|
|
a, errInit := InitDBCommandContextCobra(command)
|
|
if errInit == nil {
|
|
auditRec := a.MakeAuditRecord("configReset", audit.Success)
|
|
a.LogAuditRec(auditRec, nil)
|
|
a.Srv().Shutdown()
|
|
}
|
|
*/
|
|
|
|
return nil
|
|
}
|
|
|
|
func changeMap(oldConfigMap, defaultConfigMap map[string]interface{}, configSettings []string) error {
|
|
resOld, ok := oldConfigMap[configSettings[0]]
|
|
if !ok {
|
|
return fmt.Errorf("Unable to find a setting with that name %s", configSettings[0])
|
|
}
|
|
resDef := defaultConfigMap[configSettings[0]]
|
|
valueOld := reflect.ValueOf(resOld)
|
|
|
|
if valueOld.Kind() == reflect.Map {
|
|
if len(configSettings) == 1 {
|
|
return changeSection(resOld.(map[string]interface{}), resDef.(map[string]interface{}))
|
|
}
|
|
return changeMap(resOld.(map[string]interface{}), resDef.(map[string]interface{}), configSettings[1:])
|
|
}
|
|
if len(configSettings) == 1 {
|
|
oldConfigMap[configSettings[0]] = defaultConfigMap[configSettings[0]]
|
|
return nil
|
|
}
|
|
return fmt.Errorf("Unable to find a setting with that name %s", configSettings[0])
|
|
}
|
|
|
|
func changeSection(oldConfigMap, defaultConfigMap map[string]interface{}) error {
|
|
valueOld := reflect.ValueOf(oldConfigMap)
|
|
for _, key := range valueOld.MapKeys() {
|
|
oldConfigMap[key.String()] = defaultConfigMap[key.String()]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// configToMap converts our config into a map
|
|
func configToMap(s interface{}) map[string]interface{} {
|
|
return structToMap(s)
|
|
}
|