config: first pass at replacing lists within a slice

This commit is contained in:
Mitchell Hashimoto 2014-10-09 15:55:22 -07:00
parent 958f2ec094
commit 22908d67ba
2 changed files with 132 additions and 4 deletions

View File

@ -9,6 +9,11 @@ import (
"github.com/mitchellh/reflectwalk" "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} // interpRegexp is a regexp that matches interpolations such as ${foo.bar}
var interpRegexp *regexp.Regexp = regexp.MustCompile( var interpRegexp *regexp.Regexp = regexp.MustCompile(
`(?i)(\$+)\{([\s*-.,\\/\(\)a-z0-9_"]+)\}`) `(?i)(\$+)\{([\s*-.,\\/\(\)a-z0-9_"]+)\}`)
@ -24,7 +29,9 @@ type interpolationWalker struct {
lastValue reflect.Value lastValue reflect.Value
loc reflectwalk.Location loc reflectwalk.Location
cs []reflect.Value cs []reflect.Value
csKey []reflect.Value
csData interface{} csData interface{}
sliceIndex int
unknownKeys []string unknownKeys []string
} }
@ -49,6 +56,13 @@ func (w *interpolationWalker) Exit(loc reflectwalk.Location) error {
w.cs = w.cs[:len(w.cs)-1] w.cs = w.cs[:len(w.cs)-1]
case reflectwalk.MapValue: case reflectwalk.MapValue:
w.key = w.key[:len(w.key)-1] 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 return nil
@ -61,11 +75,23 @@ func (w *interpolationWalker) Map(m reflect.Value) error {
func (w *interpolationWalker) MapElem(m, k, v reflect.Value) error { func (w *interpolationWalker) MapElem(m, k, v reflect.Value) error {
w.csData = k w.csData = k
w.csKey = append(w.csKey, k)
w.key = append(w.key, k.String()) w.key = append(w.key, k.String())
w.lastValue = v w.lastValue = v
return nil 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 { func (w *interpolationWalker) Primitive(v reflect.Value) error {
setV := v setV := v
@ -112,13 +138,28 @@ func (w *interpolationWalker) Primitive(v reflect.Value) error {
} }
if w.Replace { if w.Replace {
// If this is an unknown variable, then we remove it from // We need to determine if we need to remove this element
// the configuration. // if the result contains any "UnknownVariableValue" which is
if replaceVal == UnknownVariableValue { // 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() w.removeCurrent()
return nil return nil
} }
// Replace in our interpolation and continue on.
result = strings.Replace(result, match[0], replaceVal, -1) result = strings.Replace(result, match[0], replaceVal, -1)
} }
} }
@ -168,3 +209,69 @@ func (w *interpolationWalker) removeCurrent() {
// Append the key to the unknown keys // Append the key to the unknown keys
w.unknownKeys = append(w.unknownKeys, strings.Join(w.key, ".")) 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)
}

View File

@ -136,6 +136,7 @@ func TestInterpolationWalker_replace(t *testing.T) {
cases := []struct { cases := []struct {
Input interface{} Input interface{}
Output interface{} Output interface{}
Value string
}{ }{
{ {
Input: map[string]interface{}{ Input: map[string]interface{}{
@ -144,6 +145,7 @@ func TestInterpolationWalker_replace(t *testing.T) {
Output: map[string]interface{}{ Output: map[string]interface{}{
"foo": "$${var.foo}", "foo": "$${var.foo}",
}, },
Value: "bar",
}, },
{ {
@ -153,6 +155,7 @@ func TestInterpolationWalker_replace(t *testing.T) {
Output: map[string]interface{}{ Output: map[string]interface{}{
"foo": "hello, bar", "foo": "hello, bar",
}, },
Value: "bar",
}, },
{ {
@ -166,12 +169,30 @@ func TestInterpolationWalker_replace(t *testing.T) {
"bar": "bar", "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 { for i, tc := range cases {
fn := func(i Interpolation) (string, error) { fn := func(i Interpolation) (string, error) {
return "bar", nil return tc.Value, nil
} }
w := &interpolationWalker{F: fn, Replace: true} w := &interpolationWalker{F: fn, Replace: true}