Consider a config of the following configuration structure
storage:
redis:
hostname: "localhost"
port: 6379
port: 3000
Also consider the following environment variables
STORAGE.REDIS.HOSTNAME=notlocalhost
PORT=3002
When using viper with the above inputs we get inconsistent results when using viper.Get("storage.redis.hostname") and when using viper.Get("storage") as a map[string]interface{}. Whilst the former returns the overridden environment variable notlocalhost, in the latter case the retrieved map ignores the overridden environment variables and pulls up the defaults retrieved from the config.
_Note: Viper version 1.3.2_
You might have run into https://stackoverflow.com/a/2821183
Environment variable names used by the utilities in the Shell [...] consist solely of uppercase letters, digits, and the '_' (underscore)
You could use viper.SetEnvKeyReplacer() to replace all dots to underscores
Hello, I think I have a similar issue, and the SO post does not cover it.
Here is a recreation of my problem, with code example :
I have a config.yaml with :
testkey:
# one: "hello"
two: 12345
and a main.go that looks like :
package main
import (
"fmt"
"github.com/spf13/viper"
)
func main() {
viper.SetConfigName("config")
viper.AddConfigPath("./")
viper.SetDefault("testkey.one", "bonjour")
viper.SetDefault("testkey.two", 9876)
err := viper.ReadInConfig()
if err != nil {
panic(err)
}
mapResult := viper.Get("testkey")
fmt.Println(mapResult)
one := viper.Get("testkey.one")
fmt.Println(one)
}
I expected it to print map[one:bonjour two:12345], as the key one is commented in the config file, and should therefore have its value set as the default given in the Go code, but I got map[two:12345] instead, where the one key is missing.
However, if I retrieve the value of viper.Get("testkey.one"), I correctly get the default valuebonjour`
To expand a bit on the issue here, I think it would be great if viper.Get'ting a parent, would also look for the default values of its children if necessary, as in the case above.
up
@akshaylb
I am not sure if it is a bug, or if actually, Viper does not break down the env var to create a tree.
Like you, I assumed that Viper would overwrite the value loaded from the YAML. But the env name becomes a simple key.
I worked around that by using a function to convert the env vars into JSON, and then loading it into Viper by using viper.MergeConfig:
func envToJson() ([]byte, error) {
list := map[string]interface{}{}
for _, e := range os.Environ() {
pair := strings.SplitN(e, "=", 2)
indexes := strings.Split(pair[0], "_")
mountMap(list, indexes, pair[1])
}
return json.MarshalIndent(list, "", " ")
}
func mountMap(m map[string]interface{}, i []string, v interface{}) {
if len(i) > 1 {
if _, ok := m[i[0]]; !ok {
m[i[0]] = map[string]interface{}{}
}
asMap, ok := m[i[0]].(map[string]interface{})
if !ok {
return // bad configuration
}
mountMap(asMap, i[1:], v)
v = asMap
}
m[i[0]] = v
}
envVarAsJson, _ := envToJson()
viper.MergeConfig(bytes.NewBuffer(envVarAsJson))
I only ran few and simple tests, work on common scenarions.
Most helpful comment
Hello, I think I have a similar issue, and the SO post does not cover it.
Here is a recreation of my problem, with code example :
I have a config.yaml with :
and a main.go that looks like :
I expected it to print
map[one:bonjour two:12345], as the keyoneis commented in the config file, and should therefore have its value set as the default given in the Go code, but I gotmap[two:12345]instead, where theonekey is missing.However, if I retrieve the value of
viper.Get("testkey.one"), I correctly get the default valuebonjour`To expand a bit on the issue here, I think it would be great if
viper.Get'ting a parent, would also look for the default values of its children if necessary, as in the case above.