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  }