github.com/prasannakumarik25/packer@v1.3.2/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/hashicorp/packer/template/interpolate"
    11  	"github.com/mitchellh/mapstructure"
    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  		SensitiveVars []string          `mapstructure:"packer_sensitive_variables"`
   116  	}
   117  
   118  	for _, r := range raws {
   119  		if err := mapstructure.Decode(r, &s); err != nil {
   120  			return nil, err
   121  		}
   122  	}
   123  
   124  	return &interpolate.Context{
   125  		BuildName:          s.BuildName,
   126  		BuildType:          s.BuildType,
   127  		TemplatePath:       s.TemplatePath,
   128  		UserVariables:      s.Vars,
   129  		SensitiveVariables: s.SensitiveVars,
   130  	}, nil
   131  }
   132  
   133  func uint8ToStringHook(f reflect.Kind, t reflect.Kind, v interface{}) (interface{}, error) {
   134  	// We need to convert []uint8 to string. We have to do this
   135  	// because internally Packer uses MsgPack for RPC and the MsgPack
   136  	// codec turns strings into []uint8
   137  	if f == reflect.Slice && t == reflect.String {
   138  		dataVal := reflect.ValueOf(v)
   139  		dataType := dataVal.Type()
   140  		elemKind := dataType.Elem().Kind()
   141  		if elemKind == reflect.Uint8 {
   142  			v = string(dataVal.Interface().([]uint8))
   143  		}
   144  	}
   145  
   146  	return v, nil
   147  }