mirror of
https://github.com/NotAShelf/catApi.git
synced 2024-11-23 16:00:43 +00:00
924 lines
29 KiB
Markdown
924 lines
29 KiB
Markdown
|
> ## 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!**
|
|||
|
|
|||
|
![Viper](.github/logo.png?raw=true)
|
|||
|
|
|||
|
[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge-flat.svg)](https://github.com/avelino/awesome-go#configuration)
|
|||
|
[![run on repl.it](https://repl.it/badge/github/sagikazarmark/Viper-example)](https://repl.it/@sagikazarmark/Viper-example#main.go)
|
|||
|
|
|||
|
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/spf13/viper/ci.yaml?branch=master&style=flat-square)](https://github.com/spf13/viper/actions?query=workflow%3ACI)
|
|||
|
[![Join the chat at https://gitter.im/spf13/viper](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/spf13/viper?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|||
|
[![Go Report Card](https://goreportcard.com/badge/github.com/spf13/viper?style=flat-square)](https://goreportcard.com/report/github.com/spf13/viper)
|
|||
|
![Go Version](https://img.shields.io/badge/go%20version-%3E=1.19-61CFDD.svg?style=flat-square)
|
|||
|
[![PkgGoDev](https://pkg.go.dev/badge/mod/github.com/spf13/viper)](https://pkg.go.dev/mod/github.com/spf13/viper)
|
|||
|
|
|||
|
**Go configuration with fangs!**
|
|||
|
|
|||
|
Many Go projects are built using Viper including:
|
|||
|
|
|||
|
* [Hugo](http://gohugo.io)
|
|||
|
* [EMC RexRay](http://rexray.readthedocs.org/en/stable/)
|
|||
|
* [Imgur’s Incus](https://github.com/Imgur/incus)
|
|||
|
* [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack)
|
|||
|
* [Docker Notary](https://github.com/docker/Notary)
|
|||
|
* [BloomApi](https://www.bloomapi.com/)
|
|||
|
* [doctl](https://github.com/digitalocean/doctl)
|
|||
|
* [Clairctl](https://github.com/jgsqware/clairctl)
|
|||
|
* [Mercure](https://mercure.rocks)
|
|||
|
* [Meshery](https://github.com/meshery/meshery)
|
|||
|
* [Bearer](https://github.com/bearer/bearer)
|
|||
|
* [Coder](https://github.com/coder/coder)
|
|||
|
* [Vitess](https://vitess.io/)
|
|||
|
|
|||
|
|
|||
|
## Install
|
|||
|
|
|||
|
```shell
|
|||
|
go get github.com/spf13/viper
|
|||
|
```
|
|||
|
|
|||
|
**Note:** Viper uses [Go Modules](https://github.com/golang/go/wiki/Modules) to manage dependencies.
|
|||
|
|
|||
|
|
|||
|
## What is 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
|
|||
|
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
|
|||
|
|
|||
|
Viper can be thought of as a registry for all of your applications configuration needs.
|
|||
|
|
|||
|
|
|||
|
## Why 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 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:
|
|||
|
|
|||
|
* explicit call to `Set`
|
|||
|
* flag
|
|||
|
* env
|
|||
|
* config
|
|||
|
* key/value store
|
|||
|
* default
|
|||
|
|
|||
|
**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"})
|
|||
|
```
|
|||
|
|
|||
|
### 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.
|
|||
|
|
|||
|
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.
|
|||
|
|
|||
|
```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
|
|||
|
panic(fmt.Errorf("fatal error config file: %w", err))
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
You can handle the specific case where no config file is found like this:
|
|||
|
|
|||
|
```go
|
|||
|
if err := viper.ReadInConfig(); err != nil {
|
|||
|
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
|
|||
|
// Config file not found; ignore error if desired
|
|||
|
} else {
|
|||
|
// Config file was found but another error was produced
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 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`
|
|||
|
|
|||
|
### 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:
|
|||
|
|
|||
|
```go
|
|||
|
viper.WriteConfig() // writes current config to predefined path set by 'viper.AddConfigPath()' and 'viper.SetConfigName'
|
|||
|
viper.SafeWriteConfig()
|
|||
|
viper.WriteConfigAs("/path/to/my/.config")
|
|||
|
viper.SafeWriteConfigAs("/path/to/my/.config") // will error since it has already been written
|
|||
|
viper.SafeWriteConfigAs("/path/to/my/.other_config")
|
|||
|
```
|
|||
|
|
|||
|
### Watching and re-reading config files
|
|||
|
|
|||
|
Viper supports the ability to have your application live read a config file while running.
|
|||
|
|
|||
|
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()`**
|
|||
|
|
|||
|
```go
|
|||
|
viper.OnConfigChange(func(e fsnotify.Event) {
|
|||
|
fmt.Println("Config file changed:", e.Name)
|
|||
|
})
|
|||
|
viper.WatchConfig()
|
|||
|
```
|
|||
|
|
|||
|
### 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.
|
|||
|
|
|||
|
```go
|
|||
|
viper.SetConfigType("yaml") // or viper.SetConfigType("YAML")
|
|||
|
|
|||
|
// any approach to require this configuration into your program.
|
|||
|
var yamlExample = []byte(`
|
|||
|
Hacker: true
|
|||
|
name: steve
|
|||
|
hobbies:
|
|||
|
- skateboarding
|
|||
|
- snowboarding
|
|||
|
- go
|
|||
|
clothing:
|
|||
|
jacket: leather
|
|||
|
trousers: denim
|
|||
|
age: 35
|
|||
|
eyes : brown
|
|||
|
beard: true
|
|||
|
`)
|
|||
|
|
|||
|
viper.ReadConfig(bytes.NewBuffer(yamlExample))
|
|||
|
|
|||
|
viper.Get("name") // this would be "steve"
|
|||
|
```
|
|||
|
|
|||
|
### Setting Overrides
|
|||
|
|
|||
|
These could be from a command line flag, or from your own application logic.
|
|||
|
|
|||
|
```go
|
|||
|
viper.Set("Verbose", true)
|
|||
|
viper.Set("LogFile", LogFile)
|
|||
|
viper.Set("host.port", 5899) // set subset
|
|||
|
```
|
|||
|
|
|||
|
### Registering and Using Aliases
|
|||
|
|
|||
|
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.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:
|
|||
|
|
|||
|
* `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
|
|||
|
|
|||
|
```go
|
|||
|
SetEnvPrefix("spf") // will be uppercased automatically
|
|||
|
BindEnv("id")
|
|||
|
|
|||
|
os.Setenv("SPF_ID", "13") // typically done outside of the app
|
|||
|
|
|||
|
id := Get("id") // 13
|
|||
|
```
|
|||
|
|
|||
|
### 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.
|
|||
|
|
|||
|
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.
|
|||
|
|
|||
|
For individual flags, the `BindPFlag()` method provides this functionality.
|
|||
|
|
|||
|
Example:
|
|||
|
|
|||
|
```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:
|
|||
|
|
|||
|
```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
|
|||
|
```
|
|||
|
|
|||
|
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:
|
|||
|
|
|||
|
```go
|
|||
|
package main
|
|||
|
|
|||
|
import (
|
|||
|
"flag"
|
|||
|
"github.com/spf13/pflag"
|
|||
|
)
|
|||
|
|
|||
|
func main() {
|
|||
|
|
|||
|
// using 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 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:
|
|||
|
|
|||
|
```go
|
|||
|
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:
|
|||
|
|
|||
|
```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{}},
|
|||
|
}
|
|||
|
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:
|
|||
|
|
|||
|
`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 uses [crypt](https://github.com/bketelsen/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.
|
|||
|
|
|||
|
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.
|
|||
|
|
|||
|
```bash
|
|||
|
$ go get github.com/bketelsen/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
|
|||
|
how to use Consul.
|
|||
|
|
|||
|
### Remote Key/Value Store Example - 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"
|
|||
|
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"
|
|||
|
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:
|
|||
|
|
|||
|
```json
|
|||
|
{
|
|||
|
"port": 8080,
|
|||
|
"hostname": "myhostname.com"
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
```go
|
|||
|
viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY")
|
|||
|
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
|
|||
|
|
|||
|
```go
|
|||
|
viper.AddRemoteProvider("firestore", "google-cloud-project-id", "collection/document")
|
|||
|
viper.SetConfigType("json") // Config's format: "json", "toml", "yaml", "yml"
|
|||
|
err := viper.ReadRemoteConfig()
|
|||
|
```
|
|||
|
|
|||
|
Of course, you're allowed to use `SecureRemoteProvider` also
|
|||
|
|
|||
|
|
|||
|
#### NATS
|
|||
|
|
|||
|
```go
|
|||
|
viper.AddRemoteProvider("nats", "nats://127.0.0.1:4222", "myapp.config")
|
|||
|
viper.SetConfigType("json")
|
|||
|
err := viper.ReadRemoteConfig()
|
|||
|
```
|
|||
|
|
|||
|
### Remote Key/Value Store Example - Encrypted
|
|||
|
|
|||
|
```go
|
|||
|
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 a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
|
|||
|
err := viper.ReadRemoteConfig()
|
|||
|
```
|
|||
|
|
|||
|
### Watching Changes in etcd - Unencrypted
|
|||
|
|
|||
|
```go
|
|||
|
// 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.
|
|||
|
err := runtime_viper.ReadRemoteConfig()
|
|||
|
|
|||
|
// unmarshal config
|
|||
|
runtime_viper.Unmarshal(&runtime_conf)
|
|||
|
|
|||
|
// 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
|
|||
|
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
|
|||
|
runtime_viper.Unmarshal(&runtime_conf)
|
|||
|
}
|
|||
|
}()
|
|||
|
```
|
|||
|
|
|||
|
## 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:
|
|||
|
|
|||
|
* `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`
|
|||
|
|
|||
|
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.
|
|||
|
|
|||
|
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:
|
|||
|
|
|||
|
```jsonc
|
|||
|
{
|
|||
|
"host": {
|
|||
|
"address": "localhost",
|
|||
|
"ports": [
|
|||
|
5799,
|
|||
|
6029
|
|||
|
]
|
|||
|
},
|
|||
|
"datastore": {
|
|||
|
"metric": {
|
|||
|
"host": "127.0.0.1",
|
|||
|
"port": 3099
|
|||
|
},
|
|||
|
"warehouse": {
|
|||
|
"host": "198.0.0.1",
|
|||
|
"port": 2112
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
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.
|
|||
|
|
|||
|
```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
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
GetString("datastore.metric.host") // returns "0.0.0.0"
|
|||
|
```
|
|||
|
|
|||
|
### Extracting a sub-tree
|
|||
|
|
|||
|
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.
|
|||
|
|
|||
|
For example, an application might use multiple different cache stores for different purposes:
|
|||
|
|
|||
|
```yaml
|
|||
|
cache:
|
|||
|
cache1:
|
|||
|
max-items: 100
|
|||
|
item-size: 64
|
|||
|
cache2:
|
|||
|
max-items: 200
|
|||
|
item-size: 80
|
|||
|
```
|
|||
|
|
|||
|
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
|
|||
|
cache1Config := viper.Sub("cache.cache1")
|
|||
|
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:
|
|||
|
|
|||
|
```go
|
|||
|
type config struct {
|
|||
|
Port int
|
|||
|
Name string
|
|||
|
PathMap string `mapstructure:"path_map"`
|
|||
|
}
|
|||
|
|
|||
|
var C config
|
|||
|
|
|||
|
err := viper.Unmarshal(&C)
|
|||
|
if err != nil {
|
|||
|
t.Fatalf("unable to decode into struct, %v", err)
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
If you want to unmarshal configuration where the keys themselves contain dot (the default key delimiter),
|
|||
|
you have to change the delimiter:
|
|||
|
|
|||
|
```go
|
|||
|
v := viper.NewWithOptions(viper.KeyDelimiter("::"))
|
|||
|
|
|||
|
v.SetDefault("chart::values", map[string]any{
|
|||
|
"ingress": map[string]any{
|
|||
|
"annotations": map[string]any{
|
|||
|
"traefik.frontend.rule.type": "PathPrefix",
|
|||
|
"traefik.ingress.kubernetes.io/ssl-redirect": "true",
|
|||
|
},
|
|||
|
},
|
|||
|
})
|
|||
|
|
|||
|
type config struct {
|
|||
|
Chart struct{
|
|||
|
Values map[string]any
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
var C config
|
|||
|
|
|||
|
v.Unmarshal(&C)
|
|||
|
```
|
|||
|
|
|||
|
Viper also supports unmarshaling into embedded structs:
|
|||
|
|
|||
|
```go
|
|||
|
/*
|
|||
|
Example config:
|
|||
|
|
|||
|
module:
|
|||
|
enabled: true
|
|||
|
token: 89h3f98hbwf987h3f98wenf89ehf
|
|||
|
*/
|
|||
|
type config struct {
|
|||
|
Module struct {
|
|||
|
Enabled bool
|
|||
|
|
|||
|
moduleConfig `mapstructure:",squash"`
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// moduleConfig could be in a module specific package
|
|||
|
type moduleConfig struct {
|
|||
|
Token string
|
|||
|
}
|
|||
|
|
|||
|
var C config
|
|||
|
|
|||
|
err := viper.Unmarshal(&C)
|
|||
|
if err != nil {
|
|||
|
t.Fatalf("unable to decode into struct, %v", err)
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Viper uses [github.com/mitchellh/mapstructure](https://github.com/mitchellh/mapstructure) under the hood for unmarshaling values which uses `mapstructure` tags by default.
|
|||
|
|
|||
|
### Decoding custom formats
|
|||
|
|
|||
|
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()`.
|
|||
|
|
|||
|
```go
|
|||
|
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)
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## Viper or Vipers?
|
|||
|
|
|||
|
Viper comes ready to use out of the box. There is no configuration or
|
|||
|
initialization needed to begin using Viper. Since most applications will want
|
|||
|
to use a single central repository for their configuration, the viper package
|
|||
|
provides this. It is similar to a singleton.
|
|||
|
|
|||
|
In all of the examples above, they demonstrate using viper in its singleton
|
|||
|
style approach.
|
|||
|
|
|||
|
### Working with multiple vipers
|
|||
|
|
|||
|
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.
|
|||
|
|
|||
|
Example:
|
|||
|
|
|||
|
```go
|
|||
|
x := viper.New()
|
|||
|
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.
|
|||
|
|
|||
|
|
|||
|
## Q & A
|
|||
|
|
|||
|
### 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)?
|
|||
|
|
|||
|
### 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.
|
|||
|
|
|||
|
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.
|
|||
|
|
|||
|
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?
|
|||
|
|
|||
|
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).**
|
|||
|
|
|||
|
_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:
|
|||
|
|
|||
|
```shell
|
|||
|
make test
|
|||
|
```
|
|||
|
|
|||
|
Run linters:
|
|||
|
|
|||
|
```shell
|
|||
|
make lint # pass -j option to run them in parallel
|
|||
|
```
|
|||
|
|
|||
|
Some linter violations can automatically be fixed:
|
|||
|
|
|||
|
```shell
|
|||
|
make fmt
|
|||
|
```
|
|||
|
|
|||
|
## License
|
|||
|
|
|||
|
The project is licensed under the [MIT License](LICENSE).
|