github.com/erriapo/terraform@v0.6.12-0.20160203182612-0340ea72354f/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) (string, 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  	return nil
    58  }
    59  
    60  func (w *interpolationWalker) Exit(loc reflectwalk.Location) error {
    61  	w.loc = reflectwalk.None
    62  
    63  	switch loc {
    64  	case reflectwalk.Map:
    65  		w.cs = w.cs[:len(w.cs)-1]
    66  	case reflectwalk.MapValue:
    67  		w.key = w.key[:len(w.key)-1]
    68  		w.csKey = w.csKey[:len(w.csKey)-1]
    69  	case reflectwalk.Slice:
    70  		// Split any values that need to be split
    71  		w.splitSlice()
    72  		w.cs = w.cs[:len(w.cs)-1]
    73  	case reflectwalk.SliceElem:
    74  		w.csKey = w.csKey[:len(w.csKey)-1]
    75  	}
    76  
    77  	return nil
    78  }
    79  
    80  func (w *interpolationWalker) Map(m reflect.Value) error {
    81  	w.cs = append(w.cs, m)
    82  	return nil
    83  }
    84  
    85  func (w *interpolationWalker) MapElem(m, k, v reflect.Value) error {
    86  	w.csData = k
    87  	w.csKey = append(w.csKey, k)
    88  	w.key = append(w.key, k.String())
    89  	w.lastValue = v
    90  	return nil
    91  }
    92  
    93  func (w *interpolationWalker) Slice(s reflect.Value) error {
    94  	w.cs = append(w.cs, s)
    95  	return nil
    96  }
    97  
    98  func (w *interpolationWalker) SliceElem(i int, elem reflect.Value) error {
    99  	w.csKey = append(w.csKey, reflect.ValueOf(i))
   100  	w.sliceIndex = i
   101  	return nil
   102  }
   103  
   104  func (w *interpolationWalker) Primitive(v reflect.Value) error {
   105  	setV := v
   106  
   107  	// We only care about strings
   108  	if v.Kind() == reflect.Interface {
   109  		setV = v
   110  		v = v.Elem()
   111  	}
   112  	if v.Kind() != reflect.String {
   113  		return nil
   114  	}
   115  
   116  	astRoot, err := hil.Parse(v.String())
   117  	if err != nil {
   118  		return err
   119  	}
   120  
   121  	// If the AST we got is just a literal string value with the same
   122  	// value then we ignore it. We have to check if its the same value
   123  	// because it is possible to input a string, get out a string, and
   124  	// have it be different. For example: "foo-$${bar}" turns into
   125  	// "foo-${bar}"
   126  	if n, ok := astRoot.(*ast.LiteralNode); ok {
   127  		if s, ok := n.Value.(string); ok && s == v.String() {
   128  			return nil
   129  		}
   130  	}
   131  
   132  	if w.ContextF != nil {
   133  		w.ContextF(w.loc, astRoot)
   134  	}
   135  
   136  	if w.F == nil {
   137  		return nil
   138  	}
   139  
   140  	replaceVal, err := w.F(astRoot)
   141  	if err != nil {
   142  		return fmt.Errorf(
   143  			"%s in:\n\n%s",
   144  			err, v.String())
   145  	}
   146  
   147  	if w.Replace {
   148  		// We need to determine if we need to remove this element
   149  		// if the result contains any "UnknownVariableValue" which is
   150  		// set if it is computed. This behavior is different if we're
   151  		// splitting (in a SliceElem) or not.
   152  		remove := false
   153  		if w.loc == reflectwalk.SliceElem && IsStringList(replaceVal) {
   154  			parts := StringList(replaceVal).Slice()
   155  			for _, p := range parts {
   156  				if p == UnknownVariableValue {
   157  					remove = true
   158  					break
   159  				}
   160  			}
   161  		} else if replaceVal == UnknownVariableValue {
   162  			remove = true
   163  		}
   164  		if remove {
   165  			w.removeCurrent()
   166  			return nil
   167  		}
   168  
   169  		resultVal := reflect.ValueOf(replaceVal)
   170  		switch w.loc {
   171  		case reflectwalk.MapKey:
   172  			m := w.cs[len(w.cs)-1]
   173  
   174  			// Delete the old value
   175  			var zero reflect.Value
   176  			m.SetMapIndex(w.csData.(reflect.Value), zero)
   177  
   178  			// Set the new key with the existing value
   179  			m.SetMapIndex(resultVal, w.lastValue)
   180  
   181  			// Set the key to be the new key
   182  			w.csData = resultVal
   183  		case reflectwalk.MapValue:
   184  			// If we're in a map, then the only way to set a map value is
   185  			// to set it directly.
   186  			m := w.cs[len(w.cs)-1]
   187  			mk := w.csData.(reflect.Value)
   188  			m.SetMapIndex(mk, resultVal)
   189  		default:
   190  			// Otherwise, we should be addressable
   191  			setV.Set(resultVal)
   192  		}
   193  	}
   194  
   195  	return nil
   196  }
   197  
   198  func (w *interpolationWalker) removeCurrent() {
   199  	// Append the key to the unknown keys
   200  	w.unknownKeys = append(w.unknownKeys, strings.Join(w.key, "."))
   201  
   202  	for i := 1; i <= len(w.cs); i++ {
   203  		c := w.cs[len(w.cs)-i]
   204  		switch c.Kind() {
   205  		case reflect.Map:
   206  			// Zero value so that we delete the map key
   207  			var val reflect.Value
   208  
   209  			// Get the key and delete it
   210  			k := w.csData.(reflect.Value)
   211  			c.SetMapIndex(k, val)
   212  			return
   213  		}
   214  	}
   215  
   216  	panic("No container found for removeCurrent")
   217  }
   218  
   219  func (w *interpolationWalker) replaceCurrent(v reflect.Value) {
   220  	c := w.cs[len(w.cs)-2]
   221  	switch c.Kind() {
   222  	case reflect.Map:
   223  		// Get the key and delete it
   224  		k := w.csKey[len(w.csKey)-1]
   225  		c.SetMapIndex(k, v)
   226  	}
   227  }
   228  
   229  func (w *interpolationWalker) splitSlice() {
   230  	// Get the []interface{} slice so we can do some operations on
   231  	// it without dealing with reflection. We'll document each step
   232  	// here to be clear.
   233  	var s []interface{}
   234  	raw := w.cs[len(w.cs)-1]
   235  	switch v := raw.Interface().(type) {
   236  	case []interface{}:
   237  		s = v
   238  	case []map[string]interface{}:
   239  		return
   240  	default:
   241  		panic("Unknown kind: " + raw.Kind().String())
   242  	}
   243  
   244  	// Check if we have any elements that we need to split. If not, then
   245  	// just return since we're done.
   246  	split := false
   247  	for _, v := range s {
   248  		sv, ok := v.(string)
   249  		if !ok {
   250  			continue
   251  		}
   252  		if IsStringList(sv) {
   253  			split = true
   254  			break
   255  		}
   256  	}
   257  	if !split {
   258  		return
   259  	}
   260  
   261  	// Make a new result slice that is twice the capacity to fit our growth.
   262  	result := make([]interface{}, 0, len(s)*2)
   263  
   264  	// Go over each element of the original slice and start building up
   265  	// the resulting slice by splitting where we have to.
   266  	for _, v := range s {
   267  		sv, ok := v.(string)
   268  		if !ok {
   269  			// Not a string, so just set it
   270  			result = append(result, v)
   271  			continue
   272  		}
   273  
   274  		if IsStringList(sv) {
   275  			for _, p := range StringList(sv).Slice() {
   276  				result = append(result, p)
   277  			}
   278  			continue
   279  		}
   280  
   281  		// Not a string list, so just set it
   282  		result = append(result, sv)
   283  	}
   284  
   285  	// Our slice is now done, we have to replace the slice now
   286  	// with this new one that we have.
   287  	w.replaceCurrent(reflect.ValueOf(result))
   288  }