From 3d8182460cd36ff24fc8263d5f9294d0099c0135 Mon Sep 17 00:00:00 2001 From: Brian Ketelsen Date: Fri, 24 Oct 2014 15:38:01 -0400 Subject: [PATCH 01/10] work in progress --- viper.go | 140 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 137 insertions(+), 3 deletions(-) diff --git a/viper.go b/viper.go index e658a8e..8f2b60a 100644 --- a/viper.go +++ b/viper.go @@ -5,13 +5,15 @@ // Viper is a application configuration system. // It believes that applications can be configured a variety of ways -// via flags, ENVIRONMENT variables, configuration files. +// via flags, ENVIRONMENT variables, configuration files retrieved +// from the file system, or a remote key/value store. // Each item takes precedence over the item below it: // flag // env // config +// key/value store // default package viper @@ -25,6 +27,7 @@ import ( "os" "path" "path/filepath" + "reflect" "runtime" "strings" "time" @@ -38,14 +41,29 @@ import ( "gopkg.in/yaml.v1" ) +// remoteProvider stores the configuration necessary +// to connect to a remote key/value store. +// Optional secretKeyring to unencrypt encrypted values +// can be provided. +type remoteProvider struct { + provider string + endpoint string + path string + secretKeyring string +} + // A set of paths to look for the config file in var configPaths []string +// A set of remote providers to search for the configuration +var remoteProviders []*remoteProvider + // Name of file to look for inside the path var configName string = "config" // extensions Supported var SupportedExts []string = []string{"json", "toml", "yaml", "yml"} +var SupportedRemoteProviders []string = []string{"etcd", "consul"} var configFile string var configType string @@ -53,6 +71,7 @@ var config map[string]interface{} = make(map[string]interface{}) var override map[string]interface{} = make(map[string]interface{}) var env map[string]string = make(map[string]string) var defaults map[string]interface{} = make(map[string]interface{}) +var kvstore map[string]interface{} = make(map[string]interface{}) var pflags map[string]*pflag.Flag = make(map[string]*pflag.Flag) var aliases map[string]string = make(map[string]string) @@ -81,6 +100,74 @@ func AddConfigPath(in string) { } } +// AddRemoteProvider adds a remote configuration source. +// Remote Providers are searched in the order they are added. +// provider is a string value, "etcd" or "consul" are currently supported. +// endpoint is the url. etcd requires http://ip:port consul requires ip:port +// path is the path in the k/v store to retrieve configuration +// To retrieve a config file called myapp.json from /configs/myapp.json +// you should set path to /configs and set config name (SetConfigName()) to +// "myapp" +func AddRemoteProvider(provider, endpoint, path string) error { + if !stringInSlice(provider, SupportedRemoteProviders) { + return UnsupportedRemoteProviderError(provider) + } + if provider != "" && endpoint != "" { + jww.INFO.Printf("adding %s:%s to remote provider list", provider, endpoint) + rp := &remoteProvider{ + endpoint: endpoint, + provider: provider, + } + if !providerPathExists(rp) { + remoteProviders = append(remoteProviders, rp) + } + } + return nil +} + +// AddSecureRemoteProvider adds a remote configuration source. +// Secure Remote Providers are searched in the order they are added. +// provider is a string value, "etcd" or "consul" are currently supported. +// endpoint is the url. etcd requires http://ip:port consul requires ip:port +// secretkeyring is the filepath to your openpgp secret keyring. e.g. /etc/secrets/myring.gpg +// path is the path in the k/v store to retrieve configuration +// To retrieve a config file called myapp.json from /configs/myapp.json +// you should set path to /configs and set config name (SetConfigName()) to +// "myapp" +// Secure Remote Providers are implemented with github.com/xordataexchange/crypt +func AddSecureRemoteProvider(provider, endpoint, secretkeyring string) error { + if !stringInSlice(provider, SupportedRemoteProviders) { + return UnsupportedRemoteProviderError(provider) + } + if provider != "" && endpoint != "" { + jww.INFO.Printf("adding %s:%s to remote provider list", provider, endpoint) + rp := &remoteProvider{ + endpoint: endpoint, + provider: provider, + } + if !providerPathExists(rp) { + remoteProviders = append(remoteProviders, rp) + } + } + return nil +} + +func providerPathExists(p *remoteProvider) bool { + + for _, y := range remoteProviders { + if reflect.DeepEqual(y, p) { + return true + } + } + return false +} + +type UnsupportedRemoteProviderError string + +func (str UnsupportedRemoteProviderError) Error() string { + return fmt.Sprintf("Unsupported Remote Provider Type %q", string(str)) +} + func GetString(key string) string { return cast.ToString(Get(key)) } @@ -132,6 +219,10 @@ func Marshal(rawVal interface{}) error { if err != nil { return err } + err = mapstructure.Decode(kvstore, rawVal) + if err != nil { + return err + } insensativiseMaps() @@ -221,6 +312,12 @@ func find(key string) interface{} { return val } + val, exists = kvstore[key] + if exists { + jww.TRACE.Println(key, "found in key/value store:", val) + return val + } + val, exists = defaults[key] if exists { jww.TRACE.Println(key, "found in defaults:", val) @@ -289,6 +386,10 @@ func registerAlias(alias string, key string) { delete(config, alias) config[key] = val } + if val, ok := kvstore[alias]; ok { + delete(kvstore, alias) + kvstore[key] = val + } if val, ok := defaults[alias]; ok { delete(defaults, alias) defaults[key] = val @@ -331,7 +432,8 @@ func SetDefault(key string, value interface{}) { } // The user provided value (via flag) -// Will be used instead of values obtained via config file, ENV or default +// Will be used instead of values obtained via +// config file, ENV, default, or key/value store func Set(key string, value interface{}) { // If alias passed in, then set the proper override key = realKey(strings.ToLower(key)) @@ -345,7 +447,7 @@ func (str UnsupportedConfigError) Error() string { } // Viper will discover and load the configuration file from disk -// searching in one of the defined paths. +// and key/value stores, searching in one of the defined paths. func ReadInConfig() error { jww.INFO.Println("Attempting to read in config file") if !stringInSlice(getConfigType(), SupportedExts) { @@ -357,6 +459,8 @@ func ReadInConfig() error { return err } + getKeyValueConfig() + MarshallReader(bytes.NewReader(file)) return nil } @@ -389,6 +493,29 @@ func insensativiseMaps() { insensativiseMap(config) insensativiseMap(defaults) insensativiseMap(override) + insensativiseMap(kvstore) +} + +// retrieve the first found remote configuration +func getKeyValueConfig() { + for _, rp := range remoteProviders { + val, err := getRemoteConfig(rp) + if err != nil { + kvstore = val + return + } + } +} + +func getRemoteConfig(provider *remoteProvider) (map[string]interface{}, error) { + switch provider.provider { + case "etcd": + // do something + case "consul": + // do something + + } + return config, nil } func insensativiseMap(m map[string]interface{}) { @@ -412,6 +539,10 @@ func AllKeys() []string { m[key] = struct{}{} } + for key, _ := range kvstore { + m[key] = struct{}{} + } + for key, _ := range override { m[key] = struct{}{} } @@ -594,6 +725,8 @@ func absPathify(inPath string) string { func Debug() { fmt.Println("Config:") pretty.Println(config) + fmt.Println("Key/Value Store:") + pretty.Println(kvstore) fmt.Println("Env:") pretty.Println(env) fmt.Println("Defaults:") @@ -613,6 +746,7 @@ func Reset() { configFile = "" configType = "" + kvstore = make(map[string]interface{}) config = make(map[string]interface{}) override = make(map[string]interface{}) env = make(map[string]string) From a28bee1fbaa139fb632f2b13d03a5e878f6b5adb Mon Sep 17 00:00:00 2001 From: Brian Ketelsen Date: Sun, 26 Oct 2014 09:42:03 -0400 Subject: [PATCH 02/10] adding preliminary etcd support --- viper.go | 55 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/viper.go b/viper.go index 8f2b60a..f0790b9 100644 --- a/viper.go +++ b/viper.go @@ -38,6 +38,7 @@ import ( "github.com/spf13/cast" jww "github.com/spf13/jwalterweatherman" "github.com/spf13/pflag" + crypt "github.com/xordataexchange/crypt/config" "gopkg.in/yaml.v1" ) @@ -459,34 +460,37 @@ func ReadInConfig() error { return err } - getKeyValueConfig() + err = getKeyValueConfig() + if err != nil { + return err + } - MarshallReader(bytes.NewReader(file)) + MarshallReader(bytes.NewReader(file), config) return nil } -func MarshallReader(in io.Reader) { +func MarshallReader(in io.Reader, c map[string]interface{}) { buf := new(bytes.Buffer) buf.ReadFrom(in) switch getConfigType() { case "yaml", "yml": - if err := yaml.Unmarshal(buf.Bytes(), &config); err != nil { + if err := yaml.Unmarshal(buf.Bytes(), &c); err != nil { jww.ERROR.Fatalf("Error parsing config: %s", err) } case "json": - if err := json.Unmarshal(buf.Bytes(), &config); err != nil { + if err := json.Unmarshal(buf.Bytes(), &c); err != nil { jww.ERROR.Fatalf("Error parsing config: %s", err) } case "toml": - if _, err := toml.Decode(buf.String(), &config); err != nil { + if _, err := toml.Decode(buf.String(), &c); err != nil { jww.ERROR.Fatalf("Error parsing config: %s", err) } } - insensativiseMap(config) + insensativiseMap(c) } func insensativiseMaps() { @@ -497,20 +501,23 @@ func insensativiseMaps() { } // retrieve the first found remote configuration -func getKeyValueConfig() { +func getKeyValueConfig() error { + var err error for _, rp := range remoteProviders { val, err := getRemoteConfig(rp) if err != nil { - kvstore = val - return + continue } + kvstore = val + return nil } + return err } func getRemoteConfig(provider *remoteProvider) (map[string]interface{}, error) { switch provider.provider { case "etcd": - // do something + return getEtcdConfig(provider) case "consul": // do something @@ -518,6 +525,32 @@ func getRemoteConfig(provider *remoteProvider) (map[string]interface{}, error) { return config, nil } +func getEtcdConfig(provider *remoteProvider) (map[string]interface{}, error) { + var cm crypt.ConfigManager + var err error + + if provider.secretKeyring != "" { + kr, err := os.Open(provider.secretKeyring) + defer kr.Close() + if err != nil { + return nil, err + } + cm, err = crypt.NewEtcdConfigManager([]string{provider.endpoint}, kr) + } else { + cm, err = crypt.NewStandardEtcdConfigManager([]string{provider.endpoint}) + } + if err != nil { + return nil, err + } + b, err := cm.Get(configFile) + if err != nil { + return nil, err + } + reader := bytes.NewReader(b) + MarshallReader(reader, kvstore) + return nil, err +} + func insensativiseMap(m map[string]interface{}) { for key, val := range m { lower := strings.ToLower(key) From f8939d92298d973ba1b80b850edc5b9b1d030e91 Mon Sep 17 00:00:00 2001 From: Brian Ketelsen Date: Sun, 26 Oct 2014 09:48:21 -0400 Subject: [PATCH 03/10] existing tests pass --- viper.go | 2 +- viper_test.go | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/viper.go b/viper.go index f0790b9..8882a73 100644 --- a/viper.go +++ b/viper.go @@ -519,7 +519,7 @@ func getRemoteConfig(provider *remoteProvider) (map[string]interface{}, error) { case "etcd": return getEtcdConfig(provider) case "consul": - // do something + // not implemented yet } return config, nil diff --git a/viper_test.go b/viper_test.go index 9818138..941bbf9 100644 --- a/viper_test.go +++ b/viper_test.go @@ -65,7 +65,7 @@ func TestMarshalling(t *testing.T) { SetConfigType("yaml") r := bytes.NewReader(yamlExample) - MarshallReader(r) + MarshallReader(r, config) assert.True(t, InConfig("name")) assert.False(t, InConfig("state")) assert.Equal(t, "steve", Get("name")) @@ -106,7 +106,7 @@ func TestYML(t *testing.T) { SetConfigType("yml") r := bytes.NewReader(yamlExample) - MarshallReader(r) + MarshallReader(r, config) assert.Equal(t, "steve", Get("name")) } @@ -114,7 +114,7 @@ func TestJSON(t *testing.T) { SetConfigType("json") r := bytes.NewReader(jsonExample) - MarshallReader(r) + MarshallReader(r, config) assert.Equal(t, "0001", Get("id")) } @@ -122,14 +122,14 @@ func TestTOML(t *testing.T) { SetConfigType("toml") r := bytes.NewReader(tomlExample) - MarshallReader(r) + MarshallReader(r, config) assert.Equal(t, "TOML Example", Get("title")) } func TestEnv(t *testing.T) { SetConfigType("json") r := bytes.NewReader(jsonExample) - MarshallReader(r) + MarshallReader(r, config) BindEnv("id") BindEnv("f", "FOOD") From c33e6906870fc45be733df7f2f6c0dfcacf5f8a1 Mon Sep 17 00:00:00 2001 From: Brian Ketelsen Date: Mon, 27 Oct 2014 10:14:45 -0400 Subject: [PATCH 04/10] add readremoteconfig for cases when there are no local configuration files --- viper.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/viper.go b/viper.go index 8882a73..9b9222c 100644 --- a/viper.go +++ b/viper.go @@ -468,7 +468,13 @@ func ReadInConfig() error { MarshallReader(bytes.NewReader(file), config) return nil } - +func ReadRemoteConfig() error { + err := getKeyValueConfig() + if err != nil { + return err + } + return nil +} func MarshallReader(in io.Reader, c map[string]interface{}) { buf := new(bytes.Buffer) buf.ReadFrom(in) From d2d8f6caa10ca54d0a9381c7b87442d7acf3dd5e Mon Sep 17 00:00:00 2001 From: Brian Ketelsen Date: Mon, 27 Oct 2014 11:03:11 -0400 Subject: [PATCH 05/10] added error for remote configuration not found --- viper.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/viper.go b/viper.go index 9b9222c..cea9e8d 100644 --- a/viper.go +++ b/viper.go @@ -508,7 +508,6 @@ func insensativiseMaps() { // retrieve the first found remote configuration func getKeyValueConfig() error { - var err error for _, rp := range remoteProviders { val, err := getRemoteConfig(rp) if err != nil { @@ -517,7 +516,13 @@ func getKeyValueConfig() error { kvstore = val return nil } - return err + return RemoteConfigError("No Files Found") +} + +type RemoteConfigError string + +func (rce RemoteConfigError) Error() string { + return fmt.Sprintf("Remote Configurations Error: %s", string(rce)) } func getRemoteConfig(provider *remoteProvider) (map[string]interface{}, error) { From ea041020036513c4caf47de5298abe9d255d2c35 Mon Sep 17 00:00:00 2001 From: Brian Ketelsen Date: Mon, 27 Oct 2014 11:06:20 -0400 Subject: [PATCH 06/10] Actually return the kvstore retrieved from the kv server --- viper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viper.go b/viper.go index cea9e8d..075f870 100644 --- a/viper.go +++ b/viper.go @@ -559,7 +559,7 @@ func getEtcdConfig(provider *remoteProvider) (map[string]interface{}, error) { } reader := bytes.NewReader(b) MarshallReader(reader, kvstore) - return nil, err + return kvstore, err } func insensativiseMap(m map[string]interface{}) { From 5e1d5e72079eb3da981a39c4c6f7922fdaefc6ad Mon Sep 17 00:00:00 2001 From: Brian Ketelsen Date: Mon, 27 Oct 2014 12:21:03 -0400 Subject: [PATCH 07/10] removing some debugging printlns --- viper.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/viper.go b/viper.go index 075f870..b065a08 100644 --- a/viper.go +++ b/viper.go @@ -118,6 +118,7 @@ func AddRemoteProvider(provider, endpoint, path string) error { rp := &remoteProvider{ endpoint: endpoint, provider: provider, + path: path, } if !providerPathExists(rp) { remoteProviders = append(remoteProviders, rp) @@ -136,7 +137,7 @@ func AddRemoteProvider(provider, endpoint, path string) error { // you should set path to /configs and set config name (SetConfigName()) to // "myapp" // Secure Remote Providers are implemented with github.com/xordataexchange/crypt -func AddSecureRemoteProvider(provider, endpoint, secretkeyring string) error { +func AddSecureRemoteProvider(provider, endpoint, path, secretkeyring string) error { if !stringInSlice(provider, SupportedRemoteProviders) { return UnsupportedRemoteProviderError(provider) } @@ -145,6 +146,7 @@ func AddSecureRemoteProvider(provider, endpoint, secretkeyring string) error { rp := &remoteProvider{ endpoint: endpoint, provider: provider, + path: path, } if !providerPathExists(rp) { remoteProviders = append(remoteProviders, rp) @@ -460,11 +462,6 @@ func ReadInConfig() error { return err } - err = getKeyValueConfig() - if err != nil { - return err - } - MarshallReader(bytes.NewReader(file), config) return nil } @@ -553,7 +550,7 @@ func getEtcdConfig(provider *remoteProvider) (map[string]interface{}, error) { if err != nil { return nil, err } - b, err := cm.Get(configFile) + b, err := cm.Get(provider.path) if err != nil { return nil, err } From 51da30f655d2ec1dff22979395a9706d79560dc1 Mon Sep 17 00:00:00 2001 From: Brian Ketelsen Date: Mon, 27 Oct 2014 15:32:46 -0400 Subject: [PATCH 08/10] Added basic documentation, pointers to crypt repository, and tests for precedence --- README.md | 46 ++++++++++++++++++++++++++++++++++++++++++---- viper_test.go | 26 ++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 09a3d50..d470fd6 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ Go configuration with fangs Viper is a complete configuration solution. Designed to work within an application to handle file based configuration and seamlessly marry that with command line flags which can also be used to control application behavior. +Viper also supports retrieving configuration values from remote key/value stores. +Etcd is currently supported, and Consul is coming soon. ## Why Viper? @@ -26,10 +28,8 @@ Viper does the following for you: Viper believes that: 1. command line flags take precedence over options set in config files -2. config files take precedence over defaults - -Config files often can be found in multiple locations. Viper allows you to set -multiple paths to search for the config file in. +2. config files take precedence over options set in remote key/value stores +3. remote key/value stores take precedence over defaults Viper configuration keys are case insensitive. @@ -70,6 +70,44 @@ Viper configuration keys are case insensitive. fmt.Println("verbose enabled") } +### Remote Key/Value Store Support +Viper will read a config string (as JSON, TOML, or YAML) retrieved from a +path in a Key/Value store such as Etcd or Consul. These values take precedence +over default values, but are overriden by configuration values retrieved from disk, +flags, or environment variables. + +Viper uses [crypt](https://github.com/xordataexchange/crypt) to retrieve configuration +from the k/v store, which means that you can store your configuration values +encrypted and have them automatically decrypted if you have the correct +gpg keyring. Encryption is optional. + +You can use remote configuration in conjunction with local configuration, or +independently of it. + +`crypt` has a command-line helper that you can use to put configurations +in your k/v store. `crypt` defaults to etcd on http://127.0.0.1:4001. + + go get github.com/xordataexchange/crypt/bin/crypt + crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json + +Confirm that your value was set: + + crypt get -plaintext /config/hugo.json + +See the `crypt` documentation for examples of how to set encrypted values, or how +to use Consul. + +### Remote Key/Value Store Example - Unencrypted + + viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json") + err := viper.ReadRemoteConfig() + +### Remote Key/Value Store Example - Encrypted + + viper.AddSecureRemoteProvier("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg") + err := viper.ReadRemoteConfig() + + ## Q & A diff --git a/viper_test.go b/viper_test.go index 941bbf9..20b1823 100644 --- a/viper_test.go +++ b/viper_test.go @@ -51,6 +51,12 @@ var jsonExample = []byte(`{ } }`) +var remoteExample = []byte(`{ +"id":"0002", +"type":"cronut", +"newkey":"remote" +}`) + func TestBasics(t *testing.T) { SetConfigFile("/tmp/config.yaml") assert.Equal(t, "/tmp/config.yaml", getConfigFile()) @@ -126,6 +132,22 @@ func TestTOML(t *testing.T) { assert.Equal(t, "TOML Example", Get("title")) } +func TestRemotePrecedence(t *testing.T) { + SetConfigType("json") + r := bytes.NewReader(jsonExample) + MarshallReader(r, config) + remote := bytes.NewReader(remoteExample) + assert.Equal(t, "0001", Get("id")) + MarshallReader(remote, kvstore) + assert.Equal(t, "0001", Get("id")) + assert.NotEqual(t, "cronut", Get("type")) + assert.Equal(t, "remote", Get("newkey")) + Set("newkey", "newvalue") + assert.NotEqual(t, "remote", Get("newkey")) + assert.Equal(t, "newvalue", Get("newkey")) + Set("newkey", "remote") +} + func TestEnv(t *testing.T) { SetConfigType("json") r := bytes.NewReader(jsonExample) @@ -147,9 +169,9 @@ func TestEnv(t *testing.T) { } func TestAllKeys(t *testing.T) { - ks := sort.StringSlice{"title", "owner", "name", "beard", "ppu", "batters", "hobbies", "clothing", "age", "hacker", "id", "type"} + ks := sort.StringSlice{"title", "newkey", "owner", "name", "beard", "ppu", "batters", "hobbies", "clothing", "age", "hacker", "id", "type"} dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") - all := map[string]interface{}{"hacker": true, "beard": true, "batters": map[string]interface{}{"batter": []interface{}{map[string]interface{}{"type": "Regular"}, map[string]interface{}{"type": "Chocolate"}, map[string]interface{}{"type": "Blueberry"}, map[string]interface{}{"type": "Devil's Food"}}}, "hobbies": []interface{}{"skateboarding", "snowboarding", "go"}, "ppu": 0.55, "clothing": map[interface{}]interface{}{"jacket": "leather", "trousers": "denim"}, "name": "crunk", "owner": map[string]interface{}{"organization": "MongoDB", "Bio": "MongoDB Chief Developer Advocate & Hacker at Large", "dob": dob}, "id": "13", "title": "TOML Example", "age": 35, "type": "donut"} + all := map[string]interface{}{"hacker": true, "beard": true, "newkey": "remote", "batters": map[string]interface{}{"batter": []interface{}{map[string]interface{}{"type": "Regular"}, map[string]interface{}{"type": "Chocolate"}, map[string]interface{}{"type": "Blueberry"}, map[string]interface{}{"type": "Devil's Food"}}}, "hobbies": []interface{}{"skateboarding", "snowboarding", "go"}, "ppu": 0.55, "clothing": map[interface{}]interface{}{"jacket": "leather", "trousers": "denim"}, "name": "crunk", "owner": map[string]interface{}{"organization": "MongoDB", "Bio": "MongoDB Chief Developer Advocate & Hacker at Large", "dob": dob}, "id": "13", "title": "TOML Example", "age": 35, "type": "donut"} var allkeys sort.StringSlice allkeys = AllKeys() From 563f4d44c41aa83380a63f0f0043cfc23beb7711 Mon Sep 17 00:00:00 2001 From: Brian Ketelsen Date: Mon, 27 Oct 2014 21:14:46 -0400 Subject: [PATCH 09/10] add support for consul --- viper.go | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/viper.go b/viper.go index b065a08..d4e7fdc 100644 --- a/viper.go +++ b/viper.go @@ -523,17 +523,6 @@ func (rce RemoteConfigError) Error() string { } func getRemoteConfig(provider *remoteProvider) (map[string]interface{}, error) { - switch provider.provider { - case "etcd": - return getEtcdConfig(provider) - case "consul": - // not implemented yet - - } - return config, nil -} - -func getEtcdConfig(provider *remoteProvider) (map[string]interface{}, error) { var cm crypt.ConfigManager var err error @@ -543,9 +532,17 @@ func getEtcdConfig(provider *remoteProvider) (map[string]interface{}, error) { if err != nil { return nil, err } - cm, err = crypt.NewEtcdConfigManager([]string{provider.endpoint}, kr) + if provider.provider == "etcd" { + cm, err = crypt.NewEtcdConfigManager([]string{provider.endpoint}, kr) + } else { + cm, err = crypt.NewConsulConfigManager([]string{provider.endpoint}, kr) + } } else { - cm, err = crypt.NewStandardEtcdConfigManager([]string{provider.endpoint}) + if provider.provider == "etcd" { + cm, err = crypt.NewStandardEtcdConfigManager([]string{provider.endpoint}) + } else { + cm, err = crypt.NewStandardConsulConfigManager([]string{provider.endpoint}) + } } if err != nil { return nil, err From e55c96ecc93e092a32a7739d7542a4b29621cf5e Mon Sep 17 00:00:00 2001 From: Brian Ketelsen Date: Mon, 27 Oct 2014 21:53:22 -0400 Subject: [PATCH 10/10] document consul support, not requirement to set config type for remote KV stores --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d470fd6..6051f6a 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Viper is a complete configuration solution. Designed to work within an application to handle file based configuration and seamlessly marry that with command line flags which can also be used to control application behavior. Viper also supports retrieving configuration values from remote key/value stores. -Etcd is currently supported, and Consul is coming soon. +Etcd and Consul are supported. ## Why Viper? @@ -100,11 +100,13 @@ to use Consul. ### Remote Key/Value Store Example - Unencrypted viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json") + viper.SetConfigType("json") // because there is no file extension in a stream of bytes err := viper.ReadRemoteConfig() ### Remote Key/Value Store Example - Encrypted viper.AddSecureRemoteProvier("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg") + viper.SetConfigType("json") // because there is no file extension in a stream of bytes err := viper.ReadRemoteConfig()