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
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,

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"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/pflag"
crypt "github.com/xordataexchange/crypt/config"
)
var v *Viper
@ -44,6 +43,14 @@ func init() {
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 @@ type Viper struct {
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 @@ func Reset() {
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 @@ func (v *Viper) AddRemoteProvider(provider, endpoint, path string) error {
}
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 @@ func (v *Viper) AddSecureRemoteProvider(provider, endpoint, path, secretkeyring
}
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 @@ func (v *Viper) AddSecureRemoteProvider(provider, endpoint, path, secretkeyring
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 @@ func (v *Viper) insensitiviseMaps() {
// 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 @@ func (v *Viper) getKeyValueConfig() error {
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 @@ func (v *Viper) watchKeyValueConfig() error {
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
}