Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UnmarshalKey / Sub only uses read config, neglects environment variables #1012

Closed
manuelstein opened this issue Nov 2, 2020 · 7 comments · Fixed by #1429
Closed

UnmarshalKey / Sub only uses read config, neglects environment variables #1012

manuelstein opened this issue Nov 2, 2020 · 7 comments · Fixed by #1429

Comments

@manuelstein
Copy link

manuelstein commented Nov 2, 2020

Description:
A structured configuration is read using ReadConfig or ReadInConfig. Subsection keys are bound to environment variables. Viper correctly updates its config, i.e. key access Get / AllSettings works correctly. But when trying to use Unmarshal, only the config file seems to be used.

Code:

package main

import (
	"bytes"
	"fmt"
	"os"

	"github.com/spf13/viper"
)

func main() {
	v := viper.New()

	type Foo struct {
		Bar string `mapstructure:"bar"`
	}
	foo := Foo{
		Bar: "A",
	}
	var yamlExample = []byte(`
foo:
  bar: "B"
`)
	// Read config
	fmt.Println("Reading config")
	v.SetConfigType("yaml")
	v.ReadConfig(bytes.NewBuffer(yamlExample))
	fmt.Printf("%#v\n\n", v.AllSettings())

	// BindEnv
	fmt.Println("Setting env")
	os.Setenv("FOO_BAR", "C")
	v.BindEnv("foo.bar", "FOO_BAR")
	fmt.Printf("%#v\n\n", v.AllSettings())

	fmt.Println("Unmarshal")
	if err := v.Sub("foo").Unmarshal(&foo); err != nil {
		panic(err)
	}
	fmt.Printf("%#v\n\n", foo)
}

Output:

Reading config
map[string]interface {}{"foo":map[string]interface {}{"bar":"B"}}

Setting env
map[string]interface {}{"foo":map[string]interface {}{"bar":"C"}}

Unmarshal
main.Foo{Bar:"B"}

Expected Output:

...
Unmarshal
main.Foo{Bar:"C"}
@github-actions
Copy link

github-actions bot commented Nov 2, 2020

👋 Thanks for reporting!

A maintainer will take a look at your issue shortly. 👀

In the meantime: We are working on Viper v2 and we would love to hear your thoughts about what you like or don't like about Viper, so we can improve or fix those issues.

⏰ If you have a couple minutes, please take some time and share your thoughts: https://forms.gle/R6faU74qPRPAzchZ9

📣 If you've already given us your feedback, you can still help by spreading the news,
either by sharing the above link or telling people about this on Twitter:

https://twitter.com/sagikazarmark/status/1306904078967074816

Thank you! ❤️

@KalleDK
Copy link

KalleDK commented Jan 11, 2021

Ran into the same issue

@KalleDK
Copy link

KalleDK commented Jan 11, 2021

The error seems to come from the Get() method. If I do the same but change Sub to Get, I also "miss" the env values. But only in "nested" values. Get only works if you give the full path, but then it works correctly!

@KalleDK
Copy link

KalleDK commented Jan 11, 2021

Example expanded with Get also

code

package main

import (
	"bytes"
	"fmt"
	"os"

	"github.com/spf13/viper"
)

func main() {
	v := viper.New()

	type Bar struct {
		Val string `mapstructure:"val"`
	}
	type Foo struct {
		Bar Bar `mapstructure:"bar"`
	}
	foo := Foo{
		Bar: Bar{
			Val: "A",
		},
	}
	var yamlExample = []byte(`
foo:
  bar:
    val: "B"
`)
	// Read config
	fmt.Println("Reading config")
	v.SetConfigType("yaml")
	v.ReadConfig(bytes.NewBuffer(yamlExample))
	fmt.Printf("%#v\n\n", v.AllSettings())

	// BindEnv
	fmt.Println("Setting env")
	os.Setenv("FOO_BAR_VAL", "C")
	v.BindEnv("foo.bar.val", "FOO_BAR_VAL")
	fmt.Printf("%#v\n\n", v.AllSettings())

	fmt.Println("Get")
	val := v.Get("foo")
	if val == nil {
		panic("missing key")
	}
	fmt.Printf("%#v\n\n", val)

	fmt.Println("Unmarshal")
	if err := v.Sub("foo").Unmarshal(&foo); err != nil {
		panic(err)
	}
	fmt.Printf("%#v\n\n", foo)
}

Output:

Reading config
map[string]interface {}{"foo":map[string]interface {}{"bar":map[string]interface {}{"val":"B"}}}

Setting env
map[string]interface {}{"foo":map[string]interface {}{"bar":map[string]interface {}{"val":"C"}}}

Get
map[string]interface {}{"bar":map[string]interface {}{"val":"B"}}

Unmarshal
main.Foo{Bar:main.Bar{Val:"B"}}

Expected:

Reading config
map[string]interface {}{"foo":map[string]interface {}{"bar":map[string]interface {}{"val":"B"}}}

Setting env
map[string]interface {}{"foo":map[string]interface {}{"bar":map[string]interface {}{"val":"C"}}}

Get
map[string]interface {}{"bar":map[string]interface {}{"val":"C"}}

Unmarshal
main.Foo{Bar:main.Bar{Val:"C"}}

@KalleDK
Copy link

KalleDK commented Jan 11, 2021

Simple workaround until fixed (mileage may vary)

func Sub(v *viper.Viper, name string) *viper.Viper {
	r := viper.New()
	for _, key := range v.AllKeys() {
		if strings.Index(key, name+".") == 0 {
			r.Set(key[len(name)+1:], v.Get(key))
		}
	}
	return r
}

@pcting
Copy link

pcting commented Mar 1, 2021

i encountered this bug today. for me it was a bit difficult to track down was i am currently merging multiple yaml files together; i thought the issue was in that part of the code.

@dizys
Copy link

dizys commented May 27, 2021

Same here. Unmarshal isn't picking up on the environment variables.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants