mirror of
https://github.com/spf13/viper
synced 2025-05-10 22:27: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
|
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!
|
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:
|
Many Go projects are built using Viper including:
|
||||||
|
|
||||||
* [Hugo](http://gohugo.io)
|
* [Hugo](http://gohugo.io)
|
||||||
|
@ -13,11 +23,11 @@ Many Go projects are built using Viper including:
|
||||||
* [doctl](https://github.com/digitalocean/doctl)
|
* [doctl](https://github.com/digitalocean/doctl)
|
||||||
* [Clairctl](https://github.com/jgsqware/clairctl)
|
* [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
|
## Install
|
||||||
```console
|
```console
|
||||||
go get -u github.com/spf13/viper
|
go get -u github.com/ory/viper
|
||||||
```
|
```
|
||||||
|
|
||||||
## What is 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`
|
To enable remote support in Viper, do a blank import of the `viper/remote`
|
||||||
package:
|
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
|
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
|
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 (
|
require (
|
||||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect
|
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/jwalterweatherman v1.0.0
|
||||||
github.com/spf13/pflag v1.0.3
|
github.com/spf13/pflag v1.0.3
|
||||||
github.com/stretchr/testify v1.2.2
|
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/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect
|
||||||
github.com/ugorji/go v1.1.4 // indirect
|
github.com/ugorji/go v1.1.4 // indirect
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // 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/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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
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 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
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/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 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
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 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
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=
|
github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
|
||||||
|
|
|
@ -11,8 +11,9 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
crypt "github.com/xordataexchange/crypt/config"
|
crypt "github.com/xordataexchange/crypt/config"
|
||||||
|
|
||||||
|
"github.com/ory/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
type remoteConfigProvider struct{}
|
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
|
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 (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCopyAndInsensitiviseMap(t *testing.T) {
|
func TestCopyAndInsensitiviseMap(t *testing.T) {
|
||||||
|
@ -52,3 +54,21 @@ func TestCopyAndInsensitiviseMap(t *testing.T) {
|
||||||
t.Fatal("Input map changed")
|
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"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/hashicorp/hcl"
|
"github.com/hashicorp/hcl"
|
||||||
"github.com/hashicorp/hcl/hcl/printer"
|
"github.com/hashicorp/hcl/hcl/printer"
|
||||||
"github.com/magiconair/properties"
|
"github.com/magiconair/properties"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
toml "github.com/pelletier/go-toml"
|
"github.com/pelletier/go-toml"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
jww "github.com/spf13/jwalterweatherman"
|
jww "github.com/spf13/jwalterweatherman"
|
||||||
|
@ -205,6 +205,8 @@ type Viper struct {
|
||||||
properties *properties.Properties
|
properties *properties.Properties
|
||||||
|
|
||||||
onConfigChange func(fsnotify.Event)
|
onConfigChange func(fsnotify.Event)
|
||||||
|
|
||||||
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns an initialized Viper instance.
|
// 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.
|
// Viper will use this and not check any of the config paths.
|
||||||
func SetConfigFile(in string) { v.SetConfigFile(in) }
|
func SetConfigFile(in string) { v.SetConfigFile(in) }
|
||||||
func (v *Viper) SetConfigFile(in string) {
|
func (v *Viper) SetConfigFile(in string) {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
if in != "" {
|
if in != "" {
|
||||||
v.configFile = in
|
v.configFile = in
|
||||||
}
|
}
|
||||||
|
@ -365,6 +369,8 @@ func (v *Viper) SetConfigFile(in string) {
|
||||||
// variables that start with "SPF_".
|
// 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) {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
if in != "" {
|
if in != "" {
|
||||||
v.envPrefix = in
|
v.envPrefix = in
|
||||||
}
|
}
|
||||||
|
@ -383,6 +389,8 @@ func (v *Viper) mergeWithEnvPrefix(in string) string {
|
||||||
// For backward compatibility reasons this is false by default.
|
// For backward compatibility reasons this is false by default.
|
||||||
func AllowEmptyEnv(allowEmptyEnv bool) { v.AllowEmptyEnv(allowEmptyEnv) }
|
func AllowEmptyEnv(allowEmptyEnv bool) { v.AllowEmptyEnv(allowEmptyEnv) }
|
||||||
func (v *Viper) AllowEmptyEnv(allowEmptyEnv bool) {
|
func (v *Viper) AllowEmptyEnv(allowEmptyEnv bool) {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
v.allowEmptyEnv = allowEmptyEnv
|
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.
|
// ConfigFileUsed returns 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 {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
|
|
||||||
|
return v.configFile
|
||||||
|
}
|
||||||
|
|
||||||
// AddConfigPath adds a path for Viper to search for the config file in.
|
// AddConfigPath adds 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) {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
|
|
||||||
if in != "" {
|
if in != "" {
|
||||||
absin := absPathify(in)
|
absin := absPathify(in)
|
||||||
jww.INFO.Println("adding", absin, "to paths to search")
|
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)
|
return v.AddRemoteProvider(provider, endpoint, path)
|
||||||
}
|
}
|
||||||
func (v *Viper) AddRemoteProvider(provider, endpoint, path string) error {
|
func (v *Viper) AddRemoteProvider(provider, endpoint, path string) error {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
|
|
||||||
if !stringInSlice(provider, SupportedRemoteProviders) {
|
if !stringInSlice(provider, SupportedRemoteProviders) {
|
||||||
return UnsupportedRemoteProviderError(provider)
|
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 {
|
func (v *Viper) AddSecureRemoteProvider(provider, endpoint, path, secretkeyring string) error {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
|
|
||||||
if !stringInSlice(provider, SupportedRemoteProviders) {
|
if !stringInSlice(provider, SupportedRemoteProviders) {
|
||||||
return UnsupportedRemoteProviderError(provider)
|
return UnsupportedRemoteProviderError(provider)
|
||||||
}
|
}
|
||||||
|
@ -652,6 +674,9 @@ func (v *Viper) isPathShadowedInAutoEnv(path []string) string {
|
||||||
// "a b c"
|
// "a b c"
|
||||||
func SetTypeByDefaultValue(enable bool) { v.SetTypeByDefaultValue(enable) }
|
func SetTypeByDefaultValue(enable bool) { v.SetTypeByDefaultValue(enable) }
|
||||||
func (v *Viper) SetTypeByDefaultValue(enable bool) {
|
func (v *Viper) SetTypeByDefaultValue(enable bool) {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
|
|
||||||
v.typeByDefValue = enable
|
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 BindFlagValue(key string, flag FlagValue) error { return v.BindFlagValue(key, flag) }
|
||||||
func (v *Viper) BindFlagValue(key string, flag FlagValue) error {
|
func (v *Viper) BindFlagValue(key string, flag FlagValue) error {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
if flag == nil {
|
if flag == nil {
|
||||||
return fmt.Errorf("flag for %q is nil", key)
|
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.
|
// EnvPrefix will be used when set when env name is not provided.
|
||||||
func BindEnv(input ...string) error { return v.BindEnv(input...) }
|
func BindEnv(input ...string) error { return v.BindEnv(input...) }
|
||||||
func (v *Viper) BindEnv(input ...string) error {
|
func (v *Viper) BindEnv(input ...string) error {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
var key, envkey string
|
var key, envkey string
|
||||||
if len(input) == 0 {
|
if len(input) == 0 {
|
||||||
return fmt.Errorf("BindEnv missing key to bind to")
|
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.
|
// Viper will check to see if an alias exists first.
|
||||||
// Note: this assumes a lower-cased key given.
|
// Note: this assumes a lower-cased key given.
|
||||||
func (v *Viper) find(lcaseKey string) interface{} {
|
func (v *Viper) find(lcaseKey string) interface{} {
|
||||||
|
v.lock.RLock()
|
||||||
|
defer v.lock.RUnlock()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
val interface{}
|
val interface{}
|
||||||
|
@ -1132,6 +1163,8 @@ func (v *Viper) IsSet(key string) bool {
|
||||||
// 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.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
v.automaticEnvApplied = true
|
v.automaticEnvApplied = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1140,6 +1173,8 @@ func (v *Viper) AutomaticEnv() {
|
||||||
// not match it.
|
// not match it.
|
||||||
func SetEnvKeyReplacer(r *strings.Replacer) { v.SetEnvKeyReplacer(r) }
|
func SetEnvKeyReplacer(r *strings.Replacer) { v.SetEnvKeyReplacer(r) }
|
||||||
func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer) {
|
func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer) {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
v.envKeyReplacer = r
|
v.envKeyReplacer = r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1153,6 +1188,8 @@ func (v *Viper) RegisterAlias(alias string, key string) {
|
||||||
func (v *Viper) registerAlias(alias string, key string) {
|
func (v *Viper) registerAlias(alias string, key string) {
|
||||||
alias = strings.ToLower(alias)
|
alias = strings.ToLower(alias)
|
||||||
if alias != key && alias != v.realKey(key) {
|
if alias != key && alias != v.realKey(key) {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
_, exists := v.aliases[alias]
|
_, exists := v.aliases[alias]
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
|
@ -1183,6 +1220,8 @@ func (v *Viper) registerAlias(alias string, key string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Viper) realKey(key string) string {
|
func (v *Viper) realKey(key string) string {
|
||||||
|
v.lock.RLock()
|
||||||
|
defer v.lock.RUnlock()
|
||||||
newkey, exists := v.aliases[key]
|
newkey, exists := v.aliases[key]
|
||||||
if exists {
|
if exists {
|
||||||
jww.DEBUG.Println("Alias", key, "to", newkey)
|
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])
|
lastKey := strings.ToLower(path[len(path)-1])
|
||||||
deepestMap := deepSearch(v.override, path[0:len(path)-1])
|
deepestMap := deepSearch(v.override, path[0:len(path)-1])
|
||||||
|
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
|
|
||||||
// set innermost value
|
// set innermost value
|
||||||
deepestMap[lastKey] = value
|
deepestMap[lastKey] = value
|
||||||
}
|
}
|
||||||
|
@ -1263,6 +1305,8 @@ func (v *Viper) ReadInConfig() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
v.config = config
|
v.config = config
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1292,6 +1336,8 @@ func (v *Viper) MergeInConfig() error {
|
||||||
// key does not exist in the file.
|
// key does not exist in the file.
|
||||||
func ReadConfig(in io.Reader) error { return v.ReadConfig(in) }
|
func ReadConfig(in io.Reader) error { return v.ReadConfig(in) }
|
||||||
func (v *Viper) ReadConfig(in io.Reader) error {
|
func (v *Viper) ReadConfig(in io.Reader) error {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
v.config = make(map[string]interface{})
|
v.config = make(map[string]interface{})
|
||||||
return v.unmarshalReader(in, v.config)
|
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.
|
// Note that the map given may be modified.
|
||||||
func MergeConfigMap(cfg map[string]interface{}) error { return v.MergeConfigMap(cfg) }
|
func MergeConfigMap(cfg map[string]interface{}) error { return v.MergeConfigMap(cfg) }
|
||||||
func (v *Viper) MergeConfigMap(cfg map[string]interface{}) error {
|
func (v *Viper) MergeConfigMap(cfg map[string]interface{}) error {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
if v.config == nil {
|
if v.config == nil {
|
||||||
v.config = make(map[string]interface{})
|
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)
|
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)
|
f, err := v.fs.OpenFile(filename, flags, v.configPermissions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
if err := v.marshalWriter(f, configType); err != nil {
|
if _, err := io.Copy(f, &buffer); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1456,11 +1510,16 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fileWriter interface {
|
||||||
|
io.Writer
|
||||||
|
io.StringWriter
|
||||||
|
}
|
||||||
|
|
||||||
// Marshal a map into Writer.
|
// Marshal a map into Writer.
|
||||||
func marshalWriter(f afero.File, configType string) error {
|
func marshalWriter(f afero.File, configType string) error {
|
||||||
return v.marshalWriter(f, configType)
|
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()
|
c := v.AllSettings()
|
||||||
switch configType {
|
switch configType {
|
||||||
case "json":
|
case "json":
|
||||||
|
@ -1648,8 +1707,11 @@ func (v *Viper) WatchRemoteConfigOnChannel() error {
|
||||||
|
|
||||||
// Retrieve the first found remote configuration.
|
// Retrieve the first found remote configuration.
|
||||||
func (v *Viper) getKeyValueConfig() error {
|
func (v *Viper) getKeyValueConfig() error {
|
||||||
|
v.lock.RLock()
|
||||||
|
defer v.lock.RUnlock()
|
||||||
|
|
||||||
if RemoteConfig == nil {
|
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 {
|
for _, rp := range v.remoteProviders {
|
||||||
|
@ -1664,6 +1726,9 @@ func (v *Viper) getKeyValueConfig() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Viper) getRemoteConfig(provider RemoteProvider) (map[string]interface{}, error) {
|
func (v *Viper) getRemoteConfig(provider RemoteProvider) (map[string]interface{}, error) {
|
||||||
|
v.lock.RLock()
|
||||||
|
defer v.lock.RUnlock()
|
||||||
|
|
||||||
reader, err := RemoteConfig.Get(provider)
|
reader, err := RemoteConfig.Get(provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// Nested keys are returned with a v.keyDelim (= ".") separator
|
||||||
func AllKeys() []string { return v.AllKeys() }
|
func AllKeys() []string { return v.AllKeys() }
|
||||||
func (v *Viper) AllKeys() []string {
|
func (v *Viper) AllKeys() []string {
|
||||||
|
v.lock.RLock()
|
||||||
|
defer v.lock.RUnlock()
|
||||||
|
|
||||||
m := map[string]bool{}
|
m := map[string]bool{}
|
||||||
// add all paths, by order of descending priority to ensure correct shadowing
|
// add all paths, by order of descending priority to ensure correct shadowing
|
||||||
m = v.flattenAndMergeMap(m, castMapStringToMapInterface(v.aliases), "")
|
m = v.flattenAndMergeMap(m, castMapStringToMapInterface(v.aliases), "")
|
||||||
|
@ -1810,12 +1878,15 @@ func (v *Viper) AllSettings() map[string]interface{} {
|
||||||
// set innermost value
|
// set innermost value
|
||||||
deepestMap[lastKey] = 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.
|
// SetFs sets the filesystem to use to read configuration.
|
||||||
func SetFs(fs afero.Fs) { v.SetFs(fs) }
|
func SetFs(fs afero.Fs) { v.SetFs(fs) }
|
||||||
func (v *Viper) SetFs(fs afero.Fs) {
|
func (v *Viper) SetFs(fs afero.Fs) {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
|
|
||||||
v.fs = fs
|
v.fs = fs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1824,6 +1895,8 @@ func (v *Viper) SetFs(fs afero.Fs) {
|
||||||
func SetConfigName(in string) { v.SetConfigName(in) }
|
func SetConfigName(in string) { v.SetConfigName(in) }
|
||||||
func (v *Viper) SetConfigName(in string) {
|
func (v *Viper) SetConfigName(in string) {
|
||||||
if in != "" {
|
if in != "" {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
v.configName = in
|
v.configName = in
|
||||||
v.configFile = ""
|
v.configFile = ""
|
||||||
}
|
}
|
||||||
|
@ -1834,6 +1907,8 @@ func (v *Viper) SetConfigName(in string) {
|
||||||
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 != "" {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
v.configType = in
|
v.configType = in
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1841,6 +1916,8 @@ func (v *Viper) SetConfigType(in string) {
|
||||||
// SetConfigPermissions sets the permissions for the config file.
|
// SetConfigPermissions sets the permissions for the config file.
|
||||||
func SetConfigPermissions(perm os.FileMode) { v.SetConfigPermissions(perm) }
|
func SetConfigPermissions(perm os.FileMode) { v.SetConfigPermissions(perm) }
|
||||||
func (v *Viper) SetConfigPermissions(perm os.FileMode) {
|
func (v *Viper) SetConfigPermissions(perm os.FileMode) {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
v.configPermissions = perm.Perm()
|
v.configPermissions = perm.Perm()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1905,6 +1982,8 @@ func (v *Viper) findConfigFile() (string, error) {
|
||||||
// purposes.
|
// purposes.
|
||||||
func Debug() { v.Debug() }
|
func Debug() { v.Debug() }
|
||||||
func (v *Viper) Debug() {
|
func (v *Viper) Debug() {
|
||||||
|
v.lock.RLock()
|
||||||
|
defer v.lock.RUnlock()
|
||||||
fmt.Printf("Aliases:\n%#v\n", v.aliases)
|
fmt.Printf("Aliases:\n%#v\n", v.aliases)
|
||||||
fmt.Printf("Override:\n%#v\n", v.override)
|
fmt.Printf("Override:\n%#v\n", v.override)
|
||||||
fmt.Printf("PFlags:\n%#v\n", v.pflags)
|
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
|
type stringValue string
|
||||||
|
|
||||||
func newStringValue(val string, p *string) *stringValue {
|
func newStringValue(val string, p *string) *stringValue {
|
||||||
|
@ -763,7 +763,7 @@ func TestBindPFlag(t *testing.T) {
|
||||||
assert.Equal(t, testString, Get("testvalue"))
|
assert.Equal(t, testString, Get("testvalue"))
|
||||||
|
|
||||||
flag.Value.Set("testing_mutate")
|
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"))
|
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) {
|
func BenchmarkGetBool(b *testing.B) {
|
||||||
key := "BenchmarkGetBool"
|
key := "BenchmarkGetBool"
|
||||||
v = New()
|
v = New()
|
||||||
|
|
Loading…
Add table
Reference in a new issue