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
type TestArtifact struct{}
type TestArtifact struct {
id string
destroyCalled bool
}
func (*TestArtifact) BuilderId() string {
return "bid"
@ -10,14 +13,20 @@ func (*TestArtifact) Files() []string {
return []string{"a", "b"}
}
func (*TestArtifact) Id() string {
return "id"
func (a *TestArtifact) Id() string {
id := a.id
if id == "" {
id = "id"
}
return id
}
func (*TestArtifact) String() string {
return "string"
}
func (*TestArtifact) Destroy() error {
func (a *TestArtifact) Destroy() error {
a.destroyCalled = true
return nil
}

View File

@ -62,6 +62,7 @@ type coreBuild struct {
type coreBuildPostProcessor struct {
processor PostProcessor
config interface{}
keepInputArtifact bool
}
// 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)
builderArtifact, err := b.builder.Run(ui, hook, cache)
if builderArtifact != nil {
artifacts = append(artifacts, builderArtifact)
}
if err != nil {
return artifacts, err
}
errors := make([]error, 0)
keepOriginalArtifact := len(b.postProcessors) == 0
// Run the post-processors
PostProcessorRunSeqLoop:
for _, ppSeq := range b.postProcessors {
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
artifact, err = corePP.processor.PostProcess(ui, artifact)
if err != nil {
@ -175,9 +199,23 @@ PostProcessorRunSeqLoop:
continue PostProcessorRunSeqLoop
}
if artifact != nil {
artifacts = append(artifacts, artifact)
priorArtifact = 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 (
"cgl.tideland.biz/asserts"
"reflect"
"testing"
)
func testBuild() Build {
func testBuild() *coreBuild {
return &coreBuild{
name: "test",
builder: &TestBuilder{},
builder: &TestBuilder{artifactId: "b"},
builderConfig: 42,
hooks: map[string][]Hook{
"foo": []Hook{&TestHook{}},
@ -18,7 +19,7 @@ func testBuild() Build {
},
postProcessors: [][]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}
build := testBuild()
coreB := build.(*coreBuild)
builder := coreB.builder.(*TestBuilder)
builder := build.builder.(*TestBuilder)
build.Prepare()
assert.True(builder.prepareCalled, "prepare should be called")
assert.Equal(builder.prepareConfig, []interface{}{42, debugFalseConfig}, "prepare config should be 42")
coreProv := coreB.provisioners[0]
coreProv := build.provisioners[0]
prov := coreProv.provisioner.(*TestProvisioner)
assert.True(prov.prepCalled, "prepare should be called")
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)
assert.True(pp.configCalled, "config should be called")
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}
build := testBuild()
coreB := build.(*coreBuild)
builder := coreB.builder.(*TestBuilder)
builder := build.builder.(*TestBuilder)
build.SetDebug(true)
build.Prepare()
assert.True(builder.prepareCalled, "prepare should be called")
assert.Equal(builder.prepareConfig, []interface{}{42, debugConfig}, "prepare config should be 42")
coreProv := coreB.provisioners[0]
coreProv := build.provisioners[0]
prov := coreProv.provisioner.(*TestProvisioner)
assert.True(prov.prepCalled, "prepare should be called")
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.Equal(len(artifacts), 2, "should have two artifacts")
coreB := build.(*coreBuild)
// Verify builder was run
builder := coreB.builder.(*TestBuilder)
builder := build.builder.(*TestBuilder)
assert.True(builder.runCalled, "run should be called")
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.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.Equal(hook.runData, 42, "should have correct data")
// Verify provisioners run
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")
// 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")
}
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) {
assert := asserts.NewTestingAsserts(t, true)
@ -154,8 +260,6 @@ func TestBuild_Cancel(t *testing.T) {
build := testBuild()
build.Cancel()
coreB := build.(*coreBuild)
builder := coreB.builder.(*TestBuilder)
builder := build.builder.(*TestBuilder)
assert.True(builder.cancelCalled, "cancel should be called")
}

View File

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

View File

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

View File

@ -42,6 +42,7 @@ type rawBuilderConfig struct {
// raw configuration that is handed to the post-processor for it to process.
type rawPostProcessorConfig struct {
Type string
KeepInputArtifact bool `mapstructure:"keep_input_artifact"`
rawConfig interface{}
}
@ -302,6 +303,7 @@ func (t *Template) Build(name string, components *ComponentFinder) (b Build, err
current[i] = coreBuildPostProcessor{
processor: pp,
config: rawPP.rawConfig,
keepInputArtifact: rawPP.KeepInputArtifact,
}
}

View File

@ -447,7 +447,10 @@ func TestTemplate_Build(t *testing.T) {
"post-processors": [
"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[0]), 1, "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) {