Skip to content

Commit

Permalink
Merge pull request #1402 from jasonberanek/858-issue
Browse files Browse the repository at this point in the history
fix build name ConfigTemplate processing [GH-858]
  • Loading branch information
sethvargo committed Nov 26, 2014
2 parents 0eb634a + 56ec6bf commit 347f02a
Show file tree
Hide file tree
Showing 4 changed files with 396 additions and 64 deletions.
28 changes: 22 additions & 6 deletions common/command/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,25 @@ func (f *BuildOptions) AllUserVars() (map[string]string, error) {
func (f *BuildOptions) Builds(t *packer.Template, cf *packer.ComponentFinder) ([]packer.Build, error) {
buildNames := t.BuildNames()

// Process the name
tpl, _, err := t.NewConfigTemplate()
if err != nil {
return nil, err
}

checks := make(map[string][]string)
checks["except"] = f.Except
checks["only"] = f.Only
for t, ns := range checks {
for _, n := range ns {
found := false
for _, actual := range buildNames {
if actual == n {
var processed string
processed, err = tpl.Process(actual, nil)
if err != nil {
return nil, err
}
if actual == n || processed == n {
found = true
break
}
Expand All @@ -88,37 +99,42 @@ func (f *BuildOptions) Builds(t *packer.Template, cf *packer.ComponentFinder) ([

builds := make([]packer.Build, 0, len(buildNames))
for _, buildName := range buildNames {
var processedBuildName string
processedBuildName, err = tpl.Process(buildName, nil)
if err != nil {
return nil, err
}
if len(f.Except) > 0 {
found := false
for _, except := range f.Except {
if buildName == except {
if buildName == except || processedBuildName == except {
found = true
break
}
}

if found {
log.Printf("Skipping build '%s' because specified by -except.", buildName)
log.Printf("Skipping build '%s' because specified by -except.", processedBuildName)
continue
}
}

if len(f.Only) > 0 {
found := false
for _, only := range f.Only {
if buildName == only {
if buildName == only || processedBuildName == only {
found = true
break
}
}

if !found {
log.Printf("Skipping build '%s' because not specified by -only.", buildName)
log.Printf("Skipping build '%s' because not specified by -only.", processedBuildName)
continue
}
}

log.Printf("Creating build: %s", buildName)
log.Printf("Creating build: %s", processedBuildName)
build, err := t.Build(buildName, cf)
if err != nil {
return nil, fmt.Errorf("Failed to create build '%s': \n\n%s", buildName, err)
Expand Down
104 changes: 93 additions & 11 deletions common/command/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,23 @@ import (

func testTemplate() (*packer.Template, *packer.ComponentFinder) {
tplData := `{
"builders": [
{
"type": "foo"
},
{
"type": "bar"
}
]
}`

tpl, err := packer.ParseTemplate([]byte(tplData), nil)
"variables": {
"foo": null
},
"builders": [
{
"type": "foo"
},
{
"name": "{{user \"foo\"}}",
"type": "bar"
}
]
}
`

tpl, err := packer.ParseTemplate([]byte(tplData), map[string]string{"foo": "bar"})
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -59,6 +65,44 @@ func TestBuildOptionsBuilds_except(t *testing.T) {
}
}

//Test to make sure the build name pattern matches
func TestBuildOptionsBuilds_exceptConfigTemplateRaw(t *testing.T) {
opts := new(BuildOptions)
opts.Except = []string{"{{user \"foo\"}}"}

bs, err := opts.Builds(testTemplate())
if err != nil {
t.Fatalf("err: %s", err)
}

if len(bs) != 1 {
t.Fatalf("bad: %d", len(bs))
}

if bs[0].Name() != "foo" {
t.Fatalf("bad: %s", bs[0].Name())
}
}

//Test to make sure the processed build name matches
func TestBuildOptionsBuilds_exceptConfigTemplateProcessed(t *testing.T) {
opts := new(BuildOptions)
opts.Except = []string{"bar"}

bs, err := opts.Builds(testTemplate())
if err != nil {
t.Fatalf("err: %s", err)
}

if len(bs) != 1 {
t.Fatalf("bad: %d", len(bs))
}

if bs[0].Name() != "foo" {
t.Fatalf("bad: %s", bs[0].Name())
}
}

func TestBuildOptionsBuilds_only(t *testing.T) {
opts := new(BuildOptions)
opts.Only = []string{"foo"}
Expand All @@ -77,6 +121,44 @@ func TestBuildOptionsBuilds_only(t *testing.T) {
}
}

//Test to make sure the build name pattern matches
func TestBuildOptionsBuilds_onlyConfigTemplateRaw(t *testing.T) {
opts := new(BuildOptions)
opts.Only = []string{"{{user \"foo\"}}"}

bs, err := opts.Builds(testTemplate())
if err != nil {
t.Fatalf("err: %s", err)
}

if len(bs) != 1 {
t.Fatalf("bad: %d", len(bs))
}

if bs[0].Name() != "bar" {
t.Fatalf("bad: %s", bs[0].Name())
}
}

//Test to make sure the processed build name matches
func TestBuildOptionsBuilds_onlyConfigTemplateProcessed(t *testing.T) {
opts := new(BuildOptions)
opts.Only = []string{"bar"}

bs, err := opts.Builds(testTemplate())
if err != nil {
t.Fatalf("err: %s", err)
}

if len(bs) != 1 {
t.Fatalf("bad: %d", len(bs))
}

if bs[0].Name() != "bar" {
t.Fatalf("bad: %s", bs[0].Name())
}
}

func TestBuildOptionsBuilds_exceptNonExistent(t *testing.T) {
opts := new(BuildOptions)
opts.Except = []string{"i-dont-exist"}
Expand Down
100 changes: 57 additions & 43 deletions packer/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -475,52 +475,13 @@ func (t *Template) Build(name string, components *ComponentFinder) (b Build, err
return
}

// Prepare the variable template processor, which is a bit unique
// because we don't allow user variable usage and we add a function
// to read from the environment.
varTpl, err := NewConfigTemplate()
if err != nil {
return nil, err
}
varTpl.Funcs(template.FuncMap{
"env": templateEnv,
"user": templateDisableUser,
})

// Prepare the variables
var varErrors []error
variables := make(map[string]string)
for k, v := range t.Variables {
if v.Required && !v.HasValue {
varErrors = append(varErrors,
fmt.Errorf("Required user variable '%s' not set", k))
}

var val string
if v.HasValue {
val = v.Value
} else {
val, err = varTpl.Process(v.Default, nil)
if err != nil {
varErrors = append(varErrors,
fmt.Errorf("Error processing user variable '%s': %s'", k, err))
}
}

variables[k] = val
}

if len(varErrors) > 0 {
return nil, &MultiError{varErrors}
}

// Process the name
tpl, err := NewConfigTemplate()
tpl, variables, err := t.NewConfigTemplate()
if err != nil {
return nil, err
}
tpl.UserVars = variables

rawName := name
name, err = tpl.Process(name, nil)
if err != nil {
return nil, err
Expand Down Expand Up @@ -554,7 +515,7 @@ func (t *Template) Build(name string, components *ComponentFinder) (b Build, err
for _, rawPPs := range t.PostProcessors {
current := make([]coreBuildPostProcessor, 0, len(rawPPs))
for _, rawPP := range rawPPs {
if rawPP.TemplateOnlyExcept.Skip(name) {
if rawPP.TemplateOnlyExcept.Skip(rawName) {
continue
}

Expand Down Expand Up @@ -587,7 +548,7 @@ func (t *Template) Build(name string, components *ComponentFinder) (b Build, err
// Prepare the provisioners
provisioners := make([]coreBuildProvisioner, 0, len(t.Provisioners))
for _, rawProvisioner := range t.Provisioners {
if rawProvisioner.TemplateOnlyExcept.Skip(name) {
if rawProvisioner.TemplateOnlyExcept.Skip(rawName) {
continue
}

Expand Down Expand Up @@ -636,6 +597,59 @@ func (t *Template) Build(name string, components *ComponentFinder) (b Build, err
return
}

//Build a ConfigTemplate object populated by the values within a
//parsed template
func (t *Template) NewConfigTemplate() (c *ConfigTemplate, variables map[string]string, err error) {

// Prepare the variable template processor, which is a bit unique
// because we don't allow user variable usage and we add a function
// to read from the environment.
varTpl, err := NewConfigTemplate()
if err != nil {
return nil, nil, err
}
varTpl.Funcs(template.FuncMap{
"env": templateEnv,
"user": templateDisableUser,
})

// Prepare the variables
var varErrors []error
variables = make(map[string]string)
for k, v := range t.Variables {
if v.Required && !v.HasValue {
varErrors = append(varErrors,
fmt.Errorf("Required user variable '%s' not set", k))
}

var val string
if v.HasValue {
val = v.Value
} else {
val, err = varTpl.Process(v.Default, nil)
if err != nil {
varErrors = append(varErrors,
fmt.Errorf("Error processing user variable '%s': %s'", k, err))
}
}

variables[k] = val
}

if len(varErrors) > 0 {
return nil, variables, &MultiError{varErrors}
}

// Process the name
tpl, err := NewConfigTemplate()
if err != nil {
return nil, variables, err
}
tpl.UserVars = variables

return tpl, variables, nil
}

// TemplateOnlyExcept contains the logic required for "only" and "except"
// meta-parameters.
type TemplateOnlyExcept struct {
Expand Down
Loading

0 comments on commit 347f02a

Please sign in to comment.