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 }