work in progress

This commit is contained in:
Brian Ketelsen 2014-10-24 15:38:01 -04:00
parent 83fd92627c
commit 3d8182460c

140
viper.go
View file

@ -5,13 +5,15 @@
// Viper is a application configuration system. // Viper is a application configuration system.
// It believes that applications can be configured a variety of ways // 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: // Each item takes precedence over the item below it:
// flag // flag
// env // env
// config // config
// key/value store
// default // default
package viper package viper
@ -25,6 +27,7 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"reflect"
"runtime" "runtime"
"strings" "strings"
"time" "time"
@ -38,14 +41,29 @@ import (
"gopkg.in/yaml.v1" "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 // A set of paths to look for the config file in
var configPaths []string 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 // Name of file to look for inside the path
var configName string = "config" var configName string = "config"
// extensions Supported // extensions Supported
var SupportedExts []string = []string{"json", "toml", "yaml", "yml"} var SupportedExts []string = []string{"json", "toml", "yaml", "yml"}
var SupportedRemoteProviders []string = []string{"etcd", "consul"}
var configFile string var configFile string
var configType string var configType string
@ -53,6 +71,7 @@ var config map[string]interface{} = make(map[string]interface{})
var override map[string]interface{} = make(map[string]interface{}) var override map[string]interface{} = make(map[string]interface{})
var env map[string]string = make(map[string]string) var env map[string]string = make(map[string]string)
var defaults map[string]interface{} = make(map[string]interface{}) 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 pflags map[string]*pflag.Flag = make(map[string]*pflag.Flag)
var aliases map[string]string = make(map[string]string) var aliases map[string]string = make(map[string]string)
@ -81,6 +100,74 @@ func AddConfigPath(in string) {
} }
} }
// 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 { func GetString(key string) string {
return cast.ToString(Get(key)) return cast.ToString(Get(key))
} }
@ -132,6 +219,10 @@ func Marshal(rawVal interface{}) error {
if err != nil { if err != nil {
return err return err
} }
err = mapstructure.Decode(kvstore, rawVal)
if err != nil {
return err
}
insensativiseMaps() insensativiseMaps()
@ -221,6 +312,12 @@ func find(key string) interface{} {
return val return val
} }
val, exists = kvstore[key]
if exists {
jww.TRACE.Println(key, "found in key/value store:", val)
return val
}
val, exists = defaults[key] val, exists = defaults[key]
if exists { if exists {
jww.TRACE.Println(key, "found in defaults:", val) jww.TRACE.Println(key, "found in defaults:", val)
@ -289,6 +386,10 @@ func registerAlias(alias string, key string) {
delete(config, alias) delete(config, alias)
config[key] = val config[key] = val
} }
if val, ok := kvstore[alias]; ok {
delete(kvstore, alias)
kvstore[key] = val
}
if val, ok := defaults[alias]; ok { if val, ok := defaults[alias]; ok {
delete(defaults, alias) delete(defaults, alias)
defaults[key] = val defaults[key] = val
@ -331,7 +432,8 @@ func SetDefault(key string, value interface{}) {
} }
// The user provided value (via flag) // 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{}) { func Set(key string, value interface{}) {
// If alias passed in, then set the proper override // If alias passed in, then set the proper override
key = realKey(strings.ToLower(key)) key = realKey(strings.ToLower(key))
@ -345,7 +447,7 @@ func (str UnsupportedConfigError) Error() string {
} }
// Viper will discover and load the configuration file from disk // 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 { func ReadInConfig() error {
jww.INFO.Println("Attempting to read in config file") jww.INFO.Println("Attempting to read in config file")
if !stringInSlice(getConfigType(), SupportedExts) { if !stringInSlice(getConfigType(), SupportedExts) {
@ -357,6 +459,8 @@ func ReadInConfig() error {
return err return err
} }
getKeyValueConfig()
MarshallReader(bytes.NewReader(file)) MarshallReader(bytes.NewReader(file))
return nil return nil
} }
@ -389,6 +493,29 @@ func insensativiseMaps() {
insensativiseMap(config) insensativiseMap(config)
insensativiseMap(defaults) insensativiseMap(defaults)
insensativiseMap(override) 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{}) { func insensativiseMap(m map[string]interface{}) {
@ -412,6 +539,10 @@ func AllKeys() []string {
m[key] = struct{}{} m[key] = struct{}{}
} }
for key, _ := range kvstore {
m[key] = struct{}{}
}
for key, _ := range override { for key, _ := range override {
m[key] = struct{}{} m[key] = struct{}{}
} }
@ -594,6 +725,8 @@ func absPathify(inPath string) string {
func Debug() { func Debug() {
fmt.Println("Config:") fmt.Println("Config:")
pretty.Println(config) pretty.Println(config)
fmt.Println("Key/Value Store:")
pretty.Println(kvstore)
fmt.Println("Env:") fmt.Println("Env:")
pretty.Println(env) pretty.Println(env)
fmt.Println("Defaults:") fmt.Println("Defaults:")
@ -613,6 +746,7 @@ func Reset() {
configFile = "" configFile = ""
configType = "" configType = ""
kvstore = make(map[string]interface{})
config = make(map[string]interface{}) config = make(map[string]interface{})
override = make(map[string]interface{}) override = make(map[string]interface{})
env = make(map[string]string) env = make(map[string]string)