mirror of
https://github.com/spf13/viper
synced 2025-05-06 04:07:17 +00:00
remove all remote code and providers except json
This commit is contained in:
parent
62edee3196
commit
ce95343c2f
5 changed files with 3 additions and 1115 deletions
6
go.mod
6
go.mod
|
@ -1,16 +1,12 @@
|
|||
module github.com/spf13/viper
|
||||
module github.com/xurwxj/viper
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/hashicorp/hcl v1.0.0
|
||||
github.com/magiconair/properties v1.8.0
|
||||
github.com/mitchellh/mapstructure v1.0.0
|
||||
github.com/pelletier/go-toml v1.2.0
|
||||
github.com/spf13/afero v1.1.2
|
||||
github.com/spf13/cast v1.2.0
|
||||
github.com/spf13/jwalterweatherman v1.0.0
|
||||
github.com/spf13/pflag v1.0.2
|
||||
golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992 // indirect
|
||||
golang.org/x/text v0.3.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.1
|
||||
)
|
||||
|
|
8
go.sum
8
go.sum
|
@ -1,14 +1,8 @@
|
|||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KHeTRY+7I=
|
||||
github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg=
|
||||
|
@ -22,5 +16,3 @@ golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
105
remote/remote.go
105
remote/remote.go
|
@ -1,105 +0,0 @@
|
|||
// Copyright © 2015 Steve Francia <spf@spf13.com>.
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package remote integrates the remote features of Viper.
|
||||
package remote
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
crypt "github.com/xordataexchange/crypt/config"
|
||||
)
|
||||
|
||||
type remoteConfigProvider struct{}
|
||||
|
||||
func (rc remoteConfigProvider) Get(rp viper.RemoteProvider) (io.Reader, error) {
|
||||
cm, err := getConfigManager(rp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, err := cm.Get(rp.Path())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bytes.NewReader(b), nil
|
||||
}
|
||||
|
||||
func (rc remoteConfigProvider) Watch(rp viper.RemoteProvider) (io.Reader, error) {
|
||||
cm, err := getConfigManager(rp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := cm.Get(rp.Path())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bytes.NewReader(resp), nil
|
||||
}
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
if rp.SecretKeyring() != "" {
|
||||
kr, err := os.Open(rp.SecretKeyring())
|
||||
defer kr.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rp.Provider() == "etcd" {
|
||||
cm, err = crypt.NewEtcdConfigManager([]string{rp.Endpoint()}, kr)
|
||||
} else {
|
||||
cm, err = crypt.NewConsulConfigManager([]string{rp.Endpoint()}, kr)
|
||||
}
|
||||
} else {
|
||||
if rp.Provider() == "etcd" {
|
||||
cm, err = crypt.NewStandardEtcdConfigManager([]string{rp.Endpoint()})
|
||||
} else {
|
||||
cm, err = crypt.NewStandardConsulConfigManager([]string{rp.Endpoint()})
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cm, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
viper.RemoteConfig = &remoteConfigProvider{}
|
||||
}
|
319
viper.go
319
viper.go
|
@ -33,14 +33,9 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
yaml "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/spf13/afero"
|
||||
"github.com/spf13/cast"
|
||||
jww "github.com/spf13/jwalterweatherman"
|
||||
|
@ -59,24 +54,10 @@ func (e ConfigMarshalError) Error() string {
|
|||
|
||||
var v *Viper
|
||||
|
||||
type RemoteResponse struct {
|
||||
Value []byte
|
||||
Error error
|
||||
}
|
||||
|
||||
func init() {
|
||||
v = New()
|
||||
}
|
||||
|
||||
type remoteConfigFactory interface {
|
||||
Get(rp RemoteProvider) (io.Reader, error)
|
||||
Watch(rp RemoteProvider) (io.Reader, error)
|
||||
WatchChannel(rp RemoteProvider) (<-chan *RemoteResponse, chan bool)
|
||||
}
|
||||
|
||||
// RemoteConfig is optional, see the remote package
|
||||
var RemoteConfig remoteConfigFactory
|
||||
|
||||
// UnsupportedConfigError denotes encountering an unsupported
|
||||
// configuration filetype.
|
||||
type UnsupportedConfigError string
|
||||
|
@ -86,24 +67,6 @@ func (str UnsupportedConfigError) Error() string {
|
|||
return fmt.Sprintf("Unsupported Config Type %q", string(str))
|
||||
}
|
||||
|
||||
// UnsupportedRemoteProviderError denotes encountering an unsupported remote
|
||||
// provider. Currently only etcd and Consul are supported.
|
||||
type UnsupportedRemoteProviderError string
|
||||
|
||||
// Error returns the formatted remote provider error.
|
||||
func (str UnsupportedRemoteProviderError) Error() string {
|
||||
return fmt.Sprintf("Unsupported Remote Provider Type %q", string(str))
|
||||
}
|
||||
|
||||
// RemoteConfigError denotes encountering an error while trying to
|
||||
// pull the configuration from the remote provider.
|
||||
type RemoteConfigError string
|
||||
|
||||
// Error returns the formatted remote provider error
|
||||
func (rce RemoteConfigError) Error() string {
|
||||
return fmt.Sprintf("Remote Configurations Error: %s", string(rce))
|
||||
}
|
||||
|
||||
// ConfigFileNotFoundError denotes failing to find configuration file.
|
||||
type ConfigFileNotFoundError struct {
|
||||
name, locations string
|
||||
|
@ -176,9 +139,6 @@ type Viper struct {
|
|||
// The filesystem to read config from.
|
||||
fs afero.Fs
|
||||
|
||||
// A set of remote providers to search for the configuration
|
||||
remoteProviders []*defaultRemoteProvider
|
||||
|
||||
// Name of file to look for inside the path
|
||||
configName string
|
||||
configFile string
|
||||
|
@ -227,49 +187,11 @@ func New() *Viper {
|
|||
// can use it in their testing as well.
|
||||
func Reset() {
|
||||
v = New()
|
||||
SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl"}
|
||||
SupportedRemoteProviders = []string{"etcd", "consul"}
|
||||
}
|
||||
|
||||
type defaultRemoteProvider struct {
|
||||
provider string
|
||||
endpoint string
|
||||
path string
|
||||
secretKeyring string
|
||||
}
|
||||
|
||||
func (rp defaultRemoteProvider) Provider() string {
|
||||
return rp.provider
|
||||
}
|
||||
|
||||
func (rp defaultRemoteProvider) Endpoint() string {
|
||||
return rp.endpoint
|
||||
}
|
||||
|
||||
func (rp defaultRemoteProvider) Path() string {
|
||||
return rp.path
|
||||
}
|
||||
|
||||
func (rp defaultRemoteProvider) SecretKeyring() string {
|
||||
return rp.secretKeyring
|
||||
}
|
||||
|
||||
// RemoteProvider stores the configuration necessary
|
||||
// to connect to a remote key/value store.
|
||||
// Optional secretKeyring to unencrypt encrypted values
|
||||
// can be provided.
|
||||
type RemoteProvider interface {
|
||||
Provider() string
|
||||
Endpoint() string
|
||||
Path() string
|
||||
SecretKeyring() string
|
||||
SupportedExts = []string{"json"}
|
||||
}
|
||||
|
||||
// SupportedExts are universally supported extensions.
|
||||
var SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl"}
|
||||
|
||||
// SupportedRemoteProviders are universally supported remote providers.
|
||||
var SupportedRemoteProviders = []string{"etcd", "consul"}
|
||||
var SupportedExts = []string{"json"}
|
||||
|
||||
func OnConfigChange(run func(in fsnotify.Event)) { v.OnConfigChange(run) }
|
||||
func (v *Viper) OnConfigChange(run func(in fsnotify.Event)) {
|
||||
|
@ -404,77 +326,6 @@ func (v *Viper) AddConfigPath(in string) {
|
|||
}
|
||||
}
|
||||
|
||||
// AddRemoteProvider adds a remote configuration source.
|
||||
// Remote Providers are searched in the order they are added.
|
||||
// provider is a string value, "etcd" or "consul" are currently supported.
|
||||
// endpoint is the url. etcd requires http://ip:port consul requires ip:port
|
||||
// path is the path in the k/v store to retrieve configuration
|
||||
// To retrieve a config file called myapp.json from /configs/myapp.json
|
||||
// you should set path to /configs and set config name (SetConfigName()) to
|
||||
// "myapp"
|
||||
func AddRemoteProvider(provider, endpoint, path string) error {
|
||||
return v.AddRemoteProvider(provider, endpoint, path)
|
||||
}
|
||||
func (v *Viper) AddRemoteProvider(provider, endpoint, path string) error {
|
||||
if !stringInSlice(provider, SupportedRemoteProviders) {
|
||||
return UnsupportedRemoteProviderError(provider)
|
||||
}
|
||||
if provider != "" && endpoint != "" {
|
||||
jww.INFO.Printf("adding %s:%s to remote provider list", provider, endpoint)
|
||||
rp := &defaultRemoteProvider{
|
||||
endpoint: endpoint,
|
||||
provider: provider,
|
||||
path: path,
|
||||
}
|
||||
if !v.providerPathExists(rp) {
|
||||
v.remoteProviders = append(v.remoteProviders, rp)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddSecureRemoteProvider adds a remote configuration source.
|
||||
// Secure Remote Providers are searched in the order they are added.
|
||||
// provider is a string value, "etcd" or "consul" are currently supported.
|
||||
// endpoint is the url. etcd requires http://ip:port consul requires ip:port
|
||||
// secretkeyring is the filepath to your openpgp secret keyring. e.g. /etc/secrets/myring.gpg
|
||||
// path is the path in the k/v store to retrieve configuration
|
||||
// To retrieve a config file called myapp.json from /configs/myapp.json
|
||||
// you should set path to /configs and set config name (SetConfigName()) to
|
||||
// "myapp"
|
||||
// Secure Remote Providers are implemented with github.com/xordataexchange/crypt
|
||||
func AddSecureRemoteProvider(provider, endpoint, path, secretkeyring string) error {
|
||||
return v.AddSecureRemoteProvider(provider, endpoint, path, secretkeyring)
|
||||
}
|
||||
|
||||
func (v *Viper) AddSecureRemoteProvider(provider, endpoint, path, secretkeyring string) error {
|
||||
if !stringInSlice(provider, SupportedRemoteProviders) {
|
||||
return UnsupportedRemoteProviderError(provider)
|
||||
}
|
||||
if provider != "" && endpoint != "" {
|
||||
jww.INFO.Printf("adding %s:%s to remote provider list", provider, endpoint)
|
||||
rp := &defaultRemoteProvider{
|
||||
endpoint: endpoint,
|
||||
provider: provider,
|
||||
path: path,
|
||||
secretKeyring: secretkeyring,
|
||||
}
|
||||
if !v.providerPathExists(rp) {
|
||||
v.remoteProviders = append(v.remoteProviders, rp)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Viper) providerPathExists(p *defaultRemoteProvider) bool {
|
||||
for _, y := range v.remoteProviders {
|
||||
if reflect.DeepEqual(y, p) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// searchMap recursively searches for a value for path in source map.
|
||||
// Returns nil if not found.
|
||||
// Note: This assumes that the path entries and map keys are lower cased.
|
||||
|
@ -1332,50 +1183,11 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
|
|||
buf.ReadFrom(in)
|
||||
|
||||
switch strings.ToLower(v.getConfigType()) {
|
||||
case "yaml", "yml":
|
||||
if err := yaml.Unmarshal(buf.Bytes(), &c); err != nil {
|
||||
return ConfigParseError{err}
|
||||
}
|
||||
|
||||
case "json":
|
||||
if err := json.Unmarshal(buf.Bytes(), &c); err != nil {
|
||||
return ConfigParseError{err}
|
||||
}
|
||||
|
||||
case "hcl":
|
||||
obj, err := hcl.Parse(string(buf.Bytes()))
|
||||
if err != nil {
|
||||
return ConfigParseError{err}
|
||||
}
|
||||
if err = hcl.DecodeObject(&c, obj); err != nil {
|
||||
return ConfigParseError{err}
|
||||
}
|
||||
|
||||
case "toml":
|
||||
tree, err := toml.LoadReader(buf)
|
||||
if err != nil {
|
||||
return ConfigParseError{err}
|
||||
}
|
||||
tmap := tree.ToMap()
|
||||
for k, v := range tmap {
|
||||
c[k] = v
|
||||
}
|
||||
|
||||
case "properties", "props", "prop":
|
||||
v.properties = properties.NewProperties()
|
||||
var err error
|
||||
if v.properties, err = properties.Load(buf.Bytes(), properties.UTF8); err != nil {
|
||||
return ConfigParseError{err}
|
||||
}
|
||||
for _, key := range v.properties.Keys() {
|
||||
value, _ := v.properties.Get(key)
|
||||
// recursively build nested maps
|
||||
path := strings.Split(key, ".")
|
||||
lastKey := strings.ToLower(path[len(path)-1])
|
||||
deepestMap := deepSearch(c, path[0:len(path)-1])
|
||||
// set innermost value
|
||||
deepestMap[lastKey] = value
|
||||
}
|
||||
}
|
||||
|
||||
insensitiviseMap(c)
|
||||
|
@ -1398,52 +1210,6 @@ func (v *Viper) marshalWriter(f afero.File, configType string) error {
|
|||
if err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
|
||||
case "hcl":
|
||||
b, err := json.Marshal(c)
|
||||
ast, err := hcl.Parse(string(b))
|
||||
if err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
err = printer.Fprint(f, ast.Node)
|
||||
if err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
|
||||
case "prop", "props", "properties":
|
||||
if v.properties == nil {
|
||||
v.properties = properties.NewProperties()
|
||||
}
|
||||
p := v.properties
|
||||
for _, key := range v.AllKeys() {
|
||||
_, _, err := p.Set(key, v.GetString(key))
|
||||
if err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
}
|
||||
_, err := p.WriteComment(f, "#", properties.UTF8)
|
||||
if err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
|
||||
case "toml":
|
||||
t, err := toml.TreeFromMap(c)
|
||||
if err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
s := t.String()
|
||||
if _, err := f.WriteString(s); err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
|
||||
case "yaml", "yml":
|
||||
b, err := yaml.Marshal(c)
|
||||
if err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
if _, err = f.WriteString(string(b)); err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -1544,22 +1310,6 @@ func mergeMaps(
|
|||
}
|
||||
}
|
||||
|
||||
// ReadRemoteConfig attempts to get configuration from a remote source
|
||||
// and read it in the remote configuration registry.
|
||||
func ReadRemoteConfig() error { return v.ReadRemoteConfig() }
|
||||
func (v *Viper) ReadRemoteConfig() error {
|
||||
return v.getKeyValueConfig()
|
||||
}
|
||||
|
||||
func WatchRemoteConfig() error { return v.WatchRemoteConfig() }
|
||||
func (v *Viper) WatchRemoteConfig() error {
|
||||
return v.watchKeyValueConfig()
|
||||
}
|
||||
|
||||
func (v *Viper) WatchRemoteConfigOnChannel() error {
|
||||
return v.watchKeyValueConfigOnChannel()
|
||||
}
|
||||
|
||||
func (v *Viper) insensitiviseMaps() {
|
||||
insensitiviseMap(v.config)
|
||||
insensitiviseMap(v.defaults)
|
||||
|
@ -1567,71 +1317,6 @@ func (v *Viper) insensitiviseMaps() {
|
|||
insensitiviseMap(v.kvstore)
|
||||
}
|
||||
|
||||
// Retrieve the first found remote configuration.
|
||||
func (v *Viper) getKeyValueConfig() error {
|
||||
if RemoteConfig == nil {
|
||||
return RemoteConfigError("Enable the remote features by doing a blank import of the viper/remote package: '_ github.com/spf13/viper/remote'")
|
||||
}
|
||||
|
||||
for _, rp := range v.remoteProviders {
|
||||
val, err := v.getRemoteConfig(rp)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
v.kvstore = val
|
||||
return nil
|
||||
}
|
||||
return RemoteConfigError("No Files Found")
|
||||
}
|
||||
|
||||
func (v *Viper) getRemoteConfig(provider RemoteProvider) (map[string]interface{}, error) {
|
||||
reader, err := RemoteConfig.Get(provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = v.unmarshalReader(reader, v.kvstore)
|
||||
return v.kvstore, err
|
||||
}
|
||||
|
||||
// Retrieve the first found remote configuration.
|
||||
func (v *Viper) watchKeyValueConfigOnChannel() error {
|
||||
for _, rp := range v.remoteProviders {
|
||||
respc, _ := RemoteConfig.WatchChannel(rp)
|
||||
//Todo: Add quit channel
|
||||
go func(rc <-chan *RemoteResponse) {
|
||||
for {
|
||||
b := <-rc
|
||||
reader := bytes.NewReader(b.Value)
|
||||
v.unmarshalReader(reader, v.kvstore)
|
||||
}
|
||||
}(respc)
|
||||
return nil
|
||||
}
|
||||
return RemoteConfigError("No Files Found")
|
||||
}
|
||||
|
||||
// Retrieve the first found remote configuration.
|
||||
func (v *Viper) watchKeyValueConfig() error {
|
||||
for _, rp := range v.remoteProviders {
|
||||
val, err := v.watchRemoteConfig(rp)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
v.kvstore = val
|
||||
return nil
|
||||
}
|
||||
return RemoteConfigError("No Files Found")
|
||||
}
|
||||
|
||||
func (v *Viper) watchRemoteConfig(provider RemoteProvider) (map[string]interface{}, error) {
|
||||
reader, err := RemoteConfig.Watch(provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = v.unmarshalReader(reader, v.kvstore)
|
||||
return v.kvstore, err
|
||||
}
|
||||
|
||||
// AllKeys returns all keys holding a value, regardless of where they are set.
|
||||
// Nested keys are returned with a v.keyDelim (= ".") separator
|
||||
func AllKeys() []string { return v.AllKeys() }
|
||||
|
|
680
viper_test.go
680
viper_test.go
|
@ -12,58 +12,25 @@ import (
|
|||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var yamlExample = []byte(`Hacker: true
|
||||
name: steve
|
||||
hobbies:
|
||||
- skateboarding
|
||||
- snowboarding
|
||||
- go
|
||||
clothing:
|
||||
jacket: leather
|
||||
trousers: denim
|
||||
pants:
|
||||
size: large
|
||||
age: 35
|
||||
eyes : brown
|
||||
beard: true
|
||||
`)
|
||||
|
||||
var yamlExampleWithExtras = []byte(`Existing: true
|
||||
Bogus: true
|
||||
`)
|
||||
|
||||
type testUnmarshalExtra struct {
|
||||
Existing bool
|
||||
}
|
||||
|
||||
var tomlExample = []byte(`
|
||||
title = "TOML Example"
|
||||
|
||||
[owner]
|
||||
organization = "MongoDB"
|
||||
Bio = "MongoDB Chief Developer Advocate & Hacker at Large"
|
||||
dob = 1979-05-27T07:32:00Z # First class dates? Why not?`)
|
||||
|
||||
var jsonExample = []byte(`{
|
||||
"id": "0001",
|
||||
"type": "donut",
|
||||
|
@ -79,66 +46,13 @@ var jsonExample = []byte(`{
|
|||
}
|
||||
}`)
|
||||
|
||||
var hclExample = []byte(`
|
||||
id = "0001"
|
||||
type = "donut"
|
||||
name = "Cake"
|
||||
ppu = 0.55
|
||||
foos {
|
||||
foo {
|
||||
key = 1
|
||||
}
|
||||
foo {
|
||||
key = 2
|
||||
}
|
||||
foo {
|
||||
key = 3
|
||||
}
|
||||
foo {
|
||||
key = 4
|
||||
}
|
||||
}`)
|
||||
|
||||
var propertiesExample = []byte(`
|
||||
p_id: 0001
|
||||
p_type: donut
|
||||
p_name: Cake
|
||||
p_ppu: 0.55
|
||||
p_batters.batter.type: Regular
|
||||
`)
|
||||
|
||||
var remoteExample = []byte(`{
|
||||
"id":"0002",
|
||||
"type":"cronut",
|
||||
"newkey":"remote"
|
||||
}`)
|
||||
|
||||
func initConfigs() {
|
||||
Reset()
|
||||
var r io.Reader
|
||||
SetConfigType("yaml")
|
||||
r = bytes.NewReader(yamlExample)
|
||||
unmarshalReader(r, v.config)
|
||||
|
||||
SetConfigType("json")
|
||||
r = bytes.NewReader(jsonExample)
|
||||
unmarshalReader(r, v.config)
|
||||
|
||||
SetConfigType("hcl")
|
||||
r = bytes.NewReader(hclExample)
|
||||
unmarshalReader(r, v.config)
|
||||
|
||||
SetConfigType("properties")
|
||||
r = bytes.NewReader(propertiesExample)
|
||||
unmarshalReader(r, v.config)
|
||||
|
||||
SetConfigType("toml")
|
||||
r = bytes.NewReader(tomlExample)
|
||||
unmarshalReader(r, v.config)
|
||||
|
||||
SetConfigType("json")
|
||||
remote := bytes.NewReader(remoteExample)
|
||||
unmarshalReader(remote, v.kvstore)
|
||||
}
|
||||
|
||||
func initConfig(typ, config string) {
|
||||
|
@ -151,10 +65,6 @@ func initConfig(typ, config string) {
|
|||
}
|
||||
}
|
||||
|
||||
func initYAML() {
|
||||
initConfig("yaml", string(yamlExample))
|
||||
}
|
||||
|
||||
func initJSON() {
|
||||
Reset()
|
||||
SetConfigType("json")
|
||||
|
@ -163,30 +73,6 @@ func initJSON() {
|
|||
unmarshalReader(r, v.config)
|
||||
}
|
||||
|
||||
func initProperties() {
|
||||
Reset()
|
||||
SetConfigType("properties")
|
||||
r := bytes.NewReader(propertiesExample)
|
||||
|
||||
unmarshalReader(r, v.config)
|
||||
}
|
||||
|
||||
func initTOML() {
|
||||
Reset()
|
||||
SetConfigType("toml")
|
||||
r := bytes.NewReader(tomlExample)
|
||||
|
||||
unmarshalReader(r, v.config)
|
||||
}
|
||||
|
||||
func initHcl() {
|
||||
Reset()
|
||||
SetConfigType("hcl")
|
||||
r := bytes.NewReader(hclExample)
|
||||
|
||||
unmarshalReader(r, v.config)
|
||||
}
|
||||
|
||||
// make directories for testing
|
||||
func initDirs(t *testing.T) (string, string, func()) {
|
||||
|
||||
|
@ -249,52 +135,6 @@ func (s *stringValue) String() string {
|
|||
return fmt.Sprintf("%s", *s)
|
||||
}
|
||||
|
||||
func TestBasics(t *testing.T) {
|
||||
SetConfigFile("/tmp/config.yaml")
|
||||
filename, err := v.getConfigFile()
|
||||
assert.Equal(t, "/tmp/config.yaml", filename)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestDefault(t *testing.T) {
|
||||
SetDefault("age", 45)
|
||||
assert.Equal(t, 45, Get("age"))
|
||||
|
||||
SetDefault("clothing.jacket", "slacks")
|
||||
assert.Equal(t, "slacks", Get("clothing.jacket"))
|
||||
|
||||
SetConfigType("yaml")
|
||||
err := ReadConfig(bytes.NewBuffer(yamlExample))
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "leather", Get("clothing.jacket"))
|
||||
}
|
||||
|
||||
func TestUnmarshaling(t *testing.T) {
|
||||
SetConfigType("yaml")
|
||||
r := bytes.NewReader(yamlExample)
|
||||
|
||||
unmarshalReader(r, v.config)
|
||||
assert.True(t, InConfig("name"))
|
||||
assert.False(t, InConfig("state"))
|
||||
assert.Equal(t, "steve", Get("name"))
|
||||
assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, Get("hobbies"))
|
||||
assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[string]interface{}{"size": "large"}}, Get("clothing"))
|
||||
assert.Equal(t, 35, Get("age"))
|
||||
}
|
||||
|
||||
func TestUnmarshalExact(t *testing.T) {
|
||||
vip := New()
|
||||
target := &testUnmarshalExtra{}
|
||||
vip.SetConfigType("yaml")
|
||||
r := bytes.NewReader(yamlExampleWithExtras)
|
||||
vip.ReadConfig(r)
|
||||
err := vip.UnmarshalExact(target)
|
||||
if err == nil {
|
||||
t.Fatal("UnmarshalExact should error when populating a struct from a conf that contains unused fields")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverrides(t *testing.T) {
|
||||
Set("age", 40)
|
||||
assert.Equal(t, 40, Get("age"))
|
||||
|
@ -322,52 +162,11 @@ func TestAliasInConfigFile(t *testing.T) {
|
|||
assert.Equal(t, false, Get("beard"))
|
||||
}
|
||||
|
||||
func TestYML(t *testing.T) {
|
||||
initYAML()
|
||||
assert.Equal(t, "steve", Get("name"))
|
||||
}
|
||||
|
||||
func TestJSON(t *testing.T) {
|
||||
initJSON()
|
||||
assert.Equal(t, "0001", Get("id"))
|
||||
}
|
||||
|
||||
func TestProperties(t *testing.T) {
|
||||
initProperties()
|
||||
assert.Equal(t, "0001", Get("p_id"))
|
||||
}
|
||||
|
||||
func TestTOML(t *testing.T) {
|
||||
initTOML()
|
||||
assert.Equal(t, "TOML Example", Get("title"))
|
||||
}
|
||||
|
||||
func TestHCL(t *testing.T) {
|
||||
initHcl()
|
||||
assert.Equal(t, "0001", Get("id"))
|
||||
assert.Equal(t, 0.55, Get("ppu"))
|
||||
assert.Equal(t, "donut", Get("type"))
|
||||
assert.Equal(t, "Cake", Get("name"))
|
||||
Set("id", "0002")
|
||||
assert.Equal(t, "0002", Get("id"))
|
||||
assert.NotEqual(t, "cronut", Get("type"))
|
||||
}
|
||||
|
||||
func TestRemotePrecedence(t *testing.T) {
|
||||
initJSON()
|
||||
|
||||
remote := bytes.NewReader(remoteExample)
|
||||
assert.Equal(t, "0001", Get("id"))
|
||||
unmarshalReader(remote, v.kvstore)
|
||||
assert.Equal(t, "0001", Get("id"))
|
||||
assert.NotEqual(t, "cronut", Get("type"))
|
||||
assert.Equal(t, "remote", Get("newkey"))
|
||||
Set("newkey", "newvalue")
|
||||
assert.NotEqual(t, "remote", Get("newkey"))
|
||||
assert.Equal(t, "newvalue", Get("newkey"))
|
||||
Set("newkey", "remote")
|
||||
}
|
||||
|
||||
func TestEnv(t *testing.T) {
|
||||
initJSON()
|
||||
|
||||
|
@ -786,31 +585,6 @@ func TestFindsNestedKeys(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
func TestReadBufConfig(t *testing.T) {
|
||||
v := New()
|
||||
v.SetConfigType("yaml")
|
||||
v.ReadConfig(bytes.NewBuffer(yamlExample))
|
||||
t.Log(v.AllKeys())
|
||||
|
||||
assert.True(t, v.InConfig("name"))
|
||||
assert.False(t, v.InConfig("state"))
|
||||
assert.Equal(t, "steve", v.Get("name"))
|
||||
assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, v.Get("hobbies"))
|
||||
assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[string]interface{}{"size": "large"}}, v.Get("clothing"))
|
||||
assert.Equal(t, 35, v.Get("age"))
|
||||
}
|
||||
|
||||
func TestIsSet(t *testing.T) {
|
||||
v := New()
|
||||
v.SetConfigType("yaml")
|
||||
v.ReadConfig(bytes.NewBuffer(yamlExample))
|
||||
assert.True(t, v.IsSet("clothing.jacket"))
|
||||
assert.False(t, v.IsSet("clothing.jackets"))
|
||||
assert.False(t, v.IsSet("helloworld"))
|
||||
v.Set("helloworld", "fubar")
|
||||
assert.True(t, v.IsSet("helloworld"))
|
||||
}
|
||||
|
||||
func TestDirsSearch(t *testing.T) {
|
||||
|
||||
root, config, cleanup := initDirs(t)
|
||||
|
@ -873,70 +647,6 @@ func TestWrongDirsSearchNotFoundForMerge(t *testing.T) {
|
|||
assert.Equal(t, `default`, v.GetString(`key`))
|
||||
}
|
||||
|
||||
func TestSub(t *testing.T) {
|
||||
v := New()
|
||||
v.SetConfigType("yaml")
|
||||
v.ReadConfig(bytes.NewBuffer(yamlExample))
|
||||
|
||||
subv := v.Sub("clothing")
|
||||
assert.Equal(t, v.Get("clothing.pants.size"), subv.Get("pants.size"))
|
||||
|
||||
subv = v.Sub("clothing.pants")
|
||||
assert.Equal(t, v.Get("clothing.pants.size"), subv.Get("size"))
|
||||
|
||||
subv = v.Sub("clothing.pants.size")
|
||||
assert.Equal(t, (*Viper)(nil), subv)
|
||||
|
||||
subv = v.Sub("missing.key")
|
||||
assert.Equal(t, (*Viper)(nil), subv)
|
||||
}
|
||||
|
||||
var hclWriteExpected = []byte(`"foos" = {
|
||||
"foo" = {
|
||||
"key" = 1
|
||||
}
|
||||
|
||||
"foo" = {
|
||||
"key" = 2
|
||||
}
|
||||
|
||||
"foo" = {
|
||||
"key" = 3
|
||||
}
|
||||
|
||||
"foo" = {
|
||||
"key" = 4
|
||||
}
|
||||
}
|
||||
|
||||
"id" = "0001"
|
||||
|
||||
"name" = "Cake"
|
||||
|
||||
"ppu" = 0.55
|
||||
|
||||
"type" = "donut"`)
|
||||
|
||||
func TestWriteConfigHCL(t *testing.T) {
|
||||
v := New()
|
||||
fs := afero.NewMemMapFs()
|
||||
v.SetFs(fs)
|
||||
v.SetConfigName("c")
|
||||
v.SetConfigType("hcl")
|
||||
err := v.ReadConfig(bytes.NewBuffer(hclExample))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := v.WriteConfigAs("c.hcl"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
read, err := afero.ReadFile(fs, "c.hcl")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, hclWriteExpected, read)
|
||||
}
|
||||
|
||||
var jsonWriteExpected = []byte(`{
|
||||
"batters": {
|
||||
"batter": [
|
||||
|
@ -987,211 +697,6 @@ p_ppu = 0.55
|
|||
p_batters.batter.type = Regular
|
||||
`)
|
||||
|
||||
func TestWriteConfigProperties(t *testing.T) {
|
||||
v := New()
|
||||
fs := afero.NewMemMapFs()
|
||||
v.SetFs(fs)
|
||||
v.SetConfigName("c")
|
||||
v.SetConfigType("properties")
|
||||
err := v.ReadConfig(bytes.NewBuffer(propertiesExample))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := v.WriteConfigAs("c.properties"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
read, err := afero.ReadFile(fs, "c.properties")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, propertiesWriteExpected, read)
|
||||
}
|
||||
|
||||
func TestWriteConfigTOML(t *testing.T) {
|
||||
fs := afero.NewMemMapFs()
|
||||
v := New()
|
||||
v.SetFs(fs)
|
||||
v.SetConfigName("c")
|
||||
v.SetConfigType("toml")
|
||||
err := v.ReadConfig(bytes.NewBuffer(tomlExample))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := v.WriteConfigAs("c.toml"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// The TOML String method does not order the contents.
|
||||
// Therefore, we must read the generated file and compare the data.
|
||||
v2 := New()
|
||||
v2.SetFs(fs)
|
||||
v2.SetConfigName("c")
|
||||
v2.SetConfigType("toml")
|
||||
v2.SetConfigFile("c.toml")
|
||||
err = v2.ReadInConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, v.GetString("title"), v2.GetString("title"))
|
||||
assert.Equal(t, v.GetString("owner.bio"), v2.GetString("owner.bio"))
|
||||
assert.Equal(t, v.GetString("owner.dob"), v2.GetString("owner.dob"))
|
||||
assert.Equal(t, v.GetString("owner.organization"), v2.GetString("owner.organization"))
|
||||
}
|
||||
|
||||
var yamlWriteExpected = []byte(`age: 35
|
||||
beard: true
|
||||
clothing:
|
||||
jacket: leather
|
||||
pants:
|
||||
size: large
|
||||
trousers: denim
|
||||
eyes: brown
|
||||
hacker: true
|
||||
hobbies:
|
||||
- skateboarding
|
||||
- snowboarding
|
||||
- go
|
||||
name: steve
|
||||
`)
|
||||
|
||||
func TestWriteConfigYAML(t *testing.T) {
|
||||
v := New()
|
||||
fs := afero.NewMemMapFs()
|
||||
v.SetFs(fs)
|
||||
v.SetConfigName("c")
|
||||
v.SetConfigType("yaml")
|
||||
err := v.ReadConfig(bytes.NewBuffer(yamlExample))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := v.WriteConfigAs("c.yaml"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
read, err := afero.ReadFile(fs, "c.yaml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, yamlWriteExpected, read)
|
||||
}
|
||||
|
||||
var yamlMergeExampleTgt = []byte(`
|
||||
hello:
|
||||
pop: 37890
|
||||
lagrenum: 765432101234567
|
||||
world:
|
||||
- us
|
||||
- uk
|
||||
- fr
|
||||
- de
|
||||
`)
|
||||
|
||||
var yamlMergeExampleSrc = []byte(`
|
||||
hello:
|
||||
pop: 45000
|
||||
lagrenum: 7654321001234567
|
||||
universe:
|
||||
- mw
|
||||
- ad
|
||||
fu: bar
|
||||
`)
|
||||
|
||||
func TestMergeConfig(t *testing.T) {
|
||||
v := New()
|
||||
v.SetConfigType("yml")
|
||||
if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if pop := v.GetInt("hello.pop"); pop != 37890 {
|
||||
t.Fatalf("pop != 37890, = %d", pop)
|
||||
}
|
||||
|
||||
if pop := v.GetInt32("hello.pop"); pop != int32(37890) {
|
||||
t.Fatalf("pop != 37890, = %d", pop)
|
||||
}
|
||||
|
||||
if pop := v.GetInt64("hello.lagrenum"); pop != int64(765432101234567) {
|
||||
t.Fatalf("int64 lagrenum != 765432101234567, = %d", pop)
|
||||
}
|
||||
|
||||
if world := v.GetStringSlice("hello.world"); len(world) != 4 {
|
||||
t.Fatalf("len(world) != 4, = %d", len(world))
|
||||
}
|
||||
|
||||
if fu := v.GetString("fu"); fu != "" {
|
||||
t.Fatalf("fu != \"\", = %s", fu)
|
||||
}
|
||||
|
||||
if err := v.MergeConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if pop := v.GetInt("hello.pop"); pop != 45000 {
|
||||
t.Fatalf("pop != 45000, = %d", pop)
|
||||
}
|
||||
|
||||
if pop := v.GetInt32("hello.pop"); pop != int32(45000) {
|
||||
t.Fatalf("pop != 45000, = %d", pop)
|
||||
}
|
||||
|
||||
if pop := v.GetInt64("hello.lagrenum"); pop != int64(7654321001234567) {
|
||||
t.Fatalf("int64 lagrenum != 7654321001234567, = %d", pop)
|
||||
}
|
||||
|
||||
if world := v.GetStringSlice("hello.world"); len(world) != 4 {
|
||||
t.Fatalf("len(world) != 4, = %d", len(world))
|
||||
}
|
||||
|
||||
if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 {
|
||||
t.Fatalf("len(universe) != 2, = %d", len(universe))
|
||||
}
|
||||
|
||||
if fu := v.GetString("fu"); fu != "bar" {
|
||||
t.Fatalf("fu != \"bar\", = %s", fu)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeConfigNoMerge(t *testing.T) {
|
||||
v := New()
|
||||
v.SetConfigType("yml")
|
||||
if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if pop := v.GetInt("hello.pop"); pop != 37890 {
|
||||
t.Fatalf("pop != 37890, = %d", pop)
|
||||
}
|
||||
|
||||
if world := v.GetStringSlice("hello.world"); len(world) != 4 {
|
||||
t.Fatalf("len(world) != 4, = %d", len(world))
|
||||
}
|
||||
|
||||
if fu := v.GetString("fu"); fu != "" {
|
||||
t.Fatalf("fu != \"\", = %s", fu)
|
||||
}
|
||||
|
||||
if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if pop := v.GetInt("hello.pop"); pop != 45000 {
|
||||
t.Fatalf("pop != 45000, = %d", pop)
|
||||
}
|
||||
|
||||
if world := v.GetStringSlice("hello.world"); len(world) != 0 {
|
||||
t.Fatalf("len(world) != 0, = %d", len(world))
|
||||
}
|
||||
|
||||
if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 {
|
||||
t.Fatalf("len(universe) != 2, = %d", len(universe))
|
||||
}
|
||||
|
||||
if fu := v.GetString("fu"); fu != "bar" {
|
||||
t.Fatalf("fu != \"bar\", = %s", fu)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalingWithAliases(t *testing.T) {
|
||||
v := New()
|
||||
v.SetDefault("ID", 1)
|
||||
|
@ -1217,16 +722,6 @@ func TestUnmarshalingWithAliases(t *testing.T) {
|
|||
assert.Equal(t, &config{ID: 1, FirstName: "Steve", Surname: "Owen"}, &C)
|
||||
}
|
||||
|
||||
func TestSetConfigNameClearsFileCache(t *testing.T) {
|
||||
SetConfigFile("/tmp/config.yaml")
|
||||
SetConfigName("default")
|
||||
f, err := v.getConfigFile()
|
||||
if err == nil {
|
||||
t.Fatalf("config file cache should have been cleared")
|
||||
}
|
||||
assert.Empty(t, f)
|
||||
}
|
||||
|
||||
func TestShadowedNestedValue(t *testing.T) {
|
||||
|
||||
config := `name: steve
|
||||
|
@ -1264,51 +759,6 @@ func TestDotParameter(t *testing.T) {
|
|||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestCaseInsensitive(t *testing.T) {
|
||||
for _, config := range []struct {
|
||||
typ string
|
||||
content string
|
||||
}{
|
||||
{"yaml", `
|
||||
aBcD: 1
|
||||
eF:
|
||||
gH: 2
|
||||
iJk: 3
|
||||
Lm:
|
||||
nO: 4
|
||||
P:
|
||||
Q: 5
|
||||
R: 6
|
||||
`},
|
||||
{"json", `{
|
||||
"aBcD": 1,
|
||||
"eF": {
|
||||
"iJk": 3,
|
||||
"Lm": {
|
||||
"P": {
|
||||
"Q": 5,
|
||||
"R": 6
|
||||
},
|
||||
"nO": 4
|
||||
},
|
||||
"gH": 2
|
||||
}
|
||||
}`},
|
||||
{"toml", `aBcD = 1
|
||||
[eF]
|
||||
gH = 2
|
||||
iJk = 3
|
||||
[eF.Lm]
|
||||
nO = 4
|
||||
[eF.Lm.P]
|
||||
Q = 5
|
||||
R = 6
|
||||
`},
|
||||
} {
|
||||
doTestCaseInsensitive(t, config.typ, config.content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCaseInsensitiveSet(t *testing.T) {
|
||||
Reset()
|
||||
m1 := map[string]interface{}{
|
||||
|
@ -1368,35 +818,6 @@ 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)
|
||||
|
@ -1411,107 +832,6 @@ func doTestCaseInsensitive(t *testing.T, typ, config string) {
|
|||
|
||||
}
|
||||
|
||||
func newViperWithConfigFile(t *testing.T) (*Viper, string, func()) {
|
||||
watchDir, err := ioutil.TempDir("", "")
|
||||
require.Nil(t, err)
|
||||
configFile := path.Join(watchDir, "config.yaml")
|
||||
err = ioutil.WriteFile(configFile, []byte("foo: bar\n"), 0640)
|
||||
require.Nil(t, err)
|
||||
cleanup := func() {
|
||||
os.RemoveAll(watchDir)
|
||||
}
|
||||
v := New()
|
||||
v.SetConfigFile(configFile)
|
||||
err = v.ReadInConfig()
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "bar", v.Get("foo"))
|
||||
return v, configFile, cleanup
|
||||
}
|
||||
|
||||
func newViperWithSymlinkedConfigFile(t *testing.T) (*Viper, string, string, func()) {
|
||||
watchDir, err := ioutil.TempDir("", "")
|
||||
require.Nil(t, err)
|
||||
dataDir1 := path.Join(watchDir, "data1")
|
||||
err = os.Mkdir(dataDir1, 0777)
|
||||
require.Nil(t, err)
|
||||
realConfigFile := path.Join(dataDir1, "config.yaml")
|
||||
t.Logf("Real config file location: %s\n", realConfigFile)
|
||||
err = ioutil.WriteFile(realConfigFile, []byte("foo: bar\n"), 0640)
|
||||
require.Nil(t, err)
|
||||
cleanup := func() {
|
||||
os.RemoveAll(watchDir)
|
||||
}
|
||||
// now, symlink the tm `data1` dir to `data` in the baseDir
|
||||
os.Symlink(dataDir1, path.Join(watchDir, "data"))
|
||||
// and link the `<watchdir>/datadir1/config.yaml` to `<watchdir>/config.yaml`
|
||||
configFile := path.Join(watchDir, "config.yaml")
|
||||
os.Symlink(path.Join(watchDir, "data", "config.yaml"), configFile)
|
||||
t.Logf("Config file location: %s\n", path.Join(watchDir, "config.yaml"))
|
||||
// init Viper
|
||||
v := New()
|
||||
v.SetConfigFile(configFile)
|
||||
err = v.ReadInConfig()
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "bar", v.Get("foo"))
|
||||
return v, watchDir, configFile, cleanup
|
||||
}
|
||||
|
||||
func TestWatchFile(t *testing.T) {
|
||||
|
||||
t.Run("file content changed", func(t *testing.T) {
|
||||
// given a `config.yaml` file being watched
|
||||
v, configFile, cleanup := newViperWithConfigFile(t)
|
||||
defer cleanup()
|
||||
_, err := os.Stat(configFile)
|
||||
require.NoError(t, err)
|
||||
t.Logf("test config file: %s\n", configFile)
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
v.OnConfigChange(func(in fsnotify.Event) {
|
||||
t.Logf("config file changed")
|
||||
wg.Done()
|
||||
})
|
||||
v.WatchConfig()
|
||||
// when overwriting the file and waiting for the custom change notification handler to be triggered
|
||||
err = ioutil.WriteFile(configFile, []byte("foo: baz\n"), 0640)
|
||||
wg.Wait()
|
||||
// then the config value should have changed
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, "baz", v.Get("foo"))
|
||||
})
|
||||
|
||||
t.Run("link to real file changed (à la Kubernetes)", func(t *testing.T) {
|
||||
// skip if not executed on Linux
|
||||
if runtime.GOOS != "linux" {
|
||||
t.Skipf("Skipping test as symlink replacements don't work on non-linux environment...")
|
||||
}
|
||||
v, watchDir, _, _ := newViperWithSymlinkedConfigFile(t)
|
||||
// defer cleanup()
|
||||
wg := sync.WaitGroup{}
|
||||
v.WatchConfig()
|
||||
v.OnConfigChange(func(in fsnotify.Event) {
|
||||
t.Logf("config file changed")
|
||||
wg.Done()
|
||||
})
|
||||
wg.Add(1)
|
||||
// when link to another `config.yaml` file
|
||||
dataDir2 := path.Join(watchDir, "data2")
|
||||
err := os.Mkdir(dataDir2, 0777)
|
||||
require.Nil(t, err)
|
||||
configFile2 := path.Join(dataDir2, "config.yaml")
|
||||
err = ioutil.WriteFile(configFile2, []byte("foo: baz\n"), 0640)
|
||||
require.Nil(t, err)
|
||||
// change the symlink using the `ln -sfn` command
|
||||
err = exec.Command("ln", "-sfn", dataDir2, path.Join(watchDir, "data")).Run()
|
||||
require.Nil(t, err)
|
||||
wg.Wait()
|
||||
// then
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, "baz", v.Get("foo"))
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkGetBool(b *testing.B) {
|
||||
key := "BenchmarkGetBool"
|
||||
v = New()
|
||||
|
|
Loading…
Add table
Reference in a new issue