packer: implement Cancel on DispatchHook

This commit is contained in:
Mitchell Hashimoto 2013-08-30 17:26:51 -07:00
parent 80e8e09ec7
commit e210151408
3 changed files with 91 additions and 5 deletions

View File

@ -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

View File

@ -1,5 +1,9 @@
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 {
// 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
} }
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
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
}

View File

@ -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) {
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")
}
}