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  }