summaryrefslogtreecommitdiff
path: root/vendor/github.com/hashicorp/terraform/config/interpolate_walk.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/config/interpolate_walk.go')
-rw-r--r--vendor/github.com/hashicorp/terraform/config/interpolate_walk.go283
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))
+}