-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Consider env vars when unmarshalling #188
Comments
I am having the same problem, it isn't fixed directly by the #195. You have to register defaults it seems before viper will load the values. I have been experimenting with some terrible reflection code to go set defaults from a given structure, but I'd like to keep reflection out where possible. Consider the following code: type Config struct {
CAFiles []string `mapstructure:"ca_files"
CertFile string `mapstructure:"cert_file"
KeyFile string `mapstructure:"key_file"
Servers []string `mapstructure:"servers"`
}
func configuredViperInstance() *viper.Viper {
v := viper.New()
v.SetConfigName("config")
v.AddConfigPath("./")
v.SetEnvPrefix("test")
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
v.AutomaticEnv()
if err := v.ReadInConfig(); err != nil && !os.IsNotExist(err) {
panic(err)
}
return v
}
func main() {
noDefaults := configuredViperInstance()
config := new(Config)
if err := noDefaults.Unmarshal(config); err != nil {
panic(err)
}
fmt.Printf("%+v\n", config)
fmt.Printf("config-cert_file: %s\n", config.CertFile)
fmt.Printf("cert_file: %s\n", noDefaults.GetString("cert_file"))
fmt.Println("-------------------------")
withDefaults := configuredViperInstance()
withDefaults.SetDefault("cert_file", "")
config = new(Config)
if err := withDefaults.Unmarshal(config); err != nil {
panic(err)
}
fmt.Printf("%+v\n", config)
fmt.Printf("config-cert_file: %s\n", config.CertFile)
fmt.Printf("cert_file: %s\n", noDefaults.GetString("cert_file"))
} I then call it passing in this json {
"servers": ["server-1", "server-2"],
"key_file": "key-file"
} TEST_CERT_FILE="env-certfile" go run nested_conf_testing.go I get the response &{CAFiles:[] CertFile: KeyFile:key-file Servers:[server-1 server-2]}
config-cert_file:
cert_file: env-certfile
-------------------------
&{CAFiles:[] CertFile:env-certfile KeyFile:key-file Servers:[server-1 server-2]}
config-cert_file: env-certfile
cert_file: env-certfile |
@rybit you need to do type Config struct {
BindPort int `mapstructure:"PORT" yaml:"port,omitempty"`
}
var c Config
// ...
viper.AutomaticEnv()
viper.BindEnv("PORT")
viper.SetDefault("PORT", 4444)
if err := viper.ReadInConfig(); err != nil {
// ...
}
if err := viper.Unmarshal(&c); err != nil {
// ...
} to get this working. |
If BindEnv or SetDefault is missing, it won't work with the latest beggining with PR #195 |
@rybit I don't think this is a bug, I'd say that this is the expected behavior: how should Viper know that a struct field has to be mapped to an environment variable, if you don't tell it to? If you ask for config value As for me, automatically adding defaults or environment binding with reflection is the way to go, if done carefully. You could then use structure tags to have the programmer define his own default value, which is very handy and flexible (I do it myself :-) ) @arekkas From what I remember, if |
I wonder if it's possible to make @arekkas' example working without explicitly doing Normally we use the following workflow in our applications:
Inside the application code we only deal with the config struct (to avoid dealing with arbitrary string keys in Viper). And we explicitly pass the config, it is not a global structure. Basically the default values of the config are defined in flags. So I would like to avoid explicitly doing |
Oh, it seems like doing So maybe adding some documentation about it can make this issue fixed. |
@burdiyan were you able to confirm that your solution with the SetEnvKeyReplacer works? I am unable to get it to work in my project. Maybe there is another thing I am missing? calling |
nvm I figured out you can fix it with this work around for _, key := range viper.AllKeys() {
val := viper.Get(key)
viper.Set(key, val)
} |
@from-nibly did you end up using viper.SetEnvPrefix("application")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv() Even with this setup the methods |
here is the full code I used to create a viper instance that I could use to func LoadConfig() *viper.Viper {
conf := viper.New()
conf.AutomaticEnv()
conf.SetEnvPrefix("ps")
conf.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
conf.SetConfigName("application")
conf.AddConfigPath("/etc/my-app")
err := conf.ReadInConfig()
if err != nil {
switch err.(type) {
default:
panic(fmt.Errorf("Fatal error loading config file: %s \n", err))
case viper.ConfigFileNotFoundError:
logger.Warning("No config file found. Using defaults and environment variables")
}
}
// workaround because viper does not treat env vars the same as other config
for _, key := range conf.AllKeys() {
val := conf.Get(key)
conf.Set(key, val)
}
return conf
} |
Odd. I do the same thing on the latest master branch and I still wind up with nothing being loaded into my struct. There's nowhere you actually define defaults? Or maybe your config file has empty values in it? When I try this |
oh yes sorry just looked at it again. I define each property as "empty string" in my config file (which gives me the list of property names in |
Edits: Syntax I've also run into this problem so I wrote a quickie (and perhaps dirty) recursive function which manually calls func BindEnvs(iface interface{}, parts ...string) {
ifv := reflect.ValueOf(iface)
ift := reflect.TypeOf(iface)
for i := 0; i < ift.NumField(); i++ {
v := ifv.Field(i)
t := ift.Field(i)
tv, ok := t.Tag.Lookup("mapstructure")
if !ok {
continue
}
switch v.Kind() {
case reflect.Struct:
BindEnvs(v.Interface(), append(parts, tv)...)
default:
viper.BindEnv(strings.Join(append(parts, tv), "."))
}
}
} Usage: // Config holds configuration
type Config struct {
Log Log `mapstructure:"log"`
DB DB `mapstructure:"mongo"`
Server Server `mapstructure:"server"`
}
// Log configuration
type Log struct {
Format string `mapstructure:"format"`
}
// DB configuration
type DB struct {
URL string `mapstructure:"url"`
Name string `mapstructure:"db"`
}
// Server holds HTTP server configuration
type Server struct {
Listen string `mapstructure:"listen"`
ShutdownTimeout time.Duration `mapstructure:"shutdown_timeout"`
}
// set up env key replacer and prefix if applicable
func init() {
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.SetEnvPrefix("MY_APP")
}
// Builds config - error handling omitted fore brevity
func config() Config {
c := Config{} // Populate with sane defaults
viper.ReadInConfig()
BindEnvs(c)
viper.Unmarshal(&c)
return c
}
// Get and use config
func main() {
c := config()
fmt.Println(c.DB.URL)
} I guess you could also do this in func init() {
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.SetEnvPrefix("MY_APP")
BindEnvs(Config{})
} |
@krak3n - thanks for the hack to get this to work. I was considering doing the same thing, and happy to have someone else work out the fiddly bits. For reference, mine defaults to use the lowercase name of the field instead of requiring the mapstructure tag (since almost all of mine are just straight strings):
|
all the options suggested above either required to set config file with empty values or bind each environment variables. These options are prone to errors. I tried following simple workaround based on comments from this issue. It does not require to bind env values or set defaults or set file with all config set to type Config struct {
Name string `yaml:"name"`
Port int `yaml:"port"`
}
func NewConfig() *Config {
return &Config{Port: 8080, Name: "foo"}
}
var cfgFile = "/tmp/overwrite.yaml" // contents: name: bar
var config *Config
func initConfigE() error {
v := viper.New()
// set default values in viper.
// Viper needs to know if a key exists in order to override it.
// https://github.com/spf13/viper/issues/188
b, err := yaml.Marshal(NewConfig())
if err != nil {
return err
}
defaultConfig := bytes.NewReader(b)
v.SetConfigType("yaml")
if err := v.MergeConfig(defaultConfig); err != nil {
return err
}
// overwrite values from config
v.SetConfigFile(cfgFile)
if err := v.MergeInConfig(); err != nil {
if _, ok := err.(viper.ConfigParseError); ok {
return err
}
// dont return error if file is missing. overwrite file is optional
}
// tell viper to overrwire env variables
v.AutomaticEnv()
v.SetEnvPrefix(envVarPrefix)
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
// refresh configuration with all merged values
config = &Config{}
return v.Unmarshal(&config)
} |
@supershal nice approach, nice way to avoid reflection. I'll see if this can work with |
Hello, any updates on this? |
Please see and follow the issue above. ☝️ |
I have the same issue but with flags. Unmarshal() works only with value from config file, it doesn't check flags. |
Hi Viper team, so I have set environment variables in my docker container and I am trying to use viper to get those values and then use unmarshall but it doesn't seem to work, does viper only works if I have a separate environment file? |
reading this I came to understanding that |
This is my solution: type Configurations struct {
Database DatabaseConfigs
}
type DatabaseConfigs struct {
User string
Pass string
Host string
Port int
Name string
}
var configs Configurations
func init() {
viper.SetConfigName("config")
viper.AddConfigPath("config")
viper.SetConfigType("yml")
if err := viper.ReadInConfig(); err != nil {
logger.Fatal("config: error reading config file: " + err.Error())
}
for _, key := range viper.AllKeys() {
envKey := strings.ToUpper(strings.ReplaceAll(key, ".", "_"))
err := viper.BindEnv(key, envKey)
if err != nil {
logger.Fatal("config: unable to bind env: " + err.Error())
}
}
if err := viper.Unmarshal(&configs); err != nil {
logger.Fatal("config: unable to decode into struct: " + err.Error())
}
}
|
Is this possible?
UPDATE: yes it is, see #188 (comment)
The text was updated successfully, but these errors were encountered: