packer-cn/helper/communicator/step_connect_winrm.go
Christopher Boumenot b57ed27352 Add support for NTLM the WinRM communicator.
WinRM exposes an HTTP transport decorator that can be used for different
authentication schemes.  Windows on Azures requires this if one is to use
the out of the box configuration.
2016-03-10 10:53:38 -08:00

146 lines
3.6 KiB
Go

package communicator
import (
"errors"
"fmt"
"log"
"time"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/communicator/winrm"
"github.com/mitchellh/packer/packer"
)
// StepConnectWinRM is a multistep Step implementation that waits for WinRM
// to become available. It gets the connection information from a single
// configuration when creating the step.
//
// Uses:
// ui packer.Ui
//
// Produces:
// communicator packer.Communicator
type StepConnectWinRM struct {
// All the fields below are documented on StepConnect
Config *Config
Host func(multistep.StateBag) (string, error)
WinRMConfig func(multistep.StateBag) (*WinRMConfig, error)
WinRMPort func(multistep.StateBag) (int, error)
}
func (s *StepConnectWinRM) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
var comm packer.Communicator
var err error
cancel := make(chan struct{})
waitDone := make(chan bool, 1)
go func() {
ui.Say("Waiting for WinRM to become available...")
comm, err = s.waitForWinRM(state, cancel)
waitDone <- true
}()
log.Printf("Waiting for WinRM, up to timeout: %s", s.Config.WinRMTimeout)
timeout := time.After(s.Config.WinRMTimeout)
WaitLoop:
for {
// Wait for either WinRM to become available, a timeout to occur,
// or an interrupt to come through.
select {
case <-waitDone:
if err != nil {
ui.Error(fmt.Sprintf("Error waiting for WinRM: %s", err))
return multistep.ActionHalt
}
ui.Say("Connected to WinRM!")
state.Put("communicator", comm)
break WaitLoop
case <-timeout:
err := fmt.Errorf("Timeout waiting for WinRM.")
state.Put("error", err)
ui.Error(err.Error())
close(cancel)
return multistep.ActionHalt
case <-time.After(1 * time.Second):
if _, ok := state.GetOk(multistep.StateCancelled); ok {
// The step sequence was cancelled, so cancel waiting for WinRM
// and just start the halting process.
close(cancel)
log.Println("Interrupt detected, quitting waiting for WinRM.")
return multistep.ActionHalt
}
}
}
return multistep.ActionContinue
}
func (s *StepConnectWinRM) Cleanup(multistep.StateBag) {
}
func (s *StepConnectWinRM) waitForWinRM(state multistep.StateBag, cancel <-chan struct{}) (packer.Communicator, error) {
var comm packer.Communicator
for {
select {
case <-cancel:
log.Println("[INFO] WinRM wait cancelled. Exiting loop.")
return nil, errors.New("WinRM wait cancelled")
case <-time.After(5 * time.Second):
}
host, err := s.Host(state)
if err != nil {
log.Printf("[DEBUG] Error getting WinRM host: %s", err)
continue
}
port := s.Config.WinRMPort
if s.WinRMPort != nil {
port, err = s.WinRMPort(state)
if err != nil {
log.Printf("[DEBUG] Error getting WinRM port: %s", err)
continue
}
}
user := s.Config.WinRMUser
password := s.Config.WinRMPassword
if s.WinRMConfig != nil {
config, err := s.WinRMConfig(state)
if err != nil {
log.Printf("[DEBUG] Error getting WinRM config: %s", err)
continue
}
if config.Username != "" {
user = config.Username
}
if config.Password != "" {
password = config.Password
}
}
log.Println("[INFO] Attempting WinRM connection...")
comm, err = winrm.New(&winrm.Config{
Host: host,
Port: port,
Username: user,
Password: password,
Timeout: s.Config.WinRMTimeout,
Https: s.Config.WinRMUseSSL,
Insecure: s.Config.WinRMInsecure,
TransportDecorator: s.Config.WinRMTransportDecorator,
})
if err != nil {
log.Printf("[ERROR] WinRM connection err: %s", err)
continue
}
break
}
return comm, nil
}