github.com/ns1/terraform@v0.7.10-0.20161109153551-8949419bef40/config/interpolate_walk.go (about)

     1  package config
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strings"
     7  
     8  	"github.com/hashicorp/hil"
     9  	"github.com/hashicorp/hil/ast"
    10  	"github.com/mitchellh/reflectwalk"
    11  )
    12  
    13  // interpolationWalker implements interfaces for the reflectwalk package
    14  // (github.com/mitchellh/reflectwalk) that can be used to automatically
    15  // execute a callback for an interpolation.
    16  type interpolationWalker struct {
    17  	// F is the function to call for every interpolation. It can be nil.
    18  	//
    19  	// If Replace is true, then the return value of F will be used to
    20  	// replace the interpolation.
    21  	F       interpolationWalkerFunc
    22  	Replace bool
    23  
    24  	// ContextF is an advanced version of F that also receives the
    25  	// location of where it is in the structure. This lets you do
    26  	// context-aware validation.
    27  	ContextF interpolationWalkerContextFunc
    28  
    29  	key         []string
    30  	lastValue   reflect.Value
    31  	loc         reflectwalk.Location
    32  	cs          []reflect.Value
    33  	csKey       []reflect.Value
    34  	csData      interface{}
    35  	sliceIndex  int
    36  	unknownKeys []string
    37  }
    38  
    39  // interpolationWalkerFunc is the callback called by interpolationWalk.
    40  // It is called with any interpolation found. It should return a value
    41  // to replace the interpolation with, along with any errors.
    42  //
    43  // If Replace is set to false in interpolationWalker, then the replace
    44  // value can be anything as it will have no effect.
    45  type interpolationWalkerFunc func(ast.Node) (interface{}, error)
    46  
    47  // interpolationWalkerContextFunc is called by interpolationWalk if
    48  // ContextF is set. This receives both the interpolation and the location
    49  // where the interpolation is.
    50  //
    51  // This callback can be used to validate the location of the interpolation
    52  // within the configuration.
    53  type interpolationWalkerContextFunc func(reflectwalk.Location, ast.Node)
    54  
    55  func (w *interpolationWalker) Enter(loc reflectwalk.Location) error {
    56  	w.loc = loc
    57  	if loc == reflectwalk.WalkLoc {
    58  		w.sliceIndex = -1
    59  	}
    60  	return nil
    61  }
    62  
    63  func (w *interpolationWalker) Exit(loc reflectwalk.Location) error {
    64  	w.loc = reflectwalk.None
    65  
    66  	switch loc {
    67  	case reflectwalk.Map:
    68  		w.cs = w.cs[:len(w.cs)-1]
    69  	case reflectwalk.MapValue:
    70  		w.key = w.key[:len(w.key)-1]
    71  		w.csKey = w.csKey[:len(w.csKey)-1]
    72  	case reflectwalk.Slice:
    73  		// Split any values that need to be split
    74  		w.splitSlice()
    75  		w.cs = w.cs[:len(w.cs)-1]
    76  	case reflectwalk.SliceElem:
    77  		w.csKey = w.csKey[:len(w.csKey)-1]
    78  		w.sliceIndex = -1
    79  	}
    80  
    81  	return nil
    82  }
    83  
    84  func (w *interpolationWalker) Map(m reflect.Value) error {
    85  	w.cs = append(w.cs, m)
    86  	return nil
    87  }
    88  
    89  func (w *interpolationWalker) MapElem(m, k, v reflect.Value) error {
    90  	w.csData = k
    91  	w.csKey = append(w.csKey, k)
    92  
    93  	if w.sliceIndex != -1 {
    94  		w.key = append(w.key, fmt.Sprintf("%d.%s", w.sliceIndex, k.String()))
    95  	} else {
    96  		w.key = append(w.key, k.String())
    97  	}
    98  
    99  	w.lastValue = v
   100  	return nil
   101  }
   102  
   103  func (w *interpolationWalker) Slice(s reflect.Value) error {
   104  	w.cs = append(w.cs, s)
   105  	return nil
   106  }
   107  
   108  func (w *interpolationWalker) SliceElem(i int, elem reflect.Value) error {
   109  	w.csKey = append(w.csKey, reflect.ValueOf(i))
   110  	w.sliceIndex = i
   111  	return nil
   112  }
   113  
   114  func (w *interpolationWalker) Primitive(v reflect.Value) error {
   115  	setV := v
   116  
   117  	// We only care about strings
   118  	if v.Kind() == reflect.Interface {
   119  		setV = v
   120  		v = v.Elem()
   121  	}
   122  	if v.Kind() != reflect.String {
   123  		return nil
   124  	}
   125  
   126  	astRoot, err := hil.Parse(v.String())
   127  	if err != nil {
   128  		return err
   129  	}
   130  
   131  	// If the AST we got is just a literal string value with the same
   132  	// value then we ignore it. We have to check if its the same value
   133  	// because it is possible to input a string, get out a string, and
   134  	// have it be different. For example: "foo-$${bar}" turns into
   135  	// "foo-${bar}"
   136  	if n, ok := astRoot.(*ast.LiteralNode); ok {
   137  		if s, ok := n.Value.(string); ok && s == v.String() {
   138  			return nil
   139  		}
   140  	}
   141  
   142  	if w.ContextF != nil {
   143  		w.ContextF(w.loc, astRoot)
   144  	}
   145  
   146  	if w.F == nil {
   147  		return nil
   148  	}
   149  
   150  	replaceVal, err := w.F(astRoot)
   151  	if err != nil {
   152  		return fmt.Errorf(
   153  			"%s in:\n\n%s",
   154  			err, v.String())
   155  	}
   156  
   157  	if w.Replace {
   158  		// We need to determine if we need to remove this element
   159  		// if the result contains any "UnknownVariableValue" which is
   160  		// set if it is computed. This behavior is different if we're
   161  		// splitting (in a SliceElem) or not.
   162  		remove := false
   163  		if w.loc == reflectwalk.SliceElem {
   164  			switch typedReplaceVal := replaceVal.(type) {
   165  			case string:
   166  				if typedReplaceVal == UnknownVariableValue {
   167  					remove = true
   168  				}
   169  			case []interface{}:
   170  				if hasUnknownValue(typedReplaceVal) {
   171  					remove = true
   172  				}
   173  			}
   174  		} else if replaceVal == UnknownVariableValue {
   175  			remove = true
   176  		}
   177  
   178  		if remove {
   179  			w.removeCurrent()
   180  			return nil
   181  		}
   182  
   183  		resultVal := reflect.ValueOf(replaceVal)
   184  		switch w.loc {
   185  		case reflectwalk.MapKey:
   186  			m := w.cs[len(w.cs)-1]
   187  
   188  			// Delete the old value
   189  			var zero reflect.Value
   190  			m.SetMapIndex(w.csData.(reflect.Value), zero)
   191  
   192  			// Set the new key with the existing value
   193  			m.SetMapIndex(resultVal, w.lastValue)
   194  
   195  			// Set the key to be the new key
   196  			w.csData = resultVal
   197  		case reflectwalk.MapValue:
   198  			// If we're in a map, then the only way to set a map value is
   199  			// to set it directly.
   200  			m := w.cs[len(w.cs)-1]
   201  			mk := w.csData.(reflect.Value)
   202  			m.SetMapIndex(mk, resultVal)
   203  		default:
   204  			// Otherwise, we should be addressable
   205  			setV.Set(resultVal)
   206  		}
   207  	}
   208  
   209  	return nil
   210  }
   211  
   212  func (w *interpolationWalker) removeCurrent() {
   213  	// Append the key to the unknown keys
   214  	w.unknownKeys = append(w.unknownKeys, strings.Join(w.key, "."))
   215  
   216  	for i := 1; i <= len(w.cs); i++ {
   217  		c := w.cs[len(w.cs)-i]
   218  		switch c.Kind() {
   219  		case reflect.Map:
   220  			// Zero value so that we delete the map key
   221  			var val reflect.Value
   222  
   223  			// Get the key and delete it
   224  			k := w.csData.(reflect.Value)
   225  			c.SetMapIndex(k, val)
   226  			return
   227  		}
   228  	}
   229  
   230  	panic("No container found for removeCurrent")
   231  }
   232  
   233  func (w *interpolationWalker) replaceCurrent(v reflect.Value) {
   234  	c := w.cs[len(w.cs)-2]
   235  	switch c.Kind() {
   236  	case reflect.Map:
   237  		// Get the key and delete it
   238  		k := w.csKey[len(w.csKey)-1]
   239  		c.SetMapIndex(k, v)
   240  	}
   241  }
   242  
   243  func hasUnknownValue(variable []interface{}) bool {
   244  	for _, value := range variable {
   245  		if strVal, ok := value.(string); ok {
   246  			if strVal == UnknownVariableValue {
   247  				return true
   248  			}
   249  		}
   250  	}
   251  	return false
   252  }
   253  
   254  func (w *interpolationWalker) splitSlice() {
   255  	raw := w.cs[len(w.cs)-1]
   256  
   257  	var s []interface{}
   258  	switch v := raw.Interface().(type) {
   259  	case []interface{}:
   260  		s = v
   261  	case []map[string]interface{}:
   262  		return
   263  	}
   264  
   265  	split := false
   266  	for _, val := range s {
   267  		if varVal, ok := val.(ast.Variable); ok && varVal.Type == ast.TypeList {
   268  			split = true
   269  		}
   270  		if _, ok := val.([]interface{}); ok {
   271  			split = true
   272  		}
   273  	}
   274  
   275  	if !split {
   276  		return
   277  	}
   278  
   279  	result := make([]interface{}, 0)
   280  	for _, v := range s {
   281  		switch val := v.(type) {
   282  		case ast.Variable:
   283  			switch val.Type {
   284  			case ast.TypeList:
   285  				elements := val.Value.([]ast.Variable)
   286  				for _, element := range elements {
   287  					result = append(result, element.Value)
   288  				}
   289  			default:
   290  				result = append(result, val.Value)
   291  			}
   292  		case []interface{}:
   293  			for _, element := range val {
   294  				result = append(result, element)
   295  			}
   296  		default:
   297  			result = append(result, v)
   298  		}
   299  	}
   300  
   301  	w.replaceCurrent(reflect.ValueOf(result))
   302  }