mirror of
https://github.com/spf13/viper
synced 2025-05-07 20:57:18 +00:00
Resolve race conditions, panics, and other errors (#1)
This patch provides fixes for: - https://github.com/spf13/viper/issues/730 - https://github.com/spf13/viper/issues/695 - https://github.com/spf13/viper/issues/353 - https://github.com/spf13/viper/issues/174 - https://github.com/spf13/viper/issues/378 - https://github.com/spf13/viper/issues/629
This commit is contained in:
parent
e02bc9eca5
commit
5bace2abf4
13 changed files with 198 additions and 18 deletions
11
.circleci/config.yml
Normal file
11
.circleci/config.yml
Normal file
|
@ -0,0 +1,11 @@
|
|||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/golang:1.12
|
||||
environment:
|
||||
- GO111MODULE=on
|
||||
working_directory: /go/src/github.com/ory/viper
|
||||
steps:
|
||||
- checkout
|
||||
- run: go test -race -v ./...
|
|
@ -1,4 +1,4 @@
|
|||
go_import_path: github.com/spf13/viper
|
||||
go_import_path: github.com/ory/viper
|
||||
|
||||
language: go
|
||||
|
||||
|
|
6
Makefile
Normal file
6
Makefile
Normal file
|
@ -0,0 +1,6 @@
|
|||
SHELL=/bin/bash -o pipefail
|
||||
|
||||
# Formats the code
|
||||
.PHONY: format
|
||||
format:
|
||||
goreturns -w -local github.com/ory $$(listx .)
|
16
README.md
16
README.md
|
@ -1,7 +1,17 @@
|
|||

|
||||
|
||||
[
|
||||
|
||||
Go configuration with fangs!
|
||||
|
||||
> This is a fork. It resolves several issues that are left unresolved in [the upstream](https://github.com/ory/viper).
|
||||
> Issues resolved and features added include:
|
||||
>
|
||||
> - Fixed race conditions when reloading configs.
|
||||
> - Added `HasChanged(key string) bool` which returns true (once!) when a value has changed.
|
||||
> - Make sure that `viper.AllSettings()` always returns `map[string]interface{}` which was not the case and incompatible
|
||||
with de/encoders like `json`.
|
||||
|
||||
Many Go projects are built using Viper including:
|
||||
|
||||
* [Hugo](http://gohugo.io)
|
||||
|
@ -13,11 +23,11 @@ Many Go projects are built using Viper including:
|
|||
* [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)
|
||||
[](https://travis-ci.org/ory/viper) [](https://gitter.im/ory/viper?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://godoc.org/github.com/ory/viper)
|
||||
|
||||
## Install
|
||||
```console
|
||||
go get -u github.com/spf13/viper
|
||||
go get -u github.com/ory/viper
|
||||
```
|
||||
|
||||
## What is Viper?
|
||||
|
@ -384,7 +394,7 @@ viper.BindFlagValues("my-flags", fSet)
|
|||
To enable remote support in Viper, do a blank import of the `viper/remote`
|
||||
package:
|
||||
|
||||
`import _ "github.com/spf13/viper/remote"`
|
||||
`import _ "github.com/ory/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
|
||||
|
|
3
go.mod
3
go.mod
|
@ -1,4 +1,4 @@
|
|||
module github.com/spf13/viper
|
||||
module github.com/ory/viper
|
||||
|
||||
require (
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect
|
||||
|
@ -28,6 +28,7 @@ require (
|
|||
github.com/spf13/jwalterweatherman v1.0.0
|
||||
github.com/spf13/pflag v1.0.3
|
||||
github.com/stretchr/testify v1.2.2
|
||||
github.com/subosito/gotenv v1.1.1
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect
|
||||
github.com/ugorji/go v1.1.4 // indirect
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
|
||||
|
|
4
go.sum
4
go.sum
|
@ -71,8 +71,6 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
|||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
|
@ -117,6 +115,8 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
|
|||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/subosito/gotenv v1.1.1 h1:TWxckSF6WVKWbo2M3tMqCtWa9NFUgqM1SSynxmYONOI=
|
||||
github.com/subosito/gotenv v1.1.1/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
|
||||
|
|
|
@ -11,8 +11,9 @@ import (
|
|||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
crypt "github.com/xordataexchange/crypt/config"
|
||||
|
||||
"github.com/ory/viper"
|
||||
)
|
||||
|
||||
type remoteConfigProvider struct{}
|
||||
|
|
12
stub/config.json
Normal file
12
stub/config.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"foo": {
|
||||
"bar": [
|
||||
{
|
||||
"baz": 1
|
||||
},
|
||||
{
|
||||
"baz": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
4
stub/config.yaml
Normal file
4
stub/config.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
foo:
|
||||
bar:
|
||||
- baz: 1
|
||||
- baz: 2
|
25
util.go
25
util.go
|
@ -219,3 +219,28 @@ func deepSearch(m map[string]interface{}, path []string) map[string]interface{}
|
|||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// toMapStringInterface is a workaround for https://github.com/ory/viper/issues/730
|
||||
// and https://github.com/go-yaml/yaml/issues/139
|
||||
func toMapStringInterface(in interface{}) interface{} {
|
||||
switch t := in.(type) {
|
||||
case map[string]interface{}:
|
||||
for k, v := range t {
|
||||
t[k] = toMapStringInterface(v)
|
||||
}
|
||||
return t
|
||||
case map[interface{}]interface{}:
|
||||
nt := make(map[string]interface{})
|
||||
for k, v := range t {
|
||||
nt[fmt.Sprintf("%s", k)] = toMapStringInterface(v)
|
||||
}
|
||||
return nt
|
||||
case []interface{}:
|
||||
for k, v := range t {
|
||||
t[k] = toMapStringInterface(v)
|
||||
}
|
||||
return t
|
||||
default:
|
||||
return in
|
||||
}
|
||||
}
|
||||
|
|
20
util_test.go
20
util_test.go
|
@ -13,6 +13,8 @@ package viper
|
|||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCopyAndInsensitiviseMap(t *testing.T) {
|
||||
|
@ -52,3 +54,21 @@ func TestCopyAndInsensitiviseMap(t *testing.T) {
|
|||
t.Fatal("Input map changed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestToMapStringInterface(t *testing.T) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"items": map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
toMapStringInterface(map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"items": map[interface{}]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
|
95
viper.go
95
viper.go
|
@ -33,14 +33,14 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/hashicorp/hcl"
|
||||
"github.com/hashicorp/hcl/hcl/printer"
|
||||
"github.com/magiconair/properties"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
toml "github.com/pelletier/go-toml"
|
||||
"github.com/pelletier/go-toml"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cast"
|
||||
jww "github.com/spf13/jwalterweatherman"
|
||||
|
@ -205,6 +205,8 @@ type Viper struct {
|
|||
properties *properties.Properties
|
||||
|
||||
onConfigChange func(fsnotify.Event)
|
||||
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// New returns an initialized Viper instance.
|
||||
|
@ -355,6 +357,8 @@ func (v *Viper) WatchConfig() {
|
|||
// 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) {
|
||||
v.lock.Lock()
|
||||
defer v.lock.Unlock()
|
||||
if in != "" {
|
||||
v.configFile = in
|
||||
}
|
||||
|
@ -365,6 +369,8 @@ func (v *Viper) SetConfigFile(in string) {
|
|||
// variables that start with "SPF_".
|
||||
func SetEnvPrefix(in string) { v.SetEnvPrefix(in) }
|
||||
func (v *Viper) SetEnvPrefix(in string) {
|
||||
v.lock.Lock()
|
||||
defer v.lock.Unlock()
|
||||
if in != "" {
|
||||
v.envPrefix = in
|
||||
}
|
||||
|
@ -383,6 +389,8 @@ func (v *Viper) mergeWithEnvPrefix(in string) string {
|
|||
// For backward compatibility reasons this is false by default.
|
||||
func AllowEmptyEnv(allowEmptyEnv bool) { v.AllowEmptyEnv(allowEmptyEnv) }
|
||||
func (v *Viper) AllowEmptyEnv(allowEmptyEnv bool) {
|
||||
v.lock.Lock()
|
||||
defer v.lock.Unlock()
|
||||
v.allowEmptyEnv = allowEmptyEnv
|
||||
}
|
||||
|
||||
|
@ -404,13 +412,21 @@ func (v *Viper) getEnv(key string) (string, bool) {
|
|||
}
|
||||
|
||||
// ConfigFileUsed returns the file used to populate the config registry.
|
||||
func ConfigFileUsed() string { return v.ConfigFileUsed() }
|
||||
func (v *Viper) ConfigFileUsed() string { return v.configFile }
|
||||
func ConfigFileUsed() string { return v.ConfigFileUsed() }
|
||||
func (v *Viper) ConfigFileUsed() string {
|
||||
v.lock.Lock()
|
||||
defer v.lock.Unlock()
|
||||
|
||||
return v.configFile
|
||||
}
|
||||
|
||||
// AddConfigPath adds a path for Viper to search for the config file in.
|
||||
// Can be called multiple times to define multiple search paths.
|
||||
func AddConfigPath(in string) { v.AddConfigPath(in) }
|
||||
func (v *Viper) AddConfigPath(in string) {
|
||||
v.lock.Lock()
|
||||
defer v.lock.Unlock()
|
||||
|
||||
if in != "" {
|
||||
absin := absPathify(in)
|
||||
jww.INFO.Println("adding", absin, "to paths to search")
|
||||
|
@ -432,6 +448,9 @@ func AddRemoteProvider(provider, endpoint, path string) error {
|
|||
return v.AddRemoteProvider(provider, endpoint, path)
|
||||
}
|
||||
func (v *Viper) AddRemoteProvider(provider, endpoint, path string) error {
|
||||
v.lock.Lock()
|
||||
defer v.lock.Unlock()
|
||||
|
||||
if !stringInSlice(provider, SupportedRemoteProviders) {
|
||||
return UnsupportedRemoteProviderError(provider)
|
||||
}
|
||||
|
@ -464,6 +483,9 @@ func AddSecureRemoteProvider(provider, endpoint, path, secretkeyring string) err
|
|||
}
|
||||
|
||||
func (v *Viper) AddSecureRemoteProvider(provider, endpoint, path, secretkeyring string) error {
|
||||
v.lock.Lock()
|
||||
defer v.lock.Unlock()
|
||||
|
||||
if !stringInSlice(provider, SupportedRemoteProviders) {
|
||||
return UnsupportedRemoteProviderError(provider)
|
||||
}
|
||||
|
@ -652,6 +674,9 @@ func (v *Viper) isPathShadowedInAutoEnv(path []string) string {
|
|||
// "a b c"
|
||||
func SetTypeByDefaultValue(enable bool) { v.SetTypeByDefaultValue(enable) }
|
||||
func (v *Viper) SetTypeByDefaultValue(enable bool) {
|
||||
v.lock.Lock()
|
||||
defer v.lock.Unlock()
|
||||
|
||||
v.typeByDefValue = enable
|
||||
}
|
||||
|
||||
|
@ -945,6 +970,8 @@ func (v *Viper) BindFlagValues(flags FlagValueSet) (err error) {
|
|||
//
|
||||
func BindFlagValue(key string, flag FlagValue) error { return v.BindFlagValue(key, flag) }
|
||||
func (v *Viper) BindFlagValue(key string, flag FlagValue) error {
|
||||
v.lock.Lock()
|
||||
defer v.lock.Unlock()
|
||||
if flag == nil {
|
||||
return fmt.Errorf("flag for %q is nil", key)
|
||||
}
|
||||
|
@ -958,6 +985,8 @@ func (v *Viper) BindFlagValue(key string, flag FlagValue) error {
|
|||
// EnvPrefix will be used when set when env name is not provided.
|
||||
func BindEnv(input ...string) error { return v.BindEnv(input...) }
|
||||
func (v *Viper) BindEnv(input ...string) error {
|
||||
v.lock.Lock()
|
||||
defer v.lock.Unlock()
|
||||
var key, envkey string
|
||||
if len(input) == 0 {
|
||||
return fmt.Errorf("BindEnv missing key to bind to")
|
||||
|
@ -982,6 +1011,8 @@ func (v *Viper) BindEnv(input ...string) error {
|
|||
// Viper will check to see if an alias exists first.
|
||||
// Note: this assumes a lower-cased key given.
|
||||
func (v *Viper) find(lcaseKey string) interface{} {
|
||||
v.lock.RLock()
|
||||
defer v.lock.RUnlock()
|
||||
|
||||
var (
|
||||
val interface{}
|
||||
|
@ -1132,6 +1163,8 @@ func (v *Viper) IsSet(key string) bool {
|
|||
// keys set in config, default & flags
|
||||
func AutomaticEnv() { v.AutomaticEnv() }
|
||||
func (v *Viper) AutomaticEnv() {
|
||||
v.lock.Lock()
|
||||
defer v.lock.Unlock()
|
||||
v.automaticEnvApplied = true
|
||||
}
|
||||
|
||||
|
@ -1140,6 +1173,8 @@ func (v *Viper) AutomaticEnv() {
|
|||
// not match it.
|
||||
func SetEnvKeyReplacer(r *strings.Replacer) { v.SetEnvKeyReplacer(r) }
|
||||
func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer) {
|
||||
v.lock.Lock()
|
||||
defer v.lock.Unlock()
|
||||
v.envKeyReplacer = r
|
||||
}
|
||||
|
||||
|
@ -1153,6 +1188,8 @@ func (v *Viper) RegisterAlias(alias string, key string) {
|
|||
func (v *Viper) registerAlias(alias string, key string) {
|
||||
alias = strings.ToLower(alias)
|
||||
if alias != key && alias != v.realKey(key) {
|
||||
v.lock.Lock()
|
||||
defer v.lock.Unlock()
|
||||
_, exists := v.aliases[alias]
|
||||
|
||||
if !exists {
|
||||
|
@ -1183,6 +1220,8 @@ func (v *Viper) registerAlias(alias string, key string) {
|
|||
}
|
||||
|
||||
func (v *Viper) realKey(key string) string {
|
||||
v.lock.RLock()
|
||||
defer v.lock.RUnlock()
|
||||
newkey, exists := v.aliases[key]
|
||||
if exists {
|
||||
jww.DEBUG.Println("Alias", key, "to", newkey)
|
||||
|
@ -1232,6 +1271,9 @@ func (v *Viper) Set(key string, value interface{}) {
|
|||
lastKey := strings.ToLower(path[len(path)-1])
|
||||
deepestMap := deepSearch(v.override, path[0:len(path)-1])
|
||||
|
||||
v.lock.Lock()
|
||||
defer v.lock.Unlock()
|
||||
|
||||
// set innermost value
|
||||
deepestMap[lastKey] = value
|
||||
}
|
||||
|
@ -1263,6 +1305,8 @@ func (v *Viper) ReadInConfig() error {
|
|||
return err
|
||||
}
|
||||
|
||||
v.lock.Lock()
|
||||
defer v.lock.Unlock()
|
||||
v.config = config
|
||||
return nil
|
||||
}
|
||||
|
@ -1292,6 +1336,8 @@ func (v *Viper) MergeInConfig() error {
|
|||
// key does not exist in the file.
|
||||
func ReadConfig(in io.Reader) error { return v.ReadConfig(in) }
|
||||
func (v *Viper) ReadConfig(in io.Reader) error {
|
||||
v.lock.Lock()
|
||||
defer v.lock.Unlock()
|
||||
v.config = make(map[string]interface{})
|
||||
return v.unmarshalReader(in, v.config)
|
||||
}
|
||||
|
@ -1310,6 +1356,8 @@ func (v *Viper) MergeConfig(in io.Reader) error {
|
|||
// Note that the map given may be modified.
|
||||
func MergeConfigMap(cfg map[string]interface{}) error { return v.MergeConfigMap(cfg) }
|
||||
func (v *Viper) MergeConfigMap(cfg map[string]interface{}) error {
|
||||
v.lock.Lock()
|
||||
defer v.lock.Unlock()
|
||||
if v.config == nil {
|
||||
v.config = make(map[string]interface{})
|
||||
}
|
||||
|
@ -1374,13 +1422,19 @@ func (v *Viper) writeConfig(filename string, force bool) error {
|
|||
return fmt.Errorf("File: %s exists. Use WriteConfig to overwrite.", filename)
|
||||
}
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
if err := v.marshalWriter(&buffer, configType); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := v.fs.OpenFile(filename, flags, v.configPermissions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := v.marshalWriter(f, configType); err != nil {
|
||||
if _, err := io.Copy(f, &buffer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -1456,11 +1510,16 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type fileWriter interface {
|
||||
io.Writer
|
||||
io.StringWriter
|
||||
}
|
||||
|
||||
// Marshal a map into Writer.
|
||||
func marshalWriter(f afero.File, configType string) error {
|
||||
return v.marshalWriter(f, configType)
|
||||
}
|
||||
func (v *Viper) marshalWriter(f afero.File, configType string) error {
|
||||
func (v *Viper) marshalWriter(f fileWriter, configType string) error {
|
||||
c := v.AllSettings()
|
||||
switch configType {
|
||||
case "json":
|
||||
|
@ -1648,8 +1707,11 @@ func (v *Viper) WatchRemoteConfigOnChannel() error {
|
|||
|
||||
// Retrieve the first found remote configuration.
|
||||
func (v *Viper) getKeyValueConfig() error {
|
||||
v.lock.RLock()
|
||||
defer v.lock.RUnlock()
|
||||
|
||||
if RemoteConfig == nil {
|
||||
return RemoteConfigError("Enable the remote features by doing a blank import of the viper/remote package: '_ github.com/spf13/viper/remote'")
|
||||
return RemoteConfigError("Enable the remote features by doing a blank import of the viper/remote package: '_ github.com/ory/viper/remote'")
|
||||
}
|
||||
|
||||
for _, rp := range v.remoteProviders {
|
||||
|
@ -1664,6 +1726,9 @@ func (v *Viper) getKeyValueConfig() error {
|
|||
}
|
||||
|
||||
func (v *Viper) getRemoteConfig(provider RemoteProvider) (map[string]interface{}, error) {
|
||||
v.lock.RLock()
|
||||
defer v.lock.RUnlock()
|
||||
|
||||
reader, err := RemoteConfig.Get(provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -1715,6 +1780,9 @@ func (v *Viper) watchRemoteConfig(provider RemoteProvider) (map[string]interface
|
|||
// Nested keys are returned with a v.keyDelim (= ".") separator
|
||||
func AllKeys() []string { return v.AllKeys() }
|
||||
func (v *Viper) AllKeys() []string {
|
||||
v.lock.RLock()
|
||||
defer v.lock.RUnlock()
|
||||
|
||||
m := map[string]bool{}
|
||||
// add all paths, by order of descending priority to ensure correct shadowing
|
||||
m = v.flattenAndMergeMap(m, castMapStringToMapInterface(v.aliases), "")
|
||||
|
@ -1810,12 +1878,15 @@ func (v *Viper) AllSettings() map[string]interface{} {
|
|||
// set innermost value
|
||||
deepestMap[lastKey] = value
|
||||
}
|
||||
return m
|
||||
return toMapStringInterface(m).(map[string]interface{}) // This is safe because the input is map[string]interface{}
|
||||
}
|
||||
|
||||
// SetFs sets the filesystem to use to read configuration.
|
||||
func SetFs(fs afero.Fs) { v.SetFs(fs) }
|
||||
func (v *Viper) SetFs(fs afero.Fs) {
|
||||
v.lock.Lock()
|
||||
defer v.lock.Unlock()
|
||||
|
||||
v.fs = fs
|
||||
}
|
||||
|
||||
|
@ -1824,6 +1895,8 @@ func (v *Viper) SetFs(fs afero.Fs) {
|
|||
func SetConfigName(in string) { v.SetConfigName(in) }
|
||||
func (v *Viper) SetConfigName(in string) {
|
||||
if in != "" {
|
||||
v.lock.Lock()
|
||||
defer v.lock.Unlock()
|
||||
v.configName = in
|
||||
v.configFile = ""
|
||||
}
|
||||
|
@ -1834,6 +1907,8 @@ func (v *Viper) SetConfigName(in string) {
|
|||
func SetConfigType(in string) { v.SetConfigType(in) }
|
||||
func (v *Viper) SetConfigType(in string) {
|
||||
if in != "" {
|
||||
v.lock.Lock()
|
||||
defer v.lock.Unlock()
|
||||
v.configType = in
|
||||
}
|
||||
}
|
||||
|
@ -1841,6 +1916,8 @@ func (v *Viper) SetConfigType(in string) {
|
|||
// SetConfigPermissions sets the permissions for the config file.
|
||||
func SetConfigPermissions(perm os.FileMode) { v.SetConfigPermissions(perm) }
|
||||
func (v *Viper) SetConfigPermissions(perm os.FileMode) {
|
||||
v.lock.Lock()
|
||||
defer v.lock.Unlock()
|
||||
v.configPermissions = perm.Perm()
|
||||
}
|
||||
|
||||
|
@ -1905,6 +1982,8 @@ func (v *Viper) findConfigFile() (string, error) {
|
|||
// purposes.
|
||||
func Debug() { v.Debug() }
|
||||
func (v *Viper) Debug() {
|
||||
v.lock.RLock()
|
||||
defer v.lock.RUnlock()
|
||||
fmt.Printf("Aliases:\n%#v\n", v.aliases)
|
||||
fmt.Printf("Override:\n%#v\n", v.override)
|
||||
fmt.Printf("PFlags:\n%#v\n", v.pflags)
|
||||
|
|
|
@ -245,7 +245,7 @@ func initDirs(t *testing.T) (string, string, func()) {
|
|||
}
|
||||
}
|
||||
|
||||
//stubs for PFlag Values
|
||||
// stubs for PFlag Values
|
||||
type stringValue string
|
||||
|
||||
func newStringValue(val string, p *string) *stringValue {
|
||||
|
@ -763,7 +763,7 @@ func TestBindPFlag(t *testing.T) {
|
|||
assert.Equal(t, testString, Get("testvalue"))
|
||||
|
||||
flag.Value.Set("testing_mutate")
|
||||
flag.Changed = true //hack for pflag usage
|
||||
flag.Changed = true // hack for pflag usage
|
||||
|
||||
assert.Equal(t, "testing_mutate", Get("testvalue"))
|
||||
|
||||
|
@ -1750,6 +1750,17 @@ func TestWatchFile(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
func TestArrayOfObjects(t *testing.T) {
|
||||
SetConfigType("yml")
|
||||
require.NoError(t, ReadConfig(bytes.NewBufferString(`foo:
|
||||
bar:
|
||||
- baz: 1
|
||||
- baz: 2`)))
|
||||
|
||||
SetConfigFile(path.Join(os.TempDir(), fmt.Sprintf("config-%d.json", time.Now().UnixNano())))
|
||||
require.NoError(t, WriteConfig())
|
||||
}
|
||||
|
||||
func BenchmarkGetBool(b *testing.B) {
|
||||
key := "BenchmarkGetBool"
|
||||
v = New()
|
||||
|
|
Loading…
Add table
Reference in a new issue