mirror of
https://github.com/spf13/viper.git
synced 2024-04-21 12:31:38 +00:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25b30aa063 | ||
|
|
8ac2e2e20f | ||
|
|
f257d19100 | ||
|
|
04691bc570 | ||
|
|
9766537120 | ||
|
|
16a9458573 | ||
|
|
df7314a14e | ||
|
|
c1de95864d | ||
|
|
a1ecfa6a20 | ||
|
|
0967fc9ace | ||
|
|
5d46e70da8 | ||
|
|
84f94806c6 | ||
|
|
0b5690fd87 |
@@ -17,6 +17,7 @@ matrix:
|
||||
|
||||
script:
|
||||
- go install ./...
|
||||
- diff -u <(echo -n) <(gofmt -d .)
|
||||
- go test -v ./...
|
||||
|
||||
after_success:
|
||||
|
||||
@@ -6,18 +6,19 @@ 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)
|
||||
* [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)
|
||||
|
||||
[](https://travis-ci.org/spf13/viper) [](https://gitter.im/spf13/viper?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://godoc.org/github.com/spf13/viper)
|
||||
|
||||
|
||||
## What is Viper?
|
||||
|
||||
Viper is a complete configuration solution for go applications including 12 factor apps. It is designed
|
||||
Viper is a complete configuration solution for Go applications including 12-Factor apps. It is designed
|
||||
to work within an application, and can handle all types of configuration needs
|
||||
and formats. It supports:
|
||||
|
||||
@@ -68,7 +69,7 @@ Viper configuration keys are case insensitive.
|
||||
### 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
|
||||
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:
|
||||
@@ -116,10 +117,10 @@ 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.WatchConfig()
|
||||
viper.OnConfigChange(func(e fsnotify.Event) {
|
||||
fmt.Println("Config file changed:", e.Name)
|
||||
})
|
||||
viper.WatchConfig()
|
||||
viper.OnConfigChange(func(e fsnotify.Event) {
|
||||
fmt.Println("Config file changed:", e.Name)
|
||||
})
|
||||
```
|
||||
|
||||
### Reading Config from io.Reader
|
||||
@@ -236,7 +237,7 @@ 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.
|
||||
|
||||
The `BindPFlag()` method provides this functionality.
|
||||
For individual flags, the `BindPFlag()` method provides this functionality.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -245,6 +246,19 @@ 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
|
||||
@@ -263,15 +277,23 @@ import (
|
||||
)
|
||||
|
||||
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`.
|
||||
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:
|
||||
|
||||
@@ -401,7 +423,7 @@ go func(){
|
||||
|
||||
## Getting Values From Viper
|
||||
|
||||
In Viper, there are a few ways to get a value depending on the value's type.
|
||||
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) : interface{}`
|
||||
@@ -531,7 +553,7 @@ func NewCache(cfg *Viper) *Cache {...}
|
||||
```
|
||||
|
||||
which creates a cache based on config information formatted as `subv`.
|
||||
Now it's easy to create these 2 caches separately as:
|
||||
Now it’s easy to create these 2 caches separately as:
|
||||
|
||||
```go
|
||||
cfg1 := viper.Sub("app.cache1")
|
||||
@@ -575,13 +597,13 @@ 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 it's 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 it’s own unique set of configurations and values. Each can read from a
|
||||
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.
|
||||
|
||||
|
||||
@@ -62,5 +62,4 @@ func TestBindFlagValue(t *testing.T) {
|
||||
flag.Changed = true //hack for pflag usage
|
||||
|
||||
assert.Equal(t, "testing_mutate", Get("testvalue"))
|
||||
|
||||
}
|
||||
|
||||
+24
-6
@@ -33,26 +33,45 @@ func (rc remoteConfigProvider) Watch(rp viper.RemoteProvider) (io.Reader, error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp,err := cm.Get(rp.Path())
|
||||
resp, err := cm.Get(rp.Path())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bytes.NewReader(resp), nil
|
||||
}
|
||||
func (rc remoteConfigProvider) WatchChannel(rp viper.RemoteProvider) (<-chan *crypt.Response, chan bool) {
|
||||
|
||||
func (rc remoteConfigProvider) WatchChannel(rp viper.RemoteProvider) (<-chan *viper.RemoteResponse, chan bool) {
|
||||
cm, err := getConfigManager(rp)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
quit := make(chan bool)
|
||||
return cm.Watch(rp.Path(), quit) ,quit
|
||||
quitwc := make(chan bool)
|
||||
viperResponsCh := make(chan *viper.RemoteResponse)
|
||||
cryptoResponseCh := cm.Watch(rp.Path(), quit)
|
||||
// need this function to convert the Channel response form crypt.Response to viper.Response
|
||||
go func(cr <-chan *crypt.Response, vr chan<- *viper.RemoteResponse, quitwc <-chan bool, quit chan<- bool) {
|
||||
for {
|
||||
select {
|
||||
case <-quitwc:
|
||||
quit <- true
|
||||
return
|
||||
case resp := <-cr:
|
||||
vr <- &viper.RemoteResponse{
|
||||
Error: resp.Error,
|
||||
Value: resp.Value,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}(cryptoResponseCh, viperResponsCh, quitwc, quit)
|
||||
|
||||
return viperResponsCh, quitwc
|
||||
}
|
||||
|
||||
|
||||
func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) {
|
||||
|
||||
var cm crypt.ConfigManager
|
||||
var err error
|
||||
|
||||
@@ -78,7 +97,6 @@ func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) {
|
||||
return nil, err
|
||||
}
|
||||
return cm, nil
|
||||
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
)
|
||||
|
||||
func TestCopyAndInsensitiviseMap(t *testing.T) {
|
||||
|
||||
var (
|
||||
given = map[string]interface{}{
|
||||
"Foo": 32,
|
||||
|
||||
@@ -21,6 +21,7 @@ package viper
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
@@ -36,11 +37,15 @@ import (
|
||||
"github.com/spf13/cast"
|
||||
jww "github.com/spf13/jwalterweatherman"
|
||||
"github.com/spf13/pflag"
|
||||
crypt "github.com/xordataexchange/crypt/config"
|
||||
)
|
||||
|
||||
var v *Viper
|
||||
|
||||
type RemoteResponse struct {
|
||||
Value []byte
|
||||
Error error
|
||||
}
|
||||
|
||||
func init() {
|
||||
v = New()
|
||||
}
|
||||
@@ -48,7 +53,7 @@ func init() {
|
||||
type remoteConfigFactory interface {
|
||||
Get(rp RemoteProvider) (io.Reader, error)
|
||||
Watch(rp RemoteProvider) (io.Reader, error)
|
||||
WatchChannel(rp RemoteProvider)(<-chan *crypt.Response, chan bool)
|
||||
WatchChannel(rp RemoteProvider) (<-chan *RemoteResponse, chan bool)
|
||||
}
|
||||
|
||||
// RemoteConfig is optional, see the remote package
|
||||
@@ -64,8 +69,7 @@ func (str UnsupportedConfigError) Error() string {
|
||||
}
|
||||
|
||||
// UnsupportedRemoteProviderError denotes encountering an unsupported remote
|
||||
// provider. Currently only etcd and Consul are
|
||||
// supported.
|
||||
// provider. Currently only etcd and Consul are supported.
|
||||
type UnsupportedRemoteProviderError string
|
||||
|
||||
// Error returns the formatted remote provider error.
|
||||
@@ -278,8 +282,8 @@ func (v *Viper) WatchConfig() {
|
||||
}()
|
||||
}
|
||||
|
||||
// SetConfigFile explicitly defines the path, name and extension of the config file
|
||||
// Viper will use this and not check any of the config paths
|
||||
// SetConfigFile explicitly defines the path, name and extension of the config file.
|
||||
// Viper will use this and not check any of the config paths.
|
||||
func SetConfigFile(in string) { v.SetConfigFile(in) }
|
||||
func (v *Viper) SetConfigFile(in string) {
|
||||
if in != "" {
|
||||
@@ -288,8 +292,8 @@ func (v *Viper) SetConfigFile(in string) {
|
||||
}
|
||||
|
||||
// SetEnvPrefix defines 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_"
|
||||
// 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 (v *Viper) SetEnvPrefix(in string) {
|
||||
if in != "" {
|
||||
@@ -307,11 +311,11 @@ func (v *Viper) mergeWithEnvPrefix(in string) string {
|
||||
|
||||
// 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)
|
||||
// (camel case to snake case for JSON keys perhaps)
|
||||
|
||||
// getEnv is 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
|
||||
// key. This allows env vars which have different keys than the config object
|
||||
// keys.
|
||||
func (v *Viper) getEnv(key string) string {
|
||||
if v.envKeyReplacer != nil {
|
||||
key = v.envKeyReplacer.Replace(key)
|
||||
@@ -319,7 +323,7 @@ func (v *Viper) getEnv(key string) string {
|
||||
return os.Getenv(key)
|
||||
}
|
||||
|
||||
// ConfigFileUsed returns the file used to populate the config registry
|
||||
// ConfigFileUsed returns the file used to populate the config registry.
|
||||
func ConfigFileUsed() string { return v.ConfigFileUsed() }
|
||||
func (v *Viper) ConfigFileUsed() string { return v.configFile }
|
||||
|
||||
@@ -592,32 +596,33 @@ func (v *Viper) Get(key string) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
valType := val
|
||||
if v.typeByDefValue {
|
||||
// TODO(bep) this branch isn't covered by a single test.
|
||||
valType := val
|
||||
path := strings.Split(lcaseKey, v.keyDelim)
|
||||
defVal := v.searchMap(v.defaults, path)
|
||||
if defVal != nil {
|
||||
valType = defVal
|
||||
}
|
||||
|
||||
switch valType.(type) {
|
||||
case bool:
|
||||
return cast.ToBool(val)
|
||||
case string:
|
||||
return cast.ToString(val)
|
||||
case int64, int32, int16, int8, int:
|
||||
return cast.ToInt(val)
|
||||
case float64, float32:
|
||||
return cast.ToFloat64(val)
|
||||
case time.Time:
|
||||
return cast.ToTime(val)
|
||||
case time.Duration:
|
||||
return cast.ToDuration(val)
|
||||
case []string:
|
||||
return cast.ToStringSlice(val)
|
||||
}
|
||||
}
|
||||
|
||||
switch valType.(type) {
|
||||
case bool:
|
||||
return cast.ToBool(val)
|
||||
case string:
|
||||
return cast.ToString(val)
|
||||
case int64, int32, int16, int8, int:
|
||||
return cast.ToInt(val)
|
||||
case float64, float32:
|
||||
return cast.ToFloat64(val)
|
||||
case time.Time:
|
||||
return cast.ToTime(val)
|
||||
case time.Duration:
|
||||
return cast.ToDuration(val)
|
||||
case []string:
|
||||
return cast.ToStringSlice(val)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
@@ -715,7 +720,15 @@ func (v *Viper) GetSizeInBytes(key string) uint {
|
||||
// UnmarshalKey takes a single key and unmarshals it into a Struct.
|
||||
func UnmarshalKey(key string, rawVal interface{}) error { return v.UnmarshalKey(key, rawVal) }
|
||||
func (v *Viper) UnmarshalKey(key string, rawVal interface{}) error {
|
||||
return mapstructure.Decode(v.Get(key), rawVal)
|
||||
err := decode(v.Get(key), defaultDecoderConfig(rawVal))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v.insensitiviseMaps()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unmarshal unmarshals the config into a Struct. Make sure that the tags
|
||||
@@ -801,7 +814,7 @@ func (v *Viper) BindFlagValues(flags FlagValueSet) (err error) {
|
||||
}
|
||||
|
||||
// BindFlagValue binds a specific key to a FlagValue.
|
||||
// Example(where serverCmd is a Cobra instance):
|
||||
// Example (where serverCmd is a Cobra instance):
|
||||
//
|
||||
// serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
|
||||
// Viper.BindFlagValue("port", serverCmd.Flags().Lookup("port"))
|
||||
@@ -882,7 +895,9 @@ func (v *Viper) find(lcaseKey string) interface{} {
|
||||
return cast.ToBool(flag.ValueString())
|
||||
case "stringSlice":
|
||||
s := strings.TrimPrefix(flag.ValueString(), "[")
|
||||
return strings.TrimSuffix(s, "]")
|
||||
s = strings.TrimSuffix(s, "]")
|
||||
res, _ := readAsCSV(s)
|
||||
return res
|
||||
default:
|
||||
return flag.ValueString()
|
||||
}
|
||||
@@ -949,7 +964,9 @@ func (v *Viper) find(lcaseKey string) interface{} {
|
||||
return cast.ToBool(flag.ValueString())
|
||||
case "stringSlice":
|
||||
s := strings.TrimPrefix(flag.ValueString(), "[")
|
||||
return strings.TrimSuffix(s, "]")
|
||||
s = strings.TrimSuffix(s, "]")
|
||||
res, _ := readAsCSV(s)
|
||||
return res
|
||||
default:
|
||||
return flag.ValueString()
|
||||
}
|
||||
@@ -959,6 +976,15 @@ func (v *Viper) find(lcaseKey string) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func readAsCSV(val string) ([]string, error) {
|
||||
if val == "" {
|
||||
return []string{}, nil
|
||||
}
|
||||
stringReader := strings.NewReader(val)
|
||||
csvReader := csv.NewReader(stringReader)
|
||||
return csvReader.Read()
|
||||
}
|
||||
|
||||
// IsSet checks to see if the key has been set in any of the data locations.
|
||||
// IsSet is case-insensitive for a key.
|
||||
func IsSet(key string) bool { return v.IsSet(key) }
|
||||
@@ -1261,7 +1287,7 @@ func (v *Viper) WatchRemoteConfigOnChannel() error {
|
||||
return v.watchKeyValueConfigOnChannel()
|
||||
}
|
||||
|
||||
// Unmarshall a Reader into a map.
|
||||
// Unmarshal a Reader into a map.
|
||||
// Should probably be an unexported function.
|
||||
func unmarshalReader(in io.Reader, c map[string]interface{}) error {
|
||||
return v.unmarshalReader(in, c)
|
||||
@@ -1309,7 +1335,7 @@ func (v *Viper) watchKeyValueConfigOnChannel() error {
|
||||
for _, rp := range v.remoteProviders {
|
||||
respc, _ := RemoteConfig.WatchChannel(rp)
|
||||
//Todo: Add quit channel
|
||||
go func(rc <-chan *crypt.Response) {
|
||||
go func(rc <-chan *RemoteResponse) {
|
||||
for {
|
||||
b := <-rc
|
||||
reader := bytes.NewReader(b.Value)
|
||||
@@ -1520,7 +1546,6 @@ func (v *Viper) searchInPath(in string) (filename string) {
|
||||
// Search all configPaths for any config file.
|
||||
// Returns the first path that exists (and is a config file).
|
||||
func (v *Viper) findConfigFile() (string, error) {
|
||||
|
||||
jww.INFO.Println("Searching for config in ", v.configPaths)
|
||||
|
||||
for _, cp := range v.configPaths {
|
||||
|
||||
@@ -538,6 +538,44 @@ func TestBindPFlags(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestBindPFlagsStringSlice(t *testing.T) {
|
||||
for _, testValue := range []struct {
|
||||
Expected []string
|
||||
Value string
|
||||
}{
|
||||
{[]string{}, ""},
|
||||
{[]string{"jeden"}, "jeden"},
|
||||
{[]string{"dwa", "trzy"}, "dwa,trzy"},
|
||||
{[]string{"cztery", "piec , szesc"}, "cztery,\"piec , szesc\""}} {
|
||||
|
||||
for _, changed := range []bool{true, false} {
|
||||
v := New() // create independent Viper object
|
||||
flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||
flagSet.StringSlice("stringslice", testValue.Expected, "test")
|
||||
flagSet.Visit(func(f *pflag.Flag) {
|
||||
if len(testValue.Value) > 0 {
|
||||
f.Value.Set(testValue.Value)
|
||||
f.Changed = changed
|
||||
}
|
||||
})
|
||||
|
||||
err := v.BindPFlags(flagSet)
|
||||
if err != nil {
|
||||
t.Fatalf("error binding flag set, %v", err)
|
||||
}
|
||||
|
||||
type TestStr struct {
|
||||
StringSlice []string
|
||||
}
|
||||
val := &TestStr{}
|
||||
if err := v.Unmarshal(val); err != nil {
|
||||
t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err)
|
||||
}
|
||||
assert.Equal(t, testValue.Expected, val.StringSlice)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBindPFlag(t *testing.T) {
|
||||
var testString = "testing"
|
||||
var testValue = newStringValue(testString, &testString)
|
||||
@@ -1102,6 +1140,35 @@ func TestCaseInsensitiveSet(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNested(t *testing.T) {
|
||||
type duration struct {
|
||||
Delay time.Duration
|
||||
}
|
||||
|
||||
type item struct {
|
||||
Name string
|
||||
Delay time.Duration
|
||||
Nested duration
|
||||
}
|
||||
|
||||
config := `[[parent]]
|
||||
delay="100ms"
|
||||
[parent.nested]
|
||||
delay="200ms"
|
||||
`
|
||||
initConfig("toml", config)
|
||||
|
||||
var items []item
|
||||
err := v.UnmarshalKey("parent", &items)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to decode into struct, %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, 1, len(items))
|
||||
assert.Equal(t, 100*time.Millisecond, items[0].Delay)
|
||||
assert.Equal(t, 200*time.Millisecond, items[0].Nested.Delay)
|
||||
}
|
||||
|
||||
func doTestCaseInsensitive(t *testing.T, typ, config string) {
|
||||
initConfig(typ, config)
|
||||
Set("RfD", true)
|
||||
|
||||
Reference in New Issue
Block a user