github.com/mmcquillan/packer@v1.1.1-0.20171009221028-c85cf0483a5d/helper/communicator/step_connect_winrm.go (about) 1 package communicator 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "log" 9 "strings" 10 "time" 11 12 "github.com/hashicorp/packer/communicator/winrm" 13 "github.com/hashicorp/packer/packer" 14 winrmcmd "github.com/masterzen/winrm" 15 "github.com/mitchellh/multistep" 16 ) 17 18 // StepConnectWinRM is a multistep Step implementation that waits for WinRM 19 // to become available. It gets the connection information from a single 20 // configuration when creating the step. 21 // 22 // Uses: 23 // ui packer.Ui 24 // 25 // Produces: 26 // communicator packer.Communicator 27 type StepConnectWinRM struct { 28 // All the fields below are documented on StepConnect 29 Config *Config 30 Host func(multistep.StateBag) (string, error) 31 WinRMConfig func(multistep.StateBag) (*WinRMConfig, error) 32 WinRMPort func(multistep.StateBag) (int, error) 33 } 34 35 func (s *StepConnectWinRM) Run(state multistep.StateBag) multistep.StepAction { 36 ui := state.Get("ui").(packer.Ui) 37 38 var comm packer.Communicator 39 var err error 40 41 cancel := make(chan struct{}) 42 waitDone := make(chan bool, 1) 43 go func() { 44 ui.Say("Waiting for WinRM to become available...") 45 comm, err = s.waitForWinRM(state, cancel) 46 waitDone <- true 47 }() 48 49 log.Printf("Waiting for WinRM, up to timeout: %s", s.Config.WinRMTimeout) 50 timeout := time.After(s.Config.WinRMTimeout) 51 WaitLoop: 52 for { 53 // Wait for either WinRM to become available, a timeout to occur, 54 // or an interrupt to come through. 55 select { 56 case <-waitDone: 57 if err != nil { 58 ui.Error(fmt.Sprintf("Error waiting for WinRM: %s", err)) 59 return multistep.ActionHalt 60 } 61 62 ui.Say("Connected to WinRM!") 63 state.Put("communicator", comm) 64 break WaitLoop 65 case <-timeout: 66 err := fmt.Errorf("Timeout waiting for WinRM.") 67 state.Put("error", err) 68 ui.Error(err.Error()) 69 close(cancel) 70 return multistep.ActionHalt 71 case <-time.After(1 * time.Second): 72 if _, ok := state.GetOk(multistep.StateCancelled); ok { 73 // The step sequence was cancelled, so cancel waiting for WinRM 74 // and just start the halting process. 75 close(cancel) 76 log.Println("Interrupt detected, quitting waiting for WinRM.") 77 return multistep.ActionHalt 78 } 79 } 80 } 81 82 return multistep.ActionContinue 83 } 84 85 func (s *StepConnectWinRM) Cleanup(multistep.StateBag) { 86 } 87 88 func (s *StepConnectWinRM) waitForWinRM(state multistep.StateBag, cancel <-chan struct{}) (packer.Communicator, error) { 89 var comm packer.Communicator 90 for { 91 select { 92 case <-cancel: 93 log.Println("[INFO] WinRM wait cancelled. Exiting loop.") 94 return nil, errors.New("WinRM wait cancelled") 95 case <-time.After(5 * time.Second): 96 } 97 98 host, err := s.Host(state) 99 if err != nil { 100 log.Printf("[DEBUG] Error getting WinRM host: %s", err) 101 continue 102 } 103 104 port := s.Config.WinRMPort 105 if s.WinRMPort != nil { 106 port, err = s.WinRMPort(state) 107 if err != nil { 108 log.Printf("[DEBUG] Error getting WinRM port: %s", err) 109 continue 110 } 111 } 112 113 user := s.Config.WinRMUser 114 password := s.Config.WinRMPassword 115 if s.WinRMConfig != nil { 116 config, err := s.WinRMConfig(state) 117 if err != nil { 118 log.Printf("[DEBUG] Error getting WinRM config: %s", err) 119 continue 120 } 121 122 if config.Username != "" { 123 user = config.Username 124 } 125 if config.Password != "" { 126 password = config.Password 127 } 128 } 129 130 log.Println("[INFO] Attempting WinRM connection...") 131 comm, err = winrm.New(&winrm.Config{ 132 Host: host, 133 Port: port, 134 Username: user, 135 Password: password, 136 Timeout: s.Config.WinRMTimeout, 137 Https: s.Config.WinRMUseSSL, 138 Insecure: s.Config.WinRMInsecure, 139 TransportDecorator: s.Config.WinRMTransportDecorator, 140 }) 141 if err != nil { 142 log.Printf("[ERROR] WinRM connection err: %s", err) 143 continue 144 } 145 146 break 147 } 148 // run an "echo" command to make sure winrm is actually connected before moving on. 149 var connectCheckCommand = winrmcmd.Powershell(`if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'}; echo "WinRM connected."`) 150 var retryableSleep = 5 * time.Second 151 // run an "echo" command to make sure that the winrm is connected 152 for { 153 cmd := &packer.RemoteCmd{Command: connectCheckCommand} 154 var buf, buf2 bytes.Buffer 155 cmd.Stdout = &buf 156 cmd.Stdout = io.MultiWriter(cmd.Stdout, &buf2) 157 select { 158 case <-cancel: 159 log.Println("WinRM wait canceled, exiting loop") 160 return comm, fmt.Errorf("WinRM wait canceled") 161 case <-time.After(retryableSleep): 162 } 163 164 log.Printf("Checking that WinRM is connected with: '%s'", connectCheckCommand) 165 ui := state.Get("ui").(packer.Ui) 166 err := cmd.StartWithUi(comm, ui) 167 168 if err != nil { 169 log.Printf("Communication connection err: %s", err) 170 continue 171 } 172 173 log.Printf("Connected to machine") 174 stdoutToRead := buf2.String() 175 if !strings.Contains(stdoutToRead, "WinRM connected.") { 176 log.Printf("echo didn't succeed; retrying...") 177 continue 178 } 179 break 180 } 181 182 return comm, nil 183 }