From 4c6800f5a38e6f8073108bae0fe86d4feae0ee22 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 16 Dec 2013 17:57:07 -0800 Subject: [PATCH] common: process user variables in non-string config decodes [GH-598] --- CHANGELOG.md | 1 + common/config.go | 45 +++++++++++++++++++++++++++++++++++++++++++ common/config_test.go | 26 +++++++++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ee7ac9ed..02ea60cf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/common/config.go b/common/config.go index a0204eed2..bcd17ee04 100644 --- a/common/config.go +++ b/common/config.go @@ -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 +} diff --git a/common/config_test.go b/common/config_test.go index c58c105e6..783c95fb2 100644 --- a/common/config_test.go +++ b/common/config_test.go @@ -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")