github.com/emate/packer@v0.8.1-0.20150625195101-fe0fde195dc6/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.TemplatePath = ctx.TemplatePath
    46  			config.InterpolateContext.UserVariables = ctx.UserVariables
    47  		}
    48  		ctx = config.InterpolateContext
    49  
    50  		// Render everything
    51  		for i, raw := range raws {
    52  			m, err := interpolate.RenderMap(raw, ctx, config.InterpolateFilter)
    53  			if err != nil {
    54  				return err
    55  			}
    56  
    57  			raws[i] = m
    58  		}
    59  	}
    60  
    61  	// Build our decoder
    62  	var md mapstructure.Metadata
    63  	decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
    64  		Result:           target,
    65  		Metadata:         &md,
    66  		WeaklyTypedInput: true,
    67  		DecodeHook: mapstructure.ComposeDecodeHookFunc(
    68  			uint8ToStringHook,
    69  			mapstructure.StringToSliceHookFunc(","),
    70  			mapstructure.StringToTimeDurationHookFunc(),
    71  		),
    72  	})
    73  	if err != nil {
    74  		return err
    75  	}
    76  	for _, raw := range raws {
    77  		if err := decoder.Decode(raw); err != nil {
    78  			return err
    79  		}
    80  	}
    81  
    82  	// Set the metadata if it is set
    83  	if config.Metadata != nil {
    84  		*config.Metadata = md
    85  	}
    86  
    87  	// If we have unused keys, it is an error
    88  	if len(md.Unused) > 0 {
    89  		var err error
    90  		sort.Strings(md.Unused)
    91  		for _, unused := range md.Unused {
    92  			if unused != "type" && !strings.HasPrefix(unused, "packer_") {
    93  				err = multierror.Append(err, fmt.Errorf(
    94  					"unknown configuration key: %q", unused))
    95  			}
    96  		}
    97  		if err != nil {
    98  			return err
    99  		}
   100  	}
   101  
   102  	return nil
   103  }
   104  
   105  // DetectContext builds a base interpolate.Context, automatically
   106  // detecting things like user variables from the raw configuration params.
   107  func DetectContext(raws ...interface{}) (*interpolate.Context, error) {
   108  	var s struct {
   109  		BuildName    string            `mapstructure:"packer_build_name"`
   110  		BuildType    string            `mapstructure:"packer_builder_type"`
   111  		TemplatePath string            `mapstructure:"packer_template_path"`
   112  		Vars         map[string]string `mapstructure:"packer_user_variables"`
   113  	}
   114  
   115  	for _, r := range raws {
   116  		if err := mapstructure.Decode(r, &s); err != nil {
   117  			return nil, err
   118  		}
   119  	}
   120  
   121  	return &interpolate.Context{
   122  		BuildName:     s.BuildName,
   123  		BuildType:     s.BuildType,
   124  		TemplatePath:  s.TemplatePath,
   125  		UserVariables: s.Vars,
   126  	}, nil
   127  }
   128  
   129  func uint8ToStringHook(f reflect.Kind, t reflect.Kind, v interface{}) (interface{}, error) {
   130  	// We need to convert []uint8 to string. We have to do this
   131  	// because internally Packer uses MsgPack for RPC and the MsgPack
   132  	// codec turns strings into []uint8
   133  	if f == reflect.Slice && t == reflect.String {
   134  		dataVal := reflect.ValueOf(v)
   135  		dataType := dataVal.Type()
   136  		elemKind := dataType.Elem().Kind()
   137  		if elemKind == reflect.Uint8 {
   138  			v = string(dataVal.Interface().([]uint8))
   139  		}
   140  	}
   141  
   142  	return v, nil
   143  }