Lookup environment variables instead of checking if the value is empty

This commit adds an `AllowEmptyEnv` option that, default off, that when set will allow set, but empty, environment variables

Fixes #317
This commit is contained in:
Márk Sági-Kazár 2018-11-06 22:53:21 +01:00 committed by Bjørn Erik Pedersen
parent 62edee3196
commit b7a3b95476
3 changed files with 53 additions and 7 deletions

View File

@ -179,13 +179,14 @@ viper.GetBool("verbose") // true
### Working with Environment Variables
Viper has full support for environment variables. This enables 12 factor
applications out of the box. There are four methods that exist to aid working
applications out of the box. There are five methods that exist to aid working
with ENV:
* `AutomaticEnv()`
* `BindEnv(string...) : error`
* `SetEnvPrefix(string)`
* `SetEnvKeyReplacer(string...) *strings.Replacer`
* `AllowEmptyEnvVar(bool)`
_When working with ENV variables, its important to recognize that Viper
treats ENV variables as case sensitive._
@ -217,6 +218,10 @@ keys to an extent. This is useful if you want to use `-` or something in your
`Get()` calls, but want your environmental variables to use `_` delimiters. An
example of using it can be found in `viper_test.go`.
By default empty environment variables are considered unset and will fall back to
the next configuration source. To treat empty environment variables as set, use
the `AllowEmptyEnv` method.
#### Env example
```go

View File

@ -187,6 +187,7 @@ type Viper struct {
automaticEnvApplied bool
envKeyReplacer *strings.Replacer
allowEmptyEnv bool
config map[string]interface{}
override map[string]interface{}
@ -373,6 +374,14 @@ func (v *Viper) mergeWithEnvPrefix(in string) string {
return strings.ToUpper(in)
}
// AllowEmptyEnv tells Viper to consider set,
// but empty environment variables as valid values instead of falling back.
// For backward compatibility reasons this is false by default.
func AllowEmptyEnv(allowEmptyEnv bool) { v.AllowEmptyEnv(allowEmptyEnv) }
func (v *Viper) AllowEmptyEnv(allowEmptyEnv bool) {
v.allowEmptyEnv = allowEmptyEnv
}
// TODO: should getEnv logic be moved into find(). Can generalize the use of
// rewriting keys many things, Ex: Get('someKey') -> some_key
// (camel case to snake case for JSON keys perhaps)
@ -380,11 +389,14 @@ func (v *Viper) mergeWithEnvPrefix(in string) string {
// getEnv is a wrapper around os.Getenv which replaces characters in the original
// key. This allows env vars which have different keys than the config object
// keys.
func (v *Viper) getEnv(key string) string {
func (v *Viper) getEnv(key string) (string, bool) {
if v.envKeyReplacer != nil {
key = v.envKeyReplacer.Replace(key)
}
return os.Getenv(key)
val, ok := os.LookupEnv(key)
return val, ok && (v.allowEmptyEnv || val != "")
}
// ConfigFileUsed returns the file used to populate the config registry.
@ -611,10 +623,9 @@ func (v *Viper) isPathShadowedInFlatMap(path []string, mi interface{}) string {
// "foo.bar.baz" in a lower-priority map
func (v *Viper) isPathShadowedInAutoEnv(path []string) string {
var parentKey string
var val string
for i := 1; i < len(path); i++ {
parentKey = strings.Join(path[0:i], v.keyDelim)
if val = v.getEnv(v.mergeWithEnvPrefix(parentKey)); val != "" {
if _, ok := v.getEnv(v.mergeWithEnvPrefix(parentKey)); ok {
return parentKey
}
}
@ -993,7 +1004,7 @@ func (v *Viper) find(lcaseKey string) interface{} {
if v.automaticEnvApplied {
// even if it hasn't been registered, if automaticEnv is used,
// check any Get request
if val = v.getEnv(v.mergeWithEnvPrefix(lcaseKey)); val != "" {
if val, ok := v.getEnv(v.mergeWithEnvPrefix(lcaseKey)); ok {
return val
}
if nested && v.isPathShadowedInAutoEnv(path) != "" {
@ -1002,7 +1013,7 @@ func (v *Viper) find(lcaseKey string) interface{} {
}
envkey, exists := v.env[lcaseKey]
if exists {
if val = v.getEnv(envkey); val != "" {
if val, ok := v.getEnv(envkey); ok {
return val
}
}

View File

@ -388,6 +388,36 @@ func TestEnv(t *testing.T) {
}
func TestEmptyEnv(t *testing.T) {
initJSON()
BindEnv("type") // Empty environment variable
BindEnv("name") // Bound, but not set environment variable
os.Clearenv()
os.Setenv("TYPE", "")
assert.Equal(t, "donut", Get("type"))
assert.Equal(t, "Cake", Get("name"))
}
func TestEmptyEnv_Allowed(t *testing.T) {
initJSON()
AllowEmptyEnv(true)
BindEnv("type") // Empty environment variable
BindEnv("name") // Bound, but not set environment variable
os.Clearenv()
os.Setenv("TYPE", "")
assert.Equal(t, "", Get("type"))
assert.Equal(t, "Cake", Get("name"))
}
func TestEnvPrefix(t *testing.T) {
initJSON()