diff --git a/builder/digitalocean/builder.go b/builder/digitalocean/builder.go index 6519d30f5..4c515b900 100644 --- a/builder/digitalocean/builder.go +++ b/builder/digitalocean/builder.go @@ -95,7 +95,9 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack }, new(stepShutdown), new(stepPowerOff), - new(stepSnapshot), + &stepSnapshot{ + snapshotTimeout: b.config.SnapshotTimeout, + }, } // Run the steps diff --git a/builder/digitalocean/builder_test.go b/builder/digitalocean/builder_test.go index 9ef7c0ad7..70ec6fea9 100644 --- a/builder/digitalocean/builder_test.go +++ b/builder/digitalocean/builder_test.go @@ -190,7 +190,46 @@ func TestBuilderPrepare_StateTimeout(t *testing.T) { if err == nil { t.Fatal("should have error") } +} + +func TestBuilderPrepare_SnapshotTimeout(t *testing.T) { + var b Builder + config := testConfig() + + // Test default + warnings, err := b.Prepare(config) + if len(warnings) > 0 { + t.Fatalf("bad: %#v", warnings) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.SnapshotTimeout != 60*time.Minute { + t.Errorf("invalid: %s", b.config.SnapshotTimeout) + } + + // Test set + config["snapshot_timeout"] = "15m" + b = Builder{} + warnings, err = b.Prepare(config) + if len(warnings) > 0 { + t.Fatalf("bad: %#v", warnings) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + // Test bad + config["snapshot_timeout"] = "badstring" + b = Builder{} + warnings, err = b.Prepare(config) + if len(warnings) > 0 { + t.Fatalf("bad: %#v", warnings) + } + if err == nil { + t.Fatal("should have error") + } } func TestBuilderPrepare_PrivateNetworking(t *testing.T) { diff --git a/builder/digitalocean/config.go b/builder/digitalocean/config.go index 451852829..25b28a3bc 100644 --- a/builder/digitalocean/config.go +++ b/builder/digitalocean/config.go @@ -33,6 +33,7 @@ type Config struct { SnapshotName string `mapstructure:"snapshot_name"` SnapshotRegions []string `mapstructure:"snapshot_regions"` StateTimeout time.Duration `mapstructure:"state_timeout"` + SnapshotTimeout time.Duration `mapstructure:"snapshot_timeout"` DropletName string `mapstructure:"droplet_name"` UserData string `mapstructure:"user_data"` UserDataFile string `mapstructure:"user_data_file"` @@ -88,6 +89,11 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { c.StateTimeout = 6 * time.Minute } + if c.SnapshotTimeout == 0 { + // Default to 60 minutes timeout, waiting for snapshot action to finish + c.SnapshotTimeout = 60 * time.Minute + } + var errs *packer.MultiError if es := c.Comm.Prepare(&c.ctx); len(es) > 0 { errs = packer.MultiErrorAppend(errs, es...) diff --git a/builder/digitalocean/step_snapshot.go b/builder/digitalocean/step_snapshot.go index f3c9430f1..511c3a377 100644 --- a/builder/digitalocean/step_snapshot.go +++ b/builder/digitalocean/step_snapshot.go @@ -12,7 +12,9 @@ import ( "github.com/hashicorp/packer/packer" ) -type stepSnapshot struct{} +type stepSnapshot struct { + snapshotTimeout time.Duration +} func (s *stepSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { client := state.Get("client").(*godo.Client) @@ -31,9 +33,11 @@ func (s *stepSnapshot) Run(ctx context.Context, state multistep.StateBag) multis } // With the pending state over, verify that we're in the active state + // because action can take a long time and may depend on the size of the final snapshot, + // the timeout is parameterized ui.Say("Waiting for snapshot to complete...") if err := waitForActionState(godo.ActionCompleted, dropletId, action.ID, - client, 20*time.Minute); err != nil { + client, s.snapshotTimeout); err != nil { // If we get an error the first time, actually report it err := fmt.Errorf("Error waiting for snapshot: %s", err) state.Put("error", err) diff --git a/website/source/docs/builders/digitalocean.html.md b/website/source/docs/builders/digitalocean.html.md index 1d382869d..8df5c9df0 100644 --- a/website/source/docs/builders/digitalocean.html.md +++ b/website/source/docs/builders/digitalocean.html.md @@ -83,6 +83,10 @@ builder. droplet to enter a desired state (such as "active") before timing out. The default state timeout is "6m". +- `snapshot_timeout` (string) - The time to wait, as a duration string, for a + snapshot action to complete (e.g snapshot creation) before timing out. The + default snapshot timeout is "60m". + - `user_data` (string) - User data to launch with the Droplet. Packer will not automatically wait for a user script to finish before shutting down the instance this must be handled in a provisioner.