common: process user variables in non-string config decodes [GH-598]

This commit is contained in:
Mitchell Hashimoto 2013-12-16 17:57:07 -08:00
parent 47d47082fc
commit 4c6800f5a3
3 changed files with 72 additions and 0 deletions

View File

@ -35,6 +35,7 @@ IMPROVEMENTS:
BUG FIXES:
* core: No colored output in machine-readable output. [GH-684]
* core: User variables can now be used for non-string fields. [GH-598]
* post-processor/vsphere: Uploads VM properly. [GH-694]
* post-processor/vsphere: Process user variables.
* provisioner/ansible-local: playbook paths are properly validated

View File

@ -7,6 +7,7 @@ import (
"net/url"
"os"
"path/filepath"
"reflect"
"runtime"
"sort"
"strings"
@ -49,8 +50,14 @@ func CheckUnusedConfig(md *mapstructure.Metadata) *packer.MultiError {
// If you need extra configuration for mapstructure, you should configure
// it manually and not use this helper function.
func DecodeConfig(target interface{}, raws ...interface{}) (*mapstructure.Metadata, error) {
decodeHook, err := decodeConfigHook(raws)
if err != nil {
return nil, err
}
var md mapstructure.Metadata
decoderConfig := &mapstructure.DecoderConfig{
DecodeHook: decodeHook,
Metadata: &md,
Result: target,
WeaklyTypedInput: true,
@ -149,3 +156,41 @@ func DownloadableURL(original string) (string, error) {
return url.String(), nil
}
// This returns a mapstructure.DecodeHookFunc that automatically template
// processes any configuration values that aren't strings but have been
// provided as strings.
//
// For example: "image_id" wants an int and the user uses a string with
// a user variable like "{{user `image_id`}}". This decode hook makes that
// work.
func decodeConfigHook(raws []interface{}) (mapstructure.DecodeHookFunc, error) {
// First thing we do is decode PackerConfig so that we can have access
// to the user variables so that we can process some templates.
var pc PackerConfig
for _, raw := range raws {
if err := mapstructure.Decode(raw, &pc); err != nil {
return nil, err
}
}
tpl, err := packer.NewConfigTemplate()
if err != nil {
return nil, err
}
tpl.UserVars = pc.PackerUserVars
return func(f reflect.Kind, t reflect.Kind, v interface{}) (interface{}, error) {
if t != reflect.String {
if sv, ok := v.(string); ok {
var err error
v, err = tpl.Process(sv, nil)
if err != nil {
return nil, err
}
}
}
return v, nil
}, nil
}

View File

@ -66,6 +66,32 @@ func TestDecodeConfig(t *testing.T) {
}
}
// This test tests the case that a user var is used for an integer
// configuration.
func TestDecodeConfig_userVarConversion(t *testing.T) {
type Local struct {
Val int
}
raw := map[string]interface{}{
"packer_user_variables": map[string]string{
"foo": "42",
},
"val": "{{user `foo`}}",
}
var result Local
_, err := DecodeConfig(&result, raw)
if err != nil {
t.Fatalf("err: %s", err)
}
if result.Val != 42 {
t.Fatalf("invalid: %#v", result.Val)
}
}
func TestDownloadableURL(t *testing.T) {
// Invalid URL: has hex code in host
_, err := DownloadableURL("http://what%20.com")