1057 lines
28 KiB
Go
1057 lines
28 KiB
Go
|
package config
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/csv"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"log"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"reflect"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"github.com/fsnotify/fsnotify"
|
||
|
"github.com/mitchellh/mapstructure"
|
||
|
"github.com/spf13/afero"
|
||
|
"github.com/spf13/cast"
|
||
|
"github.com/spf13/pflag"
|
||
|
)
|
||
|
|
||
|
// SupportedExts are universally supported extensions.
|
||
|
var SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl"}
|
||
|
|
||
|
type UnsupportedConfigError string
|
||
|
|
||
|
// Error returns the formatted configuration error.
|
||
|
func (str UnsupportedConfigError) Error() string {
|
||
|
return fmt.Sprintf("Unsupported Config Type %q", string(str))
|
||
|
}
|
||
|
|
||
|
// ConfigFileNotFoundError denotes failing to find configuration file.
|
||
|
type ConfigFileNotFoundError struct {
|
||
|
name, locations string
|
||
|
}
|
||
|
|
||
|
// Error returns the formatted configuration error.
|
||
|
func (fnfe ConfigFileNotFoundError) Error() string {
|
||
|
return fmt.Sprintf("Config File %q Not Found in %q", fnfe.name, fnfe.locations)
|
||
|
}
|
||
|
|
||
|
type CircularReferenceAliasError struct {
|
||
|
alias, key, realKey string
|
||
|
}
|
||
|
|
||
|
// Error returns the formatted configuration error.
|
||
|
func (crae CircularReferenceAliasError) Error() string {
|
||
|
return fmt.Sprintf("Creating circular reference alias[%q] for key[%q]:realKey[%q]", crae.alias, crae.key, crae.realKey)
|
||
|
}
|
||
|
|
||
|
type Configurator interface {
|
||
|
SetConfigName(in string)
|
||
|
AddConfigPath(in string) error
|
||
|
ReadInConfig() error
|
||
|
}
|
||
|
|
||
|
type configurator struct {
|
||
|
keyDelim string
|
||
|
configPaths []string
|
||
|
// The filesystem to read config from.
|
||
|
fs afero.Fs
|
||
|
// Name of file to look for inside the path
|
||
|
configName string
|
||
|
configFile string
|
||
|
configType string
|
||
|
envPrefix string
|
||
|
|
||
|
automaticEnvApplied bool
|
||
|
envKeyReplacer *strings.Replacer
|
||
|
|
||
|
config map[string]interface{}
|
||
|
override map[string]interface{}
|
||
|
defaults map[string]interface{}
|
||
|
kvstore map[string]interface{}
|
||
|
pflags map[string]FlagValue
|
||
|
env map[string]string
|
||
|
aliases map[string]string
|
||
|
typeByDefValue bool
|
||
|
|
||
|
onConfigChange func(fsnotify.Event)
|
||
|
}
|
||
|
|
||
|
var _c *configurator
|
||
|
|
||
|
func init() {
|
||
|
_c = New().(*configurator)
|
||
|
}
|
||
|
|
||
|
func New() Configurator {
|
||
|
c := new(configurator)
|
||
|
c.keyDelim = "."
|
||
|
c.configName = "config"
|
||
|
c.fs = afero.NewOsFs()
|
||
|
c.config = make(map[string]interface{})
|
||
|
c.override = make(map[string]interface{})
|
||
|
c.defaults = make(map[string]interface{})
|
||
|
c.kvstore = make(map[string]interface{})
|
||
|
c.pflags = make(map[string]FlagValue)
|
||
|
c.env = make(map[string]string)
|
||
|
c.aliases = make(map[string]string)
|
||
|
c.typeByDefValue = false
|
||
|
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
func OnConfigChange(run func(in fsnotify.Event)) { _c.OnConfigChange(run) }
|
||
|
func (c *configurator) OnConfigChange(run func(in fsnotify.Event)) {
|
||
|
c.onConfigChange = run
|
||
|
}
|
||
|
|
||
|
func WatchConfig() { _c.WatchConfig() }
|
||
|
func (c *configurator) WatchConfig() {
|
||
|
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 := c.getConfigFile()
|
||
|
if err != nil {
|
||
|
log.Println("error:", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
configFile := filepath.Clean(filename)
|
||
|
configDir, _ := filepath.Split(configFile)
|
||
|
|
||
|
done := make(chan bool)
|
||
|
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 := c.ReadInConfig()
|
||
|
if err != nil {
|
||
|
log.Println("error:", err)
|
||
|
}
|
||
|
c.onConfigChange(event)
|
||
|
}
|
||
|
}
|
||
|
case err := <-watcher.Errors:
|
||
|
log.Println("error:", err)
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
watcher.Add(configDir)
|
||
|
<-done
|
||
|
}()
|
||
|
}
|
||
|
|
||
|
func SetConfigType(in string) { _c.SetConfigType(in) }
|
||
|
func (c *configurator) SetConfigType(in string) {
|
||
|
if in != "" {
|
||
|
c.configType = in
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func SetConfigFile(in string) { _c.SetConfigFile(in) }
|
||
|
func (c *configurator) SetConfigFile(in string) {
|
||
|
if in != "" {
|
||
|
c.configFile = in
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func SetEnvPrefix(in string) { _c.SetEnvPrefix(in) }
|
||
|
func (c *configurator) SetEnvPrefix(in string) {
|
||
|
if in != "" {
|
||
|
c.envPrefix = in
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// SetConfigName sets name for the config file.
|
||
|
// Does not include extension.
|
||
|
func SetConfigName(in string) { _c.SetConfigName(in) }
|
||
|
func (c *configurator) SetConfigName(in string) {
|
||
|
if in != "" {
|
||
|
c.configName = in
|
||
|
c.configFile = ""
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// AddConfigPath adds a path for Viper to search for the config file in.
|
||
|
// Can be called multiple times to define multiple search paths.
|
||
|
func AddConfigPath(in string) error { return _c.AddConfigPath(in) }
|
||
|
func (c *configurator) AddConfigPath(in string) error {
|
||
|
if in != "" {
|
||
|
absin, err := absPathify(in)
|
||
|
if nil != err {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if !stringInSlice(absin, c.configPaths) {
|
||
|
c.configPaths = append(c.configPaths, absin)
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func ReadConfig(in io.Reader) error { return _c.ReadConfig(in) }
|
||
|
func (c *configurator) ReadConfig(in io.Reader) error {
|
||
|
c.config = make(map[string]interface{})
|
||
|
return c.unmarshalReader(in, c.config)
|
||
|
}
|
||
|
|
||
|
// ReadInConfig will discover and load the configuration file from disk
|
||
|
// and key/value stores, searching in one of the defined paths.
|
||
|
func ReadInConfig() error { return _c.ReadInConfig() }
|
||
|
func (c *configurator) ReadInConfig() error {
|
||
|
filename, err := c.getConfigFile()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if !stringInSlice(c.getConfigType(), SupportedExts) {
|
||
|
return UnsupportedConfigError(c.getConfigType())
|
||
|
}
|
||
|
|
||
|
file, err := afero.ReadFile(c.fs, filename)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
config := make(map[string]interface{})
|
||
|
|
||
|
err = c.unmarshalReader(bytes.NewReader(file), config)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
c.config = config
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// AutomaticEnv has Viper check ENV variables for all.
|
||
|
// keys set in config, default & flags
|
||
|
func AutomaticEnv() { _c.AutomaticEnv() }
|
||
|
func (c *configurator) AutomaticEnv() {
|
||
|
c.automaticEnvApplied = true
|
||
|
}
|
||
|
|
||
|
// SetEnvKeyReplacer sets the strings.Replacer on the viper object
|
||
|
// Useful for mapping an environmental variable to a key that does
|
||
|
// not match it.
|
||
|
func SetEnvKeyReplacer(r *strings.Replacer) { _c.SetEnvKeyReplacer(r) }
|
||
|
func (c *configurator) SetEnvKeyReplacer(r *strings.Replacer) {
|
||
|
c.envKeyReplacer = r
|
||
|
}
|
||
|
|
||
|
func RegisterAlias(alias string, key string) error { return _c.RegisterAlias(alias, key) }
|
||
|
func (c *configurator) RegisterAlias(alias string, key string) error {
|
||
|
return c.registerAlias(alias, strings.ToLower(key))
|
||
|
}
|
||
|
|
||
|
func (c *configurator) registerAlias(alias string, key string) error {
|
||
|
alias = strings.ToLower(alias)
|
||
|
if alias != key && alias != c.realKey(key) {
|
||
|
_, exists := c.aliases[alias]
|
||
|
|
||
|
if !exists {
|
||
|
// if we alias something that exists in one of the maps to another
|
||
|
// name, we'll never be able to get that value using the original
|
||
|
// name, so move the config value to the new realkey.
|
||
|
if val, ok := c.config[alias]; ok {
|
||
|
delete(c.config, alias)
|
||
|
c.config[key] = val
|
||
|
}
|
||
|
if val, ok := c.kvstore[alias]; ok {
|
||
|
delete(c.kvstore, alias)
|
||
|
c.kvstore[key] = val
|
||
|
}
|
||
|
if val, ok := c.defaults[alias]; ok {
|
||
|
delete(c.defaults, alias)
|
||
|
c.defaults[key] = val
|
||
|
}
|
||
|
if val, ok := c.override[alias]; ok {
|
||
|
delete(c.override, alias)
|
||
|
c.override[key] = val
|
||
|
}
|
||
|
c.aliases[alias] = key
|
||
|
}
|
||
|
} else {
|
||
|
return CircularReferenceAliasError{alias: alias, key: key, realKey: c.realKey(key)}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func SetDefault(key string, value interface{}) { _c.SetDefault(key, value) }
|
||
|
func (c *configurator) SetDefault(key string, value interface{}) {
|
||
|
// If alias passed in, then set the proper default
|
||
|
key = c.realKey(strings.ToLower(key))
|
||
|
value = toCaseInsensitiveValue(value)
|
||
|
|
||
|
path := strings.Split(key, c.keyDelim)
|
||
|
lastKey := strings.ToLower(path[len(path)-1])
|
||
|
deepestMap := deepSearch(c.defaults, path[0:len(path)-1])
|
||
|
|
||
|
// set innermost value
|
||
|
deepestMap[lastKey] = value
|
||
|
}
|
||
|
|
||
|
func Set(key string, value interface{}) { _c.Set(key, value) }
|
||
|
func (c *configurator) Set(key string, value interface{}) {
|
||
|
// If alias passed in, then set the proper override
|
||
|
key = c.realKey(strings.ToLower(key))
|
||
|
value = toCaseInsensitiveValue(value)
|
||
|
|
||
|
path := strings.Split(key, c.keyDelim)
|
||
|
lastKey := strings.ToLower(path[len(path)-1])
|
||
|
deepestMap := deepSearch(c.override, path[0:len(path)-1])
|
||
|
|
||
|
// set innermost value
|
||
|
deepestMap[lastKey] = value
|
||
|
}
|
||
|
|
||
|
func IsSet(key string) bool { return _c.IsSet(key) }
|
||
|
func (c *configurator) IsSet(key string) bool {
|
||
|
lcaseKey := strings.ToLower(key)
|
||
|
val := c.find(lcaseKey)
|
||
|
return val != nil
|
||
|
}
|
||
|
|
||
|
func Get(key string) interface{} { return _c.Get(key) }
|
||
|
func (c *configurator) Get(key string) interface{} {
|
||
|
lcaseKey := strings.ToLower(key)
|
||
|
val := c.find(lcaseKey)
|
||
|
if val == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if c.typeByDefValue {
|
||
|
// TODO(bep) this branch isn't covered by a single test.
|
||
|
valType := val
|
||
|
path := strings.Split(lcaseKey, c.keyDelim)
|
||
|
defVal := c.searchMap(c.defaults, path)
|
||
|
if defVal != nil {
|
||
|
valType = defVal
|
||
|
}
|
||
|
|
||
|
switch valType.(type) {
|
||
|
case bool:
|
||
|
return cast.ToBool(val)
|
||
|
case string:
|
||
|
return cast.ToString(val)
|
||
|
case int64, int32, int16, int8, int:
|
||
|
return cast.ToInt(val)
|
||
|
case float64, float32:
|
||
|
return cast.ToFloat64(val)
|
||
|
case time.Time:
|
||
|
return cast.ToTime(val)
|
||
|
case time.Duration:
|
||
|
return cast.ToDuration(val)
|
||
|
case []string:
|
||
|
return cast.ToStringSlice(val)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return val
|
||
|
}
|
||
|
|
||
|
// GetString returns the value associated with the key as a string.
|
||
|
func GetString(key string) string { return _c.GetString(key) }
|
||
|
func (c *configurator) GetString(key string) string {
|
||
|
return cast.ToString(c.Get(key))
|
||
|
}
|
||
|
|
||
|
// GetBool returns the value associated with the key as a boolean.
|
||
|
func GetBool(key string) bool { return _c.GetBool(key) }
|
||
|
func (c *configurator) GetBool(key string) bool {
|
||
|
return cast.ToBool(c.Get(key))
|
||
|
}
|
||
|
|
||
|
// GetInt returns the value associated with the key as an integer.
|
||
|
func GetInt(key string) int { return _c.GetInt(key) }
|
||
|
func (c *configurator) GetInt(key string) int {
|
||
|
return cast.ToInt(c.Get(key))
|
||
|
}
|
||
|
|
||
|
// GetInt64 returns the value associated with the key as an integer.
|
||
|
func GetInt64(key string) int64 { return _c.GetInt64(key) }
|
||
|
func (c *configurator) GetInt64(key string) int64 {
|
||
|
return cast.ToInt64(c.Get(key))
|
||
|
}
|
||
|
|
||
|
// GetFloat64 returns the value associated with the key as a float64.
|
||
|
func GetFloat64(key string) float64 { return _c.GetFloat64(key) }
|
||
|
func (c *configurator) GetFloat64(key string) float64 {
|
||
|
return cast.ToFloat64(c.Get(key))
|
||
|
}
|
||
|
|
||
|
// GetTime returns the value associated with the key as time.
|
||
|
func GetTime(key string) time.Time { return _c.GetTime(key) }
|
||
|
func (c *configurator) GetTime(key string) time.Time {
|
||
|
return cast.ToTime(c.Get(key))
|
||
|
}
|
||
|
|
||
|
// GetDuration returns the value associated with the key as a duration.
|
||
|
func GetDuration(key string) time.Duration { return _c.GetDuration(key) }
|
||
|
func (c *configurator) GetDuration(key string) time.Duration {
|
||
|
return cast.ToDuration(c.Get(key))
|
||
|
}
|
||
|
|
||
|
// GetStringSlice returns the value associated with the key as a slice of strings.
|
||
|
func GetStringSlice(key string) []string { return _c.GetStringSlice(key) }
|
||
|
func (c *configurator) GetStringSlice(key string) []string {
|
||
|
return cast.ToStringSlice(c.Get(key))
|
||
|
}
|
||
|
|
||
|
// GetStringMap returns the value associated with the key as a map of interfaces.
|
||
|
func GetStringMap(key string) map[string]interface{} { return _c.GetStringMap(key) }
|
||
|
func (c *configurator) GetStringMap(key string) map[string]interface{} {
|
||
|
return cast.ToStringMap(c.Get(key))
|
||
|
}
|
||
|
|
||
|
// GetStringMapString returns the value associated with the key as a map of strings.
|
||
|
func GetStringMapString(key string) map[string]string { return _c.GetStringMapString(key) }
|
||
|
func (c *configurator) GetStringMapString(key string) map[string]string {
|
||
|
return cast.ToStringMapString(c.Get(key))
|
||
|
}
|
||
|
|
||
|
// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings.
|
||
|
func GetStringMapStringSlice(key string) map[string][]string { return _c.GetStringMapStringSlice(key) }
|
||
|
func (c *configurator) GetStringMapStringSlice(key string) map[string][]string {
|
||
|
return cast.ToStringMapStringSlice(c.Get(key))
|
||
|
}
|
||
|
|
||
|
// GetSizeInBytes returns the size of the value associated with the given key
|
||
|
// in bytes.
|
||
|
func GetSizeInBytes(key string) uint { return _c.GetSizeInBytes(key) }
|
||
|
func (c *configurator) GetSizeInBytes(key string) uint {
|
||
|
sizeStr := cast.ToString(c.Get(key))
|
||
|
return parseSizeInBytes(sizeStr)
|
||
|
}
|
||
|
|
||
|
// Sub returns new Viper instance representing a sub tree of this instance.
|
||
|
// Sub is case-insensitive for a key.
|
||
|
func Sub(key string) Configurator { return _c.Sub(key) }
|
||
|
func (c *configurator) Sub(key string) Configurator {
|
||
|
subv := New()
|
||
|
data := c.Get(key)
|
||
|
if data == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if reflect.TypeOf(data).Kind() == reflect.Map {
|
||
|
subv.(*configurator).config = cast.ToStringMap(data)
|
||
|
return subv
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (c *configurator) getConfigFile() (string, error) {
|
||
|
// if explicitly set, then use it
|
||
|
if c.configFile != "" {
|
||
|
return c.configFile, nil
|
||
|
}
|
||
|
|
||
|
cf, err := c.findConfigFile()
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
c.configFile = cf
|
||
|
return c.getConfigFile()
|
||
|
}
|
||
|
|
||
|
func (c *configurator) getConfigType() string {
|
||
|
if c.configType != "" {
|
||
|
return c.configType
|
||
|
}
|
||
|
|
||
|
cf, err := c.getConfigFile()
|
||
|
if err != nil {
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
ext := filepath.Ext(cf)
|
||
|
|
||
|
if len(ext) > 1 {
|
||
|
return ext[1:]
|
||
|
}
|
||
|
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
func (c *configurator) findConfigFile() (string, error) {
|
||
|
for _, cp := range c.configPaths {
|
||
|
file := c.searchInPath(cp)
|
||
|
if file != "" {
|
||
|
return file, nil
|
||
|
}
|
||
|
}
|
||
|
return "", ConfigFileNotFoundError{c.configName, fmt.Sprintf("%s", c.configPaths)}
|
||
|
}
|
||
|
|
||
|
func (c *configurator) searchInPath(in string) (filename string) {
|
||
|
for _, ext := range SupportedExts {
|
||
|
if b, _ := exists(filepath.Join(in, c.configName+"."+ext)); b {
|
||
|
return filepath.Join(in, c.configName+"."+ext)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
func unmarshalReader(in io.Reader, config map[string]interface{}) error {
|
||
|
return _c.unmarshalReader(in, config)
|
||
|
}
|
||
|
|
||
|
func (c *configurator) unmarshalReader(in io.Reader, config map[string]interface{}) error {
|
||
|
return unmarshallConfigReader(in, config, c.getConfigType())
|
||
|
}
|
||
|
|
||
|
func (c *configurator) insensitiviseMaps() {
|
||
|
insensitiviseMap(c.config)
|
||
|
insensitiviseMap(c.defaults)
|
||
|
insensitiviseMap(c.override)
|
||
|
insensitiviseMap(c.kvstore)
|
||
|
}
|
||
|
|
||
|
func (c *configurator) realKey(key string) string {
|
||
|
newkey, exists := c.aliases[key]
|
||
|
if exists {
|
||
|
return c.realKey(newkey)
|
||
|
}
|
||
|
return key
|
||
|
}
|
||
|
|
||
|
func (c *configurator) searchMap(source map[string]interface{}, path []string) interface{} {
|
||
|
if len(path) == 0 {
|
||
|
return source
|
||
|
}
|
||
|
|
||
|
next, ok := source[path[0]]
|
||
|
if ok {
|
||
|
// Fast path
|
||
|
if len(path) == 1 {
|
||
|
return next
|
||
|
}
|
||
|
|
||
|
// Nested case
|
||
|
switch next.(type) {
|
||
|
case map[interface{}]interface{}:
|
||
|
return c.searchMap(cast.ToStringMap(next), path[1:])
|
||
|
case map[string]interface{}:
|
||
|
// Type assertion is safe here since it is only reached
|
||
|
// if the type of `next` is the same as the type being asserted
|
||
|
return c.searchMap(next.(map[string]interface{}), path[1:])
|
||
|
default:
|
||
|
// got a value but nested key expected, return "nil" for not found
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// UnmarshalKey takes a single key and unmarshals it into a Struct.
|
||
|
func UnmarshalKey(key string, rawVal interface{}) error { return _c.UnmarshalKey(key, rawVal) }
|
||
|
func (c *configurator) UnmarshalKey(key string, rawVal interface{}) error {
|
||
|
err := decode(c.Get(key), defaultDecoderConfig(rawVal))
|
||
|
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
c.insensitiviseMaps()
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Unmarshal unmarshals the config into a Struct. Make sure that the tags
|
||
|
// on the fields of the structure are properly set.
|
||
|
func Unmarshal(rawVal interface{}) error { return _c.Unmarshal(rawVal) }
|
||
|
func (c *configurator) Unmarshal(rawVal interface{}) error {
|
||
|
err := decode(c.AllSettings(), defaultDecoderConfig(rawVal))
|
||
|
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
c.insensitiviseMaps()
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// defaultDecoderConfig returns default mapsstructure.DecoderConfig with suppot
|
||
|
// of time.Duration values
|
||
|
func defaultDecoderConfig(output interface{}) *mapstructure.DecoderConfig {
|
||
|
return &mapstructure.DecoderConfig{
|
||
|
Metadata: nil,
|
||
|
Result: output,
|
||
|
WeaklyTypedInput: true,
|
||
|
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// A wrapper around mapstructure.Decode that mimics the WeakDecode functionality
|
||
|
func decode(input interface{}, config *mapstructure.DecoderConfig) error {
|
||
|
decoder, err := mapstructure.NewDecoder(config)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return decoder.Decode(input)
|
||
|
}
|
||
|
|
||
|
// UnmarshalExact unmarshals the config into a Struct, erroring if a field is nonexistent
|
||
|
// in the destination struct.
|
||
|
func (c *configurator) UnmarshalExact(rawVal interface{}) error {
|
||
|
config := defaultDecoderConfig(rawVal)
|
||
|
config.ErrorUnused = true
|
||
|
|
||
|
err := decode(c.AllSettings(), config)
|
||
|
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
c.insensitiviseMaps()
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// BindPFlags binds a full flag set to the configuration, using each flag's long
|
||
|
// name as the config key.
|
||
|
func BindPFlags(flags *pflag.FlagSet) error { return _c.BindPFlags(flags) }
|
||
|
func (c *configurator) BindPFlags(flags *pflag.FlagSet) error {
|
||
|
return c.BindFlagValues(pflagValueSet{flags})
|
||
|
}
|
||
|
|
||
|
// BindPFlag binds a specific key to a pflag (as used by cobra).
|
||
|
// Example (where serverCmd is a Cobra instance):
|
||
|
//
|
||
|
// serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
|
||
|
// Viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
|
||
|
//
|
||
|
func BindPFlag(key string, flag *pflag.Flag) error { return _c.BindPFlag(key, flag) }
|
||
|
func (c *configurator) BindPFlag(key string, flag *pflag.Flag) error {
|
||
|
return c.BindFlagValue(key, pflagValue{flag})
|
||
|
}
|
||
|
|
||
|
// BindFlagValues binds a full FlagValue set to the configuration, using each flag's long
|
||
|
// name as the config key.
|
||
|
func BindFlagValues(flags FlagValueSet) error { return _c.BindFlagValues(flags) }
|
||
|
func (c *configurator) BindFlagValues(flags FlagValueSet) (err error) {
|
||
|
flags.VisitAll(func(flag FlagValue) {
|
||
|
if err = c.BindFlagValue(flag.Name(), flag); err != nil {
|
||
|
return
|
||
|
}
|
||
|
})
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// BindFlagValue binds a specific key to a FlagValue.
|
||
|
// Example (where serverCmd is a Cobra instance):
|
||
|
//
|
||
|
// serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
|
||
|
// Viper.BindFlagValue("port", serverCmd.Flags().Lookup("port"))
|
||
|
//
|
||
|
func BindFlagValue(key string, flag FlagValue) error { return _c.BindFlagValue(key, flag) }
|
||
|
func (c *configurator) BindFlagValue(key string, flag FlagValue) error {
|
||
|
if flag == nil {
|
||
|
return fmt.Errorf("flag for %q is nil", key)
|
||
|
}
|
||
|
c.pflags[strings.ToLower(key)] = flag
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// BindEnv binds a Viper key to a ENV variable.
|
||
|
// ENV variables are case sensitive.
|
||
|
// If only a key is provided, it will use the env key matching the key, uppercased.
|
||
|
// EnvPrefix will be used when set when env name is not provided.
|
||
|
func BindEnv(input ...string) error { return _c.BindEnv(input...) }
|
||
|
func (c *configurator) BindEnv(input ...string) error {
|
||
|
var key, envkey string
|
||
|
if len(input) == 0 {
|
||
|
return fmt.Errorf("BindEnv missing key to bind to")
|
||
|
}
|
||
|
|
||
|
key = strings.ToLower(input[0])
|
||
|
|
||
|
if len(input) == 1 {
|
||
|
envkey = c.mergeWithEnvPrefix(key)
|
||
|
} else {
|
||
|
envkey = input[1]
|
||
|
}
|
||
|
|
||
|
c.env[key] = envkey
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (c *configurator) find(lcaseKey string) interface{} {
|
||
|
|
||
|
var (
|
||
|
val interface{}
|
||
|
exists bool
|
||
|
path = strings.Split(lcaseKey, c.keyDelim)
|
||
|
nested = len(path) > 1
|
||
|
)
|
||
|
|
||
|
// compute the path through the nested maps to the nested value
|
||
|
if nested && c.isPathShadowedInDeepMap(path, castMapStringToMapInterface(c.aliases)) != "" {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// if the requested key is an alias, then return the proper key
|
||
|
lcaseKey = c.realKey(lcaseKey)
|
||
|
path = strings.Split(lcaseKey, c.keyDelim)
|
||
|
nested = len(path) > 1
|
||
|
|
||
|
// Set() override first
|
||
|
val = c.searchMap(c.override, path)
|
||
|
if val != nil {
|
||
|
return val
|
||
|
}
|
||
|
if nested && c.isPathShadowedInDeepMap(path, c.override) != "" {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// PFlag override next
|
||
|
flag, exists := c.pflags[lcaseKey]
|
||
|
if exists && flag.HasChanged() {
|
||
|
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 nested && c.isPathShadowedInFlatMap(path, c.pflags) != "" {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Env override next
|
||
|
if c.automaticEnvApplied {
|
||
|
// even if it hasn't been registered, if automaticEnv is used,
|
||
|
// check any Get request
|
||
|
if val = c.getEnv(c.mergeWithEnvPrefix(lcaseKey)); val != "" {
|
||
|
return val
|
||
|
}
|
||
|
if nested && c.isPathShadowedInAutoEnv(path) != "" {
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
envkey, exists := c.env[lcaseKey]
|
||
|
if exists {
|
||
|
if val = c.getEnv(envkey); val != "" {
|
||
|
return val
|
||
|
}
|
||
|
}
|
||
|
if nested && c.isPathShadowedInFlatMap(path, c.env) != "" {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Config file next
|
||
|
val = c.searchMapWithPathPrefixes(c.config, path)
|
||
|
if val != nil {
|
||
|
return val
|
||
|
}
|
||
|
if nested && c.isPathShadowedInDeepMap(path, c.config) != "" {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// K/V store next
|
||
|
val = c.searchMap(c.kvstore, path)
|
||
|
if val != nil {
|
||
|
return val
|
||
|
}
|
||
|
if nested && c.isPathShadowedInDeepMap(path, c.kvstore) != "" {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Default next
|
||
|
val = c.searchMap(c.defaults, path)
|
||
|
if val != nil {
|
||
|
return val
|
||
|
}
|
||
|
if nested && c.isPathShadowedInDeepMap(path, c.defaults) != "" {
|
||
|
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 := c.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()
|
||
|
}
|
||
|
}
|
||
|
// last item, no need to check shadowing
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (c *configurator) searchMapWithPathPrefixes(source map[string]interface{}, path []string) interface{} {
|
||
|
if len(path) == 0 {
|
||
|
return source
|
||
|
}
|
||
|
|
||
|
// search for path prefixes, starting from the longest one
|
||
|
for i := len(path); i > 0; i-- {
|
||
|
prefixKey := strings.ToLower(strings.Join(path[0:i], c.keyDelim))
|
||
|
|
||
|
next, ok := source[prefixKey]
|
||
|
if ok {
|
||
|
// Fast path
|
||
|
if i == len(path) {
|
||
|
return next
|
||
|
}
|
||
|
|
||
|
// Nested case
|
||
|
var val interface{}
|
||
|
switch next.(type) {
|
||
|
case map[interface{}]interface{}:
|
||
|
val = c.searchMapWithPathPrefixes(cast.ToStringMap(next), path[i:])
|
||
|
case map[string]interface{}:
|
||
|
// Type assertion is safe here since it is only reached
|
||
|
// if the type of `next` is the same as the type being asserted
|
||
|
val = c.searchMapWithPathPrefixes(next.(map[string]interface{}), path[i:])
|
||
|
default:
|
||
|
// got a value but nested key expected, do nothing and look for next prefix
|
||
|
}
|
||
|
if val != nil {
|
||
|
return val
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// not found
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (c *configurator) isPathShadowedInDeepMap(path []string, m map[string]interface{}) string {
|
||
|
var parentVal interface{}
|
||
|
for i := 1; i < len(path); i++ {
|
||
|
parentVal = c.searchMap(m, path[0:i])
|
||
|
if parentVal == nil {
|
||
|
// not found, no need to add more path elements
|
||
|
return ""
|
||
|
}
|
||
|
switch parentVal.(type) {
|
||
|
case map[interface{}]interface{}:
|
||
|
continue
|
||
|
case map[string]interface{}:
|
||
|
continue
|
||
|
default:
|
||
|
// parentVal is a regular value which shadows "path"
|
||
|
return strings.Join(path[0:i], c.keyDelim)
|
||
|
}
|
||
|
}
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
func (c *configurator) isPathShadowedInFlatMap(path []string, mi interface{}) string {
|
||
|
// unify input map
|
||
|
var m map[string]interface{}
|
||
|
switch mi.(type) {
|
||
|
case map[string]string, map[string]FlagValue:
|
||
|
m = cast.ToStringMap(mi)
|
||
|
default:
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
// scan paths
|
||
|
var parentKey string
|
||
|
for i := 1; i < len(path); i++ {
|
||
|
parentKey = strings.Join(path[0:i], c.keyDelim)
|
||
|
if _, ok := m[parentKey]; ok {
|
||
|
return parentKey
|
||
|
}
|
||
|
}
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
func (c *configurator) isPathShadowedInAutoEnv(path []string) string {
|
||
|
var parentKey string
|
||
|
var val string
|
||
|
for i := 1; i < len(path); i++ {
|
||
|
parentKey = strings.Join(path[0:i], c.keyDelim)
|
||
|
if val = c.getEnv(c.mergeWithEnvPrefix(parentKey)); val != "" {
|
||
|
return parentKey
|
||
|
}
|
||
|
}
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
func (c *configurator) getEnv(key string) string {
|
||
|
if c.envKeyReplacer != nil {
|
||
|
key = c.envKeyReplacer.Replace(key)
|
||
|
}
|
||
|
return os.Getenv(key)
|
||
|
}
|
||
|
|
||
|
func (c *configurator) mergeWithEnvPrefix(in string) string {
|
||
|
if c.envPrefix != "" {
|
||
|
return strings.ToUpper(c.envPrefix + "_" + in)
|
||
|
}
|
||
|
|
||
|
return strings.ToUpper(in)
|
||
|
}
|
||
|
|
||
|
func castToMapStringInterface(
|
||
|
src map[interface{}]interface{}) map[string]interface{} {
|
||
|
tgt := map[string]interface{}{}
|
||
|
for k, v := range src {
|
||
|
tgt[fmt.Sprintf("%v", k)] = v
|
||
|
}
|
||
|
return tgt
|
||
|
}
|
||
|
|
||
|
func castMapStringToMapInterface(src map[string]string) map[string]interface{} {
|
||
|
tgt := map[string]interface{}{}
|
||
|
for k, v := range src {
|
||
|
tgt[k] = v
|
||
|
}
|
||
|
return tgt
|
||
|
}
|
||
|
|
||
|
func castMapFlagToMapInterface(src map[string]FlagValue) map[string]interface{} {
|
||
|
tgt := map[string]interface{}{}
|
||
|
for k, v := range src {
|
||
|
tgt[k] = v
|
||
|
}
|
||
|
return tgt
|
||
|
}
|
||
|
|
||
|
func readAsCSV(val string) ([]string, error) {
|
||
|
if val == "" {
|
||
|
return []string{}, nil
|
||
|
}
|
||
|
stringReader := strings.NewReader(val)
|
||
|
csvReader := csv.NewReader(stringReader)
|
||
|
return csvReader.Read()
|
||
|
}
|
||
|
|
||
|
// AllSettings merges all settings and returns them as a map[string]interface{}.
|
||
|
func AllSettings() map[string]interface{} { return _c.AllSettings() }
|
||
|
func (c *configurator) AllSettings() map[string]interface{} {
|
||
|
m := map[string]interface{}{}
|
||
|
// start from the list of keys, and construct the map one value at a time
|
||
|
for _, k := range c.AllKeys() {
|
||
|
value := c.Get(k)
|
||
|
if value == nil {
|
||
|
// should not happen, since AllKeys() returns only keys holding a value,
|
||
|
// check just in case anything changes
|
||
|
continue
|
||
|
}
|
||
|
path := strings.Split(k, c.keyDelim)
|
||
|
lastKey := strings.ToLower(path[len(path)-1])
|
||
|
deepestMap := deepSearch(m, path[0:len(path)-1])
|
||
|
// set innermost value
|
||
|
deepestMap[lastKey] = value
|
||
|
}
|
||
|
return m
|
||
|
}
|
||
|
|
||
|
// 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 _c.AllKeys() }
|
||
|
func (c *configurator) AllKeys() []string {
|
||
|
m := map[string]bool{}
|
||
|
// add all paths, by order of descending priority to ensure correct shadowing
|
||
|
m = c.flattenAndMergeMap(m, castMapStringToMapInterface(c.aliases), "")
|
||
|
m = c.flattenAndMergeMap(m, c.override, "")
|
||
|
m = c.mergeFlatMap(m, castMapFlagToMapInterface(c.pflags))
|
||
|
m = c.mergeFlatMap(m, castMapStringToMapInterface(c.env))
|
||
|
m = c.flattenAndMergeMap(m, c.config, "")
|
||
|
m = c.flattenAndMergeMap(m, c.kvstore, "")
|
||
|
m = c.flattenAndMergeMap(m, c.defaults, "")
|
||
|
|
||
|
// convert set of paths to list
|
||
|
a := []string{}
|
||
|
for x := range m {
|
||
|
a = append(a, x)
|
||
|
}
|
||
|
return a
|
||
|
}
|
||
|
|
||
|
// 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 (= ".")
|
||
|
// - 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.
|
||
|
func (c *configurator) flattenAndMergeMap(shadow map[string]bool, m map[string]interface{}, prefix string) map[string]bool {
|
||
|
if shadow != nil && prefix != "" && shadow[prefix] {
|
||
|
// prefix is shadowed => nothing more to flatten
|
||
|
return shadow
|
||
|
}
|
||
|
if shadow == nil {
|
||
|
shadow = make(map[string]bool)
|
||
|
}
|
||
|
|
||
|
var m2 map[string]interface{}
|
||
|
if prefix != "" {
|
||
|
prefix += c.keyDelim
|
||
|
}
|
||
|
for k, val := range m {
|
||
|
fullKey := prefix + k
|
||
|
switch val.(type) {
|
||
|
case map[string]interface{}:
|
||
|
m2 = val.(map[string]interface{})
|
||
|
case map[interface{}]interface{}:
|
||
|
m2 = cast.ToStringMap(val)
|
||
|
default:
|
||
|
// immediate value
|
||
|
shadow[strings.ToLower(fullKey)] = true
|
||
|
continue
|
||
|
}
|
||
|
// recursively merge to shadow map
|
||
|
shadow = c.flattenAndMergeMap(shadow, m2, fullKey)
|
||
|
}
|
||
|
return shadow
|
||
|
}
|
||
|
|
||
|
// mergeFlatMap merges the given maps, excluding values of the second map
|
||
|
// shadowed by values from the first map.
|
||
|
func (c *configurator) mergeFlatMap(shadow map[string]bool, m map[string]interface{}) map[string]bool {
|
||
|
// scan keys
|
||
|
outer:
|
||
|
for k, _ := range m {
|
||
|
path := strings.Split(k, c.keyDelim)
|
||
|
// scan intermediate paths
|
||
|
var parentKey string
|
||
|
for i := 1; i < len(path); i++ {
|
||
|
parentKey = strings.Join(path[0:i], c.keyDelim)
|
||
|
if shadow[parentKey] {
|
||
|
// path is shadowed, continue
|
||
|
continue outer
|
||
|
}
|
||
|
}
|
||
|
// add key
|
||
|
shadow[strings.ToLower(k)] = true
|
||
|
}
|
||
|
return shadow
|
||
|
}
|