From 913c1b20c903870fe6c6a3d986df4cf8305527e4 Mon Sep 17 00:00:00 2001 From: skizot722 Date: Fri, 22 Apr 2016 20:22:52 -0500 Subject: [PATCH] Add support for configuration subsets --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ confer.go | 37 +++++++++++++++++++++++++++++++++++++ confer_test.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+) diff --git a/README.md b/README.md index 6c1e98cffb..570bcaa3d4 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,7 @@ GetStringMapString(key string) : map[string]string GetStringSlice(key string) : []string GetTime(key string) : time.Time IsSet(key string) : bool +NewConfigSubset(prefix string) : *confer.Config ``` ### Deep Configuration Data @@ -120,6 +121,47 @@ Because periods aren't valid environment variable characters, when using automat LOGGER_STDOUT=/var/log/myapp go run server.go ``` +### Configuration Subsets +An alternative to the `GetStringMap` / *Materialized paths* is a configuration subset. This allows you to create a new *confer.Config object which is a subset of the full configuration, specified by a key `prefix`. This makes passing around a subset of the configuration easy, allowing you to access the values using the getter methods listed above as you would normally. The setter methods are also supported on a configuration subset, as well as nested configuration subsets. + +##### Example Config +```yml +--- +my-app: + logging: + enabled: true + level: debug + server: + workers: + - 127.0.0.1 + - 127.0.0.2 + - 127.0.0.3 + database: + bird: + host: feathers + user: postgres + password: superl33+ + wind: + host: sea-spray + user: postgres + password: easyPZ +``` + +##### Config Subset + +```go +windDbConfig := config.NewConfigSubset("my-app.database.wind") +host := windDbConfig.GetString("host") +``` + +##### Nested Config Subset + +```go +dbConfig := config.NewConfigSubset("my-app.database") +windDbConfig := dbConfig.NewConfigSubset("wind") +host := windDbConfig.GetString("host") +``` + ### Environment Bindings diff --git a/confer.go b/confer.go index 92a8ea6115..42966baab3 100644 --- a/confer.go +++ b/confer.go @@ -45,6 +45,9 @@ type Config struct { // The root path for configuration files. rootPath string + + // Key prefix for config subsets. The prefixes defines the root of the subset. + keyPrefix string } func NewConfig() *Config { @@ -53,6 +56,7 @@ func NewConfig() *Config { manager.attributes = NewConfigSource() manager.env = NewEnvSource() manager.rootPath = "" + manager.keyPrefix = "" return manager } @@ -152,6 +156,8 @@ func (manager *Config) BindEnv(input ...string) (err error) { // Get returns an interface.. // Must be typecast or used by something that will typecast func (manager *Config) Get(key string) interface{} { + key = manager.GetFullKey(key) + jww.TRACE.Println("Looking for", key) v := manager.Find(key) @@ -178,6 +184,19 @@ func (manager *Config) Get(key string) interface{} { return v } +// NewConfigSubset returns a new config based on +func (manager *Config) NewConfigSubset(prefix string) *Config { + configSubset := &Config{} + *configSubset = *manager + + // Use GetFullKey here, as the config may already be a subset. This allows + // support of nested config subsets. + prefix = manager.GetFullKey(prefix) + configSubset.keyPrefix = prefix + + return configSubset +} + // Returns true if the config key exists and is non-nil. func (manager *Config) IsSet(key string) bool { t := manager.Get(key) @@ -194,6 +213,7 @@ func (manager *Config) AutomaticEnv() { // Returns true if the key provided exists in our configuration. func (manager *Config) InConfig(key string) bool { + key = manager.GetFullKey(key) _, exists := manager.attributes.Get(key) return exists } @@ -201,6 +221,7 @@ func (manager *Config) InConfig(key string) bool { // Set the default value for this key. // Default only used when no value is provided by the user via flag, config or ENV. func (manager *Config) SetDefault(key string, value interface{}) { + key = manager.GetFullKey(key) if !manager.IsSet(key) { manager.attributes.Set(key, value) } @@ -209,6 +230,7 @@ func (manager *Config) SetDefault(key string, value interface{}) { // Explicitly sets a value. Will not override command line arguments or // environment variables, as those sources have higher precedence. func (manager *Config) Set(key string, value interface{}) { + key = manager.GetFullKey(key) manager.attributes.Set(key, value) } @@ -288,6 +310,10 @@ func (manager *Config) AllKeys() []string { leaves := map[string]struct{}{} for _, key := range keys { + if manager.keyPrefix != "" && !strings.HasPrefix(key, manager.keyPrefix) { + continue + } + // Filter out leaves. This is really ineffecient. val := manager.Get(key) if val == nil { @@ -323,3 +349,14 @@ func (manager *Config) Debug() { fmt.Println("Config file attributes:") pretty.Println(manager.attributes) } + +// GetFullKey accounts for a key prefix (config subset) and returns the full key. +func (manager *Config) GetFullKey(key string) string { + if manager.keyPrefix != "" { + jww.TRACE.Println(key, "Using key prefix in operation: ", manager.keyPrefix) + + key = manager.keyPrefix + "." + key + } + + return key +} diff --git a/confer_test.go b/confer_test.go index 5083072818..58d00ba3e3 100644 --- a/confer_test.go +++ b/confer_test.go @@ -366,6 +366,55 @@ func TestSpec(t *testing.T) { }) }) + Convey("Config Subset", func() { + config.ReadPaths("test/fixtures/application.yaml") + keyPrefix := "app.database" + configSubset := config.NewConfigSubset(keyPrefix) + + Convey("Returning a config", func() { + So(configSubset, ShouldHaveSameTypeAs, NewConfig()) + }) + + Convey("Subset gets", func() { + So(configSubset.GetString("host"), ShouldEqual, "localhost") + So(configSubset.GetString("user"), ShouldEqual, "postgres") + So(configSubset.InConfig("password"), ShouldEqual, true) + }) + + Convey("Subset sets", func() { + configSubset.SetDefault("port", 5432) + configSubset.Set("ssl.mode", "disable") + configSubset.Set("max_open_conns", 30) + + So(configSubset.Get("ssl.mode"), ShouldEqual, "disable") + So(configSubset.Get("max_open_conns"), ShouldEqual, 30) + So(configSubset.IsSet("port"), ShouldEqual, true) + So(configSubset.Get("port"), ShouldEqual, 5432) + + // Keys for config subset should be (8 in total): [ + // app.database app.database.host app.database.user app.database.password + // app.database.port app.database.ssl app.database.ssl.mode + // app.database.max_open_conns + // ] + So(len(configSubset.AllKeys()), ShouldEqual, 8) + }) + + Convey("Nested Subsets", func() { + configSubset.Set("ssl.mode", "verify-full") + configSubsetNested := configSubset.NewConfigSubset("ssl") + configSubsetNested.SetDefault("connect_timeout", 0) + + So(configSubsetNested.IsSet("mode"), ShouldEqual, true) + So(configSubsetNested.GetString("mode"), ShouldEqual, "verify-full") + So(configSubsetNested.InConfig("connect_timeout"), ShouldEqual, true) + + // Keys for nested config subset should be (3 in total): [ + // app.database.ssl app.database.ssl.mode app.database.ssl.connect_timeout + // ] + So(len(configSubsetNested.AllKeys()), ShouldEqual, 3) + }) + }) + Convey("Helpers", func() { Convey("Returning an integer", func() { config.Set("port", func() interface{} {