Almost all back-end services require some configuration items to configure our services. For some small projects, there are not many configurations. You can choose to pass the configuration only through command line parameters. However, there are many configurations for large projects. Passing through command line parameters becomes very troublesome and difficult to maintain. The standard solution is to save these configuration information in the configuration file and load and parse it when the program starts. Therefore, for a slightly larger system, configuration file loading and parsing is a necessity. There are many packages in the Go ecosystem that can load and parse configurations, and the most popular one is the Viper package.
Viper package introduction
ViperIt is a modern and complete solution for Go applications that can handle configuration files in different formats, allowing us to not worry about configuration file formats when building modern applications. Viper can also meet our various needs for application configuration. Viper has many features, some of which are the most important features as follows:
- Supports default configuration.
- Support from
json
、toml
、yaml
、yml
、properties
、props
、prop
、hcl
、dotenv
、env
Read data from a file in format. - Real-time monitoring and rereading of configuration files (optional).
- Supports reading configurations from environment variables.
- Supports reading and monitoring configuration changes from remote configuration systems (etcd or Consul).
- Read configuration from command line parameters.
- Supports reading configuration from buffers.
- You can explicitly set values for configuration items.
Viper can read configurations from different locations. Configurations at different locations have different priorities. High priority configurations will cover the same configuration with low priority. They are arranged from high to low according to priority:
- pass
Configuration of functional settings;
- Command line parameters;
- environment variables;
- Configuration file;
- key/value storage;
- default value.
Because of its powerful features, more and more excellent projects have begun to use Viper as their configuration parsing tools, such as: Hugo, Docker Notary, Clairctl, Mercure, etc.
It should be noted here that the Viper configuration key is case-sensitive.
How to use Viper
Viper has many functions, here are some commonly used and important functions to explain. In the process of using viper, the two most important functions are read configuration and read configuration. Viper provides different ways to read configuration and read configuration.
Read in the configuration and read the configuration into the viper. There are the following reading methods:
- The default configuration file name can be set.
- Read the configuration file.
- Listen and reread configuration files.
- from
Read configuration.
- Read from environment variables.
- Read from command line flags.
- Read from remote Key/Value storage.
Read configuration, read configuration from viper to application, viper provides the following functions to read configuration: Get(key string) interface{}
Get<Type>(key string) <Type>
AllSettings() map[string]interface{}
Read in configuration
Set default values
A good configuration system should support default values. viper supports setting default values for keys. Setting default values is usually useful when not setting keys through configuration files, environment variables, remote configurations, or command line flags, allowing programs to run normally even if they do not explicitly specify configurations. For example:
("ContentDir", "content") ("LayoutDir", "layouts") ("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})
Read configuration files
viper can read configuration files to parse configuration, and supportsjson
、toml
、yaml
、yml
、properties
、props
、prop
、hcl
、dotenv
、env
Format configuration file. Viper can search for multiple paths, but currently a single Viper instance only supports a single configuration file. Viper does not default to any configuration search paths, leaving default decisions to the application.
Here is an example of how to search and read configuration files using Viper:
package main import ( "fmt" "/spf13/pflag" "/spf13/viper" ) var ( cfg = ("config", "c", "", "Configuration file.") help = ("help", "h", false, "Show this help message.") ) func main() { () if *help { () return } // Read configuration from configuration file if *cfg != "" { (*cfg) // Specify the configuration file name ("yaml") // If there is no file extension in the configuration file name, you need to specify the format of the configuration file and tell viper to parse the file in which format } else { (".") // Add the current directory to the search path of the configuration file ("$HOME/.iam") // Profile search path, you can set multiple profile search paths ("config") // Configuration file name (no file extension) } if err := (); err != nil { // Read the configuration file. If a configuration file name is specified, the specified configuration file is used, otherwise search in the registered search path panic(("Fatal error config file: %s \n", err)) } ("Used configuration file is: %s\n", ()) }
When an error occurs when a configuration file is loading, you can handle specific situations where the configuration file cannot be found like this:
if err := (); err != nil { if _, ok := err.(); ok { // No error was found in the configuration file; if necessary, you can ignore it } else { // The configuration file was found, but another error occurred } } // The configuration file is found and successfully parsed
viper supports setting multiple configuration file search paths. Pay attention to the order in which search paths are added. viper will search for configuration files according to the order of added paths. If found, stop searching. If calledSetConfigFile
When the configuration file name is directly specified and the configuration file name does not have a file extension, you need to try to specify the format of the configuration file so that viper can correctly parse the configuration file.
If you search for configuration files, you need to pay attentionSetConfigName
The set configuration file name does not have an extension. When searching, viper will append the file extension after the file name and try to search for all supported extension types. For example, if we passSetConfigName
Set the configuration file nameconfig
, then viper will search in the registered search path:、
、
、
、
、
、
、
、
、
。
Write to configuration file
It is useful to read configuration files, but sometimes we may need to save the current configuration in the program for easier use or debugging. viper provides a series of functions that allow us to save the current configuration to the file. viper provides the following functions to save the configuration:
-
WriteConfig
: Save the current configuration into the configuration file currently used by viper. If the configuration file does not exist, an error will be reported. If the configuration file exists, the current configuration file will be overwritten. -
SafeWriteConfig
: Save the current configuration into the configuration file currently used by viper. If the configuration file does not exist, an error will be reported. If the configuration file exists, it will be returned.file exists
mistake. -
WriteConfigAs
: Save the current configuration into the specified file. Create a new one if the file does not exist, and overwrite the file if it exists. -
SafeWriteConfigAs
: Save the current configuration into the specified file. If the file does not exist, create a new one. If the file exists, returnfile exists
mistake.
According to experience, marked asSafe
All methods of do not overwrite any files, but are created directly (if not present), and the default behavior is to create or truncate.
A small example:
() () ("") ("")
Listen and reread configuration files
Viper supports allowing applications to read configuration files in real time at runtime, that is, hot load configurations. Can be passedWatchConfig
Function hot load configuration. In callingWatchConfig
Before function, make sure that the search path to the configuration file has been added. Optionally, a callback function can be provided for Viper to run every time a change occurs.
Example:
() (func(e ) { // Callback function that will be called after the configuration file is changed ("Config file changed:", ) })
Set configuration values
We can pass()
Functions to explicitly set configuration:
("", "colin")
Register and use alias
Alias allows multiple keys to refer to a single value. Example:
("loud", "Verbose") ("verbose", true) // The effect is equivalent to the following line of code("loud", true) // The effect is equivalent to the above line of code ("loud") // true ("verbose") // true
Use environment variables
viper also supports environment variables, and environment variables are supported through the following 5 functions:
AutomaticEnv()
BindEnv(input ...string) error
SetEnvPrefix(in string)
SetEnvKeyReplacer(r *)
AllowEmptyEnv(allowEmptyEnv bool)
Note here: viper reads environment variables case-sensitive. viper provides a mechanism to ensureENV
Variables are unique. By usingSetEnvPrefix
, you can tell Viper to use prefix when reading environment variables.BindEnv
andAutomaticEnv
All will use this prefix. For example, we set up("VIPER")
, when used("apiversion")
When , the actual environment variable read isVIPER_APIVERSION
。
BindEnv
One or two parameters are required. The first parameter is the key name, and the second is the name of the environment variable, which is case sensitive. If not providedENV
Variable name, then viper will assume that the ENV variable name is:Environment variable prefix_key name is fully capitalized
, for example: the prefix is VIPER and the key is username, then the ENV variable name is:VIPER_USERNAME
. When explicitly providedENV
When the variable name (second parameter), it will not automatically add prefix. For example, if the second parameter isid
, Viper will look for environment variablesID
。
In useENV
One important thing to note when a variable is that it will be read every time it is accessed. Viper is callingBindEnv
This value is not fixed when .
There is also a magic functionSetEnvKeyReplacer
,SetEnvKeyReplacer
Allow you to use Object to rewrite
Env
Key. If you want toGet()
Used in the call-
or.
, but I hope your environment variables are used_
The separator can be passedSetEnvKeyReplacer
To achieve it. For example, we set environment variablesUSER_SECRET_KEY=bVix2WBv0VPfrDrvlLWrhEdzjLpPCNYb
, but we want to use("-key")
, we call the function:
((".", "_", "-", "_"))
The above code is called()
When function, it will use_
Replace.
and-
. By default, an empty environment variable is considered unset and will be returned to the next configuration source. To treat an empty environment variable as set, you can useAllowEmptyEnv
method. Examples of using environment variables are as follows:
// Use environment variables("VIPER_USER_SECRET_ID", "QLdywI2MrmDVjSSv6e95weNRvmteRjfKAuNV") ("VIPER_USER_SECRET_KEY", "bVix2WBv0VPfrDrvlLWrhEdzjLpPCNYb") () // Read environment variables("VIPER") // Set the environment variable prefix: VIPER_, if it is viper, it will automatically convert to uppercase.((".", "_", "-", "_")) // Replace '.' and '-' in the (key) key string with '_'("-key") ("-id", "USER_SECRET_ID") //Bind the environment variable name to key
Use logo
viper supports pflag packages and can bind key to flag. andBindEnv
Similarly, this value is not set when calling a binding method. But it will be set when accessing it. For a single flag, you can callBindPFlag()
Make binding:
("token", ("token")) // Bind a single flag
You can also bind an existing set of pflags ():
() //Binding flag set
Read configuration
viper provides the following methods to read the configuration:
-
Get(key string) interface{}
; -
Get<Type>(key string) <Type>
; -
AllSettings() map[string]interface{}
; -
IsSet(key string) bool
。
EachGet
The method will return a zero value when it cannot find the value. To check whether a given key exists, you can useIsSet()
method.<Type>
It can be the capitalization of the type supported by viper:Bool
、Float64
、Int
、IntSlice
、String
、StringMap
、StringMapString
、StringSlice
、Time
、Duration
. For example:GetInt()
。
The specific usage of the read configuration is as follows:
Access nested keys
For example: Load the following JSON file:
{ "host": { "address": "localhost", "port": 5799 }, "datastore": { "metric": { "host": "127.0.0.1", "port": 3099 }, "warehouse": { "host": "198.0.0.1", "port": 2112 } } }
Viper can be passed in.
Delimited paths to access nested fields:
("") // (Return "127.0.0.1")
ifOverridden by direct assignment (by flag, environment variable,
set()
Methods, etc.), thenAll subkeys of will become undefined and they are overwritten by the high priority configuration level.
If there is a key that matches the separated key path, its value is returned directly. For example:
{ "": "0.0.0.0", "host": { "address": "localhost", "port": 5799 }, "datastore": { "metric": { "host": "127.0.0.1", "port": 3099 }, "warehouse": { "host": "198.0.0.1", "port": 2112 } } }
passGet the value:
("") // Return "0.0.0.0"
Extract subtree
For example: viper loads the following configuration:
app: cache1: max-items: 100 item-size: 64 cache2: max-items: 200 item-size: 80
Can be passedExtract subtrees:
subv := ("app.cache1")
subv
Now means:
max-items: 100 item-size: 64
Deserialization
viper can support parsing all or specific values to structures, maps, etc. It can be implemented through 2 functions:
Unmarshal(rawVal interface{}) error
UnmarshalKey(key string, rawVal interface{}) error
An example:
type config struct { Port int Name string PathMap string `mapstructure:"path_map"` } var C config err := (&C) if err != nil { ("unable to decode into struct, %v", err) }
If you want to parse those keys, they contain.
For configuration of (default key separator), you need to modify the separator:
v := (("::")) ("chart::values", map[string]interface{}{ "ingress": map[string]interface{}{ "annotations": map[string]interface{}{ "": "PathPrefix", "/ssl-redirect": "true", }, }, }) type config struct { Chart struct{ Values map[string]interface{} } } var C config (&C)
Viper is used in the background/mitchellh/mapstructure
To parse the value, it is used by defaultmapstructure tags
. When we need to desequence the configuration read by viper into the structure variables we define, we must use mapstructure tags.
Serialize to string
Sometimes we need to serialize all settings saved in viper into a string instead of writing them into a file, as shown below:
import ( yaml "/yaml.v2" // ... ) func yamlStringSettings() string { c := () bs, err := (c) if err != nil { ("unable to marshal config to YAML: %v", err) } return string(bs) }
Summarize
This article discusses the Viper packages used in the Go language for loading and parsing configuration files. Viper supports configuration files in multiple formats and provides many features to help developers manage configurations.
Key points include:
- Configuration file format: Viper supports JSON, TOML, YAML, HCL and INI format configuration files.
- Default value settings: Developers can set default values for configuration keys to be used when the key is not found in the configuration file.
- Read configuration files:Viper provides a variety of ways to read configuration files, including from file systems, environment variables, or command line parameters.
- Listen and reread configuration files: Viper can listen for changes to configuration files and automatically reload configurations.
- Sample code: The documentation provides some sample code to help developers better understand and use the Viper package.
This is the end of this article about how to read application configuration in Go project development. For more related Go to read application configuration content, please search for my previous article or continue browsing the related articles below. I hope everyone will support me in the future!