diff --git a/README.md b/README.md index 221d3b2..1acd85c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -> ## Viper v2 feedback +> ## Viper v2 Feedback > Viper is heading towards v2 and we would love to hear what _**you**_ would like to see in it. Share your thoughts here: https://forms.gle/R6faU74qPRPAzchZ9 > > **Thank you!** @@ -40,95 +40,86 @@ Many Go projects are built using Viper including: go get github.com/spf13/viper ``` -**Note:** Viper uses [Go Modules](https://go.dev/wiki/Modules) to manage dependencies. +> **NOTE** Viper uses [Go Modules](https://go.dev/wiki/Modules) to manage dependencies. -## What is Viper? +## Why use Viper? -Viper is a complete configuration solution for Go applications including [12-Factor apps](https://12factor.net/#the_twelve_factors). -It is designed to work within an application, and can handle all types of configuration needs +Viper is a complete configuration solution for Go applications including +[12-Factor apps](https://12factor.net/#the_twelve_factors). It is designed to +work within any application, and can handle all types of configuration needs and formats. It supports: * setting defaults -* reading from JSON, TOML, YAML, HCL, envfile and Java properties config files -* live watching and re-reading of config files (optional) -* reading from environment variables -* reading from remote config systems (etcd or Consul), and watching changes -* reading from command line flags -* reading from buffer * setting explicit values +* reading config files +* dynamic discovery of config files across multiple locations +* reading from environment variables +* reading from remote systems (e.g. Etcd or Consul) +* reading from command line flags +* reading from buffers +* live watching and updating configuration +* aliasing configuration keys for easy refactoring -Viper can be thought of as a registry for all of your applications configuration needs. +Viper can be thought of as a registry for all of your applications' +configuration needs. -## Why Viper? +## Putting Values in Viper -When building a modern application, you don’t want to worry about -configuration file formats; you want to focus on building awesome software. -Viper is here to help with that. +Viper can read from multiple configuration sources and merges them together +into one set of configuration keys and values. -Viper does the following for you: - -1. Find, load, and unmarshal a configuration file in JSON, TOML, YAML, HCL, INI, envfile or Java properties formats. -2. Provide a mechanism to set default values for your different configuration options. -3. Provide a mechanism to set override values for options specified through command line flags. -4. Provide an alias system to easily rename parameters without breaking existing code. -5. Make it easy to tell the difference between when a user has provided a command line or config file which is the same as the default. - -Viper uses the following precedence order. Each item takes precedence over the item below it: +Viper uses the following precedence for merging: * explicit call to `Set` - * flag - * env - * config - * key/value store - * default + * flags + * environment variables + * config files + * external key/value stores + * defaults -**Important:** Viper configuration keys are case insensitive. -There are ongoing discussions about making that optional. - - -## Putting Values into Viper - -### Establishing Defaults - -A good configuration system will support default values. A default value is not -required for a key, but it’s useful in the event that a key hasn't been set via -config file, environment variable, remote configuration or flag. - -Examples: - -```go -viper.SetDefault("ContentDir", "content") -viper.SetDefault("LayoutDir", "layouts") -viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"}) -``` +> **NOTE** Viper configuration keys are case insensitive. ### Reading Config Files -Viper requires minimal configuration so it knows where to look for config files. -Viper supports JSON, TOML, YAML, HCL, INI, envfile and Java Properties files. Viper can search multiple paths, but -currently a single Viper instance only supports a single configuration file. -Viper does not default to any configuration search paths leaving defaults decision -to an application. +Viper requires minimal configuration to load config files. Viper currently supports: -Here is an example of how to use Viper to search for and read a configuration file. -None of the specific paths are required, but at least one path should be provided -where a configuration file is expected. +* JSON +* TOML +* YAML +* HCL +* INI +* envfile +* Java Propeties + +A single Viper instance only supports a single configuration file, but multiple +paths may be searched for one. + +Here is an example of how to use Viper to search for and read a configuration +file. At least one path should be provided where a configuration file is +expected. ```go -viper.SetConfigName("config") // name of config file (without extension) -viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name -viper.AddConfigPath("/etc/appname/") // path to look for the config file in -viper.AddConfigPath("$HOME/.appname") // call multiple times to add many search paths -viper.AddConfigPath(".") // optionally look for config in the working directory -err := viper.ReadInConfig() // Find and read the config file -if err != nil { // Handle errors reading the config file +// Name of the config file without an extension (Viper will intuit the type +// from an extension on the actual file) +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() + +// Handle errors +if err != nil { panic(fmt.Errorf("fatal error config file: %w", err)) } ``` -You can handle the specific case where no config file is found like this: +You can handle the specific case where no config file is found. ```go var fileLookupError viper.FileLookupError @@ -145,88 +136,95 @@ if err := viper.ReadInConfig(); err != nil { // Config file found and successfully parsed ``` -*NOTE [since 1.6]:* You can also have a file without an extension and specify the format programmatically. For those configuration files that lie in the home of the user without any extension like `.bashrc` +> **NOTE (since 1.6)** You can also have a file without an extension and +> specify the format programmatically, which is useful for files that naturally +> have no extension (e.g., `.bashrc`). ### Writing Config Files -Reading from config files is useful, but at times you want to store all modifications made at run time. -For that, a bunch of commands are available, each with its own purpose: - -* WriteConfig - writes the current viper configuration to the predefined path, if exists. Errors if no predefined path. Will overwrite the current config file, if it exists. -* SafeWriteConfig - writes the current viper configuration to the predefined path. Errors if no predefined path. Will not overwrite the current config file, if it exists. -* WriteConfigAs - writes the current viper configuration to the given filepath. Will overwrite the given file, if it exists. -* SafeWriteConfigAs - writes the current viper configuration to the given filepath. Will not overwrite the given file, if it exists. - -As a rule of the thumb, everything marked with safe won't overwrite any file, but just create if not existent, whilst the default behavior is to create or truncate. - -A small examples section: +At times you may want to store all configuration modifications made during run +time. ```go -viper.WriteConfig() // writes current config to predefined path set by 'viper.AddConfigPath()' and 'viper.SetConfigName' -viper.SafeWriteConfig() +// Writes current config to the path set by `AddConfigPath` and `SetConfigName` +viper.WriteConfig() +viper.SafeWriteConfig() // Like the above, but will error if the config file exists + +// Writes current config to a specific place viper.WriteConfigAs("/path/to/my/.config") -viper.SafeWriteConfigAs("/path/to/my/.config") // will error since it has already been written + +// Will error since it has already been written +viper.SafeWriteConfigAs("/path/to/my/.config") + viper.SafeWriteConfigAs("/path/to/my/.other_config") ``` -### Watching and re-reading config files +As a rule of the thumb, methods prefixed with `Safe` won't overwrite any +existing file, while other methods will. -Viper supports the ability to have your application live read a config file while running. +### Watching and Re-reading Config Files -Gone are the days of needing to restart a server to have a config take effect, -viper powered applications can read an update to a config file while running and -not miss a beat. +Gone are the days of needing to restart a server to have a config take +effect--Viper powered applications can read an update to a config file while +running and not miss a beat. -Simply tell the viper instance to watchConfig. -Optionally you can provide a function for Viper to run each time a change occurs. - -**Make sure you add all of the configPaths prior to calling `WatchConfig()`** +It's also possible to provide a function for Viper to run each time a change +occurs. ```go +// 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() ``` -### Reading Config from io.Reader +### Reading Config from `io.Reader` -Viper predefines many configuration sources such as files, environment -variables, flags, and remote K/V store, but you are not bound to them. You can -also implement your own required configuration source and feed it to viper. +Viper predefines many configuration sources but you can also implement your own +required configuration source. ```go -viper.SetConfigType("yaml") // or viper.SetConfigType("YAML") +viper.SetConfigType("yaml") -// any approach to require this configuration into your program. var yamlExample = []byte(` -Hacker: true -name: steve +hacker: true hobbies: - skateboarding - snowboarding - go -clothing: - jacket: leather - trousers: denim -age: 35 -eyes : brown -beard: true +name: steve `) viper.ReadConfig(bytes.NewBuffer(yamlExample)) -viper.Get("name") // this would be "steve" +viper.Get("name") // "steve" +``` + +### Setting Defaults + +A good configuration system will support default values, which are used if a +key hasn't been set in some other way. + +Examples: + +```go +viper.SetDefault("ContentDir", "content") +viper.SetDefault("LayoutDir", "layouts") +viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"}) ``` ### Setting Overrides -These could be from a command line flag, or from your own application logic. +Viper allows explict setting of configuration, such as from your own +application logic. ```go -viper.Set("Verbose", true) -viper.Set("LogFile", LogFile) -viper.Set("host.port", 5899) // set subset +viper.Set("verbose", true) +viper.Set("host.port", 5899) // Set an embedded key ``` ### Registering and Using Aliases @@ -236,174 +234,122 @@ Aliases permit a single value to be referenced by multiple keys ```go viper.RegisterAlias("loud", "Verbose") -viper.Set("verbose", true) // same result as next line -viper.Set("loud", true) // same result as prior line +viper.Set("verbose", true) // Same result as next line +viper.Set("loud", true) // Same result as prior line -viper.GetBool("loud") // true +viper.GetBool("loud") // true viper.GetBool("verbose") // true ``` ### Working with Environment Variables -Viper has full support for environment variables. This enables 12 factor -applications out of the box. There are five methods that exist to aid working -with ENV: +Viper has full support for environment variables. - * `AutomaticEnv()` - * `BindEnv(string...) : error` - * `SetEnvPrefix(string)` - * `SetEnvKeyReplacer(string...) *strings.Replacer` - * `AllowEmptyEnv(bool)` - -_When working with ENV variables, it’s important to recognize that Viper -treats ENV variables as case sensitive._ - -Viper provides a mechanism to try to ensure that ENV variables are unique. By -using `SetEnvPrefix`, you can tell Viper to use a prefix while reading from -the environment variables. Both `BindEnv` and `AutomaticEnv` will use this -prefix. - -`BindEnv` takes one or more parameters. The first parameter is the key name, the -rest are the name of the environment variables to bind to this key. If more than -one are provided, they will take precedence in the specified order. The name of -the environment variable is case sensitive. If the ENV variable name is not provided, then -Viper will automatically assume that the ENV variable matches the following format: prefix + "_" + the key name in ALL CAPS. When you explicitly provide the ENV variable name (the second parameter), -it **does not** automatically add the prefix. For example if the second parameter is "id", -Viper will look for the ENV variable "ID". - -One important thing to recognize when working with ENV variables is that the -value will be read each time it is accessed. Viper does not fix the value when -the `BindEnv` is called. - -`AutomaticEnv` is a powerful helper especially when combined with -`SetEnvPrefix`. When called, Viper will check for an environment variable any -time a `viper.Get` request is made. It will apply the following rules. It will -check for an environment variable with a name matching the key uppercased and -prefixed with the `EnvPrefix` if set. - -`SetEnvKeyReplacer` allows you to use a `strings.Replacer` object to rewrite Env -keys to an extent. This is useful if you want to use `-` or something in your -`Get()` calls, but want your environmental variables to use `_` delimiters. An -example of using it can be found in `viper_test.go`. - -Alternatively, you can use `EnvKeyReplacer` with `NewWithOptions` factory function. -Unlike `SetEnvKeyReplacer`, it accepts a `StringReplacer` interface allowing you to write custom string replacing logic. - -By default empty environment variables are considered unset and will fall back to -the next configuration source. To treat empty environment variables as set, use -the `AllowEmptyEnv` method. - -#### Env example +> **NOTE** Unlike other configuration sources, environment variables are case +> sensitive. ```go -SetEnvPrefix("spf") // will be uppercased automatically -BindEnv("id") +// Tells Viper to use this prefix when reading environment variables +viper.SetEnvPrefix("spf") -os.Setenv("SPF_ID", "13") // typically done outside of the app +// Viper will look for "SPF_ID", automatically uppercasing the prefix and key +viper.BindEnv("id") -id := Get("id") // 13 +// Alternatively, we can search for any environment variable prefixed and load +// them in +viper.AutomaticEnv() + +os.Setenv("SPF_ID", "13") + +id := viper.Get("id") // 13 ``` +* By default, empty environment variables are considered unset and will fall back to + the next configuration source, unless `AllowEmptyEnv` is used. +* Viper does not "cache" environment variables--the value will be read each + time it is accessed. +* `SetEnvKeyReplacer` and `EnvKeyReplacer` allow you to use to rewrite Env + keys, which is useful to combine SCREAMING_SNAKE_CASE environment variables + to merge with kebab-cased configuration values from other sources. + ### Working with Flags -Viper has the ability to bind to flags. Specifically, Viper supports `Pflags` -as used in the [Cobra](https://github.com/spf13/cobra) library. +Viper has the ability to bind to flags. Specifically, Viper supports +[pflag](https://github.com/spf13/pflag/) as used in the +[Cobra](https://github.com/spf13/cobra) library. -Like `BindEnv`, the value is not set when the binding method is called, but when -it is accessed. This means you can bind as early as you want, even in an -`init()` function. +Like environment variables, the value is not set when the binding method is +called, but when it is accessed. -For individual flags, the `BindPFlag()` method provides this functionality. - -Example: +For individual flags, the `BindPFlag` method provides this functionality. ```go 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): - -Example: +You can also bind an existing set of pflags. ```go 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 +i := viper.GetInt("flagname") // Retrieve values from viper instead of pflag ``` -The use of [pflag](https://github.com/spf13/pflag/) in Viper does not preclude -the use of other packages that use the [flag](https://golang.org/pkg/flag/) -package from the standard library. The pflag package can handle the flags -defined for the flag package by importing these flags. This is accomplished -by a calling a convenience function provided by the pflag package called -AddGoFlagSet(). - -Example: +The standard library [flag](https://golang.org/pkg/flag/) package is not +directly supported, but may be parsed through pflag. ```go package main import ( "flag" + "github.com/spf13/pflag" ) func main() { - - // using standard library "flag" package + // Using standard library "flag" package flag.Int("flagname", 1234, "help message for flagname") + // Pass standard library flags to pflag pflag.CommandLine.AddGoFlagSet(flag.CommandLine) pflag.Parse() + + // Viper takes over viper.BindPFlags(pflag.CommandLine) - - i := viper.GetInt("flagname") // retrieve value from viper - - // ... } ``` -#### Flag interfaces - -Viper provides two Go interfaces to bind other flag systems if you don’t use `Pflags`. - -`FlagValue` represents a single flag. This is a very simple example on how to implement this interface: +Use of pflag may be avoided entirely by implementing the `FlagValue` and +`FlagValueSet` interfaces. ```go +// Implementing FlagValue + 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 simply tell Viper to bind it: - -```go viper.BindFlagValue("my-flag-name", myFlag{}) -``` -`FlagValueSet` represents a group of flags. This is a very simple example on how to implement this interface: +// Implementing FlagValueSet -```go 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 simply tell Viper to bind it: - -```go fSet := myFlagSet{ flags: []myFlag{myFlag{}, myFlag{}}, } @@ -413,45 +359,49 @@ viper.BindFlagValues("my-flags", fSet) ### Remote Key/Value Store Support To enable remote support in Viper, do a blank import of the `viper/remote` -package: +package. -`import _ "github.com/spf13/viper/remote"` +```go +import _ "github.com/spf13/viper/remote" +``` -Viper will read a config string (as JSON, TOML, YAML, HCL or envfile) retrieved from a path -in a Key/Value store such as etcd or Consul. These values take precedence over -default values, but are overridden by configuration values retrieved from disk, -flags, or environment variables. +Viper supports the following remote key/value stores. Examples for each are +provided below. -Viper supports multiple hosts. To use, pass a list of endpoints separated by `;`. For example `http://127.0.0.1:4001;http://127.0.0.1:4002`. +* Etcd and Etcd3 +* Consul +* Firestore +* NATS + +Viper will read a config string (as JSON, TOML, YAML, HCL or envfile) retrieved +from a path in a key/value store. + +Viper supports multiple hosts separated by `;`. For example: +`http://127.0.0.1:4001;http://127.0.0.1:4002`. + +#### Encryption Viper uses [crypt](https://github.com/sagikazarmark/crypt) to retrieve -configuration from the K/V store, which means that you can store your -configuration values encrypted and have them automatically decrypted if you have -the correct gpg keyring. Encryption is optional. +configuration from the key/value store, which means that you can store your +configuration values encrypted and have them automatically decrypted if you +have the correct GPG keyring. Encryption is optional. -You can use remote configuration in conjunction with local configuration, or -independently of it. - -`crypt` has a command-line helper that you can use to put configurations in your -K/V store. `crypt` defaults to etcd on http://127.0.0.1:4001. +Crypt has a command-line helper that you can use to put configurations in your +key/value store. ```bash $ go get github.com/sagikazarmark/crypt/bin/crypt $ crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json -``` - -Confirm that your value was set: - -```bash $ crypt get -plaintext /config/hugo.json ``` -See the `crypt` documentation for examples of how to set encrypted values, or +See the Crypt documentation for examples of how to set encrypted values, or how to use Consul. -### Remote Key/Value Store Example - Unencrypted +### Remote Key/Value Store Examples (unencrypted) #### etcd + ```go viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json") viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" @@ -459,6 +409,7 @@ err := viper.ReadRemoteConfig() ``` #### etcd3 + ```go viper.AddRemoteProvider("etcd3", "http://127.0.0.1:4001","/config/hugo.json") viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" @@ -466,8 +417,8 @@ err := viper.ReadRemoteConfig() ``` #### Consul -You need to set a key to Consul key/value storage with JSON value containing your desired config. -For example, create a Consul key/value store key `MY_CONSUL_KEY` with value: + +Given a Consul key `MY_CONSUL_KEY` with the value: ```json { @@ -482,7 +433,6 @@ viper.SetConfigType("json") // Need to explicitly set this to json err := viper.ReadRemoteConfig() fmt.Println(viper.Get("port")) // 8080 -fmt.Println(viper.Get("hostname")) // myhostname.com ``` #### Firestore @@ -493,8 +443,7 @@ viper.SetConfigType("json") // Config's format: "json", "toml", "yaml", "yml" err := viper.ReadRemoteConfig() ``` -Of course, you're allowed to use `SecureRemoteProvider` also - +Of course, you're allowed to use `SecureRemoteProvider` also. #### NATS @@ -504,7 +453,7 @@ viper.SetConfigType("json") err := viper.ReadRemoteConfig() ``` -### Remote Key/Value Store Example - Encrypted +### Remote Key/Value Store Examples (encrypted) ```go viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg") @@ -512,35 +461,34 @@ viper.SetConfigType("json") // because there is no file extension in a stream of err := viper.ReadRemoteConfig() ``` -### Watching Changes in etcd - Unencrypted +### Watching Key/Value Store Changes ```go -// alternatively, you can create a new viper instance. +// Alternatively, 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 a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" -// read from remote config the first time. +// Read from remote config the first time err := runtime_viper.ReadRemoteConfig() -// unmarshal config +// Unmarshal config runtime_viper.Unmarshal(&runtime_conf) -// open a goroutine to watch remote changes forever +// Open a goroutine to watch remote changes forever go func(){ for { time.Sleep(time.Second * 5) // delay after each request - // currently, only tested with etcd support + // Currently, only tested with Etcd support err := runtime_viper.WatchRemoteConfig() if err != nil { log.Errorf("unable to read remote config: %v", err) continue } - // unmarshal new config into our runtime config struct. you can also use channel - // to implement a signal to notify the system of the changes + // Unmarshal new config into our runtime config struct runtime_viper.Unmarshal(&runtime_conf) } }() @@ -548,191 +496,94 @@ go func(){ ## Getting Values From Viper -In Viper, there are a few ways to get a value depending on the value’s type. -The following functions and methods exist: +THe simplest way to retrieve configuration values from Viper is to use `Get*` +functions. `Get` will return an any type, but specific types may be retrieved +with `Get` functions. - * `Get(key string) : any` - * `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]any` - * `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]any` +Note that each `Get*` function will return a zero value if it’s key is not +found. To check if a key exists, use the `IsSet` method. -One important thing to recognize is that each Get function will return a zero -value if it’s not found. To check if a given key exists, the `IsSet()` method -has been provided. - -The zero value will also be returned if the value is set, but fails to parse -as the requested type. - -Example: -```go -viper.GetString("logfile") // case-insensitive Setting & Getting -if viper.GetBool("verbose") { - fmt.Println("verbose enabled") -} -``` -### Accessing nested keys - -The accessor methods also accept formatted paths to deeply nested keys. For -example, if the following JSON file is loaded: - -```json -{ - "host": { - "address": "localhost", - "port": 5799 - }, - "datastore": { - "metric": { - "host": "127.0.0.1", - "port": 3099 - }, - "warehouse": { - "host": "198.0.0.1", - "port": 2112 - } - } -} - -``` - -Viper can access a nested field by passing a `.` delimited path of keys: - -```go -GetString("datastore.metric.host") // (returns "127.0.0.1") -``` - -This obeys the precedence rules established above; the search for the path -will cascade through the remaining configuration registries until found. - -For example, given this configuration file, both `datastore.metric.host` and -`datastore.metric.port` are already defined (and may be overridden). If in addition -`datastore.metric.protocol` was defined in the defaults, Viper would also find it. - -However, if `datastore.metric` was overridden (by a flag, an environment variable, -the `Set()` method, …) with an immediate value, then all sub-keys of -`datastore.metric` become undefined, they are “shadowed” by the higher-priority -configuration level. - -Viper can access array indices by using numbers in the path. For example: +Nested keys use `.` as a delimiter and numbers for array indexes. Given the +following configuration: ```jsonc { - "host": { - "address": "localhost", - "ports": [ - 5799, - 6029 - ] - }, "datastore": { "metric": { "host": "127.0.0.1", - "port": 3099 - }, - "warehouse": { - "host": "198.0.0.1", - "port": 2112 + "ports": [ + 5799, + 6029 + ] } } } -GetInt("host.ports.1") // returns 6029 - ``` -Lastly, if there exists a key that matches the delimited key path, its value -will be returned instead. E.g. +```go +GetString("datastore.metric.host") // "127.0.0.1" +GetInt("host.ports.1") // 6029 +``` + +> **NOTE** Viper _does not_ deep merge configuration values. Complex values +> that are overridden will be entirely replaced. + +If there exists a key that matches the delimited key path, its value will be +returned instead. ```jsonc { "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 + "host": "127.0.0.1" } } } - -GetString("datastore.metric.host") // returns "0.0.0.0" ``` -### Extracting a sub-tree +```go +GetString("datastore.metric.host") // "0.0.0.0" +``` -When developing reusable modules, it's often useful to extract a subset of the configuration -and pass it to a module. This way the module can be instantiated more than once, with different configurations. +### Configuration Subsets -For example, an application might use multiple different cache stores for different purposes: +It's often useful to extract a subset of configuration (e.g., when developing a +reusable module which should accept specific sections of configuration). ```yaml cache: cache1: - max-items: 100 item-size: 64 + max-items: 100 cache2: - max-items: 200 item-size: 80 + max-items: 200 ``` -We could pass the cache name to a module (eg. `NewCache("cache1")`), -but it would require weird concatenation for accessing config keys and would be less separated from the global config. - -So instead of doing that let's pass a Viper instance to the constructor that represents a subset of the configuration: - ```go +func NewCache(v *Viper) *Cache { + return &Cache{ + ItemSize: v.GetInt("item-size"), + MaxItems: v.GetInt("max-items"), + } +} + cache1Config := viper.Sub("cache.cache1") -if cache1Config == nil { // Sub returns nil if the key cannot be found + +if cache1Config == nil { + // Sub returns nil if the key cannot be found panic("cache configuration not found") } cache1 := NewCache(cache1Config) ``` -**Note:** Always check the return value of `Sub`. It returns `nil` if a key cannot be found. - -Internally, the `NewCache` function can address `max-items` and `item-size` keys directly: - -```go -func NewCache(v *Viper) *Cache { - return &Cache{ - MaxItems: v.GetInt("max-items"), - ItemSize: v.GetInt("item-size"), - } -} -``` - -The resulting code is easy to test, since it's decoupled from the main config structure, -and easier to reuse (for the same reason). - - ### Unmarshaling -You also have the option of Unmarshaling all or a specific value to a struct, map, -etc. - -There are two methods to do this: - - * `Unmarshal(rawVal any) : error` - * `UnmarshalKey(key string, rawVal any) : error` - -Example: +You also have the option of Unmarshaling all or a specific value to a struct, +map, and etc., using `Unmarshal*` methods. ```go type config struct { @@ -749,8 +600,8 @@ if err != nil { } ``` -If you want to unmarshal configuration where the keys themselves contain dot (the default key delimiter), -you have to change the delimiter: +If you want to unmarshal configuration where the keys themselves contain `.` +(the default key delimiter), you can change the delimiter. ```go v := viper.NewWithOptions(viper.KeyDelimiter("::")) @@ -775,7 +626,7 @@ var C config v.Unmarshal(&C) ``` -Viper also supports unmarshaling into embedded structs: +Viper also supports unmarshaling into embedded structs. ```go /* @@ -793,7 +644,6 @@ type config struct { } } -// moduleConfig could be in a module specific package type moduleConfig struct { Token string } @@ -806,26 +656,20 @@ if err != nil { } ``` -Viper uses [github.com/go-viper/mapstructure](https://github.com/go-viper/mapstructure) under the hood for unmarshaling values which uses `mapstructure` tags by default. +Viper uses +[github.com/go-viper/mapstructure](https://github.com/go-viper/mapstructure) +under the hood for unmarshaling values which uses `mapstructure` tags, by +default. -### Decoding custom formats +### Marshalling to String -A frequently requested feature for Viper is adding more value formats and decoders. -For example, parsing character (dot, comma, semicolon, etc) separated strings into slices. - -This is already available in Viper using mapstructure decode hooks. - -Read more about the details in [this blog post](https://sagikazarmark.hu/blog/decoding-custom-formats-with-viper/). - -### Marshalling to string - -You may need to marshal all the settings held in viper into a string rather than write them to a file. -You can use your favorite format's marshaller with the config returned by `AllSettings()`. +You may need to marshal all the settings held in viper into a string rather +than write them to a file. You can use your favorite format's marshaller with +the config returned by `AllSettings`. ```go import ( yaml "go.yaml.in/yaml/v3" - // ... ) func yamlStringSettings() string { @@ -838,26 +682,42 @@ func yamlStringSettings() string { } ``` -## Viper or Vipers? +### Decoding Custom Formats -Viper comes with a global instance (singleton) out of the box. +A frequently requested feature for Viper is adding more value formats and +decoders. For example, parsing character (dot, comma, semicolon, etc) separated +strings into slices. This is already available in Viper using mapstructure +decode hooks. -Although it makes setting up configuration easy, -using it is generally discouraged as it makes testing harder and can lead to unexpected behavior. +Read more in [this blog +post](https://sagikazarmark.hu/blog/decoding-custom-formats-with-viper/). -The best practice is to initialize a Viper instance and pass that around when necessary. -The global instance _MAY_ be deprecated in the future. -See [#1855](https://github.com/spf13/viper/issues/1855) for more details. +## FAQ -### Working with multiple vipers +### Why is it called “Viper”? -You can also create many different vipers for use in your application. Each will -have its own unique set of configurations and values. Each can read from a -different config file, key value store, etc. All of the functions that viper -package supports are mirrored as methods on a viper. +Viper is designed to be a +[companion](http://en.wikipedia.org/wiki/Viper_(G.I._Joe)) to +[Cobra](https://github.com/spf13/cobra). While both can operate completely +independently, together they make a powerful pair to handle much of your +application foundation needs. -Example: +### I found a bug or want a feature, should I file an issue or a PR? + +Yes, but there are two things to be aware of. + +1. The Viper project is currently prioritizing backwards compatibility and + stability over features. +2. Features may be deferred until Viper 2 forms. + +### Can multiple Viper instances be used? + +**tl;dr:** Yes. + +Each will have its own unique configuration and can read from a different +configuration source. All of the functions that the Viper package supports are +mirrored as methods on a Viper instance. ```go x := viper.New() @@ -865,51 +725,56 @@ y := viper.New() x.SetDefault("ContentDir", "content") y.SetDefault("ContentDir", "foobar") - -//... ``` -When working with multiple vipers, it is up to the user to keep track of the -different vipers. +### Should Viper be a global singleton or passed around? +The best practice is to initialize a Viper instance and pass that around when +necessary. -## Q & A +Viper comes with a global instance (singleton) out of the box. Although it +makes setting up configuration easy, using it is generally discouraged as it +makes testing harder and can lead to unexpected behavior. -### Why is it called “Viper”? - -A: Viper is designed to be a [companion](http://en.wikipedia.org/wiki/Viper_(G.I._Joe)) -to [Cobra](https://github.com/spf13/cobra). While both can operate completely -independently, together they make a powerful pair to handle much of your -application foundation needs. - -### Why is it called “Cobra”? - -Is there a better name for a [commander](http://en.wikipedia.org/wiki/Cobra_Commander)? +The global instance may be deprecated in the future. See +[#1855](https://github.com/spf13/viper/issues/1855) for more details. ### Does Viper support case sensitive keys? **tl;dr:** No. -Viper merges configuration from various sources, many of which are either case insensitive or uses different casing than the rest of the sources (eg. env vars). -In order to provide the best experience when using multiple sources, the decision has been made to make all keys case insensitive. +Viper merges configuration from various sources, many of which are either case +insensitive or use different casing than other sources (e.g., env vars). In +order to provide the best experience when using multiple sources, all keys are +made case insensitive. -There has been several attempts to implement case sensitivity, but unfortunately it's not that trivial. We might take a stab at implementing it in [Viper v2](https://github.com/spf13/viper/issues/772), but despite the initial noise, it does not seem to be requested that much. +There has been several attempts to implement case sensitivity, but +unfortunately it's not trivial. We might take a stab at implementing it in +[Viper v2](https://github.com/spf13/viper/issues/772), but despite the initial +noise, it does not seem to be requested that much. -You can vote for case sensitivity by filling out this feedback form: https://forms.gle/R6faU74qPRPAzchZ9 +You can vote for case sensitivity by filling out this feedback form: +https://forms.gle/R6faU74qPRPAzchZ9. -### Is it safe to concurrently read and write to a viper? +### Is it safe to concurrently read and write to a Viper instance? + +No, you will need to synchronize access to Viper yourself (for example by using +the `sync` package). Concurrent reads and writes can cause a panic. -No, you will need to synchronize access to the viper yourself (for example by using the `sync` package). Concurrent reads and writes can cause a panic. ## Troubleshooting See [TROUBLESHOOTING.md](TROUBLESHOOTING.md). + ## Development -**For an optimal developer experience, it is recommended to install [Nix](https://nixos.org/download.html) and [direnv](https://direnv.net/docs/installation.html).** +**For an optimal developer experience, it is recommended to install +[Nix](https://nixos.org/download.html) and +[direnv](https://direnv.net/docs/installation.html).** -_Alternatively, install [Go](https://go.dev/dl/) on your computer then run `make deps` to install the rest of the dependencies._ +_Alternatively, install [Go](https://go.dev/dl/) on your computer then run +`make deps` to install the rest of the dependencies._ Run the test suite: @@ -929,6 +794,7 @@ Some linter violations can automatically be fixed: make fmt ``` + ## License The project is licensed under the [MIT License](LICENSE).