--- a/vendor/github.com/spf13/viper/viper.go Wed Sep 18 19:17:42 2019 +0200
+++ b/vendor/github.com/spf13/viper/viper.go Sun Feb 16 18:54:01 2020 +0100
@@ -3,7 +3,7 @@
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
-// Viper is a application configuration system.
+// Viper is an application configuration system.
// It believes that applications can be configured a variety of ways
// via flags, ENVIRONMENT variables, configuration files retrieved
// from the file system, or a remote key/value store.
@@ -23,6 +23,7 @@
"bytes"
"encoding/csv"
"encoding/json"
+ "errors"
"fmt"
"io"
"log"
@@ -30,20 +31,22 @@
"path/filepath"
"reflect"
"strings"
+ "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/pelletier/go-toml"
"github.com/spf13/afero"
"github.com/spf13/cast"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/pflag"
+ "github.com/subosito/gotenv"
+ "gopkg.in/ini.v1"
+ "gopkg.in/yaml.v2"
)
// ConfigMarshalError happens when failing to marshal the configuration.
@@ -113,6 +116,14 @@
return fmt.Sprintf("Config File %q Not Found in %q", fnfe.name, fnfe.locations)
}
+// ConfigFileAlreadyExistsError denotes failure to write new configuration file.
+type ConfigFileAlreadyExistsError string
+
+// Error returns the formatted error when configuration already exists.
+func (faee ConfigFileAlreadyExistsError) Error() string {
+ return fmt.Sprintf("Config File %q Already Exists", string(faee))
+}
+
// A DecoderConfigOption can be passed to viper.Unmarshal to configure
// mapstructure.DecoderConfig options
type DecoderConfigOption func(*mapstructure.DecoderConfig)
@@ -179,13 +190,15 @@
remoteProviders []*defaultRemoteProvider
// Name of file to look for inside the path
- configName string
- configFile string
- configType string
- envPrefix string
+ configName string
+ configFile string
+ configType string
+ configPermissions os.FileMode
+ envPrefix string
automaticEnvApplied bool
- envKeyReplacer *strings.Replacer
+ envKeyReplacer StringReplacer
+ allowEmptyEnv bool
config map[string]interface{}
override map[string]interface{}
@@ -208,6 +221,7 @@
v := new(Viper)
v.keyDelim = "."
v.configName = "config"
+ v.configPermissions = os.FileMode(0644)
v.fs = afero.NewOsFs()
v.config = make(map[string]interface{})
v.override = make(map[string]interface{})
@@ -221,12 +235,58 @@
return v
}
-// Intended for testing, will reset all to default settings.
+// Option configures Viper using the functional options paradigm popularized by Rob Pike and Dave Cheney.
+// If you're unfamiliar with this style,
+// see https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html and
+// https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis.
+type Option interface {
+ apply(v *Viper)
+}
+
+type optionFunc func(v *Viper)
+
+func (fn optionFunc) apply(v *Viper) {
+ fn(v)
+}
+
+// KeyDelimiter sets the delimiter used for determining key parts.
+// By default it's value is ".".
+func KeyDelimiter(d string) Option {
+ return optionFunc(func(v *Viper) {
+ v.keyDelim = d
+ })
+}
+
+// StringReplacer applies a set of replacements to a string.
+type StringReplacer interface {
+ // Replace returns a copy of s with all replacements performed.
+ Replace(s string) string
+}
+
+// EnvKeyReplacer sets a replacer used for mapping environment variables to internal keys.
+func EnvKeyReplacer(r StringReplacer) Option {
+ return optionFunc(func(v *Viper) {
+ v.envKeyReplacer = r
+ })
+}
+
+// NewWithOptions creates a new Viper instance.
+func NewWithOptions(opts ...Option) *Viper {
+ v := New()
+
+ for _, opt := range opts {
+ opt.apply(v)
+ }
+
+ return v
+}
+
+// Reset is intended for testing, will reset all to default settings.
// In the public interface for the viper package so applications
// can use it in their testing as well.
func Reset() {
v = New()
- SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl"}
+ SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "dotenv", "env", "ini"}
SupportedRemoteProviders = []string{"etcd", "consul"}
}
@@ -265,7 +325,7 @@
}
// SupportedExts are universally supported extensions.
-var SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl"}
+var SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "dotenv", "env", "ini"}
// SupportedRemoteProviders are universally supported remote providers.
var SupportedRemoteProviders = []string{"etcd", "consul"}
@@ -276,50 +336,74 @@
}
func WatchConfig() { v.WatchConfig() }
+
func (v *Viper) WatchConfig() {
+ initWG := sync.WaitGroup{}
+ initWG.Add(1)
go func() {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
-
// we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way
filename, err := v.getConfigFile()
if err != nil {
- log.Println("error:", err)
+ log.Printf("error: %v\n", err)
+ initWG.Done()
return
}
configFile := filepath.Clean(filename)
configDir, _ := filepath.Split(configFile)
+ realConfigFile, _ := filepath.EvalSymlinks(filename)
- done := make(chan bool)
+ eventsWG := sync.WaitGroup{}
+ eventsWG.Add(1)
go func() {
for {
select {
- case event := <-watcher.Events:
- // we only care about the config file
- if filepath.Clean(event.Name) == configFile {
- if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create {
- err := v.ReadInConfig()
- if err != nil {
- log.Println("error:", err)
- }
- if v.onConfigChange != nil {
- v.onConfigChange(event)
- }
+ case event, ok := <-watcher.Events:
+ if !ok { // 'Events' channel is closed
+ eventsWG.Done()
+ return
+ }
+ currentConfigFile, _ := filepath.EvalSymlinks(filename)
+ // we only care about the config file with the following cases:
+ // 1 - if the config file was modified or created
+ // 2 - if the real path to the config file changed (eg: k8s ConfigMap replacement)
+ const writeOrCreateMask = fsnotify.Write | fsnotify.Create
+ if (filepath.Clean(event.Name) == configFile &&
+ event.Op&writeOrCreateMask != 0) ||
+ (currentConfigFile != "" && currentConfigFile != realConfigFile) {
+ realConfigFile = currentConfigFile
+ err := v.ReadInConfig()
+ if err != nil {
+ log.Printf("error reading config file: %v\n", err)
}
+ if v.onConfigChange != nil {
+ v.onConfigChange(event)
+ }
+ } else if filepath.Clean(event.Name) == configFile &&
+ event.Op&fsnotify.Remove&fsnotify.Remove != 0 {
+ eventsWG.Done()
+ return
}
- case err := <-watcher.Errors:
- log.Println("error:", err)
+
+ case err, ok := <-watcher.Errors:
+ if ok { // 'Errors' channel is not closed
+ log.Printf("watcher error: %v\n", err)
+ }
+ eventsWG.Done()
+ return
}
}
}()
-
watcher.Add(configDir)
- <-done
+ initWG.Done() // done initializing the watch in this go routine, so the parent routine can move on...
+ eventsWG.Wait() // now, wait for event loop to end in this go-routine...
}()
+ initWG.Wait() // make sure that the go routine above fully ended before returning
}
// SetConfigFile explicitly defines the path, name and extension of the config file.
@@ -349,6 +433,14 @@
return strings.ToUpper(in)
}
+// AllowEmptyEnv tells Viper to consider set,
+// but empty environment variables as valid values instead of falling back.
+// For backward compatibility reasons this is false by default.
+func AllowEmptyEnv(allowEmptyEnv bool) { v.AllowEmptyEnv(allowEmptyEnv) }
+func (v *Viper) AllowEmptyEnv(allowEmptyEnv bool) {
+ v.allowEmptyEnv = allowEmptyEnv
+}
+
// TODO: should getEnv logic be moved into find(). Can generalize the use of
// rewriting keys many things, Ex: Get('someKey') -> some_key
// (camel case to snake case for JSON keys perhaps)
@@ -356,11 +448,14 @@
// getEnv is a wrapper around os.Getenv which replaces characters in the original
// key. This allows env vars which have different keys than the config object
// keys.
-func (v *Viper) getEnv(key string) string {
+func (v *Viper) getEnv(key string) (string, bool) {
if v.envKeyReplacer != nil {
key = v.envKeyReplacer.Replace(key)
}
- return os.Getenv(key)
+
+ val, ok := os.LookupEnv(key)
+
+ return val, ok && (v.allowEmptyEnv || val != "")
}
// ConfigFileUsed returns the file used to populate the config registry.
@@ -587,10 +682,9 @@
// "foo.bar.baz" in a lower-priority map
func (v *Viper) isPathShadowedInAutoEnv(path []string) string {
var parentKey string
- var val string
for i := 1; i < len(path); i++ {
parentKey = strings.Join(path[0:i], v.keyDelim)
- if val = v.getEnv(v.mergeWithEnvPrefix(parentKey)); val != "" {
+ if _, ok := v.getEnv(v.mergeWithEnvPrefix(parentKey)); ok {
return parentKey
}
}
@@ -631,7 +725,7 @@
func Get(key string) interface{} { return v.Get(key) }
func (v *Viper) Get(key string) interface{} {
lcaseKey := strings.ToLower(key)
- val := v.find(lcaseKey)
+ val := v.find(lcaseKey, true)
if val == nil {
return nil
}
@@ -652,6 +746,12 @@
return cast.ToString(val)
case int32, int16, int8, int:
return cast.ToInt(val)
+ case uint:
+ return cast.ToUint(val)
+ case uint32:
+ return cast.ToUint32(val)
+ case uint64:
+ return cast.ToUint64(val)
case int64:
return cast.ToInt64(val)
case float64, float32:
@@ -662,6 +762,8 @@
return cast.ToDuration(val)
case []string:
return cast.ToStringSlice(val)
+ case []int:
+ return cast.ToIntSlice(val)
}
}
@@ -715,6 +817,24 @@
return cast.ToInt64(v.Get(key))
}
+// GetUint returns the value associated with the key as an unsigned integer.
+func GetUint(key string) uint { return v.GetUint(key) }
+func (v *Viper) GetUint(key string) uint {
+ return cast.ToUint(v.Get(key))
+}
+
+// GetUint32 returns the value associated with the key as an unsigned integer.
+func GetUint32(key string) uint32 { return v.GetUint32(key) }
+func (v *Viper) GetUint32(key string) uint32 {
+ return cast.ToUint32(v.Get(key))
+}
+
+// GetUint64 returns the value associated with the key as an unsigned integer.
+func GetUint64(key string) uint64 { return v.GetUint64(key) }
+func (v *Viper) GetUint64(key string) uint64 {
+ return cast.ToUint64(v.Get(key))
+}
+
// GetFloat64 returns the value associated with the key as a float64.
func GetFloat64(key string) float64 { return v.GetFloat64(key) }
func (v *Viper) GetFloat64(key string) float64 {
@@ -733,6 +853,12 @@
return cast.ToDuration(v.Get(key))
}
+// GetIntSlice returns the value associated with the key as a slice of int values.
+func GetIntSlice(key string) []int { return v.GetIntSlice(key) }
+func (v *Viper) GetIntSlice(key string) []int {
+ return cast.ToIntSlice(v.Get(key))
+}
+
// GetStringSlice returns the value associated with the key as a slice of strings.
func GetStringSlice(key string) []string { return v.GetStringSlice(key) }
func (v *Viper) GetStringSlice(key string) []string {
@@ -776,8 +902,6 @@
return err
}
- v.insensitiviseMaps()
-
return nil
}
@@ -793,8 +917,6 @@
return err
}
- v.insensitiviseMaps()
-
return nil
}
@@ -827,8 +949,11 @@
// UnmarshalExact unmarshals the config into a Struct, erroring if a field is nonexistent
// in the destination struct.
-func (v *Viper) UnmarshalExact(rawVal interface{}) error {
- config := defaultDecoderConfig(rawVal)
+func UnmarshalExact(rawVal interface{}, opts ...DecoderConfigOption) error {
+ return v.UnmarshalExact(rawVal, opts...)
+}
+func (v *Viper) UnmarshalExact(rawVal interface{}, opts ...DecoderConfigOption) error {
+ config := defaultDecoderConfig(rawVal, opts...)
config.ErrorUnused = true
err := decode(v.AllSettings(), config)
@@ -837,8 +962,6 @@
return err
}
- v.insensitiviseMaps()
-
return nil
}
@@ -895,7 +1018,7 @@
func (v *Viper) BindEnv(input ...string) error {
var key, envkey string
if len(input) == 0 {
- return fmt.Errorf("BindEnv missing key to bind to")
+ return fmt.Errorf("missing key to bind to")
}
key = strings.ToLower(input[0])
@@ -912,12 +1035,15 @@
}
// Given a key, find the value.
-// Viper will check in the following order:
-// flag, env, config file, key/value store, default.
+//
// Viper will check to see if an alias exists first.
+// Viper will then check in the following order:
+// flag, env, config file, key/value store.
+// Lastly, if no value was found and flagDefault is true, and if the key
+// corresponds to a flag, the flag's default value is returned.
+//
// Note: this assumes a lower-cased key given.
-func (v *Viper) find(lcaseKey string) interface{} {
-
+func (v *Viper) find(lcaseKey string, flagDefault bool) interface{} {
var (
val interface{}
exists bool
@@ -957,6 +1083,11 @@
s = strings.TrimSuffix(s, "]")
res, _ := readAsCSV(s)
return res
+ case "intSlice":
+ s := strings.TrimPrefix(flag.ValueString(), "[")
+ s = strings.TrimSuffix(s, "]")
+ res, _ := readAsCSV(s)
+ return cast.ToIntSlice(res)
default:
return flag.ValueString()
}
@@ -969,7 +1100,7 @@
if v.automaticEnvApplied {
// even if it hasn't been registered, if automaticEnv is used,
// check any Get request
- if val = v.getEnv(v.mergeWithEnvPrefix(lcaseKey)); val != "" {
+ if val, ok := v.getEnv(v.mergeWithEnvPrefix(lcaseKey)); ok {
return val
}
if nested && v.isPathShadowedInAutoEnv(path) != "" {
@@ -978,7 +1109,7 @@
}
envkey, exists := v.env[lcaseKey]
if exists {
- if val = v.getEnv(envkey); val != "" {
+ if val, ok := v.getEnv(envkey); ok {
return val
}
}
@@ -1013,24 +1144,31 @@
return nil
}
- // last chance: if no other value is returned and a flag does exist for the value,
- // get the flag's value even if the flag's value has not changed
- if flag, exists := v.pflags[lcaseKey]; exists {
- switch flag.ValueType() {
- case "int", "int8", "int16", "int32", "int64":
- return cast.ToInt(flag.ValueString())
- case "bool":
- return cast.ToBool(flag.ValueString())
- case "stringSlice":
- s := strings.TrimPrefix(flag.ValueString(), "[")
- s = strings.TrimSuffix(s, "]")
- res, _ := readAsCSV(s)
- return res
- default:
- return flag.ValueString()
+ if flagDefault {
+ // last chance: if no value is found and a flag does exist for the key,
+ // get the flag's default value even if the flag's value has not been set.
+ if flag, exists := v.pflags[lcaseKey]; exists {
+ switch flag.ValueType() {
+ case "int", "int8", "int16", "int32", "int64":
+ return cast.ToInt(flag.ValueString())
+ case "bool":
+ return cast.ToBool(flag.ValueString())
+ case "stringSlice":
+ s := strings.TrimPrefix(flag.ValueString(), "[")
+ s = strings.TrimSuffix(s, "]")
+ res, _ := readAsCSV(s)
+ return res
+ case "intSlice":
+ s := strings.TrimPrefix(flag.ValueString(), "[")
+ s = strings.TrimSuffix(s, "]")
+ res, _ := readAsCSV(s)
+ return cast.ToIntSlice(res)
+ default:
+ return flag.ValueString()
+ }
}
+ // last item, no need to check shadowing
}
- // last item, no need to check shadowing
return nil
}
@@ -1049,7 +1187,7 @@
func IsSet(key string) bool { return v.IsSet(key) }
func (v *Viper) IsSet(key string) bool {
lcaseKey := strings.ToLower(key)
- val := v.find(lcaseKey)
+ val := v.find(lcaseKey, false)
return val != nil
}
@@ -1068,8 +1206,8 @@
v.envKeyReplacer = r
}
-// Aliases provide another accessor for the same key.
-// This enables one to change a name without breaking the application
+// RegisterAlias creates an alias that provides another accessor for the same key.
+// This enables one to change a name without breaking the application.
func RegisterAlias(alias string, key string) { v.RegisterAlias(alias, key) }
func (v *Viper) RegisterAlias(alias string, key string) {
v.registerAlias(alias, strings.ToLower(key))
@@ -1224,13 +1362,21 @@
// MergeConfig merges a new configuration with an existing config.
func MergeConfig(in io.Reader) error { return v.MergeConfig(in) }
func (v *Viper) MergeConfig(in io.Reader) error {
- if v.config == nil {
- v.config = make(map[string]interface{})
- }
cfg := make(map[string]interface{})
if err := v.unmarshalReader(in, cfg); err != nil {
return err
}
+ return v.MergeConfigMap(cfg)
+}
+
+// MergeConfigMap merges the configuration from the map given with an existing config.
+// Note that the map given may be modified.
+func MergeConfigMap(cfg map[string]interface{}) error { return v.MergeConfigMap(cfg) }
+func (v *Viper) MergeConfigMap(cfg map[string]interface{}) error {
+ if v.config == nil {
+ v.config = make(map[string]interface{})
+ }
+ insensitiviseMap(cfg)
mergeMaps(cfg, v.config, nil)
return nil
}
@@ -1248,11 +1394,10 @@
// SafeWriteConfig writes current configuration to file only if the file does not exist.
func SafeWriteConfig() error { return v.SafeWriteConfig() }
func (v *Viper) SafeWriteConfig() error {
- filename, err := v.getConfigFile()
- if err != nil {
- return err
+ if len(v.configPaths) < 1 {
+ return errors.New("missing configuration for 'configPath'")
}
- return v.writeConfig(filename, false)
+ return v.SafeWriteConfigAs(filepath.Join(v.configPaths[0], v.configName+"."+v.configType))
}
// WriteConfigAs writes current configuration to a given filename.
@@ -1264,15 +1409,18 @@
// SafeWriteConfigAs writes current configuration to a given filename if it does not exist.
func SafeWriteConfigAs(filename string) error { return v.SafeWriteConfigAs(filename) }
func (v *Viper) SafeWriteConfigAs(filename string) error {
+ alreadyExists, err := afero.Exists(v.fs, filename)
+ if alreadyExists && err == nil {
+ return ConfigFileAlreadyExistsError(filename)
+ }
return v.writeConfig(filename, false)
}
-func writeConfig(filename string, force bool) error { return v.writeConfig(filename, force) }
func (v *Viper) writeConfig(filename string, force bool) error {
jww.INFO.Println("Attempting to write configuration to file.")
ext := filepath.Ext(filename)
if len(ext) <= 1 {
- return fmt.Errorf("Filename: %s requires valid extension.", filename)
+ return fmt.Errorf("filename: %s requires valid extension", filename)
}
configType := ext[1:]
if !stringInSlice(configType, SupportedExts) {
@@ -1281,21 +1429,21 @@
if v.config == nil {
v.config = make(map[string]interface{})
}
- var flags int
- if force == true {
- flags = os.O_CREATE | os.O_TRUNC | os.O_WRONLY
- } else {
- if _, err := os.Stat(filename); os.IsNotExist(err) {
- flags = os.O_WRONLY
- } else {
- return fmt.Errorf("File: %s exists. Use WriteConfig to overwrite.", filename)
- }
+ flags := os.O_CREATE | os.O_TRUNC | os.O_WRONLY
+ if !force {
+ flags |= os.O_EXCL
}
- f, err := v.fs.OpenFile(filename, flags, os.FileMode(0644))
+ f, err := v.fs.OpenFile(filename, flags, v.configPermissions)
if err != nil {
return err
}
- return v.marshalWriter(f, configType)
+ defer f.Close()
+
+ if err := v.marshalWriter(f, configType); err != nil {
+ return err
+ }
+
+ return f.Sync()
}
// Unmarshal a Reader into a map.
@@ -1319,7 +1467,7 @@
}
case "hcl":
- obj, err := hcl.Parse(string(buf.Bytes()))
+ obj, err := hcl.Parse(buf.String())
if err != nil {
return ConfigParseError{err}
}
@@ -1337,6 +1485,15 @@
c[k] = v
}
+ case "dotenv", "env":
+ env, err := gotenv.StrictParse(buf)
+ if err != nil {
+ return ConfigParseError{err}
+ }
+ for k, v := range env {
+ c[k] = v
+ }
+
case "properties", "props", "prop":
v.properties = properties.NewProperties()
var err error
@@ -1352,6 +1509,23 @@
// set innermost value
deepestMap[lastKey] = value
}
+
+ case "ini":
+ cfg := ini.Empty()
+ err := cfg.Append(buf.Bytes())
+ if err != nil {
+ return ConfigParseError{err}
+ }
+ sections := cfg.Sections()
+ for i := 0; i < len(sections); i++ {
+ section := sections[i]
+ keys := section.Keys()
+ for j := 0; j < len(keys); j++ {
+ key := keys[j]
+ value := cfg.Section(section.Name()).Key(key.Name()).String()
+ c[section.Name()+"."+key.Name()] = value
+ }
+ }
}
insensitiviseMap(c)
@@ -1359,9 +1533,6 @@
}
// Marshal a map into Writer.
-func marshalWriter(f afero.File, configType string) error {
- return v.marshalWriter(f, configType)
-}
func (v *Viper) marshalWriter(f afero.File, configType string) error {
c := v.AllSettings()
switch configType {
@@ -1377,6 +1548,9 @@
case "hcl":
b, err := json.Marshal(c)
+ if err != nil {
+ return ConfigMarshalError{err}
+ }
ast, err := hcl.Parse(string(b))
if err != nil {
return ConfigMarshalError{err}
@@ -1402,6 +1576,18 @@
return ConfigMarshalError{err}
}
+ case "dotenv", "env":
+ lines := []string{}
+ for _, key := range v.AllKeys() {
+ envName := strings.ToUpper(strings.Replace(key, ".", "_", -1))
+ val := v.Get(key)
+ lines = append(lines, fmt.Sprintf("%v=%v", envName, val))
+ }
+ s := strings.Join(lines, "\n")
+ if _, err := f.WriteString(s); err != nil {
+ return ConfigMarshalError{err}
+ }
+
case "toml":
t, err := toml.TreeFromMap(c)
if err != nil {
@@ -1420,6 +1606,22 @@
if _, err = f.WriteString(string(b)); err != nil {
return ConfigMarshalError{err}
}
+
+ case "ini":
+ keys := v.AllKeys()
+ cfg := ini.Empty()
+ ini.PrettyFormat = false
+ for i := 0; i < len(keys); i++ {
+ key := keys[i]
+ lastSep := strings.LastIndex(key, ".")
+ sectionName := key[:(lastSep)]
+ keyName := key[(lastSep + 1):]
+ if sectionName == "default" {
+ sectionName = ""
+ }
+ cfg.Section(sectionName).Key(keyName).SetValue(Get(key).(string))
+ }
+ cfg.WriteTo(f)
}
return nil
}
@@ -1536,13 +1738,6 @@
return v.watchKeyValueConfigOnChannel()
}
-func (v *Viper) insensitiviseMaps() {
- insensitiviseMap(v.config)
- insensitiviseMap(v.defaults)
- insensitiviseMap(v.override)
- insensitiviseMap(v.kvstore)
-}
-
// Retrieve the first found remote configuration.
func (v *Viper) getKeyValueConfig() error {
if RemoteConfig == nil {
@@ -1573,7 +1768,7 @@
func (v *Viper) watchKeyValueConfigOnChannel() error {
for _, rp := range v.remoteProviders {
respc, _ := RemoteConfig.WatchChannel(rp)
- //Todo: Add quit channel
+ // Todo: Add quit channel
go func(rc <-chan *RemoteResponse) {
for {
b := <-rc
@@ -1609,7 +1804,7 @@
}
// AllKeys returns all keys holding a value, regardless of where they are set.
-// 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 (v *Viper) AllKeys() []string {
m := map[string]bool{}
@@ -1623,7 +1818,7 @@
m = v.flattenAndMergeMap(m, v.defaults, "")
// convert set of paths to list
- a := []string{}
+ a := make([]string, 0, len(m))
for x := range m {
a = append(a, x)
}
@@ -1632,7 +1827,7 @@
// flattenAndMergeMap recursively flattens the given map into a map[string]bool
// of key paths (used as a set, easier to manipulate than a []string):
-// - each path is merged into a single key string, delimited with v.keyDelim (= ".")
+// - each path is merged into a single key string, delimited with v.keyDelim
// - if a path is shadowed by an earlier value in the initial shadow map,
// it is skipped.
// The resulting set of paths is merged to the given shadow set at the same time.
@@ -1672,7 +1867,7 @@
func (v *Viper) mergeFlatMap(shadow map[string]bool, m map[string]interface{}) map[string]bool {
// scan keys
outer:
- for k, _ := range m {
+ for k := range m {
path := strings.Split(k, v.keyDelim)
// scan intermediate paths
var parentKey string
@@ -1735,6 +1930,12 @@
}
}
+// SetConfigPermissions sets the permissions for the config file.
+func SetConfigPermissions(perm os.FileMode) { v.SetConfigPermissions(perm) }
+func (v *Viper) SetConfigPermissions(perm os.FileMode) {
+ v.configPermissions = perm.Perm()
+}
+
func (v *Viper) getConfigType() string {
if v.configType != "" {
return v.configType
@@ -1775,6 +1976,12 @@
}
}
+ if v.configType != "" {
+ if b, _ := exists(v.fs, filepath.Join(in, v.configName)); b {
+ return filepath.Join(in, v.configName)
+ }
+ }
+
return ""
}