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 }