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 }