Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Golang configuration and use of Viper

2025-01-15 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)05/31 Report--

In this article, the editor gives you a detailed introduction of "golang configuration and use of Viper", with detailed content, clear steps and proper handling of details. I hope this "golang configuration and use of Viper" article can help you solve your doubts.

Install go get github.com/spf13/viper. What is Viper?

Viper is a complete configuration solution for Go applications, including Twelve-Factor App. It is designed to work in applications and can handle all types of configuration requirements and formats. It supports the following features:

Set default value

Read configuration information from configuration files in JSON, TOML, YAML, HCL, envfile, and Java properties formats

Real-time monitoring and rereading of configuration files (optional)

Read from environment variables

Read and monitor configuration changes from a remote configuration system (etcd or Consul)

Read the configuration from the command line argument

Read configuration from buffer

Explicit configuration valu

Why choose Viper?

When building modern applications, you don't have to worry about the configuration file format; you want to focus on building great software. Viper is here to help you in this respect.

Viper can do the following for you:

Find, load, and deserialize configuration files in JSON, TOML, YAML, HCL, INI, envfile, and Java properties formats.

Provides a mechanism to set default values for your different configuration options.

Provides a mechanism to override the value of the specified option with command-line arguments.

Provides an alias system to easily rename parameters without breaking existing code.

When the user provides the same command line or configuration file as the default value, it is easy to tell the difference between them.

Viper will follow the following priorities. Each project takes precedence over the items below:

Display call Set setting value

Command line argument (flag)

Environment variable

Configuration file

Key/value storage

Default value

Important: the Key currently configured by Viper is case-insensitive. Whether to make this option optional is currently under discussion.

Save the value into Viper to establish the default value

A good configuration system should support default values. Default values are not required for keys, but they are useful if they are not set through a configuration file, environment variable, remote configuration, or command line flag (flag).

For example:

Viper.SetDefault ("ContentDir", "content") viper.SetDefault ("LayoutDir", "layouts") viper.SetDefault ("Taxonomies", map [string] string {"tag": "tags", "category": "categories"}) reads the configuration file

Viper needs to know at least where to find the configuration of the configuration file. Viper supports configuration files in JSON, TOML, YAML, HCL, envfile, and Java properties formats. Viper can search multiple paths, but currently a single Viper instance only supports a single profile. Viper does not default any configuration search paths, leaving the default decision to the application.

The following is an example of how to use Viper to search for and read configuration files. No specific path is required, but at least one path expected by the configuration file should be provided.

Viper.SetConfigFile (". / config.yaml") / / specify the profile path viper.SetConfigName ("config") / / profile name (no extension) viper.SetConfigType ("yaml") / / if the profile name does not have an extension Then you need to configure this viper.AddConfigPath ("/ etc/appname/") / / find the path where the configuration file is located viper.AddConfigPath ("$HOME/.appname") / / multiple calls to add multiple search paths viper.AddConfigPath (".") / / you can also find the configuration err: = viper.ReadInConfig () / / find and read the configuration file if err! = Nil {/ / handles the error panic reading the configuration file (fmt.Errorf ("Fatal error config file:% s\ n") Err)}

When there is an error loading the configuration file, you can handle specific situations where the configuration file cannot be found as follows:

If err: = viper.ReadInConfig (); err! = nil {if _, ok: = err. (viper.ConfigFileNotFoundError); ok {/ / configuration file was not found error; if necessary, ignore} else {/ / configuration file was found, but another error}} / / configuration file was found and parsed successfully

Note [from 1.6]: you can also have files without an extension and specify their format programmatically. There is no extension for the configuration file located in the user $HOME directory, such as .bashrc.

Here are two additional questions for readers to answer and verify by themselves.

When you read the configuration in the following way, viper will look for any configuration file with the file name config from the. / conf directory. If both. / conf/config.json and. / conf/config.yaml configuration files exist, which configuration file will viper load the configuration from?

Viper.SetConfigName ("config") viper.AddConfigPath (". / conf")

Can the desired effect be achieved by using viper.SetConfigType ("yaml") to specify the profile type under the above two statements?

Write to configuration file

It's useful to read the configuration file from the configuration file, but sometimes you want to store all the changes you make at run time. To do this, you can use the following set of commands, each for its own purpose:

WriteConfig-writes the current viper configuration to the predefined path and overwrites it, if any. If there is no predefined path, an error is reported.

SafeWriteConfig-writes the current viper configuration to a predefined path. If there is no predefined path, an error is reported. If it exists, the current profile will not be overwritten.

WriteConfigAs-writes the current viper configuration to the given file path. The given file will be overwritten (if it exists).

SafeWriteConfigAs-writes the current viper configuration to the given file path. The given file is not overwritten (if it exists).

As a rule of thumb, all methods marked as safe do not overwrite any files, but are created directly (if they do not exist), while the default behavior is to create or truncate.

A small example:

Viper.WriteConfig () / writes the current configuration to the predefined path of "viper.AddConfigPath ()" and "viper.SetConfigName" settings viper.SafeWriteConfig () viper.WriteConfigAs ("/ path/to/my/.config") viper.SafeWriteConfigAs ("/ path/to/my/.config") / / because the configuration file has been written, it will report an error viper.SafeWriteConfigAs ("/ path/to/my/.other_config") monitoring and rereading the configuration file

Viper supports the ability to read configuration files in real time at run time.

Gone are the days when you need to restart the server for the configuration to take effect, and viper-driven applications can read updates to the configuration file at run time without missing any messages.

Just tell the viper instance watchConfig. Optionally, you can provide a callback function for Viper to run each time a change occurs.

Make sure that all configuration paths are added before calling WatchConfig ().

Viper.WatchConfig () viper.OnConfigChange (callback function fmt.Println ("Config file changed:", e.Name)} that will be called after the func (e fsnotify.Event) {/ / configuration file changes) reads the configuration from io.Reader

Viper pre-defines many configuration sources, such as files, environment variables, flags, and remote Kmax V storage, but you are not bound by them. You can also implement the configuration source you need and provide it to viper.

Viper.SetConfigType ("yaml") / / or viper.SetConfigType ("YAML") / / any method that needs to add this configuration to the program. Var yamlExample = [] byte (`Hacker: truename: stevehobbies:- skateboarding- snowboarding- goclothing: jacket: leather trousers: denimage: 35eyes: brownbeard: true`) viper.ReadConfig (bytes.NewBuffer (yamlExample)) viper.Get ("name") / / you will get the "steve" override setting here

These may come from command-line flags or from your own application logic.

Viper.Set ("Verbose", true) viper.Set ("LogFile", LogFile) registers and uses aliases

Aliases allow multiple keys to reference a single value

Viper.RegisterAlias ("loud", "Verbose") / / register aliases (where loud and Verbose have established aliases) viper.Set ("verbose", true) / / the result is the same as the next line viper.Set ("loud", true) / / the result is the same as the previous line viper.GetBool ("loud") / / trueviper.GetBool ("verbose") / / true uses environment variables

Viper fully supports environment variables. This makes Twelve-Factor App available right out of the box. There are five ways to help collaborate with ENV:

AutomaticEnv ()

BindEnv (string...): error

SetEnvPrefix (string)

SetEnvKeyReplacer (string...) * strings.Replacer

AllowEmptyEnv (bool)

When using ENV variables, it is important to be aware that Viper treats ENV variables as case-sensitive.

Viper provides a mechanism to ensure that ENV variables are unique. By using SetEnvPrefix, you can tell Viper to use prefixes when reading environment variables. Both BindEnv and AutomaticEnv will use this prefix.

BindEnv uses one or two parameters. The first parameter is the key name, and the second is the name of the environment variable. The name of the environment variable is case-sensitive. If no ENV variable name is provided, Viper automatically assumes that the ENV variable matches the following format: prefix + "_" + key name in all uppercase. When you explicitly provide the ENV variable name (the second parameter), it does not automatically add a prefix. For example, if the second parameter is "id", Viper looks for the environment variable "ID".

One of the important things to note when using the ENV variable is that it will be read every time the value is accessed. Viper does not fix this value when calling BindEnv.

AutomaticEnv is a powerful assistant, especially when used in conjunction with SetEnvPrefix. When called, Viper checks the environment variables at any time when the viper.Get request is made. It will apply the following rules. It checks whether the name of the environment variable matches the key (if EnvPrefix is set).

SetEnvKeyReplacer allows you to override the Env key to some extent using the strings.Replacer object. This is useful if you want to use-or something else in the Get () call, but use the _ delimiter in the environment variable. Examples of its use can be found in viper_test.go.

Alternatively, you can use EnvKeyReplacer with the NewWithOptions factory function. Unlike SetEnvKeyReplacer, it accepts the StringReplacer interface, which allows you to write custom string replacement logic.

By default, empty environment variables are considered unset and will be returned to the next configuration source. To treat an empty environment variable as set, use the AllowEmptyEnv method.

Env example: SetEnvPrefix ("spf") / / will be automatically converted to uppercase BindEnv ("id") os.Setenv ("SPF_ID", "13") / / usually done outside the application id: = Get ("id") / / 13 using Flags

Viper has the ability to bind to flags. Specifically, Viper supports Pflag used in the Cobra library.

Similar to BindEnv, this value is not set when the bound method is called, but when the method is accessed. This means that you can bind as early as you need, even in the init () function.

For individual flags, the BindPFlag () method provides this functionality.

For example:

ServerCmd.Flags () .Int ("port", 1138, "Port to run Application server on") viper.BindPFlag ("port", serverCmd.Flags () .Lookup ("port")

You can also bind an existing set of pflags (pflag.FlagSet):

For example:

Pflag.Int ("flagname", 1234, "help message for flagname") pflag.Parse () viper.BindPFlags (pflag.CommandLine) I: = viper.GetInt ("flagname") / / retrieve values from viper instead of pflag

Using pflag in Viper does not prevent other packages from using the flag package from the standard library. The pflag package can handle the flags defined by the flag package by importing these flags. This is achieved by calling the convenience function AddGoFlagSet () provided by the pflag package.

For example:

Package mainimport ("flag"github.com/spf13/pflag") func main () {/ / use the standard library "flag" package flag.Int ("flagname", 1234) "help message for flagname") pflag.CommandLine.AddGoFlagSet (flag.CommandLine) pflag.Parse () viper.BindPFlags (pflag.CommandLine) I: = viper.GetInt ("flagname") / / retrieve values from viper.} flag interface

If you do not use Pflag,Viper provides two Go interfaces to bind other flag systems.

FlagValue represents a single flag. This is a very simple example of how to implement this interface:

Type myFlag struct {} func (f myFlag) HasChanged () bool {return false} func (f myFlag) Name () string {return "my-flag-name"} func (f myFlag) ValueString () string {return "my-flag-value"} func (f myFlag) ValueType () string {return "string"}

Once your flag implements this interface, you can easily tell Viper to bind it:

Viper.BindFlagValue ("my-flag-name", myFlag {})

FlagValueSet represents a set of flags. This is a very simple example of how to implement this interface:

Type myFlagSet struct {flags [] myFlag} func (f myFlagSet) VisitAll (fn func (FlagValue)) {for _, flag: = range flags {fn (flag)}}

Once your flag set implements this interface, you can easily tell Viper to bind it:

FSet: = myFlagSet {flags: [] myFlag {myFlag {}, myFlag {},} viper.BindFlagValues ("my-flags", fSet) remote Key/Value storage support

To enable remote support in Viper, you need to import the viper/remote package anonymously in your code.

Import _ "github.com/spf13/viper/remote"

Viper reads configuration strings (such as JSON, TOML, YAML, HCL, envfile, and Java properties formats) retrieved from paths in Key/Value storage, such as etcd or Consul. These values take precedence over the default values, but are overridden by configuration values retrieved from disk, flag, or environment variables. That is to say, the priority of Viper loading configuration values is: configuration file on disk > command line flag bit > environment variable > remote Key/Value storage > default value. )

Viper uses crypt to retrieve the configuration from the Kramp V store, which means that if you have the correct gpg key, you can encrypt the configuration value and automatically decrypt it. Encryption is optional.

You can use the remote configuration in combination with the local configuration, or you can use it independently.

Crypt has a command-line helper that you can use to put the configuration into the Khand V store. Crypt uses etcd in http://127.0.0.1:4001 by default.

$go get github.com/bketelsen/crypt/bin/crypt$ crypt set-plaintext / config/hugo.json / Users/hugo/settings/config.json

The confirmation value has been set:

$crypt get-plaintext / config/hugo.json

For examples of how to set encryption values or how to use Consul, see the crypt documentation.

Remote Key/Value storage example-unencrypted etcdviper.AddRemoteProvider ("etcd", "http://127.0.0.1:4001","/config/hugo.json")viper.SetConfigType("json") / because there is no file extension in the byte stream, so the type needs to be set here.) Supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" err: = viper.ReadRemoteConfig () Consul

You need to set a Key in the Consul Key/Value store to save the JSON value that contains the required configuration. For example, create a keyMY_CONSUL_KEY to store the following values in the Consul key/value store:

{"port": 8080, "hostname": "liwenzhou.com"} viper.AddRemoteProvider ("consul", "localhost:8500", "MY_CONSUL_KEY") viper.SetConfigType ("json") / / need to be set to jsonerr: = viper.ReadRemoteConfig () fmt.Println (viper.Get ("port")) / / 8080fmt.Println (viper.Get ("hostname")) / / liwenzhou.comFirestoreviper.AddRemoteProvider ("firestore", "google-cloud-project-id") "collection/document") viper.SetConfigType ("json") / / configuration format: "json", "toml", "yaml", "yml" err: = viper.ReadRemoteConfig ()

Of course, you can also use SecureRemoteProvider.

Remote Key/Value storage example-encrypt viper.AddSecureRemoteProvider ("etcd", "http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg")viper.SetConfigType("json") / because there is no file extension in the byte stream, so you need to set the type here.) Supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" err: = viper.ReadRemoteConfig () to monitor changes in etcd-unencrypted / / or you can create a new viper instance var runtime_viper = viper.New () runtime_viper.AddRemoteProvider ("etcd", "http://127.0.0.1:4001",") "/ config/hugo.yml") runtime_viper.SetConfigType ("yaml") / / because there is no file extension in the byte stream So you need to set the type here. "json", "toml", "yaml", "yml", "properties", "props", "prop", "env" are supported "dotenv" / / read configuration err from remote for the first time: = runtime_viper.ReadRemoteConfig () / / deserialize runtime_viper.Unmarshal (& runtime_conf) / / Open a separate goroutine that has been monitoring remote changes go func () {for {time.Sleep (time.Second * 5) / / delay after each request / / currently only tested Etcd supports err: = runtime_viper.WatchRemoteConfig () if err! = nil {log.Errorf ("unable to read remote config:% v") Err) continue} / / deserializes the new configuration into our runtime configuration structure. You can also use channel to implement a signal to notify the system of changes runtime_viper.Unmarshal (& runtime_conf)}} () to get the value from Viper

In Viper, there are several ways to get a value based on the type of value. The following functions and methods exist:

Get (key string): interface {}

GetBool (key string): bool

GetFloat64 (key string): float64

GetInt (key string): int

GetIntSlice (key string): [] int

GetString (key string): string

GetStringMap (key string): map [string] interface {}

GetStringMapString (key string): map [string] string

GetStringSlice (key string): [] string

GetTime (key string): time.Time

GetDuration (key string): time.Duration

IsSet (key string): bool

AllSettings (): map [string] interface {}

One important thing to realize is that every Get method returns zero when it cannot find a value. To check whether a given key exists, the IsSet () method is provided.

For example:

Viper.GetString ("logfile") / / case-insensitive settings and get if viper.GetBool ("verbose") {fmt.Println ("verbose enabled")} access to nested keys

The accessor method also accepts formatting paths for deeply nested keys. For example, if you 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. Separate paths to access nested fields:

GetString ("datastore.metric.host") / / (returns "127.0.0.1")

This follows the precedence rules established above; the search path traverses the rest of the configuration registry until it is found. (note: because Viper supports multiple configuration sources, such as configuration files on disk > command line flag bits > environment variables > remote Key/Value storage > default values, when looking for a configuration if it is not found in the current configuration source, we will continue to look from subsequent configuration sources until we find them.)

For example, in the case of this profile, both datastore.metric.host and datastore.metric.port are defined (and can be overwritten). It will also be found if datastore.metric.protocol,Viper is also defined in the default value.

However, if datastore.metric is overridden by a direct assignment (by flag, environment variables, set () method, etc.)... Then all subkeys of the datastore.metric will become undefined, and they will be "shadowed" by the high priority configuration level

Finally, if there is a key that matches the separated key path, its value is returned. For example:

{"datastore.metric.host": "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}} GetString ("datastore.metric.host") / / returns "0.0.0.0" extraction subtree

Extract the subtree from Viper.

For example, the viper instance now represents the following configuration:

App: cache1: max-items: 100 item-size: 64 cache2: max-items: 200 item-size: 80

After execution:

Subv: = viper.Sub ("app.cache1")

Subv now represents:

Max-items: 100item-size: 64

Suppose we now have a function like this:

Func NewCache (cfg * Viper) * Cache {...}

It creates a cache based on configuration information in subv format. You can now easily create the two caches separately, as follows:

Cfg1: = viper.Sub ("app.cache1") cache1: = NewCache (cfg1) cfg2: = viper.Sub ("app.cache2") cache2: = NewCache (cfg2) deserialization

You can also choose to resolve all or specific values to structures, map, and so on.

There are two ways to do this:

Unmarshal (rawVal interface {}): error

UnmarshalKey (key string, rawVal interface {}): error

For example:

Type config struct {Port int Name string PathMap string `mapstructure: "path_map" `} var C configerr: = viper.Unmarshal (& C) if err! = nil {t.Fatalf ("unable to decode into struct,% v", err)}

If you want to parse those keys, they themselves contain. (default key delimiter) configuration, you need to modify the delimiter:

V: = viper.NewWithOptions (viper.KeyDelimiter ("::") v.SetDefault ("chart::values", map [string] interface {} {"ingress": map [string] interface {} {"annotations": map [string] interface {} {"traefik.frontend.rule.type": "PathPrefix", "traefik.ingress.kubernetes.io/ssl-redirect": "true",},} }) type config struct {Chart struct {Values map [string] interface {}} var C configv.Unmarshal (& C)

Viper also supports parsing to embedded structures:

/ * Example config:module: enabled: true token: 89h5f98hbwf987h5f98wenf89ehf*/type config struct {Module struct {Enabled bool moduleConfig `mapstructure: ", squash" `}} / / moduleConfig could be in a module specific packagetype moduleConfig struct {Token string} var C configerr: = viper.Unmarshal (& C) if err! = nil {t.Fatalf ("unable to decode into struct,% v", err)}

Viper uses github.com/mitchellh/mapstructure to parse values in the background, and it uses mapstructuretag by default.

Note that when we need to reverse the configuration read by viper into our defined structure variables, be sure to use mapstructuretag!

Serialize into a string

You may need to serialize all the settings saved in viper into a string instead of writing them to a file. You can use the serializer in your preferred format with the configuration returned by AllSettings ().

Import (yaml "gopkg.in/yaml.v2" / /...) func yamlStringSettings () string {c: = viper.AllSettings () bs, err: = yaml.Marshal (c) if err! = nil {log.Fatalf ("unable to marshal config to YAML:% v", err)} return string (bs)} use a single or multiple Viper instance?

Viper is available out of the box. You don't need to configure or initialize to start using Viper. Because most applications want to use a single central repository to manage their configuration information, the viper package provides this functionality. It is similar to the singleton pattern.

In all of the above examples, they demonstrate how to use viper in their singleton style.

Using multiple viper instances

You can also create many different viper instances in your application. Each has its own unique set of configurations and values. Everyone can read data from different configuration files, key value stores, etc. Each can be read from different configuration files, key value stores, and so on. All the functions supported by the viper package are mirrored as methods of the viper instance.

For example:

X: = viper.New () y: = viper.New () x.SetDefault ("ContentDir", "content") y.SetDefault ("ContentDir", "foobar") / /.

When using multiple viper instances, different viper instances are managed by the user.

Use the Viper example

Suppose our project now has a. / conf/config.yaml configuration file that reads as follows:

Port: 8123version: "v1.2.3"

Next, the sample code demonstrates two ways to use viper to manage project configuration information in a project.

Use viper directly to manage configuration

Here we use a demo to demonstrate how to use viper in a web project built by the gin framework, use viper to load the information in the configuration file, and directly use the viper.GetXXX () method in the code to get the corresponding configuration values.

Package mainimport ("fmt"net/http"github.com/gin-gonic/gin"github.com/spf13/viper") func main () {viper.SetConfigFile ("config.yaml") / / specify the configuration file viper.AddConfigPath (". / conf/") / / specify the path to find the configuration file err: = viper.ReadInConfig () / / read configuration information if err! = nil {/ / failed to read configuration information panic (fmt.Errorf ("Fatal error config file:% s\ n" Err)} / / Monitoring profile changes viper.WatchConfig () r: = gin.Default () / / the return value of access / version will change with the configuration file changes r.GET ("/ version", func (c * gin.Context) {c.String (http.StatusOK) Viper.GetString ("version")}) if err: = r.Run (fmt.Sprintf (":% d", viper.GetInt ("port") Err! = nil {panic (err)}} use structure variables to save configuration information

In addition to the above usage, we can also define the structure corresponding to the configuration file in the project. After loading the configuration information, viper uses the structure variable to save the configuration information.

Package mainimport ("fmt"net/http"github.com/fsnotify/fsnotify"github.com/gin-gonic/gin"github.com/spf13/viper") type Config struct {Port int `mapstructure: "port" `Version string `mapstructure: "version" `} var Conf = new (Config) func main () {viper.SetConfigFile (". / conf/config.yaml ") / / specify the configuration file path err: = viper.ReadInConfig () / / read configuration information if err! = nil {/ / failed to read configuration information panic (" Fatal error config file:% s\ n ") Err)} / / Save the read configuration information to the global variable Conf if err: = viper.Unmarshal (Conf) Err! = nil {panic (fmt.Errorf ("unmarshal conf failed, err:%s\ n", err))} / / Monitoring profile changes viper.WatchConfig () / / Note! After the configuration file changes, it should be synchronized to the global variable Conf viper.OnConfigChange (func (in fsnotify.Event) {fmt.Println).) If err: = viper.Unmarshal (Conf) Err! = nil {panic (fmt.Errorf ("unmarshal conf failed, err:%s\ n", err)}) r: = gin.Default () / / access / version the return value will change with the configuration file r.GET ("/ version", func (c * gin.Context) {c.String (http.StatusOK) Conf.Version)}) if err: = r.Run (fmt.Sprintf (":% d", Conf.Port)) Err! = nil {panic (err)}} read here, this article "golang configuration and use of Viper" has been introduced. If you want to master the knowledge points of this article, you still need to practice and use it yourself. If you want to know more about related articles, welcome to follow the industry information channel.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report