Files
mostlymatter/config/unmarshal.go
Jesús Espino a63684fcb5 Consistent license message for all the go files (#13235)
* Consistent license message for all the go files

* Fixing the last set of unconsistencies with the license headers

* Addressing PR review comments

* Fixing busy.go and busy_test.go license header
2019-11-29 12:59:40 +01:00

214 строки
5.7 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package config
import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"reflect"
"strings"
"github.com/mattermost/viper"
"github.com/pkg/errors"
"github.com/mattermost/mattermost-server/v5/mlog"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/utils/jsonutils"
)
// newViper creates an instance of viper.Viper configured for parsing a configuration.
func newViper(allowEnvironmentOverrides bool) *viper.Viper {
v := viper.New()
v.SetConfigType("json")
v.AllowEmptyEnv(true)
if allowEnvironmentOverrides {
v.SetEnvPrefix("mm")
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
v.AutomaticEnv()
}
// Set zeroed defaults for all the config settings so that Viper knows what environment variables
// it needs to be looking for. The correct defaults will later be applied using Config.SetDefaults.
defaults := getDefaultsFromStruct(model.Config{})
for key, value := range defaults {
if key == "PluginSettings.Plugins" || key == "PluginSettings.PluginStates" {
continue
}
v.SetDefault(key, value)
}
return v
}
func getDefaultsFromStruct(s interface{}) map[string]interface{} {
return flattenStructToMap(structToMap(reflect.TypeOf(s)))
}
// Converts a struct type into a nested map with keys matching the struct's fields and values
// matching the zeroed value of the corresponding field.
func structToMap(t reflect.Type) (out map[string]interface{}) {
defer func() {
if r := recover(); r != nil {
mlog.Error("Panicked in structToMap. This should never happen.", mlog.Any("err", r))
}
}()
if t.Kind() != reflect.Struct {
// Should never hit this, but this will prevent a panic if that does happen somehow
return nil
}
out = map[string]interface{}{}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
var value interface{}
switch field.Type.Kind() {
case reflect.Struct:
value = structToMap(field.Type)
case reflect.Ptr:
indirectType := field.Type.Elem()
if indirectType.Kind() == reflect.Struct {
// Follow pointers to structs since we need to define defaults for their fields
value = structToMap(indirectType)
} else {
value = nil
}
default:
value = reflect.Zero(field.Type).Interface()
}
out[field.Name] = value
}
return
}
// Flattens a nested map so that the result is a single map with keys corresponding to the
// path through the original map. For example,
// {
// "a": {
// "b": 1
// },
// "c": "sea"
// }
// would flatten to
// {
// "a.b": 1,
// "c": "sea"
// }
func flattenStructToMap(in map[string]interface{}) map[string]interface{} {
out := make(map[string]interface{})
for key, value := range in {
if valueAsMap, ok := value.(map[string]interface{}); ok {
sub := flattenStructToMap(valueAsMap)
for subKey, subValue := range sub {
out[key+"."+subKey] = subValue
}
} else {
out[key] = value
}
}
return out
}
// marshalConfig converts the given configuration into JSON bytes for persistence.
func marshalConfig(cfg *model.Config) ([]byte, error) {
return json.MarshalIndent(cfg, "", " ")
}
// unmarshalConfig unmarshals a raw configuration into a Config model and environment variable overrides.
func unmarshalConfig(r io.Reader, allowEnvironmentOverrides bool) (*model.Config, map[string]interface{}, error) {
// Pre-flight check the syntax of the configuration file to improve error messaging.
configData, err := ioutil.ReadAll(r)
if err != nil {
return nil, nil, errors.Wrapf(err, "failed to read")
}
var rawConfig interface{}
if err = json.Unmarshal(configData, &rawConfig); err != nil {
return nil, nil, jsonutils.HumanizeJsonError(err, configData)
}
v := newViper(allowEnvironmentOverrides)
if err := v.ReadConfig(bytes.NewReader(configData)); err != nil {
return nil, nil, err
}
var config model.Config
unmarshalErr := v.Unmarshal(&config)
// https://github.com/spf13/viper/issues/324
// https://github.com/spf13/viper/issues/348
if unmarshalErr == nil {
config.PluginSettings.Plugins = make(map[string]map[string]interface{})
unmarshalErr = v.UnmarshalKey("pluginsettings.plugins", &config.PluginSettings.Plugins)
}
if unmarshalErr == nil {
config.PluginSettings.PluginStates = make(map[string]*model.PluginState)
unmarshalErr = v.UnmarshalKey("pluginsettings.pluginstates", &config.PluginSettings.PluginStates)
}
envConfig := v.EnvSettings()
var envErr error
if envConfig, envErr = fixEnvSettingsCase(envConfig); envErr != nil {
return nil, nil, envErr
}
return &config, envConfig, unmarshalErr
}
// Fixes the case of the environment variables sent back from Viper since Viper stores everything
// as lower case.
func fixEnvSettingsCase(in map[string]interface{}) (out map[string]interface{}, err error) {
defer func() {
if r := recover(); r != nil {
mlog.Error("Panicked in fixEnvSettingsCase. This should never happen.", mlog.Any("err", r))
out = in
}
}()
var fixCase func(map[string]interface{}, reflect.Type) map[string]interface{}
fixCase = func(in map[string]interface{}, t reflect.Type) map[string]interface{} {
if t.Kind() != reflect.Struct {
// Should never hit this, but this will prevent a panic if that does happen somehow
return nil
}
fixCaseOut := make(map[string]interface{}, len(in))
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
key := field.Name
if value, ok := in[strings.ToLower(key)]; ok {
if valueAsMap, ok := value.(map[string]interface{}); ok {
fixCaseOut[key] = fixCase(valueAsMap, field.Type)
} else {
fixCaseOut[key] = value
}
}
}
return fixCaseOut
}
out = fixCase(in, reflect.TypeOf(model.Config{}))
return
}