packer: implement Cancel on DispatchHook
This commit is contained in:
parent
80e8e09ec7
commit
e210151408
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue