From a774e2b4444702955917c5201aa194bd5d693609 Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Thu, 13 Jun 2013 19:56:34 +0200 Subject: [PATCH] builder/digitalocean: completed initial pass at all steps. --- builder/digitalocean/step_connect_ssh.go | 1 - builder/digitalocean/step_destroy_droplet.go | 29 +++++++++ builder/digitalocean/step_destroy_ssh_key.go | 29 +++++++++ builder/digitalocean/step_droplet_info.go | 54 ++--------------- builder/digitalocean/step_power_off.go | 37 +++++++++++ builder/digitalocean/step_provision.go | 22 +++++++ builder/digitalocean/step_snapshot.go | 39 ++++++++++++ builder/digitalocean/wait.go | 64 ++++++++++++++++++++ 8 files changed, 224 insertions(+), 51 deletions(-) create mode 100644 builder/digitalocean/step_destroy_droplet.go create mode 100644 builder/digitalocean/step_destroy_ssh_key.go create mode 100644 builder/digitalocean/step_power_off.go create mode 100644 builder/digitalocean/step_provision.go create mode 100644 builder/digitalocean/step_snapshot.go create mode 100644 builder/digitalocean/wait.go diff --git a/builder/digitalocean/step_connect_ssh.go b/builder/digitalocean/step_connect_ssh.go index 6852b0f76..7e0bcd9ed 100644 --- a/builder/digitalocean/step_connect_ssh.go +++ b/builder/digitalocean/step_connect_ssh.go @@ -17,7 +17,6 @@ type stepConnectSSH struct { func (s *stepConnectSSH) Run(state map[string]interface{}) multistep.StepAction { config := state["config"].(config) - client := state["client"].(*DigitalOceanClient) privateKey := state["privateKey"].(string) ui := state["ui"].(packer.Ui) ipAddress := state["droplet_ip"] diff --git a/builder/digitalocean/step_destroy_droplet.go b/builder/digitalocean/step_destroy_droplet.go new file mode 100644 index 000000000..65af363c0 --- /dev/null +++ b/builder/digitalocean/step_destroy_droplet.go @@ -0,0 +1,29 @@ +package digitalocean + +import ( + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +type stepDestroyDroplet struct{} + +func (s *stepDestroyDroplet) Run(state map[string]interface{}) multistep.StepAction { + client := state["client"].(*DigitalOceanClient) + ui := state["ui"].(packer.Ui) + dropletId := state["droplet_id"].(uint) + + ui.Say("Destroying droplet...") + + err := client.DestroyDroplet(dropletId) + + if err != nil { + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *stepDestroyDroplet) Cleanup(state map[string]interface{}) { + // no cleanup +} diff --git a/builder/digitalocean/step_destroy_ssh_key.go b/builder/digitalocean/step_destroy_ssh_key.go new file mode 100644 index 000000000..c6fe675e1 --- /dev/null +++ b/builder/digitalocean/step_destroy_ssh_key.go @@ -0,0 +1,29 @@ +package digitalocean + +import ( + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +type stepDestroySSHKey struct{} + +func (s *stepDestroySSHKey) Run(state map[string]interface{}) multistep.StepAction { + client := state["client"].(*DigitalOceanClient) + ui := state["ui"].(packer.Ui) + sshKeyId := state["ssh_key_id"].(uint) + + ui.Say("Destroying temporary ssh key...") + + err := client.DestroyKey(sshKeyId) + + if err != nil { + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *stepDestroySSHKey) Cleanup(state map[string]interface{}) { + // no cleanup +} diff --git a/builder/digitalocean/step_droplet_info.go b/builder/digitalocean/step_droplet_info.go index 102448ef2..ba3fb6987 100644 --- a/builder/digitalocean/step_droplet_info.go +++ b/builder/digitalocean/step_droplet_info.go @@ -3,8 +3,6 @@ package digitalocean import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "log" - "time" ) type stepDropletInfo struct{} @@ -12,59 +10,15 @@ 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...") - // Wait for the droplet to become active - active := make(chan bool, 1) + err := waitForDropletState("active", dropletId, client) - go func() { - var err error - - attempts := 0 - for { - select { - default: - } - - attempts += 1 - - log.Printf("Checking droplet status... (attempt: %d)", attempts) - - ip, status, err := client.DropletStatus(dropletId) - - if status == "active" { - break - } - - // Wait a second in between - time.Sleep(1 * time.Second) - } - - active <- true - }() - - log.Printf("Waiting for up to 3 minutes for droplet to become active") - duration, _ := time.ParseDuration("3m") - timeout := time.After(duration) - -ActiveWaitLoop: - for { - select { - case <-active: - // We connected. Just break the loop. - break ActiveWaitLoop - case <-timeout: - ui.Error("Timeout while waiting to for droplet to become active") - return multistep.ActionHalt - case <-time.After(1 * time.Second): - if _, ok := state[multistep.StateCancelled]; ok { - log.Println("Interrupt detected, quitting waiting droplet to become active") - return multistep.ActionHalt - } - } + if err != nil { + ui.Error(err.Error()) + return multistep.ActionHalt } // Set the IP on the state for later diff --git a/builder/digitalocean/step_power_off.go b/builder/digitalocean/step_power_off.go new file mode 100644 index 000000000..3cd1220fc --- /dev/null +++ b/builder/digitalocean/step_power_off.go @@ -0,0 +1,37 @@ +package digitalocean + +import ( + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +type stepPowerOff struct{} + +func (s *stepPowerOff) Run(state map[string]interface{}) multistep.StepAction { + client := state["client"].(*DigitalOceanClient) + ui := state["ui"].(packer.Ui) + dropletId := state["droplet_id"].(uint) + + // Poweroff the droplet so it can be snapshot + err := client.PowerOffDroplet(dropletId) + + if err != nil { + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say("Waiting for droplet to power off...") + + err = waitForDropletState("off", dropletId, client) + + if err != nil { + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *stepPowerOff) Cleanup(state map[string]interface{}) { + // no cleanup +} diff --git a/builder/digitalocean/step_provision.go b/builder/digitalocean/step_provision.go new file mode 100644 index 000000000..f1e6c8f49 --- /dev/null +++ b/builder/digitalocean/step_provision.go @@ -0,0 +1,22 @@ +package digitalocean + +import ( + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" +) + +type stepProvision struct{} + +func (*stepProvision) Run(state map[string]interface{}) multistep.StepAction { + comm := state["communicator"].(packer.Communicator) + hook := state["hook"].(packer.Hook) + ui := state["ui"].(packer.Ui) + + log.Println("Running the provision hook") + hook.Run(packer.HookProvision, ui, comm, nil) + + return multistep.ActionContinue +} + +func (*stepProvision) Cleanup(map[string]interface{}) {} diff --git a/builder/digitalocean/step_snapshot.go b/builder/digitalocean/step_snapshot.go new file mode 100644 index 000000000..93e450cf6 --- /dev/null +++ b/builder/digitalocean/step_snapshot.go @@ -0,0 +1,39 @@ +package digitalocean + +import ( + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +type stepSnapshot struct{} + +func (s *stepSnapshot) 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("Creating snapshot...") + + err := client.CreateSnapshot(dropletId, c.SnapshotName) + + if err != nil { + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say("Waiting for snapshot to complete...") + + err = waitForDropletState("active", dropletId, client) + + if err != nil { + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *stepSnapshot) Cleanup(state map[string]interface{}) { + // no cleanup +} diff --git a/builder/digitalocean/wait.go b/builder/digitalocean/wait.go new file mode 100644 index 000000000..b0794d2c2 --- /dev/null +++ b/builder/digitalocean/wait.go @@ -0,0 +1,64 @@ +package digitalocean + +import ( + "errors" + "log" + "time" +) + +// 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 { + active := make(chan bool, 1) + + go func() { + attempts := 0 + for { + select { + default: + } + + attempts += 1 + + log.Printf("Checking droplet status... (attempt: %d)", attempts) + + _, status, err := client.DropletStatus(dropletId) + + if err != nil { + log.Println(err) + break + } + + if status == desiredState { + break + } + + // Wait a second in between + time.Sleep(1 * time.Second) + } + + active <- true + }() + + log.Printf("Waiting for up to 3 minutes for droplet to become %s", desiredState) + duration, _ := time.ParseDuration("3m") + timeout := time.After(duration) + +ActiveWaitLoop: + for { + select { + case <-active: + // We connected. Just break the loop. + break ActiveWaitLoop + case <-timeout: + err := errors.New("Timeout while waiting to for droplet to become active") + return err + case <-time.After(1 * time.Second): + err := errors.New("Interrupt detected, quitting waiting for droplet") + return err + } + } + + // If we got this far, there were no errors + return nil +}