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