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