From 2b4f7d3cde7da656a0fbb4ac4c6bbf7f033a51f9 Mon Sep 17 00:00:00 2001 From: victor23d Date: Sat, 22 Jun 2019 16:02:30 +0000 Subject: [PATCH 01/25] Add dotenv documentation in README.md --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 24ffb20..62b002b 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ to work within an application, and can handle all types of configuration needs and formats. It supports: * setting defaults -* reading from JSON, TOML, YAML, HCL, and Java properties config files +* reading from JSON, TOML, YAML, HCL, envfile and Java properties config files * live watching and re-reading of config files (optional) * reading from environment variables * reading from remote config systems (etcd or Consul), and watching changes @@ -46,7 +46,7 @@ Viper is here to help with that. Viper does the following for you: -1. Find, load, and unmarshal a configuration file in JSON, TOML, YAML, HCL, or Java properties formats. +1. Find, load, and unmarshal a configuration file in JSON, TOML, YAML, HCL, envfile or Java properties formats. 2. Provide a mechanism to set default values for your different configuration options. 3. Provide a mechanism to set override values for options specified through @@ -87,7 +87,7 @@ viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "cat ### Reading Config Files Viper requires minimal configuration so it knows where to look for config files. -Viper supports JSON, TOML, YAML, HCL, and Java Properties files. Viper can search multiple paths, but +Viper supports JSON, TOML, YAML, HCL, envfile and Java Properties files. Viper can search multiple paths, but currently a single Viper instance only supports a single configuration file. Viper does not default to any configuration search paths leaving defaults decision to an application. @@ -372,7 +372,7 @@ package: `import _ "github.com/spf13/viper/remote"` -Viper will read a config string (as JSON, TOML, YAML or HCL) retrieved from a path +Viper will read a config string (as JSON, TOML, YAML, HCL or envfile) retrieved from a path in a Key/Value store such as etcd or Consul. These values take precedence over default values, but are overridden by configuration values retrieved from disk, flags, or environment variables. @@ -407,7 +407,7 @@ how to use Consul. #### etcd ```go viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json") -viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop" +viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" err := viper.ReadRemoteConfig() ``` @@ -435,7 +435,7 @@ fmt.Println(viper.Get("hostname")) // myhostname.com ```go viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg") -viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop" +viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" err := viper.ReadRemoteConfig() ``` @@ -446,7 +446,7 @@ err := viper.ReadRemoteConfig() var runtime_viper = viper.New() runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml") -runtime_viper.SetConfigType("yaml") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop" +runtime_viper.SetConfigType("yaml") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" // read from remote config the first time. err := runtime_viper.ReadRemoteConfig() From 96441b4b7414b184eb59823c0e6ca5494e4706d8 Mon Sep 17 00:00:00 2001 From: Bruce Wang Date: Thu, 20 Jun 2019 15:08:30 +0800 Subject: [PATCH 02/25] BindEnv details specified. Now less confusion. --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 62b002b..f2537ce 100644 --- a/README.md +++ b/README.md @@ -225,9 +225,8 @@ prefix. `BindEnv` takes one or two parameters. The first parameter is the key name, the second is the name of the environment variable. The name of the environment variable is case sensitive. If the ENV variable name is not provided, then -Viper will automatically assume that the key name matches the ENV variable name, -but the ENV variable is IN ALL CAPS. When you explicitly provide the ENV -variable name, it **does not** automatically add the prefix. +Viper will automatically assume that the ENV variable is the prefix + "_" + the key name in ALL CAPS. When you explicitly provide the ENV +variable name which is the second parameter, it **does not** automatically add the prefix, which means if the second parameter is "id", the environment variable is just plain "ID". One important thing to recognize when working with ENV variables is that the value will be read each time it is accessed. Viper does not fix the value when From d34be8d9ee3308fecdadaecf8139cabeaa4a64a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rk=20S=C3=A1gi-Kaz=C3=A1r?= Date: Sat, 13 Jul 2019 11:32:26 +0200 Subject: [PATCH 03/25] Improve BindEnv details --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f2537ce..5fdaa48 100644 --- a/README.md +++ b/README.md @@ -225,8 +225,9 @@ prefix. `BindEnv` takes one or two parameters. The first parameter is the key name, the second is the name of the environment variable. The name of the environment variable is case sensitive. If the ENV variable name is not provided, then -Viper will automatically assume that the ENV variable is the prefix + "_" + the key name in ALL CAPS. When you explicitly provide the ENV -variable name which is the second parameter, it **does not** automatically add the prefix, which means if the second parameter is "id", the environment variable is just plain "ID". +Viper will automatically assume that the ENV variable matches the following format: prefix + "_" + the key name in ALL CAPS. When you explicitly provide the ENV variable name (the second parameter), +it **does not** automatically add the prefix. For example if the second parameter is "id", +Viper will look for the ENV variable "ID". One important thing to recognize when working with ENV variables is that the value will be read each time it is accessed. Viper does not fix the value when From 5ae3a072d1057b77d443b65ed0927998111ec766 Mon Sep 17 00:00:00 2001 From: lucperkins Date: Fri, 21 Jun 2019 18:43:31 -0700 Subject: [PATCH 04/25] Add documentation for no config file found Signed-off-by: lucperkins --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 5fdaa48..f4dc3bd 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,20 @@ if err != nil { // Handle errors reading the config file } ``` +You can handle the specific case where no config file is found like this: + +```go +if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + // Config file not found; ignore error if desired + } else { + // Config file was found but another error was produced + } +} + +// Config file found and successfully parsed +``` + ### Writing Config Files Reading from config files is useful, but at times you want to store all modifications made at run time. From 72cbe340cb4939713d5f43c01ccd82fa6155bdd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rk=20S=C3=A1gi-Kaz=C3=A1r?= Date: Sat, 13 Jul 2019 11:42:53 +0200 Subject: [PATCH 05/25] Improve travis --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index bb83057..d122f22 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,10 +4,12 @@ language: go env: global: - - GO111MODULE="on" + - GO111MODULE="on" + - GOFLAGS="-mod=readonly" go: - 1.11.x + - 1.12.x - tip os: @@ -27,5 +29,3 @@ script: after_success: - go get -u -d github.com/spf13/hugo - cd $GOPATH/src/github.com/spf13/hugo && make && ./hugo -s docs && cd - - -sudo: false From b8221cf4ee9174f124f60bdcf24944da0b0b5643 Mon Sep 17 00:00:00 2001 From: AGirard Date: Wed, 19 Jun 2019 16:05:58 +0200 Subject: [PATCH 06/25] Add GetIntSlice helper method --- README.md | 1 + viper.go | 6 ++++++ viper_test.go | 11 +++++++++++ 3 files changed, 18 insertions(+) diff --git a/README.md b/README.md index f4dc3bd..171f51c 100644 --- a/README.md +++ b/README.md @@ -496,6 +496,7 @@ The following functions and methods exist: * `GetBool(key string) : bool` * `GetFloat64(key string) : float64` * `GetInt(key string) : int` + * `GetIntSlice(key string) : []int` * `GetString(key string) : string` * `GetStringMap(key string) : map[string]interface{}` * `GetStringMapString(key string) : map[string]string` diff --git a/viper.go b/viper.go index 0699c4c..881bf0c 100644 --- a/viper.go +++ b/viper.go @@ -797,6 +797,12 @@ func (v *Viper) GetDuration(key string) time.Duration { return cast.ToDuration(v.Get(key)) } +// GetIntSlice returns the value associated with the key as a slice of strings. +func GetIntSlice(key string) []int { return v.GetIntSlice(key) } +func (v *Viper) GetIntSlice(key string) []int { + return cast.ToIntSlice(v.Get(key)) +} + // GetStringSlice returns the value associated with the key as a slice of strings. func GetStringSlice(key string) []string { return v.GetStringSlice(key) } func (v *Viper) GetStringSlice(key string) []string { diff --git a/viper_test.go b/viper_test.go index c40c971..75afaae 100644 --- a/viper_test.go +++ b/viper_test.go @@ -1262,6 +1262,9 @@ hello: universe: - mw - ad + ints: + - 1 + - 2 fu: bar `) @@ -1328,6 +1331,10 @@ func TestMergeConfig(t *testing.T) { t.Fatalf("len(universe) != 2, = %d", len(universe)) } + if ints := v.GetIntSlice("hello.ints"); len(ints) != 2 { + t.Fatalf("len(ints) != 2, = %d", len(ints)) + } + if fu := v.GetString("fu"); fu != "bar" { t.Fatalf("fu != \"bar\", = %s", fu) } @@ -1368,6 +1375,10 @@ func TestMergeConfigNoMerge(t *testing.T) { t.Fatalf("len(universe) != 2, = %d", len(universe)) } + if ints := v.GetIntSlice("hello.ints"); len(ints) != 2 { + t.Fatalf("len(ints) != 2, = %d", len(ints)) + } + if fu := v.GetString("fu"); fu != "bar" { t.Fatalf("fu != \"bar\", = %s", fu) } From 7fdad0204e0e1c63db2813b0882ebe61722e2e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rk=20S=C3=A1gi-Kaz=C3=A1r?= Date: Sat, 13 Jul 2019 11:57:28 +0200 Subject: [PATCH 07/25] Fix typo --- viper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viper.go b/viper.go index 881bf0c..859a86f 100644 --- a/viper.go +++ b/viper.go @@ -797,7 +797,7 @@ func (v *Viper) GetDuration(key string) time.Duration { return cast.ToDuration(v.Get(key)) } -// GetIntSlice returns the value associated with the key as a slice of strings. +// GetIntSlice returns the value associated with the key as a slice of int values. func GetIntSlice(key string) []int { return v.GetIntSlice(key) } func (v *Viper) GetIntSlice(key string) []int { return cast.ToIntSlice(v.Get(key)) From 275a36d0a03ab384fbf34cca7fbee094d95fc0b2 Mon Sep 17 00:00:00 2001 From: CodeLingo Bot Date: Wed, 13 Feb 2019 00:48:45 +0000 Subject: [PATCH 08/25] Fix function comments based on best practices from Effective Go Signed-off-by: CodeLingo Bot --- flags.go | 2 +- viper.go | 4 ++-- viper_test.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/flags.go b/flags.go index dd32f4e..b5ddbf5 100644 --- a/flags.go +++ b/flags.go @@ -36,7 +36,7 @@ type pflagValue struct { flag *pflag.Flag } -// HasChanges returns whether the flag has changes or not. +// HasChanged returns whether the flag has changes or not. func (p pflagValue) HasChanged() bool { return p.flag.Changed } diff --git a/viper.go b/viper.go index 859a86f..5c441b4 100644 --- a/viper.go +++ b/viper.go @@ -226,7 +226,7 @@ func New() *Viper { return v } -// Intended for testing, will reset all to default settings. +// Reset: Intended for testing, will reset all to default settings. // In the public interface for the viper package so applications // can use it in their testing as well. func Reset() { @@ -1142,7 +1142,7 @@ func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer) { v.envKeyReplacer = r } -// Aliases provide another accessor for the same key. +// RegisterAlias: Aliases provide another accessor for the same key. // This enables one to change a name without breaking the application func RegisterAlias(alias string, key string) { v.RegisterAlias(alias, key) } func (v *Viper) RegisterAlias(alias string, key string) { diff --git a/viper_test.go b/viper_test.go index 75afaae..f91791f 100644 --- a/viper_test.go +++ b/viper_test.go @@ -1774,7 +1774,7 @@ func BenchmarkGet(b *testing.B) { } } -// This is the "perfect result" for the above. +// BenchmarkGetBoolFromMap is the "perfect result" for the above. func BenchmarkGetBoolFromMap(b *testing.B) { m := make(map[string]bool) key := "BenchmarkGetBool" From e6d1c6bc9a6debfae12cfd0285a24d1b2fc0b31a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rk=20S=C3=A1gi-Kaz=C3=A1r?= Date: Sat, 13 Jul 2019 12:04:26 +0200 Subject: [PATCH 09/25] Improve godoc --- viper.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/viper.go b/viper.go index 5c441b4..74ea372 100644 --- a/viper.go +++ b/viper.go @@ -226,7 +226,7 @@ func New() *Viper { return v } -// Reset: Intended for testing, will reset all to default settings. +// Reset is intended for testing, will reset all to default settings. // In the public interface for the viper package so applications // can use it in their testing as well. func Reset() { @@ -1142,8 +1142,8 @@ func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer) { v.envKeyReplacer = r } -// RegisterAlias: Aliases provide another accessor for the same key. -// This enables one to change a name without breaking the application +// RegisterAlias creates an alias that provides another accessor for the same key. +// This enables one to change a name without breaking the application. func RegisterAlias(alias string, key string) { v.RegisterAlias(alias, key) } func (v *Viper) RegisterAlias(alias string, key string) { v.registerAlias(alias, strings.ToLower(key)) From e325492b82e97dec8208f8a2362ee9efe9023261 Mon Sep 17 00:00:00 2001 From: Nicolas Martin Date: Thu, 3 Jan 2019 15:31:09 +0100 Subject: [PATCH 10/25] Add missing call to initWF.Done() --- viper.go | 1 + 1 file changed, 1 insertion(+) diff --git a/viper.go b/viper.go index 74ea372..363a6f3 100644 --- a/viper.go +++ b/viper.go @@ -295,6 +295,7 @@ func (v *Viper) WatchConfig() { filename, err := v.getConfigFile() if err != nil { log.Printf("error: %v\n", err) + initWG.Done() return } From e02bc9eca55d5fc66221bc0aeeaaa77410603914 Mon Sep 17 00:00:00 2001 From: bpizzi Date: Fri, 27 Jul 2018 10:10:14 +0200 Subject: [PATCH 11/25] Fixed missing f.Close() in writeConfig() Defering can cause trouble because we're writing to the file outside the function where the defering is registered, calling f.Sync() ensures that bytes are flushed to disk even is Close() is called too soon. --- viper.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/viper.go b/viper.go index 363a6f3..79c334e 100644 --- a/viper.go +++ b/viper.go @@ -1378,7 +1378,13 @@ func (v *Viper) writeConfig(filename string, force bool) error { if err != nil { return err } - return v.marshalWriter(f, configType) + defer f.Close() + + if err := v.marshalWriter(f, configType); err != nil { + return err + } + + return f.Sync() } // Unmarshal a Reader into a map. From cdccc8152cf6202c7ce866570a7390d81bf02fbd Mon Sep 17 00:00:00 2001 From: Rodrigo Chiossi Date: Tue, 30 Jan 2018 10:15:47 +0000 Subject: [PATCH 12/25] Fix SafeWriteConfig If the config file does not exist and the force flag is not set, OpenFile would not use O_CREATE flag, causing viper to fail with error "File not exist" and to not create the config file. This patch changes the behavior of writeConfig() so that if force is set to false, OpenFile will use O_EXCL flag, thus failing if the file already exists or creating a new file otherwise. Signed-off-by: Rodrigo Chiossi --- viper.go | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/viper.go b/viper.go index 79c334e..b89a96a 100644 --- a/viper.go +++ b/viper.go @@ -1364,15 +1364,9 @@ func (v *Viper) writeConfig(filename string, force bool) error { if v.config == nil { v.config = make(map[string]interface{}) } - var flags int - if force == true { - flags = os.O_CREATE | os.O_TRUNC | os.O_WRONLY - } else { - if _, err := os.Stat(filename); os.IsNotExist(err) { - flags = os.O_WRONLY - } else { - return fmt.Errorf("File: %s exists. Use WriteConfig to overwrite.", filename) - } + flags := os.O_CREATE | os.O_TRUNC | os.O_WRONLY + if !force { + flags |= os.O_EXCL } f, err := v.fs.OpenFile(filename, flags, v.configPermissions) if err != nil { From d65fa7608b54d861eae8524279cdc7e52f4dd432 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 20 Jul 2019 00:03:44 +0200 Subject: [PATCH 13/25] Fixed typo Just a nitpicky typo fix. --- viper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viper.go b/viper.go index b89a96a..07ef850 100644 --- a/viper.go +++ b/viper.go @@ -345,7 +345,7 @@ func (v *Viper) WatchConfig() { } }() watcher.Add(configDir) - initWG.Done() // done initalizing the watch in this go routine, so the parent routine can move on... + initWG.Done() // done initializing the watch in this go routine, so the parent routine can move on... eventsWG.Wait() // now, wait for event loop to end in this go-routine... }() initWG.Wait() // make sure that the go routine above fully ended before returning From 1b33e8258e07ea1da9064b06ee29a0d0831b693d Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 20 Jul 2019 00:10:42 +0200 Subject: [PATCH 14/25] Add error handling Added error handling around ineffectual err assignments. Please review thoroughly. --- viper.go | 3 +++ viper_test.go | 1 + 2 files changed, 4 insertions(+) diff --git a/viper.go b/viper.go index 07ef850..712c5e3 100644 --- a/viper.go +++ b/viper.go @@ -1469,6 +1469,9 @@ func (v *Viper) marshalWriter(f afero.File, configType string) error { case "hcl": b, err := json.Marshal(c) + if err != nil { + return ConfigMarshalError{err} + } ast, err := hcl.Parse(string(b)) if err != nil { return ConfigMarshalError{err} diff --git a/viper_test.go b/viper_test.go index f91791f..c1ea262 100644 --- a/viper_test.go +++ b/viper_test.go @@ -952,6 +952,7 @@ func TestDirsSearch(t *testing.T) { v.SetDefault(`key`, `default`) entries, err := ioutil.ReadDir(root) + assert.Nil(t, err) for _, e := range entries { if e.IsDir() { v.AddConfigPath(e.Name()) From 33bf76add3b746c1d275f0a5451b4d5750b2f2ff Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 20 Jul 2019 00:07:23 +0200 Subject: [PATCH 15/25] Simplify code Removed unnecessary conversions. --- viper.go | 4 ++-- viper_test.go | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/viper.go b/viper.go index 712c5e3..c65881a 100644 --- a/viper.go +++ b/viper.go @@ -1402,7 +1402,7 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error { } case "hcl": - obj, err := hcl.Parse(string(buf.Bytes())) + obj, err := hcl.Parse(buf.String()) if err != nil { return ConfigParseError{err} } @@ -1772,7 +1772,7 @@ func (v *Viper) flattenAndMergeMap(shadow map[string]bool, m map[string]interfac func (v *Viper) mergeFlatMap(shadow map[string]bool, m map[string]interface{}) map[string]bool { // scan keys outer: - for k, _ := range m { + for k := range m { path := strings.Split(k, v.keyDelim) // scan intermediate paths var parentKey string diff --git a/viper_test.go b/viper_test.go index c1ea262..592551c 100644 --- a/viper_test.go +++ b/viper_test.go @@ -8,7 +8,6 @@ package viper import ( "bytes" "encoding/json" - "fmt" "io" "io/ioutil" "os" @@ -263,7 +262,7 @@ func (s *stringValue) Type() string { } func (s *stringValue) String() string { - return fmt.Sprintf("%s", *s) + return string(*s) } func TestBasics(t *testing.T) { @@ -498,8 +497,7 @@ func TestAllKeys(t *testing.T) { dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") all := map[string]interface{}{"owner": map[string]interface{}{"organization": "MongoDB", "bio": "MongoDB Chief Developer Advocate & Hacker at Large", "dob": dob}, "title": "TOML Example", "ppu": 0.55, "eyes": "brown", "clothing": map[string]interface{}{"trousers": "denim", "jacket": "leather", "pants": map[string]interface{}{"size": "large"}}, "id": "0001", "batters": map[string]interface{}{"batter": []interface{}{map[string]interface{}{"type": "Regular"}, map[string]interface{}{"type": "Chocolate"}, map[string]interface{}{"type": "Blueberry"}, map[string]interface{}{"type": "Devil's Food"}}}, "hacker": true, "beard": true, "hobbies": []interface{}{"skateboarding", "snowboarding", "go"}, "age": 35, "type": "donut", "newkey": "remote", "name": "Cake", "p_id": "0001", "p_ppu": "0.55", "p_name": "Cake", "p_batters": map[string]interface{}{"batter": map[string]interface{}{"type": "Regular"}}, "p_type": "donut", "foos": []map[string]interface{}{map[string]interface{}{"foo": []map[string]interface{}{map[string]interface{}{"key": 1}, map[string]interface{}{"key": 2}, map[string]interface{}{"key": 3}, map[string]interface{}{"key": 4}}}}, "title_dotenv": "DotEnv Example", "type_dotenv": "donut", "name_dotenv": "Cake"} - var allkeys sort.StringSlice - allkeys = AllKeys() + allkeys := sort.StringSlice(AllKeys()) allkeys.Sort() ks.Sort() From e697d557b7f549ddf91098cef2ad6e92176dbcdf Mon Sep 17 00:00:00 2001 From: TwiN Date: Fri, 16 Aug 2019 17:15:10 -0400 Subject: [PATCH 16/25] Fix small typo --- viper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viper.go b/viper.go index c65881a..5133de7 100644 --- a/viper.go +++ b/viper.go @@ -3,7 +3,7 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. -// Viper is a application configuration system. +// Viper is an application configuration system. // It believes that applications can be configured a variety of ways // via flags, ENVIRONMENT variables, configuration files retrieved // from the file system, or a remote key/value store. From bd1db6bb8c597678e6e65e9d8f364ed0ead2721b Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Thu, 29 Aug 2019 15:29:27 +0200 Subject: [PATCH 17/25] Run go mod tidy --- go.mod | 1 + go.sum | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 9ee26ef..a1ea058 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( github.com/spf13/jwalterweatherman v1.0.0 github.com/spf13/pflag v1.0.3 github.com/stretchr/testify v1.2.2 + github.com/subosito/gotenv v1.2.0 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect github.com/ugorji/go v1.1.4 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect diff --git a/go.sum b/go.sum index a718a4d..6c22453 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,6 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= @@ -117,6 +115,8 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= From 99520c81d86ee7d0ee2ef143c8576be0c7533827 Mon Sep 17 00:00:00 2001 From: inkychris Date: Sat, 16 Mar 2019 11:10:32 +0000 Subject: [PATCH 18/25] Implemented ability to unmarshal keys containing dots to structs. Changed formatting of test objects for better git diffing and readibility. Fixed failing tests on Windows. --- viper.go | 20 +++++++- viper_test.go | 123 ++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 134 insertions(+), 9 deletions(-) diff --git a/viper.go b/viper.go index 5133de7..491799c 100644 --- a/viper.go +++ b/viper.go @@ -1789,6 +1789,24 @@ outer: return shadow } +// Converts a fully qualified map key into a list of relative +// map keys, allowing for keys to contain the delimiter themselves +func keyComponents(v *Viper, key string) []string { + var result []string + components := strings.Split(key, v.keyDelim) + for index := 0; index < len(components); index++ { + potentialKey := strings.Join(components[0:index], v.keyDelim) + if v.Get(potentialKey) != nil { + result = append(result, potentialKey) + } + } + result = append(result, key) + for i := len(result) - 1; i > 0; i-- { + result[i] = strings.Replace(result[i], result[i-1]+v.keyDelim, "", 1) + } + return result +} + // AllSettings merges all settings and returns them as a map[string]interface{}. func AllSettings() map[string]interface{} { return v.AllSettings() } func (v *Viper) AllSettings() map[string]interface{} { @@ -1801,7 +1819,7 @@ func (v *Viper) AllSettings() map[string]interface{} { // check just in case anything changes continue } - path := strings.Split(k, v.keyDelim) + path := keyComponents(v, k) lastKey := strings.ToLower(path[len(path)-1]) deepestMap := deepSearch(m, path[0:len(path)-1]) // set innermost value diff --git a/viper_test.go b/viper_test.go index 592551c..3c15ed2 100644 --- a/viper_test.go +++ b/viper_test.go @@ -13,6 +13,7 @@ import ( "os" "os/exec" "path" + "path/filepath" "reflect" "runtime" "sort" @@ -45,6 +46,10 @@ clothing: age: 35 eyes : brown beard: true +emails: + steve@hacker.com: + created: 01/02/03 + active: true `) var yamlExampleWithExtras = []byte(`Existing: true @@ -207,11 +212,16 @@ func initHcl() { func initDirs(t *testing.T) (string, string, func()) { var ( - testDirs = []string{`a a`, `b`, `c\c`, `D_`} + testDirs = []string{`a a`, `b`, `C_`} config = `improbable` ) + if runtime.GOOS != "windows" { + testDirs = append(testDirs, `d\d`) + } + root, err := ioutil.TempDir("", "") + require.NoError(t, err, "Failed to create temporary directory") cleanup := true defer func() { @@ -224,7 +234,7 @@ func initDirs(t *testing.T) (string, string, func()) { assert.Nil(t, err) err = os.Chdir(root) - assert.Nil(t, err) + require.Nil(t, err) for _, dir := range testDirs { err = os.Mkdir(dir, 0750) @@ -415,7 +425,10 @@ func TestEmptyEnv(t *testing.T) { BindEnv("type") // Empty environment variable BindEnv("name") // Bound, but not set environment variable - os.Clearenv() + os.Unsetenv("type") + os.Unsetenv("TYPE") + os.Unsetenv("name") + os.Unsetenv("NAME") os.Setenv("TYPE", "") @@ -431,7 +444,10 @@ func TestEmptyEnv_Allowed(t *testing.T) { BindEnv("type") // Empty environment variable BindEnv("name") // Bound, but not set environment variable - os.Clearenv() + os.Unsetenv("type") + os.Unsetenv("TYPE") + os.Unsetenv("name") + os.Unsetenv("NAME") os.Setenv("TYPE", "") @@ -491,11 +507,98 @@ func TestSetEnvKeyReplacer(t *testing.T) { func TestAllKeys(t *testing.T) { initConfigs() - ks := sort.StringSlice{"title", "newkey", "owner.organization", "owner.dob", "owner.bio", "name", "beard", "ppu", "batters.batter", "hobbies", "clothing.jacket", "clothing.trousers", "clothing.pants.size", "age", "hacker", "id", "type", "eyes", "p_id", "p_ppu", "p_batters.batter.type", "p_type", "p_name", "foos", - "title_dotenv", "type_dotenv", "name_dotenv", + ks := sort.StringSlice{ + "title", + "newkey", + "owner.organization", + "owner.dob", + "owner.bio", + "name", + "beard", + "ppu", + "batters.batter", + "hobbies", + "clothing.jacket", + "clothing.trousers", + "clothing.pants.size", + "age", + "hacker", + "id", + "type", + "eyes", + "p_id", + "p_ppu", + "p_batters.batter.type", + "p_type", + "p_name", + "foos", + "title_dotenv", + "type_dotenv", + "name_dotenv", + "emails.steve@hacker.com.active", + "emails.steve@hacker.com.created", } dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") - all := map[string]interface{}{"owner": map[string]interface{}{"organization": "MongoDB", "bio": "MongoDB Chief Developer Advocate & Hacker at Large", "dob": dob}, "title": "TOML Example", "ppu": 0.55, "eyes": "brown", "clothing": map[string]interface{}{"trousers": "denim", "jacket": "leather", "pants": map[string]interface{}{"size": "large"}}, "id": "0001", "batters": map[string]interface{}{"batter": []interface{}{map[string]interface{}{"type": "Regular"}, map[string]interface{}{"type": "Chocolate"}, map[string]interface{}{"type": "Blueberry"}, map[string]interface{}{"type": "Devil's Food"}}}, "hacker": true, "beard": true, "hobbies": []interface{}{"skateboarding", "snowboarding", "go"}, "age": 35, "type": "donut", "newkey": "remote", "name": "Cake", "p_id": "0001", "p_ppu": "0.55", "p_name": "Cake", "p_batters": map[string]interface{}{"batter": map[string]interface{}{"type": "Regular"}}, "p_type": "donut", "foos": []map[string]interface{}{map[string]interface{}{"foo": []map[string]interface{}{map[string]interface{}{"key": 1}, map[string]interface{}{"key": 2}, map[string]interface{}{"key": 3}, map[string]interface{}{"key": 4}}}}, "title_dotenv": "DotEnv Example", "type_dotenv": "donut", "name_dotenv": "Cake"} + all := map[string]interface{}{ + "owner": map[string]interface{}{ + "organization": "MongoDB", + "bio": "MongoDB Chief Developer Advocate & Hacker at Large", + "dob": dob, + }, + "title": "TOML Example", + "ppu": 0.55, + "emails": map[string]interface{}{ + "steve@hacker.com": map[string]interface{}{ + "active": true, + "created": "01/02/03", + }, + }, + "eyes": "brown", + "clothing": map[string]interface{}{ + "trousers": "denim", + "jacket": "leather", + "pants": map[string]interface{}{"size": "large"}, + }, + "id": "0001", + "batters": map[string]interface{}{ + "batter": []interface{}{ + map[string]interface{}{"type": "Regular"}, + map[string]interface{}{"type": "Chocolate"}, + map[string]interface{}{"type": "Blueberry"}, + map[string]interface{}{"type": "Devil's Food"}, + }, + }, + "hacker": true, + "beard": true, + "hobbies": []interface{}{ + "skateboarding", + "snowboarding", + "go", + }, + "age": 35, + "type": "donut", + "newkey": "remote", + "name": "Cake", + "p_id": "0001", + "p_ppu": "0.55", + "p_name": "Cake", + "p_batters": map[string]interface{}{ + "batter": map[string]interface{}{"type": "Regular"}, + }, + "p_type": "donut", + "foos": []map[string]interface{}{ + { + "foo": []map[string]interface{}{ + {"key": 1}, + {"key": 2}, + {"key": 3}, + {"key": 4}}, + }, + }, + "title_dotenv": "DotEnv Example", + "type_dotenv": "donut", + "name_dotenv": "Cake", + } allkeys := sort.StringSlice(AllKeys()) allkeys.Sort() @@ -960,7 +1063,7 @@ func TestDirsSearch(t *testing.T) { err = v.ReadInConfig() assert.Nil(t, err) - assert.Equal(t, `value is `+path.Base(v.configPaths[0]), v.GetString(`key`)) + assert.Equal(t, `value is `+filepath.Base(v.configPaths[0]), v.GetString(`key`)) } func TestWrongDirsSearchNotFound(t *testing.T) { @@ -1213,6 +1316,10 @@ clothing: pants: size: large trousers: denim +emails: + steve@hacker.com: + active: true + created: 01/02/03 eyes: brown hacker: true hobbies: From 583f79b3ea0db3590b23667b3130094ca14c1dac Mon Sep 17 00:00:00 2001 From: Lars Lehtonen Date: Wed, 18 Sep 2019 08:47:54 -0700 Subject: [PATCH 19/25] remote: fix two dropped errors --- remote/remote.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/remote/remote.go b/remote/remote.go index 810d070..02cf5a9 100644 --- a/remote/remote.go +++ b/remote/remote.go @@ -77,11 +77,12 @@ func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) { var err error if rp.SecretKeyring() != "" { - kr, err := os.Open(rp.SecretKeyring()) - defer kr.Close() + var kr *os.File + kr, err = os.Open(rp.SecretKeyring()) if err != nil { return nil, err } + defer kr.Close() if rp.Provider() == "etcd" { cm, err = crypt.NewEtcdConfigManager([]string{rp.Endpoint()}, kr) } else { From c171232d3abcdff8a584d1083fd5c11cfc5bf5c3 Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Fri, 27 Sep 2019 21:50:09 +0200 Subject: [PATCH 20/25] Add go version to the mod file --- go.mod | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.mod b/go.mod index a1ea058..b24a373 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,7 @@ module github.com/spf13/viper +go 1.12 + require ( github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect github.com/coreos/bbolt v1.3.2 // indirect From 371f39c3ab6ffb983f89c737291fe9ec8a5096c7 Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Fri, 27 Sep 2019 21:51:10 +0200 Subject: [PATCH 21/25] Add Go 1.13 to travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index d122f22..ed677bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ env: go: - 1.11.x - 1.12.x + - 1.13.x - tip os: From 398adc5a7da8f7fb1dda192beb2a9db63f797445 Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Fri, 27 Sep 2019 17:09:18 +0200 Subject: [PATCH 22/25] Add breaking test that requires reverting #673 --- viper_test.go | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/viper_test.go b/viper_test.go index 3c15ed2..5416bd6 100644 --- a/viper_test.go +++ b/viper_test.go @@ -254,7 +254,7 @@ func initDirs(t *testing.T) (string, string, func()) { } } -//stubs for PFlag Values +// stubs for PFlag Values type stringValue string func newStringValue(val string, p *string) *stringValue { @@ -864,7 +864,7 @@ func TestBindPFlag(t *testing.T) { assert.Equal(t, testString, Get("testvalue")) flag.Value.Set("testing_mutate") - flag.Changed = true //hack for pflag usage + flag.Changed = true // hack for pflag usage assert.Equal(t, "testing_mutate", Get("testvalue")) @@ -1856,6 +1856,23 @@ func TestWatchFile(t *testing.T) { } +func TestUnmarshal_DotSeparatorBackwardCompatibility(t *testing.T) { + flags := pflag.NewFlagSet("test", pflag.ContinueOnError) + flags.String("foo.bar", "cobra_flag", "") + + v := New() + assert.NoError(t, v.BindPFlags(flags)) + + config := &struct { + Foo struct { + Bar string + } + }{} + + assert.NoError(t, v.Unmarshal(config)) + assert.Equal(t, "cobra_flag", config.Foo.Bar) +} + func BenchmarkGetBool(b *testing.B) { key := "BenchmarkGetBool" v = New() From 71509d28871fd3eeda5f56fb097e65d318c578d6 Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Fri, 27 Sep 2019 17:18:11 +0200 Subject: [PATCH 23/25] Revert the effective changes of #673 --- viper.go | 20 +------------------- viper_test.go | 18 +----------------- 2 files changed, 2 insertions(+), 36 deletions(-) diff --git a/viper.go b/viper.go index 491799c..5133de7 100644 --- a/viper.go +++ b/viper.go @@ -1789,24 +1789,6 @@ outer: return shadow } -// Converts a fully qualified map key into a list of relative -// map keys, allowing for keys to contain the delimiter themselves -func keyComponents(v *Viper, key string) []string { - var result []string - components := strings.Split(key, v.keyDelim) - for index := 0; index < len(components); index++ { - potentialKey := strings.Join(components[0:index], v.keyDelim) - if v.Get(potentialKey) != nil { - result = append(result, potentialKey) - } - } - result = append(result, key) - for i := len(result) - 1; i > 0; i-- { - result[i] = strings.Replace(result[i], result[i-1]+v.keyDelim, "", 1) - } - return result -} - // AllSettings merges all settings and returns them as a map[string]interface{}. func AllSettings() map[string]interface{} { return v.AllSettings() } func (v *Viper) AllSettings() map[string]interface{} { @@ -1819,7 +1801,7 @@ func (v *Viper) AllSettings() map[string]interface{} { // check just in case anything changes continue } - path := keyComponents(v, k) + path := strings.Split(k, v.keyDelim) lastKey := strings.ToLower(path[len(path)-1]) deepestMap := deepSearch(m, path[0:len(path)-1]) // set innermost value diff --git a/viper_test.go b/viper_test.go index 5416bd6..f8364a0 100644 --- a/viper_test.go +++ b/viper_test.go @@ -46,10 +46,6 @@ clothing: age: 35 eyes : brown beard: true -emails: - steve@hacker.com: - created: 01/02/03 - active: true `) var yamlExampleWithExtras = []byte(`Existing: true @@ -535,8 +531,6 @@ func TestAllKeys(t *testing.T) { "title_dotenv", "type_dotenv", "name_dotenv", - "emails.steve@hacker.com.active", - "emails.steve@hacker.com.created", } dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") all := map[string]interface{}{ @@ -547,13 +541,7 @@ func TestAllKeys(t *testing.T) { }, "title": "TOML Example", "ppu": 0.55, - "emails": map[string]interface{}{ - "steve@hacker.com": map[string]interface{}{ - "active": true, - "created": "01/02/03", - }, - }, - "eyes": "brown", + "eyes": "brown", "clothing": map[string]interface{}{ "trousers": "denim", "jacket": "leather", @@ -1316,10 +1304,6 @@ clothing: pants: size: large trousers: denim -emails: - steve@hacker.com: - active: true - created: 01/02/03 eyes: brown hacker: true hobbies: From 40e41dd2240a2ae3f6b0a908ad9ddc6cefd5e456 Mon Sep 17 00:00:00 2001 From: Navid Shaikh Date: Wed, 16 Oct 2019 13:09:46 +0530 Subject: [PATCH 24/25] Updates YAML to v2.2.4 Make sure we're not vulnerable to CVE-2019-11253. See https://github.com/kubernetes/kubernetes/issues/83253 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b24a373..d7d702f 100644 --- a/go.mod +++ b/go.mod @@ -42,5 +42,5 @@ require ( golang.org/x/net v0.0.0-20190522155817-f3200d17e092 // indirect golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect google.golang.org/grpc v1.21.0 // indirect - gopkg.in/yaml.v2 v2.2.2 + gopkg.in/yaml.v2 v2.2.4 ) diff --git a/go.sum b/go.sum index 6c22453..f0c87ff 100644 --- a/go.sum +++ b/go.sum @@ -175,6 +175,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 72b022eb357a56469725dcd03918449e2278d02e Mon Sep 17 00:00:00 2001 From: Vivek V Date: Wed, 9 Oct 2019 19:06:40 +0530 Subject: [PATCH 25/25] Added capacity to slice creation --- viper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viper.go b/viper.go index 5133de7..e9010bc 100644 --- a/viper.go +++ b/viper.go @@ -1723,7 +1723,7 @@ func (v *Viper) AllKeys() []string { m = v.flattenAndMergeMap(m, v.defaults, "") // convert set of paths to list - a := []string{} + a := make([]string, 0, len(m)) for x := range m { a = append(a, x) }