Description
This is going to be an umbrella issue for other open issues for this problem.
These are:
- Unmarshal() does not use the env variable if the config file has no that key #1236
- Commented out property in YAML yields inconsistencies between Get and Unmarshal when overriding using environment variables #725
- Unmarshal using ENV without binding #688
- Does viper require a config file in order to use ENV variables? #584
- Can I tell
viper.Unmarshal
to take environment variables into account? #212 - Consider env vars when unmarshalling #188
- Unmarshal doesn't check environment variables with AutomaticEnv #140
Problem
Currently Viper cannot unmarshal automatically loaded environment variables.
Here is a failing test case reproducing the issue:
package viper
import (
"os"
"testing"
)
func TestUnmarshal_AutomaticEnv(t *testing.T) {
os.Setenv("MYKEY", "myvalue")
type Config struct {
MyKey string `mapstructure:"mykey"`
}
var c Config
AutomaticEnv()
if err := Unmarshal(&c); err != nil {
t.Fatal(err)
}
if c.MyKey != "myvalue" {
t.Error("failed to unmarshal automatically loaded environment variable")
}
os.Clearenv()
}
Root cause
Automatic environment variable loading works as follows:
- User requests a value from Viper:
viper.GetString("mykey")
- Viper runs the env key replacer on the key
- Viper compares the key (case insensitively) with the list of environment variables
- Returns the value if one is found
Unmarshal, however, doesn't work by requesting a key, quite the opposite: it tries to unmarshal existing keys onto the given structure. Since automatically loaded environment variables are not in the list of existing keys (unlike defaults and bind env vars), these variables won't be unmarshaled.
Workaround
- Use
BindEnv
to manually bind environment variables - Use
SetDefault
to set a default value (even an empty one), so Viper can find the key and try to find a matching env var
Possible solutions
The linked issues list a number of workarounds to fix this issue.
Personally, I'm in favor of the following:
- Unmarshal the given struct onto a
map[string]interface{}
. That will give use a nested structure of all the keys otherwise mapstructure expects. - Flatten the structure, so we get a list of expected keys
- Check that every key is defined. If not, check if there is a matching environment variable.
- Merge the available settings with the matching, automatically loaded environment variables.
- Use mapstructure as usual
Although I think that this is currently the best available solution, it doesn't mean we can't do better, so please do suggest if you have an idea.
Activity