github.com/jerryclinesmith/packer@v0.3.7/common/step_connect_ssh.go (about) 1 package common 2 3 import ( 4 gossh "code.google.com/p/go.crypto/ssh" 5 "errors" 6 "fmt" 7 "github.com/mitchellh/multistep" 8 "github.com/mitchellh/packer/communicator/ssh" 9 "github.com/mitchellh/packer/packer" 10 "log" 11 "strings" 12 "time" 13 ) 14 15 // StepConnectSSH is a multistep Step implementation that waits for SSH 16 // to become available. It gets the connection information from a single 17 // configuration when creating the step. 18 // 19 // Uses: 20 // ui packer.Ui 21 // 22 // Produces: 23 // communicator packer.Communicator 24 type StepConnectSSH struct { 25 // SSHAddress is a function that returns the TCP address to connect to 26 // for SSH. This is a function so that you can query information 27 // if necessary for this address. 28 SSHAddress func(multistep.StateBag) (string, error) 29 30 // SSHConfig is a function that returns the proper client configuration 31 // for SSH access. 32 SSHConfig func(multistep.StateBag) (*gossh.ClientConfig, error) 33 34 // SSHWaitTimeout is the total timeout to wait for SSH to become available. 35 SSHWaitTimeout time.Duration 36 37 // NoPty, if true, will not request a Pty from the remote end. 38 NoPty bool 39 40 comm packer.Communicator 41 } 42 43 func (s *StepConnectSSH) Run(state multistep.StateBag) multistep.StepAction { 44 ui := state.Get("ui").(packer.Ui) 45 46 var comm packer.Communicator 47 var err error 48 49 cancel := make(chan struct{}) 50 waitDone := make(chan bool, 1) 51 go func() { 52 ui.Say("Waiting for SSH to become available...") 53 comm, err = s.waitForSSH(state, cancel) 54 waitDone <- true 55 }() 56 57 log.Printf("Waiting for SSH, up to timeout: %s", s.SSHWaitTimeout) 58 timeout := time.After(s.SSHWaitTimeout) 59 WaitLoop: 60 for { 61 // Wait for either SSH to become available, a timeout to occur, 62 // or an interrupt to come through. 63 select { 64 case <-waitDone: 65 if err != nil { 66 ui.Error(fmt.Sprintf("Error waiting for SSH: %s", err)) 67 return multistep.ActionHalt 68 } 69 70 ui.Say("Connected to SSH!") 71 s.comm = comm 72 state.Put("communicator", comm) 73 break WaitLoop 74 case <-timeout: 75 ui.Error("Timeout waiting for SSH.") 76 close(cancel) 77 return multistep.ActionHalt 78 case <-time.After(1 * time.Second): 79 if _, ok := state.GetOk(multistep.StateCancelled); ok { 80 // The step sequence was cancelled, so cancel waiting for SSH 81 // and just start the halting process. 82 close(cancel) 83 log.Println("Interrupt detected, quitting waiting for SSH.") 84 return multistep.ActionHalt 85 } 86 } 87 } 88 89 return multistep.ActionContinue 90 } 91 92 func (s *StepConnectSSH) Cleanup(multistep.StateBag) { 93 } 94 95 func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan struct{}) (packer.Communicator, error) { 96 handshakeAttempts := 0 97 98 var comm packer.Communicator 99 for { 100 select { 101 case <-cancel: 102 log.Println("SSH wait cancelled. Exiting loop.") 103 return nil, errors.New("SSH wait cancelled") 104 case <-time.After(5 * time.Second): 105 } 106 107 // First we request the TCP connection information 108 address, err := s.SSHAddress(state) 109 if err != nil { 110 log.Printf("Error getting SSH address: %s", err) 111 continue 112 } 113 114 // Retrieve the SSH configuration 115 sshConfig, err := s.SSHConfig(state) 116 if err != nil { 117 log.Printf("Error getting SSH config: %s", err) 118 continue 119 } 120 121 // Attempt to connect to SSH port 122 connFunc := ssh.ConnectFunc("tcp", address) 123 nc, err := connFunc() 124 if err != nil { 125 log.Printf("TCP connection to SSH ip/port failed: %s", err) 126 continue 127 } 128 nc.Close() 129 130 // Then we attempt to connect via SSH 131 config := &ssh.Config{ 132 Connection: connFunc, 133 SSHConfig: sshConfig, 134 NoPty: s.NoPty, 135 } 136 137 log.Println("Attempting SSH connection...") 138 comm, err = ssh.New(config) 139 if err != nil { 140 log.Printf("SSH handshake err: %s", err) 141 142 // Only count this as an attempt if we were able to attempt 143 // to authenticate. Note this is very brittle since it depends 144 // on the string of the error... but I don't see any other way. 145 if strings.Contains(err.Error(), "authenticate") { 146 log.Printf("Detected authentication error. Increasing handshake attempts.") 147 handshakeAttempts += 1 148 } 149 150 if handshakeAttempts < 10 { 151 // Try to connect via SSH a handful of times 152 continue 153 } 154 155 return nil, err 156 } 157 158 break 159 } 160 161 return comm, nil 162 }