mirror of
https://github.com/spf13/viper
synced 2024-12-22 19:47: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
|
||||
_obj
|
||||
_test
|
||||
.idea
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
|
|
28
README.md
28
README.md
|
@ -72,7 +72,7 @@ Examples:
|
|||
If you want to support a config file, Viper requires a minimal
|
||||
configuration so it knows where to look for the config file. Viper
|
||||
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.AddConfigPath("/etc/appname/") // path to look for the config file in
|
||||
|
@ -84,14 +84,14 @@ currently a single viper only supports a single config file.
|
|||
|
||||
### Reading Config from io.Reader
|
||||
|
||||
Viper predefined many configuration sources, such as files, environment variables, flags and
|
||||
Viper predefined many configuration sources, such as files, environment variables, flags and
|
||||
remote K/V store. But you are not bound to them. You can also implement your own way to
|
||||
require configuration and feed it to viper.
|
||||
|
||||
````go
|
||||
viper.SetConfigType("yaml") // or viper.SetConfigType("YAML")
|
||||
|
||||
// any approach to require this configuration into your program.
|
||||
// any approach to require this configuration into your program.
|
||||
var yamlExample = []byte(`
|
||||
Hacker: true
|
||||
name: steve
|
||||
|
@ -112,6 +112,22 @@ viper.ReadConfig(bytes.NewBuffer(yamlExample))
|
|||
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
|
||||
|
||||
These could be from a command line flag, or from your own application logic.
|
||||
|
@ -270,7 +286,7 @@ to use Consul.
|
|||
continue
|
||||
}
|
||||
|
||||
// marshal new config into our runtime config struct. you can also use channel
|
||||
// marshal new config into our runtime config struct. you can also use channel
|
||||
// to implement a signal to notify the system of the changes
|
||||
runtime_viper.Marshal(&runtime_conf)
|
||||
}
|
||||
|
@ -307,7 +323,7 @@ Example:
|
|||
|
||||
### Accessing nested keys
|
||||
|
||||
The accessor methods also accept formatted paths to deeply nested keys.
|
||||
The accessor methods also accept formatted paths to deeply nested keys.
|
||||
For example, if the following JSON file is loaded:
|
||||
|
||||
```
|
||||
|
@ -346,7 +362,7 @@ On the other hand, if the primary key was not defined, Viper would go through th
|
|||
remaining registries looking for it.
|
||||
|
||||
Lastly, if there exists a key that matches the delimited key path, its value will
|
||||
be returned instead. E.g.
|
||||
be returned instead. E.g.
|
||||
|
||||
```
|
||||
{
|
||||
|
|
103
viper.go
103
viper.go
|
@ -133,13 +133,16 @@ type Viper struct {
|
|||
automaticEnvApplied bool
|
||||
envKeyReplacer *strings.Replacer
|
||||
|
||||
config map[string]interface{}
|
||||
override map[string]interface{}
|
||||
defaults map[string]interface{}
|
||||
kvstore map[string]interface{}
|
||||
pflags map[string]*pflag.Flag
|
||||
env map[string]string
|
||||
aliases map[string]string
|
||||
cascadeConfigurations bool
|
||||
|
||||
config map[string]interface{}
|
||||
override map[string]interface{}
|
||||
defaults map[string]interface{}
|
||||
kvstore map[string]interface{}
|
||||
cascadingConfigs map[string]map[string]interface{}
|
||||
pflags map[string]*pflag.Flag
|
||||
env map[string]string
|
||||
aliases map[string]string
|
||||
}
|
||||
|
||||
// Returns an initialized Viper instance.
|
||||
|
@ -154,6 +157,7 @@ func New() *Viper {
|
|||
v.pflags = make(map[string]*pflag.Flag)
|
||||
v.env = make(map[string]string)
|
||||
v.aliases = make(map[string]string)
|
||||
v.cascadeConfigurations = false
|
||||
|
||||
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 {
|
||||
if v.envPrefix != "" {
|
||||
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 (v *Viper) Marshal(rawVal interface{}) error {
|
||||
err := mapstructure.WeakDecode(v.AllSettings(), rawVal)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -562,6 +572,7 @@ func (v *Viper) BindEnv(input ...string) (err error) {
|
|||
func (v *Viper) find(key string) interface{} {
|
||||
var val interface{}
|
||||
var exists bool
|
||||
var file string
|
||||
|
||||
// if the requested key is an alias, then return the proper key
|
||||
key = v.realKey(key)
|
||||
|
@ -607,6 +618,15 @@ func (v *Viper) find(key string) interface{} {
|
|||
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]
|
||||
if exists {
|
||||
jww.TRACE.Println(key, "found in key/value store:", val)
|
||||
|
@ -622,6 +642,53 @@ func (v *Viper) find(key string) interface{} {
|
|||
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
|
||||
func IsSet(key string) bool { return v.IsSet(key) }
|
||||
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) {
|
||||
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 {
|
||||
file := v.searchInPath(cp)
|
||||
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()
|
||||
file := v.searchInPath(wd)
|
||||
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
|
||||
|
|
105
viper_test.go
105
viper_test.go
|
@ -10,6 +10,8 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"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, "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