Merge pull request #4663 from marema31/debug-prompt-provisionner

Debug prompt between provisioner scripts
This commit is contained in:
M. Marsh 2018-06-07 16:13:44 -07:00 committed by GitHub
commit b4fd69a963
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 164 additions and 4 deletions

View File

@ -200,10 +200,18 @@ func (b *coreBuild) Run(originalUi Ui, cache Cache) ([]Artifact, error) {
if len(p.config) > 0 {
pConfig = p.config[0]
}
hookedProvisioners[i] = &HookedProvisioner{
p.provisioner,
pConfig,
p.pType,
if b.debug {
hookedProvisioners[i] = &HookedProvisioner{
&DebuggedProvisioner{Provisioner: p.provisioner},
pConfig,
p.pType,
}
} else {
hookedProvisioners[i] = &HookedProvisioner{
p.provisioner,
pConfig,
p.pType,
}
}
}

View File

@ -2,6 +2,7 @@ package packer
import (
"fmt"
"log"
"sync"
"time"
)
@ -168,3 +169,90 @@ func (p *PausedProvisioner) Cancel() {
func (p *PausedProvisioner) provision(result chan<- error, ui Ui, comm Communicator) {
result <- p.Provisioner.Provision(ui, comm)
}
// DebuggedProvisioner is a Provisioner implementation that waits until a key
// press before the provisioner is actually run.
type DebuggedProvisioner struct {
Provisioner Provisioner
cancelCh chan struct{}
doneCh chan struct{}
lock sync.Mutex
}
func (p *DebuggedProvisioner) Prepare(raws ...interface{}) error {
return p.Provisioner.Prepare(raws...)
}
func (p *DebuggedProvisioner) Provision(ui Ui, comm Communicator) error {
p.lock.Lock()
cancelCh := make(chan struct{})
p.cancelCh = cancelCh
// Setup the done channel, which is trigger when we're done
doneCh := make(chan struct{})
defer close(doneCh)
p.doneCh = doneCh
p.lock.Unlock()
defer func() {
p.lock.Lock()
defer p.lock.Unlock()
if p.cancelCh == cancelCh {
p.cancelCh = nil
}
if p.doneCh == doneCh {
p.doneCh = nil
}
}()
// Use a select to determine if we get cancelled during the wait
message := "Pausing before the next provisioner . Press enter to continue."
result := make(chan string, 1)
go func() {
line, err := ui.Ask(message)
if err != nil {
log.Printf("Error asking for input: %s", err)
}
result <- line
}()
select {
case <-result:
case <-cancelCh:
return nil
}
provDoneCh := make(chan error, 1)
go p.provision(provDoneCh, ui, comm)
select {
case err := <-provDoneCh:
return err
case <-cancelCh:
p.Provisioner.Cancel()
return <-provDoneCh
}
}
func (p *DebuggedProvisioner) Cancel() {
var doneCh chan struct{}
p.lock.Lock()
if p.cancelCh != nil {
close(p.cancelCh)
p.cancelCh = nil
}
if p.doneCh != nil {
doneCh = p.doneCh
}
p.lock.Unlock()
<-doneCh
}
func (p *DebuggedProvisioner) provision(result chan<- error, ui Ui, comm Communicator) {
result <- p.Provisioner.Provision(ui, comm)
}

View File

@ -197,3 +197,67 @@ func TestPausedProvisionerCancel(t *testing.T) {
t.Fatal("cancel should be called")
}
}
func TestDebuggedProvisioner_impl(t *testing.T) {
var _ Provisioner = new(DebuggedProvisioner)
}
func TestDebuggedProvisionerPrepare(t *testing.T) {
mock := new(MockProvisioner)
prov := &DebuggedProvisioner{
Provisioner: mock,
}
prov.Prepare(42)
if !mock.PrepCalled {
t.Fatal("prepare should be called")
}
if mock.PrepConfigs[0] != 42 {
t.Fatal("should have proper configs")
}
}
func TestDebuggedProvisionerProvision(t *testing.T) {
mock := new(MockProvisioner)
prov := &DebuggedProvisioner{
Provisioner: mock,
}
ui := testUi()
comm := new(MockCommunicator)
writeReader(ui, "\n")
prov.Provision(ui, comm)
if !mock.ProvCalled {
t.Fatal("prov should be called")
}
if mock.ProvUi != ui {
t.Fatal("should have proper ui")
}
if mock.ProvCommunicator != comm {
t.Fatal("should have proper comm")
}
}
func TestDebuggedProvisionerCancel(t *testing.T) {
mock := new(MockProvisioner)
prov := &DebuggedProvisioner{
Provisioner: mock,
}
provCh := make(chan struct{})
mock.ProvFunc = func() error {
close(provCh)
time.Sleep(10 * time.Millisecond)
return nil
}
// Start provisioning and wait for it to start
go prov.Provision(testUi(), new(MockCommunicator))
<-provCh
// Cancel it
prov.Cancel()
if !mock.CancelCalled {
t.Fatal("cancel should be called")
}
}