github.com/askholme/packer@v0.7.2-0.20140924152349-70d9566a6852/packer/provisioner.go (about)

     1  package packer
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  	"time"
     7  )
     8  
     9  // A provisioner is responsible for installing and configuring software
    10  // on a machine prior to building the actual image.
    11  type Provisioner interface {
    12  	// Prepare is called with a set of configurations to setup the
    13  	// internal state of the provisioner. The multiple configurations
    14  	// should be merged in some sane way.
    15  	Prepare(...interface{}) error
    16  
    17  	// Provision is called to actually provision the machine. A UI is
    18  	// given to communicate with the user, and a communicator is given that
    19  	// is guaranteed to be connected to some machine so that provisioning
    20  	// can be done.
    21  	Provision(Ui, Communicator) error
    22  
    23  	// Cancel is called to cancel the provisioning. This is usually called
    24  	// while Provision is still being called. The Provisioner should act
    25  	// to stop its execution as quickly as possible in a race-free way.
    26  	Cancel()
    27  }
    28  
    29  // A Hook implementation that runs the given provisioners.
    30  type ProvisionHook struct {
    31  	// The provisioners to run as part of the hook. These should already
    32  	// be prepared (by calling Prepare) at some earlier stage.
    33  	Provisioners []Provisioner
    34  
    35  	lock               sync.Mutex
    36  	runningProvisioner Provisioner
    37  }
    38  
    39  // Runs the provisioners in order.
    40  func (h *ProvisionHook) Run(name string, ui Ui, comm Communicator, data interface{}) error {
    41  	defer func() {
    42  		h.lock.Lock()
    43  		defer h.lock.Unlock()
    44  
    45  		h.runningProvisioner = nil
    46  	}()
    47  
    48  	for _, p := range h.Provisioners {
    49  		h.lock.Lock()
    50  		h.runningProvisioner = p
    51  		h.lock.Unlock()
    52  
    53  		if err := p.Provision(ui, comm); err != nil {
    54  			return err
    55  		}
    56  	}
    57  
    58  	return nil
    59  }
    60  
    61  // Cancels the privisioners that are still running.
    62  func (h *ProvisionHook) Cancel() {
    63  	h.lock.Lock()
    64  	defer h.lock.Unlock()
    65  
    66  	if h.runningProvisioner != nil {
    67  		h.runningProvisioner.Cancel()
    68  	}
    69  }
    70  
    71  // PausedProvisioner is a Provisioner implementation that pauses before
    72  // the provisioner is actually run.
    73  type PausedProvisioner struct {
    74  	PauseBefore time.Duration
    75  	Provisioner Provisioner
    76  
    77  	cancelCh chan struct{}
    78  	doneCh   chan struct{}
    79  	lock     sync.Mutex
    80  }
    81  
    82  func (p *PausedProvisioner) Prepare(raws ...interface{}) error {
    83  	return p.Provisioner.Prepare(raws...)
    84  }
    85  
    86  func (p *PausedProvisioner) Provision(ui Ui, comm Communicator) error {
    87  	p.lock.Lock()
    88  	cancelCh := make(chan struct{})
    89  	p.cancelCh = cancelCh
    90  
    91  	// Setup the done channel, which is trigger when we're done
    92  	doneCh := make(chan struct{})
    93  	defer close(doneCh)
    94  	p.doneCh = doneCh
    95  	p.lock.Unlock()
    96  
    97  	defer func() {
    98  		p.lock.Lock()
    99  		defer p.lock.Unlock()
   100  		if p.cancelCh == cancelCh {
   101  			p.cancelCh = nil
   102  		}
   103  		if p.doneCh == doneCh {
   104  			p.doneCh = nil
   105  		}
   106  	}()
   107  
   108  	// Use a select to determine if we get cancelled during the wait
   109  	ui.Say(fmt.Sprintf("Pausing %s before the next provisioner...", p.PauseBefore))
   110  	select {
   111  	case <-time.After(p.PauseBefore):
   112  	case <-cancelCh:
   113  		return nil
   114  	}
   115  
   116  	provDoneCh := make(chan error, 1)
   117  	go p.provision(provDoneCh, ui, comm)
   118  
   119  	select {
   120  	case err := <-provDoneCh:
   121  		return err
   122  	case <-cancelCh:
   123  		p.Provisioner.Cancel()
   124  		return <-provDoneCh
   125  	}
   126  }
   127  
   128  func (p *PausedProvisioner) Cancel() {
   129  	var doneCh chan struct{}
   130  
   131  	p.lock.Lock()
   132  	if p.cancelCh != nil {
   133  		close(p.cancelCh)
   134  		p.cancelCh = nil
   135  	}
   136  	if p.doneCh != nil {
   137  		doneCh = p.doneCh
   138  	}
   139  	p.lock.Unlock()
   140  
   141  	<-doneCh
   142  }
   143  
   144  func (p *PausedProvisioner) provision(result chan<- error, ui Ui, comm Communicator) {
   145  	result <- p.Provisioner.Provision(ui, comm)
   146  }