Configuration management in Go (using Viper)
January 14, 2017
Config
factor from The Twelve-Factor App states that application’s configuration should be
isolated from code to make configuring an app for different environments very convenient and scaling it up a breeze.
Configuration management for modern applications which run on so many different environment is becoming complex with advent of microservices and cloud computing. Not to mention dealing with several formats/markup languages to store app’s configurations.
This post is about one of my favorite Go libraries Viper which claims to be a complete configuration solution for Go applications. Among other features, it supports (as of this writing) reading configuration from
- JSON, TOML, YAML, HCL, and Java properties config files
- Environment variables
- Commandline flags
- Remote config systems (etcd or Consul)
Let’s see how to use Viper to take advantage of some of these features.
Install Viper
go get github.com/spf13/viper
Read from config file
While it supports various file formats, we’ll use an example JSON configuration below.
/config/env.json
{
"prod": {
"host": "192.168.1.1",
"port": "8081",
"enabled": true
},
"dev": {
"host": "192.168.1.2",
"port": "8082",
"enabled": true
},
"qa": {
"host": "192.168.1.3",
"port": "8083",
"enabled": true
}
}
Note: Viper does not require any initialization before using, unless we’ll be dealing multiple different configurations. check working with multiple vipers
Set config file we want to read
There are 2 ways to do this. Set path including config file name and extension
viper.SetConfigFile("./configs/env.json")
Or Set path(s) to loook for config files in.
// Add paths. Accepts multiple paths. It will search these paths in given order
viper.AddConfigPath("./configs")
viper.AddConfigPath("$HOME/configs")
// And then register config file name (no extension)
viper.SetConfigName("env")
// Optionally we can set specific config type
// viper.SetConfigType("json")
Read the config file in viper
// Searches for config file in given paths and read it
if err := viper.ReadInConfig(); err != nil {
log.Fatalf("Error reading config file, %s", err)
}
// Confirm which config file is used
fmt.Printf("Using config: %s\n", viper.ConfigFileUsed())
Get values from viper
Get method can retrieve any value given the key (case-insensitive) to use. Get returns an interface. For a specific value use one of the Get____ methods (e.g. GetInt, GetBool etc)
port := viper.Get("prod.port") // returns string
//port := viper.GetInt("prod.port") // returns integer
fmt.Printf("Value: %v, Type: %T\n", port, port)
Check if a particular key is set using IsSet
if !viper.IsSet("prod.port") {
log.Fatal("missing port number")
}
Notice that we can trverse nested configuration using a .
delimited path e.g. prod.port
Read values in struct
Instead of reading keys one by one we can extract sub-tree using Sub
and decode it in struct using Unmarshal
prod := viper.Sub("prod")
// Unmarshal into struct. Struct fields should match keys from config (case in-sensitive)
type config struct {
Host string
Port int
enabled bool
}
var C config
err := prod.Unmarshal(&C)
if err != nil {
log.Fatalf("unable to decode into struct, %v", err)
}
fmt.Println(C.Host)
Read from Env variables [TODO]
You can find full source of this example in github repo
This post gives just a glimpse of Viper’s capabilities. It’s worth exploring some of the interesting things it offers like,