Merge branch 'common-ssh'
This unifies all the builders so that they all wait for SSH using a common step available in builder/common. This common step has the best practices of every previously unique step, such as retrying SSH handshakes, limiting the number of SSH handshakes, timeouts, and so on.
This commit is contained in:
commit
91c1434764
|
@ -27,8 +27,10 @@ FEATURES:
|
|||
|
||||
IMPROVEMENTS:
|
||||
|
||||
* everything: invalid keys in configuration are now considered validation
|
||||
* core: invalid keys in configuration are now considered validation
|
||||
errors. [GH-104]
|
||||
* core: all builders now share a common SSH connection core, improving
|
||||
SSH reliability over all the builders.
|
||||
* amazon-ebs: Credentials will come from IAM role if available. [GH-160]
|
||||
* amazon-ebs: Verify the source AMI is EBS-backed before launching. [GH-169]
|
||||
* shell provisioner: the build name and builder type are available in
|
||||
|
|
|
@ -159,7 +159,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
&stepKeyPair{},
|
||||
&stepSecurityGroup{},
|
||||
&stepRunSourceInstance{},
|
||||
&stepConnectSSH{},
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: sshAddress,
|
||||
SSHConfig: sshConfig,
|
||||
SSHWaitTimeout: b.config.sshTimeout,
|
||||
},
|
||||
&stepProvision{},
|
||||
&stepStopInstance{},
|
||||
&stepCreateAMI{},
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package amazonebs
|
||||
|
||||
import (
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
)
|
||||
|
||||
func sshAddress(state map[string]interface{}) (string, error) {
|
||||
config := state["config"].(config)
|
||||
instance := state["instance"].(*ec2.Instance)
|
||||
return fmt.Sprintf("%s:%d", instance.DNSName, config.SSHPort), nil
|
||||
}
|
||||
|
||||
func sshConfig(state map[string]interface{}) (*gossh.ClientConfig, error) {
|
||||
config := state["config"].(config)
|
||||
privateKey := state["privateKey"].(string)
|
||||
|
||||
keyring := new(ssh.SimpleKeychain)
|
||||
if err := keyring.AddPEMKey(privateKey); err != nil {
|
||||
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
|
||||
}
|
||||
|
||||
return &gossh.ClientConfig{
|
||||
User: config.SSHUsername,
|
||||
Auth: []gossh.ClientAuth{
|
||||
gossh.ClientAuthKeyring(keyring),
|
||||
},
|
||||
}, nil
|
||||
}
|
|
@ -1,149 +0,0 @@
|
|||
package amazonebs
|
||||
|
||||
import (
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
type stepConnectSSH struct {
|
||||
cancel bool
|
||||
comm packer.Communicator
|
||||
}
|
||||
|
||||
func (s *stepConnectSSH) Run(state map[string]interface{}) multistep.StepAction {
|
||||
config := state["config"].(config)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
var comm packer.Communicator
|
||||
var err error
|
||||
|
||||
waitDone := make(chan bool, 1)
|
||||
go func() {
|
||||
comm, err = s.waitForSSH(state)
|
||||
waitDone <- true
|
||||
}()
|
||||
|
||||
log.Printf("Waiting for SSH, up to timeout: %s", config.sshTimeout.String())
|
||||
|
||||
timeout := time.After(config.sshTimeout)
|
||||
WaitLoop:
|
||||
for {
|
||||
// Wait for either SSH to become available, a timeout to occur,
|
||||
// or an interrupt to come through.
|
||||
select {
|
||||
case <-waitDone:
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error waiting for SSH: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.comm = comm
|
||||
state["communicator"] = comm
|
||||
break WaitLoop
|
||||
case <-timeout:
|
||||
ui.Error("Timeout waiting for SSH.")
|
||||
s.cancel = true
|
||||
return multistep.ActionHalt
|
||||
case <-time.After(1 * time.Second):
|
||||
if _, ok := state[multistep.StateCancelled]; ok {
|
||||
log.Println("Interrupt detected, quitting waiting for SSH.")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepConnectSSH) Cleanup(map[string]interface{}) {
|
||||
if s.comm != nil {
|
||||
// Close it TODO
|
||||
s.comm = nil
|
||||
}
|
||||
}
|
||||
|
||||
// This blocks until SSH becomes available, and sends the communicator
|
||||
// on the given channel.
|
||||
func (s *stepConnectSSH) waitForSSH(state map[string]interface{}) (packer.Communicator, error) {
|
||||
config := state["config"].(config)
|
||||
instance := state["instance"].(*ec2.Instance)
|
||||
privateKey := state["privateKey"].(string)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
// Build the keyring for authentication. This stores the private key
|
||||
// we'll use to authenticate.
|
||||
keyring := &ssh.SimpleKeychain{}
|
||||
err := keyring.AddPEMKey(privateKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
|
||||
}
|
||||
|
||||
// Create the function that will be used to create the connection
|
||||
connFunc := ssh.ConnectFunc(
|
||||
"tcp", fmt.Sprintf("%s:%d", instance.DNSName, config.SSHPort), 5*time.Minute)
|
||||
|
||||
ui.Say("Waiting for SSH to become available...")
|
||||
var comm packer.Communicator
|
||||
for {
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
if s.cancel {
|
||||
log.Println("SSH wait cancelled. Exiting loop.")
|
||||
return nil, errors.New("SSH wait cancelled")
|
||||
}
|
||||
|
||||
// First just attempt a normal TCP connection that we close right
|
||||
// away. We just test this in order to wait for the TCP port to be ready.
|
||||
nc, err := connFunc()
|
||||
if err != nil {
|
||||
log.Printf("TCP connection to SSH ip/port failed: %s", err)
|
||||
continue
|
||||
}
|
||||
nc.Close()
|
||||
|
||||
// Build the configuration to connect to SSH
|
||||
config := &ssh.Config{
|
||||
Connection: connFunc,
|
||||
SSHConfig: &gossh.ClientConfig{
|
||||
User: config.SSHUsername,
|
||||
Auth: []gossh.ClientAuth{
|
||||
gossh.ClientAuthKeyring(keyring),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
sshConnectSuccess := make(chan bool, 1)
|
||||
go func() {
|
||||
comm, err = ssh.New(config)
|
||||
if err != nil {
|
||||
log.Printf("SSH connection fail: %s", err)
|
||||
sshConnectSuccess <- false
|
||||
return
|
||||
}
|
||||
|
||||
sshConnectSuccess <- true
|
||||
}()
|
||||
|
||||
select {
|
||||
case success := <-sshConnectSuccess:
|
||||
if !success {
|
||||
continue
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
log.Printf("SSH handshake timeout. Trying again.")
|
||||
continue
|
||||
}
|
||||
|
||||
ui.Say("Connected via SSH!")
|
||||
break
|
||||
}
|
||||
|
||||
return comm, nil
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
// StepConnectSSH is a multistep Step implementation that waits for SSH
|
||||
// to become available. It gets the connection information from a single
|
||||
// configuration when creating the step.
|
||||
//
|
||||
// Uses:
|
||||
// ui packer.Ui
|
||||
//
|
||||
// Produces:
|
||||
// communicator packer.Communicator
|
||||
type StepConnectSSH struct {
|
||||
// SSHAddress is a function that returns the TCP address to connect to
|
||||
// for SSH. This is a function so that you can query information
|
||||
// if necessary for this address.
|
||||
SSHAddress func(map[string]interface{}) (string, error)
|
||||
|
||||
// SSHConfig is a function that returns the proper client configuration
|
||||
// for SSH access.
|
||||
SSHConfig func(map[string]interface{}) (*gossh.ClientConfig, error)
|
||||
|
||||
// SSHWaitTimeout is the total timeout to wait for SSH to become available.
|
||||
SSHWaitTimeout time.Duration
|
||||
|
||||
cancel bool
|
||||
comm packer.Communicator
|
||||
}
|
||||
|
||||
func (s *StepConnectSSH) Run(state map[string]interface{}) multistep.StepAction {
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
var comm packer.Communicator
|
||||
var err error
|
||||
|
||||
waitDone := make(chan bool, 1)
|
||||
go func() {
|
||||
ui.Say("Waiting for SSH to become available...")
|
||||
comm, err = s.waitForSSH(state)
|
||||
waitDone <- true
|
||||
}()
|
||||
|
||||
log.Printf("Waiting for SSH, up to timeout: %s", s.SSHWaitTimeout)
|
||||
timeout := time.After(s.SSHWaitTimeout)
|
||||
WaitLoop:
|
||||
for {
|
||||
// Wait for either SSH to become available, a timeout to occur,
|
||||
// or an interrupt to come through.
|
||||
select {
|
||||
case <-waitDone:
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error waiting for SSH: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say("Connected to SSH!")
|
||||
s.comm = comm
|
||||
state["communicator"] = comm
|
||||
break WaitLoop
|
||||
case <-timeout:
|
||||
ui.Error("Timeout waiting for SSH.")
|
||||
s.cancel = true
|
||||
return multistep.ActionHalt
|
||||
case <-time.After(1 * time.Second):
|
||||
if _, ok := state[multistep.StateCancelled]; ok {
|
||||
// The step sequence was cancelled, so cancel waiting for SSH
|
||||
// and just start the halting process.
|
||||
s.cancel = true
|
||||
log.Println("Interrupt detected, quitting waiting for SSH.")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepConnectSSH) Cleanup(map[string]interface{}) {
|
||||
}
|
||||
|
||||
func (s *StepConnectSSH) waitForSSH(state map[string]interface{}) (packer.Communicator, error) {
|
||||
handshakeAttempts := 0
|
||||
|
||||
var comm packer.Communicator
|
||||
for {
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
if s.cancel {
|
||||
log.Println("SSH wait cancelled. Exiting loop.")
|
||||
return nil, errors.New("SSH wait cancelled")
|
||||
}
|
||||
|
||||
// First we request the TCP connection information
|
||||
address, err := s.SSHAddress(state)
|
||||
if err != nil {
|
||||
log.Printf("Error getting SSH address: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Retrieve the SSH configuration
|
||||
sshConfig, err := s.SSHConfig(state)
|
||||
if err != nil {
|
||||
log.Printf("Error getting SSH config: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Attempt to connect to SSH port
|
||||
connFunc := ssh.ConnectFunc("tcp", address, 5*time.Minute)
|
||||
nc, err := connFunc()
|
||||
if err != nil {
|
||||
log.Printf("TCP connection to SSH ip/port failed: %s", err)
|
||||
continue
|
||||
}
|
||||
nc.Close()
|
||||
|
||||
// Then we attempt to connect via SSH
|
||||
config := &ssh.Config{
|
||||
Connection: connFunc,
|
||||
SSHConfig: sshConfig,
|
||||
}
|
||||
|
||||
comm, err = ssh.New(config)
|
||||
if err != nil {
|
||||
log.Printf("SSH handshake err: %s", err)
|
||||
|
||||
handshakeAttempts += 1
|
||||
if handshakeAttempts < 10 {
|
||||
// Try to connect via SSH a handful of times
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
return comm, nil
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStepConnectSSH_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(StepConnectSSH)
|
||||
if _, ok := raw.(multistep.Step); !ok {
|
||||
t.Fatalf("connect ssh should be a step")
|
||||
}
|
||||
}
|
|
@ -216,7 +216,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
new(stepCreateSSHKey),
|
||||
new(stepCreateDroplet),
|
||||
new(stepDropletInfo),
|
||||
new(stepConnectSSH),
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: sshAddress,
|
||||
SSHConfig: sshConfig,
|
||||
SSHWaitTimeout: 5 * time.Minute,
|
||||
},
|
||||
new(stepProvision),
|
||||
new(stepPowerOff),
|
||||
new(stepSnapshot),
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package digitalocean
|
||||
|
||||
import (
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
)
|
||||
|
||||
func sshAddress(state map[string]interface{}) (string, error) {
|
||||
config := state["config"].(config)
|
||||
ipAddress := state["droplet_ip"].(string)
|
||||
return fmt.Sprintf("%s:%d", ipAddress, config.SSHPort), nil
|
||||
}
|
||||
|
||||
func sshConfig(state map[string]interface{}) (*gossh.ClientConfig, error) {
|
||||
config := state["config"].(config)
|
||||
privateKey := state["privateKey"].(string)
|
||||
|
||||
keyring := new(ssh.SimpleKeychain)
|
||||
if err := keyring.AddPEMKey(privateKey); err != nil {
|
||||
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
|
||||
}
|
||||
|
||||
return &gossh.ClientConfig{
|
||||
User: config.SSHUsername,
|
||||
Auth: []gossh.ClientAuth{
|
||||
gossh.ClientAuthKeyring(keyring),
|
||||
},
|
||||
}, nil
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
package digitalocean
|
||||
|
||||
import (
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
type stepConnectSSH struct {
|
||||
comm packer.Communicator
|
||||
}
|
||||
|
||||
func (s *stepConnectSSH) Run(state map[string]interface{}) multistep.StepAction {
|
||||
config := state["config"].(config)
|
||||
privateKey := state["privateKey"].(string)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
ipAddress := state["droplet_ip"]
|
||||
|
||||
// Build the keyring for authentication. This stores the private key
|
||||
// we'll use to authenticate.
|
||||
keyring := &ssh.SimpleKeychain{}
|
||||
err := keyring.AddPEMKey(privateKey)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error setting up SSH config: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
connFunc := ssh.ConnectFunc(
|
||||
"tcp",
|
||||
fmt.Sprintf("%s:%d", ipAddress, config.SSHPort),
|
||||
5*time.Minute)
|
||||
|
||||
// Build the actual SSH client configuration
|
||||
sshConfig := &ssh.Config{
|
||||
Connection: connFunc,
|
||||
SSHConfig: &gossh.ClientConfig{
|
||||
User: config.SSHUsername,
|
||||
Auth: []gossh.ClientAuth{
|
||||
gossh.ClientAuthKeyring(keyring),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Start trying to connect to SSH
|
||||
connected := make(chan error, 1)
|
||||
connectQuit := make(chan bool, 1)
|
||||
defer func() {
|
||||
connectQuit <- true
|
||||
}()
|
||||
|
||||
var comm packer.Communicator
|
||||
go func() {
|
||||
ui.Say("Connecting to the droplet via SSH...")
|
||||
attempts := 0
|
||||
handshakeAttempts := 0
|
||||
for {
|
||||
select {
|
||||
case <-connectQuit:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
// A brief sleep so we're not being overly zealous attempting
|
||||
// to connect to the instance.
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
attempts += 1
|
||||
nc, err := connFunc()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
nc.Close()
|
||||
|
||||
log.Println("TCP connection made. Attempting SSH handshake.")
|
||||
comm, err = ssh.New(sshConfig)
|
||||
if err == nil {
|
||||
log.Println("Connected to SSH!")
|
||||
break
|
||||
}
|
||||
|
||||
handshakeAttempts += 1
|
||||
log.Printf("SSH handshake error: %s", err)
|
||||
|
||||
if handshakeAttempts > 5 {
|
||||
connected <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
connected <- nil
|
||||
}()
|
||||
|
||||
log.Printf("Waiting up to %s for SSH connection", config.sshTimeout)
|
||||
timeout := time.After(config.sshTimeout)
|
||||
|
||||
ConnectWaitLoop:
|
||||
for {
|
||||
select {
|
||||
case err := <-connected:
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error connecting to SSH: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// We connected. Just break the loop.
|
||||
break ConnectWaitLoop
|
||||
case <-timeout:
|
||||
err := errors.New("Timeout waiting for SSH to become available.")
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
case <-time.After(1 * time.Second):
|
||||
if _, ok := state[multistep.StateCancelled]; ok {
|
||||
log.Println("Interrupt detected, quitting waiting for SSH.")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the communicator on the state bag so it can be used later
|
||||
s.comm = comm
|
||||
state["communicator"] = comm
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepConnectSSH) Cleanup(map[string]interface{}) {
|
||||
if s.comm != nil {
|
||||
// TODO: close
|
||||
s.comm = nil
|
||||
}
|
||||
}
|
|
@ -323,7 +323,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
new(stepVBoxManage),
|
||||
new(stepRun),
|
||||
new(stepTypeBootCommand),
|
||||
new(stepWaitForSSH),
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: sshAddress,
|
||||
SSHConfig: sshConfig,
|
||||
SSHWaitTimeout: b.config.sshWaitTimeout,
|
||||
},
|
||||
new(stepUploadVersion),
|
||||
new(stepUploadGuestAdditions),
|
||||
new(stepProvision),
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package virtualbox
|
||||
|
||||
import (
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
)
|
||||
|
||||
func sshAddress(state map[string]interface{}) (string, error) {
|
||||
sshHostPort := state["sshHostPort"].(uint)
|
||||
return fmt.Sprintf("127.0.0.1:%d", sshHostPort), nil
|
||||
}
|
||||
|
||||
func sshConfig(state map[string]interface{}) (*gossh.ClientConfig, error) {
|
||||
config := state["config"].(*config)
|
||||
|
||||
return &gossh.ClientConfig{
|
||||
User: config.SSHUser,
|
||||
Auth: []gossh.ClientAuth{
|
||||
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
|
||||
gossh.ClientAuthKeyboardInteractive(
|
||||
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
|
||||
},
|
||||
}, nil
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
package virtualbox
|
||||
|
||||
import (
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
// This step waits for SSH to become available and establishes an SSH
|
||||
// connection.
|
||||
//
|
||||
// Uses:
|
||||
// config *config
|
||||
// sshHostPort uint
|
||||
// ui packer.Ui
|
||||
//
|
||||
// Produces:
|
||||
// communicator packer.Communicator
|
||||
type stepWaitForSSH struct {
|
||||
cancel bool
|
||||
comm packer.Communicator
|
||||
}
|
||||
|
||||
func (s *stepWaitForSSH) Run(state map[string]interface{}) multistep.StepAction {
|
||||
config := state["config"].(*config)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
var comm packer.Communicator
|
||||
var err error
|
||||
|
||||
waitDone := make(chan bool, 1)
|
||||
go func() {
|
||||
comm, err = s.waitForSSH(state)
|
||||
waitDone <- true
|
||||
}()
|
||||
|
||||
log.Printf("Waiting for SSH, up to timeout: %s", config.sshWaitTimeout.String())
|
||||
timeout := time.After(config.sshWaitTimeout)
|
||||
WaitLoop:
|
||||
for {
|
||||
// Wait for either SSH to become available, a timeout to occur,
|
||||
// or an interrupt to come through.
|
||||
select {
|
||||
case <-waitDone:
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error waiting for SSH: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.comm = comm
|
||||
state["communicator"] = comm
|
||||
break WaitLoop
|
||||
case <-timeout:
|
||||
ui.Error("Timeout waiting for SSH.")
|
||||
s.cancel = true
|
||||
return multistep.ActionHalt
|
||||
case <-time.After(1 * time.Second):
|
||||
if _, ok := state[multistep.StateCancelled]; ok {
|
||||
log.Println("Interrupt detected, quitting waiting for SSH.")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepWaitForSSH) Cleanup(map[string]interface{}) {
|
||||
if s.comm != nil {
|
||||
// TODO: close
|
||||
s.comm = nil
|
||||
}
|
||||
}
|
||||
|
||||
// This blocks until SSH becomes available, and sends the communicator
|
||||
// on the given channel.
|
||||
func (s *stepWaitForSSH) waitForSSH(state map[string]interface{}) (packer.Communicator, error) {
|
||||
config := state["config"].(*config)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
sshHostPort := state["sshHostPort"].(uint)
|
||||
|
||||
connFunc := ssh.ConnectFunc(
|
||||
"tcp", fmt.Sprintf("127.0.0.1:%d", sshHostPort), 5*time.Minute)
|
||||
|
||||
ui.Say("Waiting for SSH to become available...")
|
||||
var comm packer.Communicator
|
||||
for {
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
if s.cancel {
|
||||
log.Println("SSH wait cancelled. Exiting loop.")
|
||||
return nil, errors.New("SSH wait cancelled")
|
||||
}
|
||||
|
||||
// Attempt to connect to SSH port
|
||||
nc, err := connFunc()
|
||||
if err != nil {
|
||||
log.Printf("TCP connection to SSH ip/port failed: %s", err)
|
||||
continue
|
||||
}
|
||||
nc.Close()
|
||||
|
||||
// Then we attempt to connect via SSH
|
||||
config := &ssh.Config{
|
||||
Connection: connFunc,
|
||||
SSHConfig: &gossh.ClientConfig{
|
||||
User: config.SSHUser,
|
||||
Auth: []gossh.ClientAuth{
|
||||
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
|
||||
gossh.ClientAuthKeyboardInteractive(
|
||||
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
sshConnectSuccess := make(chan bool, 1)
|
||||
go func() {
|
||||
comm, err = ssh.New(config)
|
||||
if err != nil {
|
||||
log.Printf("SSH connection fail: %s", err)
|
||||
sshConnectSuccess <- false
|
||||
return
|
||||
}
|
||||
|
||||
sshConnectSuccess <- true
|
||||
}()
|
||||
|
||||
select {
|
||||
case success := <-sshConnectSuccess:
|
||||
if !success {
|
||||
continue
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
log.Printf("SSH handshake timeout. Trying again.")
|
||||
continue
|
||||
}
|
||||
|
||||
ui.Say("Connected via SSH!")
|
||||
break
|
||||
}
|
||||
|
||||
return comm, nil
|
||||
}
|
|
@ -284,7 +284,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
&stepConfigureVNC{},
|
||||
&stepRun{},
|
||||
&stepTypeBootCommand{},
|
||||
&stepWaitForSSH{},
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: sshAddress,
|
||||
SSHConfig: sshConfig,
|
||||
SSHWaitTimeout: b.config.sshWaitTimeout,
|
||||
},
|
||||
&stepUploadTools{},
|
||||
&stepProvision{},
|
||||
&stepShutdown{},
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
package vmware
|
||||
|
||||
import (
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func sshAddress(state map[string]interface{}) (string, error) {
|
||||
config := state["config"].(*config)
|
||||
vmxPath := state["vmx_path"].(string)
|
||||
|
||||
log.Println("Lookup up IP information...")
|
||||
f, err := os.Open(vmxPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
vmxBytes, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
vmxData := ParseVMX(string(vmxBytes))
|
||||
|
||||
var ok bool
|
||||
macAddress := ""
|
||||
if macAddress, ok = vmxData["ethernet0.address"]; !ok || macAddress == "" {
|
||||
if macAddress, ok = vmxData["ethernet0.generatedAddress"]; !ok || macAddress == "" {
|
||||
return "", errors.New("couldn't find MAC address in VMX")
|
||||
}
|
||||
}
|
||||
|
||||
ipLookup := &DHCPLeaseGuestLookup{
|
||||
Device: "vmnet8",
|
||||
MACAddress: macAddress,
|
||||
}
|
||||
|
||||
ipAddress, err := ipLookup.GuestIP()
|
||||
if err != nil {
|
||||
log.Printf("IP lookup failed: %s", err)
|
||||
return "", fmt.Errorf("IP lookup failed: %s", err)
|
||||
}
|
||||
|
||||
log.Printf("Detected IP: %s", ipAddress)
|
||||
return fmt.Sprintf("%s:%d", ipAddress, config.SSHPort), nil
|
||||
}
|
||||
|
||||
func sshConfig(state map[string]interface{}) (*gossh.ClientConfig, error) {
|
||||
config := state["config"].(*config)
|
||||
|
||||
return &gossh.ClientConfig{
|
||||
User: config.SSHUser,
|
||||
Auth: []gossh.ClientAuth{
|
||||
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
|
||||
gossh.ClientAuthKeyboardInteractive(
|
||||
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
|
||||
},
|
||||
}, nil
|
||||
}
|
|
@ -1,183 +0,0 @@
|
|||
package vmware
|
||||
|
||||
import (
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// This step waits for SSH to become available and establishes an SSH
|
||||
// connection.
|
||||
//
|
||||
// Uses:
|
||||
// config *config
|
||||
// ui packer.Ui
|
||||
// vmx_path string
|
||||
//
|
||||
// Produces:
|
||||
// communicator packer.Communicator
|
||||
type stepWaitForSSH struct {
|
||||
cancel bool
|
||||
comm packer.Communicator
|
||||
}
|
||||
|
||||
func (s *stepWaitForSSH) Run(state map[string]interface{}) multistep.StepAction {
|
||||
config := state["config"].(*config)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
var comm packer.Communicator
|
||||
var err error
|
||||
|
||||
waitDone := make(chan bool, 1)
|
||||
go func() {
|
||||
comm, err = s.waitForSSH(state)
|
||||
waitDone <- true
|
||||
}()
|
||||
|
||||
log.Printf("Waiting for SSH, up to timeout: %s", config.sshWaitTimeout.String())
|
||||
timeout := time.After(config.sshWaitTimeout)
|
||||
WaitLoop:
|
||||
for {
|
||||
// Wait for either SSH to become available, a timeout to occur,
|
||||
// or an interrupt to come through.
|
||||
select {
|
||||
case <-waitDone:
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error waiting for SSH: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.comm = comm
|
||||
state["communicator"] = comm
|
||||
break WaitLoop
|
||||
case <-timeout:
|
||||
ui.Error("Timeout waiting for SSH.")
|
||||
s.cancel = true
|
||||
return multistep.ActionHalt
|
||||
case <-time.After(1 * time.Second):
|
||||
if _, ok := state[multistep.StateCancelled]; ok {
|
||||
log.Println("Interrupt detected, quitting waiting for SSH.")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepWaitForSSH) Cleanup(map[string]interface{}) {
|
||||
if s.comm != nil {
|
||||
// TODO: close
|
||||
s.comm = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Reads the network information for lookup via DHCP.
|
||||
func (s *stepWaitForSSH) dhcpLeaseLookup(vmxPath string) (GuestIPFinder, error) {
|
||||
f, err := os.Open(vmxPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
vmxBytes, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vmxData := ParseVMX(string(vmxBytes))
|
||||
|
||||
var ok bool
|
||||
macAddress := ""
|
||||
if macAddress, ok = vmxData["ethernet0.address"]; !ok || macAddress == "" {
|
||||
if macAddress, ok = vmxData["ethernet0.generatedAddress"]; !ok || macAddress == "" {
|
||||
return nil, errors.New("couldn't find MAC address in VMX")
|
||||
}
|
||||
}
|
||||
|
||||
return &DHCPLeaseGuestLookup{"vmnet8", macAddress}, nil
|
||||
}
|
||||
|
||||
// This blocks until SSH becomes available, and sends the communicator
|
||||
// on the given channel.
|
||||
func (s *stepWaitForSSH) waitForSSH(state map[string]interface{}) (packer.Communicator, error) {
|
||||
config := state["config"].(*config)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
vmxPath := state["vmx_path"].(string)
|
||||
|
||||
handshakeAttempts := 0
|
||||
|
||||
ui.Say("Waiting for SSH to become available...")
|
||||
var comm packer.Communicator
|
||||
for {
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
if s.cancel {
|
||||
log.Println("SSH wait cancelled. Exiting loop.")
|
||||
return nil, errors.New("SSH wait cancelled")
|
||||
}
|
||||
|
||||
// First we wait for the IP to become available...
|
||||
log.Println("Lookup up IP information...")
|
||||
ipLookup, err := s.dhcpLeaseLookup(vmxPath)
|
||||
if err != nil {
|
||||
log.Printf("Can't lookup via DHCP lease: %s", err)
|
||||
}
|
||||
|
||||
ip, err := ipLookup.GuestIP()
|
||||
if err != nil {
|
||||
log.Printf("IP lookup failed: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("Detected IP: %s", ip)
|
||||
|
||||
// Attempt to connect to SSH port
|
||||
connFunc := ssh.ConnectFunc(
|
||||
"tcp", fmt.Sprintf("%s:%d", ip, config.SSHPort), 5*time.Minute)
|
||||
nc, err := connFunc()
|
||||
if err != nil {
|
||||
log.Printf("TCP connection to SSH ip/port failed: %s", err)
|
||||
continue
|
||||
}
|
||||
nc.Close()
|
||||
|
||||
// Then we attempt to connect via SSH
|
||||
config := &ssh.Config{
|
||||
Connection: connFunc,
|
||||
SSHConfig: &gossh.ClientConfig{
|
||||
User: config.SSHUser,
|
||||
Auth: []gossh.ClientAuth{
|
||||
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
|
||||
gossh.ClientAuthKeyboardInteractive(
|
||||
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
comm, err = ssh.New(config)
|
||||
if err != nil {
|
||||
log.Printf("SSH handshake err: %s", err)
|
||||
|
||||
handshakeAttempts += 1
|
||||
if handshakeAttempts < 10 {
|
||||
// Try to connect via SSH a handful of times
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ui.Say("Connected via SSH!")
|
||||
break
|
||||
}
|
||||
|
||||
return comm, nil
|
||||
}
|
Loading…
Reference in New Issue