Merge pull request #26 from mitchellh/digital-ocean-state-timeout

DigitalOcean: Add configuration for state timeout
This commit is contained in:
Mitchell Hashimoto 2013-06-23 11:46:55 -07:00
commit a93668bed3
7 changed files with 61 additions and 11 deletions

View File

@ -39,12 +39,14 @@ type config struct {
SSHPort uint `mapstructure:"ssh_port"` SSHPort uint `mapstructure:"ssh_port"`
SSHTimeout time.Duration SSHTimeout time.Duration
EventDelay time.Duration EventDelay time.Duration
StateTimeout time.Duration
PackerDebug bool `mapstructure:"packer_debug"` PackerDebug bool `mapstructure:"packer_debug"`
RawSnapshotName string `mapstructure:"snapshot_name"` RawSnapshotName string `mapstructure:"snapshot_name"`
RawSSHTimeout string `mapstructure:"ssh_timeout"` RawSSHTimeout string `mapstructure:"ssh_timeout"`
RawEventDelay string `mapstructure:"event_delay"` RawEventDelay string `mapstructure:"event_delay"`
RawStateTimeout string `mapstructure:"state_timeout"`
} }
type Builder struct { type Builder struct {
@ -104,6 +106,12 @@ func (b *Builder) Prepare(raws ...interface{}) error {
b.config.RawEventDelay = "5s" 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 // A list of errors on the configuration
errs := make([]error, 0) 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")) 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 { if err != nil {
errs = append(errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err)) 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 { if err != nil {
errs = append(errs, fmt.Errorf("Failed parsing event_delay: %s", err)) 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 // Parse the name of the snapshot
snapNameBuf := new(bytes.Buffer) snapNameBuf := new(bytes.Buffer)

View File

@ -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) { func TestBuilderPrepare_SnapshotName(t *testing.T) {
var b Builder var b Builder
config := testConfig() config := testConfig()

View File

@ -11,11 +11,12 @@ type stepDropletInfo struct{}
func (s *stepDropletInfo) Run(state map[string]interface{}) multistep.StepAction { func (s *stepDropletInfo) Run(state map[string]interface{}) multistep.StepAction {
client := state["client"].(*DigitalOceanClient) client := state["client"].(*DigitalOceanClient)
ui := state["ui"].(packer.Ui) ui := state["ui"].(packer.Ui)
c := state["config"].(config)
dropletId := state["droplet_id"].(uint) dropletId := state["droplet_id"].(uint)
ui.Say("Waiting for droplet to become active...") ui.Say("Waiting for droplet to become active...")
err := waitForDropletState("active", dropletId, client) err := waitForDropletState("active", dropletId, client, c)
if err != nil { if err != nil {
err := fmt.Errorf("Error waiting for droplet to become active: %s", err) err := fmt.Errorf("Error waiting for droplet to become active: %s", err)
state["error"] = err state["error"] = err

View File

@ -34,7 +34,7 @@ func (s *stepPowerOff) Run(state map[string]interface{}) multistep.StepAction {
ui.Say("Waiting for droplet to power off...") ui.Say("Waiting for droplet to power off...")
err = waitForDropletState("off", dropletId, client) err = waitForDropletState("off", dropletId, client, c)
if err != nil { if err != nil {
err := fmt.Errorf("Error waiting for droplet to become 'off': %s", err) err := fmt.Errorf("Error waiting for droplet to become 'off': %s", err)
state["error"] = err state["error"] = err

View File

@ -26,7 +26,7 @@ func (s *stepSnapshot) Run(state map[string]interface{}) multistep.StepAction {
} }
ui.Say("Waiting for snapshot to complete...") ui.Say("Waiting for snapshot to complete...")
err = waitForDropletState("active", dropletId, client) err = waitForDropletState("active", dropletId, client, c)
if err != nil { if err != nil {
err := fmt.Errorf("Error waiting for snapshot to complete: %s", err) err := fmt.Errorf("Error waiting for snapshot to complete: %s", err)
state["error"] = err state["error"] = err

View File

@ -8,7 +8,7 @@ import (
// 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(desiredState string, dropletId uint, client *DigitalOceanClient) error { func waitForDropletState(desiredState string, dropletId uint, client *DigitalOceanClient, c config) error {
active := make(chan bool, 1) active := make(chan bool, 1)
go func() { go func() {
@ -36,9 +36,8 @@ func waitForDropletState(desiredState string, dropletId uint, client *DigitalOce
active <- true active <- true
}() }()
log.Printf("Waiting for up to 3 minutes for droplet to become %s", desiredState) log.Printf("Waiting for up to %s for droplet to become %s", c.RawStateTimeout, desiredState)
duration, _ := time.ParseDuration("3m") timeout := time.After(c.StateTimeout)
timeout := time.After(duration)
ActiveWaitLoop: ActiveWaitLoop:
for { for {

View File

@ -64,6 +64,10 @@ Optional:
* `ssh_username` (string) - The username to use in order to communicate * `ssh_username` (string) - The username to use in order to communicate
over SSH to the running droplet. Default is "root". 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 ## Basic Example
Here is a basic example. It is completely valid as soon as you enter your Here is a basic example. It is completely valid as soon as you enter your