go-micro.dev/v5@v5.12.0/config/source/env/env.go (about) 1 package env 2 3 import ( 4 "os" 5 "strconv" 6 "strings" 7 "time" 8 9 "dario.cat/mergo" 10 "go-micro.dev/v5/config/source" 11 ) 12 13 var ( 14 DefaultPrefixes = []string{} 15 ) 16 17 type env struct { 18 opts source.Options 19 prefixes []string 20 strippedPrefixes []string 21 } 22 23 func (e *env) Read() (*source.ChangeSet, error) { 24 var changes map[string]interface{} 25 26 for _, env := range os.Environ() { 27 if len(e.prefixes) > 0 || len(e.strippedPrefixes) > 0 { 28 notFound := true 29 30 if _, ok := matchPrefix(e.prefixes, env); ok { 31 notFound = false 32 } 33 34 if match, ok := matchPrefix(e.strippedPrefixes, env); ok { 35 env = strings.TrimPrefix(env, match) 36 notFound = false 37 } 38 39 if notFound { 40 continue 41 } 42 } 43 44 pair := strings.SplitN(env, "=", 2) 45 value := pair[1] 46 keys := strings.Split(strings.ToLower(pair[0]), "_") 47 reverse(keys) 48 49 tmp := make(map[string]interface{}) 50 for i, k := range keys { 51 if i == 0 { 52 if intValue, err := strconv.Atoi(value); err == nil { 53 tmp[k] = intValue 54 } else if boolValue, err := strconv.ParseBool(value); err == nil { 55 tmp[k] = boolValue 56 } else { 57 tmp[k] = value 58 } 59 continue 60 } 61 62 tmp = map[string]interface{}{k: tmp} 63 } 64 65 if err := mergo.Map(&changes, tmp); err != nil { 66 return nil, err 67 } 68 } 69 70 b, err := e.opts.Encoder.Encode(changes) 71 if err != nil { 72 return nil, err 73 } 74 75 cs := &source.ChangeSet{ 76 Format: e.opts.Encoder.String(), 77 Data: b, 78 Timestamp: time.Now(), 79 Source: e.String(), 80 } 81 cs.Checksum = cs.Sum() 82 83 return cs, nil 84 } 85 86 func matchPrefix(pre []string, s string) (string, bool) { 87 for _, p := range pre { 88 if strings.HasPrefix(s, p) { 89 return p, true 90 } 91 } 92 93 return "", false 94 } 95 96 func reverse(ss []string) { 97 for i := len(ss)/2 - 1; i >= 0; i-- { 98 opp := len(ss) - 1 - i 99 ss[i], ss[opp] = ss[opp], ss[i] 100 } 101 } 102 103 func (e *env) Watch() (source.Watcher, error) { 104 return newWatcher() 105 } 106 107 func (e *env) Write(cs *source.ChangeSet) error { 108 return nil 109 } 110 111 func (e *env) String() string { 112 return "env" 113 } 114 115 // NewSource returns a config source for parsing ENV variables. 116 // Underscores are delimiters for nesting, and all keys are lowercased. 117 // 118 // Example: 119 // 120 // "DATABASE_SERVER_HOST=localhost" will convert to 121 // 122 // { 123 // "database": { 124 // "server": { 125 // "host": "localhost" 126 // } 127 // } 128 // } 129 func NewSource(opts ...source.Option) source.Source { 130 options := source.NewOptions(opts...) 131 132 var sp []string 133 var pre []string 134 if p, ok := options.Context.Value(strippedPrefixKey{}).([]string); ok { 135 sp = p 136 } 137 138 if p, ok := options.Context.Value(prefixKey{}).([]string); ok { 139 pre = p 140 } 141 142 if len(sp) > 0 || len(pre) > 0 { 143 pre = append(pre, DefaultPrefixes...) 144 } 145 return &env{prefixes: pre, strippedPrefixes: sp, opts: options} 146 }