Viper Configuration Registry for Go

repository·master·Indexed 12 days ago

https://github.com/spf13/viper

A comprehensive configuration solution for Go applications supporting 12-factor app patterns. Viper manages settings from multiple sources including JSON, TOML, YAML, INI, environment variables, command line flags (pflag), and remote key/value stores like Etcd, Consul, Firestore, and NATS. It features dynamic updates via live watching, configuration precedence handling, and the ability to unmarshal settings into Go structs using mapstructure.

Tokens
12.4K
Snippets
60
Records
76
Agent score
84%

What's inside Viper

  1. What is Viper and why use it?

    master

    Viper is a complete configuration solution for Go applications, designed to support 12-Factor app patterns. It acts as a central registry for all application configuration needs.

    Key capabilities include:

    • Defaults & Overrides: Setting default values and explicit overrides.
    • File Management: Reading configuration files and dynamically discovering them across multiple locations.
    • Multiple Sources: Reading configuration from environment variables, command line flags, buffers, and remote systems (like Etcd or Consul).
    • Dynamic Updates: Live watching and updating configuration in real-time.
    • Key Management: Aliasing configuration keys to facilitate easy refactoring.
  2. Use multiple Viper instances

    master

    Viper supports multiple independent instances. Each instance maintains its own unique configuration and can read from different sources. All package-level functions are mirrored as methods on the *Viper instance.

    Best Practice: Instead of using the global singleton, initialize a viper.New() instance and pass it around your application. This makes testing easier and prevents unexpected behavior caused by shared state.

    x := viper.New()
    y := viper.New()
    
    x.SetDefault("ContentDir", "content")
    y.SetDefault("ContentDir", "foobar")
  3. Viper key case sensitivity

    master
    Viper keys are case-insensitive. This design choice ensures compatibility when merging configuration from various sources (like environment variables) that may use different casing conventions.
  4. Understand Viper configuration precedence

    master

    Viper merges multiple configuration sources into a single set of keys. When multiple sources provide the same key, Viper follows this specific order of precedence (highest to lowest):

    1. Explicit calls to Set
    2. Flags
    3. Environment variables
    4. Config files
    5. External key/value stores
    6. Defaults

    Note: Configuration keys are case-insensitive, except for environment variables which are case-sensitive.

  5. Concurrency safety in Viper

    master
    Viper is not safe for concurrent reads and writes. If you need to access a single Viper instance from multiple goroutines, you must implement your own synchronization (e.g., using sync.Mutex). Concurrent access can cause a panic.
  6. Use remote key/value stores

    master

    Viper supports remote configuration providers. To enable this, you must perform a blank import of the viper/remote package.

    Supported Providers:

    • Etcd / Etcd3
    • Consul
    • Firestore
    • NATS

    Key Features:

    • Encryption: Using the crypt library, you can store and automatically decrypt configuration values if a GPG keyring is present. Use AddSecureRemoteProvider for encrypted stores.
    • Watching: You can watch for changes in remote stores using WatchRemoteConfig() in a loop or goroutine.

    Note: When reading from a remote provider, you must call SetConfigType because the stream of bytes does not have a file extension.

    import _ "github.com/spf13/viper/remote"
    
    // Example: Consul
    viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY")
    viper.SetConfigType("json")
    err := viper.ReadRemoteConfig()
    
    // Example: Watching remote changes (Etcd)
    go func(){
    	for {
    		time.Sleep(time.Second * 5)
    		err := runtime_viper.WatchRemoteConfig()
    		if err != nil {
    			continue
    		}
    		runtime_viper.Unmarshal(&runtime_conf)
    	}
    }()
  7. Work with environment variables

    master

    Viper supports environment variables. Note that environment variables are case-sensitive, unlike other Viper sources.

    • SetEnvPrefix(prefix): Sets a prefix for all environment variables (e.g., spf_).
    • BindEnv(key): Binds a specific key to an environment variable. If a prefix is set, Viper looks for PREFIX_KEY (uppercased).
    • AutomaticEnv(): Tells Viper to automatically look for environment variables that match configuration keys.

    To handle the difference between kebab-case config keys and SCREAMING_SNAKE_CASE environment variables, use SetEnvKeyReplacer.

    // Tells Viper to use this prefix when reading environment variables
    viper.SetEnvPrefix("spf")
    
    // Viper will look for "SPF_ID", automatically uppercasing the prefix and key
    viper.BindEnv("id")
    
    // Alternatively, search for any environment variable prefixed and load them in
    viper.AutomaticEnv()
    
    os.Setenv("SPF_ID", "13")
    
    id := viper.Get("id") // 13
  8. Read configuration from files

    master

    Viper can search through multiple paths to find and read a configuration file. Supported formats include JSON, TOML, YAML, INI, envfile, and Java Properties. You specify the file name (without extension) and add search paths using AddConfigPath.

    // Name of the config file without an extension
    viper.SetConfigName("config")
    
    // Add search paths to find the file
    viper.AddConfigPath("/etc/appname/")
    viper.AddConfigPath("$HOME/.appname")
    viper.AddConfigPath(".")
    
    // Find and read the config file
    err := viper.ReadInConfig()
    if err != nil {
    	panic(fmt.Errorf("fatal error config file: %w", err))
    })
  9. Watch and automatically re-read configuration files

    master

    Viper can monitor configuration files for changes and execute a callback function whenever a change is detected. All config paths must be defined via AddConfigPath before calling WatchConfig().

    // All config paths must be defined prior to calling `WatchConfig()`
    viper.AddConfigPath("$HOME/.appname")
    
    viper.OnConfigChange(func(e fsnotify.Event) {
    	fmt.Println("Config file changed:", e.Name)
    })
    
    viper.WatchConfig()
  10. Restore support for HCL, Java properties, and INI (v1.20.x breaking change)

    master

    In v1.20.x, support for HCL, Java properties, and INI was removed from the Viper core to reduce dependencies. To use these formats, you must import them from github.com/go-viper/encoding and register them with a custom CodecRegistry during initialization.

    import (
        "github.com/go-viper/encoding/hcl"
        "github.com/go-viper/encoding/javaproperties"
        "github.com/go-viper/encoding/ini"
    )
    
    codecRegistry := viper.NewCodecRegistry()
    
    // Register HCL
    codecRegistry.RegisterCodec("hcl", hcl.Codec{})
    codecRegistry.RegisterCodec("tfvars", hcl.Codec{})
    
    // Register Java Properties
    codecRegistry.RegisterCodec("properties", &javaproperties.Codec{})
    codecRegistry.RegisterCodec("props", &javaproperties.Codec{})
    
    // Register INI
    codecRegistry.RegisterCodec("ini", ini.Codec{})
    
    v := viper.NewWithOptions(
        viper.WithCodecRegistry(codecRegistry),
    )
  11. Bind configuration to flags

    master

    Viper can bind to command-line flags, specifically supporting the pflag library (used by Cobra). Values are not set at the time of binding, but are retrieved from the flag when accessed via Viper.

    Using pflag

    You can bind individual flags with BindPFlag or an entire flag set with BindPFlags.

    Using standard library flag

    To use the standard flag package, you must first pass the flags to pflag using AddGoFlagSet.

    Custom Flag Implementations

    You can avoid pflag by implementing the FlagValue and FlagValueSet interfaces and using BindFlagValue or BindFlagValues.

    // Binding an individual pflag
    serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
    viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
    
    // Binding an entire pflag set
    pflag.Int("flagname", 1234, "help message for flagname")
    pflag.Parse()
    viper.BindPFlags(pflag.CommandLine)
    
    i := viper.GetInt("flagname")
    
    // Using standard library flag package via pflag
    flag.Int("flagname", 1234, "help message for flagname")
    pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
    pflag.Parse()
    viper.BindPFlags(pflag.CommandLine)