Go language programming viper configuration management tool

catalogue

Viper

Viper is a third-party library of Golang, Github: https://github.com/spf13/viper . It is used to process the configuration information of Golang program. It can process configuration files in various formats.

Viper supports the following features:

  • Set default values for configuration items.
  • Load and parse configuration files in JSON, TOML, YAML, HCL or Java properties format.
  • You can read the configuration in the command line parameters and specify the overwritten configuration value, which is often used with Cobra.
  • Configuration data can be read from environment variables.
  • Data can be read from the remote configuration system and monitored (e.g. ETCD, consult).
  • The configuration can be read from the Buffer.
  • You can easily distinguish the difference between the command line parameters or configuration files provided by the user and the default values.
  • You can monitor the changes of the configuration file and re read the configuration file.
  • An alias system is provided, which can rename parameters without destroying existing code.
  • Call function to set configuration information

The priority order of Viper reading configuration information, from high to low:

  1. Explicitly call the Set function.
  2. Command line arguments.
  3. Environment variables.
  4. Configuration file.
  5. Remote key/value storage system.
  6. Default value

In short, Viper allows developers to focus on and implement innovative business logic without worrying about the format of configuration files.

Note: the key of Viper configuration item is not case sensitive.

Use of Viper

Set default values

The default value is not required, but it is recommended. If Set function, command line parameters, environment variables, configuration files and remote configuration system are not specified, the default value will take effect.

Example:

viper.SetDefault("name", "xiaoming")
viper.SetDefault("age", "12")
viper.SetDefault("notifyList", []string{"xiaohong", "xiaoli", "xiaowang"})

Explicitly set key values

If a key passes viper If the set method sets a value, this value has the highest priority.

viper.Set("redis.port", 5381)

Read configuration from command line parameters

If a key does not pass viper If the set method explicitly sets the value, it will try to read from the command line parameters when getting. Viper uses the pflag library to parse command line options.

  • Command line
go run test.go --ip=192.168.7.3 --port=3306
  • code
package main

import (
	"fmt"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
)

func main() {
    // Define command line options
	pflag.String("ip", "127.0.0.1", "Server running address")
	pflag.Int64("port", 8080, "Server running port")
	// Parse command line options.
	pflag.Parse()

    // Bind options to viper.
	viper.BindPFlags(pflag.CommandLine)
	fmt.Printf("ip :%s , port:%s", viper.GetString("ip"), viper.GetString("port"))
}

Read configuration from environment variable

If the key value is not obtained in the previous methods, it will try to read from the environment variable.

In Golang, the os package is usually used to obtain environment variables, such as:

getenv := os.Getenv("JAVA_HOME")
fmt.Print(getenv)

Viper provides a unique way:

// Bind all environment variables.
viper.AutomaticEnv()

// Verify whether the binding is successful.
fmt.Println("GOPATH: ", viper.Get("GOPATH"))

// Read possible environment variables.
if env := viper.Get("JAVA_HOME"); env == nil {
    println("error!")
} else {
    fmt.Printf("%#v\n", env)
}

You can also bind the specified environment variables separately:

func init() {
  // Bind specific environment variables
  viper.BindEnv("redis.port")
  viper.BindEnv("go.path", "GOPATH")
}

func main() {
  fmt.Println("go path: ", viper.Get("go.path"))
}

BindEnv method. If only one parameter is passed in, this parameter represents both the key name and the environment variable name; If two parameters are passed in, the first parameter represents the key name and the second parameter represents the environment variable name.

You can also use viper The setenvprefix method sets the prefix of the environment variable. In this way, the environment variables bound by the AutomaticEnv method and BindEnv method,
When using the Get method, Viper will automatically add this prefix and look it up from the environment variable. If the corresponding environment variable does not exist, Viper will automatically convert all key names to uppercase and look it up again.

Read configuration from configuration file

Reading configuration from configuration file belongs to io Reader read configuration. From Io The form of reading configuration in reader is very flexible. The source can be a file, a string generated in the program, or even a byte stream that can be read from a network connection (remote configuration system).

Viper can specify to search configuration files from multiple paths and supports JSON, TOML, YAML, HCL and Java properties format files. However, at present, a single Viper instance only supports a single configuration file. By default, Viper does not search for any paths. Therefore, the path is not required, but it is recommended to provide at least one path if a configuration file is used.

Example:

viper.SetConfigName("dbConfig")            // Set the configuration file name without suffix.
viper.AddConfigPath("/workspace/path1/")   // First search path.
viper.AddConfigPath("/workspace/path2/")   // Multiple paths can be added and will be found in order.
viper.AddConfigPath(".")                   // . indicates the current directory.
err := viper.ReadInConfig()                // Search the path defined above and read the configuration data.

if err != nil {
    panic(fmt.Errorf("Fatal error config file: %s \n", err))
}

After reading the contents of the configuration file, Viper will automatically resolve according to the type of the configuration file, and then call viper The get method gets the key value of the configuration. Example: read configuration from YAML file.

  • YAML file
userName: "xiaoming"
address: "Guangzhou City XXX"
sex: 1
company:
  name: "xxx"
  employeeId: 1000
  department:
    - "Technology Department"
  • code
package main

import (
	"fmt"
	"github.com/spf13/viper"
)

type UserInfo struct {
	UserName string
	Address string
	Sex byte
	Company Company
}

type Company struct {
	Name string
	EmployeeId int
	Department []interface{}
}

func main() {
	// Building Viper instances
	v := viper.New()
	
	// Set profile name
	v.SetConfigName("userInfo")
	
	// Set profile path
	v.AddConfigPath("/root/go/src/webDemo/")
	
	// Set profile type
	v.SetConfigType("yaml")

	if err := v.ReadInConfig();err != nil {
		fmt.Printf("err:%s\n",err)
	}
	fmt.Printf("userName:%s sex:%s company.name:%s \n", v.Get("userName"), v.Get("sex"), v.Get("company.name"))

    // Deserialize to Struct type variable
	var userInfo UserInfo
	if err := v.Unmarshal(&userInfo) ; err != nil{
		fmt.Printf("err:%s",err)
	}
	fmt.Println(userInfo)
}

Note: generally, the information of the configuration file can be stored in two forms in the code:

  1. Directly resolve to a key/value Map type variable.
  2. Explicitly deserialize variables of type Struct.

Monitor the configuration file and reread the configuration data

Viper supports dynamic updating of configuration files, so that the application has the ability to read the configuration files at runtime. Therefore, the configuration can take effect without restarting the server. The WatchConfig() function of viper instance needs to be called. Viper uses fsnotify library to realize the function of monitoring file modification.

Example:

package main

import (
  "fmt"
  "log"
  "time"

  "github.com/spf13/viper"
)

func main() {
  viper.SetConfigName("config")
  viper.SetConfigType("toml")
  viper.AddConfigPath(".")
  err := viper.ReadInConfig()
  if err != nil {
    log.Fatal("read config failed: %v", err)
  }

  viper.WatchConfig()

  fmt.Println("redis port before sleep: ", viper.Get("redis.port"))
  time.Sleep(time.Second * 10)
  fmt.Println("redis port after sleep: ", viper.Get("redis.port"))
}

Just call viper Watchconfig, Viper will automatically listen for configuration changes. If there are modifications, reload the configuration.

In the above example, we first print redis Port, and then Sleep 10s. During this period, redis.com is modified in the configuration Port, and print again after Sleep. It is found that the modified value is printed out:

redis port before sleep:  7381
redis port after sleep:  73810

In addition, you can also specify a callback function for the action of configuration modification to obtain the change notification:

viper.OnConfigChange(func(e fsnotify.Event) {
    fmt.Printf("Config file:%s Op:%s\n", e.Name, e.Op)
})

This callback will be executed when the file is modified.

Read Key

After configuring various key sources, you can read the values of various key sources.

It is worth noting that the Get method provided by Viper returns a value of interface {} Type, which will be inconvenient to use. Therefore, Viper also provides GetType series methods to return the value of the specified Type. If the specified key does not exist or the Type is incorrect, the GetType method returns a zero value of the corresponding Type. The Type can be:

  • Bool
  • Float64
  • Int
  • String
  • Time
  • Duration
  • IntSlice
  • StringSlice

If you want to determine whether a key exists, you can use the IsSet method. In addition, GetStringMap and GetStringMapString methods can directly return all key value pairs under a key in Map. The former returns map[string]interface{}, and the latter returns map[string]string. The AllSettings method returns all settings with map[string]interface {}.

Examples

  • config.toml file
[server]
protocols = ["http", "https", "port"]
ports = [10000, 10001, 10002]
timeout = 3s
  • code
func main() {
  viper.SetConfigName("config")
  viper.SetConfigType("toml")
  viper.AddConfigPath(".")
  err := viper.ReadInConfig()
  if err != nil {
    log.Fatal("read config failed: %v", err)
  }

  fmt.Println("protocols: ", viper.GetStringSlice("server.protocols"))
  fmt.Println("ports: ", viper.GetIntSlice("server.ports"))
  fmt.Println("timeout: ", viper.GetDuration("server.timeout"))

  fmt.Println("mysql ip: ", viper.GetString("mysql.ip"))
  fmt.Println("mysql port: ", viper.GetInt("mysql.port"))

  if viper.IsSet("redis.port") {
    fmt.Println("redis.port is set")
  } else {
    fmt.Println("redis.port is not set")
  }

  fmt.Println("mysql settings: ", viper.GetStringMap("mysql"))
  fmt.Println("redis settings: ", viper.GetStringMap("redis"))
  fmt.Println("all settings: ", viper.AllSettings())
}
  • result
protocols:  [http https port]
ports:  [10000 10001 10002]
timeout:  3s
mysql ip:  127.0.0.1
mysql port:  3306
redis.port is set
mysql settings:  map[database:awesome ip:127.0.0.1 password:123456 port:3306 user:dj]
redis settings:  map[ip:127.0.0.1 port:7381]
all settings:  map[app_name:awesome web log_level:DEBUG mysql:map[database:awesome ip:127.0.0.1 password:123456 port:3306 user:dj] redis:map[ip:127.0.0.1 port:7381] server:map[ports:[10000 10001 10002] protocols:[http https port]]]

If redis. In the configuration file If port is commented out, redis.com is output port is not set.

Save configuration

When we want to save the configuration generated in the program or the changes made, we can use the interface provided by Viper:

  • WriteConfig: writes the current Viper configuration to a predefined path. If there is no predefined path, an error is returned. Otherwise, the current configuration will be overwritten.
  • SafeWriteConfig: the same function as above, but if the configuration file exists, it will not be overwritten;
  • WriteConfigAs: save the configuration to the specified path. If the file exists, overwrite it;
  • SafeWriteConfig: the same function as above, but if the configuration file exists, it will not be overwritten.

Example:

package main

import (
  "log"

  "github.com/spf13/viper"
)

func main() {
  viper.SetConfigName("config")
  viper.SetConfigType("toml")
  viper.AddConfigPath(".")

  viper.Set("app_name", "awesome web")
  viper.Set("log_level", "DEBUG")
  viper.Set("mysql.ip", "127.0.0.1")
  viper.Set("mysql.port", 3306)
  viper.Set("mysql.user", "root")
  viper.Set("mysql.password", "123456")
  viper.Set("mysql.database", "awesome")

  viper.Set("redis.ip", "127.0.0.1")
  viper.Set("redis.port", 6381)

  err := viper.SafeWriteConfig()
  if err != nil {
    log.Fatal("write config failed: ", err)
  }
}

The saved configuration file is as follows:

app_name = "awesome web"
log_level = "DEBUG"

[mysql]
  database = "awesome"
  ip = "127.0.0.1"
  password = "123456"
  port = 3306
  user = "root"

[redis]
  ip = "127.0.0.1"
  port = 6381

Reference documents

https://www.cnblogs.com/rickiyang/p/11074161.html

Tags: Go

Posted by tlenker09 on Mon, 23 May 2022 09:39:54 +0300