Merge upstream master.

This commit is contained in:
Stephan Zeissler 2015-08-20 12:44:00 +02:00
parent f783671488
commit a99a3b4689
6 changed files with 1102 additions and 285 deletions

View file

@ -1,6 +1,5 @@
language: go language: go
go: go:
- 1.2
- 1.3 - 1.3
- release - release
- tip - tip

402
README.md
View file

@ -1,44 +1,47 @@
viper [![Build Status](https://travis-ci.org/spf13/viper.svg)](https://travis-ci.org/spf13/viper) viper [![Build Status](https://travis-ci.org/spf13/viper.svg)](https://travis-ci.org/spf13/viper)
===== =====
[![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 configuration with fangs Go configuration with fangs
## What is Viper? ## What is Viper?
Viper is a complete configuration solution for go applications. It has Viper is a complete configuration solution for go applications. It is designed
been designed to work within an application to handle all types of to work within an application, and can handle all types of configuration needs
configuration. It supports and formats. It supports:
* setting defaults * setting defaults
* reading from yaml, toml and json config files * reading from JSON, TOML, and YAML config files
* reading from environment variables * reading from environment variables
* reading from remote config systems (Etcd or Consul) * reading from remote config systems (Etcd or Consul), and watching changes
* reading from command line flags * reading from command line flags
* reading from buffer
* setting explicit values * setting explicit values
It can be thought of as a registry for all of your applications Viper can be thought of as a registry for all of your applications
configuration needs. configuration needs.
## Why Viper? ## Why Viper?
When building a modern application you dont want to have to worry about When building a modern application, you dont want to worry about
configuration file formats, you want to focus on building awesome software. configuration file formats; you want to focus on building awesome software.
Viper is here to help with that. Viper is here to help with that.
Viper does the following for you: Viper does the following for you:
1. Find, load and marshal a configuration file in YAML, TOML or JSON. 1. Find, load, and marshal a configuration file in JSON, TOML, or YAML.
2. Provide a mechanism to set default values for your different 2. Provide a mechanism to set default values for your different
configuration options configuration options.
3. Provide a mechanism to set override values for options specified 3. Provide a mechanism to set override values for options specified through
through command line flags. command line flags.
4. Provide an alias system to easily rename parameters without breaking 4. Provide an alias system to easily rename parameters without breaking existing
existing code. code.
5. Make it easy to tell the difference between when a user has provided 5. Make it easy to tell the difference between when a user has provided a
a command line or config file which is the same as the default. command line or config file which is the same as the default.
Viper uses the following precedence order. Each item takes precedence Viper uses the following precedence order. Each item takes precedence over the
over the item below it: item below it:
* explicit call to Set * explicit call to Set
* flag * flag
@ -53,40 +56,78 @@ Viper configuration keys are case insensitive.
### Establishing Defaults ### Establishing Defaults
A good configuration system will support default values. A default value A good configuration system will support default values. A default value is not
is not required for a key, but can establish a default to be used in the required for a key, but it's useful in the event that a key hasnt be set via
event that the key hasnt be set via config file, environment variable, config file, environment variable, remote configuration or flag.
remote configuration or flag.
Examples: Examples:
```go
viper.SetDefault("ContentDir", "content") viper.SetDefault("ContentDir", "content")
viper.SetDefault("LayoutDir", "layouts") viper.SetDefault("LayoutDir", "layouts")
viper.SetDefault("Indexes", map[string]string{"tag": "tags", "category": "categories"}) viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})
```
### Reading Config Files ### Reading Config Files
If you want to support a config file, Viper requires a minimal Viper requires minimal configuration so it knows where to look for config files.
configuration so it knows where to look for the config file. Viper Viper supports JSON, TOML and YAML files. Viper can search multiple paths, but
supports yaml, toml and json files. Viper can search multiple paths, but currently a single Viper instance only supports a single configuration file.
currently a single viper only supports a single config file.
```go
viper.SetConfigName("config") // name of config file (without extension) viper.SetConfigName("config") // name of config file (without extension)
viper.AddConfigPath("/etc/appname/") // path to look for the config file in 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("$HOME/.appname") // call multiple times to add many search paths
viper.ReadInConfig() // Find and read the config file 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: %s \n", err))
}
```
### 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 ### Setting Overrides
These could be from a command line flag, or from your own application logic. These could be from a command line flag, or from your own application logic.
```go
viper.Set("Verbose", true) viper.Set("Verbose", true)
viper.Set("LogFile", LogFile) viper.Set("LogFile", LogFile)
```
### Registering and Using Aliases ### Registering and Using Aliases
Aliases permit a single value to be referenced by multiple keys Aliases permit a single value to be referenced by multiple keys
```go
viper.RegisterAlias("loud", "Verbose") viper.RegisterAlias("loud", "Verbose")
viper.Set("verbose", true) // same result as next line viper.Set("verbose", true) // same result as next line
@ -94,148 +135,273 @@ Aliases permit a single value to be referenced by multiple keys
viper.GetBool("loud") // true viper.GetBool("loud") // true
viper.GetBool("verbose") // true viper.GetBool("verbose") // true
```
### Working with Environment Variables ### Working with Environment Variables
Viper has full support for environment variables. This enables 12 factor Viper has full support for environment variables. This enables 12 factor
applications out of the box. There are three methods that exist to aid applications out of the box. There are four methods that exist to aid working
with working with ENV: with ENV:
* AutomaticEnv() * `AutomaticEnv()`
* BindEnv(string...) : error * `BindEnv(string...) : error`
* SetEnvPrefix(string) * `SetEnvPrefix(string)`
* `SetEnvReplacer(string...) *strings.Replacer`
_When working with ENV variables its important to recognize that Viper _When working with ENV variables, its important to recognize that Viper
treats ENV variables as case sensitive._ treats ENV variables as case sensitive._
Viper provides a mechanism to try to ensure that ENV variables are Viper provides a mechanism to try to ensure that ENV variables are unique. By
unique. By using SetEnvPrefix you can tell Viper to use add a prefix using `SetEnvPrefix`, you can tell Viper to use add a prefix while reading from
while reading from the environment variables. Both BindEnv and the environment variables. Both `BindEnv` and `AutomaticEnv` will use this
AutomaticEnv will use this prefix. prefix.
BindEnv takes one or two parameters. The first parameter is the key `BindEnv` takes one or two parameters. The first parameter is the key name, the
name, the second is the name of the environment variable. The name of second is the name of the environment variable. The name of the environment
the environment variable is case sensitive. If the ENV variable name is variable is case sensitive. If the ENV variable name is not provided, then
not provided then Viper will automatically assume that the key name Viper will automatically assume that the key name matches the ENV variable name,
matches the ENV variable name but the ENV variable is IN ALL CAPS. When but the ENV variable is IN ALL CAPS. When you explicitly provide the ENV
you explicitly provide the env variable name it **Does not** variable name, it **does not** automatically add the prefix.
automatically add the prefix.
One important thing to recognize when working with ENV variables is that One important thing to recognize when working with ENV variables is that the
the value will be read each time it is accessed. It does not fix the value will be read each time it is accessed. Viper does not fix the value when
value when the BindEnv is called. the `BindEnv` is called.
AutomaticEnv is a powerful helper especially when combined with `AutomaticEnv` is a powerful helper especially when combined with
SetEnvPrefix. When called, Viper will check for an environment variable `SetEnvPrefix`. When called, Viper will check for an environment variable any
any time a viper.Get request is made. It will apply the following rules. time a `viper.Get` request is made. It will apply the following rules. It will
It will check for a environment variable with a name matching the key check for a environment variable with a name matching the key uppercased and
uppercased and prefixed with the EnvPrefix if set. prefixed with the `EnvPrefix` if set.
`SetEnvReplacer` 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`.
#### Env example #### Env example
```go
SetEnvPrefix("spf") // will be uppercased automatically SetEnvPrefix("spf") // will be uppercased automatically
BindEnv("id") BindEnv("id")
os.Setenv("SPF_ID", "13") // typically done outside of the app os.Setenv("SPF_ID", "13") // typically done outside of the app
id := Get("id")) // 13 id := Get("id") // 13
```
### Working with Flags ### Working with Flags
Viper has the ability to bind to flags. Specifically Viper supports Viper has the ability to bind to flags. Specifically, Viper supports `Pflags`
Pflags as used in the [Cobra](http://github.com/spf13/cobra) library. 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 Like `BindEnv`, the value is not set when the binding method is called, but when
when it is accessed. This means you can bind as early as you want, even it is accessed. This means you can bind as early as you want, even in an
in an init() function. `init()` function.
The BindPFlag() method provides this functionality. The `BindPFlag()` method provides this functionality.
Example: Example:
```go
serverCmd.Flags().Int("port", 1138, "Port to run Application server on") serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
viper.BindPFlag("port", serverCmd.Flags().Lookup("port")) viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
```
### Remote Key/Value Store Support ### Remote Key/Value Store Support
Viper will read a config string (as JSON, TOML, or YAML) retrieved from a
path in a Key/Value store such as Etcd or Consul. These values take precedence To enable remote support in Viper, do a blank import of the `viper/remote`
over default values, but are overriden by configuration values retrieved from disk, package:
`import _ github.com/spf13/viper/remote`
Viper will read a config string (as JSON, TOML, or YAML) 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. flags, or environment variables.
Viper uses [crypt](https://github.com/xordataexchange/crypt) to retrieve configuration Viper uses [crypt](https://github.com/xordataexchange/crypt) to retrieve
from the k/v store, which means that you can store your configuration values configuration from the K/V store, which means that you can store your
encrypted and have them automatically decrypted if you have the correct configuration values encrypted and have them automatically decrypted if you have
gpg keyring. Encryption is optional. the correct gpg keyring. Encryption is optional.
You can use remote configuration in conjunction with local configuration, or You can use remote configuration in conjunction with local configuration, or
independently of it. independently of it.
`crypt` has a command-line helper that you can use to put configurations `crypt` has a command-line helper that you can use to put configurations in your
in your k/v store. `crypt` defaults to etcd on http://127.0.0.1:4001. K/V store. `crypt` defaults to etcd on http://127.0.0.1:4001.
go get github.com/xordataexchange/crypt/bin/crypt ```bash
crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json $ go get github.com/xordataexchange/crypt/bin/crypt
$ crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json
```
Confirm that your value was set: Confirm that your value was set:
crypt get -plaintext /config/hugo.json ```bash
$ crypt get -plaintext /config/hugo.json
```
See the `crypt` documentation for examples of how to set encrypted values, or how See the `crypt` documentation for examples of how to set encrypted values, or
to use Consul. how to use Consul.
### Remote Key/Value Store Example - Unencrypted ### Remote Key/Value Store Example - Unencrypted
```go
viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json") 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 viper.SetConfigType("json") // because there is no file extension in a stream of bytes
err := viper.ReadRemoteConfig() err := viper.ReadRemoteConfig()
```
### Remote Key/Value Store Example - Encrypted ### Remote Key/Value Store Example - Encrypted
```go
viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg") 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 viper.SetConfigType("json") // because there is no file extension in a stream of bytes
err := viper.ReadRemoteConfig() 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
// read from remote config the first time.
err := runtime_viper.ReadRemoteConfig()
// marshal config
runtime_viper.Marshal(&runtime_conf)
// open a goroutine to wath remote changes forever
go func(){
for {
time.Sleep(time.Second * 5) // delay after each request
// currenlty, only tested with etcd support
err := runtime_viper.WatchRemoteConfig()
if err != nil {
log.Errorf("unable to read remote config: %v", err)
continue
}
// marshal 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.Marshal(&runtime_conf)
}
}()
```
## Getting Values From Viper ## Getting Values From Viper
In Viper there are a few ways to get a value depending on what type of value you want to retrieved. In Viper, there are a few ways to get a value depending on the value's type.
The following functions and methods exist: The following functions and methods exist:
* Get(key string) : interface{} * `Get(key string) : interface{}`
* GetBool(key string) : bool * `GetBool(key string) : bool`
* GetFloat64(key string) : float64 * `GetFloat64(key string) : float64`
* GetInt(key string) : int * `GetInt(key string) : int`
* GetString(key string) : string * `GetString(key string) : string`
* GetStringMap(key string) : map[string]interface{} * `GetStringMap(key string) : map[string]interface{}`
* GetStringMapString(key string) : map[string]string * `GetStringMapString(key string) : map[string]string`
* GetStringSlice(key string) : []string * `GetStringSlice(key string) : []string`
* GetTime(key string) : time.Time * `GetTime(key string) : time.Time`
* IsSet(key string) : bool * `GetDuration(key string) : time.Duration`
* `IsSet(key string) : bool`
One important thing to recognize is that each Get function will return One important thing to recognize is that each Get function will return a zero
its zero value if its not found. To check if a given key exists, the IsSet() value if its not found. To check if a given key exists, the `IsSet()` method
method has been provided. has been provided.
Example: Example:
```go
viper.GetString("logfile") // case insensitive Setting & Getting viper.GetString("logfile") // case-insensitive Setting & Getting
if viper.GetBool("verbose") { if viper.GetBool("verbose") {
fmt.Println("verbose enabled") 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 root key
(in this example, `datastore`) will cascade through the remaining configuration
registries until found. The search for the sub-keys (`metric` and `host`),
however, will not.
For example, if the `metric` key was not defined in the configuration loaded
from file, but was defined in the defaults, Viper would return the zero value.
On the other hand, if the primary key was not defined, Viper would go through
the remaining registries looking for it.
Lastly, if there exists a key that matches the delimited key path, its value
will be returned instead. E.g.
```json
{
"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"
```
### Marshaling ### Marshaling
You also have the option of Marshaling all or a specific value to a struct, map, etc. You also have the option of Marshaling all or a specific value to a struct, map,
etc.
There are two methods to do this: There are two methods to do this:
* Marshal(rawVal interface{}) : error * `Marshal(rawVal interface{}) : error`
* MarshalKey(key string, rawVal interface{}) : error * `MarshalKey(key string, rawVal interface{}) : error`
Example: Example:
```go
type config struct { type config struct {
Port int Port int
Name string Name string
@ -247,54 +413,56 @@ Example:
if err != nil { if err != nil {
t.Fatalf("unable to decode into struct, %v", err) t.Fatalf("unable to decode into struct, %v", err)
} }
```
## Viper or Vipers? ## Viper or Vipers?
Viper comes ready to use out of the box. There is no configuration or Viper comes ready to use out of the box. There is no configuration or
initialization needed to begin using Viper. Since most applications will initialization needed to begin using Viper. Since most applications will want
want to use a single central repository for their configuration the to use a single central repository for their configuration, the viper package
viper package provides this. It is similar to a singleton. provides this. It is similar to a singleton.
In all of the examples above they demonstrate using viper in its In all of the examples above, they demonstrate using viper in it's singleton
singleton style approach. style approach.
### Working with multiple vipers ### Working with multiple vipers
You can also create many different vipers for use in your application. You can also create many different vipers for use in your application. Each will
Each will have its own unique set of configurations and values. Each have its own unique set of configurations and values. Each can read from a
can read from a different config file, key value store, etc. All of the different config file, key value store, etc. All of the functions that viper
functions that viper package supports are mirrored as methods on a viper. package supports are mirrored as methods on a viper.
Example: Example:
```go
x := viper.New() x := viper.New()
y := viper.New() y := viper.New()
x.SetDefault("ContentDir", "content") x.SetDefault("ContentDir", "content")
y.SetDefault("ContentDir", "foobar") y.SetDefault("ContentDir", "foobar")
... //...
```
When working with multiple vipers it is up to the user to keep track of When working with multiple vipers, it is up to the user to keep track of the
the different vipers. different vipers.
## Q & A ## Q & A
Q: Why not INI files? Q: Why not INI files?
A: Ini files are pretty awful. Theres no standard format and they are hard to A: Ini files are pretty awful. Theres no standard format, and they are hard to
validate. Viper is designed to work with YAML, TOML or JSON files. If someone validate. Viper is designed to work with JSON, TOML or YAML files. If someone
really wants to add this feature, Id be happy to merge it. Its easy to really wants to add this feature, Id be happy to merge it. Its easy to specify
specify which formats your application will permit. which formats your application will permit.
Q: Why is it called "viper"? Q: Why is it called “Viper”?
A: Viper is designed to be a companion to A: Viper is designed to be a [companion](http://en.wikipedia.org/wiki/Viper_(G.I._Joe))
[Cobra](http://github.com/spf13/cobra). While both can operate completely to [Cobra](https://github.com/spf13/cobra). While both can operate completely
independently, together they make a powerful pair to handle much of your independently, together they make a powerful pair to handle much of your
application foundation needs. application foundation needs.
Q: Why is it called "Cobra"? Q: Why is it called “Cobra”?
A: Is there a better name for a commander? A: Is there a better name for a [commander](http://en.wikipedia.org/wiki/Cobra_Commander)?

77
remote/remote.go Normal file
View file

@ -0,0 +1,77 @@
// Copyright © 2015 Steve Francia <spf@spf13.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
// Package remote integrates the remote features of Viper.
package remote
import (
"bytes"
"github.com/spf13/viper"
crypt "github.com/xordataexchange/crypt/config"
"io"
"os"
)
type remoteConfigProvider struct{}
func (rc remoteConfigProvider) Get(rp viper.RemoteProvider) (io.Reader, error) {
cm, err := getConfigManager(rp)
if err != nil {
return nil, err
}
b, err := cm.Get(rp.Path())
if err != nil {
return nil, err
}
return bytes.NewReader(b), nil
}
func (rc remoteConfigProvider) Watch(rp viper.RemoteProvider) (io.Reader, error) {
cm, err := getConfigManager(rp)
if err != nil {
return nil, err
}
resp := <-cm.Watch(rp.Path(), nil)
err = resp.Error
if err != nil {
return nil, err
}
return bytes.NewReader(resp.Value), nil
}
func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) {
var cm crypt.ConfigManager
var err error
if rp.SecretKeyring() != "" {
kr, err := os.Open(rp.SecretKeyring())
defer kr.Close()
if err != nil {
return nil, err
}
if rp.Provider() == "etcd" {
cm, err = crypt.NewEtcdConfigManager([]string{rp.Endpoint()}, kr)
} else {
cm, err = crypt.NewConsulConfigManager([]string{rp.Endpoint()}, kr)
}
} else {
if rp.Provider() == "etcd" {
cm, err = crypt.NewStandardEtcdConfigManager([]string{rp.Endpoint()})
} else {
cm, err = crypt.NewStandardConsulConfigManager([]string{rp.Endpoint()})
}
}
if err != nil {
return nil, err
}
return cm, nil
}
func init() {
viper.RemoteConfig = &remoteConfigProvider{}
}

84
util.go
View file

@ -19,13 +19,26 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"unicode"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/magiconair/properties"
"github.com/spf13/cast"
jww "github.com/spf13/jwalterweatherman" jww "github.com/spf13/jwalterweatherman"
"gopkg.in/yaml.v1" "gopkg.in/yaml.v2"
) )
func insensativiseMap(m map[string]interface{}) { // Denotes failing to parse configuration file.
type ConfigParseError struct {
err error
}
// Returns the formatted configuration error.
func (pe ConfigParseError) Error() string {
return fmt.Sprintf("While parsing config: %s", pe.err.Error())
}
func insensitiviseMap(m map[string]interface{}) {
for key, val := range m { for key, val := range m {
lower := strings.ToLower(key) lower := strings.ToLower(key)
if key != lower { if key != lower {
@ -116,26 +129,81 @@ func findCWD() (string, error) {
return path, nil return path, nil
} }
func marshallConfigReader(in io.Reader, c map[string]interface{}, configType string) { func marshallConfigReader(in io.Reader, c map[string]interface{}, configType string) error {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
buf.ReadFrom(in) buf.ReadFrom(in)
switch configType { switch strings.ToLower(configType) {
case "yaml", "yml": case "yaml", "yml":
if err := yaml.Unmarshal(buf.Bytes(), &c); err != nil { if err := yaml.Unmarshal(buf.Bytes(), &c); err != nil {
jww.ERROR.Fatalf("Error parsing config: %s", err) return ConfigParseError{err}
} }
case "json": case "json":
if err := json.Unmarshal(buf.Bytes(), &c); err != nil { if err := json.Unmarshal(buf.Bytes(), &c); err != nil {
jww.ERROR.Fatalf("Error parsing config: %s", err) return ConfigParseError{err}
} }
case "toml": case "toml":
if _, err := toml.Decode(buf.String(), &c); err != nil { if _, err := toml.Decode(buf.String(), &c); err != nil {
jww.ERROR.Fatalf("Error parsing config: %s", err) return ConfigParseError{err}
}
case "properties", "props", "prop":
var p *properties.Properties
var err error
if p, err = properties.Load(buf.Bytes(), properties.UTF8); err != nil {
return ConfigParseError{err}
}
for _, key := range p.Keys() {
value, _ := p.Get(key)
c[key] = value
} }
} }
insensativiseMap(c) insensitiviseMap(c)
return nil
}
func safeMul(a, b uint) uint {
c := a * b
if a > 1 && b > 1 && c/b != a {
return 0
}
return c
}
// parseSizeInBytes converts strings like 1GB or 12 mb into an unsigned integer number of bytes
func parseSizeInBytes(sizeStr string) uint {
sizeStr = strings.TrimSpace(sizeStr)
lastChar := len(sizeStr) - 1
multiplier := uint(1)
if lastChar > 0 {
if sizeStr[lastChar] == 'b' || sizeStr[lastChar] == 'B' {
if lastChar > 1 {
switch unicode.ToLower(rune(sizeStr[lastChar-1])) {
case 'k':
multiplier = 1 << 10
sizeStr = strings.TrimSpace(sizeStr[:lastChar-1])
case 'm':
multiplier = 1 << 20
sizeStr = strings.TrimSpace(sizeStr[:lastChar-1])
case 'g':
multiplier = 1 << 30
sizeStr = strings.TrimSpace(sizeStr[:lastChar-1])
default:
multiplier = 1
sizeStr = strings.TrimSpace(sizeStr[:lastChar])
}
}
}
}
size := cast.ToInt(sizeStr)
if size < 0 {
size = 0
}
return safeMul(uint(size), multiplier)
} }

420
viper.go
View file

@ -10,6 +10,7 @@
// Each item takes precedence over the item below it: // Each item takes precedence over the item below it:
// overrides
// flag // flag
// env // env
// config // config
@ -34,7 +35,6 @@ import (
"github.com/spf13/cast" "github.com/spf13/cast"
jww "github.com/spf13/jwalterweatherman" jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/pflag" "github.com/spf13/pflag"
crypt "github.com/xordataexchange/crypt/config"
) )
var v *Viper var v *Viper
@ -43,32 +43,96 @@ func init() {
v = New() v = New()
} }
type remoteConfigFactory interface {
Get(rp RemoteProvider) (io.Reader, error)
Watch(rp RemoteProvider) (io.Reader, error)
}
// RemoteConfig is optional, see the remote package
var RemoteConfig remoteConfigFactory
// Denotes encountering an unsupported
// configuration filetype.
type UnsupportedConfigError string type UnsupportedConfigError string
// Returns the formatted configuration error.
func (str UnsupportedConfigError) Error() string { func (str UnsupportedConfigError) Error() string {
return fmt.Sprintf("Unsupported Config Type %q", string(str)) return fmt.Sprintf("Unsupported Config Type %q", string(str))
} }
// Denotes encountering an unsupported remote
// provider. Currently only Etcd and Consul are
// supported.
type UnsupportedRemoteProviderError string type UnsupportedRemoteProviderError string
// Returns the formatted remote provider error.
func (str UnsupportedRemoteProviderError) Error() string { func (str UnsupportedRemoteProviderError) Error() string {
return fmt.Sprintf("Unsupported Remote Provider Type %q", string(str)) return fmt.Sprintf("Unsupported Remote Provider Type %q", string(str))
} }
// Denotes encountering an error while trying to
// pull the configuration from the remote provider.
type RemoteConfigError string type RemoteConfigError string
// Returns the formatted remote provider error
func (rce RemoteConfigError) Error() string { func (rce RemoteConfigError) Error() string {
return fmt.Sprintf("Remote Configurations Error: %s", string(rce)) return fmt.Sprintf("Remote Configurations Error: %s", string(rce))
} }
// A viper is a unexported struct. Use New() to create a new instance of viper // Denotes failing to find configuration file.
// or use the functions for a "global instance" type ConfigFileNotFoundError struct {
name, locations string
}
// Returns the formatted configuration error.
func (fnfe ConfigFileNotFoundError) Error() string {
return fmt.Sprintf("Config File %q Not Found in %q", fnfe.name, fnfe.locations)
}
// Viper is a prioritized configuration registry. It
// maintains a set of configuration sources, fetches
// values to populate those, and provides them according
// to the source's priority.
// The priority of the sources is the following:
// 1. overrides
// 2. flags
// 3. env. variables
// 4. config file
// 5. key/value store
// 6. defaults
//
// For example, if values from the following sources were loaded:
//
// Defaults : {
// "secret": "",
// "user": "default",
// "endpoint": "https://localhost"
// }
// Config : {
// "user": "root"
// "secret": "defaultsecret"
// }
// Env : {
// "secret": "somesecretkey"
// }
//
// The resulting config will have the following values:
//
// {
// "secret": "somesecretkey",
// "user": "root",
// "endpoint": "https://localhost"
// }
type Viper struct { type Viper struct {
// Delimiter that separates a list of keys
// used to access a nested value in one go
keyDelim string
// A set of paths to look for the config file in // A set of paths to look for the config file in
configPaths []string configPaths []string
// A set of remote providers to search for the configuration // A set of remote providers to search for the configuration
remoteProviders []*remoteProvider remoteProviders []*defaultRemoteProvider
// Name of file to look for inside the path // Name of file to look for inside the path
configName string configName string
@ -77,6 +141,7 @@ type Viper struct {
envPrefix string envPrefix string
automaticEnvApplied bool automaticEnvApplied bool
envKeyReplacer *strings.Replacer
config map[string]interface{} config map[string]interface{}
override map[string]interface{} override map[string]interface{}
@ -87,9 +152,10 @@ type Viper struct {
aliases map[string]string aliases map[string]string
} }
// The prescribed way to create a new Viper // Returns an initialized Viper instance.
func New() *Viper { func New() *Viper {
v := new(Viper) v := new(Viper)
v.keyDelim = "."
v.configName = "config" v.configName = "config"
v.config = make(map[string]interface{}) v.config = make(map[string]interface{})
v.override = make(map[string]interface{}) v.override = make(map[string]interface{})
@ -102,21 +168,53 @@ func New() *Viper {
return v return v
} }
// remoteProvider stores the configuration necessary // Intended for testing, will reset all to default settings.
// to connect to a remote key/value store. // In the public interface for the viper package so applications
// Optional secretKeyring to unencrypt encrypted values // can use it in their testing as well.
// can be provided. func Reset() {
type remoteProvider struct { v = New()
SupportedExts = []string{"json", "toml", "yaml", "yml"}
SupportedRemoteProviders = []string{"etcd", "consul"}
}
type defaultRemoteProvider struct {
provider string provider string
endpoint string endpoint string
path string path string
secretKeyring string secretKeyring string
} }
// universally supported extensions func (rp defaultRemoteProvider) Provider() string {
var SupportedExts []string = []string{"json", "toml", "yaml", "yml"} return rp.provider
}
// universally supported remote providers func (rp defaultRemoteProvider) Endpoint() string {
return rp.endpoint
}
func (rp defaultRemoteProvider) Path() string {
return rp.path
}
func (rp defaultRemoteProvider) SecretKeyring() string {
return rp.secretKeyring
}
// RemoteProvider stores the configuration necessary
// to connect to a remote key/value store.
// Optional secretKeyring to unencrypt encrypted values
// can be provided.
type RemoteProvider interface {
Provider() string
Endpoint() string
Path() string
SecretKeyring() string
}
// Universally supported extensions.
var SupportedExts []string = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop"}
// Universally supported remote providers.
var SupportedRemoteProviders []string = []string{"etcd", "consul"} var SupportedRemoteProviders []string = []string{"etcd", "consul"}
// Explicitly define the path, name and extension of the config file // Explicitly define the path, name and extension of the config file
@ -129,6 +227,8 @@ func (v *Viper) SetConfigFile(in string) {
} }
// Define a prefix that ENVIRONMENT variables will use. // Define a prefix that ENVIRONMENT variables will use.
// E.g. if your prefix is "spf", the env registry
// will look for env. variables that start with "SPF_"
func SetEnvPrefix(in string) { v.SetEnvPrefix(in) } func SetEnvPrefix(in string) { v.SetEnvPrefix(in) }
func (v *Viper) SetEnvPrefix(in string) { func (v *Viper) SetEnvPrefix(in string) {
if in != "" { if in != "" {
@ -144,11 +244,25 @@ func (v *Viper) mergeWithEnvPrefix(in string) string {
return strings.ToUpper(in) return strings.ToUpper(in)
} }
// Return the config file used // TODO: should getEnv logic be moved into find(). Can generalize the use of
// rewriting keys many things, Ex: Get('someKey') -> some_key
// (cammel case to snake case for JSON keys perhaps)
// getEnv s a wrapper around os.Getenv which replaces characters in the original
// key. This allows env vars which have different keys then the config object
// keys
func (v *Viper) getEnv(key string) string {
if v.envKeyReplacer != nil {
key = v.envKeyReplacer.Replace(key)
}
return os.Getenv(key)
}
// Return the file used to populate the config registry
func ConfigFileUsed() string { return v.ConfigFileUsed() } func ConfigFileUsed() string { return v.ConfigFileUsed() }
func (v *Viper) ConfigFileUsed() string { return v.configFile } func (v *Viper) ConfigFileUsed() string { return v.configFile }
// Add a path for viper to search for the config file in. // Add a path for Viper to search for the config file in.
// Can be called multiple times to define multiple search paths. // Can be called multiple times to define multiple search paths.
func AddConfigPath(in string) { v.AddConfigPath(in) } func AddConfigPath(in string) { v.AddConfigPath(in) }
func (v *Viper) AddConfigPath(in string) { func (v *Viper) AddConfigPath(in string) {
@ -178,7 +292,7 @@ func (v *Viper) AddRemoteProvider(provider, endpoint, path string) error {
} }
if provider != "" && endpoint != "" { if provider != "" && endpoint != "" {
jww.INFO.Printf("adding %s:%s to remote provider list", provider, endpoint) jww.INFO.Printf("adding %s:%s to remote provider list", provider, endpoint)
rp := &remoteProvider{ rp := &defaultRemoteProvider{
endpoint: endpoint, endpoint: endpoint,
provider: provider, provider: provider,
path: path, path: path,
@ -210,10 +324,11 @@ func (v *Viper) AddSecureRemoteProvider(provider, endpoint, path, secretkeyring
} }
if provider != "" && endpoint != "" { if provider != "" && endpoint != "" {
jww.INFO.Printf("adding %s:%s to remote provider list", provider, endpoint) jww.INFO.Printf("adding %s:%s to remote provider list", provider, endpoint)
rp := &remoteProvider{ rp := &defaultRemoteProvider{
endpoint: endpoint, endpoint: endpoint,
provider: provider, provider: provider,
path: path, path: path,
secretKeyring: secretkeyring,
} }
if !v.providerPathExists(rp) { if !v.providerPathExists(rp) {
v.remoteProviders = append(v.remoteProviders, rp) v.remoteProviders = append(v.remoteProviders, rp)
@ -222,7 +337,7 @@ func (v *Viper) AddSecureRemoteProvider(provider, endpoint, path, secretkeyring
return nil return nil
} }
func (v *Viper) providerPathExists(p *remoteProvider) bool { func (v *Viper) providerPathExists(p *defaultRemoteProvider) bool {
for _, y := range v.remoteProviders { for _, y := range v.remoteProviders {
if reflect.DeepEqual(y, p) { if reflect.DeepEqual(y, p) {
return true return true
@ -231,22 +346,52 @@ func (v *Viper) providerPathExists(p *remoteProvider) bool {
return false return false
} }
func (v *Viper) searchMap(source map[string]interface{}, path []string) interface{} {
if len(path) == 0 {
return source
}
if next, ok := source[path[0]]; ok {
switch next.(type) {
case map[interface{}]interface{}:
return v.searchMap(cast.ToStringMap(next), path[1:])
case map[string]interface{}:
// Type assertion is safe here since it is only reached
// if the type of `next` is the same as the type being asserted
return v.searchMap(next.(map[string]interface{}), path[1:])
default:
return next
}
} else {
return nil
}
}
// Viper is essentially repository for configurations // Viper is essentially repository for configurations
// Get can retrieve any value given the key to use // Get can retrieve any value given the key to use
// Get has the behavior of returning the value associated with the first // Get has the behavior of returning the value associated with the first
// place from where it is set. Viper will check in the following order: // place from where it is set. Viper will check in the following order:
// flag, env, config file, key/value store, default // override, flag, env, config file, key/value store, default
// //
// Get returns an interface. For a specific value use one of the Get____ methods. // Get returns an interface. For a specific value use one of the Get____ methods.
func Get(key string) interface{} { return v.Get(key) } func Get(key string) interface{} { return v.Get(key) }
func (v *Viper) Get(key string) interface{} { func (v *Viper) Get(key string) interface{} {
key = strings.ToLower(key) path := strings.Split(key, v.keyDelim)
val := v.find(key)
val := v.find(strings.ToLower(key))
if val == nil { if val == nil {
source := v.find(path[0])
if source == nil {
return nil return nil
} }
if reflect.TypeOf(source).Kind() == reflect.Map {
val = v.searchMap(cast.ToStringMap(source), path[1:])
}
}
switch val.(type) { switch val.(type) {
case bool: case bool:
return cast.ToBool(val) return cast.ToBool(val)
@ -258,87 +403,125 @@ func (v *Viper) Get(key string) interface{} {
return cast.ToFloat64(val) return cast.ToFloat64(val)
case time.Time: case time.Time:
return cast.ToTime(val) return cast.ToTime(val)
case time.Duration:
return cast.ToDuration(val)
case []string: case []string:
return val return val
} }
return val return val
} }
// Returns the value associated with the key as a string
func GetString(key string) string { return v.GetString(key) } func GetString(key string) string { return v.GetString(key) }
func (v *Viper) GetString(key string) string { func (v *Viper) GetString(key string) string {
return cast.ToString(v.Get(key)) return cast.ToString(v.Get(key))
} }
// Returns the value associated with the key asa boolean
func GetBool(key string) bool { return v.GetBool(key) } func GetBool(key string) bool { return v.GetBool(key) }
func (v *Viper) GetBool(key string) bool { func (v *Viper) GetBool(key string) bool {
return cast.ToBool(v.Get(key)) return cast.ToBool(v.Get(key))
} }
// Returns the value associated with the key as an integer
func GetInt(key string) int { return v.GetInt(key) } func GetInt(key string) int { return v.GetInt(key) }
func (v *Viper) GetInt(key string) int { func (v *Viper) GetInt(key string) int {
return cast.ToInt(v.Get(key)) return cast.ToInt(v.Get(key))
} }
// Returns the value associated with the key as a float64
func GetFloat64(key string) float64 { return v.GetFloat64(key) } func GetFloat64(key string) float64 { return v.GetFloat64(key) }
func (v *Viper) GetFloat64(key string) float64 { func (v *Viper) GetFloat64(key string) float64 {
return cast.ToFloat64(v.Get(key)) return cast.ToFloat64(v.Get(key))
} }
// Returns the value associated with the key as time
func GetTime(key string) time.Time { return v.GetTime(key) } func GetTime(key string) time.Time { return v.GetTime(key) }
func (v *Viper) GetTime(key string) time.Time { func (v *Viper) GetTime(key string) time.Time {
return cast.ToTime(v.Get(key)) return cast.ToTime(v.Get(key))
} }
// Returns the value associated with the key as a duration
func GetDuration(key string) time.Duration { return v.GetDuration(key) }
func (v *Viper) GetDuration(key string) time.Duration {
return cast.ToDuration(v.Get(key))
}
// Returns the value associated with the key as a slice of strings
func GetStringSlice(key string) []string { return v.GetStringSlice(key) } func GetStringSlice(key string) []string { return v.GetStringSlice(key) }
func (v *Viper) GetStringSlice(key string) []string { func (v *Viper) GetStringSlice(key string) []string {
return cast.ToStringSlice(v.Get(key)) return cast.ToStringSlice(v.Get(key))
} }
// Returns the value associated with the key as a map of interfaces
func GetStringMap(key string) map[string]interface{} { return v.GetStringMap(key) } func GetStringMap(key string) map[string]interface{} { return v.GetStringMap(key) }
func (v *Viper) GetStringMap(key string) map[string]interface{} { func (v *Viper) GetStringMap(key string) map[string]interface{} {
return cast.ToStringMap(v.Get(key)) return cast.ToStringMap(v.Get(key))
} }
// Returns the value associated with the key as a map of strings
func GetStringMapString(key string) map[string]string { return v.GetStringMapString(key) } func GetStringMapString(key string) map[string]string { return v.GetStringMapString(key) }
func (v *Viper) GetStringMapString(key string) map[string]string { func (v *Viper) GetStringMapString(key string) map[string]string {
return cast.ToStringMapString(v.Get(key)) return cast.ToStringMapString(v.Get(key))
} }
// Returns the size of the value associated with the given key
// in bytes.
func GetSizeInBytes(key string) uint { return v.GetSizeInBytes(key) }
func (v *Viper) GetSizeInBytes(key string) uint {
sizeStr := cast.ToString(v.Get(key))
return parseSizeInBytes(sizeStr)
}
// Takes a single key and marshals it into a Struct // Takes a single key and marshals it into a Struct
func MarshalKey(key string, rawVal interface{}) error { return v.MarshalKey(key, rawVal) } func MarshalKey(key string, rawVal interface{}) error { return v.MarshalKey(key, rawVal) }
func (v *Viper) MarshalKey(key string, rawVal interface{}) error { func (v *Viper) MarshalKey(key string, rawVal interface{}) error {
return mapstructure.Decode(v.Get(key), rawVal) return mapstructure.Decode(v.Get(key), rawVal)
} }
// Marshals the config into a Struct // Marshals the config into a Struct. Make sure that the tags
// on the fields of the structure are properly set.
func Marshal(rawVal interface{}) error { return v.Marshal(rawVal) } func Marshal(rawVal interface{}) error { return v.Marshal(rawVal) }
func (v *Viper) Marshal(rawVal interface{}) error { func (v *Viper) Marshal(rawVal interface{}) error {
err := mapstructure.Decode(v.defaults, rawVal) err := mapstructure.WeakDecode(v.AllSettings(), rawVal)
if err != nil {
return err
}
err = mapstructure.Decode(v.config, rawVal)
if err != nil {
return err
}
err = mapstructure.Decode(v.override, rawVal)
if err != nil {
return err
}
err = mapstructure.Decode(v.kvstore, rawVal)
if err != nil { if err != nil {
return err return err
} }
v.insensativiseMaps() v.insensitiviseMaps()
return nil return nil
} }
// Bind a full flag set to the configuration, using each flag's long
// name as the config key.
func BindPFlags(flags *pflag.FlagSet) (err error) { return v.BindPFlags(flags) }
func (v *Viper) BindPFlags(flags *pflag.FlagSet) (err error) {
flags.VisitAll(func(flag *pflag.Flag) {
if err != nil {
// an error has been encountered in one of the previous flags
return
}
err = v.BindPFlag(flag.Name, flag)
switch flag.Value.Type() {
case "int", "int8", "int16", "int32", "int64":
v.SetDefault(flag.Name, cast.ToInt(flag.Value.String()))
case "bool":
v.SetDefault(flag.Name, cast.ToBool(flag.Value.String()))
default:
v.SetDefault(flag.Name, flag.Value.String())
}
})
return
}
// Bind a specific key to a flag (as used by cobra) // Bind a specific key to a flag (as used by cobra)
// Example(where serverCmd is a Cobra instance):
// //
// serverCmd.Flags().Int("port", 1138, "Port to run Application server on") // serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
// viper.BindPFlag("port", serverCmd.Flags().Lookup("port")) // Viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
// //
func BindPFlag(key string, flag *pflag.Flag) (err error) { return v.BindPFlag(key, flag) } func BindPFlag(key string, flag *pflag.Flag) (err error) { return v.BindPFlag(key, flag) }
func (v *Viper) BindPFlag(key string, flag *pflag.Flag) (err error) { func (v *Viper) BindPFlag(key string, flag *pflag.Flag) (err error) {
@ -349,16 +532,16 @@ func (v *Viper) BindPFlag(key string, flag *pflag.Flag) (err error) {
switch flag.Value.Type() { switch flag.Value.Type() {
case "int", "int8", "int16", "int32", "int64": case "int", "int8", "int16", "int32", "int64":
SetDefault(key, cast.ToInt(flag.Value.String())) v.SetDefault(key, cast.ToInt(flag.Value.String()))
case "bool": case "bool":
SetDefault(key, cast.ToBool(flag.Value.String())) v.SetDefault(key, cast.ToBool(flag.Value.String()))
default: default:
SetDefault(key, flag.Value.String()) v.SetDefault(key, flag.Value.String())
} }
return nil return nil
} }
// Binds a viper key to a ENV variable // Binds a Viper key to a ENV variable
// ENV variables are case sensitive // ENV variables are case sensitive
// If only a key is provided, it will use the env key matching the key, uppercased. // If only a key is provided, it will use the env key matching the key, uppercased.
// EnvPrefix will be used when set when env name is not provided. // EnvPrefix will be used when set when env name is not provided.
@ -411,7 +594,7 @@ func (v *Viper) find(key string) interface{} {
if v.automaticEnvApplied { if v.automaticEnvApplied {
// even if it hasn't been registered, if automaticEnv is used, // even if it hasn't been registered, if automaticEnv is used,
// check any Get request // check any Get request
if val = os.Getenv(v.mergeWithEnvPrefix(key)); val != "" { if val = v.getEnv(v.mergeWithEnvPrefix(key)); val != "" {
jww.TRACE.Println(key, "found in environment with val:", val) jww.TRACE.Println(key, "found in environment with val:", val)
return val return val
} }
@ -420,7 +603,7 @@ func (v *Viper) find(key string) interface{} {
envkey, exists := v.env[key] envkey, exists := v.env[key]
if exists { if exists {
jww.TRACE.Println(key, "registered as env var", envkey) jww.TRACE.Println(key, "registered as env var", envkey)
if val = os.Getenv(envkey); val != "" { if val = v.getEnv(envkey); val != "" {
jww.TRACE.Println(envkey, "found in environment with val:", val) jww.TRACE.Println(envkey, "found in environment with val:", val)
return val return val
} else { } else {
@ -456,13 +639,21 @@ func (v *Viper) IsSet(key string) bool {
return t != nil return t != nil
} }
// Have viper check ENV variables for all // Have Viper check ENV variables for all
// keys set in config, default & flags // keys set in config, default & flags
func AutomaticEnv() { v.AutomaticEnv() } func AutomaticEnv() { v.AutomaticEnv() }
func (v *Viper) AutomaticEnv() { func (v *Viper) AutomaticEnv() {
v.automaticEnvApplied = true v.automaticEnvApplied = true
} }
// SetEnvKeyReplacer sets the strings.Replacer on the viper object
// Useful for mapping an environmental variable to a key that does
// not match it.
func SetEnvKeyReplacer(r *strings.Replacer) { v.SetEnvKeyReplacer(r) }
func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer) {
v.envKeyReplacer = r
}
// Aliases provide another accessor for the same key. // Aliases provide another accessor for the same key.
// This enables one to change a name without breaking the application // This enables one to change a name without breaking the application
func RegisterAlias(alias string, key string) { v.RegisterAlias(alias, key) } func RegisterAlias(alias string, key string) { v.RegisterAlias(alias, key) }
@ -531,9 +722,9 @@ func (v *Viper) SetDefault(key string, value interface{}) {
v.defaults[key] = value v.defaults[key] = value
} }
// The user provided value (via flag) // Sets the value for the key in the override regiser.
// Will be used instead of values obtained via // Will be used instead of values obtained via
// config file, ENV, default, or key/value store // flags, config file, ENV, default, or key/value store
func Set(key string, value interface{}) { v.Set(key, value) } func Set(key string, value interface{}) { v.Set(key, value) }
func (v *Viper) Set(key string, value interface{}) { func (v *Viper) Set(key string, value interface{}) {
// If alias passed in, then set the proper override // If alias passed in, then set the proper override
@ -555,8 +746,15 @@ func (v *Viper) ReadInConfig() error {
return err return err
} }
v.marshalReader(bytes.NewReader(file), v.config) v.config = make(map[string]interface{})
return nil
return v.marshalReader(bytes.NewReader(file), v.config)
}
func ReadConfig(in io.Reader) error { return v.ReadConfig(in) }
func (v *Viper) ReadConfig(in io.Reader) error {
v.config = make(map[string]interface{})
return v.marshalReader(in, v.config)
} }
// ReadInRawConfig reads the given raw bytes to load configuration. It is // ReadInRawConfig reads the given raw bytes to load configuration. It is
@ -568,6 +766,14 @@ func (v *Viper) ReadInRawConfig(raw []byte) error {
return nil return nil
} }
// func ReadBufConfig(buf *bytes.Buffer) error { return v.ReadBufConfig(buf) }
// func (v *Viper) ReadBufConfig(buf *bytes.Buffer) error {
// v.config = make(map[string]interface{})
// return v.marshalReader(buf, v.config)
// }
// Attempts to get configuration from a remote source
// and read it in the remote configuration registry.
func ReadRemoteConfig() error { return v.ReadRemoteConfig() } func ReadRemoteConfig() error { return v.ReadRemoteConfig() }
func (v *Viper) ReadRemoteConfig() error { func (v *Viper) ReadRemoteConfig() error {
err := v.getKeyValueConfig() err := v.getKeyValueConfig()
@ -577,22 +783,38 @@ func (v *Viper) ReadRemoteConfig() error {
return nil return nil
} }
// Marshall a Reader into a map func WatchRemoteConfig() error { return v.WatchRemoteConfig() }
// Should probably be an unexported function func (v *Viper) WatchRemoteConfig() error {
func marshalReader(in io.Reader, c map[string]interface{}) { v.marshalReader(in, c) } err := v.watchKeyValueConfig()
func (v *Viper) marshalReader(in io.Reader, c map[string]interface{}) { if err != nil {
marshallConfigReader(in, c, v.getConfigType()) return err
}
return nil
} }
func (v *Viper) insensativiseMaps() { // Marshall a Reader into a map
insensativiseMap(v.config) // Should probably be an unexported function
insensativiseMap(v.defaults) func marshalReader(in io.Reader, c map[string]interface{}) error {
insensativiseMap(v.override) return v.marshalReader(in, c)
insensativiseMap(v.kvstore) }
func (v *Viper) marshalReader(in io.Reader, c map[string]interface{}) error {
return marshallConfigReader(in, c, v.getConfigType())
}
func (v *Viper) insensitiviseMaps() {
insensitiviseMap(v.config)
insensitiviseMap(v.defaults)
insensitiviseMap(v.override)
insensitiviseMap(v.kvstore)
} }
// retrieve the first found remote configuration // retrieve the first found remote configuration
func (v *Viper) getKeyValueConfig() error { func (v *Viper) getKeyValueConfig() error {
if RemoteConfig == nil {
return RemoteConfigError("Enable the remote features by doing a blank import of the viper/remote package: '_ github.com/spf13/viper/remote'")
}
for _, rp := range v.remoteProviders { for _, rp := range v.remoteProviders {
val, err := v.getRemoteConfig(rp) val, err := v.getRemoteConfig(rp)
if err != nil { if err != nil {
@ -604,37 +826,35 @@ func (v *Viper) getKeyValueConfig() error {
return RemoteConfigError("No Files Found") return RemoteConfigError("No Files Found")
} }
func (v *Viper) getRemoteConfig(provider *remoteProvider) (map[string]interface{}, error) { func (v *Viper) getRemoteConfig(provider *defaultRemoteProvider) (map[string]interface{}, error) {
var cm crypt.ConfigManager
var err error
if provider.secretKeyring != "" { reader, err := RemoteConfig.Get(provider)
kr, err := os.Open(provider.secretKeyring)
defer kr.Close()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if provider.provider == "etcd" { err = v.marshalReader(reader, v.kvstore)
cm, err = crypt.NewEtcdConfigManager([]string{provider.endpoint}, kr) return v.kvstore, err
} else {
cm, err = crypt.NewConsulConfigManager([]string{provider.endpoint}, kr)
} }
} else {
if provider.provider == "etcd" { // retrieve the first found remote configuration
cm, err = crypt.NewStandardEtcdConfigManager([]string{provider.endpoint}) func (v *Viper) watchKeyValueConfig() error {
} else { for _, rp := range v.remoteProviders {
cm, err = crypt.NewStandardConsulConfigManager([]string{provider.endpoint}) val, err := v.watchRemoteConfig(rp)
if err != nil {
continue
} }
v.kvstore = val
return nil
} }
return RemoteConfigError("No Files Found")
}
func (v *Viper) watchRemoteConfig(provider *defaultRemoteProvider) (map[string]interface{}, error) {
reader, err := RemoteConfig.Watch(provider)
if err != nil { if err != nil {
return nil, err return nil, err
} }
b, err := cm.Get(provider.path) err = v.marshalReader(reader, v.kvstore)
if err != nil {
return nil, err
}
reader := bytes.NewReader(b)
v.marshalReader(reader, v.kvstore)
return v.kvstore, err return v.kvstore, err
} }
@ -687,6 +907,8 @@ func (v *Viper) SetConfigName(in string) {
} }
} }
// Sets the type of the configuration returned by the
// remote source, e.g. "json".
func SetConfigType(in string) { v.SetConfigType(in) } func SetConfigType(in string) { v.SetConfigType(in) }
func (v *Viper) SetConfigType(in string) { func (v *Viper) SetConfigType(in string) {
if in != "" { if in != "" {
@ -740,6 +962,7 @@ func (v *Viper) searchInPath(in string) (filename string) {
// search all configPaths for any config file. // search all configPaths for any config file.
// Returns the first path that exists (and is a config file) // Returns the first path that exists (and is a config file)
func (v *Viper) findConfigFile() (string, error) { func (v *Viper) findConfigFile() (string, error) {
jww.INFO.Println("Searching for config in ", v.configPaths) jww.INFO.Println("Searching for config in ", v.configPaths)
for _, cp := range v.configPaths { for _, cp := range v.configPaths {
@ -748,34 +971,25 @@ func (v *Viper) findConfigFile() (string, error) {
return file, nil return file, nil
} }
} }
return "", ConfigFileNotFoundError{v.configName, fmt.Sprintf("%s", v.configPaths)}
cwd, _ := findCWD()
file := v.searchInPath(cwd)
if file != "" {
return file, nil
}
// try the current working directory
wd, _ := os.Getwd()
file = v.searchInPath(wd)
if file != "" {
return file, nil
}
return "", fmt.Errorf("config file not found in: %s", v.configPaths)
} }
// Prints all configuration registries for debugging
// purposes.
func Debug() { v.Debug() } func Debug() { v.Debug() }
func (v *Viper) Debug() { func (v *Viper) Debug() {
fmt.Println("Config:")
pretty.Println(v.config)
fmt.Println("Key/Value Store:")
pretty.Println(v.kvstore)
fmt.Println("Env:")
pretty.Println(v.env)
fmt.Println("Defaults:")
pretty.Println(v.defaults)
fmt.Println("Override:")
pretty.Println(v.override)
fmt.Println("Aliases:") fmt.Println("Aliases:")
pretty.Println(v.aliases) pretty.Println(v.aliases)
fmt.Println("Override:")
pretty.Println(v.override)
fmt.Println("PFlags")
pretty.Println(v.pflags)
fmt.Println("Env:")
pretty.Println(v.env)
fmt.Println("Key/Value Store:")
pretty.Println(v.kvstore)
fmt.Println("Config:")
pretty.Println(v.config)
fmt.Println("Defaults:")
pretty.Println(v.defaults)
} }

View file

@ -8,8 +8,12 @@ package viper
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path"
"reflect"
"sort" "sort"
"strings"
"testing" "testing"
"time" "time"
@ -26,6 +30,8 @@ hobbies:
clothing: clothing:
jacket: leather jacket: leather
trousers: denim trousers: denim
pants:
size: large
age: 35 age: 35
eyes : brown eyes : brown
beard: true beard: true
@ -54,12 +60,13 @@ var jsonExample = []byte(`{
} }
}`) }`)
// Intended for testing, will reset all to default settings. var propertiesExample = []byte(`
func Reset() { p_id: 0001
v = New() p_type: donut
SupportedExts = []string{"json", "toml", "yaml", "yml"} p_name: Cake
SupportedRemoteProviders = []string{"etcd", "consul"} p_ppu: 0.55
} p_batters.batter.type: Regular
`)
var remoteExample = []byte(`{ var remoteExample = []byte(`{
"id":"0002", "id":"0002",
@ -77,6 +84,10 @@ func initConfigs() {
r = bytes.NewReader(jsonExample) r = bytes.NewReader(jsonExample)
marshalReader(r, v.config) marshalReader(r, v.config)
SetConfigType("properties")
r = bytes.NewReader(propertiesExample)
marshalReader(r, v.config)
SetConfigType("toml") SetConfigType("toml")
r = bytes.NewReader(tomlExample) r = bytes.NewReader(tomlExample)
marshalReader(r, v.config) marshalReader(r, v.config)
@ -102,6 +113,14 @@ func initJSON() {
marshalReader(r, v.config) marshalReader(r, v.config)
} }
func initProperties() {
Reset()
SetConfigType("properties")
r := bytes.NewReader(propertiesExample)
marshalReader(r, v.config)
}
func initTOML() { func initTOML() {
Reset() Reset()
SetConfigType("toml") SetConfigType("toml")
@ -110,6 +129,47 @@ func initTOML() {
marshalReader(r, v.config) marshalReader(r, v.config)
} }
// make directories for testing
func initDirs(t *testing.T) (string, string, func()) {
var (
testDirs = []string{`a a`, `b`, `c\c`, `D_`}
config = `improbable`
)
root, err := ioutil.TempDir("", "")
cleanup := true
defer func() {
if cleanup {
os.Chdir("..")
os.RemoveAll(root)
}
}()
assert.Nil(t, err)
err = os.Chdir(root)
assert.Nil(t, err)
for _, dir := range testDirs {
err = os.Mkdir(dir, 0750)
assert.Nil(t, err)
err = ioutil.WriteFile(
path.Join(dir, config+".toml"),
[]byte("key = \"value is "+dir+"\"\n"),
0640)
assert.Nil(t, err)
}
cleanup = false
return root, config, func() {
os.Chdir("..")
os.RemoveAll(root)
}
}
//stubs for PFlag Values //stubs for PFlag Values
type stringValue string type stringValue string
@ -150,7 +210,7 @@ func TestMarshalling(t *testing.T) {
assert.False(t, InConfig("state")) assert.False(t, InConfig("state"))
assert.Equal(t, "steve", Get("name")) assert.Equal(t, "steve", Get("name"))
assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, Get("hobbies")) assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, Get("hobbies"))
assert.Equal(t, map[interface{}]interface{}{"jacket": "leather", "trousers": "denim"}, Get("clothing")) assert.Equal(t, map[interface{}]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[interface{}]interface{}{"size": "large"}}, Get("clothing"))
assert.Equal(t, 35, Get("age")) assert.Equal(t, 35, Get("age"))
} }
@ -191,6 +251,11 @@ func TestJSON(t *testing.T) {
assert.Equal(t, "0001", Get("id")) assert.Equal(t, "0001", Get("id"))
} }
func TestProperties(t *testing.T) {
initProperties()
assert.Equal(t, "0001", Get("p_id"))
}
func TestTOML(t *testing.T) { func TestTOML(t *testing.T) {
initTOML() initTOML()
assert.Equal(t, "TOML Example", Get("title")) assert.Equal(t, "TOML Example", Get("title"))
@ -228,6 +293,7 @@ func TestEnv(t *testing.T) {
AutomaticEnv() AutomaticEnv()
assert.Equal(t, "crunk", Get("name")) assert.Equal(t, "crunk", Get("name"))
} }
func TestEnvPrefix(t *testing.T) { func TestEnvPrefix(t *testing.T) {
@ -250,12 +316,41 @@ func TestEnvPrefix(t *testing.T) {
assert.Equal(t, "crunk", Get("name")) assert.Equal(t, "crunk", Get("name"))
} }
func TestAutoEnv(t *testing.T) {
Reset()
AutomaticEnv()
os.Setenv("FOO_BAR", "13")
assert.Equal(t, "13", Get("foo_bar"))
}
func TestAutoEnvWithPrefix(t *testing.T) {
Reset()
AutomaticEnv()
SetEnvPrefix("Baz")
os.Setenv("BAZ_BAR", "13")
assert.Equal(t, "13", Get("bar"))
}
func TestSetEnvReplacer(t *testing.T) {
Reset()
AutomaticEnv()
os.Setenv("REFRESH_INTERVAL", "30s")
replacer := strings.NewReplacer("-", "_")
SetEnvKeyReplacer(replacer)
assert.Equal(t, "30s", Get("refresh-interval"))
}
func TestAllKeys(t *testing.T) { func TestAllKeys(t *testing.T) {
initConfigs() initConfigs()
ks := sort.StringSlice{"title", "newkey", "owner", "name", "beard", "ppu", "batters", "hobbies", "clothing", "age", "hacker", "id", "type", "eyes"} ks := sort.StringSlice{"title", "newkey", "owner", "name", "beard", "ppu", "batters", "hobbies", "clothing", "age", "hacker", "id", "type", "eyes", "p_id", "p_ppu", "p_batters.batter.type", "p_type", "p_name"}
dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z")
all := map[string]interface{}{"owner": map[string]interface{}{"organization": "MongoDB", "Bio": "MongoDB Chief Developer Advocate & Hacker at Large", "dob": dob}, "title": "TOML Example", "ppu": 0.55, "eyes": "brown", "clothing": map[interface{}]interface{}{"trousers": "denim", "jacket": "leather"}, "id": "0001", "batters": map[string]interface{}{"batter": []interface{}{map[string]interface{}{"type": "Regular"}, map[string]interface{}{"type": "Chocolate"}, map[string]interface{}{"type": "Blueberry"}, map[string]interface{}{"type": "Devil's Food"}}}, "hacker": true, "beard": true, "hobbies": []interface{}{"skateboarding", "snowboarding", "go"}, "age": 35, "type": "donut", "newkey": "remote", "name": "Cake"} all := map[string]interface{}{"owner": map[string]interface{}{"organization": "MongoDB", "Bio": "MongoDB Chief Developer Advocate & Hacker at Large", "dob": dob}, "title": "TOML Example", "ppu": 0.55, "eyes": "brown", "clothing": map[interface{}]interface{}{"trousers": "denim", "jacket": "leather", "pants": map[interface{}]interface{}{"size": "large"}}, "id": "0001", "batters": map[string]interface{}{"batter": []interface{}{map[string]interface{}{"type": "Regular"}, map[string]interface{}{"type": "Chocolate"}, map[string]interface{}{"type": "Blueberry"}, map[string]interface{}{"type": "Devil's Food"}}}, "hacker": true, "beard": true, "hobbies": []interface{}{"skateboarding", "snowboarding", "go"}, "age": 35, "type": "donut", "newkey": "remote", "name": "Cake", "p_id": "0001", "p_ppu": "0.55", "p_name": "Cake", "p_batters.batter.type": "Regular", "p_type": "donut"}
var allkeys sort.StringSlice var allkeys sort.StringSlice
allkeys = AllKeys() allkeys = AllKeys()
@ -309,6 +404,41 @@ func TestMarshal(t *testing.T) {
assert.Equal(t, &C, &config{Name: "Steve", Port: 1234}) assert.Equal(t, &C, &config{Name: "Steve", Port: 1234})
} }
func TestBindPFlags(t *testing.T) {
flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
var testValues = map[string]*string{
"host": nil,
"port": nil,
"endpoint": nil,
}
var mutatedTestValues = map[string]string{
"host": "localhost",
"port": "6060",
"endpoint": "/public",
}
for name, _ := range testValues {
testValues[name] = flagSet.String(name, "", "test")
}
err := BindPFlags(flagSet)
if err != nil {
t.Fatalf("error binding flag set, %v", err)
}
flagSet.VisitAll(func(flag *pflag.Flag) {
flag.Value.Set(mutatedTestValues[flag.Name])
flag.Changed = true
})
for name, expected := range mutatedTestValues {
assert.Equal(t, Get(name), expected)
}
}
func TestBindPFlag(t *testing.T) { func TestBindPFlag(t *testing.T) {
var testString = "testing" var testString = "testing"
var testValue = newStringValue(testString, &testString) var testValue = newStringValue(testString, &testString)
@ -352,3 +482,164 @@ func TestBoundCaseSensitivity(t *testing.T) {
assert.Equal(t, "green", Get("eyes")) assert.Equal(t, "green", Get("eyes"))
} }
func TestSizeInBytes(t *testing.T) {
input := map[string]uint{
"": 0,
"b": 0,
"12 bytes": 0,
"200000000000gb": 0,
"12 b": 12,
"43 MB": 43 * (1 << 20),
"10mb": 10 * (1 << 20),
"1gb": 1 << 30,
}
for str, expected := range input {
assert.Equal(t, expected, parseSizeInBytes(str), str)
}
}
func TestFindsNestedKeys(t *testing.T) {
initConfigs()
dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z")
Set("super", map[string]interface{}{
"deep": map[string]interface{}{
"nested": "value",
},
})
expected := map[string]interface{}{
"super": map[string]interface{}{
"deep": map[string]interface{}{
"nested": "value",
},
},
"super.deep": map[string]interface{}{
"nested": "value",
},
"super.deep.nested": "value",
"owner.organization": "MongoDB",
"batters.batter": []interface{}{
map[string]interface{}{
"type": "Regular",
},
map[string]interface{}{
"type": "Chocolate",
},
map[string]interface{}{
"type": "Blueberry",
},
map[string]interface{}{
"type": "Devil's Food",
},
},
"hobbies": []interface{}{
"skateboarding", "snowboarding", "go",
},
"title": "TOML Example",
"newkey": "remote",
"batters": map[string]interface{}{
"batter": []interface{}{
map[string]interface{}{
"type": "Regular",
},
map[string]interface{}{
"type": "Chocolate",
}, map[string]interface{}{
"type": "Blueberry",
}, map[string]interface{}{
"type": "Devil's Food",
},
},
},
"eyes": "brown",
"age": 35,
"owner": map[string]interface{}{
"organization": "MongoDB",
"Bio": "MongoDB Chief Developer Advocate & Hacker at Large",
"dob": dob,
},
"owner.Bio": "MongoDB Chief Developer Advocate & Hacker at Large",
"type": "donut",
"id": "0001",
"name": "Cake",
"hacker": true,
"ppu": 0.55,
"clothing": map[interface{}]interface{}{
"jacket": "leather",
"trousers": "denim",
"pants": map[interface{}]interface{}{
"size": "large",
},
},
"clothing.jacket": "leather",
"clothing.pants.size": "large",
"clothing.trousers": "denim",
"owner.dob": dob,
"beard": true,
}
for key, expectedValue := range expected {
assert.Equal(t, expectedValue, v.Get(key))
}
}
func TestReadBufConfig(t *testing.T) {
v := New()
v.SetConfigType("yaml")
v.ReadConfig(bytes.NewBuffer(yamlExample))
t.Log(v.AllKeys())
assert.True(t, v.InConfig("name"))
assert.False(t, v.InConfig("state"))
assert.Equal(t, "steve", v.Get("name"))
assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, v.Get("hobbies"))
assert.Equal(t, map[interface{}]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[interface{}]interface{}{"size": "large"}}, v.Get("clothing"))
assert.Equal(t, 35, v.Get("age"))
}
func TestDirsSearch(t *testing.T) {
root, config, cleanup := initDirs(t)
defer cleanup()
v := New()
v.SetConfigName(config)
v.SetDefault(`key`, `default`)
entries, err := ioutil.ReadDir(root)
for _, e := range entries {
if e.IsDir() {
v.AddConfigPath(e.Name())
}
}
err = v.ReadInConfig()
assert.Nil(t, err)
assert.Equal(t, `value is `+path.Base(v.configPaths[0]), v.GetString(`key`))
}
func TestWrongDirsSearchNotFound(t *testing.T) {
_, config, cleanup := initDirs(t)
defer cleanup()
v := New()
v.SetConfigName(config)
v.SetDefault(`key`, `default`)
v.AddConfigPath(`whattayoutalkingbout`)
v.AddConfigPath(`thispathaintthere`)
err := v.ReadInConfig()
assert.Equal(t, reflect.TypeOf(UnsupportedConfigError("")), reflect.TypeOf(err))
// Even though config did not load and the error might have
// been ignored by the client, the default still loads
assert.Equal(t, `default`, v.GetString(`key`))
}