#Managing Env vars with viper

14 messages · Page 1 of 1 (latest)

gritty epoch
#

I am trying to move all my configs for my web server to env vars, Problem is viper is not reading them. I get an error about Config File "config" Not Found in "[]"
Is there any way for me to read all my env vars which are present in .env files, without actually adding viper.SetConfigFile(".env") As the file will not be present in my containerised environments. Here is my config loader code

package server

import (
    "fmt"
    "os"
    "strings"

    "github.com/spf13/viper"
)

type Config struct {
    Environment string `mapstructure:"ENVIRONMENT"`
    BindAddress string `mapstructure:"BIND_ADDR"`
}

func LoadConfig() (config Config, err error) {
    viper.SetEnvPrefix("MAILER_")
    viper.AutomaticEnv()
    err = viper.ReadInConfig()
    if err != nil {
                // Added a panic for debug purpose
        panic("Error occurred while loading env vars")
    }
    viper.Unmarshal(&config)
    return
}
velvet mica
#

iirc you need to set the 'search path' by doing something like:

viper.AddConfigPath("/etc/appname/")   // path to look for the config file in
viper.AddConfigPath("$HOME/.appname")  // call multiple times to add many search paths
viper.AddConfigPath(".")               // optionally look for config in the working directory

I think in your case you'll only need the last option

gritty epoch
#

It doesn't work as well. My point is I don't want to read from any file and instead read from env vars only.

velvet mica
#

ah, sorry I misread. On a quick reading of the docs, I'm not sure you need to call viper.ReadInConfig when using viper.AutomaticEnv. Try not calling ReadInConfig

#

from the docs:

AutomaticEnv is a powerful helper especially when combined with SetEnvPrefix. When called, Viper will check for an environment variable any time a viper.Get request is made. It will apply the following rules. It will check for an environment variable with a name matching the key uppercased and prefixed with the EnvPrefix if set.
velvet mica
#

one thing tho is that unless viper is made aware of the variables existing it doesn't automatically check. The docs say to do something like viper.BindEnv("PORT") to make viper aware of it before calling unmarshall

#

this test example works for me:

package main

import (
    "fmt"
    "log"

    "github.com/spf13/viper"
)

type (
    Config struct {
        Name string `mapstructure:"NAME"`
        Host string `mapstructure:"HOST"`
    }
)

func main() {
    viper.SetEnvPrefix("APP")
    viper.AutomaticEnv()
    viper.BindEnv("NAME")
    viper.BindEnv("HOST")
    config := Config{}
    fmt.Println(viper.AllSettings())
    if err := viper.Unmarshal(&config); err != nil {
        log.Fatalln(err)
    }
    fmt.Printf("%#v\n", config)
}
gritty epoch
# velvet mica one thing tho is that unless viper is made aware of the variables existing it do...

Thanks for the help, I read the same in Viper's documentation but couldn't make it out. Nevertheless, I am disappointed that in 2024 viper still has to be notified about the existence of the env vars that need to be loaded, coming from a world of Python this is a big concern that I have to duplicate my code by binding the members of my struct line by line, libs like pydantic made it so so good.
Are you aware of any other such package that solves this issue?

velvet mica
#

in my experience viper is a bit too big and wide for it's own good. There are more specific packages which do just as good a job with less

#

ah, maybe not, it reads an env file...

gritty epoch
#

I don't want to use .env as it would require extra efforts in managing my CI and deployments.

velvet mica
#

if you haven't found a better way yet, this works for me:

package main

import (
    "fmt"
    "os"

    "github.com/golobby/env/v2"
    "github.com/k0kubun/pp"
)

func main() {
    config := Config{}
    err := env.Feed(&config)
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    pp.Println(config)
}

type Config struct {
    Debug bool `env:"DEBUG"` // Possible Values: "true", "false", "1", "0"
    App   struct {
        Name string `env:"APP_NAME"`
        Port int16  `env:"APP_PORT"`
    }
    Database struct {
        Name string `env:"DB_NAME"`
        Port int16  `env:"DB_PORT"`
        User string `env:"DB_USER"`
        Pass string `env:"DB_PASSWORD"`
    }
    IPs []string `env:"IPS"` // Possible Value: "192.168.0.1, 192.168.0.2"
    IDs []int32  `env:"IDS"` // Possible Value: "10, 11, 12"
}

Just thought it might help.

It seems that the golobby/dotenv library has a sibling called simply golobby/env

HTH