New upstream version 1.3.1
Anthony Fok
5 years ago
20 | 20 | |
21 | 21 | *.exe |
22 | 22 | *.test |
23 | *.bench⏎ | |
23 | *.bench | |
24 | ||
25 | .vscode | |
26 | ||
27 | # exclude dependencies in the `/vendor` folder | |
28 | vendor |
0 | 0 | go_import_path: github.com/spf13/viper |
1 | 1 | |
2 | 2 | language: go |
3 | ||
4 | env: | |
5 | global: | |
6 | - GO111MODULE="on" | |
7 | ||
3 | 8 | go: |
4 | - 1.10.x | |
5 | 9 | - 1.11.x |
6 | 10 | - tip |
7 | 11 |
178 | 178 | ### Working with Environment Variables |
179 | 179 | |
180 | 180 | Viper has full support for environment variables. This enables 12 factor |
181 | applications out of the box. There are four methods that exist to aid working | |
181 | applications out of the box. There are five methods that exist to aid working | |
182 | 182 | with ENV: |
183 | 183 | |
184 | 184 | * `AutomaticEnv()` |
185 | 185 | * `BindEnv(string...) : error` |
186 | 186 | * `SetEnvPrefix(string)` |
187 | 187 | * `SetEnvKeyReplacer(string...) *strings.Replacer` |
188 | * `AllowEmptyEnvVar(bool)` | |
188 | 189 | |
189 | 190 | _When working with ENV variables, it’s important to recognize that Viper |
190 | 191 | treats ENV variables as case sensitive._ |
215 | 216 | keys to an extent. This is useful if you want to use `-` or something in your |
216 | 217 | `Get()` calls, but want your environmental variables to use `_` delimiters. An |
217 | 218 | example of using it can be found in `viper_test.go`. |
219 | ||
220 | By default empty environment variables are considered unset and will fall back to | |
221 | the next configuration source. To treat empty environment variables as set, use | |
222 | the `AllowEmptyEnv` method. | |
218 | 223 | |
219 | 224 | #### Env example |
220 | 225 |
0 | 0 | module github.com/spf13/viper |
1 | 1 | |
2 | 2 | require ( |
3 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect | |
4 | github.com/coreos/etcd v3.3.10+incompatible // indirect | |
5 | github.com/coreos/go-etcd v2.0.0+incompatible // indirect | |
6 | github.com/coreos/go-semver v0.2.0 // indirect | |
3 | 7 | github.com/fsnotify/fsnotify v1.4.7 |
4 | 8 | github.com/hashicorp/hcl v1.0.0 |
5 | 9 | github.com/magiconair/properties v1.8.0 |
6 | github.com/mitchellh/mapstructure v1.0.0 | |
10 | github.com/mitchellh/mapstructure v1.1.2 | |
7 | 11 | github.com/pelletier/go-toml v1.2.0 |
8 | 12 | github.com/spf13/afero v1.1.2 |
9 | github.com/spf13/cast v1.2.0 | |
13 | github.com/spf13/cast v1.3.0 | |
10 | 14 | github.com/spf13/jwalterweatherman v1.0.0 |
11 | github.com/spf13/pflag v1.0.2 | |
12 | golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992 // indirect | |
15 | github.com/spf13/pflag v1.0.3 | |
16 | github.com/stretchr/testify v1.2.2 | |
17 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 // indirect | |
18 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 | |
19 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 // indirect | |
20 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a // indirect | |
13 | 21 | golang.org/x/text v0.3.0 // indirect |
14 | gopkg.in/yaml.v2 v2.2.1 | |
22 | gopkg.in/yaml.v2 v2.2.2 | |
15 | 23 | ) |
0 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA= | |
1 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= | |
2 | github.com/coreos/etcd v3.3.10+incompatible h1:KjVWqrZ5U0wa3CxY2AxlH6/UcB+PK2td1DcsYhA+HRs= | |
3 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= | |
4 | github.com/coreos/go-etcd v2.0.0+incompatible h1:bXhRBIXoTm9BYHS3gE0TtQuyNZyeEMux2sDi4oo5YOo= | |
5 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= | |
6 | github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY= | |
7 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= | |
8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | |
0 | 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
1 | 10 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= |
2 | 11 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= |
4 | 13 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= |
5 | 14 | github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= |
6 | 15 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= |
7 | github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KHeTRY+7I= | |
8 | github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= | |
16 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= | |
17 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= | |
9 | 18 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= |
10 | 19 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= |
20 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | |
21 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | |
11 | 22 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= |
12 | 23 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= |
13 | github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg= | |
14 | github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= | |
24 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= | |
25 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= | |
15 | 26 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= |
16 | 27 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= |
17 | github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc= | |
18 | github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= | |
19 | golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992 h1:BH3eQWeGbwRU2+wxxuuPOdFBmaiBH81O8BugSjHeTFg= | |
20 | golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
28 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= | |
29 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= | |
30 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= | |
31 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | |
32 | github.com/stretchr/testify v1.2.3-0.20181014000028-04af85275a5c h1:03OmljzZYsezlgAfa+f/cY8E8XXPiFh5bgANMhUlDI4= | |
33 | github.com/stretchr/testify v1.2.3-0.20181014000028-04af85275a5c/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | |
34 | github.com/stretchr/testify v1.2.3-0.20181115233458-8019298d9fa5 h1:ixuBiBNIIQ3RKRSZy9B0DgaqreXG6NDHrbwAFGg8Mwk= | |
35 | github.com/stretchr/testify v1.2.3-0.20181115233458-8019298d9fa5/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | |
36 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648= | |
37 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= | |
38 | github.com/xordataexchange/crypt v0.0.2 h1:VBfFXTpEwLq2hzs42qCHOyKw5AqEm9DYGqBuINmzUZY= | |
39 | github.com/xordataexchange/crypt v0.0.2/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= | |
40 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow= | |
41 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= | |
42 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= | |
43 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | |
44 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A= | |
45 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
21 | 46 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= |
22 | 47 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
23 | 48 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
24 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= | |
25 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | |
49 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= | |
50 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
29 | 29 | "path/filepath" |
30 | 30 | "reflect" |
31 | 31 | "strings" |
32 | "sync" | |
32 | 33 | "time" |
33 | 34 | |
34 | 35 | yaml "gopkg.in/yaml.v2" |
185 | 186 | |
186 | 187 | automaticEnvApplied bool |
187 | 188 | envKeyReplacer *strings.Replacer |
189 | allowEmptyEnv bool | |
188 | 190 | |
189 | 191 | config map[string]interface{} |
190 | 192 | override map[string]interface{} |
275 | 277 | } |
276 | 278 | |
277 | 279 | func WatchConfig() { v.WatchConfig() } |
280 | ||
278 | 281 | func (v *Viper) WatchConfig() { |
282 | initWG := sync.WaitGroup{} | |
283 | initWG.Add(1) | |
279 | 284 | go func() { |
280 | 285 | watcher, err := fsnotify.NewWatcher() |
281 | 286 | if err != nil { |
282 | 287 | log.Fatal(err) |
283 | 288 | } |
284 | 289 | defer watcher.Close() |
285 | ||
286 | 290 | // we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way |
287 | 291 | filename, err := v.getConfigFile() |
288 | 292 | if err != nil { |
289 | log.Println("error:", err) | |
293 | log.Printf("error: %v\n", err) | |
290 | 294 | return |
291 | 295 | } |
292 | 296 | |
293 | 297 | configFile := filepath.Clean(filename) |
294 | 298 | configDir, _ := filepath.Split(configFile) |
295 | ||
296 | done := make(chan bool) | |
299 | realConfigFile, _ := filepath.EvalSymlinks(filename) | |
300 | ||
301 | eventsWG := sync.WaitGroup{} | |
302 | eventsWG.Add(1) | |
297 | 303 | go func() { |
298 | 304 | for { |
299 | 305 | select { |
300 | case event := <-watcher.Events: | |
301 | // we only care about the config file | |
302 | if filepath.Clean(event.Name) == configFile { | |
303 | if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create { | |
304 | err := v.ReadInConfig() | |
305 | if err != nil { | |
306 | log.Println("error:", err) | |
307 | } | |
308 | if v.onConfigChange != nil { | |
309 | v.onConfigChange(event) | |
310 | } | |
306 | case event, ok := <-watcher.Events: | |
307 | if !ok { // 'Events' channel is closed | |
308 | eventsWG.Done() | |
309 | return | |
310 | } | |
311 | currentConfigFile, _ := filepath.EvalSymlinks(filename) | |
312 | // we only care about the config file with the following cases: | |
313 | // 1 - if the config file was modified or created | |
314 | // 2 - if the real path to the config file changed (eg: k8s ConfigMap replacement) | |
315 | const writeOrCreateMask = fsnotify.Write | fsnotify.Create | |
316 | if (filepath.Clean(event.Name) == configFile && | |
317 | event.Op&writeOrCreateMask != 0) || | |
318 | (currentConfigFile != "" && currentConfigFile != realConfigFile) { | |
319 | realConfigFile = currentConfigFile | |
320 | err := v.ReadInConfig() | |
321 | if err != nil { | |
322 | log.Printf("error reading config file: %v\n", err) | |
311 | 323 | } |
324 | if v.onConfigChange != nil { | |
325 | v.onConfigChange(event) | |
326 | } | |
327 | } else if filepath.Clean(event.Name) == configFile && | |
328 | event.Op&fsnotify.Remove&fsnotify.Remove != 0 { | |
329 | eventsWG.Done() | |
330 | return | |
312 | 331 | } |
313 | case err := <-watcher.Errors: | |
314 | log.Println("error:", err) | |
332 | ||
333 | case err, ok := <-watcher.Errors: | |
334 | if ok { // 'Errors' channel is not closed | |
335 | log.Printf("watcher error: %v\n", err) | |
336 | } | |
337 | eventsWG.Done() | |
338 | return | |
315 | 339 | } |
316 | 340 | } |
317 | 341 | }() |
318 | ||
319 | 342 | watcher.Add(configDir) |
320 | <-done | |
343 | initWG.Done() // done initalizing the watch in this go routine, so the parent routine can move on... | |
344 | eventsWG.Wait() // now, wait for event loop to end in this go-routine... | |
321 | 345 | }() |
346 | initWG.Wait() // make sure that the go routine above fully ended before returning | |
322 | 347 | } |
323 | 348 | |
324 | 349 | // SetConfigFile explicitly defines the path, name and extension of the config file. |
348 | 373 | return strings.ToUpper(in) |
349 | 374 | } |
350 | 375 | |
376 | // AllowEmptyEnv tells Viper to consider set, | |
377 | // but empty environment variables as valid values instead of falling back. | |
378 | // For backward compatibility reasons this is false by default. | |
379 | func AllowEmptyEnv(allowEmptyEnv bool) { v.AllowEmptyEnv(allowEmptyEnv) } | |
380 | func (v *Viper) AllowEmptyEnv(allowEmptyEnv bool) { | |
381 | v.allowEmptyEnv = allowEmptyEnv | |
382 | } | |
383 | ||
351 | 384 | // TODO: should getEnv logic be moved into find(). Can generalize the use of |
352 | 385 | // rewriting keys many things, Ex: Get('someKey') -> some_key |
353 | 386 | // (camel case to snake case for JSON keys perhaps) |
355 | 388 | // getEnv is a wrapper around os.Getenv which replaces characters in the original |
356 | 389 | // key. This allows env vars which have different keys than the config object |
357 | 390 | // keys. |
358 | func (v *Viper) getEnv(key string) string { | |
391 | func (v *Viper) getEnv(key string) (string, bool) { | |
359 | 392 | if v.envKeyReplacer != nil { |
360 | 393 | key = v.envKeyReplacer.Replace(key) |
361 | 394 | } |
362 | return os.Getenv(key) | |
395 | ||
396 | val, ok := os.LookupEnv(key) | |
397 | ||
398 | return val, ok && (v.allowEmptyEnv || val != "") | |
363 | 399 | } |
364 | 400 | |
365 | 401 | // ConfigFileUsed returns the file used to populate the config registry. |
586 | 622 | // "foo.bar.baz" in a lower-priority map |
587 | 623 | func (v *Viper) isPathShadowedInAutoEnv(path []string) string { |
588 | 624 | var parentKey string |
589 | var val string | |
590 | 625 | for i := 1; i < len(path); i++ { |
591 | 626 | parentKey = strings.Join(path[0:i], v.keyDelim) |
592 | if val = v.getEnv(v.mergeWithEnvPrefix(parentKey)); val != "" { | |
627 | if _, ok := v.getEnv(v.mergeWithEnvPrefix(parentKey)); ok { | |
593 | 628 | return parentKey |
594 | 629 | } |
595 | 630 | } |
968 | 1003 | if v.automaticEnvApplied { |
969 | 1004 | // even if it hasn't been registered, if automaticEnv is used, |
970 | 1005 | // check any Get request |
971 | if val = v.getEnv(v.mergeWithEnvPrefix(lcaseKey)); val != "" { | |
1006 | if val, ok := v.getEnv(v.mergeWithEnvPrefix(lcaseKey)); ok { | |
972 | 1007 | return val |
973 | 1008 | } |
974 | 1009 | if nested && v.isPathShadowedInAutoEnv(path) != "" { |
977 | 1012 | } |
978 | 1013 | envkey, exists := v.env[lcaseKey] |
979 | 1014 | if exists { |
980 | if val = v.getEnv(envkey); val != "" { | |
1015 | if val, ok := v.getEnv(envkey); ok { | |
981 | 1016 | return val |
982 | 1017 | } |
983 | 1018 | } |
1223 | 1258 | // MergeConfig merges a new configuration with an existing config. |
1224 | 1259 | func MergeConfig(in io.Reader) error { return v.MergeConfig(in) } |
1225 | 1260 | func (v *Viper) MergeConfig(in io.Reader) error { |
1226 | if v.config == nil { | |
1227 | v.config = make(map[string]interface{}) | |
1228 | } | |
1229 | 1261 | cfg := make(map[string]interface{}) |
1230 | 1262 | if err := v.unmarshalReader(in, cfg); err != nil { |
1231 | 1263 | return err |
1232 | 1264 | } |
1265 | return v.MergeConfigMap(cfg) | |
1266 | } | |
1267 | ||
1268 | // MergeConfigMap merges the configuration from the map given with an existing config. | |
1269 | // Note that the map given may be modified. | |
1270 | func MergeConfigMap(cfg map[string]interface{}) error { return v.MergeConfigMap(cfg) } | |
1271 | func (v *Viper) MergeConfigMap(cfg map[string]interface{}) error { | |
1272 | if v.config == nil { | |
1273 | v.config = make(map[string]interface{}) | |
1274 | } | |
1275 | insensitiviseMap(cfg) | |
1233 | 1276 | mergeMaps(cfg, v.config, nil) |
1234 | 1277 | return nil |
1235 | 1278 | } |
11 | 11 | "io" |
12 | 12 | "io/ioutil" |
13 | 13 | "os" |
14 | "os/exec" | |
14 | 15 | "path" |
15 | 16 | "reflect" |
17 | "runtime" | |
16 | 18 | "sort" |
17 | 19 | "strings" |
20 | "sync" | |
18 | 21 | "testing" |
19 | 22 | "time" |
20 | 23 | |
24 | "github.com/fsnotify/fsnotify" | |
21 | 25 | "github.com/mitchellh/mapstructure" |
22 | 26 | "github.com/spf13/afero" |
23 | 27 | "github.com/spf13/cast" |
24 | 28 | |
25 | 29 | "github.com/spf13/pflag" |
26 | 30 | "github.com/stretchr/testify/assert" |
31 | "github.com/stretchr/testify/require" | |
27 | 32 | ) |
28 | 33 | |
29 | 34 | var yamlExample = []byte(`Hacker: true |
380 | 385 | |
381 | 386 | assert.Equal(t, "crunk", Get("name")) |
382 | 387 | |
388 | } | |
389 | ||
390 | func TestEmptyEnv(t *testing.T) { | |
391 | initJSON() | |
392 | ||
393 | BindEnv("type") // Empty environment variable | |
394 | BindEnv("name") // Bound, but not set environment variable | |
395 | ||
396 | os.Clearenv() | |
397 | ||
398 | os.Setenv("TYPE", "") | |
399 | ||
400 | assert.Equal(t, "donut", Get("type")) | |
401 | assert.Equal(t, "Cake", Get("name")) | |
402 | } | |
403 | ||
404 | func TestEmptyEnv_Allowed(t *testing.T) { | |
405 | initJSON() | |
406 | ||
407 | AllowEmptyEnv(true) | |
408 | ||
409 | BindEnv("type") // Empty environment variable | |
410 | BindEnv("name") // Bound, but not set environment variable | |
411 | ||
412 | os.Clearenv() | |
413 | ||
414 | os.Setenv("TYPE", "") | |
415 | ||
416 | assert.Equal(t, "", Get("type")) | |
417 | assert.Equal(t, "Cake", Get("name")) | |
383 | 418 | } |
384 | 419 | |
385 | 420 | func TestEnvPrefix(t *testing.T) { |
577 | 612 | } |
578 | 613 | |
579 | 614 | func TestBindPFlagsStringSlice(t *testing.T) { |
580 | for _, testValue := range []struct { | |
615 | tests := []struct { | |
581 | 616 | Expected []string |
582 | 617 | Value string |
583 | 618 | }{ |
584 | {[]string{}, ""}, | |
619 | {nil, ""}, | |
585 | 620 | {[]string{"jeden"}, "jeden"}, |
586 | 621 | {[]string{"dwa", "trzy"}, "dwa,trzy"}, |
587 | {[]string{"cztery", "piec , szesc"}, "cztery,\"piec , szesc\""}} { | |
622 | {[]string{"cztery", "piec , szesc"}, "cztery,\"piec , szesc\""}, | |
623 | } | |
624 | ||
625 | v := New() // create independent Viper object | |
626 | defaultVal := []string{"default"} | |
627 | v.SetDefault("stringslice", defaultVal) | |
628 | ||
629 | for _, testValue := range tests { | |
630 | flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) | |
631 | flagSet.StringSlice("stringslice", testValue.Expected, "test") | |
588 | 632 | |
589 | 633 | for _, changed := range []bool{true, false} { |
590 | v := New() // create independent Viper object | |
591 | flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) | |
592 | flagSet.StringSlice("stringslice", testValue.Expected, "test") | |
593 | flagSet.Visit(func(f *pflag.Flag) { | |
594 | if len(testValue.Value) > 0 { | |
595 | f.Value.Set(testValue.Value) | |
596 | f.Changed = changed | |
597 | } | |
634 | flagSet.VisitAll(func(f *pflag.Flag) { | |
635 | f.Value.Set(testValue.Value) | |
636 | f.Changed = changed | |
598 | 637 | }) |
599 | 638 | |
600 | 639 | err := v.BindPFlags(flagSet) |
609 | 648 | if err := v.Unmarshal(val); err != nil { |
610 | 649 | t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err) |
611 | 650 | } |
612 | assert.Equal(t, testValue.Expected, val.StringSlice) | |
651 | if changed { | |
652 | assert.Equal(t, testValue.Expected, val.StringSlice) | |
653 | } else { | |
654 | assert.Equal(t, defaultVal, val.StringSlice) | |
655 | } | |
613 | 656 | } |
614 | 657 | } |
615 | 658 | } |
1186 | 1229 | } |
1187 | 1230 | } |
1188 | 1231 | |
1232 | func TestMergeConfigMap(t *testing.T) { | |
1233 | v := New() | |
1234 | v.SetConfigType("yml") | |
1235 | if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { | |
1236 | t.Fatal(err) | |
1237 | } | |
1238 | ||
1239 | assert := func(i int) { | |
1240 | large := v.GetInt("hello.lagrenum") | |
1241 | pop := v.GetInt("hello.pop") | |
1242 | if large != 765432101234567 { | |
1243 | t.Fatal("Got large num:", large) | |
1244 | } | |
1245 | ||
1246 | if pop != i { | |
1247 | t.Fatal("Got pop:", pop) | |
1248 | } | |
1249 | } | |
1250 | ||
1251 | assert(37890) | |
1252 | ||
1253 | update := map[string]interface{}{ | |
1254 | "Hello": map[string]interface{}{ | |
1255 | "Pop": 1234, | |
1256 | }, | |
1257 | "World": map[interface{}]interface{}{ | |
1258 | "Rock": 345, | |
1259 | }, | |
1260 | } | |
1261 | ||
1262 | if err := v.MergeConfigMap(update); err != nil { | |
1263 | t.Fatal(err) | |
1264 | } | |
1265 | ||
1266 | if rock := v.GetInt("world.rock"); rock != 345 { | |
1267 | t.Fatal("Got rock:", rock) | |
1268 | } | |
1269 | ||
1270 | assert(1234) | |
1271 | ||
1272 | } | |
1273 | ||
1189 | 1274 | func TestUnmarshalingWithAliases(t *testing.T) { |
1190 | 1275 | v := New() |
1191 | 1276 | v.SetDefault("ID", 1) |
1405 | 1490 | |
1406 | 1491 | } |
1407 | 1492 | |
1493 | func newViperWithConfigFile(t *testing.T) (*Viper, string, func()) { | |
1494 | watchDir, err := ioutil.TempDir("", "") | |
1495 | require.Nil(t, err) | |
1496 | configFile := path.Join(watchDir, "config.yaml") | |
1497 | err = ioutil.WriteFile(configFile, []byte("foo: bar\n"), 0640) | |
1498 | require.Nil(t, err) | |
1499 | cleanup := func() { | |
1500 | os.RemoveAll(watchDir) | |
1501 | } | |
1502 | v := New() | |
1503 | v.SetConfigFile(configFile) | |
1504 | err = v.ReadInConfig() | |
1505 | require.Nil(t, err) | |
1506 | require.Equal(t, "bar", v.Get("foo")) | |
1507 | return v, configFile, cleanup | |
1508 | } | |
1509 | ||
1510 | func newViperWithSymlinkedConfigFile(t *testing.T) (*Viper, string, string, func()) { | |
1511 | watchDir, err := ioutil.TempDir("", "") | |
1512 | require.Nil(t, err) | |
1513 | dataDir1 := path.Join(watchDir, "data1") | |
1514 | err = os.Mkdir(dataDir1, 0777) | |
1515 | require.Nil(t, err) | |
1516 | realConfigFile := path.Join(dataDir1, "config.yaml") | |
1517 | t.Logf("Real config file location: %s\n", realConfigFile) | |
1518 | err = ioutil.WriteFile(realConfigFile, []byte("foo: bar\n"), 0640) | |
1519 | require.Nil(t, err) | |
1520 | cleanup := func() { | |
1521 | os.RemoveAll(watchDir) | |
1522 | } | |
1523 | // now, symlink the tm `data1` dir to `data` in the baseDir | |
1524 | os.Symlink(dataDir1, path.Join(watchDir, "data")) | |
1525 | // and link the `<watchdir>/datadir1/config.yaml` to `<watchdir>/config.yaml` | |
1526 | configFile := path.Join(watchDir, "config.yaml") | |
1527 | os.Symlink(path.Join(watchDir, "data", "config.yaml"), configFile) | |
1528 | t.Logf("Config file location: %s\n", path.Join(watchDir, "config.yaml")) | |
1529 | // init Viper | |
1530 | v := New() | |
1531 | v.SetConfigFile(configFile) | |
1532 | err = v.ReadInConfig() | |
1533 | require.Nil(t, err) | |
1534 | require.Equal(t, "bar", v.Get("foo")) | |
1535 | return v, watchDir, configFile, cleanup | |
1536 | } | |
1537 | ||
1538 | func TestWatchFile(t *testing.T) { | |
1539 | if runtime.GOOS == "linux" { | |
1540 | // TODO(bep) FIX ME | |
1541 | t.Skip("Skip test on Linux ...") | |
1542 | } | |
1543 | ||
1544 | t.Run("file content changed", func(t *testing.T) { | |
1545 | // given a `config.yaml` file being watched | |
1546 | v, configFile, cleanup := newViperWithConfigFile(t) | |
1547 | defer cleanup() | |
1548 | _, err := os.Stat(configFile) | |
1549 | require.NoError(t, err) | |
1550 | t.Logf("test config file: %s\n", configFile) | |
1551 | wg := sync.WaitGroup{} | |
1552 | wg.Add(1) | |
1553 | v.OnConfigChange(func(in fsnotify.Event) { | |
1554 | t.Logf("config file changed") | |
1555 | wg.Done() | |
1556 | }) | |
1557 | v.WatchConfig() | |
1558 | // when overwriting the file and waiting for the custom change notification handler to be triggered | |
1559 | err = ioutil.WriteFile(configFile, []byte("foo: baz\n"), 0640) | |
1560 | wg.Wait() | |
1561 | // then the config value should have changed | |
1562 | require.Nil(t, err) | |
1563 | assert.Equal(t, "baz", v.Get("foo")) | |
1564 | }) | |
1565 | ||
1566 | t.Run("link to real file changed (à la Kubernetes)", func(t *testing.T) { | |
1567 | // skip if not executed on Linux | |
1568 | if runtime.GOOS != "linux" { | |
1569 | t.Skipf("Skipping test as symlink replacements don't work on non-linux environment...") | |
1570 | } | |
1571 | v, watchDir, _, _ := newViperWithSymlinkedConfigFile(t) | |
1572 | // defer cleanup() | |
1573 | wg := sync.WaitGroup{} | |
1574 | v.WatchConfig() | |
1575 | v.OnConfigChange(func(in fsnotify.Event) { | |
1576 | t.Logf("config file changed") | |
1577 | wg.Done() | |
1578 | }) | |
1579 | wg.Add(1) | |
1580 | // when link to another `config.yaml` file | |
1581 | dataDir2 := path.Join(watchDir, "data2") | |
1582 | err := os.Mkdir(dataDir2, 0777) | |
1583 | require.Nil(t, err) | |
1584 | configFile2 := path.Join(dataDir2, "config.yaml") | |
1585 | err = ioutil.WriteFile(configFile2, []byte("foo: baz\n"), 0640) | |
1586 | require.Nil(t, err) | |
1587 | // change the symlink using the `ln -sfn` command | |
1588 | err = exec.Command("ln", "-sfn", dataDir2, path.Join(watchDir, "data")).Run() | |
1589 | require.Nil(t, err) | |
1590 | wg.Wait() | |
1591 | // then | |
1592 | require.Nil(t, err) | |
1593 | assert.Equal(t, "baz", v.Get("foo")) | |
1594 | }) | |
1595 | ||
1596 | } | |
1597 | ||
1408 | 1598 | func BenchmarkGetBool(b *testing.B) { |
1409 | 1599 | key := "BenchmarkGetBool" |
1410 | 1600 | v = New() |