github.com/haalcala/mattermost-server-change-repo@v0.0.0-20210713015153-16753fbeee5f/config/environment.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 "os" 8 "reflect" 9 "strconv" 10 "strings" 11 12 "github.com/mattermost/mattermost-server/v5/model" 13 ) 14 15 func GetEnvironment() map[string]string { 16 mmenv := make(map[string]string) 17 for _, env := range os.Environ() { 18 kv := strings.SplitN(env, "=", 2) 19 key := strings.ToUpper(kv[0]) 20 if strings.HasPrefix(key, "MM") { 21 mmenv[key] = kv[1] 22 } 23 } 24 25 return mmenv 26 } 27 28 func applyEnvKey(key, value string, rValueSubject reflect.Value) { 29 keyParts := strings.SplitN(key, "_", 2) 30 if len(keyParts) < 1 { 31 return 32 } 33 rFieldValue := rValueSubject.FieldByNameFunc(func(candidate string) bool { 34 candidateUpper := strings.ToUpper(candidate) 35 return candidateUpper == keyParts[0] 36 }) 37 38 if !rFieldValue.IsValid() { 39 return 40 } 41 42 if rFieldValue.Kind() == reflect.Ptr { 43 rFieldValue = rFieldValue.Elem() 44 if !rFieldValue.IsValid() { 45 return 46 } 47 } 48 49 switch rFieldValue.Kind() { 50 case reflect.Struct: 51 // If we have only one part left, we can't deal with a struct 52 // the env var is incomplete so give up. 53 if len(keyParts) < 2 { 54 return 55 } 56 applyEnvKey(keyParts[1], value, rFieldValue) 57 case reflect.String: 58 rFieldValue.Set(reflect.ValueOf(value)) 59 case reflect.Bool: 60 boolVal, err := strconv.ParseBool(value) 61 if err == nil { 62 rFieldValue.Set(reflect.ValueOf(boolVal)) 63 } 64 case reflect.Int: 65 intVal, err := strconv.ParseInt(value, 10, 0) 66 if err == nil { 67 rFieldValue.Set(reflect.ValueOf(int(intVal))) 68 } 69 case reflect.Int64: 70 intVal, err := strconv.ParseInt(value, 10, 0) 71 if err == nil { 72 rFieldValue.Set(reflect.ValueOf(intVal)) 73 } 74 case reflect.SliceOf(reflect.TypeOf("")).Kind(): 75 rFieldValue.Set(reflect.ValueOf(strings.Split(value, " "))) 76 } 77 } 78 79 func applyEnvironmentMap(inputConfig *model.Config, env map[string]string) *model.Config { 80 appliedConfig := inputConfig.Clone() 81 82 rvalConfig := reflect.ValueOf(appliedConfig).Elem() 83 for envKey, envValue := range env { 84 applyEnvKey(strings.TrimPrefix(envKey, "MM_"), envValue, rvalConfig) 85 } 86 87 return appliedConfig 88 } 89 90 // generateEnvironmentMap creates a map[string]interface{} containing true at the leaves mirroring the 91 // configuration structure so the client can know which env variables are overridden 92 func generateEnvironmentMap(env map[string]string) map[string]interface{} { 93 rType := reflect.TypeOf(model.Config{}) 94 return generateEnvironmentMapWithBaseKey(env, rType, "MM") 95 } 96 97 func generateEnvironmentMapWithBaseKey(env map[string]string, rType reflect.Type, base string) map[string]interface{} { 98 if rType.Kind() != reflect.Struct { 99 return nil 100 } 101 102 mapRepresentation := make(map[string]interface{}) 103 for i := 0; i < rType.NumField(); i++ { 104 rField := rType.Field(i) 105 if rField.Type.Kind() == reflect.Struct { 106 if val := generateEnvironmentMapWithBaseKey(env, rField.Type, base+"_"+rField.Name); val != nil { 107 mapRepresentation[rField.Name] = val 108 } 109 } else { 110 if _, ok := env[strings.ToUpper(base+"_"+rField.Name)]; ok { 111 mapRepresentation[rField.Name] = true 112 } 113 } 114 } 115 116 if len(mapRepresentation) == 0 { 117 return nil 118 } 119 120 return mapRepresentation 121 } 122 123 // removeEnvOverrides returns a new config without the given environment overrides. 124 // If a config variable has an environment override, that variable is set to the value that was 125 // read from the store. 126 func removeEnvOverrides(cfg, cfgWithoutEnv *model.Config, envOverrides map[string]interface{}) *model.Config { 127 paths := getPaths(envOverrides) 128 newCfg := cfg.Clone() 129 for _, path := range paths { 130 originalVal := getVal(cfgWithoutEnv, path) 131 newVal := getVal(newCfg, path) 132 if newVal.CanSet() { 133 newVal.Set(originalVal) 134 } 135 } 136 return newCfg 137 } 138 139 // getPaths turns a nested map into a slice of paths describing the keys of the map. Eg: 140 // map[string]map[string]map[string]bool{"this":{"is first":{"path":true}, "is second":{"path":true}))) is turned into: 141 // [][]string{{"this", "is first", "path"}, {"this", "is second", "path"}} 142 func getPaths(m map[string]interface{}) [][]string { 143 return getPathsRec(m, nil) 144 } 145 146 // getPathsRec assembles the paths (see `getPaths` above) 147 func getPathsRec(src interface{}, curPath []string) [][]string { 148 if srcMap, ok := src.(map[string]interface{}); ok { 149 paths := [][]string{} 150 for k, v := range srcMap { 151 paths = append(paths, getPathsRec(v, append(curPath, k))...) 152 } 153 return paths 154 } 155 156 return [][]string{curPath} 157 } 158 159 // getVal walks `src` (here it starts with a model.Config, then recurses into its leaves) 160 // and returns the reflect.Value of the leaf at the end `path` 161 func getVal(src interface{}, path []string) reflect.Value { 162 var val reflect.Value 163 164 // If we recursed on a Value, we already have it. If we're calling on an interface{}, get the Value. 165 switch v := src.(type) { 166 case reflect.Value: 167 val = v 168 default: 169 val = reflect.ValueOf(src) 170 } 171 172 // Move into the struct 173 if val.Kind() == reflect.Ptr { 174 val = val.Elem().FieldByName(path[0]) 175 } else { 176 val = val.FieldByName(path[0]) 177 } 178 if val.Kind() == reflect.Ptr { 179 val = val.Elem() 180 } 181 if val.Kind() == reflect.Struct { 182 return getVal(val, path[1:]) 183 } 184 return val 185 }