diff --git a/helper/config/decode.go b/helper/config/decode.go index 73e470d27..0148ad27c 100644 --- a/helper/config/decode.go +++ b/helper/config/decode.go @@ -104,7 +104,8 @@ func Decode(target interface{}, config *DecodeOpts, raws ...interface{}) error { // 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"` + TemplatePath string `mapstructure:"packer_template_path"` + Vars map[string]string `mapstructure:"packer_user_variables"` } for _, r := range raws { @@ -114,6 +115,7 @@ func DetectContext(raws ...interface{}) (*interpolate.Context, error) { } return &interpolate.Context{ + TemplatePath: s.TemplatePath, UserVariables: s.Vars, }, nil } diff --git a/packer/build.go b/packer/build.go index 987e4d339..757ef89eb 100644 --- a/packer/build.go +++ b/packer/build.go @@ -24,6 +24,9 @@ const ( // force build is enabled. ForceConfigKey = "packer_force" + // TemplatePathKey is the path to the template that configured this build + TemplatePathKey = "packer_template_path" + // This key contains a map[string]string of the user variables for // template processing. UserVariablesConfigKey = "packer_user_variables" @@ -78,6 +81,7 @@ type coreBuild struct { hooks map[string][]Hook postProcessors [][]coreBuildPostProcessor provisioners []coreBuildProvisioner + templatePath string variables map[string]string debug bool @@ -125,6 +129,7 @@ func (b *coreBuild) Prepare() (warn []string, err error) { BuilderTypeConfigKey: b.builderType, DebugConfigKey: b.debug, ForceConfigKey: b.force, + TemplatePathKey: b.templatePath, UserVariablesConfigKey: b.variables, } diff --git a/packer/build_test.go b/packer/build_test.go index 4f93e03a5..b183fb95a 100644 --- a/packer/build_test.go +++ b/packer/build_test.go @@ -32,6 +32,7 @@ func testDefaultPackerConfig() map[string]interface{} { BuilderTypeConfigKey: "foo", DebugConfigKey: false, ForceConfigKey: false, + TemplatePathKey: "", UserVariablesConfigKey: make(map[string]string), } } diff --git a/packer/core.go b/packer/core.go index d4cb9fea5..0f7886772 100644 --- a/packer/core.go +++ b/packer/core.go @@ -50,27 +50,10 @@ type ComponentFinder struct { // NewCore creates a new Core. func NewCore(c *CoreConfig) (*Core, error) { - // Go through and interpolate all the build names. We shuld be able - // to do this at this point with the variables. - builds := make(map[string]*template.Builder) - for _, b := range c.Template.Builders { - v, err := interpolate.Render(b.Name, &interpolate.Context{ - UserVariables: c.Variables, - }) - if err != nil { - return nil, fmt.Errorf( - "Error interpolating builder '%s': %s", - b.Name, err) - } - - builds[v] = b - } - result := &Core{ components: c.Components, template: c.Template, variables: c.Variables, - builds: builds, } if err := result.validate(); err != nil { return nil, err @@ -79,6 +62,20 @@ func NewCore(c *CoreConfig) (*Core, error) { return nil, err } + // Go through and interpolate all the build names. We shuld be able + // to do this at this point with the variables. + result.builds = make(map[string]*template.Builder) + for _, b := range c.Template.Builders { + v, err := interpolate.Render(b.Name, result.context()) + if err != nil { + return nil, fmt.Errorf( + "Error interpolating builder '%s': %s", + b.Name, err) + } + + result.builds[v] = b + } + return result, nil } @@ -204,6 +201,7 @@ func (c *Core) Build(n string) (Build, error) { builderType: configBuilder.Type, postProcessors: postProcessors, provisioners: provisioners, + templatePath: c.template.Path, variables: c.variables, }, nil } @@ -243,7 +241,9 @@ func (c *Core) init() error { } // Go through the variables and interpolate the environment variables - ctx := &interpolate.Context{EnableEnv: true} + ctx := c.context() + ctx.EnableEnv = true + ctx.UserVariables = nil for k, v := range c.template.Variables { // Ignore variables that are required if v.Required { @@ -268,3 +268,10 @@ func (c *Core) init() error { return nil } + +func (c *Core) context() *interpolate.Context { + return &interpolate.Context{ + TemplatePath: c.template.Path, + UserVariables: c.variables, + } +} diff --git a/packer/core_test.go b/packer/core_test.go index 9df7ca85c..cc03574ed 100644 --- a/packer/core_test.go +++ b/packer/core_test.go @@ -2,6 +2,7 @@ package packer import ( "os" + "path/filepath" "reflect" "testing" @@ -338,6 +339,35 @@ func TestCoreBuild_postProcess(t *testing.T) { } } +func TestCoreBuild_templatePath(t *testing.T) { + config := TestCoreConfig(t) + testCoreTemplate(t, config, fixtureDir("build-template-path.json")) + b := TestBuilder(t, config, "test") + core := TestCore(t, config) + + expected, _ := filepath.Abs("./test-fixtures") + + build, err := core.Build("test") + if err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := build.Prepare(); err != nil { + t.Fatalf("err: %s", err) + } + + // Interpolate the config + var result map[string]interface{} + err = configHelper.Decode(&result, nil, b.PrepareConfig...) + if err != nil { + t.Fatalf("err: %s", err) + } + + if result["value"] != expected { + t.Fatalf("bad: %#v", result) + } +} + func TestCoreValidate(t *testing.T) { cases := []struct { File string diff --git a/packer/test-fixtures/build-template-path.json b/packer/test-fixtures/build-template-path.json new file mode 100644 index 000000000..dcda0818a --- /dev/null +++ b/packer/test-fixtures/build-template-path.json @@ -0,0 +1,6 @@ +{ + "builders": [{ + "type": "test", + "value": "{{template_dir}}" + }] +} diff --git a/template/interpolate/funcs.go b/template/interpolate/funcs.go index ca51fea81..6092707b8 100644 --- a/template/interpolate/funcs.go +++ b/template/interpolate/funcs.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "os" + "path/filepath" "strconv" "strings" "text/template" @@ -23,12 +24,13 @@ func init() { // Funcs are the interpolation funcs that are available within interpolations. var FuncGens = map[string]FuncGenerator{ - "env": funcGenEnv, - "isotime": funcGenIsotime, - "pwd": funcGenPwd, - "timestamp": funcGenTimestamp, - "uuid": funcGenUuid, - "user": funcGenUser, + "env": funcGenEnv, + "isotime": funcGenIsotime, + "pwd": funcGenPwd, + "template_dir": funcGenTemplateDir, + "timestamp": funcGenTimestamp, + "uuid": funcGenUuid, + "user": funcGenUser, "upper": funcGenPrimitive(strings.ToUpper), "lower": funcGenPrimitive(strings.ToLower), @@ -92,6 +94,21 @@ func funcGenPwd(ctx *Context) interface{} { } } +func funcGenTemplateDir(ctx *Context) interface{} { + return func() (string, error) { + if ctx == nil || ctx.TemplatePath == "" { + return "", errors.New("template path not available") + } + + path, err := filepath.Abs(filepath.Dir(ctx.TemplatePath)) + if err != nil { + return "", err + } + + return path, nil + } +} + func funcGenTimestamp(ctx *Context) interface{} { return func() string { return strconv.FormatInt(InitTime.Unix(), 10) diff --git a/template/interpolate/funcs_test.go b/template/interpolate/funcs_test.go index aad05d376..ff877f13e 100644 --- a/template/interpolate/funcs_test.go +++ b/template/interpolate/funcs_test.go @@ -2,6 +2,7 @@ package interpolate import ( "os" + "path/filepath" "strconv" "testing" "time" @@ -116,6 +117,36 @@ func TestFuncPwd(t *testing.T) { } } +func TestFuncTemplatePath(t *testing.T) { + path := "foo/bar" + expected, _ := filepath.Abs(filepath.Dir(path)) + + cases := []struct { + Input string + Output string + }{ + { + `{{template_dir}}`, + expected, + }, + } + + ctx := &Context{ + TemplatePath: path, + } + for _, tc := range cases { + i := &I{Value: tc.Input} + result, err := i.Render(ctx) + if err != nil { + t.Fatalf("Input: %s\n\nerr: %s", tc.Input, err) + } + + if result != tc.Output { + t.Fatalf("Input: %s\n\nGot: %s", tc.Input, result) + } + } +} + func TestFuncTimestamp(t *testing.T) { expected := strconv.FormatInt(InitTime.Unix(), 10) diff --git a/template/interpolate/i.go b/template/interpolate/i.go index c0cd13f85..d5f7c8413 100644 --- a/template/interpolate/i.go +++ b/template/interpolate/i.go @@ -14,6 +14,10 @@ type Context struct { // Funcs are extra functions available in the template Funcs map[string]interface{} + // TemplatePath is the path to the template that this is being + // rendered within. + TemplatePath string + // UserVariables is the mapping of user variables that the // "user" function reads from. UserVariables map[string]string diff --git a/template/parse.go b/template/parse.go index c19c4b4ff..a7b187f37 100644 --- a/template/parse.go +++ b/template/parse.go @@ -310,5 +310,11 @@ func ParseFile(path string) (*Template, error) { } defer f.Close() - return Parse(f) + tpl, err := Parse(f) + if err != nil { + return nil, err + } + + tpl.Path = path + return tpl, nil } diff --git a/template/parse_test.go b/template/parse_test.go index 78b4416ba..46bb75ad8 100644 --- a/template/parse_test.go +++ b/template/parse_test.go @@ -272,11 +272,15 @@ func TestParse(t *testing.T) { } for _, tc := range cases { + path := fixtureDir(tc.File) tpl, err := ParseFile(fixtureDir(tc.File)) if (err != nil) != tc.Err { t.Fatalf("err: %s", err) } + if tc.Result != nil { + tc.Result.Path = path + } if tpl != nil { tpl.RawContents = nil } diff --git a/template/template.go b/template/template.go index bee4e510e..a22e78b95 100644 --- a/template/template.go +++ b/template/template.go @@ -11,6 +11,10 @@ import ( // Template represents the parsed template that is used to configure // Packer builds. type Template struct { + // Path is the path to the template. This will be blank if Parse is + // used, but will be automatically populated by ParseFile. + Path string + Description string MinVersion string diff --git a/website/source/docs/templates/configuration-templates.html.markdown b/website/source/docs/templates/configuration-templates.html.markdown index f5ae4a362..f940ebcc1 100644 --- a/website/source/docs/templates/configuration-templates.html.markdown +++ b/website/source/docs/templates/configuration-templates.html.markdown @@ -55,10 +55,11 @@ While some configuration settings have local variables specific to only that configuration, a set of functions are available globally for use in _any string_ in Packer templates. These are listed below for reference. -* `lower` - Lowercases the string. -* `pwd` - The working directory while executing Packer. * `isotime [FORMAT]` - UTC time, which can be [formatted](http://golang.org/pkg/time/#example_Time_Format). See more examples below. +* `lower` - Lowercases the string. +* `pwd` - The working directory while executing Packer. +* `template_dir` - The directory to the template for the build. * `timestamp` - The current Unix timestamp in UTC. * `uuid` - Returns a random UUID. * `upper` - Uppercases the string.