github.com/askholme/packer@v0.7.2-0.20140924152349-70d9566a6852/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 err := fmt.Errorf("Timeout waiting for SSH.") 76 state.Put("error", err) 77 ui.Error(err.Error()) 78 close(cancel) 79 return multistep.ActionHalt 80 case <-time.After(1 * time.Second): 81 if _, ok := state.GetOk(multistep.StateCancelled); ok { 82 // The step sequence was cancelled, so cancel waiting for SSH 83 // and just start the halting process. 84 close(cancel) 85 log.Println("Interrupt detected, quitting waiting for SSH.") 86 return multistep.ActionHalt 87 } 88 } 89 } 90 91 return multistep.ActionContinue 92 } 93 94 func (s *StepConnectSSH) Cleanup(multistep.StateBag) { 95 } 96 97 func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan struct{}) (packer.Communicator, error) { 98 handshakeAttempts := 0 99 100 var comm packer.Communicator 101 first := true 102 for { 103 // Don't check for cancel or wait on first iteration 104 if !first { 105 select { 106 case <-cancel: 107 log.Println("SSH wait cancelled. Exiting loop.") 108 return nil, errors.New("SSH wait cancelled") 109 case <-time.After(5 * time.Second): 110 } 111 } 112 first = false 113 114 // First we request the TCP connection information 115 address, err := s.SSHAddress(state) 116 if err != nil { 117 log.Printf("Error getting SSH address: %s", err) 118 continue 119 } 120 121 // Retrieve the SSH configuration 122 sshConfig, err := s.SSHConfig(state) 123 if err != nil { 124 log.Printf("Error getting SSH config: %s", err) 125 continue 126 } 127 128 // Attempt to connect to SSH port 129 connFunc := ssh.ConnectFunc("tcp", address) 130 nc, err := connFunc() 131 if err != nil { 132 log.Printf("TCP connection to SSH ip/port failed: %s", err) 133 continue 134 } 135 nc.Close() 136 137 // Then we attempt to connect via SSH 138 config := &ssh.Config{ 139 Connection: connFunc, 140 SSHConfig: sshConfig, 141 NoPty: s.NoPty, 142 } 143 144 log.Println("Attempting SSH connection...") 145 comm, err = ssh.New(address, config) 146 if err != nil { 147 log.Printf("SSH handshake err: %s", err) 148 149 // Only count this as an attempt if we were able to attempt 150 // to authenticate. Note this is very brittle since it depends 151 // on the string of the error... but I don't see any other way. 152 if strings.Contains(err.Error(), "authenticate") { 153 log.Printf("Detected authentication error. Increasing handshake attempts.") 154 handshakeAttempts += 1 155 } 156 157 if handshakeAttempts < 10 { 158 // Try to connect via SSH a handful of times 159 continue 160 } 161 162 return nil, err 163 } 164 165 break 166 } 167 168 return comm, nil 169 }