packer: implement Cancel in ProvisionHook

This commit is contained in:
Mitchell Hashimoto 2013-08-30 23:39:29 -07:00
parent 66f7f5aad5
commit 6e098d1aaf
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 return multistep.ActionContinue
case <-time.After(1 * time.Second): case <-time.After(1 * time.Second):
if _, ok := state[multistep.StateCancelled]; ok { if _, ok := state[multistep.StateCancelled]; ok {
log.Println("Cancelling provisioning due to interrupt...")
hook.Cancel() hook.Cancel()
return multistep.ActionHalt 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] = make([]Hook, 0, 1)
} }
hooks[HookProvision] = append(hooks[HookProvision], &ProvisionHook{provisioners}) hooks[HookProvision] = append(hooks[HookProvision], &ProvisionHook{
Provisioners: provisioners,
})
} }
hook := &DispatchHook{Mapping: hooks} hook := &DispatchHook{Mapping: hooks}

View File

@ -1,5 +1,9 @@
package packer package packer
import (
"sync"
)
// A provisioner is responsible for installing and configuring software // A provisioner is responsible for installing and configuring software
// on a machine prior to building the actual image. // on a machine prior to building the actual image.
type Provisioner interface { type Provisioner interface {
@ -25,11 +29,25 @@ type ProvisionHook struct {
// The provisioners to run as part of the hook. These should already // The provisioners to run as part of the hook. These should already
// be prepared (by calling Prepare) at some earlier stage. // be prepared (by calling Prepare) at some earlier stage.
Provisioners []Provisioner Provisioners []Provisioner
lock sync.Mutex
runningProvisioner Provisioner
} }
// Runs the provisioners in order. // Runs the provisioners in order.
func (h *ProvisionHook) Run(name string, ui Ui, comm Communicator, data interface{}) error { 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 { for _, p := range h.Provisioners {
h.lock.Lock()
h.runningProvisioner = p
h.lock.Unlock()
if err := p.Provision(ui, comm); err != nil { if err := p.Provision(ui, comm); err != nil {
return err 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. // Cancels the privisioners that are still running.
func (h *ProvisionHook) Cancel() { 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 // MockProvisioner is an implementation of Provisioner that can be
// used for tests. // used for tests.
type MockProvisioner struct { type MockProvisioner struct {
ProvFunc func() error
PrepCalled bool PrepCalled bool
PrepConfigs []interface{} PrepConfigs []interface{}
ProvCalled bool ProvCalled bool
@ -19,7 +21,12 @@ func (t *MockProvisioner) Prepare(configs ...interface{}) error {
func (t *MockProvisioner) Provision(ui Ui, comm Communicator) error { func (t *MockProvisioner) Provision(ui Ui, comm Communicator) error {
t.ProvCalled = true t.ProvCalled = true
t.ProvUi = ui t.ProvUi = ui
return nil
if t.ProvFunc == nil {
return nil
}
return t.ProvFunc()
} }
func (t *MockProvisioner) Cancel() { func (t *MockProvisioner) Cancel() {

View File

@ -1,6 +1,10 @@
package packer package packer
import "testing" import (
"sync"
"testing"
"time"
)
func TestProvisionHook_Impl(t *testing.T) { func TestProvisionHook_Impl(t *testing.T) {
var raw interface{} var raw interface{}
@ -18,7 +22,10 @@ func TestProvisionHook(t *testing.T) {
var comm Communicator = nil var comm Communicator = nil
var data interface{} = nil var data interface{} = nil
hook := &ProvisionHook{[]Provisioner{pA, pB}} hook := &ProvisionHook{
Provisioners: []Provisioner{pA, pB},
}
hook.Run("foo", ui, comm, data) hook.Run("foo", ui, comm, data)
if !pA.ProvCalled { 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 // TODO(mitchellh): Test that they're run in the proper order