Allow BindEnv to register multiple environment variables.

This change modifies BindEnv to permit a list of environment variable
names in order to support multiple env. vars. for the same config key.
When this form is used, env. keys take precedence in the written order.

Closes #971
This commit is contained in:
Gabriel Aszalos 2020-09-10 13:08:26 +03:00 committed by Márk Sági-Kazár
parent b534983313
commit b655224c01
2 changed files with 32 additions and 13 deletions

View File

@ -205,7 +205,7 @@ type Viper struct {
defaults map[string]interface{} defaults map[string]interface{}
kvstore map[string]interface{} kvstore map[string]interface{}
pflags map[string]FlagValue pflags map[string]FlagValue
env map[string]string env map[string][]string
aliases map[string]string aliases map[string]string
typeByDefValue bool typeByDefValue bool
@ -228,7 +228,7 @@ func New() *Viper {
v.defaults = make(map[string]interface{}) v.defaults = make(map[string]interface{})
v.kvstore = make(map[string]interface{}) v.kvstore = make(map[string]interface{})
v.pflags = make(map[string]FlagValue) v.pflags = make(map[string]FlagValue)
v.env = make(map[string]string) v.env = make(map[string][]string)
v.aliases = make(map[string]string) v.aliases = make(map[string]string)
v.typeByDefValue = false v.typeByDefValue = false
@ -1029,21 +1029,18 @@ func (v *Viper) BindFlagValue(key string, flag FlagValue) error {
func BindEnv(input ...string) error { return v.BindEnv(input...) } func BindEnv(input ...string) error { return v.BindEnv(input...) }
func (v *Viper) BindEnv(input ...string) error { func (v *Viper) BindEnv(input ...string) error {
var key, envkey string
if len(input) == 0 { if len(input) == 0 {
return fmt.Errorf("missing key to bind to") return fmt.Errorf("missing key to bind to")
} }
key = strings.ToLower(input[0]) key := strings.ToLower(input[0])
if len(input) == 1 { if len(input) == 1 {
envkey = v.mergeWithEnvPrefix(key) v.env[key] = append(v.env[key], v.mergeWithEnvPrefix(key))
} else { } else {
envkey = input[1] v.env[key] = append(v.env[key], input[1:]...)
} }
v.env[key] = envkey
return nil return nil
} }
@ -1122,12 +1119,14 @@ func (v *Viper) find(lcaseKey string, flagDefault bool) interface{} {
return nil return nil
} }
} }
envkey, exists := v.env[lcaseKey] envkeys, exists := v.env[lcaseKey]
if exists { if exists {
for _, envkey := range envkeys {
if val, ok := v.getEnv(envkey); ok { if val, ok := v.getEnv(envkey); ok {
return val return val
} }
} }
}
if nested && v.isPathShadowedInFlatMap(path, v.env) != "" { if nested && v.isPathShadowedInFlatMap(path, v.env) != "" {
return nil return nil
} }
@ -1711,6 +1710,14 @@ func castToMapStringInterface(
return tgt return tgt
} }
func castMapStringSliceToMapInterface(src map[string][]string) map[string]interface{} {
tgt := map[string]interface{}{}
for k, v := range src {
tgt[k] = v
}
return tgt
}
func castMapStringToMapInterface(src map[string]string) map[string]interface{} { func castMapStringToMapInterface(src map[string]string) map[string]interface{} {
tgt := map[string]interface{}{} tgt := map[string]interface{}{}
for k, v := range src { for k, v := range src {
@ -1883,7 +1890,7 @@ func (v *Viper) AllKeys() []string {
m = v.flattenAndMergeMap(m, castMapStringToMapInterface(v.aliases), "") m = v.flattenAndMergeMap(m, castMapStringToMapInterface(v.aliases), "")
m = v.flattenAndMergeMap(m, v.override, "") m = v.flattenAndMergeMap(m, v.override, "")
m = v.mergeFlatMap(m, castMapFlagToMapInterface(v.pflags)) m = v.mergeFlatMap(m, castMapFlagToMapInterface(v.pflags))
m = v.mergeFlatMap(m, castMapStringToMapInterface(v.env)) m = v.mergeFlatMap(m, castMapStringSliceToMapInterface(v.env))
m = v.flattenAndMergeMap(m, v.config, "") m = v.flattenAndMergeMap(m, v.config, "")
m = v.flattenAndMergeMap(m, v.kvstore, "") m = v.flattenAndMergeMap(m, v.kvstore, "")
m = v.flattenAndMergeMap(m, v.defaults, "") m = v.flattenAndMergeMap(m, v.defaults, "")

View File

@ -486,10 +486,11 @@ func TestEnv(t *testing.T) {
initJSON() initJSON()
BindEnv("id") BindEnv("id")
BindEnv("f", "FOOD") BindEnv("f", "FOOD", "OLD_FOOD")
os.Setenv("ID", "13") os.Setenv("ID", "13")
os.Setenv("FOOD", "apple") os.Setenv("FOOD", "apple")
os.Setenv("OLD_FOOD", "banana")
os.Setenv("NAME", "crunk") os.Setenv("NAME", "crunk")
assert.Equal(t, "13", Get("id")) assert.Equal(t, "13", Get("id"))
@ -501,6 +502,17 @@ func TestEnv(t *testing.T) {
assert.Equal(t, "crunk", Get("name")) assert.Equal(t, "crunk", Get("name"))
} }
func TestMultipleEnv(t *testing.T) {
initJSON()
BindEnv("f", "FOOD", "OLD_FOOD")
os.Unsetenv("FOOD")
os.Setenv("OLD_FOOD", "banana")
assert.Equal(t, "banana", Get("f"))
}
func TestEmptyEnv(t *testing.T) { func TestEmptyEnv(t *testing.T) {
initJSON() initJSON()