diff --git a/packer/build_test.go b/packer/build_test.go index bb990e2fa..cbac0f208 100644 --- a/packer/build_test.go +++ b/packer/build_test.go @@ -16,7 +16,7 @@ func testBuild() *coreBuild { "foo": []Hook{&MockHook{}}, }, provisioners: []coreBuildProvisioner{ - coreBuildProvisioner{&TestProvisioner{}, []interface{}{42}}, + coreBuildProvisioner{&MockProvisioner{}, []interface{}{42}}, }, postProcessors: [][]coreBuildPostProcessor{ []coreBuildPostProcessor{ @@ -59,9 +59,9 @@ func TestBuild_Prepare(t *testing.T) { assert.Equal(builder.prepareConfig, []interface{}{42, packerConfig}, "prepare config should be 42") coreProv := build.provisioners[0] - prov := coreProv.provisioner.(*TestProvisioner) - assert.True(prov.prepCalled, "prepare should be called") - assert.Equal(prov.prepConfigs, []interface{}{42, packerConfig}, "prepare should be called with proper config") + prov := coreProv.provisioner.(*MockProvisioner) + assert.True(prov.PrepCalled, "prepare should be called") + assert.Equal(prov.PrepConfigs, []interface{}{42, packerConfig}, "prepare should be called with proper config") corePP := build.postProcessors[0][0] pp := corePP.processor.(*TestPostProcessor) @@ -104,9 +104,9 @@ func TestBuild_Prepare_Debug(t *testing.T) { assert.Equal(builder.prepareConfig, []interface{}{42, packerConfig}, "prepare config should be 42") coreProv := build.provisioners[0] - prov := coreProv.provisioner.(*TestProvisioner) - assert.True(prov.prepCalled, "prepare should be called") - assert.Equal(prov.prepConfigs, []interface{}{42, packerConfig}, "prepare should be called with proper config") + prov := coreProv.provisioner.(*MockProvisioner) + assert.True(prov.PrepCalled, "prepare should be called") + assert.Equal(prov.PrepConfigs, []interface{}{42, packerConfig}, "prepare should be called with proper config") } func TestBuildPrepare_variables_default(t *testing.T) { @@ -193,8 +193,8 @@ func TestBuild_Run(t *testing.T) { // Verify provisioners run dispatchHook.Run(HookProvision, nil, nil, 42) - prov := build.provisioners[0].provisioner.(*TestProvisioner) - assert.True(prov.provCalled, "provision should be called") + prov := build.provisioners[0].provisioner.(*MockProvisioner) + assert.True(prov.ProvCalled, "provision should be called") // Verify post-processor was run pp := build.postProcessors[0][0].processor.(*TestPostProcessor) diff --git a/packer/environment_test.go b/packer/environment_test.go index d357797b4..6cda3f7d2 100644 --- a/packer/environment_test.go +++ b/packer/environment_test.go @@ -20,7 +20,7 @@ func init() { func testComponentFinder() *ComponentFinder { builderFactory := func(n string) (Builder, error) { return testBuilder(), nil } ppFactory := func(n string) (PostProcessor, error) { return new(TestPostProcessor), nil } - provFactory := func(n string) (Provisioner, error) { return new(TestProvisioner), nil } + provFactory := func(n string) (Provisioner, error) { return new(MockProvisioner), nil } return &ComponentFinder{ Builder: builderFactory, PostProcessor: ppFactory, @@ -309,7 +309,7 @@ func TestEnvironment_PostProcessor_Error(t *testing.T) { func TestEnvironmentProvisioner(t *testing.T) { assert := asserts.NewTestingAsserts(t, true) - p := &TestProvisioner{} + p := &MockProvisioner{} ps := make(map[string]Provisioner) ps["foo"] = p diff --git a/packer/plugin/plugin_test.go b/packer/plugin/plugin_test.go index 7adaec5da..17018f82d 100644 --- a/packer/plugin/plugin_test.go +++ b/packer/plugin/plugin_test.go @@ -64,7 +64,7 @@ func TestHelperProcess(*testing.T) { case "post-processor": ServePostProcessor(new(helperPostProcessor)) case "provisioner": - ServeProvisioner(new(helperProvisioner)) + ServeProvisioner(new(packer.MockProvisioner)) case "start-timeout": time.Sleep(1 * time.Minute) os.Exit(1) diff --git a/packer/plugin/provisioner.go b/packer/plugin/provisioner.go index 7445c4165..0feb9d727 100644 --- a/packer/plugin/provisioner.go +++ b/packer/plugin/provisioner.go @@ -28,6 +28,15 @@ func (c *cmdProvisioner) Provision(ui packer.Ui, comm packer.Communicator) error return c.p.Provision(ui, comm) } +func (c *cmdProvisioner) Cancel() { + defer func() { + r := recover() + c.checkExit(r, nil) + }() + + c.p.Cancel() +} + func (c *cmdProvisioner) checkExit(p interface{}, cb func()) { if c.client.Exited() && cb != nil { cb() diff --git a/packer/plugin/provisioner_test.go b/packer/plugin/provisioner_test.go index 4a665411c..f0d7eb773 100644 --- a/packer/plugin/provisioner_test.go +++ b/packer/plugin/provisioner_test.go @@ -1,21 +1,10 @@ package plugin import ( - "github.com/mitchellh/packer/packer" "os/exec" "testing" ) -type helperProvisioner byte - -func (helperProvisioner) Prepare(...interface{}) error { - return nil -} - -func (helperProvisioner) Provision(packer.Ui, packer.Communicator) error { - return nil -} - func TestProvisioner_NoExist(t *testing.T) { c := NewClient(&ClientConfig{Cmd: exec.Command("i-should-not-exist")}) defer c.Kill() diff --git a/packer/provisioner.go b/packer/provisioner.go index bee9a0961..246795c35 100644 --- a/packer/provisioner.go +++ b/packer/provisioner.go @@ -13,6 +13,11 @@ type Provisioner interface { // is guaranteed to be connected to some machine so that provisioning // can be done. Provision(Ui, Communicator) error + + // Cancel is called to cancel the provisioning. This is usually called + // while Provision is still being called. The Provisioner should act + // to stop its execution as quickly as possible in a race-free way. + Cancel() } // A Hook implementation that runs the given provisioners. diff --git a/packer/provisioner_mock.go b/packer/provisioner_mock.go new file mode 100644 index 000000000..d05d8eda7 --- /dev/null +++ b/packer/provisioner_mock.go @@ -0,0 +1,27 @@ +package packer + +// MockProvisioner is an implementation of Provisioner that can be +// used for tests. +type MockProvisioner struct { + PrepCalled bool + PrepConfigs []interface{} + ProvCalled bool + ProvUi Ui + CancelCalled bool +} + +func (t *MockProvisioner) Prepare(configs ...interface{}) error { + t.PrepCalled = true + t.PrepConfigs = configs + return nil +} + +func (t *MockProvisioner) Provision(ui Ui, comm Communicator) error { + t.ProvCalled = true + t.ProvUi = ui + return nil +} + +func (t *MockProvisioner) Cancel() { + t.CancelCalled = true +} diff --git a/packer/provisioner_test.go b/packer/provisioner_test.go index 1db8b55bc..6107531e2 100644 --- a/packer/provisioner_test.go +++ b/packer/provisioner_test.go @@ -2,23 +2,6 @@ package packer import "testing" -type TestProvisioner struct { - prepCalled bool - prepConfigs []interface{} - provCalled bool -} - -func (t *TestProvisioner) Prepare(configs ...interface{}) error { - t.prepCalled = true - t.prepConfigs = configs - return nil -} - -func (t *TestProvisioner) Provision(Ui, Communicator) error { - t.provCalled = true - return nil -} - func TestProvisionHook_Impl(t *testing.T) { var raw interface{} raw = &ProvisionHook{} @@ -28,8 +11,8 @@ func TestProvisionHook_Impl(t *testing.T) { } func TestProvisionHook(t *testing.T) { - pA := &TestProvisioner{} - pB := &TestProvisioner{} + pA := &MockProvisioner{} + pB := &MockProvisioner{} ui := testUi() var comm Communicator = nil @@ -38,11 +21,11 @@ func TestProvisionHook(t *testing.T) { hook := &ProvisionHook{[]Provisioner{pA, pB}} hook.Run("foo", ui, comm, data) - if !pA.provCalled { + if !pA.ProvCalled { t.Error("provision should be called on pA") } - if !pB.provCalled { + if !pB.ProvCalled { t.Error("provision should be called on pB") } } diff --git a/packer/rpc/provisioner.go b/packer/rpc/provisioner.go index dbe366e56..7d3ed1617 100644 --- a/packer/rpc/provisioner.go +++ b/packer/rpc/provisioner.go @@ -2,6 +2,7 @@ package rpc import ( "github.com/mitchellh/packer/packer" + "log" "net/rpc" ) @@ -47,6 +48,13 @@ func (p *provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { return p.client.Call("Provisioner.Provision", args, new(interface{})) } +func (p *provisioner) Cancel() { + err := p.client.Call("Provisioner.Cancel", new(interface{}), new(interface{})) + if err != nil { + log.Printf("Provisioner.Cancel err: %s", err) + } +} + func (p *ProvisionerServer) Prepare(args *ProvisionerPrepareArgs, reply *error) error { *reply = p.p.Prepare(args.Configs...) if *reply != nil { @@ -71,3 +79,8 @@ func (p *ProvisionerServer) Provision(args *ProvisionerProvisionArgs, reply *int return nil } + +func (p *ProvisionerServer) Cancel(args *interface{}, reply *interface{}) error { + p.p.Cancel() + return nil +} diff --git a/packer/rpc/provisioner_test.go b/packer/rpc/provisioner_test.go index 106ae62eb..e251d814f 100644 --- a/packer/rpc/provisioner_test.go +++ b/packer/rpc/provisioner_test.go @@ -7,32 +7,11 @@ import ( "testing" ) -type testProvisioner struct { - prepareCalled bool - prepareConfigs []interface{} - provCalled bool - provComm packer.Communicator - provUi packer.Ui -} - -func (p *testProvisioner) Prepare(configs ...interface{}) error { - p.prepareCalled = true - p.prepareConfigs = configs - return nil -} - -func (p *testProvisioner) Provision(ui packer.Ui, comm packer.Communicator) error { - p.provCalled = true - p.provComm = comm - p.provUi = ui - return nil -} - func TestProvisionerRPC(t *testing.T) { assert := asserts.NewTestingAsserts(t, true) // Create the interface to test - p := new(testProvisioner) + p := new(packer.MockProvisioner) // Start the server server := rpc.NewServer() @@ -47,17 +26,23 @@ func TestProvisionerRPC(t *testing.T) { config := 42 pClient := Provisioner(client) pClient.Prepare(config) - assert.True(p.prepareCalled, "prepare should be called") - assert.Equal(p.prepareConfigs, []interface{}{42}, "prepare should be called with right arg") + assert.True(p.PrepCalled, "prepare should be called") + assert.Equal(p.PrepConfigs, []interface{}{42}, "prepare should be called with right arg") // Test Provision ui := &testUi{} - comm := new(packer.MockCommunicator) + comm := &packer.MockCommunicator{} pClient.Provision(ui, comm) - assert.True(p.provCalled, "provision should be called") + assert.True(p.ProvCalled, "provision should be called") - p.provUi.Say("foo") + p.ProvUi.Say("foo") assert.True(ui.sayCalled, "say should be called") + + // Test Cancel + pClient.Cancel() + if !p.CancelCalled { + t.Fatal("cancel should be called") + } } func TestProvisioner_Implements(t *testing.T) { diff --git a/packer/template_test.go b/packer/template_test.go index 398e48975..2e3fbc672 100644 --- a/packer/template_test.go +++ b/packer/template_test.go @@ -589,7 +589,7 @@ func TestTemplate_Build(t *testing.T) { "test-builder": builder, } - provisioner := &TestProvisioner{} + provisioner := &MockProvisioner{} provisionerMap := map[string]Provisioner{ "test-prov": provisioner, } @@ -677,7 +677,7 @@ func TestTemplate_Build_ProvisionerOverride(t *testing.T) { "test-builder": builder, } - provisioner := &TestProvisioner{} + provisioner := &MockProvisioner{} provisionerMap := map[string]Provisioner{ "test-prov": provisioner, }