github.com/yoctocloud/packer@v0.6.2-0.20160520224004-e11a0a18423f/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  	// Shortcut
    42  	if len(h.Provisioners) == 0 {
    43  		return nil
    44  	}
    45  
    46  	if comm == nil {
    47  		return fmt.Errorf(
    48  			"No communicator found for provisioners! This is usually because the\n" +
    49  				"`communicator` config was set to \"none\". If you have any provisioners\n" +
    50  				"then a communicator is required. Please fix this to continue.")
    51  	}
    52  
    53  	defer func() {
    54  		h.lock.Lock()
    55  		defer h.lock.Unlock()
    56  
    57  		h.runningProvisioner = nil
    58  	}()
    59  
    60  	for _, p := range h.Provisioners {
    61  		h.lock.Lock()
    62  		h.runningProvisioner = p
    63  		h.lock.Unlock()
    64  
    65  		if err := p.Provision(ui, comm); err != nil {
    66  			return err
    67  		}
    68  	}
    69  
    70  	return nil
    71  }
    72  
    73  // Cancels the privisioners that are still running.
    74  func (h *ProvisionHook) Cancel() {
    75  	h.lock.Lock()
    76  	defer h.lock.Unlock()
    77  
    78  	if h.runningProvisioner != nil {
    79  		h.runningProvisioner.Cancel()
    80  	}
    81  }
    82  
    83  // PausedProvisioner is a Provisioner implementation that pauses before
    84  // the provisioner is actually run.
    85  type PausedProvisioner struct {
    86  	PauseBefore time.Duration
    87  	Provisioner Provisioner
    88  
    89  	cancelCh chan struct{}
    90  	doneCh   chan struct{}
    91  	lock     sync.Mutex
    92  }
    93  
    94  func (p *PausedProvisioner) Prepare(raws ...interface{}) error {
    95  	return p.Provisioner.Prepare(raws...)
    96  }
    97  
    98  func (p *PausedProvisioner) Provision(ui Ui, comm Communicator) error {
    99  	p.lock.Lock()
   100  	cancelCh := make(chan struct{})
   101  	p.cancelCh = cancelCh
   102  
   103  	// Setup the done channel, which is trigger when we're done
   104  	doneCh := make(chan struct{})
   105  	defer close(doneCh)
   106  	p.doneCh = doneCh
   107  	p.lock.Unlock()
   108  
   109  	defer func() {
   110  		p.lock.Lock()
   111  		defer p.lock.Unlock()
   112  		if p.cancelCh == cancelCh {
   113  			p.cancelCh = nil
   114  		}
   115  		if p.doneCh == doneCh {
   116  			p.doneCh = nil
   117  		}
   118  	}()
   119  
   120  	// Use a select to determine if we get cancelled during the wait
   121  	ui.Say(fmt.Sprintf("Pausing %s before the next provisioner...", p.PauseBefore))
   122  	select {
   123  	case <-time.After(p.PauseBefore):
   124  	case <-cancelCh:
   125  		return nil
   126  	}
   127  
   128  	provDoneCh := make(chan error, 1)
   129  	go p.provision(provDoneCh, ui, comm)
   130  
   131  	select {
   132  	case err := <-provDoneCh:
   133  		return err
   134  	case <-cancelCh:
   135  		p.Provisioner.Cancel()
   136  		return <-provDoneCh
   137  	}
   138  }
   139  
   140  func (p *PausedProvisioner) Cancel() {
   141  	var doneCh chan struct{}
   142  
   143  	p.lock.Lock()
   144  	if p.cancelCh != nil {
   145  		close(p.cancelCh)
   146  		p.cancelCh = nil
   147  	}
   148  	if p.doneCh != nil {
   149  		doneCh = p.doneCh
   150  	}
   151  	p.lock.Unlock()
   152  
   153  	<-doneCh
   154  }
   155  
   156  func (p *PausedProvisioner) provision(result chan<- error, ui Ui, comm Communicator) {
   157  	result <- p.Provisioner.Provision(ui, comm)
   158  }