91 lines
2.4 KiB
Go
91 lines
2.4 KiB
Go
package digitalocean
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
|
|
"github.com/digitalocean/godo"
|
|
"github.com/hashicorp/packer/packer"
|
|
"github.com/hashicorp/packer/packer-plugin-sdk/multistep"
|
|
)
|
|
|
|
type stepShutdown struct{}
|
|
|
|
func (s *stepShutdown) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
client := state.Get("client").(*godo.Client)
|
|
c := state.Get("config").(*Config)
|
|
ui := state.Get("ui").(packer.Ui)
|
|
dropletId := state.Get("droplet_id").(int)
|
|
|
|
// Gracefully power off the droplet. We have to retry this a number
|
|
// of times because sometimes it says it completed when it actually
|
|
// did absolutely nothing (*ALAKAZAM!* magic!). We give up after
|
|
// a pretty arbitrary amount of time.
|
|
ui.Say("Gracefully shutting down droplet...")
|
|
_, _, err := client.DropletActions.Shutdown(context.TODO(), dropletId)
|
|
if 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
|
|
}
|
|
|
|
// A channel we use as a flag to end our goroutines
|
|
done := make(chan struct{})
|
|
shutdownRetryDone := make(chan struct{})
|
|
|
|
// Make sure we wait for the shutdown retry goroutine to end
|
|
// before moving on.
|
|
defer func() {
|
|
close(done)
|
|
<-shutdownRetryDone
|
|
}()
|
|
|
|
// Start a goroutine that just keeps trying to shut down the
|
|
// droplet.
|
|
go func() {
|
|
defer close(shutdownRetryDone)
|
|
|
|
for attempts := 2; attempts > 0; attempts++ {
|
|
log.Printf("ShutdownDroplet attempt #%d...", attempts)
|
|
_, _, err := client.DropletActions.Shutdown(context.TODO(), dropletId)
|
|
if err != nil {
|
|
log.Printf("Shutdown retry error: %s", err)
|
|
}
|
|
|
|
select {
|
|
case <-done:
|
|
return
|
|
case <-time.After(20 * time.Second):
|
|
// Retry!
|
|
}
|
|
}
|
|
}()
|
|
|
|
err = waitForDropletState("off", dropletId, client, c.StateTimeout)
|
|
if 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
|
|
}
|
|
|
|
if err := waitForDropletUnlocked(client, dropletId, c.StateTimeout); 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
|
|
}
|
|
|
|
func (s *stepShutdown) Cleanup(state multistep.StateBag) {
|
|
// no cleanup
|
|
}
|