hackerman 2019-07-15 12:52:53 +02:00 committed by GitHub
parent e02bc9eca5
commit 5bace2abf4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 198 additions and 18 deletions

11
.circleci/config.yml Normal file
View 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 ./...

View file

@ -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
View file

@ -0,0 +1,6 @@
SHELL=/bin/bash -o pipefail
# Formats the code
.PHONY: format
format:
goreturns -w -local github.com/ory $$(listx .)

View file

@ -1,7 +1,17 @@
![viper logo](https://cloud.githubusercontent.com/assets/173412/10886745/998df88a-8151-11e5-9448-4736db51020d.png) ![viper logo](https://cloud.githubusercontent.com/assets/173412/10886745/998df88a-8151-11e5-9448-4736db51020d.png)
[![CircleCI](https://circleci.com/gh/ory/viper/tree/master.svg?style=shield](https://circleci.com/gh/ory/viper/tree/master)
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)
[![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) [![GoDoc](https://godoc.org/github.com/spf13/viper?status.svg)](https://godoc.org/github.com/spf13/viper) [![Build Status](https://travis-ci.org/ory/viper.svg)](https://travis-ci.org/ory/viper) [![Join the chat at https://gitter.im/ory/viper](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ory/viper?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![GoDoc](https://godoc.org/github.com/ory/viper?status.svg)](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
View file

@ -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
View file

@ -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=

View file

@ -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
View file

@ -0,0 +1,12 @@
{
"foo": {
"bar": [
{
"baz": 1
},
{
"baz": 2
}
]
}
}

4
stub/config.yaml Normal file
View file

@ -0,0 +1,4 @@
foo:
bar:
- baz: 1
- baz: 2

25
util.go
View file

@ -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
}
}

View file

@ -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",
},
}),
)
}

View file

@ -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)

View file

@ -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()