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 { OnConfigChange(run func(in fsnotify.Event)) WatchConfig() SetConfigType(in string) SetConfigFile(in string) SetEnvPrefix(in string) SetConfigName(in string) AddConfigPath(in string) error ReadConfig(in io.Reader) error ReadInConfig() error AutomaticEnv() SetEnvKeyReplacer(r *strings.Replacer) RegisterAlias(alias string, key string) error SetDefault(key string, value interface{}) Set(key string, value interface{}) IsSet(key string) bool Get(key string) interface{} GetString(key string) string GetBool(key string) bool GetInt(key string) int GetInt64(key string) int64 GetFloat64(key string) float64 GetTime(key string) time.Time GetDuration(key string) time.Duration GetStringSlice(key string) []string GetStringMap(key string) map[string]interface{} GetStringMapString(key string) map[string]string GetStringMapStringSlice(key string) map[string][]string GetSizeInBytes(key string) uint Sub(key string) Configurator UnmarshalKey(key string, rawVal interface{}) error Unmarshal(rawVal interface{}) error UnmarshalExact(rawVal interface{}) error Marshal(configType string) ([]byte, error) BindPFlags(flags *pflag.FlagSet) error BindPFlag(key string, flag *pflag.Flag) error BindFlagValues(flags FlagValueSet) (err error) BindFlagValue(key string, flag FlagValue) error BindEnv(input ...string) error AllSettings() map[string]interface{} AllKeys() []string } 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 } // Marshal marshals the config to []byte. func Marshal(configType string) ([]byte, error) { return _c.Marshal(configType) } func (c *configurator) Marshal(configType string) ([]byte, error) { return marshallConfig(c.config, configType) } // 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 }