Viper: How to set and fetch nested variable with environment variable?

Created on 20 Sep 2016  路  5Comments  路  Source: spf13/viper

Intention

Suppose I want to have a nested variable with the key foo.bar and I want viper to automatically take the value from an environment variable.

Problem

Since foo.bar or FOO.BAR is not a valid environment variable name due to the dot, I have no clue what environment variable I need to set.

To put the question differently: How does viper substitute the . in nested variables when mapped to environment variables?

Testable example snippet:

To show what I mean, here's an example code snippet:

package main_test

import (
    "fmt"
    "os"

    "github.com/spf13/viper"
)

func ExampleSimpleEnvVar() {
    os.Setenv("FOO", "bar")
    viper.AutomaticEnv()
    fmt.Printf("foo=%v", viper.Get("foo"))
    // Output: foo=bar
}

func ExampleNestedValue() {
    os.Setenv("FOO_BAR", "blabla")
    viper.SetDefault("foo.bar", "default")
    viper.AutomaticEnv()
    fmt.Printf("foo.bar=%v", viper.Get("foo.bar"))
    // Output: foo.bar=blabla
}

Example output

The ExampleSimpleEnvVar() is there to proof that my approach works at least for non-nested environment variables. But the ExampleNestedValue() function fails:

$ go test -v
=== RUN   ExampleSimpleEnvVar
--- PASS: ExampleSimpleEnvVar (0.00s)
=== RUN   ExampleNestedValue
--- FAIL: ExampleNestedValue (0.00s)
got:
foo.bar=default
want:
foo.bar=blabla
FAIL
exit status 1
FAIL    github.com/kwk/viper-test   0.005s

Details:

Viper version: a78f70b5b977efe08e313a9e2341c3f5457abdaf
Go version: go version go1.7 linux/amd64

Most helpful comment

Problem solved!

Using the viper.SetEnvKeyReplacer(replacer) worked for me:

package main_test

import (
    "fmt"
    "os"
    "strings"

    "github.com/spf13/viper"
)

func ExampleSimpleEnvVar() {
    os.Setenv("FOO", "bar")
    viper.AutomaticEnv()
    fmt.Printf("foo=%v", viper.Get("foo"))
    // Output: foo=bar
}

func ExampleNestedValue() {
    os.Setenv("FOO_BAR", "blabla")
    replacer := strings.NewReplacer(".", "_")
    viper.SetEnvKeyReplacer(replacer)
    viper.SetDefault("foo.bar", "default")
    viper.AutomaticEnv()
    fmt.Printf("foo.bar=%v", viper.Get("foo.bar"))
    // Output: foo.bar=blabla
}

Proof

$ go test -v
=== RUN   ExampleSimpleEnvVar
--- PASS: ExampleSimpleEnvVar (0.00s)
=== RUN   ExampleNestedValue
--- PASS: ExampleNestedValue (0.00s)
PASS
ok      github.com/kwk/viper-test   0.005s

All 5 comments

Okay, I could use

os.Setenv("FOO.BAR", "blabla")

but in the real world this implies that whatever system I'm using supports dots in the identifier of a variable.

For example, export doesn't work:

$ export FOO.BAR=42
bash: export: `FOO.BAR=42': not a valid identifier

With env I can set FOO.BAR but I cannot read it again.

$ env "FOO.BAR=42" echo "${FOO.BAR}"
bash: ${FOO.BAR}: bad substitution

Problem solved!

Using the viper.SetEnvKeyReplacer(replacer) worked for me:

package main_test

import (
    "fmt"
    "os"
    "strings"

    "github.com/spf13/viper"
)

func ExampleSimpleEnvVar() {
    os.Setenv("FOO", "bar")
    viper.AutomaticEnv()
    fmt.Printf("foo=%v", viper.Get("foo"))
    // Output: foo=bar
}

func ExampleNestedValue() {
    os.Setenv("FOO_BAR", "blabla")
    replacer := strings.NewReplacer(".", "_")
    viper.SetEnvKeyReplacer(replacer)
    viper.SetDefault("foo.bar", "default")
    viper.AutomaticEnv()
    fmt.Printf("foo.bar=%v", viper.Get("foo.bar"))
    // Output: foo.bar=blabla
}

Proof

$ go test -v
=== RUN   ExampleSimpleEnvVar
--- PASS: ExampleSimpleEnvVar (0.00s)
=== RUN   ExampleNestedValue
--- PASS: ExampleNestedValue (0.00s)
PASS
ok      github.com/kwk/viper-test   0.005s

Great!

I believe this does not work with viper.Sub("foo").GetString("bar")?
Not a big deal but would be nice to be able to pass sub config to other layer :-D
Does anyone know if it is possible to achieve?

@tinhtooaung It should work by calling SetEnvPrefix and AutomaticEnv on sub manually.

Was this page helpful?
0 / 5 - 0 ratings