From d5555d45cc55d79e70faab1c363f51cdc7e207f8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 20 Sep 2013 11:13:43 -0700 Subject: [PATCH] packer: `only` metaparameter for post-processors [GH-438] --- packer/template.go | 129 +++++++++++++++++++++++++++++----------- packer/template_test.go | 91 +++++++++++++++++++++++++++- 2 files changed, 184 insertions(+), 36 deletions(-) diff --git a/packer/template.go b/packer/template.go index 12d47e5a8..7e27bf93d 100644 --- a/packer/template.go +++ b/packer/template.go @@ -48,6 +48,8 @@ type RawBuilderConfig struct { // configuration. It contains the type of the post processor as well as the // raw configuration that is handed to the post-processor for it to process. type RawPostProcessorConfig struct { + TemplateOnlyExcept `mapstructure:",squash"` + Type string KeepInputArtifact bool `mapstructure:"keep_input_artifact"` RawConfig map[string]interface{} @@ -57,9 +59,10 @@ type RawPostProcessorConfig struct { // It contains the type of the provisioner as well as the raw configuration // that is handed to the provisioner for it to process. type RawProvisionerConfig struct { + TemplateOnlyExcept `mapstructure:",squash"` + Type string Override map[string]interface{} - Only []string RawConfig interface{} } @@ -190,32 +193,50 @@ func ParseTemplate(data []byte) (t *Template, err error) { continue } - t.PostProcessors[i] = make([]RawPostProcessorConfig, len(rawPP)) - configs := t.PostProcessors[i] + configs := make([]RawPostProcessorConfig, 0, len(rawPP)) for j, pp := range rawPP { - config := &configs[j] - if err := mapstructure.Decode(pp, config); err != nil { + var config RawPostProcessorConfig + if err := mapstructure.Decode(pp, &config); err != nil { if merr, ok := err.(*mapstructure.Error); ok { for _, err := range merr.Errors { - errors = append(errors, fmt.Errorf("Post-processor #%d.%d: %s", i+1, j+1, err)) + errors = append(errors, + fmt.Errorf("Post-processor #%d.%d: %s", i+1, j+1, err)) } } else { - errors = append(errors, fmt.Errorf("Post-processor %d.%d: %s", i+1, j+1, err)) + errors = append(errors, + fmt.Errorf("Post-processor %d.%d: %s", i+1, j+1, err)) } continue } if config.Type == "" { - errors = append(errors, fmt.Errorf("Post-processor %d.%d: missing 'type'", i+1, j+1)) + errors = append(errors, + fmt.Errorf("Post-processor %d.%d: missing 'type'", i+1, j+1)) continue } // Remove the input keep_input_artifact option + config.TemplateOnlyExcept.Prune(pp) delete(pp, "keep_input_artifact") + // Verify that the only settings are good + if errs := config.TemplateOnlyExcept.Validate(t.Builders); len(errs) > 0 { + for _, err := range errs { + errors = append(errors, + fmt.Errorf("Post-processor %d.%d: %s", i+1, j+1, err)) + } + + continue + } + config.RawConfig = pp + + // Add it to the list of configs + configs = append(configs, config) } + + t.PostProcessors[i] = configs } // Gather all the provisioners @@ -239,7 +260,7 @@ func ParseTemplate(data []byte) (t *Template, err error) { } // Delete the keys that we used - delete(v, "only") + raw.TemplateOnlyExcept.Prune(v) delete(v, "override") // Verify that the override keys exist... @@ -251,14 +272,8 @@ func ParseTemplate(data []byte) (t *Template, err error) { } // Verify that the only settings are good - if len(raw.Only) > 0 { - for _, n := range raw.Only { - if _, ok := t.Builders[n]; !ok { - errors = append(errors, - fmt.Errorf("provisioner %d: 'only' specified builder '%s' not found", - i+1, n)) - } - } + if errs := raw.TemplateOnlyExcept.Validate(t.Builders); len(errs) > 0 { + errors = append(errors, errs...) } raw.RawConfig = v @@ -411,8 +426,12 @@ func (t *Template) Build(name string, components *ComponentFinder) (b Build, err // Prepare the post-processors postProcessors := make([][]coreBuildPostProcessor, 0, len(t.PostProcessors)) for _, rawPPs := range t.PostProcessors { - current := make([]coreBuildPostProcessor, len(rawPPs)) - for i, rawPP := range rawPPs { + current := make([]coreBuildPostProcessor, 0, len(rawPPs)) + for _, rawPP := range rawPPs { + if rawPP.TemplateOnlyExcept.Skip(name) { + continue + } + pp, err := components.PostProcessor(rawPP.Type) if err != nil { return nil, err @@ -422,12 +441,18 @@ func (t *Template) Build(name string, components *ComponentFinder) (b Build, err return nil, fmt.Errorf("PostProcessor type not found: %s", rawPP.Type) } - current[i] = coreBuildPostProcessor{ + current = append(current, coreBuildPostProcessor{ processor: pp, processorType: rawPP.Type, config: rawPP.RawConfig, keepInputArtifact: rawPP.KeepInputArtifact, - } + }) + } + + // If we have no post-processors in this chain, just continue. + // This can happen if the post-processors skip certain builds. + if len(current) == 0 { + continue } postProcessors = append(postProcessors, current) @@ -436,19 +461,8 @@ 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 len(rawProvisioner.Only) > 0 { - onlyFound := false - for _, n := range rawProvisioner.Only { - if n == name { - onlyFound = true - break - } - } - - if !onlyFound { - // Skip this provisioner - continue - } + if rawProvisioner.TemplateOnlyExcept.Skip(name) { + continue } var provisioner Provisioner @@ -497,3 +511,50 @@ func (t *Template) Build(name string, components *ComponentFinder) (b Build, err return } + +// TemplateOnlyExcept contains the logic required for "only" and "except" +// meta-parameters. +type TemplateOnlyExcept struct { + Only []string + Except []string +} + +// Prune will prune out the used values from the raw map. +func (t *TemplateOnlyExcept) Prune(raw map[string]interface{}) { + delete(raw, "except") + delete(raw, "only") +} + +// Skip tests if we should skip putting this item onto a build. +func (t *TemplateOnlyExcept) Skip(name string) bool { + if len(t.Only) > 0 { + onlyFound := false + for _, n := range t.Only { + if n == name { + onlyFound = true + break + } + } + + if !onlyFound { + // Skip this provisioner + return true + } + } + + return false +} + +// Validates the only/except parameters. +func (t *TemplateOnlyExcept) Validate(b map[string]RawBuilderConfig) (e []error) { + if len(t.Only) > 0 { + for _, n := range t.Only { + if _, ok := b[n]; !ok { + e = append(e, + fmt.Errorf("'only' specified builder '%s' not found", n)) + } + } + } + + return +} diff --git a/packer/template_test.go b/packer/template_test.go index c8fb6b4ae..c5c0cf146 100644 --- a/packer/template_test.go +++ b/packer/template_test.go @@ -11,21 +11,28 @@ import ( func testTemplateComponentFinder() *ComponentFinder { builder := testBuilder() + pp := new(TestPostProcessor) provisioner := &MockProvisioner{} builderMap := map[string]Builder{ "test-builder": builder, } + ppMap := map[string]PostProcessor{ + "test-pp": pp, + } + provisionerMap := map[string]Provisioner{ "test-prov": provisioner, } builderFactory := func(n string) (Builder, error) { return builderMap[n], nil } + ppFactory := func(n string) (PostProcessor, error) { return ppMap[n], nil } provFactory := func(n string) (Provisioner, error) { return provisionerMap[n], nil } return &ComponentFinder{ - Builder: builderFactory, - Provisioner: provFactory, + Builder: builderFactory, + PostProcessor: ppFactory, + Provisioner: provFactory, } } @@ -683,6 +690,86 @@ func TestTemplate_Build(t *testing.T) { } } +func TestTemplateBuild_onlyPPInvalid(t *testing.T) { + data := ` + { + "builders": [ + { + "name": "test1", + "type": "test-builder" + }, + { + "name": "test2", + "type": "test-builder" + } + ], + + "post-processors": [ + { + "type": "test-pp", + "only": "test5" + } + ] + } + ` + + _, err := ParseTemplate([]byte(data)) + if err == nil { + t.Fatal("should have error") + } +} + +func TestTemplateBuild_onlyPP(t *testing.T) { + data := ` + { + "builders": [ + { + "name": "test1", + "type": "test-builder" + }, + { + "name": "test2", + "type": "test-builder" + } + ], + + "post-processors": [ + { + "type": "test-pp", + "only": ["test2"] + } + ] + } + ` + + template, err := ParseTemplate([]byte(data)) + 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 no 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_onlyProvInvalid(t *testing.T) { data := ` {