github.com/bendemaree/terraform@v0.5.4-0.20150613200311-f50d97d6eee6/config/interpolate_walk.go (about)

     1  package config
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strings"
     7  
     8  	"github.com/hashicorp/terraform/config/lang"
     9  	"github.com/hashicorp/terraform/config/lang/ast"
    10  	"github.com/mitchellh/reflectwalk"
    11  )
    12  
    13  // InterpSplitDelim is the delimeter that is looked for to split when
    14  // it is returned.
    15  const InterpSplitDelim = `B780FFEC-B661-4EB8-9236-A01737AD98B6`
    16  
    17  // interpolationWalker implements interfaces for the reflectwalk package
    18  // (github.com/mitchellh/reflectwalk) that can be used to automatically
    19  // execute a callback for an interpolation.
    20  type interpolationWalker struct {
    21  	// F is the function to call for every interpolation. It can be nil.
    22  	//
    23  	// If Replace is true, then the return value of F will be used to
    24  	// replace the interpolation.
    25  	F       interpolationWalkerFunc
    26  	Replace bool
    27  
    28  	// ContextF is an advanced version of F that also receives the
    29  	// location of where it is in the structure. This lets you do
    30  	// context-aware validation.
    31  	ContextF interpolationWalkerContextFunc
    32  
    33  	key         []string
    34  	lastValue   reflect.Value
    35  	loc         reflectwalk.Location
    36  	cs          []reflect.Value
    37  	csKey       []reflect.Value
    38  	csData      interface{}
    39  	sliceIndex  int
    40  	unknownKeys []string
    41  }
    42  
    43  // interpolationWalkerFunc is the callback called by interpolationWalk.
    44  // It is called with any interpolation found. It should return a value
    45  // to replace the interpolation with, along with any errors.
    46  //
    47  // If Replace is set to false in interpolationWalker, then the replace
    48  // value can be anything as it will have no effect.
    49  type interpolationWalkerFunc func(ast.Node) (string, error)
    50  
    51  // interpolationWalkerContextFunc is called by interpolationWalk if
    52  // ContextF is set. This receives both the interpolation and the location
    53  // where the interpolation is.
    54  //
    55  // This callback can be used to validate the location of the interpolation
    56  // within the configuration.
    57  type interpolationWalkerContextFunc func(reflectwalk.Location, ast.Node)
    58  
    59  func (w *interpolationWalker) Enter(loc reflectwalk.Location) error {
    60  	w.loc = loc
    61  	return nil
    62  }
    63  
    64  func (w *interpolationWalker) Exit(loc reflectwalk.Location) error {
    65  	w.loc = reflectwalk.None
    66  
    67  	switch loc {
    68  	case reflectwalk.Map:
    69  		w.cs = w.cs[:len(w.cs)-1]
    70  	case reflectwalk.MapValue:
    71  		w.key = w.key[:len(w.key)-1]
    72  		w.csKey = w.csKey[:len(w.csKey)-1]
    73  	case reflectwalk.Slice:
    74  		// Split any values that need to be split
    75  		w.splitSlice()
    76  		w.cs = w.cs[:len(w.cs)-1]
    77  	case reflectwalk.SliceElem:
    78  		w.csKey = w.csKey[:len(w.csKey)-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  	w.key = append(w.key, k.String())
    93  	w.lastValue = v
    94  	return nil
    95  }
    96  
    97  func (w *interpolationWalker) Slice(s reflect.Value) error {
    98  	w.cs = append(w.cs, s)
    99  	return nil
   100  }
   101  
   102  func (w *interpolationWalker) SliceElem(i int, elem reflect.Value) error {
   103  	w.csKey = append(w.csKey, reflect.ValueOf(i))
   104  	w.sliceIndex = i
   105  	return nil
   106  }
   107  
   108  func (w *interpolationWalker) Primitive(v reflect.Value) error {
   109  	setV := v
   110  
   111  	// We only care about strings
   112  	if v.Kind() == reflect.Interface {
   113  		setV = v
   114  		v = v.Elem()
   115  	}
   116  	if v.Kind() != reflect.String {
   117  		return nil
   118  	}
   119  
   120  	astRoot, err := lang.Parse(v.String())
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	// If the AST we got is just a literal string value, then we ignore it
   126  	if _, ok := astRoot.(*ast.LiteralNode); ok {
   127  		return nil
   128  	}
   129  
   130  	if w.ContextF != nil {
   131  		w.ContextF(w.loc, astRoot)
   132  	}
   133  
   134  	if w.F == nil {
   135  		return nil
   136  	}
   137  
   138  	replaceVal, err := w.F(astRoot)
   139  	if err != nil {
   140  		return fmt.Errorf(
   141  			"%s in:\n\n%s",
   142  			err, v.String())
   143  	}
   144  
   145  	if w.Replace {
   146  		// We need to determine if we need to remove this element
   147  		// if the result contains any "UnknownVariableValue" which is
   148  		// set if it is computed. This behavior is different if we're
   149  		// splitting (in a SliceElem) or not.
   150  		remove := false
   151  		if w.loc == reflectwalk.SliceElem {
   152  			parts := strings.Split(replaceVal, InterpSplitDelim)
   153  			for _, p := range parts {
   154  				if p == UnknownVariableValue {
   155  					remove = true
   156  					break
   157  				}
   158  			}
   159  		} else if replaceVal == UnknownVariableValue {
   160  			remove = true
   161  		}
   162  		if remove {
   163  			w.removeCurrent()
   164  			return nil
   165  		}
   166  
   167  		resultVal := reflect.ValueOf(replaceVal)
   168  		switch w.loc {
   169  		case reflectwalk.MapKey:
   170  			m := w.cs[len(w.cs)-1]
   171  
   172  			// Delete the old value
   173  			var zero reflect.Value
   174  			m.SetMapIndex(w.csData.(reflect.Value), zero)
   175  
   176  			// Set the new key with the existing value
   177  			m.SetMapIndex(resultVal, w.lastValue)
   178  
   179  			// Set the key to be the new key
   180  			w.csData = resultVal
   181  		case reflectwalk.MapValue:
   182  			// If we're in a map, then the only way to set a map value is
   183  			// to set it directly.
   184  			m := w.cs[len(w.cs)-1]
   185  			mk := w.csData.(reflect.Value)
   186  			m.SetMapIndex(mk, resultVal)
   187  		default:
   188  			// Otherwise, we should be addressable
   189  			setV.Set(resultVal)
   190  		}
   191  	}
   192  
   193  	return nil
   194  }
   195  
   196  func (w *interpolationWalker) removeCurrent() {
   197  	// Append the key to the unknown keys
   198  	w.unknownKeys = append(w.unknownKeys, strings.Join(w.key, "."))
   199  
   200  	for i := 1; i <= len(w.cs); i++ {
   201  		c := w.cs[len(w.cs)-i]
   202  		switch c.Kind() {
   203  		case reflect.Map:
   204  			// Zero value so that we delete the map key
   205  			var val reflect.Value
   206  
   207  			// Get the key and delete it
   208  			k := w.csData.(reflect.Value)
   209  			c.SetMapIndex(k, val)
   210  			return
   211  		}
   212  	}
   213  
   214  	panic("No container found for removeCurrent")
   215  }
   216  
   217  func (w *interpolationWalker) replaceCurrent(v reflect.Value) {
   218  	c := w.cs[len(w.cs)-2]
   219  	switch c.Kind() {
   220  	case reflect.Map:
   221  		// Get the key and delete it
   222  		k := w.csKey[len(w.csKey)-1]
   223  		c.SetMapIndex(k, v)
   224  	}
   225  }
   226  
   227  func (w *interpolationWalker) splitSlice() {
   228  	// Get the []interface{} slice so we can do some operations on
   229  	// it without dealing with reflection. We'll document each step
   230  	// here to be clear.
   231  	var s []interface{}
   232  	raw := w.cs[len(w.cs)-1]
   233  	switch v := raw.Interface().(type) {
   234  	case []interface{}:
   235  		s = v
   236  	case []map[string]interface{}:
   237  		return
   238  	default:
   239  		panic("Unknown kind: " + raw.Kind().String())
   240  	}
   241  
   242  	// Check if we have any elements that we need to split. If not, then
   243  	// just return since we're done.
   244  	split := false
   245  	for _, v := range s {
   246  		sv, ok := v.(string)
   247  		if !ok {
   248  			continue
   249  		}
   250  		if idx := strings.Index(sv, InterpSplitDelim); idx >= 0 {
   251  			split = true
   252  			break
   253  		}
   254  	}
   255  	if !split {
   256  		return
   257  	}
   258  
   259  	// Make a new result slice that is twice the capacity to fit our growth.
   260  	result := make([]interface{}, 0, len(s)*2)
   261  
   262  	// Go over each element of the original slice and start building up
   263  	// the resulting slice by splitting where we have to.
   264  	for _, v := range s {
   265  		sv, ok := v.(string)
   266  		if !ok {
   267  			// Not a string, so just set it
   268  			result = append(result, v)
   269  			continue
   270  		}
   271  
   272  		// Split on the delimiter
   273  		for _, p := range strings.Split(sv, InterpSplitDelim) {
   274  			result = append(result, p)
   275  		}
   276  	}
   277  
   278  	// Our slice is now done, we have to replace the slice now
   279  	// with this new one that we have.
   280  	w.replaceCurrent(reflect.ValueOf(result))
   281  }