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