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