github.com/marksheahan/packer@v0.10.2-0.20160613200515-1acb2d6645a0/helper/config/decode.go (about)

     1  package config
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"sort"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/go-multierror"
    10  	"github.com/mitchellh/mapstructure"
    11  	"github.com/mitchellh/packer/template/interpolate"
    12  )
    13  
    14  // DecodeOpts are the options for decoding configuration.
    15  type DecodeOpts struct {
    16  	// Metadata, if non-nil, will be set to the metadata post-decode
    17  	Metadata *mapstructure.Metadata
    18  
    19  	// Interpolate, if true, will automatically interpolate the
    20  	// configuration with the given InterpolateContext. User variables
    21  	// will be automatically detected and added in-place to the given
    22  	// context.
    23  	Interpolate        bool
    24  	InterpolateContext *interpolate.Context
    25  	InterpolateFilter  *interpolate.RenderFilter
    26  }
    27  
    28  // Decode decodes the configuration into the target and optionally
    29  // automatically interpolates all the configuration as it goes.
    30  func Decode(target interface{}, config *DecodeOpts, raws ...interface{}) error {
    31  	if config == nil {
    32  		config = &DecodeOpts{Interpolate: true}
    33  	}
    34  
    35  	// Interpolate first
    36  	if config.Interpolate {
    37  		// Detect user variables from the raws and merge them into our context
    38  		ctx, err := DetectContext(raws...)
    39  		if err != nil {
    40  			return err
    41  		}
    42  		if config.InterpolateContext == nil {
    43  			config.InterpolateContext = ctx
    44  		} else {
    45  			config.InterpolateContext.BuildName = ctx.BuildName
    46  			config.InterpolateContext.BuildType = ctx.BuildType
    47  			config.InterpolateContext.TemplatePath = ctx.TemplatePath
    48  			config.InterpolateContext.UserVariables = ctx.UserVariables
    49  		}
    50  		ctx = config.InterpolateContext
    51  
    52  		// Render everything
    53  		for i, raw := range raws {
    54  			m, err := interpolate.RenderMap(raw, ctx, config.InterpolateFilter)
    55  			if err != nil {
    56  				return err
    57  			}
    58  
    59  			raws[i] = m
    60  		}
    61  	}
    62  
    63  	// Build our decoder
    64  	var md mapstructure.Metadata
    65  	decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
    66  		Result:           target,
    67  		Metadata:         &md,
    68  		WeaklyTypedInput: true,
    69  		DecodeHook: mapstructure.ComposeDecodeHookFunc(
    70  			uint8ToStringHook,
    71  			mapstructure.StringToSliceHookFunc(","),
    72  			mapstructure.StringToTimeDurationHookFunc(),
    73  		),
    74  	})
    75  	if err != nil {
    76  		return err
    77  	}
    78  	for _, raw := range raws {
    79  		if err := decoder.Decode(raw); err != nil {
    80  			return err
    81  		}
    82  	}
    83  
    84  	// Set the metadata if it is set
    85  	if config.Metadata != nil {
    86  		*config.Metadata = md
    87  	}
    88  
    89  	// If we have unused keys, it is an error
    90  	if len(md.Unused) > 0 {
    91  		var err error
    92  		sort.Strings(md.Unused)
    93  		for _, unused := range md.Unused {
    94  			if unused != "type" && !strings.HasPrefix(unused, "packer_") {
    95  				err = multierror.Append(err, fmt.Errorf(
    96  					"unknown configuration key: %q", unused))
    97  			}
    98  		}
    99  		if err != nil {
   100  			return err
   101  		}
   102  	}
   103  
   104  	return nil
   105  }
   106  
   107  // DetectContext builds a base interpolate.Context, automatically
   108  // detecting things like user variables from the raw configuration params.
   109  func DetectContext(raws ...interface{}) (*interpolate.Context, error) {
   110  	var s struct {
   111  		BuildName    string            `mapstructure:"packer_build_name"`
   112  		BuildType    string            `mapstructure:"packer_builder_type"`
   113  		TemplatePath string            `mapstructure:"packer_template_path"`
   114  		Vars         map[string]string `mapstructure:"packer_user_variables"`
   115  	}
   116  
   117  	for _, r := range raws {
   118  		if err := mapstructure.Decode(r, &s); err != nil {
   119  			return nil, err
   120  		}
   121  	}
   122  
   123  	return &interpolate.Context{
   124  		BuildName:     s.BuildName,
   125  		BuildType:     s.BuildType,
   126  		TemplatePath:  s.TemplatePath,
   127  		UserVariables: s.Vars,
   128  	}, nil
   129  }
   130  
   131  func uint8ToStringHook(f reflect.Kind, t reflect.Kind, v interface{}) (interface{}, error) {
   132  	// We need to convert []uint8 to string. We have to do this
   133  	// because internally Packer uses MsgPack for RPC and the MsgPack
   134  	// codec turns strings into []uint8
   135  	if f == reflect.Slice && t == reflect.String {
   136  		dataVal := reflect.ValueOf(v)
   137  		dataType := dataVal.Type()
   138  		elemKind := dataType.Elem().Kind()
   139  		if elemKind == reflect.Uint8 {
   140  			v = string(dataVal.Interface().([]uint8))
   141  		}
   142  	}
   143  
   144  	return v, nil
   145  }