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