From ba8fbc86214c5b36624be3c34d61407fdd46d8f0 Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Sun, 23 Jun 2013 12:51:51 +0200 Subject: [PATCH 1/2] builder/digitalocean: add configurable state_timeout The state_timeout config allows you to determine the timeout for "waiting for droplet to become [active, off, etc.]". This still defaults to 3 minutes. --- builder/digitalocean/builder.go | 22 +++++++++++++--- builder/digitalocean/builder_test.go | 32 +++++++++++++++++++++++ builder/digitalocean/step_droplet_info.go | 3 ++- builder/digitalocean/step_power_off.go | 2 +- builder/digitalocean/step_snapshot.go | 2 +- builder/digitalocean/wait.go | 7 +++-- 6 files changed, 57 insertions(+), 11 deletions(-) diff --git a/builder/digitalocean/builder.go b/builder/digitalocean/builder.go index a9224ae7e..edae076f2 100644 --- a/builder/digitalocean/builder.go +++ b/builder/digitalocean/builder.go @@ -39,12 +39,14 @@ type config struct { SSHPort uint `mapstructure:"ssh_port"` SSHTimeout time.Duration EventDelay time.Duration + StateTimeout time.Duration PackerDebug bool `mapstructure:"packer_debug"` RawSnapshotName string `mapstructure:"snapshot_name"` RawSSHTimeout string `mapstructure:"ssh_timeout"` RawEventDelay string `mapstructure:"event_delay"` + RawStateTimeout string `mapstructure:"state_timeout"` } type Builder struct { @@ -104,6 +106,12 @@ func (b *Builder) Prepare(raws ...interface{}) error { b.config.RawEventDelay = "5s" } + if b.config.RawStateTimeout == "" { + // Default to 3 minute timeouts waiting for + // desired state. i.e waiting for droplet to become active + b.config.RawStateTimeout = "3m" + } + // A list of errors on the configuration errs := make([]error, 0) @@ -117,17 +125,23 @@ func (b *Builder) Prepare(raws ...interface{}) error { errs = append(errs, errors.New("an api_key must be specified")) } - timeout, err := time.ParseDuration(b.config.RawSSHTimeout) + sshTimeout, err := time.ParseDuration(b.config.RawSSHTimeout) if err != nil { errs = append(errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err)) } - b.config.SSHTimeout = timeout + b.config.SSHTimeout = sshTimeout - delay, err := time.ParseDuration(b.config.RawEventDelay) + eventDelay, err := time.ParseDuration(b.config.RawEventDelay) if err != nil { errs = append(errs, fmt.Errorf("Failed parsing event_delay: %s", err)) } - b.config.EventDelay = delay + b.config.EventDelay = eventDelay + + stateTimeout, err := time.ParseDuration(b.config.RawStateTimeout) + if err != nil { + errs = append(errs, fmt.Errorf("Failed parsing state_timeout: %s", err)) + } + b.config.StateTimeout = stateTimeout // Parse the name of the snapshot snapNameBuf := new(bytes.Buffer) diff --git a/builder/digitalocean/builder_test.go b/builder/digitalocean/builder_test.go index 907d5e7b4..a2c2d4fc6 100644 --- a/builder/digitalocean/builder_test.go +++ b/builder/digitalocean/builder_test.go @@ -253,6 +253,38 @@ func TestBuilderPrepare_EventDelay(t *testing.T) { } +func TestBuilderPrepare_StateTimeout(t *testing.T) { + var b Builder + config := testConfig() + + // Test default + err := b.Prepare(config) + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.RawStateTimeout != "3m" { + t.Errorf("invalid: %d", b.config.RawStateTimeout) + } + + // Test set + config["state_timeout"] = "5m" + b = Builder{} + err = b.Prepare(config) + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + // Test bad + config["state_timeout"] = "tubes" + b = Builder{} + err = b.Prepare(config) + if err == nil { + t.Fatal("should have error") + } + +} + func TestBuilderPrepare_SnapshotName(t *testing.T) { var b Builder config := testConfig() diff --git a/builder/digitalocean/step_droplet_info.go b/builder/digitalocean/step_droplet_info.go index 8e9b5693d..54daeebc4 100644 --- a/builder/digitalocean/step_droplet_info.go +++ b/builder/digitalocean/step_droplet_info.go @@ -11,11 +11,12 @@ type stepDropletInfo struct{} func (s *stepDropletInfo) Run(state map[string]interface{}) multistep.StepAction { client := state["client"].(*DigitalOceanClient) ui := state["ui"].(packer.Ui) + c := state["config"].(config) dropletId := state["droplet_id"].(uint) ui.Say("Waiting for droplet to become active...") - err := waitForDropletState("active", dropletId, client) + err := waitForDropletState("active", dropletId, client, c) if err != nil { err := fmt.Errorf("Error waiting for droplet to become active: %s", err) state["error"] = err diff --git a/builder/digitalocean/step_power_off.go b/builder/digitalocean/step_power_off.go index f0d85fe94..750cb8578 100644 --- a/builder/digitalocean/step_power_off.go +++ b/builder/digitalocean/step_power_off.go @@ -34,7 +34,7 @@ func (s *stepPowerOff) Run(state map[string]interface{}) multistep.StepAction { ui.Say("Waiting for droplet to power off...") - err = waitForDropletState("off", dropletId, client) + err = waitForDropletState("off", dropletId, client, c) if err != nil { err := fmt.Errorf("Error waiting for droplet to become 'off': %s", err) state["error"] = err diff --git a/builder/digitalocean/step_snapshot.go b/builder/digitalocean/step_snapshot.go index 8ec3e68e7..a032dfcc0 100644 --- a/builder/digitalocean/step_snapshot.go +++ b/builder/digitalocean/step_snapshot.go @@ -26,7 +26,7 @@ func (s *stepSnapshot) Run(state map[string]interface{}) multistep.StepAction { } ui.Say("Waiting for snapshot to complete...") - err = waitForDropletState("active", dropletId, client) + err = waitForDropletState("active", dropletId, client, c) if err != nil { err := fmt.Errorf("Error waiting for snapshot to complete: %s", err) state["error"] = err diff --git a/builder/digitalocean/wait.go b/builder/digitalocean/wait.go index c712c16ad..85394e414 100644 --- a/builder/digitalocean/wait.go +++ b/builder/digitalocean/wait.go @@ -8,7 +8,7 @@ import ( // waitForState simply blocks until the droplet is in // a state we expect, while eventually timing out. -func waitForDropletState(desiredState string, dropletId uint, client *DigitalOceanClient) error { +func waitForDropletState(desiredState string, dropletId uint, client *DigitalOceanClient, c config) error { active := make(chan bool, 1) go func() { @@ -36,9 +36,8 @@ func waitForDropletState(desiredState string, dropletId uint, client *DigitalOce active <- true }() - log.Printf("Waiting for up to 3 minutes for droplet to become %s", desiredState) - duration, _ := time.ParseDuration("3m") - timeout := time.After(duration) + log.Printf("Waiting for up to %s for droplet to become %s", c.RawStateTimeout, desiredState) + timeout := time.After(c.StateTimeout) ActiveWaitLoop: for { From 15d42af48bd9ed223cad42b8ba637204ec2e0c1f Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Sun, 23 Jun 2013 12:57:05 +0200 Subject: [PATCH 2/2] website: document digitalocean state_timeout configuration --- website/source/docs/builders/digitalocean.html.markdown | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/source/docs/builders/digitalocean.html.markdown b/website/source/docs/builders/digitalocean.html.markdown index 5ad4f3a84..100091936 100644 --- a/website/source/docs/builders/digitalocean.html.markdown +++ b/website/source/docs/builders/digitalocean.html.markdown @@ -64,6 +64,10 @@ Optional: * `ssh_username` (string) - The username to use in order to communicate over SSH to the running droplet. Default is "root". +* `state_timeout` (string) - The time to wait, as a duration string, +for a droplet to enter a desired state (such as "active") before +timing out. The default state timeout is "3m". + ## Basic Example Here is a basic example. It is completely valid as soon as you enter your