github.com/avenga/couper@v1.12.2/config/env/env.go (about) 1 package env 2 3 import ( 4 "fmt" 5 "os" 6 "reflect" 7 "strconv" 8 "strings" 9 "sync" 10 "time" 11 12 "github.com/avenga/couper/config" 13 ) 14 15 const PREFIX = "COUPER_" 16 17 var OsEnvironMu = sync.Mutex{} 18 var OsEnviron = os.Environ 19 20 func SetTestOsEnviron(f func() []string) { 21 OsEnvironMu.Lock() 22 defer OsEnvironMu.Unlock() 23 24 OsEnviron = f 25 } 26 27 func Decode(conf interface{}) { 28 DecodeWithPrefix(conf, "") 29 } 30 31 func DecodeWithPrefix(conf interface{}, prefix string) { 32 ctxPrefix := PREFIX + prefix 33 envMap := make(map[string]string) 34 35 OsEnvironMu.Lock() 36 envVars := OsEnviron() 37 OsEnvironMu.Unlock() 38 39 for _, v := range envVars { 40 key := strings.Split(v, "=") 41 if !strings.HasPrefix(key[0], ctxPrefix) { 42 continue 43 } 44 envMap[strings.ToLower(key[0][len(ctxPrefix):])] = key[1] 45 } 46 47 if len(envMap) == 0 { 48 return 49 } 50 51 val := reflect.ValueOf(conf) 52 if val.Kind() == reflect.Ptr { 53 val = val.Elem() 54 } 55 for i := 0; i < val.NumField(); i++ { 56 field := val.Type().Field(i) 57 58 switch val.Field(i).Kind() { 59 case reflect.Ptr: 60 continue 61 case reflect.Struct: 62 DecodeWithPrefix(val.Field(i).Interface(), prefix) 63 default: 64 } 65 66 envVal, ok := field.Tag.Lookup("env") 67 if !ok { // fallback to hcl struct tag 68 envVal, ok = field.Tag.Lookup("hcl") 69 if !ok { 70 continue 71 } 72 } 73 envVal = strings.Split(envVal, ",")[0] 74 75 mapVal, exist := envMap[envVal] 76 if !exist || mapVal == "" { 77 continue 78 } 79 80 variableName := strings.ToUpper(PREFIX + envVal) 81 switch val.Field(i).Interface().(type) { 82 case bool: 83 val.Field(i).SetBool(mapVal == "true") 84 case int: 85 intVal, err := strconv.Atoi(mapVal) 86 if err != nil { 87 fmt.Fprintf(os.Stderr, "Invalid integer value for %q: %s\n", variableName, mapVal) 88 os.Exit(1) 89 } 90 val.Field(i).SetInt(int64(intVal)) 91 case string: 92 val.Field(i).SetString(mapVal) 93 case []string, config.List: 94 slice := strings.Split(mapVal, ",") 95 for idx, v := range slice { 96 slice[idx] = strings.TrimSpace(v) 97 } 98 val.Field(i).Set(reflect.ValueOf(slice)) 99 case time.Duration: 100 parsedDuration, err := time.ParseDuration(mapVal) 101 if err != nil { 102 fmt.Fprintf(os.Stderr, "Invalid duration value for %q: %s\n", variableName, mapVal) 103 os.Exit(1) 104 } 105 val.Field(i).Set(reflect.ValueOf(parsedDuration)) 106 default: 107 panic(fmt.Sprintf("env decode: type mapping not implemented: %v", reflect.TypeOf(val.Field(i).Interface()))) 108 } 109 } 110 }