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 }