packer-cn/template/interpolate/render.go

283 lines
6.5 KiB
Go

package interpolate
import (
"fmt"
"reflect"
"strings"
"sync"
"github.com/mitchellh/mapstructure"
"github.com/mitchellh/reflectwalk"
)
// RenderFilter is an option for filtering what gets rendered and
// doesn't within an interface.
type RenderFilter struct {
Include []string
Exclude []string
once sync.Once
excludeSet map[string]struct{}
includeSet map[string]struct{}
}
// RenderMap renders all the strings in the given interface. The
// interface must decode into a map[string]interface{}, but is left
// as an interface{} type to ease backwards compatibility with the way
// arguments are passed around in Packer.
func RenderMap(v interface{}, ctx *Context, f *RenderFilter) (map[string]interface{}, error) {
// First decode it into the map
var m map[string]interface{}
if err := mapstructure.Decode(v, &m); err != nil {
return nil, err
}
// Now go through each value and render it
for k, raw := range m {
// Always validate every field
if err := ValidateInterface(raw, ctx); err != nil {
return nil, fmt.Errorf("invalid '%s': %s", k, err)
}
if !f.include(k) {
continue
}
raw, err := RenderInterface(raw, ctx)
if err != nil {
return nil, fmt.Errorf("render '%s': %s", k, err)
}
m[k] = raw
}
return m, nil
}
// RenderInterface renders any value and returns the resulting value.
func RenderInterface(v interface{}, ctx *Context) (interface{}, error) {
f := func(v string) (string, error) {
return Render(v, ctx)
}
walker := &renderWalker{
F: f,
Replace: true,
}
err := reflectwalk.Walk(v, walker)
if err != nil {
return nil, err
}
if walker.Top != nil {
v = walker.Top
}
return v, nil
}
// ValidateInterface renders any value and returns the resulting value.
func ValidateInterface(v interface{}, ctx *Context) error {
f := func(v string) (string, error) {
return v, Validate(v, ctx)
}
walker := &renderWalker{
F: f,
Replace: false,
}
err := reflectwalk.Walk(v, walker)
if err != nil {
return err
}
return nil
}
// Include checks whether a key should be included.
func (f *RenderFilter) include(k string) bool {
if f == nil {
return true
}
k = strings.ToLower(k)
f.once.Do(f.init)
if len(f.includeSet) > 0 {
_, ok := f.includeSet[k]
return ok
}
if len(f.excludeSet) > 0 {
_, ok := f.excludeSet[k]
return !ok
}
return true
}
func (f *RenderFilter) init() {
f.includeSet = make(map[string]struct{})
for _, v := range f.Include {
f.includeSet[strings.ToLower(v)] = struct{}{}
}
f.excludeSet = make(map[string]struct{})
for _, v := range f.Exclude {
f.excludeSet[strings.ToLower(v)] = struct{}{}
}
}
// renderWalker implements interfaces for the reflectwalk package
// (github.com/mitchellh/reflectwalk) that can be used to automatically
// execute a callback for an interpolation.
type renderWalker struct {
// F is the function to call for every interpolation. It can be nil.
//
// If Replace is true, then the return value of F will be used to
// replace the interpolation.
F renderWalkerFunc
Replace bool
// ContextF is an advanced version of F that also receives the
// location of where it is in the structure. This lets you do
// context-aware validation.
ContextF renderWalkerContextFunc
// Top is the top value of the walk. This might get replaced if the
// top value needs to be modified. It is valid to read after any walk.
// If it is nil, it means the top wasn't replaced.
Top interface{}
key []string
lastValue reflect.Value
loc reflectwalk.Location
cs []reflect.Value
csKey []reflect.Value
csData interface{}
sliceIndex int
}
// renderWalkerFunc is the callback called by interpolationWalk.
// It is called with any interpolation found. It should return a value
// to replace the interpolation with, along with any errors.
//
// If Replace is set to false in renderWalker, then the replace
// value can be anything as it will have no effect.
type renderWalkerFunc func(string) (string, error)
// renderWalkerContextFunc is called by interpolationWalk if
// ContextF is set. This receives both the interpolation and the location
// where the interpolation is.
//
// This callback can be used to validate the location of the interpolation
// within the configuration.
type renderWalkerContextFunc func(reflectwalk.Location, string)
func (w *renderWalker) Enter(loc reflectwalk.Location) error {
w.loc = loc
return nil
}
func (w *renderWalker) Exit(loc reflectwalk.Location) error {
w.loc = reflectwalk.None
switch loc {
case reflectwalk.Map:
w.cs = w.cs[:len(w.cs)-1]
case reflectwalk.MapValue:
w.key = w.key[:len(w.key)-1]
w.csKey = w.csKey[:len(w.csKey)-1]
case reflectwalk.Slice:
// Split any values that need to be split
w.cs = w.cs[:len(w.cs)-1]
case reflectwalk.SliceElem:
w.csKey = w.csKey[:len(w.csKey)-1]
}
return nil
}
func (w *renderWalker) Map(m reflect.Value) error {
w.cs = append(w.cs, m)
return nil
}
func (w *renderWalker) MapElem(m, k, v reflect.Value) error {
w.csData = k
w.csKey = append(w.csKey, k)
w.key = append(w.key, k.String())
w.lastValue = v
return nil
}
func (w *renderWalker) Slice(s reflect.Value) error {
w.cs = append(w.cs, s)
return nil
}
func (w *renderWalker) SliceElem(i int, elem reflect.Value) error {
w.csKey = append(w.csKey, reflect.ValueOf(i))
w.sliceIndex = i
return nil
}
func (w *renderWalker) Primitive(v reflect.Value) error {
setV := v
// We only care about strings
if v.Kind() == reflect.Interface {
setV = v
v = v.Elem()
}
if v.Kind() != reflect.String {
return nil
}
strV := v.String()
if w.ContextF != nil {
w.ContextF(w.loc, strV)
}
if w.F == nil {
return nil
}
replaceVal, err := w.F(strV)
if err != nil {
return fmt.Errorf(
"%s in:\n\n%s",
err, v.String())
}
if w.Replace {
resultVal := reflect.ValueOf(replaceVal)
switch w.loc {
case reflectwalk.MapKey:
m := w.cs[len(w.cs)-1]
// Delete the old value
var zero reflect.Value
m.SetMapIndex(w.csData.(reflect.Value), zero)
// Set the new key with the existing value
m.SetMapIndex(resultVal, w.lastValue)
// Set the key to be the new key
w.csData = resultVal
case reflectwalk.MapValue:
// If we're in a map, then the only way to set a map value is
// to set it directly.
m := w.cs[len(w.cs)-1]
mk := w.csData.(reflect.Value)
m.SetMapIndex(mk, resultVal)
case reflectwalk.WalkLoc:
// At the root element, we can't write that, so we just save it
w.Top = resultVal.Interface()
default:
// Otherwise, we should be addressable
setV.Set(resultVal)
}
}
return nil
}