packer: keep_input_artifact will keep prior artifact in a PP

[GH-19]
This commit is contained in:
Mitchell Hashimoto 2013-06-18 22:45:53 -07:00
parent 221281b714
commit dabe7ac584
7 changed files with 201 additions and 40 deletions

View File

@ -1,6 +1,9 @@
package packer package packer
type TestArtifact struct{} type TestArtifact struct {
id string
destroyCalled bool
}
func (*TestArtifact) BuilderId() string { func (*TestArtifact) BuilderId() string {
return "bid" return "bid"
@ -10,14 +13,20 @@ func (*TestArtifact) Files() []string {
return []string{"a", "b"} return []string{"a", "b"}
} }
func (*TestArtifact) Id() string { func (a *TestArtifact) Id() string {
return "id" id := a.id
if id == "" {
id = "id"
}
return id
} }
func (*TestArtifact) String() string { func (*TestArtifact) String() string {
return "string" return "string"
} }
func (*TestArtifact) Destroy() error { func (a *TestArtifact) Destroy() error {
a.destroyCalled = true
return nil return nil
} }

View File

@ -60,8 +60,9 @@ type coreBuild struct {
// Keeps track of the post-processor and the configuration of the // Keeps track of the post-processor and the configuration of the
// post-processor used within a build. // post-processor used within a build.
type coreBuildPostProcessor struct { type coreBuildPostProcessor struct {
processor PostProcessor processor PostProcessor
config interface{} config interface{}
keepInputArtifact bool
} }
// Keeps track of the provisioner and the configuration of the provisioner // Keeps track of the provisioner and the configuration of the provisioner
@ -153,21 +154,44 @@ func (b *coreBuild) Run(ui Ui, cache Cache) ([]Artifact, error) {
artifacts := make([]Artifact, 0, 1) artifacts := make([]Artifact, 0, 1)
builderArtifact, err := b.builder.Run(ui, hook, cache) builderArtifact, err := b.builder.Run(ui, hook, cache)
if builderArtifact != nil {
artifacts = append(artifacts, builderArtifact)
}
if err != nil { if err != nil {
return artifacts, err return artifacts, err
} }
errors := make([]error, 0) errors := make([]error, 0)
keepOriginalArtifact := len(b.postProcessors) == 0
// Run the post-processors // Run the post-processors
PostProcessorRunSeqLoop: PostProcessorRunSeqLoop:
for _, ppSeq := range b.postProcessors { for _, ppSeq := range b.postProcessors {
artifact := builderArtifact artifact := builderArtifact
for _, corePP := range ppSeq {
var priorArtifact Artifact
for i, corePP := range ppSeq {
if i == 0 {
// This is the first post-processor. We handle deleting
// previous artifacts a bit different because multiple
// post-processors may be using the original and need it.
if !keepOriginalArtifact {
keepOriginalArtifact = corePP.keepInputArtifact
}
} else {
if priorArtifact == nil {
errors = append(errors, fmt.Errorf("Post-processor returned nil artifact mid-chain."))
continue PostProcessorRunSeqLoop
}
// We have a prior artifact. If we want to keep it, we append
// it to the results list. Otherwise, we destroy it.
if corePP.keepInputArtifact {
artifacts = append(artifacts, priorArtifact)
} else {
if err := priorArtifact.Destroy(); err != nil {
errors = append(errors, fmt.Errorf("Failed cleaning up prior artifact: %s", err))
}
}
}
var err error var err error
artifact, err = corePP.processor.PostProcess(ui, artifact) artifact, err = corePP.processor.PostProcess(ui, artifact)
if err != nil { if err != nil {
@ -175,9 +199,23 @@ PostProcessorRunSeqLoop:
continue PostProcessorRunSeqLoop continue PostProcessorRunSeqLoop
} }
if artifact != nil { priorArtifact = artifact
artifacts = append(artifacts, artifact) }
}
// Add on the last artifact to the results
if priorArtifact != nil {
artifacts = append(artifacts, priorArtifact)
}
}
if keepOriginalArtifact {
artifacts = append(artifacts, nil)
copy(artifacts[1:], artifacts)
artifacts[0] = builderArtifact
} else {
log.Printf("Deleting original artifact for build '%s'", b.name)
if err := builderArtifact.Destroy(); err != nil {
errors = append(errors, fmt.Errorf("Error destroying builder artifact: %s", err))
} }
} }

View File

@ -2,13 +2,14 @@ package packer
import ( import (
"cgl.tideland.biz/asserts" "cgl.tideland.biz/asserts"
"reflect"
"testing" "testing"
) )
func testBuild() Build { func testBuild() *coreBuild {
return &coreBuild{ return &coreBuild{
name: "test", name: "test",
builder: &TestBuilder{}, builder: &TestBuilder{artifactId: "b"},
builderConfig: 42, builderConfig: 42,
hooks: map[string][]Hook{ hooks: map[string][]Hook{
"foo": []Hook{&TestHook{}}, "foo": []Hook{&TestHook{}},
@ -18,7 +19,7 @@ func testBuild() Build {
}, },
postProcessors: [][]coreBuildPostProcessor{ postProcessors: [][]coreBuildPostProcessor{
[]coreBuildPostProcessor{ []coreBuildPostProcessor{
coreBuildPostProcessor{&TestPostProcessor{}, 42}, coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp"}, 42, true},
}, },
}, },
} }
@ -41,19 +42,18 @@ func TestBuild_Prepare(t *testing.T) {
debugFalseConfig := map[string]interface{}{DebugConfigKey: false} debugFalseConfig := map[string]interface{}{DebugConfigKey: false}
build := testBuild() build := testBuild()
coreB := build.(*coreBuild) builder := build.builder.(*TestBuilder)
builder := coreB.builder.(*TestBuilder)
build.Prepare() build.Prepare()
assert.True(builder.prepareCalled, "prepare should be called") assert.True(builder.prepareCalled, "prepare should be called")
assert.Equal(builder.prepareConfig, []interface{}{42, debugFalseConfig}, "prepare config should be 42") assert.Equal(builder.prepareConfig, []interface{}{42, debugFalseConfig}, "prepare config should be 42")
coreProv := coreB.provisioners[0] coreProv := build.provisioners[0]
prov := coreProv.provisioner.(*TestProvisioner) prov := coreProv.provisioner.(*TestProvisioner)
assert.True(prov.prepCalled, "prepare should be called") assert.True(prov.prepCalled, "prepare should be called")
assert.Equal(prov.prepConfigs, []interface{}{42, debugFalseConfig}, "prepare should be called with proper config") assert.Equal(prov.prepConfigs, []interface{}{42, debugFalseConfig}, "prepare should be called with proper config")
corePP := coreB.postProcessors[0][0] corePP := build.postProcessors[0][0]
pp := corePP.processor.(*TestPostProcessor) pp := corePP.processor.(*TestPostProcessor)
assert.True(pp.configCalled, "config should be called") assert.True(pp.configCalled, "config should be called")
assert.Equal(pp.configVal, 42, "config should have right value") assert.Equal(pp.configVal, 42, "config should have right value")
@ -85,15 +85,14 @@ func TestBuild_Prepare_Debug(t *testing.T) {
debugConfig := map[string]interface{}{DebugConfigKey: true} debugConfig := map[string]interface{}{DebugConfigKey: true}
build := testBuild() build := testBuild()
coreB := build.(*coreBuild) builder := build.builder.(*TestBuilder)
builder := coreB.builder.(*TestBuilder)
build.SetDebug(true) build.SetDebug(true)
build.Prepare() build.Prepare()
assert.True(builder.prepareCalled, "prepare should be called") assert.True(builder.prepareCalled, "prepare should be called")
assert.Equal(builder.prepareConfig, []interface{}{42, debugConfig}, "prepare config should be 42") assert.Equal(builder.prepareConfig, []interface{}{42, debugConfig}, "prepare config should be 42")
coreProv := coreB.provisioners[0] coreProv := build.provisioners[0]
prov := coreProv.provisioner.(*TestProvisioner) prov := coreProv.provisioner.(*TestProvisioner)
assert.True(prov.prepCalled, "prepare should be called") assert.True(prov.prepCalled, "prepare should be called")
assert.Equal(prov.prepConfigs, []interface{}{42, debugConfig}, "prepare should be called with proper config") assert.Equal(prov.prepConfigs, []interface{}{42, debugConfig}, "prepare should be called with proper config")
@ -111,10 +110,8 @@ func TestBuild_Run(t *testing.T) {
assert.Nil(err, "should not error") assert.Nil(err, "should not error")
assert.Equal(len(artifacts), 2, "should have two artifacts") assert.Equal(len(artifacts), 2, "should have two artifacts")
coreB := build.(*coreBuild)
// Verify builder was run // Verify builder was run
builder := coreB.builder.(*TestBuilder) builder := build.builder.(*TestBuilder)
assert.True(builder.runCalled, "run should be called") assert.True(builder.runCalled, "run should be called")
assert.Equal(builder.runUi, ui, "run should be called with ui") assert.Equal(builder.runUi, ui, "run should be called with ui")
@ -122,20 +119,129 @@ func TestBuild_Run(t *testing.T) {
dispatchHook := builder.runHook dispatchHook := builder.runHook
dispatchHook.Run("foo", nil, nil, 42) dispatchHook.Run("foo", nil, nil, 42)
hook := coreB.hooks["foo"][0].(*TestHook) hook := build.hooks["foo"][0].(*TestHook)
assert.True(hook.runCalled, "run should be called") assert.True(hook.runCalled, "run should be called")
assert.Equal(hook.runData, 42, "should have correct data") assert.Equal(hook.runData, 42, "should have correct data")
// Verify provisioners run // Verify provisioners run
dispatchHook.Run(HookProvision, nil, nil, 42) dispatchHook.Run(HookProvision, nil, nil, 42)
prov := coreB.provisioners[0].provisioner.(*TestProvisioner) prov := build.provisioners[0].provisioner.(*TestProvisioner)
assert.True(prov.provCalled, "provision should be called") assert.True(prov.provCalled, "provision should be called")
// Verify post-processor was run // Verify post-processor was run
pp := coreB.postProcessors[0][0].processor.(*TestPostProcessor) pp := build.postProcessors[0][0].processor.(*TestPostProcessor)
assert.True(pp.ppCalled, "post processor should be called") assert.True(pp.ppCalled, "post processor should be called")
} }
func TestBuild_Run_Artifacts(t *testing.T) {
cache := &TestCache{}
ui := testUi()
// Test case: Test that with no post-processors, we only get the
// main build.
build := testBuild()
build.postProcessors = [][]coreBuildPostProcessor{}
build.Prepare()
artifacts, err := build.Run(ui, cache)
if err != nil {
t.Fatalf("err: %s", err)
}
expectedIds := []string{"b"}
artifactIds := make([]string, len(artifacts))
for i, artifact := range artifacts {
artifactIds[i] = artifact.Id()
}
if !reflect.DeepEqual(artifactIds, expectedIds) {
t.Fatalf("unexpected ids: %#v", artifactIds)
}
// Test case: Test that with a single post-processor that doesn't keep
// inputs, only that post-processors results are returned.
build = testBuild()
build.postProcessors = [][]coreBuildPostProcessor{
[]coreBuildPostProcessor{
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp"}, 42, false},
},
}
build.Prepare()
artifacts, err = build.Run(ui, cache)
if err != nil {
t.Fatalf("err: %s", err)
}
expectedIds = []string{"pp"}
artifactIds = make([]string, len(artifacts))
for i, artifact := range artifacts {
artifactIds[i] = artifact.Id()
}
if !reflect.DeepEqual(artifactIds, expectedIds) {
t.Fatalf("unexpected ids: %#v", artifactIds)
}
// Test case: Test that with multiple post-processors, as long as one
// keeps the original, the original is kept.
build = testBuild()
build.postProcessors = [][]coreBuildPostProcessor{
[]coreBuildPostProcessor{
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp1"}, 42, false},
},
[]coreBuildPostProcessor{
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp2"}, 42, true},
},
}
build.Prepare()
artifacts, err = build.Run(ui, cache)
if err != nil {
t.Fatalf("err: %s", err)
}
expectedIds = []string{"b", "pp1", "pp2"}
artifactIds = make([]string, len(artifacts))
for i, artifact := range artifacts {
artifactIds[i] = artifact.Id()
}
if !reflect.DeepEqual(artifactIds, expectedIds) {
t.Fatalf("unexpected ids: %#v", artifactIds)
}
// Test case: Test that with sequences, intermediaries are kept if they
// want to be.
build = testBuild()
build.postProcessors = [][]coreBuildPostProcessor{
[]coreBuildPostProcessor{
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp1a"}, 42, false},
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp1b"}, 42, true},
},
[]coreBuildPostProcessor{
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp2a"}, 42, false},
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp2b"}, 42, false},
},
}
build.Prepare()
artifacts, err = build.Run(ui, cache)
if err != nil {
t.Fatalf("err: %s", err)
}
expectedIds = []string{"pp1a", "pp1b", "pp2b"}
artifactIds = make([]string, len(artifacts))
for i, artifact := range artifacts {
artifactIds[i] = artifact.Id()
}
if !reflect.DeepEqual(artifactIds, expectedIds) {
t.Fatalf("unexpected ids: %#v", artifactIds)
}
}
func TestBuild_RunBeforePrepare(t *testing.T) { func TestBuild_RunBeforePrepare(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true) assert := asserts.NewTestingAsserts(t, true)
@ -154,8 +260,6 @@ func TestBuild_Cancel(t *testing.T) {
build := testBuild() build := testBuild()
build.Cancel() build.Cancel()
coreB := build.(*coreBuild) builder := build.builder.(*TestBuilder)
builder := coreB.builder.(*TestBuilder)
assert.True(builder.cancelCalled, "cancel should be called") assert.True(builder.cancelCalled, "cancel should be called")
} }

View File

@ -1,6 +1,8 @@
package packer package packer
type TestBuilder struct { type TestBuilder struct {
artifactId string
prepareCalled bool prepareCalled bool
prepareConfig []interface{} prepareConfig []interface{}
runCalled bool runCalled bool
@ -21,7 +23,7 @@ func (tb *TestBuilder) Run(ui Ui, h Hook, c Cache) (Artifact, error) {
tb.runHook = h tb.runHook = h
tb.runUi = ui tb.runUi = ui
tb.runCache = c tb.runCache = c
return new(TestArtifact), nil return &TestArtifact{id: tb.artifactId}, nil
} }
func (tb *TestBuilder) Cancel() { func (tb *TestBuilder) Cancel() {

View File

@ -1,6 +1,7 @@
package packer package packer
type TestPostProcessor struct { type TestPostProcessor struct {
artifactId string
configCalled bool configCalled bool
configVal interface{} configVal interface{}
ppCalled bool ppCalled bool
@ -18,5 +19,5 @@ func (pp *TestPostProcessor) PostProcess(ui Ui, a Artifact) (Artifact, error) {
pp.ppCalled = true pp.ppCalled = true
pp.ppArtifact = a pp.ppArtifact = a
pp.ppUi = ui pp.ppUi = ui
return new(TestArtifact), nil return &TestArtifact{id: pp.artifactId}, nil
} }

View File

@ -41,8 +41,9 @@ type rawBuilderConfig struct {
// configuration. It contains the type of the post processor as well as the // 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. // raw configuration that is handed to the post-processor for it to process.
type rawPostProcessorConfig struct { type rawPostProcessorConfig struct {
Type string Type string
rawConfig interface{} KeepInputArtifact bool `mapstructure:"keep_input_artifact"`
rawConfig interface{}
} }
// rawProvisionerConfig represents a raw, unprocessed provisioner configuration. // rawProvisionerConfig represents a raw, unprocessed provisioner configuration.
@ -300,8 +301,9 @@ func (t *Template) Build(name string, components *ComponentFinder) (b Build, err
} }
current[i] = coreBuildPostProcessor{ current[i] = coreBuildPostProcessor{
processor: pp, processor: pp,
config: rawPP.rawConfig, config: rawPP.rawConfig,
keepInputArtifact: rawPP.KeepInputArtifact,
} }
} }

View File

@ -447,7 +447,10 @@ func TestTemplate_Build(t *testing.T) {
"post-processors": [ "post-processors": [
"simple", "simple",
["simple", "simple"] [
"simple",
{ "type": "simple", "keep_input_artifact": true }
]
] ]
} }
` `
@ -497,6 +500,8 @@ func TestTemplate_Build(t *testing.T) {
assert.Equal(len(coreBuild.postProcessors), 2, "should have pps") assert.Equal(len(coreBuild.postProcessors), 2, "should have pps")
assert.Equal(len(coreBuild.postProcessors[0]), 1, "should have correct number") assert.Equal(len(coreBuild.postProcessors[0]), 1, "should have correct number")
assert.Equal(len(coreBuild.postProcessors[1]), 2, "should have correct number") assert.Equal(len(coreBuild.postProcessors[1]), 2, "should have correct number")
assert.False(coreBuild.postProcessors[1][0].keepInputArtifact, "shoule be correct")
assert.True(coreBuild.postProcessors[1][1].keepInputArtifact, "shoule be correct")
} }
func TestTemplate_Build_ProvisionerOverride(t *testing.T) { func TestTemplate_Build_ProvisionerOverride(t *testing.T) {