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