github.com/RajatVaryani/mattermost-server@v5.11.1+incompatible/config/unmarshal.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package config 5 6 import ( 7 "bytes" 8 "encoding/json" 9 "io" 10 "io/ioutil" 11 "reflect" 12 "strings" 13 14 "github.com/mattermost/viper" 15 "github.com/pkg/errors" 16 17 "github.com/mattermost/mattermost-server/mlog" 18 "github.com/mattermost/mattermost-server/model" 19 "github.com/mattermost/mattermost-server/utils/jsonutils" 20 ) 21 22 // newViper creates an instance of viper.Viper configured for parsing a configuration. 23 func newViper(allowEnvironmentOverrides bool) *viper.Viper { 24 v := viper.New() 25 26 v.SetConfigType("json") 27 28 if allowEnvironmentOverrides { 29 v.SetEnvPrefix("mm") 30 v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) 31 v.AutomaticEnv() 32 } 33 34 // Set zeroed defaults for all the config settings so that Viper knows what environment variables 35 // it needs to be looking for. The correct defaults will later be applied using Config.SetDefaults. 36 defaults := getDefaultsFromStruct(model.Config{}) 37 38 for key, value := range defaults { 39 if key == "PluginSettings.Plugins" || key == "PluginSettings.PluginStates" { 40 continue 41 } 42 43 v.SetDefault(key, value) 44 } 45 46 return v 47 } 48 49 func getDefaultsFromStruct(s interface{}) map[string]interface{} { 50 return flattenStructToMap(structToMap(reflect.TypeOf(s))) 51 } 52 53 // Converts a struct type into a nested map with keys matching the struct's fields and values 54 // matching the zeroed value of the corresponding field. 55 func structToMap(t reflect.Type) (out map[string]interface{}) { 56 defer func() { 57 if r := recover(); r != nil { 58 mlog.Error("Panicked in structToMap. This should never happen.", mlog.Any("err", r)) 59 } 60 }() 61 62 if t.Kind() != reflect.Struct { 63 // Should never hit this, but this will prevent a panic if that does happen somehow 64 return nil 65 } 66 67 out = map[string]interface{}{} 68 69 for i := 0; i < t.NumField(); i++ { 70 field := t.Field(i) 71 72 var value interface{} 73 74 switch field.Type.Kind() { 75 case reflect.Struct: 76 value = structToMap(field.Type) 77 case reflect.Ptr: 78 indirectType := field.Type.Elem() 79 80 if indirectType.Kind() == reflect.Struct { 81 // Follow pointers to structs since we need to define defaults for their fields 82 value = structToMap(indirectType) 83 } else { 84 value = nil 85 } 86 default: 87 value = reflect.Zero(field.Type).Interface() 88 } 89 90 out[field.Name] = value 91 } 92 93 return 94 } 95 96 // Flattens a nested map so that the result is a single map with keys corresponding to the 97 // path through the original map. For example, 98 // { 99 // "a": { 100 // "b": 1 101 // }, 102 // "c": "sea" 103 // } 104 // would flatten to 105 // { 106 // "a.b": 1, 107 // "c": "sea" 108 // } 109 func flattenStructToMap(in map[string]interface{}) map[string]interface{} { 110 out := make(map[string]interface{}) 111 112 for key, value := range in { 113 if valueAsMap, ok := value.(map[string]interface{}); ok { 114 sub := flattenStructToMap(valueAsMap) 115 116 for subKey, subValue := range sub { 117 out[key+"."+subKey] = subValue 118 } 119 } else { 120 out[key] = value 121 } 122 } 123 124 return out 125 } 126 127 // marshalConfig converts the given configuration into JSON bytes for persistence. 128 func marshalConfig(cfg *model.Config) ([]byte, error) { 129 return json.MarshalIndent(cfg, "", " ") 130 } 131 132 // unmarshalConfig unmarshals a raw configuration into a Config model and environment variable overrides. 133 func unmarshalConfig(r io.Reader, allowEnvironmentOverrides bool) (*model.Config, map[string]interface{}, error) { 134 // Pre-flight check the syntax of the configuration file to improve error messaging. 135 configData, err := ioutil.ReadAll(r) 136 if err != nil { 137 return nil, nil, errors.Wrapf(err, "failed to read") 138 } 139 140 var rawConfig interface{} 141 if err = json.Unmarshal(configData, &rawConfig); err != nil { 142 return nil, nil, jsonutils.HumanizeJsonError(err, configData) 143 } 144 145 v := newViper(allowEnvironmentOverrides) 146 if err := v.ReadConfig(bytes.NewReader(configData)); err != nil { 147 return nil, nil, err 148 } 149 150 var config model.Config 151 unmarshalErr := v.Unmarshal(&config) 152 // https://github.com/spf13/viper/issues/324 153 // https://github.com/spf13/viper/issues/348 154 if unmarshalErr == nil { 155 config.PluginSettings.Plugins = make(map[string]map[string]interface{}) 156 unmarshalErr = v.UnmarshalKey("pluginsettings.plugins", &config.PluginSettings.Plugins) 157 } 158 if unmarshalErr == nil { 159 config.PluginSettings.PluginStates = make(map[string]*model.PluginState) 160 unmarshalErr = v.UnmarshalKey("pluginsettings.pluginstates", &config.PluginSettings.PluginStates) 161 } 162 163 envConfig := v.EnvSettings() 164 165 var envErr error 166 if envConfig, envErr = fixEnvSettingsCase(envConfig); envErr != nil { 167 return nil, nil, envErr 168 } 169 170 return &config, envConfig, unmarshalErr 171 } 172 173 // Fixes the case of the environment variables sent back from Viper since Viper stores everything 174 // as lower case. 175 func fixEnvSettingsCase(in map[string]interface{}) (out map[string]interface{}, err error) { 176 defer func() { 177 if r := recover(); r != nil { 178 mlog.Error("Panicked in fixEnvSettingsCase. This should never happen.", mlog.Any("err", r)) 179 out = in 180 } 181 }() 182 183 var fixCase func(map[string]interface{}, reflect.Type) map[string]interface{} 184 fixCase = func(in map[string]interface{}, t reflect.Type) map[string]interface{} { 185 if t.Kind() != reflect.Struct { 186 // Should never hit this, but this will prevent a panic if that does happen somehow 187 return nil 188 } 189 190 fixCaseOut := make(map[string]interface{}, len(in)) 191 192 for i := 0; i < t.NumField(); i++ { 193 field := t.Field(i) 194 195 key := field.Name 196 if value, ok := in[strings.ToLower(key)]; ok { 197 if valueAsMap, ok := value.(map[string]interface{}); ok { 198 fixCaseOut[key] = fixCase(valueAsMap, field.Type) 199 } else { 200 fixCaseOut[key] = value 201 } 202 } 203 } 204 205 return fixCaseOut 206 } 207 208 out = fixCase(in, reflect.TypeOf(model.Config{})) 209 210 return 211 }