mirror of
https://github.com/spf13/viper
synced 2024-12-23 03:57:01 +00:00
Merge remote-tracking branch 'wrobbins/master'
This commit is contained in:
commit
eac5b31a90
4 changed files with 220 additions and 17 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -6,6 +6,7 @@
|
||||||
# Folders
|
# Folders
|
||||||
_obj
|
_obj
|
||||||
_test
|
_test
|
||||||
|
.idea
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
# Architecture specific extensions/prefixes
|
||||||
*.[568vq]
|
*.[568vq]
|
||||||
|
|
18
README.md
18
README.md
|
@ -72,7 +72,7 @@ Examples:
|
||||||
If you want to support a config file, Viper requires a minimal
|
If you want to support a config file, Viper requires a minimal
|
||||||
configuration so it knows where to look for the config file. Viper
|
configuration so it knows where to look for the config file. Viper
|
||||||
supports json, toml and yaml files. Viper can search multiple paths, but
|
supports json, toml and yaml files. Viper can search multiple paths, but
|
||||||
currently a single viper only supports a single config file.
|
currently a single viper only supports a single config file, unless cascading is enabled.
|
||||||
|
|
||||||
viper.SetConfigName("config") // name of config file (without extension)
|
viper.SetConfigName("config") // name of config file (without extension)
|
||||||
viper.AddConfigPath("/etc/appname/") // path to look for the config file in
|
viper.AddConfigPath("/etc/appname/") // path to look for the config file in
|
||||||
|
@ -112,6 +112,22 @@ viper.ReadConfig(bytes.NewBuffer(yamlExample))
|
||||||
viper.Get("name") // this would be "steve"
|
viper.Get("name") // this would be "steve"
|
||||||
````
|
````
|
||||||
|
|
||||||
|
#### Enabling Cascading
|
||||||
|
|
||||||
|
By default Viper stops reading configuration once it encounters the first available configuration file.
|
||||||
|
That means each configuration file must contain all configuration values you need.
|
||||||
|
By enabling cascading you can create sparse configuration files. Configuration will cascade down in
|
||||||
|
the order that files are added by AddConfigPath. For more see viper_test's cascading tests.
|
||||||
|
|
||||||
|
Consider:
|
||||||
|
|
||||||
|
* \etc\myapp\myapp.json
|
||||||
|
* ($GOPATH)\src\myapp\myapp.json
|
||||||
|
|
||||||
|
You can check in a default myapp.json for development and only override certain kvps in production
|
||||||
|
|
||||||
|
viper.EnableCascading(true)
|
||||||
|
|
||||||
### Setting Overrides
|
### Setting Overrides
|
||||||
|
|
||||||
These could be from a command line flag, or from your own application logic.
|
These could be from a command line flag, or from your own application logic.
|
||||||
|
|
103
viper.go
103
viper.go
|
@ -133,13 +133,16 @@ type Viper struct {
|
||||||
automaticEnvApplied bool
|
automaticEnvApplied bool
|
||||||
envKeyReplacer *strings.Replacer
|
envKeyReplacer *strings.Replacer
|
||||||
|
|
||||||
config map[string]interface{}
|
cascadeConfigurations bool
|
||||||
override map[string]interface{}
|
|
||||||
defaults map[string]interface{}
|
config map[string]interface{}
|
||||||
kvstore map[string]interface{}
|
override map[string]interface{}
|
||||||
pflags map[string]*pflag.Flag
|
defaults map[string]interface{}
|
||||||
env map[string]string
|
kvstore map[string]interface{}
|
||||||
aliases map[string]string
|
cascadingConfigs map[string]map[string]interface{}
|
||||||
|
pflags map[string]*pflag.Flag
|
||||||
|
env map[string]string
|
||||||
|
aliases map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns an initialized Viper instance.
|
// Returns an initialized Viper instance.
|
||||||
|
@ -154,6 +157,7 @@ func New() *Viper {
|
||||||
v.pflags = make(map[string]*pflag.Flag)
|
v.pflags = make(map[string]*pflag.Flag)
|
||||||
v.env = make(map[string]string)
|
v.env = make(map[string]string)
|
||||||
v.aliases = make(map[string]string)
|
v.aliases = make(map[string]string)
|
||||||
|
v.cascadeConfigurations = false
|
||||||
|
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
@ -226,6 +230,13 @@ func (v *Viper) SetEnvPrefix(in string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enable cascading configuration values for files. Will traverse down
|
||||||
|
// ConfigPaths in an attempt to find keys
|
||||||
|
func EnableCascading(enable bool) { v.EnableCascading(enable) }
|
||||||
|
func (v *Viper) EnableCascading(enable bool) {
|
||||||
|
v.cascadeConfigurations = enable
|
||||||
|
}
|
||||||
|
|
||||||
func (v *Viper) mergeWithEnvPrefix(in string) string {
|
func (v *Viper) mergeWithEnvPrefix(in string) string {
|
||||||
if v.envPrefix != "" {
|
if v.envPrefix != "" {
|
||||||
return strings.ToUpper(v.envPrefix + "_" + in)
|
return strings.ToUpper(v.envPrefix + "_" + in)
|
||||||
|
@ -474,7 +485,6 @@ func (v *Viper) MarshalKey(key string, rawVal interface{}) error {
|
||||||
func Marshal(rawVal interface{}) error { return v.Marshal(rawVal) }
|
func Marshal(rawVal interface{}) error { return v.Marshal(rawVal) }
|
||||||
func (v *Viper) Marshal(rawVal interface{}) error {
|
func (v *Viper) Marshal(rawVal interface{}) error {
|
||||||
err := mapstructure.WeakDecode(v.AllSettings(), rawVal)
|
err := mapstructure.WeakDecode(v.AllSettings(), rawVal)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -562,6 +572,7 @@ func (v *Viper) BindEnv(input ...string) (err error) {
|
||||||
func (v *Viper) find(key string) interface{} {
|
func (v *Viper) find(key string) interface{} {
|
||||||
var val interface{}
|
var val interface{}
|
||||||
var exists bool
|
var exists bool
|
||||||
|
var file string
|
||||||
|
|
||||||
// if the requested key is an alias, then return the proper key
|
// if the requested key is an alias, then return the proper key
|
||||||
key = v.realKey(key)
|
key = v.realKey(key)
|
||||||
|
@ -607,6 +618,15 @@ func (v *Viper) find(key string) interface{} {
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v.cascadeConfigurations {
|
||||||
|
//cascade down the rest of the files
|
||||||
|
val, exists, file = v.findCascading(key)
|
||||||
|
if exists {
|
||||||
|
jww.TRACE.Printf("%s found in config: %s (%s)", key, val, file)
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val, exists = v.kvstore[key]
|
val, exists = v.kvstore[key]
|
||||||
if exists {
|
if exists {
|
||||||
jww.TRACE.Println(key, "found in key/value store:", val)
|
jww.TRACE.Println(key, "found in key/value store:", val)
|
||||||
|
@ -622,6 +642,53 @@ func (v *Viper) find(key string) interface{} {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *Viper) findCascading(key string) (interface{}, bool, string) {
|
||||||
|
|
||||||
|
configFiles := v.findAllConfigFiles()
|
||||||
|
|
||||||
|
if v.cascadingConfigs == nil {
|
||||||
|
v.cascadingConfigs = make(map[string]map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, configFile := range configFiles {
|
||||||
|
config := v.cascadingConfigs[configFile]
|
||||||
|
|
||||||
|
if config == nil {
|
||||||
|
var err error
|
||||||
|
config, err = readConfigFile(configFile)
|
||||||
|
if err != nil {
|
||||||
|
jww.ERROR.Print(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
v.cascadingConfigs[configFile] = config
|
||||||
|
}
|
||||||
|
|
||||||
|
jww.TRACE.Printf("Looking in %s for key %s", configFile, key)
|
||||||
|
result := config[key]
|
||||||
|
if result != nil {
|
||||||
|
return result, true, configFile
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func readConfigFile(configFile string) (map[string]interface{}, error) {
|
||||||
|
jww.TRACE.Printf("marshalling %s ", configFile)
|
||||||
|
|
||||||
|
file, err := ioutil.ReadFile(configFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = make(map[string]interface{})
|
||||||
|
|
||||||
|
marshallConfigReader(bytes.NewReader(file), config, filepath.Ext(configFile)[1:])
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Check to see if the key has been set in any of the data locations
|
// Check to see if the key has been set in any of the data locations
|
||||||
func IsSet(key string) bool { return v.IsSet(key) }
|
func IsSet(key string) bool { return v.IsSet(key) }
|
||||||
func (v *Viper) IsSet(key string) bool {
|
func (v *Viper) IsSet(key string) bool {
|
||||||
|
@ -986,10 +1053,23 @@ func (v *Viper) searchInPath(in string) (filename string) {
|
||||||
func (v *Viper) findConfigFile() (string, error) {
|
func (v *Viper) findConfigFile() (string, error) {
|
||||||
jww.INFO.Println("Searching for config in ", v.configPaths)
|
jww.INFO.Println("Searching for config in ", v.configPaths)
|
||||||
|
|
||||||
|
var validFiles = v.findAllConfigFiles()
|
||||||
|
|
||||||
|
if len(validFiles) == 0 {
|
||||||
|
return "", fmt.Errorf("config file not found in: %s", v.configPaths)
|
||||||
|
}
|
||||||
|
|
||||||
|
return validFiles[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Viper) findAllConfigFiles() []string {
|
||||||
|
|
||||||
|
var validFiles []string
|
||||||
for _, cp := range v.configPaths {
|
for _, cp := range v.configPaths {
|
||||||
file := v.searchInPath(cp)
|
file := v.searchInPath(cp)
|
||||||
if file != "" {
|
if file != "" {
|
||||||
return file, nil
|
jww.TRACE.Println("Found config file in: %s", file)
|
||||||
|
validFiles = append(validFiles, file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -997,9 +1077,10 @@ func (v *Viper) findConfigFile() (string, error) {
|
||||||
wd, _ := os.Getwd()
|
wd, _ := os.Getwd()
|
||||||
file := v.searchInPath(wd)
|
file := v.searchInPath(wd)
|
||||||
if file != "" {
|
if file != "" {
|
||||||
return file, nil
|
validFiles = append(validFiles, file)
|
||||||
}
|
}
|
||||||
return "", fmt.Errorf("config file not found in: %s", v.configPaths)
|
|
||||||
|
return validFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prints all configuration registries for debugging
|
// Prints all configuration registries for debugging
|
||||||
|
|
105
viper_test.go
105
viper_test.go
|
@ -10,6 +10,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -587,3 +589,106 @@ func TestReadDir(t *testing.T) {
|
||||||
assert.Equal(t, "https://donuts/api/1/", v.Get("app.service.url"))
|
assert.Equal(t, "https://donuts/api/1/", v.Get("app.service.url"))
|
||||||
assert.Equal(t, "0123456789abcdef", v.Get("app.service.key"))
|
assert.Equal(t, "0123456789abcdef", v.Get("app.service.key"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCanCascadeConfigurationValues(t *testing.T) {
|
||||||
|
|
||||||
|
v2 := New()
|
||||||
|
|
||||||
|
generateCascadingTests(v2, "cascading")
|
||||||
|
|
||||||
|
v2.ReadInConfig()
|
||||||
|
v2.EnableCascading(true)
|
||||||
|
|
||||||
|
assert.Equal(t, "high", v2.GetString("0"), "Key 0 should be high")
|
||||||
|
assert.Equal(t, "med", v2.GetString("1"), "Key 1 should be med")
|
||||||
|
assert.Equal(t, "low", v2.GetString("2"), "key 2 should be low")
|
||||||
|
|
||||||
|
v2.EnableCascading(false)
|
||||||
|
|
||||||
|
assert.Nil(t, v2.Get("1"), "With enable cascading disabled, no value for 1 should exist")
|
||||||
|
assert.Nil(t, v2.Get("2"), "With enable cascading disabled, no value for 2 should exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindAllConfigPaths(t *testing.T) {
|
||||||
|
|
||||||
|
v2 := New()
|
||||||
|
|
||||||
|
file := "viper_test"
|
||||||
|
|
||||||
|
var expected = generateCascadingTests(v2, file)
|
||||||
|
|
||||||
|
found := v2.findAllConfigFiles()
|
||||||
|
|
||||||
|
for _, fp := range expected {
|
||||||
|
command := exec.Command("rm", fp)
|
||||||
|
command.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, expected, removeDuplicates(found), "All files should exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateCascadingTests(v2 *Viper, file_name string) []string {
|
||||||
|
|
||||||
|
v2.SetConfigName(file_name)
|
||||||
|
|
||||||
|
tmp := os.Getenv("TMPDIR")
|
||||||
|
if tmp == "" {
|
||||||
|
tmp, _ = filepath.Abs(filepath.Dir("./"))
|
||||||
|
}
|
||||||
|
// $TMPDIR/a > $TMPDIR/b > %TMPDIR
|
||||||
|
paths := []string{path.Join(tmp, "a"), path.Join(tmp, "b"), tmp}
|
||||||
|
|
||||||
|
v2.SetConfigName(file_name)
|
||||||
|
|
||||||
|
var expected []string
|
||||||
|
|
||||||
|
for idx, fp := range paths {
|
||||||
|
v2.AddConfigPath(fp)
|
||||||
|
|
||||||
|
exec.Command("mkdir", "-m", "777", fp).Run()
|
||||||
|
|
||||||
|
full_path := path.Join(fp, file_name+".json")
|
||||||
|
|
||||||
|
var val string
|
||||||
|
switch idx {
|
||||||
|
case 0:
|
||||||
|
val = "high"
|
||||||
|
break
|
||||||
|
case 1:
|
||||||
|
val = "med"
|
||||||
|
break
|
||||||
|
case 2:
|
||||||
|
val = "low"
|
||||||
|
}
|
||||||
|
|
||||||
|
config := "{"
|
||||||
|
for i := 0; i <= idx; i++ {
|
||||||
|
config += fmt.Sprintf("\"%d\": \"%s\"", i, val)
|
||||||
|
if i == idx {
|
||||||
|
config += "\n"
|
||||||
|
} else {
|
||||||
|
config += ",\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config += "}"
|
||||||
|
|
||||||
|
ioutil.WriteFile(full_path, []byte(config), 0777)
|
||||||
|
|
||||||
|
expected = append(expected, full_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return expected
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeDuplicates(a []string) []string {
|
||||||
|
result := []string{}
|
||||||
|
seen := map[string]string{}
|
||||||
|
for _, val := range a {
|
||||||
|
if _, ok := seen[val]; !ok {
|
||||||
|
result = append(result, val)
|
||||||
|
seen[val] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue