From 56ec6bf7af982de26cd9daadb522942e6b8b8e89 Mon Sep 17 00:00:00 2001 From: "Jason A. Beranek" Date: Sat, 9 Aug 2014 21:52:33 -0500 Subject: [PATCH] common/command/template,packer/template: fix build name ConfigTemplate processing [GH-858] --- common/command/template.go | 28 +++- common/command/template_test.go | 104 +++++++++++++-- packer/template.go | 100 ++++++++------ packer/template_test.go | 228 +++++++++++++++++++++++++++++++- 4 files changed, 396 insertions(+), 64 deletions(-) diff --git a/common/command/template.go b/common/command/template.go index 7496883ff29..27a42f90191 100644 --- a/common/command/template.go +++ b/common/command/template.go @@ -66,6 +66,12 @@ 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 @@ -73,7 +79,12 @@ func (f *BuildOptions) Builds(t *packer.Template, cf *packer.ComponentFinder) ([ 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 } @@ -88,17 +99,22 @@ 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 } } @@ -106,19 +122,19 @@ func (f *BuildOptions) Builds(t *packer.Template, cf *packer.ComponentFinder) ([ 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) diff --git a/common/command/template_test.go b/common/command/template_test.go index 7ece7acaea8..419ee70126d 100644 --- a/common/command/template_test.go +++ b/common/command/template_test.go @@ -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) } @@ -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"} @@ -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"} diff --git a/packer/template.go b/packer/template.go index ed71e90d679..61040f51b4c 100644 --- a/packer/template.go +++ b/packer/template.go @@ -473,52 +473,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 @@ -552,7 +513,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 } @@ -585,7 +546,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 } @@ -634,6 +595,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 { diff --git a/packer/template_test.go b/packer/template_test.go index 547b29dafe7..6feff138a8f 100644 --- a/packer/template_test.go +++ b/packer/template_test.go @@ -1165,7 +1165,62 @@ func TestTemplateBuild_exceptPP(t *testing.T) { t.Fatal("should have no postProcessors") } - // Verify test2 has no post-processors + // Verify test2 has one post-processors + build, err = template.Build("test2", testTemplateComponentFinder()) + if err != nil { + t.Fatalf("err: %s", err) + } + + cbuild = build.(*coreBuild) + if len(cbuild.postProcessors) != 1 { + t.Fatalf("invalid: %d", len(cbuild.postProcessors)) + } +} + +func TestTemplateBuild_exceptPPConfigTemplateName(t *testing.T) { + data := ` + { + "variables": { + "foo": null + }, + + "builders": [ + { + "name": "test1-{{user \"foo\"}}", + "type": "test-builder" + }, + { + "name": "test2", + "type": "test-builder" + } + ], + + "post-processors": [ + { + "type": "test-pp", + "except": ["test1-{{user \"foo\"}}"] + } + ] + } + ` + + template, err := ParseTemplate([]byte(data), map[string]string{"foo": "bar"}) + if err != nil { + t.Fatalf("err: %s", err) + } + + // Verify test1 has no post-processors + build, err := template.Build("test1-{{user \"foo\"}}", testTemplateComponentFinder()) + if err != nil { + t.Fatalf("err: %s", err) + } + + cbuild := build.(*coreBuild) + if len(cbuild.postProcessors) > 0 { + t.Fatal("should have no postProcessors") + } + + // Verify test2 has one post-processors build, err = template.Build("test2", testTemplateComponentFinder()) if err != nil { t.Fatalf("err: %s", err) @@ -1245,7 +1300,62 @@ func TestTemplateBuild_exceptProv(t *testing.T) { t.Fatal("should have no provisioners") } - // Verify test2 has no provisioners + // Verify test2 has one provisioners + build, err = template.Build("test2", testTemplateComponentFinder()) + if err != nil { + t.Fatalf("err: %s", err) + } + + cbuild = build.(*coreBuild) + if len(cbuild.provisioners) != 1 { + t.Fatalf("invalid: %d", len(cbuild.provisioners)) + } +} + +func TestTemplateBuild_exceptProvConfigTemplateName(t *testing.T) { + data := ` + { + "variables": { + "foo": null + }, + + "builders": [ + { + "name": "test1-{{user \"foo\"}}", + "type": "test-builder" + }, + { + "name": "test2", + "type": "test-builder" + } + ], + + "provisioners": [ + { + "type": "test-prov", + "except": ["test1-{{user \"foo\"}}"] + } + ] + } + ` + + template, err := ParseTemplate([]byte(data), map[string]string{"foo": "bar"}) + if err != nil { + t.Fatalf("err: %s", err) + } + + // Verify test1 has no provisioners + build, err := template.Build("test1-{{user \"foo\"}}", testTemplateComponentFinder()) + if err != nil { + t.Fatalf("err: %s", err) + } + + cbuild := build.(*coreBuild) + if len(cbuild.provisioners) > 0 { + t.Fatal("should have no provisioners") + } + + // Verify test2 has one provisioners build, err = template.Build("test2", testTemplateComponentFinder()) if err != nil { t.Fatalf("err: %s", err) @@ -1325,7 +1435,7 @@ func TestTemplateBuild_onlyPP(t *testing.T) { t.Fatal("should have no postProcessors") } - // Verify test2 has no post-processors + // Verify test2 has one post-processors build, err = template.Build("test2", testTemplateComponentFinder()) if err != nil { t.Fatalf("err: %s", err) @@ -1337,6 +1447,61 @@ func TestTemplateBuild_onlyPP(t *testing.T) { } } +func TestTemplateBuild_onlyPPConfigTemplateName(t *testing.T) { + data := ` + { + "variables": { + "foo": null + }, + + "builders": [ + { + "name": "test1", + "type": "test-builder" + }, + { + "name": "test2-{{user \"foo\"}}", + "type": "test-builder" + } + ], + + "post-processors": [ + { + "type": "test-pp", + "only": ["test2-{{user \"foo\"}}"] + } + ] + } + ` + + template, err := ParseTemplate([]byte(data), map[string]string{"foo": "bar"}) + if err != nil { + t.Fatalf("err: %s", err) + } + + // Verify test1 has no post-processors + build, err := template.Build("test1", testTemplateComponentFinder()) + if err != nil { + t.Fatalf("err: %s", err) + } + + cbuild := build.(*coreBuild) + if len(cbuild.postProcessors) > 0 { + t.Fatal("should have no postProcessors") + } + + // Verify test2 has one post-processors + build, err = template.Build("test2-{{user \"foo\"}}", testTemplateComponentFinder()) + if err != nil { + t.Fatalf("err: %s", err) + } + + cbuild = build.(*coreBuild) + if len(cbuild.postProcessors) != 1 { + t.Fatalf("invalid: %d", len(cbuild.postProcessors)) + } +} + func TestTemplateBuild_onlyProvInvalid(t *testing.T) { data := ` { @@ -1405,7 +1570,7 @@ func TestTemplateBuild_onlyProv(t *testing.T) { t.Fatal("should have no provisioners") } - // Verify test2 has no provisioners + // Verify test2 has one provisioners build, err = template.Build("test2", testTemplateComponentFinder()) if err != nil { t.Fatalf("err: %s", err) @@ -1417,6 +1582,61 @@ func TestTemplateBuild_onlyProv(t *testing.T) { } } +func TestTemplateBuild_onlyProvConfigTemplateName(t *testing.T) { + data := ` + { + "variables": { + "foo": null + }, + + "builders": [ + { + "name": "test1", + "type": "test-builder" + }, + { + "name": "test2-{{user \"foo\"}}", + "type": "test-builder" + } + ], + + "provisioners": [ + { + "type": "test-prov", + "only": ["test2-{{user \"foo\"}}"] + } + ] + } + ` + + template, err := ParseTemplate([]byte(data), map[string]string{"foo": "bar"}) + if err != nil { + t.Fatalf("err: %s", err) + } + + // Verify test1 has no provisioners + build, err := template.Build("test1", testTemplateComponentFinder()) + if err != nil { + t.Fatalf("err: %s", err) + } + + cbuild := build.(*coreBuild) + if len(cbuild.provisioners) > 0 { + t.Fatal("should have no provisioners") + } + + // Verify test2 has one provisioners + build, err = template.Build("test2-{{user \"foo\"}}", testTemplateComponentFinder()) + if err != nil { + t.Fatalf("err: %s", err) + } + + cbuild = build.(*coreBuild) + if len(cbuild.provisioners) != 1 { + t.Fatalf("invalid: %d", len(cbuild.provisioners)) + } +} + func TestTemplate_Build_ProvisionerOverride(t *testing.T) { data := ` {