diff --git a/config/raw_config.go b/config/raw_config.go new file mode 100644 index 000000000..13d72f503 --- /dev/null +++ b/config/raw_config.go @@ -0,0 +1,46 @@ +package config + +// UnknownVariableValue is a sentinel value that can be used +// to denote that the value of a variable is unknown at this time. +// RawConfig uses this information to build up data about +// unknown keys. +const UnknownVariableValue = "74D93920-ED26-11E3-AC10-0800200C9A66" + +// RawConfig is a structure that holds a piece of configuration +// where te overall structure is unknown since it will be used +// to configure a plugin or some other similar external component. +// +// RawConfigs can be interpolated with variables that come from +// other resources, user variables, etc. +// +// RawConfig supports a query-like interface to request +// information from deep within the structure. +type RawConfig struct { + Raw map[string]interface{} + Variables map[string]InterpolatedVariable +} + +// Interpolate uses the given mapping of variable values and uses +// those as the values to replace any variables in this raw +// configuration. +// +// Any prior calls to Interpolate are replaced with this one. +// +// If a variable key is missing, this will panic. +func (r *RawConfig) Interpolate(map[string]string) { +} + +// Config returns the entire configuration with the variables +// interpolated from any call to Interpolate. +// +// If any interpolated variables are unknown (value set to +// UnknownVariableValue), the first non-container (map, slice, etc.) element +// will be removed from the config. The keys of unknown variables +// can be found using the UnknownKeys function. +// +// By pruning out unknown keys from the configuration, the raw +// structure will always successfully decode into its ultimate +// structure using something like mapstructure. +func (r *RawConfig) Config() map[string]interface{} { + return nil +} diff --git a/config/raw_config_test.go b/config/raw_config_test.go new file mode 100644 index 000000000..b663f62c4 --- /dev/null +++ b/config/raw_config_test.go @@ -0,0 +1,3 @@ +package config + + diff --git a/config/variable.go b/config/variable.go index b668bcfec..4fac2c122 100644 --- a/config/variable.go +++ b/config/variable.go @@ -101,8 +101,10 @@ func (w *variableDetectWalker) Primitive(v reflect.Value) error { type variableReplaceWalker struct { Values map[string]string - loc reflectwalk.Location - m, mk reflect.Value + loc reflectwalk.Location + m, mk reflect.Value + cs []reflect.Value + csData interface{} } func (w *variableReplaceWalker) Enter(loc reflectwalk.Location) error { @@ -112,16 +114,24 @@ func (w *variableReplaceWalker) Enter(loc reflectwalk.Location) error { func (w *variableReplaceWalker) Exit(loc reflectwalk.Location) error { w.loc = reflectwalk.None + + switch loc { + case reflectwalk.Map: + w.cs = w.cs[:len(w.cs)-1] + } + return nil } -func (w *variableReplaceWalker) Map(reflect.Value) error { +func (w *variableReplaceWalker) Map(m reflect.Value) error { + w.cs = append(w.cs, m) return nil } func (w *variableReplaceWalker) MapElem(m, k, v reflect.Value) error { w.m = m w.mk = k + w.csData = k return nil } @@ -157,6 +167,13 @@ func (w *variableReplaceWalker) Primitive(v reflect.Value) error { panic("no value for variable key: " + key) } + // If this is an unknown variable, then we remove it from + // the configuration. + if value == UnknownVariableValue { + w.removeCurrent() + return nil + } + // Replace result = strings.Replace(result, match[0], value, -1) } @@ -173,3 +190,16 @@ func (w *variableReplaceWalker) Primitive(v reflect.Value) error { return nil } + +func (w *variableReplaceWalker) removeCurrent() { + c := w.cs[len(w.cs)-1] + switch c.Kind() { + case reflect.Map: + // Zero value so that we delete the map key + var val reflect.Value + + // Get the key and delete it + k := w.csData.(reflect.Value) + c.SetMapIndex(k, val) + } +} diff --git a/config/variable_test.go b/config/variable_test.go index e08796d5e..1579a8b99 100644 --- a/config/variable_test.go +++ b/config/variable_test.go @@ -106,7 +106,8 @@ func TestVariableDetectWalker_empty(t *testing.T) { func TestVariableReplaceWalker(t *testing.T) { w := &variableReplaceWalker{ Values: map[string]string{ - "var.bar": "bar", + "var.bar": "bar", + "var.unknown": UnknownVariableValue, }, } @@ -138,6 +139,33 @@ func TestVariableReplaceWalker(t *testing.T) { }, }, }, + { + map[string]interface{}{ + "foo": map[string]interface{}{ + "foo": []string{"${var.bar}"}, + }, + }, + map[string]interface{}{ + "foo": map[string]interface{}{ + "foo": []string{"bar"}, + }, + }, + }, + { + map[string]interface{}{ + "foo": "bar", + "bar": "hello${var.unknown}world", + }, + map[string]interface{}{ + "foo": "bar", + }, + }, + { + map[string]interface{}{ + "foo": []string{"foo", "${var.unknown}", "bar"}, + }, + map[string]interface{}{}, + }, } for i, tc := range cases {