Commit 94f6e7fe authored by Mitchell Hashimoto's avatar Mitchell Hashimoto

Merge pull request #2151 from mitchellh/f-paths

Add template_dir for path to template [GH-54]
parents 7ce76d28 048c764e
...@@ -104,6 +104,7 @@ func Decode(target interface{}, config *DecodeOpts, raws ...interface{}) error { ...@@ -104,6 +104,7 @@ func Decode(target interface{}, config *DecodeOpts, raws ...interface{}) error {
// detecting things like user variables from the raw configuration params. // detecting things like user variables from the raw configuration params.
func DetectContext(raws ...interface{}) (*interpolate.Context, error) { func DetectContext(raws ...interface{}) (*interpolate.Context, error) {
var s struct { var s struct {
TemplatePath string `mapstructure:"packer_template_path"`
Vars map[string]string `mapstructure:"packer_user_variables"` Vars map[string]string `mapstructure:"packer_user_variables"`
} }
...@@ -114,6 +115,7 @@ func DetectContext(raws ...interface{}) (*interpolate.Context, error) { ...@@ -114,6 +115,7 @@ func DetectContext(raws ...interface{}) (*interpolate.Context, error) {
} }
return &interpolate.Context{ return &interpolate.Context{
TemplatePath: s.TemplatePath,
UserVariables: s.Vars, UserVariables: s.Vars,
}, nil }, nil
} }
......
...@@ -24,6 +24,9 @@ const ( ...@@ -24,6 +24,9 @@ const (
// force build is enabled. // force build is enabled.
ForceConfigKey = "packer_force" 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 // This key contains a map[string]string of the user variables for
// template processing. // template processing.
UserVariablesConfigKey = "packer_user_variables" UserVariablesConfigKey = "packer_user_variables"
...@@ -78,6 +81,7 @@ type coreBuild struct { ...@@ -78,6 +81,7 @@ type coreBuild struct {
hooks map[string][]Hook hooks map[string][]Hook
postProcessors [][]coreBuildPostProcessor postProcessors [][]coreBuildPostProcessor
provisioners []coreBuildProvisioner provisioners []coreBuildProvisioner
templatePath string
variables map[string]string variables map[string]string
debug bool debug bool
...@@ -125,6 +129,7 @@ func (b *coreBuild) Prepare() (warn []string, err error) { ...@@ -125,6 +129,7 @@ func (b *coreBuild) Prepare() (warn []string, err error) {
BuilderTypeConfigKey: b.builderType, BuilderTypeConfigKey: b.builderType,
DebugConfigKey: b.debug, DebugConfigKey: b.debug,
ForceConfigKey: b.force, ForceConfigKey: b.force,
TemplatePathKey: b.templatePath,
UserVariablesConfigKey: b.variables, UserVariablesConfigKey: b.variables,
} }
......
...@@ -32,6 +32,7 @@ func testDefaultPackerConfig() map[string]interface{} { ...@@ -32,6 +32,7 @@ func testDefaultPackerConfig() map[string]interface{} {
BuilderTypeConfigKey: "foo", BuilderTypeConfigKey: "foo",
DebugConfigKey: false, DebugConfigKey: false,
ForceConfigKey: false, ForceConfigKey: false,
TemplatePathKey: "",
UserVariablesConfigKey: make(map[string]string), UserVariablesConfigKey: make(map[string]string),
} }
} }
......
...@@ -50,27 +50,10 @@ type ComponentFinder struct { ...@@ -50,27 +50,10 @@ type ComponentFinder struct {
// NewCore creates a new Core. // NewCore creates a new Core.
func NewCore(c *CoreConfig) (*Core, error) { 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{ result := &Core{
components: c.Components, components: c.Components,
template: c.Template, template: c.Template,
variables: c.Variables, variables: c.Variables,
builds: builds,
} }
if err := result.validate(); err != nil { if err := result.validate(); err != nil {
return nil, err return nil, err
...@@ -79,6 +62,20 @@ func NewCore(c *CoreConfig) (*Core, error) { ...@@ -79,6 +62,20 @@ func NewCore(c *CoreConfig) (*Core, error) {
return nil, err 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 return result, nil
} }
...@@ -204,6 +201,7 @@ func (c *Core) Build(n string) (Build, error) { ...@@ -204,6 +201,7 @@ func (c *Core) Build(n string) (Build, error) {
builderType: configBuilder.Type, builderType: configBuilder.Type,
postProcessors: postProcessors, postProcessors: postProcessors,
provisioners: provisioners, provisioners: provisioners,
templatePath: c.template.Path,
variables: c.variables, variables: c.variables,
}, nil }, nil
} }
...@@ -243,7 +241,9 @@ func (c *Core) init() error { ...@@ -243,7 +241,9 @@ func (c *Core) init() error {
} }
// Go through the variables and interpolate the environment variables // 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 { for k, v := range c.template.Variables {
// Ignore variables that are required // Ignore variables that are required
if v.Required { if v.Required {
...@@ -268,3 +268,10 @@ func (c *Core) init() error { ...@@ -268,3 +268,10 @@ func (c *Core) init() error {
return nil return nil
} }
func (c *Core) context() *interpolate.Context {
return &interpolate.Context{
TemplatePath: c.template.Path,
UserVariables: c.variables,
}
}
...@@ -2,6 +2,7 @@ package packer ...@@ -2,6 +2,7 @@ package packer
import ( import (
"os" "os"
"path/filepath"
"reflect" "reflect"
"testing" "testing"
...@@ -338,6 +339,35 @@ func TestCoreBuild_postProcess(t *testing.T) { ...@@ -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) { func TestCoreValidate(t *testing.T) {
cases := []struct { cases := []struct {
File string File string
......
{
"builders": [{
"type": "test",
"value": "{{template_dir}}"
}]
}
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"text/template" "text/template"
...@@ -26,6 +27,7 @@ var FuncGens = map[string]FuncGenerator{ ...@@ -26,6 +27,7 @@ var FuncGens = map[string]FuncGenerator{
"env": funcGenEnv, "env": funcGenEnv,
"isotime": funcGenIsotime, "isotime": funcGenIsotime,
"pwd": funcGenPwd, "pwd": funcGenPwd,
"template_dir": funcGenTemplateDir,
"timestamp": funcGenTimestamp, "timestamp": funcGenTimestamp,
"uuid": funcGenUuid, "uuid": funcGenUuid,
"user": funcGenUser, "user": funcGenUser,
...@@ -92,6 +94,21 @@ func funcGenPwd(ctx *Context) interface{} { ...@@ -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{} { func funcGenTimestamp(ctx *Context) interface{} {
return func() string { return func() string {
return strconv.FormatInt(InitTime.Unix(), 10) return strconv.FormatInt(InitTime.Unix(), 10)
......
...@@ -2,6 +2,7 @@ package interpolate ...@@ -2,6 +2,7 @@ package interpolate
import ( import (
"os" "os"
"path/filepath"
"strconv" "strconv"
"testing" "testing"
"time" "time"
...@@ -116,6 +117,36 @@ func TestFuncPwd(t *testing.T) { ...@@ -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) { func TestFuncTimestamp(t *testing.T) {
expected := strconv.FormatInt(InitTime.Unix(), 10) expected := strconv.FormatInt(InitTime.Unix(), 10)
......
...@@ -14,6 +14,10 @@ type Context struct { ...@@ -14,6 +14,10 @@ type Context struct {
// Funcs are extra functions available in the template // Funcs are extra functions available in the template
Funcs map[string]interface{} 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 // UserVariables is the mapping of user variables that the
// "user" function reads from. // "user" function reads from.
UserVariables map[string]string UserVariables map[string]string
......
...@@ -310,5 +310,11 @@ func ParseFile(path string) (*Template, error) { ...@@ -310,5 +310,11 @@ func ParseFile(path string) (*Template, error) {
} }
defer f.Close() defer f.Close()
return Parse(f) tpl, err := Parse(f)
if err != nil {
return nil, err
}
tpl.Path = path
return tpl, nil
} }
...@@ -272,11 +272,15 @@ func TestParse(t *testing.T) { ...@@ -272,11 +272,15 @@ func TestParse(t *testing.T) {
} }
for _, tc := range cases { for _, tc := range cases {
path := fixtureDir(tc.File)
tpl, err := ParseFile(fixtureDir(tc.File)) tpl, err := ParseFile(fixtureDir(tc.File))
if (err != nil) != tc.Err { if (err != nil) != tc.Err {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if tc.Result != nil {
tc.Result.Path = path
}
if tpl != nil { if tpl != nil {
tpl.RawContents = nil tpl.RawContents = nil
} }
......
...@@ -11,6 +11,10 @@ import ( ...@@ -11,6 +11,10 @@ import (
// Template represents the parsed template that is used to configure // Template represents the parsed template that is used to configure
// Packer builds. // Packer builds.
type Template struct { 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 Description string
MinVersion string MinVersion string
......
...@@ -55,10 +55,11 @@ While some configuration settings have local variables specific to only that ...@@ -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_ configuration, a set of functions are available globally for use in _any string_
in Packer templates. These are listed below for reference. 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). * `isotime [FORMAT]` - UTC time, which can be [formatted](http://golang.org/pkg/time/#example_Time_Format).
See more examples below. 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. * `timestamp` - The current Unix timestamp in UTC.
* `uuid` - Returns a random UUID. * `uuid` - Returns a random UUID.
* `upper` - Uppercases the string. * `upper` - Uppercases the string.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment