github.com/adamar/terraform@v0.2.2-0.20141016210445-2e703afdad0e/config/interpolate_walk.go (about)

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