Commit e2101514 authored by Mitchell Hashimoto's avatar Mitchell Hashimoto

packer: implement Cancel on DispatchHook

parent 80e8e09e
...@@ -210,7 +210,7 @@ func (b *coreBuild) Run(originalUi Ui, cache Cache) ([]Artifact, error) { ...@@ -210,7 +210,7 @@ func (b *coreBuild) Run(originalUi Ui, cache Cache) ([]Artifact, error) {
hooks[HookProvision] = append(hooks[HookProvision], &ProvisionHook{provisioners}) hooks[HookProvision] = append(hooks[HookProvision], &ProvisionHook{provisioners})
} }
hook := &DispatchHook{hooks} hook := &DispatchHook{Mapping: hooks}
artifacts := make([]Artifact, 0, 1) artifacts := make([]Artifact, 0, 1)
// The builder just has a normal Ui, but targetted // The builder just has a normal Ui, but targetted
......
package packer package packer
import (
"sync"
)
// This is the hook that should be fired for provisioners to run. // This is the hook that should be fired for provisioners to run.
const HookProvision = "packer_provision" const HookProvision = "packer_provision"
...@@ -24,12 +28,20 @@ type Hook interface { ...@@ -24,12 +28,20 @@ type Hook interface {
// A Hook implementation that dispatches based on an internal mapping. // A Hook implementation that dispatches based on an internal mapping.
type DispatchHook struct { type DispatchHook struct {
Mapping map[string][]Hook Mapping map[string][]Hook
l sync.Mutex
cancelled bool
runningHook Hook
} }
// Runs the hook with the given name by dispatching it to the proper // Runs the hook with the given name by dispatching it to the proper
// hooks if a mapping exists. If a mapping doesn't exist, then nothing // hooks if a mapping exists. If a mapping doesn't exist, then nothing
// happens. // happens.
func (h *DispatchHook) Run(name string, ui Ui, comm Communicator, data interface{}) error { func (h *DispatchHook) Run(name string, ui Ui, comm Communicator, data interface{}) error {
h.l.Lock()
h.cancelled = false
h.l.Unlock()
hooks, ok := h.Mapping[name] hooks, ok := h.Mapping[name]
if !ok { if !ok {
// No hooks for that name. No problem. // No hooks for that name. No problem.
...@@ -37,6 +49,15 @@ func (h *DispatchHook) Run(name string, ui Ui, comm Communicator, data interface ...@@ -37,6 +49,15 @@ func (h *DispatchHook) Run(name string, ui Ui, comm Communicator, data interface
} }
for _, hook := range hooks { for _, hook := range hooks {
h.l.Lock()
if h.cancelled {
h.l.Unlock()
return nil
}
h.runningHook = hook
h.l.Unlock()
if err := hook.Run(name, ui, comm, data); err != nil { if err := hook.Run(name, ui, comm, data); err != nil {
return err return err
} }
...@@ -45,4 +66,15 @@ func (h *DispatchHook) Run(name string, ui Ui, comm Communicator, data interface ...@@ -45,4 +66,15 @@ func (h *DispatchHook) Run(name string, ui Ui, comm Communicator, data interface
return nil return nil
} }
func (h *DispatchHook) Cancel() {} // Cancels all the hooks that are currently in-flight, if any. This will
// block until the hooks are all cancelled.
func (h *DispatchHook) Cancel() {
h.l.Lock()
defer h.l.Unlock()
if h.runningHook != nil {
h.runningHook.Cancel()
}
h.cancelled = true
}
...@@ -2,21 +2,57 @@ package packer ...@@ -2,21 +2,57 @@ package packer
import ( import (
"cgl.tideland.biz/asserts" "cgl.tideland.biz/asserts"
"sync"
"testing" "testing"
"time"
) )
// A helper Hook implementation for testing cancels.
type CancelHook struct {
sync.Mutex
cancelCh chan struct{}
doneCh chan struct{}
Cancelled bool
}
func (h *CancelHook) Run(string, Ui, Communicator, interface{}) error {
h.Lock()
h.cancelCh = make(chan struct{})
h.doneCh = make(chan struct{})
h.Unlock()
defer close(h.doneCh)
select {
case <-h.cancelCh:
h.Cancelled = true
case <-time.After(1 * time.Second):
}
return nil
}
func (h *CancelHook) Cancel() {
h.Lock()
close(h.cancelCh)
h.Unlock()
<-h.doneCh
}
func TestDispatchHook_Implements(t *testing.T) { func TestDispatchHook_Implements(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true) assert := asserts.NewTestingAsserts(t, true)
var r Hook var r Hook
c := &DispatchHook{nil} c := &DispatchHook{}
assert.Implementor(c, &r, "should be a Hook") assert.Implementor(c, &r, "should be a Hook")
} }
func TestDispatchHook_Run_NoHooks(t *testing.T) { func TestDispatchHook_Run_NoHooks(t *testing.T) {
// Just make sure nothing blows up // Just make sure nothing blows up
dh := &DispatchHook{make(map[string][]Hook)} dh := &DispatchHook{}
dh.Run("foo", nil, nil, nil) dh.Run("foo", nil, nil, nil)
} }
...@@ -27,10 +63,28 @@ func TestDispatchHook_Run(t *testing.T) { ...@@ -27,10 +63,28 @@ func TestDispatchHook_Run(t *testing.T) {
mapping := make(map[string][]Hook) mapping := make(map[string][]Hook)
mapping["foo"] = []Hook{hook} mapping["foo"] = []Hook{hook}
dh := &DispatchHook{mapping} dh := &DispatchHook{Mapping: mapping}
dh.Run("foo", nil, nil, 42) dh.Run("foo", nil, nil, 42)
assert.True(hook.RunCalled, "run should be called") assert.True(hook.RunCalled, "run should be called")
assert.Equal(hook.RunName, "foo", "should be proper event") assert.Equal(hook.RunName, "foo", "should be proper event")
assert.Equal(hook.RunData, 42, "should be correct data") assert.Equal(hook.RunData, 42, "should be correct data")
} }
func TestDispatchHook_cancel(t *testing.T) {
hook := new(CancelHook)
dh := &DispatchHook{
Mapping: map[string][]Hook{
"foo": []Hook{hook},
},
}
go dh.Run("foo", nil, nil, 42)
time.Sleep(100 * time.Millisecond)
dh.Cancel()
if !hook.Cancelled {
t.Fatal("hook should've cancelled")
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment