From 22908d67ba770ec75a4146a55fbe6e66eeb570ac Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 9 Oct 2014 15:55:22 -0700 Subject: [PATCH] config: first pass at replacing lists within a slice --- config/interpolate_walk.go | 113 +++++++++++++++++++++++++++++++- config/interpolate_walk_test.go | 23 ++++++- 2 files changed, 132 insertions(+), 4 deletions(-) diff --git a/config/interpolate_walk.go b/config/interpolate_walk.go index b91c48c37..6ff995bf0 100644 --- a/config/interpolate_walk.go +++ b/config/interpolate_walk.go @@ -9,6 +9,11 @@ import ( "github.com/mitchellh/reflectwalk" ) +// InterpSplitDelim is the delimeter that is looked for to split when +// it is returned. This is a comma right now but should eventually become +// a value that a user is very unlikely to use (such as UUID). +const InterpSplitDelim = `,` + // interpRegexp is a regexp that matches interpolations such as ${foo.bar} var interpRegexp *regexp.Regexp = regexp.MustCompile( `(?i)(\$+)\{([\s*-.,\\/\(\)a-z0-9_"]+)\}`) @@ -24,7 +29,9 @@ type interpolationWalker struct { lastValue reflect.Value loc reflectwalk.Location cs []reflect.Value + csKey []reflect.Value csData interface{} + sliceIndex int unknownKeys []string } @@ -49,6 +56,13 @@ func (w *interpolationWalker) Exit(loc reflectwalk.Location) error { w.cs = w.cs[:len(w.cs)-1] case reflectwalk.MapValue: w.key = w.key[:len(w.key)-1] + w.csKey = w.csKey[:len(w.csKey)-1] + case reflectwalk.Slice: + // Split any values that need to be split + w.splitSlice() + w.cs = w.cs[:len(w.cs)-1] + case reflectwalk.SliceElem: + w.csKey = w.csKey[:len(w.csKey)-1] } return nil @@ -61,11 +75,23 @@ func (w *interpolationWalker) Map(m reflect.Value) error { func (w *interpolationWalker) MapElem(m, k, v reflect.Value) error { w.csData = k + w.csKey = append(w.csKey, k) w.key = append(w.key, k.String()) w.lastValue = v return nil } +func (w *interpolationWalker) Slice(s reflect.Value) error { + w.cs = append(w.cs, s) + return nil +} + +func (w *interpolationWalker) SliceElem(i int, elem reflect.Value) error { + w.csKey = append(w.csKey, reflect.ValueOf(i)) + w.sliceIndex = i + return nil +} + func (w *interpolationWalker) Primitive(v reflect.Value) error { setV := v @@ -112,13 +138,28 @@ func (w *interpolationWalker) Primitive(v reflect.Value) error { } if w.Replace { - // If this is an unknown variable, then we remove it from - // the configuration. - if replaceVal == UnknownVariableValue { + // We need to determine if we need to remove this element + // if the result contains any "UnknownVariableValue" which is + // set if it is computed. This behavior is different if we're + // splitting (in a SliceElem) or not. + remove := false + if w.loc == reflectwalk.SliceElem { + parts := strings.Split(replaceVal, InterpSplitDelim) + for _, p := range parts { + if p == UnknownVariableValue { + remove = true + break + } + } + } else if replaceVal == UnknownVariableValue { + remove = true + } + if remove { w.removeCurrent() return nil } + // Replace in our interpolation and continue on. result = strings.Replace(result, match[0], replaceVal, -1) } } @@ -168,3 +209,69 @@ func (w *interpolationWalker) removeCurrent() { // Append the key to the unknown keys w.unknownKeys = append(w.unknownKeys, strings.Join(w.key, ".")) } + +func (w *interpolationWalker) replace(v reflect.Value, offset int) { + c := w.cs[len(w.cs)-1+offset] + switch c.Kind() { + case reflect.Map: + // Get the key and delete it + k := w.csKey[len(w.csKey)-1] + c.SetMapIndex(k, v) + } +} + +func (w *interpolationWalker) splitSlice() { + // Get the []interface{} slice so we can do some operations on + // it without dealing with reflection. We'll document each step + // here to be clear. + var s []interface{} + raw := w.cs[len(w.cs)-1] + switch v := raw.Interface().(type) { + case []interface{}: + s = v + case []map[string]interface{}: + return + default: + panic("Unknown kind: " + raw.Kind().String()) + } + + // Check if we have any elements that we need to split. If not, then + // just return since we're done. + split := false + for _, v := range s { + sv, ok := v.(string) + if !ok { + continue + } + if idx := strings.Index(sv, InterpSplitDelim); idx >= 0 { + split = true + break + } + } + if !split { + return + } + + // Make a new result slice that is twice the capacity to fit our growth. + result := make([]interface{}, 0, len(s)*2) + + // Go over each element of the original slice and start building up + // the resulting slice by splitting where we have to. + for _, v := range s { + sv, ok := v.(string) + if !ok { + // Not a string, so just set it + result = append(result, v) + continue + } + + // Split on the delimiter + for _, p := range strings.Split(sv, InterpSplitDelim) { + result = append(result, p) + } + } + + // Our slice is now done, we have to replace the slice now + // with this new one that we have. + w.replace(reflect.ValueOf(result), -1) +} diff --git a/config/interpolate_walk_test.go b/config/interpolate_walk_test.go index 5b5c98488..be5c4ea06 100644 --- a/config/interpolate_walk_test.go +++ b/config/interpolate_walk_test.go @@ -136,6 +136,7 @@ func TestInterpolationWalker_replace(t *testing.T) { cases := []struct { Input interface{} Output interface{} + Value string }{ { Input: map[string]interface{}{ @@ -144,6 +145,7 @@ func TestInterpolationWalker_replace(t *testing.T) { Output: map[string]interface{}{ "foo": "$${var.foo}", }, + Value: "bar", }, { @@ -153,6 +155,7 @@ func TestInterpolationWalker_replace(t *testing.T) { Output: map[string]interface{}{ "foo": "hello, bar", }, + Value: "bar", }, { @@ -166,12 +169,30 @@ func TestInterpolationWalker_replace(t *testing.T) { "bar": "bar", }, }, + Value: "bar", + }, + + { + Input: map[string]interface{}{ + "foo": []interface{}{ + "${var.foo}", + "bing", + }, + }, + Output: map[string]interface{}{ + "foo": []interface{}{ + "bar", + "baz", + "bing", + }, + }, + Value: "bar,baz", }, } for i, tc := range cases { fn := func(i Interpolation) (string, error) { - return "bar", nil + return tc.Value, nil } w := &interpolationWalker{F: fn, Replace: true}