github.com/rothwerx/packer@v0.9.0/template/interpolate/render.go (about)

     1  package interpolate
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strings"
     7  	"sync"
     8  
     9  	"github.com/mitchellh/mapstructure"
    10  	"github.com/mitchellh/reflectwalk"
    11  )
    12  
    13  // RenderFilter is an option for filtering what gets rendered and
    14  // doesn't within an interface.
    15  type RenderFilter struct {
    16  	Include []string
    17  	Exclude []string
    18  
    19  	once       sync.Once
    20  	excludeSet map[string]struct{}
    21  	includeSet map[string]struct{}
    22  }
    23  
    24  // RenderMap renders all the strings in the given interface. The
    25  // interface must decode into a map[string]interface{}, but is left
    26  // as an interface{} type to ease backwards compatibility with the way
    27  // arguments are passed around in Packer.
    28  func RenderMap(v interface{}, ctx *Context, f *RenderFilter) (map[string]interface{}, error) {
    29  	// First decode it into the map
    30  	var m map[string]interface{}
    31  	if err := mapstructure.Decode(v, &m); err != nil {
    32  		return nil, err
    33  	}
    34  
    35  	// Now go through each value and render it
    36  	for k, raw := range m {
    37  		// Always validate every field
    38  		if err := ValidateInterface(raw, ctx); err != nil {
    39  			return nil, fmt.Errorf("invalid '%s': %s", k, err)
    40  		}
    41  
    42  		if !f.include(k) {
    43  			continue
    44  		}
    45  
    46  		raw, err := RenderInterface(raw, ctx)
    47  		if err != nil {
    48  			return nil, fmt.Errorf("render '%s': %s", k, err)
    49  		}
    50  
    51  		m[k] = raw
    52  	}
    53  
    54  	return m, nil
    55  }
    56  
    57  // RenderInterface renders any value and returns the resulting value.
    58  func RenderInterface(v interface{}, ctx *Context) (interface{}, error) {
    59  	f := func(v string) (string, error) {
    60  		return Render(v, ctx)
    61  	}
    62  
    63  	walker := &renderWalker{
    64  		F:       f,
    65  		Replace: true,
    66  	}
    67  	err := reflectwalk.Walk(v, walker)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	if walker.Top != nil {
    73  		v = walker.Top
    74  	}
    75  	return v, nil
    76  }
    77  
    78  // ValidateInterface renders any value and returns the resulting value.
    79  func ValidateInterface(v interface{}, ctx *Context) error {
    80  	f := func(v string) (string, error) {
    81  		return v, Validate(v, ctx)
    82  	}
    83  
    84  	walker := &renderWalker{
    85  		F:       f,
    86  		Replace: false,
    87  	}
    88  	err := reflectwalk.Walk(v, walker)
    89  	if err != nil {
    90  		return err
    91  	}
    92  
    93  	return nil
    94  }
    95  
    96  // Include checks whether a key should be included.
    97  func (f *RenderFilter) include(k string) bool {
    98  	if f == nil {
    99  		return true
   100  	}
   101  
   102  	k = strings.ToLower(k)
   103  
   104  	f.once.Do(f.init)
   105  	if len(f.includeSet) > 0 {
   106  		_, ok := f.includeSet[k]
   107  		return ok
   108  	}
   109  	if len(f.excludeSet) > 0 {
   110  		_, ok := f.excludeSet[k]
   111  		return !ok
   112  	}
   113  
   114  	return true
   115  }
   116  
   117  func (f *RenderFilter) init() {
   118  	f.includeSet = make(map[string]struct{})
   119  	for _, v := range f.Include {
   120  		f.includeSet[strings.ToLower(v)] = struct{}{}
   121  	}
   122  
   123  	f.excludeSet = make(map[string]struct{})
   124  	for _, v := range f.Exclude {
   125  		f.excludeSet[strings.ToLower(v)] = struct{}{}
   126  	}
   127  }
   128  
   129  // renderWalker implements interfaces for the reflectwalk package
   130  // (github.com/mitchellh/reflectwalk) that can be used to automatically
   131  // execute a callback for an interpolation.
   132  type renderWalker struct {
   133  	// F is the function to call for every interpolation. It can be nil.
   134  	//
   135  	// If Replace is true, then the return value of F will be used to
   136  	// replace the interpolation.
   137  	F       renderWalkerFunc
   138  	Replace bool
   139  
   140  	// ContextF is an advanced version of F that also receives the
   141  	// location of where it is in the structure. This lets you do
   142  	// context-aware validation.
   143  	ContextF renderWalkerContextFunc
   144  
   145  	// Top is the top value of the walk. This might get replaced if the
   146  	// top value needs to be modified. It is valid to read after any walk.
   147  	// If it is nil, it means the top wasn't replaced.
   148  	Top interface{}
   149  
   150  	key         []string
   151  	lastValue   reflect.Value
   152  	loc         reflectwalk.Location
   153  	cs          []reflect.Value
   154  	csKey       []reflect.Value
   155  	csData      interface{}
   156  	sliceIndex  int
   157  	unknownKeys []string
   158  }
   159  
   160  // renderWalkerFunc is the callback called by interpolationWalk.
   161  // It is called with any interpolation found. It should return a value
   162  // to replace the interpolation with, along with any errors.
   163  //
   164  // If Replace is set to false in renderWalker, then the replace
   165  // value can be anything as it will have no effect.
   166  type renderWalkerFunc func(string) (string, error)
   167  
   168  // renderWalkerContextFunc is called by interpolationWalk if
   169  // ContextF is set. This receives both the interpolation and the location
   170  // where the interpolation is.
   171  //
   172  // This callback can be used to validate the location of the interpolation
   173  // within the configuration.
   174  type renderWalkerContextFunc func(reflectwalk.Location, string)
   175  
   176  func (w *renderWalker) Enter(loc reflectwalk.Location) error {
   177  	w.loc = loc
   178  	return nil
   179  }
   180  
   181  func (w *renderWalker) Exit(loc reflectwalk.Location) error {
   182  	w.loc = reflectwalk.None
   183  
   184  	switch loc {
   185  	case reflectwalk.Map:
   186  		w.cs = w.cs[:len(w.cs)-1]
   187  	case reflectwalk.MapValue:
   188  		w.key = w.key[:len(w.key)-1]
   189  		w.csKey = w.csKey[:len(w.csKey)-1]
   190  	case reflectwalk.Slice:
   191  		// Split any values that need to be split
   192  		w.cs = w.cs[:len(w.cs)-1]
   193  	case reflectwalk.SliceElem:
   194  		w.csKey = w.csKey[:len(w.csKey)-1]
   195  	}
   196  
   197  	return nil
   198  }
   199  
   200  func (w *renderWalker) Map(m reflect.Value) error {
   201  	w.cs = append(w.cs, m)
   202  	return nil
   203  }
   204  
   205  func (w *renderWalker) MapElem(m, k, v reflect.Value) error {
   206  	w.csData = k
   207  	w.csKey = append(w.csKey, k)
   208  	w.key = append(w.key, k.String())
   209  	w.lastValue = v
   210  	return nil
   211  }
   212  
   213  func (w *renderWalker) Slice(s reflect.Value) error {
   214  	w.cs = append(w.cs, s)
   215  	return nil
   216  }
   217  
   218  func (w *renderWalker) SliceElem(i int, elem reflect.Value) error {
   219  	w.csKey = append(w.csKey, reflect.ValueOf(i))
   220  	w.sliceIndex = i
   221  	return nil
   222  }
   223  
   224  func (w *renderWalker) Primitive(v reflect.Value) error {
   225  	setV := v
   226  
   227  	// We only care about strings
   228  	if v.Kind() == reflect.Interface {
   229  		setV = v
   230  		v = v.Elem()
   231  	}
   232  	if v.Kind() != reflect.String {
   233  		return nil
   234  	}
   235  
   236  	strV := v.String()
   237  	if w.ContextF != nil {
   238  		w.ContextF(w.loc, strV)
   239  	}
   240  
   241  	if w.F == nil {
   242  		return nil
   243  	}
   244  
   245  	replaceVal, err := w.F(strV)
   246  	if err != nil {
   247  		return fmt.Errorf(
   248  			"%s in:\n\n%s",
   249  			err, v.String())
   250  	}
   251  
   252  	if w.Replace {
   253  		resultVal := reflect.ValueOf(replaceVal)
   254  		switch w.loc {
   255  		case reflectwalk.MapKey:
   256  			m := w.cs[len(w.cs)-1]
   257  
   258  			// Delete the old value
   259  			var zero reflect.Value
   260  			m.SetMapIndex(w.csData.(reflect.Value), zero)
   261  
   262  			// Set the new key with the existing value
   263  			m.SetMapIndex(resultVal, w.lastValue)
   264  
   265  			// Set the key to be the new key
   266  			w.csData = resultVal
   267  		case reflectwalk.MapValue:
   268  			// If we're in a map, then the only way to set a map value is
   269  			// to set it directly.
   270  			m := w.cs[len(w.cs)-1]
   271  			mk := w.csData.(reflect.Value)
   272  			m.SetMapIndex(mk, resultVal)
   273  		case reflectwalk.WalkLoc:
   274  			// At the root element, we can't write that, so we just save it
   275  			w.Top = resultVal.Interface()
   276  		default:
   277  			// Otherwise, we should be addressable
   278  			setV.Set(resultVal)
   279  		}
   280  	}
   281  
   282  	return nil
   283  }
   284  
   285  func (w *renderWalker) removeCurrent() {
   286  	// Append the key to the unknown keys
   287  	w.unknownKeys = append(w.unknownKeys, strings.Join(w.key, "."))
   288  
   289  	for i := 1; i <= len(w.cs); i++ {
   290  		c := w.cs[len(w.cs)-i]
   291  		switch c.Kind() {
   292  		case reflect.Map:
   293  			// Zero value so that we delete the map key
   294  			var val reflect.Value
   295  
   296  			// Get the key and delete it
   297  			k := w.csData.(reflect.Value)
   298  			c.SetMapIndex(k, val)
   299  			return
   300  		}
   301  	}
   302  
   303  	panic("No container found for removeCurrent")
   304  }
   305  
   306  func (w *renderWalker) replaceCurrent(v reflect.Value) {
   307  	c := w.cs[len(w.cs)-2]
   308  	switch c.Kind() {
   309  	case reflect.Map:
   310  		// Get the key and delete it
   311  		k := w.csKey[len(w.csKey)-1]
   312  		c.SetMapIndex(k, v)
   313  	}
   314  }