From f6caa7b65299c52b9c148955759795e109ce8f3b Mon Sep 17 00:00:00 2001 From: Jason Yellick Date: Wed, 28 Sep 2016 13:47:44 -0400 Subject: [PATCH] Add string slice config support When overriding a configuration option via the env which is supposed to be a collection collection (slice) of options, the override is instead interpretted as a string. This patch modifies the config decode hook to handle this special case and appropriately decode the env override to a []string instead of a plain string. There is no JIRA issue associated with this, but is being done to support the Kafka epic effort, visible here: https://jira.hyperledger.org/browse/FAB-32 Reclaim commit after a little Gerrit accident Change-Id: Iae800b5237549232b90c7d7cf91590a3d8caf178 Signed-off-by: Jason Yellick --- orderer/config/config_test.go | 42 +++++++++++++++++++++++++++++++++++ orderer/config/config_util.go | 37 +++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/orderer/config/config_test.go b/orderer/config/config_test.go index 5750b230eaa..eb9d2546e43 100644 --- a/orderer/config/config_test.go +++ b/orderer/config/config_test.go @@ -17,8 +17,11 @@ limitations under the License. package config import ( + "bytes" "fmt" "os" + "reflect" + "strings" "testing" "github.com/spf13/viper" @@ -50,6 +53,45 @@ func TestBadConfig(t *testing.T) { } } +type testSlice struct { + Inner struct { + Slice []string + } +} + +func TestEnvSlice(t *testing.T) { + envVar := "ORDERER_INNER_SLICE" + envVal := "[a, b, c]" + os.Setenv(envVar, envVal) + defer os.Unsetenv(envVar) + config := viper.New() + config.SetEnvPrefix(Prefix) + config.AutomaticEnv() + replacer := strings.NewReplacer(".", "_") + config.SetEnvKeyReplacer(replacer) + config.SetConfigType("yaml") + + data := "---\nInner:\n Slice: [d,e,f]" + + err := config.ReadConfig(bytes.NewReader([]byte(data))) + + if err != nil { + t.Fatalf("Error reading %s plugin config: %s", Prefix, err) + } + + var uconf testSlice + + err = ExactWithDateUnmarshal(config, &uconf) + if err != nil { + t.Fatalf("Failed to unmarshal with: %s", err) + } + + expected := []string{"a", "b", "c"} + if !reflect.DeepEqual(uconf.Inner.Slice, expected) { + t.Fatalf("Did not get back the right slice, expeced: %v got %v", expected, uconf.Inner.Slice) + } +} + // TestEnvInnerVar verifies that with the Unmarshal function that // the environmental overrides still work on internal vars. This was // a bug in the original viper implementation that is worked around in diff --git a/orderer/config/config_util.go b/orderer/config/config_util.go index 94cd3ccd75b..aed5e0bac07 100644 --- a/orderer/config/config_util.go +++ b/orderer/config/config_util.go @@ -17,6 +17,10 @@ limitations under the License. package config import ( + "reflect" + "strings" + "time" + "github.com/mitchellh/mapstructure" "github.com/spf13/viper" ) @@ -45,6 +49,37 @@ func getKeysRecursively(base string, v *viper.Viper, nodeKeys map[string]interfa return result } +// customDecodeHook adds the additional functions of parsing durations from strings +// as well as parsing strings of the format "[thing1, thing2, thing3]" into string slices +// Note that whitespace around slice elements is removed +func customDecodeHook() mapstructure.DecodeHookFunc { + durationHook := mapstructure.StringToTimeDurationHookFunc() + return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + dur, err := mapstructure.DecodeHookExec(durationHook, f, t, data) + if err == nil { + if _, ok := dur.(time.Duration); ok { + return dur, nil + } + } + + if f.Kind() != reflect.String { + return data, nil + } + + raw := data.(string) + l := len(raw) + if raw[0] == '[' && raw[l-1] == ']' { + slice := strings.Split(raw[1:l-1], ",") + for i, v := range slice { + slice[i] = strings.TrimSpace(v) + } + return slice, nil + } + + return data, nil + } +} + // ExactWithDateUnmarshal is intended to unmarshal a config file into a structure // producing error when extraneous variables are introduced and supporting // the time.Duration type @@ -58,7 +93,7 @@ func ExactWithDateUnmarshal(v *viper.Viper, output interface{}) error { Metadata: nil, Result: output, WeaklyTypedInput: true, - DecodeHook: mapstructure.StringToTimeDurationHookFunc(), + DecodeHook: customDecodeHook(), } decoder, err := mapstructure.NewDecoder(config)