packer: implement Cancel in ProvisionHook

This commit is contained in:
Mitchell Hashimoto 2013-08-30 23:39:29 -07:00
parent 9f559cb25c
commit 0dc347c70d
5 changed files with 87 additions and 5 deletions

View File

@ -42,6 +42,7 @@ func (*StepProvision) Run(state map[string]interface{}) multistep.StepAction {
return multistep.ActionContinue
case <-time.After(1 * time.Second):
if _, ok := state[multistep.StateCancelled]; ok {
log.Println("Cancelling provisioning due to interrupt...")
hook.Cancel()
return multistep.ActionHalt
}

View File

@ -207,7 +207,9 @@ func (b *coreBuild) Run(originalUi Ui, cache Cache) ([]Artifact, error) {
hooks[HookProvision] = make([]Hook, 0, 1)
}
hooks[HookProvision] = append(hooks[HookProvision], &ProvisionHook{provisioners})
hooks[HookProvision] = append(hooks[HookProvision], &ProvisionHook{
Provisioners: provisioners,
})
}
hook := &DispatchHook{Mapping: hooks}

View File

@ -1,5 +1,9 @@
package packer
import (
"sync"
)
// A provisioner is responsible for installing and configuring software
// on a machine prior to building the actual image.
type Provisioner interface {
@ -25,11 +29,25 @@ type ProvisionHook struct {
// The provisioners to run as part of the hook. These should already
// be prepared (by calling Prepare) at some earlier stage.
Provisioners []Provisioner
lock sync.Mutex
runningProvisioner Provisioner
}
// Runs the provisioners in order.
func (h *ProvisionHook) Run(name string, ui Ui, comm Communicator, data interface{}) error {
defer func() {
h.lock.Lock()
defer h.lock.Unlock()
h.runningProvisioner = nil
}()
for _, p := range h.Provisioners {
h.lock.Lock()
h.runningProvisioner = p
h.lock.Unlock()
if err := p.Provision(ui, comm); err != nil {
return err
}
@ -40,5 +58,10 @@ func (h *ProvisionHook) Run(name string, ui Ui, comm Communicator, data interfac
// Cancels the privisioners that are still running.
func (h *ProvisionHook) Cancel() {
// TODO(mitchellh): implement
h.lock.Lock()
defer h.lock.Unlock()
if h.runningProvisioner != nil {
h.runningProvisioner.Cancel()
}
}

View File

@ -3,6 +3,8 @@ package packer
// MockProvisioner is an implementation of Provisioner that can be
// used for tests.
type MockProvisioner struct {
ProvFunc func() error
PrepCalled bool
PrepConfigs []interface{}
ProvCalled bool
@ -19,7 +21,12 @@ func (t *MockProvisioner) Prepare(configs ...interface{}) error {
func (t *MockProvisioner) Provision(ui Ui, comm Communicator) error {
t.ProvCalled = true
t.ProvUi = ui
return nil
if t.ProvFunc == nil {
return nil
}
return t.ProvFunc()
}
func (t *MockProvisioner) Cancel() {

View File

@ -1,6 +1,10 @@
package packer
import "testing"
import (
"sync"
"testing"
"time"
)
func TestProvisionHook_Impl(t *testing.T) {
var raw interface{}
@ -18,7 +22,10 @@ func TestProvisionHook(t *testing.T) {
var comm Communicator = nil
var data interface{} = nil
hook := &ProvisionHook{[]Provisioner{pA, pB}}
hook := &ProvisionHook{
Provisioners: []Provisioner{pA, pB},
}
hook.Run("foo", ui, comm, data)
if !pA.ProvCalled {
@ -30,4 +37,46 @@ func TestProvisionHook(t *testing.T) {
}
}
func TestProvisionHook_cancel(t *testing.T) {
var lock sync.Mutex
order := make([]string, 0, 2)
p := &MockProvisioner{
ProvFunc: func() error {
time.Sleep(50 * time.Millisecond)
lock.Lock()
defer lock.Unlock()
order = append(order, "prov")
return nil
},
}
hook := &ProvisionHook{
Provisioners: []Provisioner{p},
}
finished := make(chan struct{})
go func() {
hook.Run("foo", nil, nil, nil)
close(finished)
}()
// Cancel it while it is running
time.Sleep(10 * time.Millisecond)
hook.Cancel()
lock.Lock()
order = append(order, "cancel")
lock.Unlock()
// Wait
<-finished
// Verify order
if order[0] != "cancel" || order[1] != "prov" {
t.Fatalf("bad: %#v", order)
}
}
// TODO(mitchellh): Test that they're run in the proper order