Make the remote features optional
diff --git a/README.md b/README.md
index 16d017b..76e1071 100644
--- a/README.md
+++ b/README.md
@@ -201,6 +201,11 @@
### Remote Key/Value Store Support
+
+To enable remote support in Viper, do a blank import of the `viper/remote` package:
+
+`import _ github.com/spf13/viper/remote`
+
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,
diff --git a/remote/remote.go b/remote/remote.go
new file mode 100644
index 0000000..faaf3b3
--- /dev/null
+++ b/remote/remote.go
@@ -0,0 +1,77 @@
+// Copyright © 2015 Steve Francia <spf@spf13.com>.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file.
+
+// Package remote integrates the remote features of Viper.
+package remote
+
+import (
+ "bytes"
+ "github.com/spf13/viper"
+ crypt "github.com/xordataexchange/crypt/config"
+ "io"
+ "os"
+)
+
+type remoteConfigProvider struct{}
+
+func (rc remoteConfigProvider) Get(rp viper.RemoteProvider) (io.Reader, error) {
+ cm, err := getConfigManager(rp)
+ if err != nil {
+ return nil, err
+ }
+ b, err := cm.Get(rp.Path())
+ if err != nil {
+ return nil, err
+ }
+ return bytes.NewReader(b), nil
+}
+
+func (rc remoteConfigProvider) Watch(rp viper.RemoteProvider) (io.Reader, error) {
+ cm, err := getConfigManager(rp)
+ if err != nil {
+ return nil, err
+ }
+ resp := <-cm.Watch(rp.Path(), nil)
+ err = resp.Error
+ if err != nil {
+ return nil, err
+ }
+
+ return bytes.NewReader(resp.Value), nil
+}
+
+func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) {
+
+ var cm crypt.ConfigManager
+ var err error
+
+ if rp.SecretKeyring() != "" {
+ kr, err := os.Open(rp.SecretKeyring())
+ defer kr.Close()
+ if err != nil {
+ return nil, err
+ }
+ if rp.Provider() == "etcd" {
+ cm, err = crypt.NewEtcdConfigManager([]string{rp.Endpoint()}, kr)
+ } else {
+ cm, err = crypt.NewConsulConfigManager([]string{rp.Endpoint()}, kr)
+ }
+ } else {
+ if rp.Provider() == "etcd" {
+ cm, err = crypt.NewStandardEtcdConfigManager([]string{rp.Endpoint()})
+ } else {
+ cm, err = crypt.NewStandardConsulConfigManager([]string{rp.Endpoint()})
+ }
+ }
+ if err != nil {
+ return nil, err
+ }
+ return cm, nil
+
+}
+
+func init() {
+ viper.RemoteConfig = &remoteConfigProvider{}
+}
diff --git a/viper.go b/viper.go
index 11f3a77..799983f 100644
--- a/viper.go
+++ b/viper.go
@@ -35,7 +35,6 @@
"github.com/spf13/cast"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/pflag"
- crypt "github.com/xordataexchange/crypt/config"
)
var v *Viper
@@ -44,6 +43,14 @@
v = New()
}
+type remoteConfigFactory interface {
+ Get(rp RemoteProvider) (io.Reader, error)
+ Watch(rp RemoteProvider) (io.Reader, error)
+}
+
+// RemoteConfig is optional, see the remote package
+var RemoteConfig remoteConfigFactory
+
// Denotes encountering an unsupported
// configuration filetype.
type UnsupportedConfigError string
@@ -115,7 +122,7 @@
configPaths []string
// A set of remote providers to search for the configuration
- remoteProviders []*remoteProvider
+ remoteProviders []*defaultRemoteProvider
// Name of file to look for inside the path
configName string
@@ -160,17 +167,40 @@
SupportedRemoteProviders = []string{"etcd", "consul"}
}
-// 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 {
+type defaultRemoteProvider struct {
provider string
endpoint string
path string
secretKeyring string
}
+func (rp defaultRemoteProvider) Provider() string {
+ return rp.provider
+}
+
+func (rp defaultRemoteProvider) Endpoint() string {
+ return rp.endpoint
+}
+
+func (rp defaultRemoteProvider) Path() string {
+ return rp.path
+}
+
+func (rp defaultRemoteProvider) SecretKeyring() string {
+ return rp.secretKeyring
+}
+
+// RemoteProvider stores the configuration necessary
+// to connect to a remote key/value store.
+// Optional secretKeyring to unencrypt encrypted values
+// can be provided.
+type RemoteProvider interface {
+ Provider() string
+ Endpoint() string
+ Path() string
+ SecretKeyring() string
+}
+
// Universally supported extensions.
var SupportedExts []string = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop"}
@@ -252,7 +282,7 @@
}
if provider != "" && endpoint != "" {
jww.INFO.Printf("adding %s:%s to remote provider list", provider, endpoint)
- rp := &remoteProvider{
+ rp := &defaultRemoteProvider{
endpoint: endpoint,
provider: provider,
path: path,
@@ -284,7 +314,7 @@
}
if provider != "" && endpoint != "" {
jww.INFO.Printf("adding %s:%s to remote provider list", provider, endpoint)
- rp := &remoteProvider{
+ rp := &defaultRemoteProvider{
endpoint: endpoint,
provider: provider,
path: path,
@@ -296,7 +326,7 @@
return nil
}
-func (v *Viper) providerPathExists(p *remoteProvider) bool {
+func (v *Viper) providerPathExists(p *defaultRemoteProvider) bool {
for _, y := range v.remoteProviders {
if reflect.DeepEqual(y, p) {
return true
@@ -759,6 +789,10 @@
// retrieve the first found remote configuration
func (v *Viper) getKeyValueConfig() error {
+ if RemoteConfig == nil {
+ return RemoteConfigError("Enable the remote features by doing a blank import of the viper/remote package: '_ github.com/spf13/viper/remote'")
+ }
+
for _, rp := range v.remoteProviders {
val, err := v.getRemoteConfig(rp)
if err != nil {
@@ -770,36 +804,12 @@
return RemoteConfigError("No Files Found")
}
-func (v *Viper) getRemoteConfig(provider *remoteProvider) (map[string]interface{}, error) {
- var cm crypt.ConfigManager
- var err error
+func (v *Viper) getRemoteConfig(provider *defaultRemoteProvider) (map[string]interface{}, error) {
- if provider.secretKeyring != "" {
- kr, err := os.Open(provider.secretKeyring)
- defer kr.Close()
- if err != nil {
- return nil, err
- }
- if provider.provider == "etcd" {
- cm, err = crypt.NewEtcdConfigManager([]string{provider.endpoint}, kr)
- } else {
- cm, err = crypt.NewConsulConfigManager([]string{provider.endpoint}, kr)
- }
- } else {
- if provider.provider == "etcd" {
- cm, err = crypt.NewStandardEtcdConfigManager([]string{provider.endpoint})
- } else {
- cm, err = crypt.NewStandardConsulConfigManager([]string{provider.endpoint})
- }
- }
+ reader, err := RemoteConfig.Get(provider)
if err != nil {
return nil, err
}
- b, err := cm.Get(provider.path)
- if err != nil {
- return nil, err
- }
- reader := bytes.NewReader(b)
v.marshalReader(reader, v.kvstore)
return v.kvstore, err
}
@@ -817,39 +827,11 @@
return RemoteConfigError("No Files Found")
}
-func (v *Viper) watchRemoteConfig(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
- }
- if provider.provider == "etcd" {
- cm, err = crypt.NewEtcdConfigManager([]string{provider.endpoint}, kr)
- } else {
- cm, err = crypt.NewConsulConfigManager([]string{provider.endpoint}, kr)
- }
- } else {
- if provider.provider == "etcd" {
- cm, err = crypt.NewStandardEtcdConfigManager([]string{provider.endpoint})
- } else {
- cm, err = crypt.NewStandardConsulConfigManager([]string{provider.endpoint})
- }
- }
+func (v *Viper) watchRemoteConfig(provider *defaultRemoteProvider) (map[string]interface{}, error) {
+ reader, err := RemoteConfig.Watch(provider)
if err != nil {
return nil, err
}
- resp := <-cm.Watch(provider.path, nil)
- // b, err := cm.Watch(provider.path, nil)
- err = resp.Error
- if err != nil {
- return nil, err
- }
-
- reader := bytes.NewReader(resp.Value)
v.marshalReader(reader, v.kvstore)
return v.kvstore, err
}