work in progress
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 @@
"os"
"path"
"path/filepath"
+ "reflect"
"runtime"
"strings"
"time"
@@ -38,14 +41,29 @@
"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 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 @@
}
}
+// 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 @@
if err != nil {
return err
}
+ err = mapstructure.Decode(kvstore, rawVal)
+ if err != nil {
+ return err
+ }
insensativiseMaps()
@@ -221,6 +312,12 @@
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 @@
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 @@
}
// 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 @@
}
// 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 @@
return err
}
+ getKeyValueConfig()
+
MarshallReader(bytes.NewReader(file))
return nil
}
@@ -389,6 +493,29 @@
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 @@
m[key] = struct{}{}
}
+ for key, _ := range kvstore {
+ m[key] = struct{}{}
+ }
+
for key, _ := range override {
m[key] = struct{}{}
}
@@ -594,6 +725,8 @@
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 @@
configFile = ""
configType = ""
+ kvstore = make(map[string]interface{})
config = make(map[string]interface{})
override = make(map[string]interface{})
env = make(map[string]string)