github.com/StackPointCloud/packer@v0.10.2-0.20180716202532-b28098e0f79b/packer/provisioner.go (about)

     1  package packer
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"sync"
     7  	"time"
     8  )
     9  
    10  // A provisioner is responsible for installing and configuring software
    11  // on a machine prior to building the actual image.
    12  type Provisioner interface {
    13  	// Prepare is called with a set of configurations to setup the
    14  	// internal state of the provisioner. The multiple configurations
    15  	// should be merged in some sane way.
    16  	Prepare(...interface{}) error
    17  
    18  	// Provision is called to actually provision the machine. A UI is
    19  	// given to communicate with the user, and a communicator is given that
    20  	// is guaranteed to be connected to some machine so that provisioning
    21  	// can be done.
    22  	Provision(Ui, Communicator) error
    23  
    24  	// Cancel is called to cancel the provisioning. This is usually called
    25  	// while Provision is still being called. The Provisioner should act
    26  	// to stop its execution as quickly as possible in a race-free way.
    27  	Cancel()
    28  }
    29  
    30  // A HookedProvisioner represents a provisioner and information describing it
    31  type HookedProvisioner struct {
    32  	Provisioner Provisioner
    33  	Config      interface{}
    34  	TypeName    string
    35  }
    36  
    37  // A Hook implementation that runs the given provisioners.
    38  type ProvisionHook struct {
    39  	// The provisioners to run as part of the hook. These should already
    40  	// be prepared (by calling Prepare) at some earlier stage.
    41  	Provisioners []*HookedProvisioner
    42  
    43  	lock               sync.Mutex
    44  	runningProvisioner Provisioner
    45  }
    46  
    47  // Runs the provisioners in order.
    48  func (h *ProvisionHook) Run(name string, ui Ui, comm Communicator, data interface{}) error {
    49  	// Shortcut
    50  	if len(h.Provisioners) == 0 {
    51  		return nil
    52  	}
    53  
    54  	if comm == nil {
    55  		return fmt.Errorf(
    56  			"No communicator found for provisioners! This is usually because the\n" +
    57  				"`communicator` config was set to \"none\". If you have any provisioners\n" +
    58  				"then a communicator is required. Please fix this to continue.")
    59  	}
    60  
    61  	defer func() {
    62  		h.lock.Lock()
    63  		defer h.lock.Unlock()
    64  
    65  		h.runningProvisioner = nil
    66  	}()
    67  
    68  	for _, p := range h.Provisioners {
    69  		h.lock.Lock()
    70  		h.runningProvisioner = p.Provisioner
    71  		h.lock.Unlock()
    72  
    73  		ts := CheckpointReporter.AddSpan(p.TypeName, "provisioner", p.Config)
    74  
    75  		err := p.Provisioner.Provision(ui, comm)
    76  
    77  		ts.End(err)
    78  		if err != nil {
    79  			return err
    80  		}
    81  	}
    82  
    83  	return nil
    84  }
    85  
    86  // Cancels the provisioners that are still running.
    87  func (h *ProvisionHook) Cancel() {
    88  	h.lock.Lock()
    89  	defer h.lock.Unlock()
    90  
    91  	if h.runningProvisioner != nil {
    92  		h.runningProvisioner.Cancel()
    93  	}
    94  }
    95  
    96  // PausedProvisioner is a Provisioner implementation that pauses before
    97  // the provisioner is actually run.
    98  type PausedProvisioner struct {
    99  	PauseBefore time.Duration
   100  	Provisioner Provisioner
   101  
   102  	cancelCh chan struct{}
   103  	doneCh   chan struct{}
   104  	lock     sync.Mutex
   105  }
   106  
   107  func (p *PausedProvisioner) Prepare(raws ...interface{}) error {
   108  	return p.Provisioner.Prepare(raws...)
   109  }
   110  
   111  func (p *PausedProvisioner) Provision(ui Ui, comm Communicator) error {
   112  	p.lock.Lock()
   113  	cancelCh := make(chan struct{})
   114  	p.cancelCh = cancelCh
   115  
   116  	// Setup the done channel, which is trigger when we're done
   117  	doneCh := make(chan struct{})
   118  	defer close(doneCh)
   119  	p.doneCh = doneCh
   120  	p.lock.Unlock()
   121  
   122  	defer func() {
   123  		p.lock.Lock()
   124  		defer p.lock.Unlock()
   125  		if p.cancelCh == cancelCh {
   126  			p.cancelCh = nil
   127  		}
   128  		if p.doneCh == doneCh {
   129  			p.doneCh = nil
   130  		}
   131  	}()
   132  
   133  	// Use a select to determine if we get cancelled during the wait
   134  	ui.Say(fmt.Sprintf("Pausing %s before the next provisioner...", p.PauseBefore))
   135  	select {
   136  	case <-time.After(p.PauseBefore):
   137  	case <-cancelCh:
   138  		return nil
   139  	}
   140  
   141  	provDoneCh := make(chan error, 1)
   142  	go p.provision(provDoneCh, ui, comm)
   143  
   144  	select {
   145  	case err := <-provDoneCh:
   146  		return err
   147  	case <-cancelCh:
   148  		p.Provisioner.Cancel()
   149  		return <-provDoneCh
   150  	}
   151  }
   152  
   153  func (p *PausedProvisioner) Cancel() {
   154  	var doneCh chan struct{}
   155  
   156  	p.lock.Lock()
   157  	if p.cancelCh != nil {
   158  		close(p.cancelCh)
   159  		p.cancelCh = nil
   160  	}
   161  	if p.doneCh != nil {
   162  		doneCh = p.doneCh
   163  	}
   164  	p.lock.Unlock()
   165  
   166  	<-doneCh
   167  }
   168  
   169  func (p *PausedProvisioner) provision(result chan<- error, ui Ui, comm Communicator) {
   170  	result <- p.Provisioner.Provision(ui, comm)
   171  }
   172  
   173  // DebuggedProvisioner is a Provisioner implementation that waits until a key
   174  // press before the provisioner is actually run.
   175  type DebuggedProvisioner struct {
   176  	Provisioner Provisioner
   177  
   178  	cancelCh chan struct{}
   179  	doneCh   chan struct{}
   180  	lock     sync.Mutex
   181  }
   182  
   183  func (p *DebuggedProvisioner) Prepare(raws ...interface{}) error {
   184  	return p.Provisioner.Prepare(raws...)
   185  }
   186  
   187  func (p *DebuggedProvisioner) Provision(ui Ui, comm Communicator) error {
   188  	p.lock.Lock()
   189  	cancelCh := make(chan struct{})
   190  	p.cancelCh = cancelCh
   191  
   192  	// Setup the done channel, which is trigger when we're done
   193  	doneCh := make(chan struct{})
   194  	defer close(doneCh)
   195  	p.doneCh = doneCh
   196  	p.lock.Unlock()
   197  
   198  	defer func() {
   199  		p.lock.Lock()
   200  		defer p.lock.Unlock()
   201  		if p.cancelCh == cancelCh {
   202  			p.cancelCh = nil
   203  		}
   204  		if p.doneCh == doneCh {
   205  			p.doneCh = nil
   206  		}
   207  	}()
   208  
   209  	// Use a select to determine if we get cancelled during the wait
   210  	message := "Pausing before the next provisioner . Press enter to continue."
   211  
   212  	result := make(chan string, 1)
   213  	go func() {
   214  		line, err := ui.Ask(message)
   215  		if err != nil {
   216  			log.Printf("Error asking for input: %s", err)
   217  		}
   218  
   219  		result <- line
   220  	}()
   221  
   222  	select {
   223  	case <-result:
   224  	case <-cancelCh:
   225  		return nil
   226  	}
   227  
   228  	provDoneCh := make(chan error, 1)
   229  	go p.provision(provDoneCh, ui, comm)
   230  
   231  	select {
   232  	case err := <-provDoneCh:
   233  		return err
   234  	case <-cancelCh:
   235  		p.Provisioner.Cancel()
   236  		return <-provDoneCh
   237  	}
   238  }
   239  
   240  func (p *DebuggedProvisioner) Cancel() {
   241  	var doneCh chan struct{}
   242  
   243  	p.lock.Lock()
   244  	if p.cancelCh != nil {
   245  		close(p.cancelCh)
   246  		p.cancelCh = nil
   247  	}
   248  	if p.doneCh != nil {
   249  		doneCh = p.doneCh
   250  	}
   251  	p.lock.Unlock()
   252  
   253  	<-doneCh
   254  }
   255  
   256  func (p *DebuggedProvisioner) provision(result chan<- error, ui Ui, comm Communicator) {
   257  	result <- p.Provisioner.Provision(ui, comm)
   258  }