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()