Commit dcf140f9 authored by Mitchell Hashimoto's avatar Mitchell Hashimoto

builder/digitalocean: more robust wait for pending

parent 0e0cd280
...@@ -64,8 +64,13 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { ...@@ -64,8 +64,13 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
} }
if c.SnapshotName == "" { if c.SnapshotName == "" {
def, err := interpolate.Render("packer-{{timestamp}}", nil)
if err != nil {
panic(err)
}
// Default to packer-{{ unix timestamp (utc) }} // Default to packer-{{ unix timestamp (utc) }}
c.SnapshotName = "packer-{{timestamp}}" c.SnapshotName = def
} }
if c.DropletName == "" { if c.DropletName == "" {
......
...@@ -3,6 +3,7 @@ package digitalocean ...@@ -3,6 +3,7 @@ package digitalocean
import ( import (
"fmt" "fmt"
"log" "log"
"time"
"github.com/digitalocean/godo" "github.com/digitalocean/godo"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
...@@ -48,6 +49,15 @@ func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction { ...@@ -48,6 +49,15 @@ func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionHalt return multistep.ActionHalt
} }
// Wait for the droplet to become unlocked for future steps
if err := waitForDropletUnlocked(client, dropletId, 2*time.Minute); err != nil {
// If we get an error the first time, actually report it
err := fmt.Errorf("Error powering off droplet: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue return multistep.ActionContinue
} }
......
...@@ -65,7 +65,19 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { ...@@ -65,7 +65,19 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
err = waitForDropletState("off", dropletId, client, 2*time.Minute) err = waitForDropletState("off", dropletId, client, 2*time.Minute)
if err != nil { if err != nil {
log.Printf("Error waiting for graceful off: %s", err) // If we get an error the first time, actually report it
err := fmt.Errorf("Error shutting down droplet: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if err := waitForDropletUnlocked(client, dropletId, 2*time.Minute); err != nil {
// If we get an error the first time, actually report it
err := fmt.Errorf("Error shutting down droplet: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
} }
return multistep.ActionContinue return multistep.ActionContinue
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"time"
"github.com/digitalocean/godo" "github.com/digitalocean/godo"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
...@@ -27,6 +28,18 @@ func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction { ...@@ -27,6 +28,18 @@ func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionHalt return multistep.ActionHalt
} }
// Wait for the droplet to become unlocked first. For snapshots
// this can end up taking quite a long time, so we hardcode this to
// 10 minutes.
if err := waitForDropletUnlocked(client, dropletId, 10*time.Minute); err != nil {
// If we get an error the first time, actually report it
err := fmt.Errorf("Error shutting down droplet: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// With the pending state over, verify that we're in the active state
ui.Say("Waiting for snapshot to complete...") ui.Say("Waiting for snapshot to complete...")
err = waitForDropletState("active", dropletId, client, c.stateTimeout) err = waitForDropletState("active", dropletId, client, c.stateTimeout)
if err != nil { if err != nil {
......
...@@ -8,6 +8,55 @@ import ( ...@@ -8,6 +8,55 @@ import (
"github.com/digitalocean/godo" "github.com/digitalocean/godo"
) )
// waitForDropletUnlocked waits for the Droplet to be unlocked to
// avoid "pending" errors when making state changes.
func waitForDropletUnlocked(
client *godo.Client, dropletId int, timeout time.Duration) error {
done := make(chan struct{})
defer close(done)
result := make(chan error, 1)
go func() {
attempts := 0
for {
attempts += 1
log.Printf("[DEBUG] Checking droplet lock state... (attempt: %d)", attempts)
droplet, _, err := client.Droplets.Get(dropletId)
if err != nil {
result <- err
return
}
if !droplet.Locked {
result <- nil
return
}
// Wait 3 seconds in between
time.Sleep(3 * time.Second)
// Verify we shouldn't exit
select {
case <-done:
// We finished, so just exit the goroutine
return
default:
// Keep going
}
}
}()
log.Printf("[DEBUG] Waiting for up to %d seconds for droplet to unlock", timeout/time.Second)
select {
case err := <-result:
return err
case <-time.After(timeout):
return fmt.Errorf(
"Timeout while waiting to for droplet to unlock")
}
}
// waitForState simply blocks until the droplet is in // waitForState simply blocks until the droplet is in
// a state we expect, while eventually timing out. // a state we expect, while eventually timing out.
func waitForDropletState( func waitForDropletState(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment