diff --git a/builder/amazonebs/builder.go b/builder/amazonebs/builder.go index 1a5c4bcd9..660a27526 100644 --- a/builder/amazonebs/builder.go +++ b/builder/amazonebs/builder.go @@ -14,6 +14,7 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "log" + "time" ) // The unique ID for this builder @@ -30,9 +31,12 @@ type config struct { InstanceType string `mapstructure:"instance_type"` SSHUsername string `mapstructure:"ssh_username"` SSHPort int `mapstructure:"ssh_port"` + SSHTimeout time.Duration // Configuration of the resulting AMI AMIName string `mapstructure:"ami_name"` + + RawSSHTimeout string `mapstructure:"ssh_timeout"` } type Builder struct { @@ -40,16 +44,20 @@ type Builder struct { runner multistep.Runner } -func (b *Builder) Prepare(raw interface{}) (err error) { - err = mapstructure.Decode(raw, &b.config) +func (b *Builder) Prepare(raw interface{}) error { + err := mapstructure.Decode(raw, &b.config) if err != nil { - return + return err } if b.config.SSHPort == 0 { b.config.SSHPort = 22 } + if b.config.RawSSHTimeout == "" { + b.config.RawSSHTimeout = "1m" + } + // Accumulate any errors errs := make([]error, 0) @@ -79,12 +87,17 @@ func (b *Builder) Prepare(raw interface{}) (err error) { errs = append(errs, errors.New("An ssh_username must be specified")) } + b.config.SSHTimeout, err = time.ParseDuration(b.config.RawSSHTimeout) + if err != nil { + errs = append(errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err)) + } + if len(errs) > 0 { return &packer.MultiError{errs} } log.Printf("Config: %+v", b.config) - return + return nil } func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) packer.Artifact { diff --git a/builder/amazonebs/builder_test.go b/builder/amazonebs/builder_test.go index 18776cfc5..eb0d72482 100644 --- a/builder/amazonebs/builder_test.go +++ b/builder/amazonebs/builder_test.go @@ -191,6 +191,25 @@ func TestBuilderPrepare_SSHPort(t *testing.T) { } } +func TestBuilderPrepare_SSHTimeout(t *testing.T) { + var b Builder + config := testConfig() + + // Test with a bad value + config["ssh_timeout"] = "this is not good" + err := b.Prepare(config) + if err == nil { + t.Fatal("should have error") + } + + // Test with a good one + config["ssh_timeout"] = "5s" + err = b.Prepare(config) + if err != nil { + t.Fatalf("should not have error: %s", err) + } +} + func TestBuilderPrepare_SSHUsername(t *testing.T) { var b Builder config := testConfig() diff --git a/builder/amazonebs/step_connect_ssh.go b/builder/amazonebs/step_connect_ssh.go index 36b30ddc8..e342cee02 100644 --- a/builder/amazonebs/step_connect_ssh.go +++ b/builder/amazonebs/step_connect_ssh.go @@ -39,17 +39,50 @@ func (s *stepConnectSSH) Run(state map[string]interface{}) multistep.StepAction }, } - // Try to connect for SSH a few times - ui.Say("Connecting to the instance via SSH...") - for i := 0; i < 5; i++ { - time.Sleep(time.Duration(i) * time.Second) + // Start trying to connect to SSH + connected := make(chan bool, 1) + connectQuit := make(chan bool, 1) + defer func() { + connectQuit <- true + }() - log.Printf( - "Opening TCP conn for SSH to %s:%d (attempt %d)", - instance.DNSName, config.SSHPort, i+1) - s.conn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", instance.DNSName, config.SSHPort)) - if err != nil { - continue + go func() { + var err error + + ui.Say("Connecting to the instance via SSH...") + attempts := 0 + for { + select { + case <-connectQuit: + return + default: + } + + attempts += 1 + log.Printf( + "Opening TCP conn for SSH to %s:%d (attempt %d)", + instance.DNSName, config.SSHPort, attempts) + s.conn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", instance.DNSName, config.SSHPort)) + if err == nil { + break + } + } + + connected <- true + }() + + log.Printf("Waiting up to %s for SSH connection", config.SSHTimeout) + timeout := time.After(config.SSHTimeout) + + ConnectWaitLoop: + for { + select { + case <-connected: + // We connected. Just break the loop. + break ConnectWaitLoop + case <-timeout: + ui.Error("Timeout while waiting to connect to SSH.") + return multistep.ActionHalt } }