diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/config/interpolate_walk.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/config/interpolate_walk.go | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/config/interpolate_walk.go b/vendor/github.com/hashicorp/terraform/config/interpolate_walk.go new file mode 100644 index 00000000..ead3d102 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/config/interpolate_walk.go @@ -0,0 +1,283 @@ +package config + +import ( + "fmt" + "reflect" + "strings" + + "github.com/hashicorp/hil" + "github.com/hashicorp/hil/ast" + "github.com/mitchellh/reflectwalk" +) + +// interpolationWalker implements interfaces for the reflectwalk package +// (github.com/mitchellh/reflectwalk) that can be used to automatically +// execute a callback for an interpolation. +type interpolationWalker struct { + // F is the function to call for every interpolation. It can be nil. + // + // If Replace is true, then the return value of F will be used to + // replace the interpolation. + F interpolationWalkerFunc + Replace bool + + // ContextF is an advanced version of F that also receives the + // location of where it is in the structure. This lets you do + // context-aware validation. + ContextF interpolationWalkerContextFunc + + key []string + lastValue reflect.Value + loc reflectwalk.Location + cs []reflect.Value + csKey []reflect.Value + csData interface{} + sliceIndex []int + unknownKeys []string +} + +// interpolationWalkerFunc is the callback called by interpolationWalk. +// It is called with any interpolation found. It should return a value +// to replace the interpolation with, along with any errors. +// +// If Replace is set to false in interpolationWalker, then the replace +// value can be anything as it will have no effect. +type interpolationWalkerFunc func(ast.Node) (interface{}, error) + +// interpolationWalkerContextFunc is called by interpolationWalk if +// ContextF is set. This receives both the interpolation and the location +// where the interpolation is. +// +// This callback can be used to validate the location of the interpolation +// within the configuration. +type interpolationWalkerContextFunc func(reflectwalk.Location, ast.Node) + +func (w *interpolationWalker) Enter(loc reflectwalk.Location) error { + w.loc = loc + return nil +} + +func (w *interpolationWalker) Exit(loc reflectwalk.Location) error { + w.loc = reflectwalk.None + + switch loc { + case reflectwalk.Map: + 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] + w.sliceIndex = w.sliceIndex[:len(w.sliceIndex)-1] + } + + return nil +} + +func (w *interpolationWalker) Map(m reflect.Value) error { + w.cs = append(w.cs, m) + return nil +} + +func (w *interpolationWalker) MapElem(m, k, v reflect.Value) error { + w.csData = k + w.csKey = append(w.csKey, k) + + if l := len(w.sliceIndex); l > 0 { + w.key = append(w.key, fmt.Sprintf("%d.%s", w.sliceIndex[l-1], k.String())) + } else { + 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 = append(w.sliceIndex, i) + return nil +} + +func (w *interpolationWalker) Primitive(v reflect.Value) error { + setV := v + + // We only care about strings + if v.Kind() == reflect.Interface { + setV = v + v = v.Elem() + } + if v.Kind() != reflect.String { + return nil + } + + astRoot, err := hil.Parse(v.String()) + if err != nil { + return err + } + + // If the AST we got is just a literal string value with the same + // value then we ignore it. We have to check if its the same value + // because it is possible to input a string, get out a string, and + // have it be different. For example: "foo-$${bar}" turns into + // "foo-${bar}" + if n, ok := astRoot.(*ast.LiteralNode); ok { + if s, ok := n.Value.(string); ok && s == v.String() { + return nil + } + } + + if w.ContextF != nil { + w.ContextF(w.loc, astRoot) + } + + if w.F == nil { + return nil + } + + replaceVal, err := w.F(astRoot) + if err != nil { + return fmt.Errorf( + "%s in:\n\n%s", + err, v.String()) + } + + if w.Replace { + // 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 { + switch typedReplaceVal := replaceVal.(type) { + case string: + if typedReplaceVal == UnknownVariableValue { + remove = true + } + case []interface{}: + if hasUnknownValue(typedReplaceVal) { + remove = true + } + } + } else if replaceVal == UnknownVariableValue { + remove = true + } + + if remove { + w.unknownKeys = append(w.unknownKeys, strings.Join(w.key, ".")) + } + + resultVal := reflect.ValueOf(replaceVal) + switch w.loc { + case reflectwalk.MapKey: + m := w.cs[len(w.cs)-1] + + // Delete the old value + var zero reflect.Value + m.SetMapIndex(w.csData.(reflect.Value), zero) + + // Set the new key with the existing value + m.SetMapIndex(resultVal, w.lastValue) + + // Set the key to be the new key + w.csData = resultVal + case reflectwalk.MapValue: + // If we're in a map, then the only way to set a map value is + // to set it directly. + m := w.cs[len(w.cs)-1] + mk := w.csData.(reflect.Value) + m.SetMapIndex(mk, resultVal) + default: + // Otherwise, we should be addressable + setV.Set(resultVal) + } + } + + return nil +} + +func (w *interpolationWalker) replaceCurrent(v reflect.Value) { + // if we don't have at least 2 values, we're not going to find a map, but + // we could panic. + if len(w.cs) < 2 { + return + } + + c := w.cs[len(w.cs)-2] + switch c.Kind() { + case reflect.Map: + // Get the key and delete it + k := w.csKey[len(w.csKey)-1] + c.SetMapIndex(k, v) + } +} + +func hasUnknownValue(variable []interface{}) bool { + for _, value := range variable { + if strVal, ok := value.(string); ok { + if strVal == UnknownVariableValue { + return true + } + } + } + return false +} + +func (w *interpolationWalker) splitSlice() { + raw := w.cs[len(w.cs)-1] + + var s []interface{} + switch v := raw.Interface().(type) { + case []interface{}: + s = v + case []map[string]interface{}: + return + } + + split := false + for _, val := range s { + if varVal, ok := val.(ast.Variable); ok && varVal.Type == ast.TypeList { + split = true + } + if _, ok := val.([]interface{}); ok { + split = true + } + } + + if !split { + return + } + + result := make([]interface{}, 0) + for _, v := range s { + switch val := v.(type) { + case ast.Variable: + switch val.Type { + case ast.TypeList: + elements := val.Value.([]ast.Variable) + for _, element := range elements { + result = append(result, element.Value) + } + default: + result = append(result, val.Value) + } + case []interface{}: + for _, element := range val { + result = append(result, element) + } + default: + result = append(result, v) + } + } + + w.replaceCurrent(reflect.ValueOf(result)) +} |