Make the remote features optional

This commit is contained in:
bep 2015-05-30 21:28:33 +02:00
parent d62d4bb4c6
commit be5ff3e484
3 changed files with 130 additions and 66 deletions

View file

@ -201,6 +201,11 @@ Example:
### Remote Key/Value Store Support ### 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 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 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, over default values, but are overriden by configuration values retrieved from disk,

77
remote/remote.go Normal file
View file

@ -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{}
}

114
viper.go
View file

@ -35,7 +35,6 @@ import (
"github.com/spf13/cast" "github.com/spf13/cast"
jww "github.com/spf13/jwalterweatherman" jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/pflag" "github.com/spf13/pflag"
crypt "github.com/xordataexchange/crypt/config"
) )
var v *Viper var v *Viper
@ -44,6 +43,14 @@ func init() {
v = New() 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 // Denotes encountering an unsupported
// configuration filetype. // configuration filetype.
type UnsupportedConfigError string type UnsupportedConfigError string
@ -115,7 +122,7 @@ type Viper struct {
configPaths []string configPaths []string
// A set of remote providers to search for the configuration // A set of remote providers to search for the configuration
remoteProviders []*remoteProvider remoteProviders []*defaultRemoteProvider
// Name of file to look for inside the path // Name of file to look for inside the path
configName string configName string
@ -160,17 +167,40 @@ func Reset() {
SupportedRemoteProviders = []string{"etcd", "consul"} SupportedRemoteProviders = []string{"etcd", "consul"}
} }
// remoteProvider stores the configuration necessary type defaultRemoteProvider struct {
// to connect to a remote key/value store.
// Optional secretKeyring to unencrypt encrypted values
// can be provided.
type remoteProvider struct {
provider string provider string
endpoint string endpoint string
path string path string
secretKeyring 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. // Universally supported extensions.
var SupportedExts []string = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop"} var SupportedExts []string = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop"}
@ -252,7 +282,7 @@ func (v *Viper) AddRemoteProvider(provider, endpoint, path string) error {
} }
if provider != "" && endpoint != "" { if provider != "" && endpoint != "" {
jww.INFO.Printf("adding %s:%s to remote provider list", provider, endpoint) jww.INFO.Printf("adding %s:%s to remote provider list", provider, endpoint)
rp := &remoteProvider{ rp := &defaultRemoteProvider{
endpoint: endpoint, endpoint: endpoint,
provider: provider, provider: provider,
path: path, path: path,
@ -284,7 +314,7 @@ func (v *Viper) AddSecureRemoteProvider(provider, endpoint, path, secretkeyring
} }
if provider != "" && endpoint != "" { if provider != "" && endpoint != "" {
jww.INFO.Printf("adding %s:%s to remote provider list", provider, endpoint) jww.INFO.Printf("adding %s:%s to remote provider list", provider, endpoint)
rp := &remoteProvider{ rp := &defaultRemoteProvider{
endpoint: endpoint, endpoint: endpoint,
provider: provider, provider: provider,
path: path, path: path,
@ -296,7 +326,7 @@ func (v *Viper) AddSecureRemoteProvider(provider, endpoint, path, secretkeyring
return nil return nil
} }
func (v *Viper) providerPathExists(p *remoteProvider) bool { func (v *Viper) providerPathExists(p *defaultRemoteProvider) bool {
for _, y := range v.remoteProviders { for _, y := range v.remoteProviders {
if reflect.DeepEqual(y, p) { if reflect.DeepEqual(y, p) {
return true return true
@ -759,6 +789,10 @@ func (v *Viper) insensitiviseMaps() {
// retrieve the first found remote configuration // retrieve the first found remote configuration
func (v *Viper) getKeyValueConfig() error { 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 { for _, rp := range v.remoteProviders {
val, err := v.getRemoteConfig(rp) val, err := v.getRemoteConfig(rp)
if err != nil { if err != nil {
@ -770,36 +804,12 @@ func (v *Viper) getKeyValueConfig() error {
return RemoteConfigError("No Files Found") return RemoteConfigError("No Files Found")
} }
func (v *Viper) getRemoteConfig(provider *remoteProvider) (map[string]interface{}, error) { func (v *Viper) getRemoteConfig(provider *defaultRemoteProvider) (map[string]interface{}, error) {
var cm crypt.ConfigManager
var err error
if provider.secretKeyring != "" { reader, err := RemoteConfig.Get(provider)
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})
}
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
b, err := cm.Get(provider.path)
if err != nil {
return nil, err
}
reader := bytes.NewReader(b)
v.marshalReader(reader, v.kvstore) v.marshalReader(reader, v.kvstore)
return v.kvstore, err return v.kvstore, err
} }
@ -817,39 +827,11 @@ func (v *Viper) watchKeyValueConfig() error {
return RemoteConfigError("No Files Found") return RemoteConfigError("No Files Found")
} }
func (v *Viper) watchRemoteConfig(provider *remoteProvider) (map[string]interface{}, error) { func (v *Viper) watchRemoteConfig(provider *defaultRemoteProvider) (map[string]interface{}, error) {
var cm crypt.ConfigManager reader, err := RemoteConfig.Watch(provider)
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})
}
}
if err != nil { if err != nil {
return nil, err 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) v.marshalReader(reader, v.kvstore)
return v.kvstore, err return v.kvstore, err
} }