builder/virtualbox: Wait for SSH to become available
This commit is contained in:
parent
e7cc350536
commit
5c0d8ecd72
|
@ -34,10 +34,14 @@ type config struct {
|
||||||
OutputDir string `mapstructure:"output_directory"`
|
OutputDir string `mapstructure:"output_directory"`
|
||||||
SSHHostPortMin uint `mapstructure:"ssh_host_port_min"`
|
SSHHostPortMin uint `mapstructure:"ssh_host_port_min"`
|
||||||
SSHHostPortMax uint `mapstructure:"ssh_host_port_max"`
|
SSHHostPortMax uint `mapstructure:"ssh_host_port_max"`
|
||||||
|
SSHPassword string `mapstructure:"ssh_password"`
|
||||||
SSHPort uint `mapstructure:"ssh_port"`
|
SSHPort uint `mapstructure:"ssh_port"`
|
||||||
|
SSHUser string `mapstructure:"ssh_username"`
|
||||||
|
SSHWaitTimeout time.Duration ``
|
||||||
VMName string `mapstructure:"vm_name"`
|
VMName string `mapstructure:"vm_name"`
|
||||||
|
|
||||||
RawBootWait string `mapstructure:"boot_wait"`
|
RawBootWait string `mapstructure:"boot_wait"`
|
||||||
|
RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) Prepare(raw interface{}) error {
|
func (b *Builder) Prepare(raw interface{}) error {
|
||||||
|
@ -140,6 +144,19 @@ func (b *Builder) Prepare(raw interface{}) error {
|
||||||
errs = append(errs, errors.New("ssh_host_port_min must be less than ssh_host_port_max"))
|
errs = append(errs, errors.New("ssh_host_port_min must be less than ssh_host_port_max"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if b.config.SSHUser == "" {
|
||||||
|
errs = append(errs, errors.New("An ssh_username must be specified."))
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.RawSSHWaitTimeout == "" {
|
||||||
|
b.config.RawSSHWaitTimeout = "20m"
|
||||||
|
}
|
||||||
|
|
||||||
|
b.config.SSHWaitTimeout, err = time.ParseDuration(b.config.RawSSHWaitTimeout)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
b.driver, err = b.newDriver()
|
b.driver, err = b.newDriver()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, fmt.Errorf("Failed creating VirtualBox driver: %s", err))
|
errs = append(errs, fmt.Errorf("Failed creating VirtualBox driver: %s", err))
|
||||||
|
@ -164,6 +181,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) packer
|
||||||
new(stepForwardSSH),
|
new(stepForwardSSH),
|
||||||
new(stepRun),
|
new(stepRun),
|
||||||
new(stepTypeBootCommand),
|
new(stepTypeBootCommand),
|
||||||
|
new(stepWaitForSSH),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup the state bag
|
// Setup the state bag
|
||||||
|
|
|
@ -11,6 +11,7 @@ func testConfig() map[string]interface{} {
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"iso_md5": "foo",
|
"iso_md5": "foo",
|
||||||
"iso_url": "http://www.google.com/",
|
"iso_url": "http://www.google.com/",
|
||||||
|
"ssh_username": "foo",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,3 +198,39 @@ func TestBuilderPrepare_SSHHostPort(t *testing.T) {
|
||||||
t.Fatalf("should not have error: %s", err)
|
t.Fatalf("should not have error: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_SSHUser(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
config["ssh_username"] = ""
|
||||||
|
err := b.Prepare(config)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
config["ssh_username"] = "exists"
|
||||||
|
err = b.Prepare(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_SSHWaitTimeout(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
// Test with a bad value
|
||||||
|
config["ssh_wait_timeout"] = "this is not good"
|
||||||
|
err := b.Prepare(config)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with a good one
|
||||||
|
config["ssh_wait_timeout"] = "5s"
|
||||||
|
err = b.Prepare(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -66,7 +66,6 @@ func (s *stepTypeBootCommand) Run(state map[string]interface{}) multistep.StepAc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(15 * time.Second)
|
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
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"
|
||||||
|
"net"
|
||||||
|
"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
|
||||||
|
conn net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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.conn != nil {
|
||||||
|
s.conn.Close()
|
||||||
|
s.conn = 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)
|
||||||
|
|
||||||
|
ui.Say("Waiting for SSH to become available...")
|
||||||
|
var comm packer.Communicator
|
||||||
|
var nc net.Conn
|
||||||
|
for {
|
||||||
|
if nc != nil {
|
||||||
|
nc.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
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 := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", sshHostPort))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("TCP connection to SSH ip/port failed: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then we attempt to connect via SSH
|
||||||
|
sshConfig := &gossh.ClientConfig{
|
||||||
|
User: config.SSHUser,
|
||||||
|
Auth: []gossh.ClientAuth{
|
||||||
|
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
comm, err = ssh.New(nc, sshConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("SSH connection fail: %s", err)
|
||||||
|
nc.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say("Connected via SSH!")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the connection so we can close it later
|
||||||
|
s.conn = nc
|
||||||
|
return comm, nil
|
||||||
|
}
|
Loading…
Reference in New Issue