diff --git a/packer/build_test.go b/packer/build_test.go index c5eedbc81..bb990e2fa 100644 --- a/packer/build_test.go +++ b/packer/build_test.go @@ -13,7 +13,7 @@ func testBuild() *coreBuild { builderConfig: 42, builderType: "foo", hooks: map[string][]Hook{ - "foo": []Hook{&TestHook{}}, + "foo": []Hook{&MockHook{}}, }, provisioners: []coreBuildProvisioner{ coreBuildProvisioner{&TestProvisioner{}, []interface{}{42}}, @@ -187,9 +187,9 @@ func TestBuild_Run(t *testing.T) { dispatchHook := builder.runHook dispatchHook.Run("foo", nil, nil, 42) - hook := build.hooks["foo"][0].(*TestHook) - assert.True(hook.runCalled, "run should be called") - assert.Equal(hook.runData, 42, "should have correct data") + hook := build.hooks["foo"][0].(*MockHook) + 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) diff --git a/packer/environment_test.go b/packer/environment_test.go index e301e8c7a..d357797b4 100644 --- a/packer/environment_test.go +++ b/packer/environment_test.go @@ -227,7 +227,7 @@ func TestEnvironment_DefaultCli_Version(t *testing.T) { func TestEnvironment_Hook(t *testing.T) { assert := asserts.NewTestingAsserts(t, true) - hook := &TestHook{} + hook := &MockHook{} hooks := make(map[string]Hook) hooks["foo"] = hook diff --git a/packer/hook.go b/packer/hook.go index 4a79222f4..f31b62a5d 100644 --- a/packer/hook.go +++ b/packer/hook.go @@ -11,8 +11,14 @@ const HookProvision = "packer_provision" // you must reference the documentation for the specific hook you're interested // in. In addition to that, the Hook is given access to a UI so that it can // output things to the user. +// +// Cancel is called when the hook needs to be cancelled. This will usually +// be called when Run is still in progress so the mechanism that handles this +// must be race-free. Cancel should attempt to cancel the hook in the +// quickest, safest way possible. type Hook interface { Run(string, Ui, Communicator, interface{}) error + Cancel() } // A Hook implementation that dispatches based on an internal mapping. @@ -38,3 +44,5 @@ func (h *DispatchHook) Run(name string, ui Ui, comm Communicator, data interface return nil } + +func (h *DispatchHook) Cancel() {} diff --git a/packer/hook_mock.go b/packer/hook_mock.go new file mode 100644 index 000000000..eedf5bb90 --- /dev/null +++ b/packer/hook_mock.go @@ -0,0 +1,24 @@ +package packer + +// MockHook is an implementation of Hook that can be used for tests. +type MockHook struct { + RunCalled bool + RunComm Communicator + RunData interface{} + RunName string + RunUi Ui + CancelCalled bool +} + +func (t *MockHook) Run(name string, ui Ui, comm Communicator, data interface{}) error { + t.RunCalled = true + t.RunComm = comm + t.RunData = data + t.RunName = name + t.RunUi = ui + return nil +} + +func (t *MockHook) Cancel() { + t.CancelCalled = true +} diff --git a/packer/hook_test.go b/packer/hook_test.go index e43dfd1fd..06db0073d 100644 --- a/packer/hook_test.go +++ b/packer/hook_test.go @@ -5,23 +5,6 @@ import ( "testing" ) -type TestHook struct { - runCalled bool - runComm Communicator - runData interface{} - runName string - runUi Ui -} - -func (t *TestHook) Run(name string, ui Ui, comm Communicator, data interface{}) error { - t.runCalled = true - t.runComm = comm - t.runData = data - t.runName = name - t.runUi = ui - return nil -} - func TestDispatchHook_Implements(t *testing.T) { assert := asserts.NewTestingAsserts(t, true) @@ -40,14 +23,14 @@ func TestDispatchHook_Run_NoHooks(t *testing.T) { func TestDispatchHook_Run(t *testing.T) { assert := asserts.NewTestingAsserts(t, true) - hook := &TestHook{} + hook := &MockHook{} mapping := make(map[string][]Hook) mapping["foo"] = []Hook{hook} dh := &DispatchHook{mapping} dh.Run("foo", nil, nil, 42) - assert.True(hook.runCalled, "run should be called") - assert.Equal(hook.runName, "foo", "should be proper event") - assert.Equal(hook.runData, 42, "should be correct data") + assert.True(hook.RunCalled, "run should be called") + assert.Equal(hook.RunName, "foo", "should be proper event") + assert.Equal(hook.RunData, 42, "should be correct data") } diff --git a/packer/plugin/hook.go b/packer/plugin/hook.go index 90b0779d9..5d4dbf06a 100644 --- a/packer/plugin/hook.go +++ b/packer/plugin/hook.go @@ -19,8 +19,17 @@ func (c *cmdHook) Run(name string, ui packer.Ui, comm packer.Communicator, data return c.hook.Run(name, ui, comm, data) } +func (c *cmdHook) Cancel() { + defer func() { + r := recover() + c.checkExit(r, nil) + }() + + c.hook.Cancel() +} + func (c *cmdHook) checkExit(p interface{}, cb func()) { - if c.client.Exited() { + if c.client.Exited() && cb != nil { cb() } else if p != nil && !Killed { log.Panic(p) diff --git a/packer/plugin/hook_test.go b/packer/plugin/hook_test.go index e6880e616..6f897da19 100644 --- a/packer/plugin/hook_test.go +++ b/packer/plugin/hook_test.go @@ -1,17 +1,10 @@ package plugin import ( - "github.com/mitchellh/packer/packer" "os/exec" "testing" ) -type helperHook byte - -func (helperHook) Run(string, packer.Ui, packer.Communicator, interface{}) error { - return nil -} - func TestHook_NoExist(t *testing.T) { c := NewClient(&ClientConfig{Cmd: exec.Command("i-should-not-exist")}) defer c.Kill() diff --git a/packer/plugin/plugin_test.go b/packer/plugin/plugin_test.go index 0610887ec..7adaec5da 100644 --- a/packer/plugin/plugin_test.go +++ b/packer/plugin/plugin_test.go @@ -2,6 +2,7 @@ package plugin import ( "fmt" + "github.com/mitchellh/packer/packer" "log" "os" "os/exec" @@ -54,7 +55,7 @@ func TestHelperProcess(*testing.T) { case "command": ServeCommand(new(helperCommand)) case "hook": - ServeHook(new(helperHook)) + ServeHook(new(packer.MockHook)) case "invalid-rpc-address": fmt.Println("lolinvalid") case "mock": diff --git a/packer/provisioner.go b/packer/provisioner.go index 99a0a2ac2..bee9a0961 100644 --- a/packer/provisioner.go +++ b/packer/provisioner.go @@ -32,3 +32,8 @@ func (h *ProvisionHook) Run(name string, ui Ui, comm Communicator, data interfac return nil } + +// Cancels the privisioners that are still running. +func (h *ProvisionHook) Cancel() { + // TODO(mitchellh): implement +} diff --git a/packer/rpc/builder_test.go b/packer/rpc/builder_test.go index 8dff67b30..5235de9b5 100644 --- a/packer/rpc/builder_test.go +++ b/packer/rpc/builder_test.go @@ -72,7 +72,7 @@ func TestBuilderRPC(t *testing.T) { // Test Run cache := new(testCache) - hook := &testHook{} + hook := &packer.MockHook{} ui := &testUi{} artifact, err := bClient.Run(ui, hook, cache) assert.Nil(err, "should have no error") @@ -83,7 +83,7 @@ func TestBuilderRPC(t *testing.T) { assert.True(cache.lockCalled, "lock should be called") b.runHook.Run("foo", nil, nil, nil) - assert.True(hook.runCalled, "run should be called") + assert.True(hook.RunCalled, "run should be called") b.runUi.Say("format") assert.True(ui.sayCalled, "say should be called") diff --git a/packer/rpc/hook.go b/packer/rpc/hook.go index 27840f080..ea8fb6eef 100644 --- a/packer/rpc/hook.go +++ b/packer/rpc/hook.go @@ -37,6 +37,10 @@ func (h *hook) Run(name string, ui packer.Ui, comm packer.Communicator, data int return h.client.Call("Hook.Run", args, new(interface{})) } +func (h *hook) Cancel() { + // TODO(mitchellh): implement +} + func (h *HookServer) Run(args *HookRunArgs, reply *interface{}) error { client, err := rpc.Dial("tcp", args.RPCAddress) if err != nil { diff --git a/packer/rpc/hook_test.go b/packer/rpc/hook_test.go index fcc2aea7a..ea780a74e 100644 --- a/packer/rpc/hook_test.go +++ b/packer/rpc/hook_test.go @@ -7,21 +7,11 @@ import ( "testing" ) -type testHook struct { - runCalled bool - runUi packer.Ui -} - -func (h *testHook) Run(name string, ui packer.Ui, comm packer.Communicator, data interface{}) error { - h.runCalled = true - return nil -} - func TestHookRPC(t *testing.T) { assert := asserts.NewTestingAsserts(t, true) // Create the UI to test - h := new(testHook) + h := new(packer.MockHook) // Serve server := rpc.NewServer() @@ -37,7 +27,7 @@ func TestHookRPC(t *testing.T) { // Test Run ui := &testUi{} hClient.Run("foo", ui, nil, 42) - assert.True(h.runCalled, "run should be called") + assert.True(h.RunCalled, "run should be called") } func TestHook_Implements(t *testing.T) {