github.com/emate/packer@v0.8.1-0.20150625195101-fe0fde195dc6/provisioner/windows-restart/provisioner.go (about)

     1  package restart
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/masterzen/winrm/winrm"
    10  	"github.com/mitchellh/packer/common"
    11  	"github.com/mitchellh/packer/helper/config"
    12  	"github.com/mitchellh/packer/packer"
    13  	"github.com/mitchellh/packer/template/interpolate"
    14  )
    15  
    16  var DefaultRestartCommand = "powershell \"& {Restart-Computer -force }\""
    17  var DefaultRestartCheckCommand = winrm.Powershell(`echo "${env:COMPUTERNAME} restarted."`)
    18  var retryableSleep = 5 * time.Second
    19  
    20  type Config struct {
    21  	common.PackerConfig `mapstructure:",squash"`
    22  
    23  	// The command used to restart the guest machine
    24  	RestartCommand string `mapstructure:"restart_command"`
    25  
    26  	// The command used to check if the guest machine has restarted
    27  	// The output of this command will be displayed to the user
    28  	RestartCheckCommand string `mapstructure:"restart_check_command"`
    29  
    30  	// The timeout for waiting for the machine to restart
    31  	RestartTimeout time.Duration `mapstructure:"restart_timeout"`
    32  
    33  	ctx interpolate.Context
    34  }
    35  
    36  type Provisioner struct {
    37  	config     Config
    38  	comm       packer.Communicator
    39  	ui         packer.Ui
    40  	cancel     chan struct{}
    41  	cancelLock sync.Mutex
    42  }
    43  
    44  func (p *Provisioner) Prepare(raws ...interface{}) error {
    45  	err := config.Decode(&p.config, &config.DecodeOpts{
    46  		Interpolate:        true,
    47  		InterpolateContext: &p.config.ctx,
    48  		InterpolateFilter: &interpolate.RenderFilter{
    49  			Exclude: []string{
    50  				"execute_command",
    51  			},
    52  		},
    53  	}, raws...)
    54  	if err != nil {
    55  		return err
    56  	}
    57  
    58  	if p.config.RestartCommand == "" {
    59  		p.config.RestartCommand = DefaultRestartCommand
    60  	}
    61  
    62  	if p.config.RestartCheckCommand == "" {
    63  		p.config.RestartCheckCommand = DefaultRestartCheckCommand
    64  	}
    65  
    66  	if p.config.RestartTimeout == 0 {
    67  		p.config.RestartTimeout = 5 * time.Minute
    68  	}
    69  
    70  	return nil
    71  }
    72  
    73  func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
    74  	p.cancelLock.Lock()
    75  	p.cancel = make(chan struct{})
    76  	p.cancelLock.Unlock()
    77  
    78  	ui.Say("Restarting Machine")
    79  	p.comm = comm
    80  	p.ui = ui
    81  
    82  	var cmd *packer.RemoteCmd
    83  	command := p.config.RestartCommand
    84  	err := p.retryable(func() error {
    85  		cmd = &packer.RemoteCmd{Command: command}
    86  		return cmd.StartWithUi(comm, ui)
    87  	})
    88  
    89  	if err != nil {
    90  		return err
    91  	}
    92  
    93  	if cmd.ExitStatus != 0 {
    94  		return fmt.Errorf("Restart script exited with non-zero exit status: %d", cmd.ExitStatus)
    95  	}
    96  
    97  	return waitForRestart(p)
    98  }
    99  
   100  var waitForRestart = func(p *Provisioner) error {
   101  	ui := p.ui
   102  	ui.Say("Waiting for machine to restart...")
   103  	waitDone := make(chan bool, 1)
   104  	timeout := time.After(p.config.RestartTimeout)
   105  	var err error
   106  
   107  	go func() {
   108  		log.Printf("Waiting for machine to become available...")
   109  		err = waitForCommunicator(p)
   110  		waitDone <- true
   111  	}()
   112  
   113  	log.Printf("Waiting for machine to reboot with timeout: %s", p.config.RestartTimeout)
   114  
   115  WaitLoop:
   116  	for {
   117  		// Wait for either WinRM to become available, a timeout to occur,
   118  		// or an interrupt to come through.
   119  		select {
   120  		case <-waitDone:
   121  			if err != nil {
   122  				ui.Error(fmt.Sprintf("Error waiting for WinRM: %s", err))
   123  				return err
   124  			}
   125  
   126  			ui.Say("Machine successfully restarted, moving on")
   127  			close(p.cancel)
   128  			break WaitLoop
   129  		case <-timeout:
   130  			err := fmt.Errorf("Timeout waiting for WinRM.")
   131  			ui.Error(err.Error())
   132  			close(p.cancel)
   133  			return err
   134  		case <-p.cancel:
   135  			close(waitDone)
   136  			return fmt.Errorf("Interrupt detected, quitting waiting for machine to restart")
   137  			break WaitLoop
   138  		}
   139  	}
   140  
   141  	return nil
   142  
   143  }
   144  
   145  var waitForCommunicator = func(p *Provisioner) error {
   146  	cmd := &packer.RemoteCmd{Command: p.config.RestartCheckCommand}
   147  
   148  	for {
   149  		select {
   150  		case <-p.cancel:
   151  			log.Println("Communicator wait cancelled, exiting loop")
   152  			return fmt.Errorf("Communicator wait cancelled")
   153  		case <-time.After(retryableSleep):
   154  		}
   155  
   156  		log.Printf("Attempting to communicator to machine with: '%s'", cmd.Command)
   157  
   158  		err := cmd.StartWithUi(p.comm, p.ui)
   159  		if err != nil {
   160  			log.Printf("Communication connection err: %s", err)
   161  			continue
   162  		}
   163  
   164  		log.Printf("Connected to machine")
   165  		break
   166  	}
   167  
   168  	return nil
   169  }
   170  
   171  func (p *Provisioner) Cancel() {
   172  	log.Printf("Received interrupt Cancel()")
   173  
   174  	p.cancelLock.Lock()
   175  	defer p.cancelLock.Unlock()
   176  	if p.cancel != nil {
   177  		close(p.cancel)
   178  	}
   179  }
   180  
   181  // retryable will retry the given function over and over until a
   182  // non-error is returned.
   183  func (p *Provisioner) retryable(f func() error) error {
   184  	startTimeout := time.After(p.config.RestartTimeout)
   185  	for {
   186  		var err error
   187  		if err = f(); err == nil {
   188  			return nil
   189  		}
   190  
   191  		// Create an error and log it
   192  		err = fmt.Errorf("Retryable error: %s", err)
   193  		log.Printf(err.Error())
   194  
   195  		// Check if we timed out, otherwise we retry. It is safe to
   196  		// retry since the only error case above is if the command
   197  		// failed to START.
   198  		select {
   199  		case <-startTimeout:
   200  			return err
   201  		default:
   202  			time.Sleep(retryableSleep)
   203  		}
   204  	}
   205  }