helper/config: decoder

This commit is contained in:
Mitchell Hashimoto 2015-05-27 10:44:10 -07:00
parent 71932cccc9
commit 241f76b5b1
2 changed files with 194 additions and 0 deletions

108
helper/config/decode.go Normal file
View File

@ -0,0 +1,108 @@
package config
import (
"reflect"
"github.com/mitchellh/mapstructure"
"github.com/mitchellh/packer/template/interpolate"
)
// DecodeOpts are the options for decoding configuration.
type DecodeOpts struct {
// Interpolate, if true, will automatically interpolate the
// configuration with the given InterpolateContext. User variables
// will be automatically detected and added in-place to the given
// context.
Interpolate bool
InterpolateContext *interpolate.Context
InterpolateFilter *interpolate.RenderFilter
}
// Decode decodes the configuration into the target and optionally
// automatically interpolates all the configuration as it goes.
func Decode(target interface{}, config *DecodeOpts, raws ...interface{}) error {
if config == nil {
config = &DecodeOpts{Interpolate: true}
}
// Interpolate first
if config.Interpolate {
// Detect user variables from the raws and merge them into our context
ctx, err := DetectContext(raws...)
if err != nil {
return err
}
if config.InterpolateContext == nil {
config.InterpolateContext = ctx
} else {
config.InterpolateContext.UserVariables = ctx.UserVariables
}
ctx = config.InterpolateContext
// Render everything
for i, raw := range raws {
m, err := interpolate.RenderMap(raw, ctx, config.InterpolateFilter)
if err != nil {
return err
}
raws[i] = m
}
}
// Build our decoder
var md mapstructure.Metadata
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Result: target,
Metadata: &md,
WeaklyTypedInput: true,
DecodeHook: mapstructure.ComposeDecodeHookFunc(
uint8ToStringHook,
mapstructure.StringToSliceHookFunc(","),
),
})
if err != nil {
return err
}
for _, raw := range raws {
if err := decoder.Decode(raw); err != nil {
return err
}
}
return nil
}
// DetectContext builds a base interpolate.Context, automatically
// detecting things like user variables from the raw configuration params.
func DetectContext(raws ...interface{}) (*interpolate.Context, error) {
var s struct {
Vars map[string]string `mapstructure:"packer_user_variables"`
}
for _, r := range raws {
if err := mapstructure.Decode(r, &s); err != nil {
return nil, err
}
}
return &interpolate.Context{
UserVariables: s.Vars,
}, nil
}
func uint8ToStringHook(f reflect.Kind, t reflect.Kind, v interface{}) (interface{}, error) {
// We need to convert []uint8 to string. We have to do this
// because internally Packer uses MsgPack for RPC and the MsgPack
// codec turns strings into []uint8
if f == reflect.Slice && t == reflect.String {
dataVal := reflect.ValueOf(v)
dataType := dataVal.Type()
elemKind := dataType.Elem().Kind()
if elemKind == reflect.Uint8 {
v = string(dataVal.Interface().([]uint8))
}
}
return v, nil
}

View File

@ -0,0 +1,86 @@
package config
import (
"reflect"
"testing"
"github.com/mitchellh/packer/template/interpolate"
)
func TestDecode(t *testing.T) {
type Target struct {
Name string
Address string
}
cases := map[string]struct {
Input []interface{}
Output *Target
Opts *DecodeOpts
}{
"basic": {
[]interface{}{
map[string]interface{}{
"name": "bar",
},
},
&Target{
Name: "bar",
},
nil,
},
"variables": {
[]interface{}{
map[string]interface{}{
"name": "{{user `name`}}",
},
map[string]interface{}{
"packer_user_variables": map[string]string{
"name": "bar",
},
},
},
&Target{
Name: "bar",
},
nil,
},
"filter": {
[]interface{}{
map[string]interface{}{
"name": "{{user `name`}}",
"address": "{{user `name`}}",
},
map[string]interface{}{
"packer_user_variables": map[string]string{
"name": "bar",
},
},
},
&Target{
Name: "bar",
Address: "{{user `name`}}",
},
&DecodeOpts{
Interpolate: true,
InterpolateFilter: &interpolate.RenderFilter{
Include: []string{"name"},
},
},
},
}
for k, tc := range cases {
var result Target
err := Decode(&result, tc.Opts, tc.Input...)
if err != nil {
t.Fatalf("err: %s\n\n%s", k, err)
}
if !reflect.DeepEqual(&result, tc.Output) {
t.Fatalf("bad:\n\n%#v\n\n%#v", &result, tc.Output)
}
}
}