From cf63dd10bfaacb17cc0ca1c0ce879ce6fed0cc34 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Thu, 31 May 2018 11:29:04 -0700 Subject: [PATCH] replace AMIStateRefreshFunc, InstanceStateRefreshFunc, our spot instance waiter, our chroot volume waiter, and our snapshot waiters with waiters form AWS's SDK. --- builder/amazon/chroot/step_attach_volume.go | 53 +-- builder/amazon/chroot/step_create_volume.go | 17 +- builder/amazon/chroot/step_register_ami.go | 14 +- builder/amazon/chroot/step_snapshot.go | 22 +- builder/amazon/common/state.go | 358 ++++++++++-------- builder/amazon/common/step_ami_region_copy.go | 10 +- builder/amazon/common/step_encrypted_ami.go | 9 +- .../amazon/common/step_run_source_instance.go | 8 +- .../amazon/common/step_run_spot_instance.go | 23 +- builder/amazon/ebs/step_create_ami.go | 9 +- .../amazon/ebssurrogate/step_register_ami.go | 9 +- .../ebssurrogate/step_snapshot_volumes.go | 26 +- builder/amazon/instance/step_register_ami.go | 9 +- .../amazon-import/post-processor.go | 19 +- 14 files changed, 215 insertions(+), 371 deletions(-) diff --git a/builder/amazon/chroot/step_attach_volume.go b/builder/amazon/chroot/step_attach_volume.go index 01ab32464..7698ece07 100644 --- a/builder/amazon/chroot/step_attach_volume.go +++ b/builder/amazon/chroot/step_attach_volume.go @@ -2,10 +2,8 @@ package chroot import ( "context" - "errors" "fmt" "strings" - "time" "github.com/aws/aws-sdk-go/service/ec2" awscommon "github.com/hashicorp/packer/builder/amazon/common" @@ -52,35 +50,7 @@ func (s *StepAttachVolume) Run(_ context.Context, state multistep.StateBag) mult s.volumeId = volumeId // Wait for the volume to become attached - stateChange := awscommon.StateChangeConf{ - Pending: []string{"attaching"}, - StepState: state, - Target: "attached", - Refresh: func() (interface{}, string, error) { - attempts := 0 - for attempts < 30 { - resp, err := ec2conn.DescribeVolumes(&ec2.DescribeVolumesInput{VolumeIds: []*string{&volumeId}}) - if err != nil { - return nil, "", err - } - if len(resp.Volumes[0].Attachments) > 0 { - a := resp.Volumes[0].Attachments[0] - return a, *a.State, nil - } - // When Attachment on volume is not present sleep for 2s and retry - attempts += 1 - ui.Say(fmt.Sprintf( - "Volume %s show no attachments. Attempt %d/30. Sleeping for 2s and will retry.", - volumeId, attempts)) - time.Sleep(2 * time.Second) - } - - // Attachment on volume is not present after all attempts - return nil, "", errors.New("No attachments on volume.") - }, - } - - _, err = awscommon.WaitForState(&stateChange) + err = awscommon.WaitUntilVolumeAttached(ec2conn, s.volumeId) if err != nil { err := fmt.Errorf("Error waiting for volume: %s", err) state.Put("error", err) @@ -116,26 +86,7 @@ func (s *StepAttachVolume) CleanupFunc(state multistep.StateBag) error { s.attached = false // Wait for the volume to detach - stateChange := awscommon.StateChangeConf{ - Pending: []string{"attaching", "attached", "detaching"}, - StepState: state, - Target: "detached", - Refresh: func() (interface{}, string, error) { - resp, err := ec2conn.DescribeVolumes(&ec2.DescribeVolumesInput{VolumeIds: []*string{&s.volumeId}}) - if err != nil { - return nil, "", err - } - - v := resp.Volumes[0] - if len(v.Attachments) > 0 { - return v, *v.Attachments[0].State, nil - } else { - return v, "detached", nil - } - }, - } - - _, err = awscommon.WaitForState(&stateChange) + err = awscommon.WaitUntilVolumeDetached(ec2conn, s.volumeId) if err != nil { return fmt.Errorf("Error waiting for volume: %s", err) } diff --git a/builder/amazon/chroot/step_create_volume.go b/builder/amazon/chroot/step_create_volume.go index b3c205b26..c10fda4da 100644 --- a/builder/amazon/chroot/step_create_volume.go +++ b/builder/amazon/chroot/step_create_volume.go @@ -84,22 +84,7 @@ func (s *StepCreateVolume) Run(_ context.Context, state multistep.StateBag) mult log.Printf("Volume ID: %s", s.volumeId) // Wait for the volume to become ready - stateChange := awscommon.StateChangeConf{ - Pending: []string{"creating"}, - StepState: state, - Target: "available", - Refresh: func() (interface{}, string, error) { - resp, err := ec2conn.DescribeVolumes(&ec2.DescribeVolumesInput{VolumeIds: []*string{&s.volumeId}}) - if err != nil { - return nil, "", err - } - - v := resp.Volumes[0] - return v, *v.State, nil - }, - } - - _, err = awscommon.WaitForState(&stateChange) + err = awscommon.WaitUntilVolumeAvailable(ec2conn, s.volumeId) if err != nil { err := fmt.Errorf("Error waiting for volume: %s", err) state.Put("error", err) diff --git a/builder/amazon/chroot/step_register_ami.go b/builder/amazon/chroot/step_register_ami.go index 6fa9838b0..31a41cb9d 100644 --- a/builder/amazon/chroot/step_register_ami.go +++ b/builder/amazon/chroot/step_register_ami.go @@ -18,7 +18,7 @@ type StepRegisterAMI struct { EnableAMISriovNetSupport bool } -func (s *StepRegisterAMI) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { +func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*Config) ec2conn := state.Get("ec2").(*ec2.EC2) snapshotId := state.Get("snapshot_id").(string) @@ -103,21 +103,15 @@ func (s *StepRegisterAMI) Run(_ context.Context, state multistep.StateBag) multi state.Put("amis", amis) // Wait for the image to become ready - stateChange := awscommon.StateChangeConf{ - Pending: []string{"pending"}, - Target: "available", - Refresh: awscommon.AMIStateRefreshFunc(ec2conn, *registerResp.ImageId), - StepState: state, - } - - ui.Say("Waiting for AMI to become ready...") - if _, err := awscommon.WaitForState(&stateChange); err != nil { + if err := awscommon.WaitUntilAMIAvailable(ec2conn, *registerResp.ImageId); err != nil { err := fmt.Errorf("Error waiting for AMI: %s", err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } + ui.Say("Waiting for AMI to become ready...") + return multistep.ActionContinue } diff --git a/builder/amazon/chroot/step_snapshot.go b/builder/amazon/chroot/step_snapshot.go index 3fb3ff701..4e9070bc5 100644 --- a/builder/amazon/chroot/step_snapshot.go +++ b/builder/amazon/chroot/step_snapshot.go @@ -2,7 +2,6 @@ package chroot import ( "context" - "errors" "fmt" "time" @@ -44,26 +43,7 @@ func (s *StepSnapshot) Run(_ context.Context, state multistep.StateBag) multiste ui.Message(fmt.Sprintf("Snapshot ID: %s", s.snapshotId)) // Wait for the snapshot to be ready - stateChange := awscommon.StateChangeConf{ - Pending: []string{"pending"}, - StepState: state, - Target: "completed", - Refresh: func() (interface{}, string, error) { - resp, err := ec2conn.DescribeSnapshots(&ec2.DescribeSnapshotsInput{SnapshotIds: []*string{&s.snapshotId}}) - if err != nil { - return nil, "", err - } - - if len(resp.Snapshots) == 0 { - return nil, "", errors.New("No snapshots found.") - } - - s := resp.Snapshots[0] - return s, *s.State, nil - }, - } - - _, err = awscommon.WaitForState(&stateChange) + err = awscommon.WaitUntilSnapshotDone(ec2conn, s.snapshotId) if err != nil { err := fmt.Errorf("Error waiting for snapshot: %s", err) state.Put("error", err) diff --git a/builder/amazon/common/state.go b/builder/amazon/common/state.go index 4b5fe0d46..50e4e4b7a 100644 --- a/builder/amazon/common/state.go +++ b/builder/amazon/common/state.go @@ -1,16 +1,13 @@ package common import ( - "errors" - "fmt" "log" - "net" "os" "strconv" - "strings" "time" - "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/packer/helper/multistep" ) @@ -35,189 +32,220 @@ type StateChangeConf struct { Target string } -// AMIStateRefreshFunc returns a StateRefreshFunc that is used to watch -// an AMI for state changes. -func AMIStateRefreshFunc(conn *ec2.EC2, imageId string) StateRefreshFunc { - return func() (interface{}, string, error) { - resp, err := conn.DescribeImages(&ec2.DescribeImagesInput{ - ImageIds: []*string{&imageId}, - }) - if err != nil { - if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidAMIID.NotFound" { - // Set this to nil as if we didn't find anything. - resp = nil - } else if isTransientNetworkError(err) { - // Transient network error, treat it as if we didn't find anything - resp = nil - } else { - log.Printf("Error on AMIStateRefresh: %s", err) - return nil, "", err - } - } +// Following are wrapper functions that use Packer's environment-variables to +// determing retry logic, then call the AWS SDK's built-in waiters. - if resp == nil || len(resp.Images) == 0 { - // Sometimes AWS has consistency issues and doesn't see the - // AMI. Return an empty state. - return nil, "", nil - } - - i := resp.Images[0] - return i, *i.State, nil +func WaitUntilAMIAvailable(conn *ec2.EC2, imageId string) error { + imageInput := ec2.DescribeImagesInput{ + ImageIds: []*string{&imageId}, } + + err := conn.WaitUntilImageAvailableWithContext(aws.BackgroundContext(), + &imageInput, + getWaiterOptions()...) + return err } -// InstanceStateRefreshFunc returns a StateRefreshFunc that is used to watch -// an EC2 instance. -func InstanceStateRefreshFunc(conn *ec2.EC2, instanceId string) StateRefreshFunc { - return func() (interface{}, string, error) { - resp, err := conn.DescribeInstances(&ec2.DescribeInstancesInput{ - InstanceIds: []*string{&instanceId}, - }) - if err != nil { - if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidInstanceID.NotFound" { - // Set this to nil as if we didn't find anything. - resp = nil - } else if isTransientNetworkError(err) { - // Transient network error, treat it as if we didn't find anything - resp = nil - } else { - log.Printf("Error on InstanceStateRefresh: %s", err) - return nil, "", err - } - } +func WaitUntilInstanceTerminated(conn *ec2.EC2, instanceId string) error { - if resp == nil || len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 { - // Sometimes AWS just has consistency issues and doesn't see - // our instance yet. Return an empty state. - return nil, "", nil - } - - i := resp.Reservations[0].Instances[0] - return i, *i.State.Name, nil + instanceInput := ec2.DescribeInstancesInput{ + InstanceIds: []*string{&instanceId}, } + + err := conn.WaitUntilInstanceTerminatedWithContext(aws.BackgroundContext(), + &instanceInput, + getWaiterOptions()...) + return err } -// SpotRequestStateRefreshFunc returns a StateRefreshFunc that is used to watch -// a spot request for state changes. -func SpotRequestStateRefreshFunc(conn *ec2.EC2, spotRequestId string) StateRefreshFunc { - return func() (interface{}, string, error) { - resp, err := conn.DescribeSpotInstanceRequests(&ec2.DescribeSpotInstanceRequestsInput{ - SpotInstanceRequestIds: []*string{&spotRequestId}, - }) - - if err != nil { - if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidSpotInstanceRequestID.NotFound" { - // Set this to nil as if we didn't find anything. - resp = nil - } else if isTransientNetworkError(err) { - // Transient network error, treat it as if we didn't find anything - resp = nil - } else { - log.Printf("Error on SpotRequestStateRefresh: %s", err) - return nil, "", err - } - } - - if resp == nil || len(resp.SpotInstanceRequests) == 0 { - // Sometimes AWS has consistency issues and doesn't see the - // SpotRequest. Return an empty state. - return nil, "", nil - } - - i := resp.SpotInstanceRequests[0] - return i, *i.State, nil +// This function works for both requesting and cancelling spot instances. +func WaitUntilSpotRequestFulfilled(conn *ec2.EC2, spotRequestId string) error { + spotRequestInput := ec2.DescribeSpotInstanceRequestsInput{ + SpotInstanceRequestIds: []*string{&spotRequestId}, } + + err := conn.WaitUntilSpotInstanceRequestFulfilledWithContext(aws.BackgroundContext(), + &spotRequestInput, + getWaiterOptions()...) + return err } -func ImportImageRefreshFunc(conn *ec2.EC2, importTaskId string) StateRefreshFunc { - return func() (interface{}, string, error) { - resp, err := conn.DescribeImportImageTasks(&ec2.DescribeImportImageTasksInput{ - ImportTaskIds: []*string{ - &importTaskId, +func WaitUntilVolumeAvailable(conn *ec2.EC2, volumeId string) error { + volumeInput := ec2.DescribeVolumesInput{ + VolumeIds: []*string{&volumeId}, + } + + err := conn.WaitUntilVolumeAvailableWithContext(aws.BackgroundContext(), + &volumeInput, + getWaiterOptions()...) + return err +} + +func WaitUntilSnapshotDone(conn *ec2.EC2, snapshotID string) error { + snapInput := ec2.DescribeSnapshotsInput{ + SnapshotIds: []*string{&snapshotID}, + } + + err := conn.WaitUntilSnapshotCompletedWithContext(aws.BackgroundContext(), + &snapInput, + getWaiterOptions()...) + return err +} + +// Wrappers for our custom AWS waiters + +func WaitUntilVolumeAttached(conn *ec2.EC2, volumeId string) error { + volumeInput := ec2.DescribeVolumesInput{ + VolumeIds: []*string{&volumeId}, + } + + err := WaitForVolumeToBeAttached(conn, + aws.BackgroundContext(), + &volumeInput, + getWaiterOptions()...) + return err +} + +func WaitUntilVolumeDetached(conn *ec2.EC2, volumeId string) error { + volumeInput := ec2.DescribeVolumesInput{ + VolumeIds: []*string{&volumeId}, + } + + err := WaitForVolumeToBeAttached(conn, + aws.BackgroundContext(), + &volumeInput, + getWaiterOptions()...) + return err +} + +func WaitUntilImageImported(conn *ec2.EC2, taskID string) error { + importInput := ec2.DescribeImportImageTasksInput{ + ImportTaskIds: []*string{&taskID}, + } + + err := WaitForImageToBeImported(conn, + aws.BackgroundContext(), + &importInput, + getWaiterOptions()...) + return err +} + +// Custom waiters using AWS's request.Waiter + +func WaitForVolumeToBeAttached(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeVolumesInput, opts ...request.WaiterOption) error { + w := request.Waiter{ + Name: "DescribeVolumes", + MaxAttempts: 40, + Delay: request.ConstantWaiterDelay(5 * time.Second), + Acceptors: []request.WaiterAcceptor{ + { + State: request.SuccessWaiterState, + Matcher: request.PathAllWaiterMatch, + Argument: "Volumes[].State", + Expected: "attached", + }, + { + State: request.FailureWaiterState, + Matcher: request.PathAnyWaiterMatch, + Argument: "Volumes[].State", + Expected: "deleted", }, }, - ) - if err != nil { - if ec2err, ok := err.(awserr.Error); ok && strings.HasPrefix(ec2err.Code(), "InvalidConversionTaskId") { - resp = nil - } else if isTransientNetworkError(err) { - resp = nil - } else { - log.Printf("Error on ImportImageRefresh: %s", err) - return nil, "", err + Logger: c.Config.Logger, + NewRequest: func(opts []request.Option) (*request.Request, error) { + var inCpy *ec2.DescribeVolumesInput + if input != nil { + tmp := *input + inCpy = &tmp } - } - - if resp == nil || len(resp.ImportImageTasks) == 0 { - return nil, "", nil - } - - i := resp.ImportImageTasks[0] - return i, *i.Status, nil + req, _ := c.DescribeVolumesRequest(inCpy) + req.SetContext(ctx) + req.ApplyOptions(opts...) + return req, nil + }, } + return w.WaitWithContext(ctx) } -// WaitForState watches an object and waits for it to achieve a certain -// state. -func WaitForState(conf *StateChangeConf) (i interface{}, err error) { - log.Printf("Waiting for state to become: %s", conf.Target) - - sleepSeconds := SleepSeconds() - maxTicks := TimeoutSeconds()/sleepSeconds + 1 - notfoundTick := 0 - - for { - var currentState string - i, currentState, err = conf.Refresh() - if err != nil { - return - } - - if i == nil { - // If we didn't find the resource, check if we have been - // not finding it for awhile, and if so, report an error. - notfoundTick += 1 - if notfoundTick > maxTicks { - return nil, errors.New("couldn't find resource") +func WaitForVolumeToBeDetached(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeVolumesInput, opts ...request.WaiterOption) error { + w := request.Waiter{ + Name: "DescribeVolumes", + MaxAttempts: 40, + Delay: request.ConstantWaiterDelay(5 * time.Second), + Acceptors: []request.WaiterAcceptor{ + { + State: request.SuccessWaiterState, + Matcher: request.PathAllWaiterMatch, + Argument: "Volumes[].State", + Expected: "detached", + }, + }, + Logger: c.Config.Logger, + NewRequest: func(opts []request.Option) (*request.Request, error) { + var inCpy *ec2.DescribeVolumesInput + if input != nil { + tmp := *input + inCpy = &tmp } - } else { - // Reset the counter for when a resource isn't found - notfoundTick = 0 - - if currentState == conf.Target { - return - } - - if conf.StepState != nil { - if _, ok := conf.StepState.GetOk(multistep.StateCancelled); ok { - return nil, errors.New("interrupted") - } - } - - found := false - for _, allowed := range conf.Pending { - if currentState == allowed { - found = true - break - } - } - - if !found { - err := fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target) - return nil, err - } - } - - time.Sleep(time.Duration(sleepSeconds) * time.Second) + req, _ := c.DescribeVolumesRequest(inCpy) + req.SetContext(ctx) + req.ApplyOptions(opts...) + return req, nil + }, } + return w.WaitWithContext(ctx) } -func isTransientNetworkError(err error) bool { - if nerr, ok := err.(net.Error); ok && nerr.Temporary() { - return true +func WaitForImageToBeImported(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeImportImageTasksInput, opts ...request.WaiterOption) error { + w := request.Waiter{ + Name: "DescribeImages", + MaxAttempts: 40, + Delay: request.ConstantWaiterDelay(5 * time.Second), + Acceptors: []request.WaiterAcceptor{ + { + State: request.SuccessWaiterState, + Matcher: request.PathAllWaiterMatch, + Argument: "ImportImageTasks[].State", + Expected: "completed", + }, + { + State: request.RetryWaiterState, + Matcher: request.ErrorWaiterMatch, + Expected: "InvalidConversionTaskId", + }, + }, + Logger: c.Config.Logger, + NewRequest: func(opts []request.Option) (*request.Request, error) { + var inCpy *ec2.DescribeImportImageTasksInput + if input != nil { + tmp := *input + inCpy = &tmp + } + req, _ := c.DescribeImportImageTasksRequest(inCpy) + req.SetContext(ctx) + req.ApplyOptions(opts...) + return req, nil + }, } + return w.WaitWithContext(ctx) +} - return false +// This helper function uses the environment variables AWS_TIMEOUT_SECONDS and +// AWS_POLL_DELAY_SECONDS to generate waiter options that can be passed into any +// request.Waiter function. These options will control how many times the waiter +// will retry the request, as well as how long to wait between the retries. +func getWaiterOptions() []request.WaiterOption { + // use env vars to read in the wait delay and the max amount of time to wait + delay := SleepSeconds() + timeoutSeconds := TimeoutSeconds() + // AWS sdk uses max attempts instead of a timeout; convert timeout into + // max attempts + maxAttempts := timeoutSeconds / delay + delaySeconds := request.ConstantWaiterDelay(time.Duration(delay) * time.Second) + + return []request.WaiterOption{ + request.WithWaiterDelay(delaySeconds), + request.WithWaiterMaxAttempts(maxAttempts)} } // Returns 300 seconds (5 minutes) by default diff --git a/builder/amazon/common/step_ami_region_copy.go b/builder/amazon/common/step_ami_region_copy.go index a3d92b467..511a523fb 100644 --- a/builder/amazon/common/step_ami_region_copy.go +++ b/builder/amazon/common/step_ami_region_copy.go @@ -116,14 +116,8 @@ func amiRegionCopy(state multistep.StateBag, config *AccessConfig, name string, imageId, target, err) } - stateChange := StateChangeConf{ - Pending: []string{"pending"}, - Target: "available", - Refresh: AMIStateRefreshFunc(regionconn, *resp.ImageId), - StepState: state, - } - - if _, err := WaitForState(&stateChange); err != nil { + // Wait for the image to become ready + if err := WaitUntilAMIAvailable(regionconn, *resp.ImageId); err != nil { return "", snapshotIds, fmt.Errorf("Error waiting for AMI (%s) in region (%s): %s", *resp.ImageId, target, err) } diff --git a/builder/amazon/common/step_encrypted_ami.go b/builder/amazon/common/step_encrypted_ami.go index a07c0ee25..5125a70b0 100644 --- a/builder/amazon/common/step_encrypted_ami.go +++ b/builder/amazon/common/step_encrypted_ami.go @@ -65,15 +65,8 @@ func (s *StepCreateEncryptedAMICopy) Run(_ context.Context, state multistep.Stat } // Wait for the copy to become ready - stateChange := StateChangeConf{ - Pending: []string{"pending"}, - Target: "available", - Refresh: AMIStateRefreshFunc(ec2conn, *copyResp.ImageId), - StepState: state, - } - ui.Say("Waiting for AMI copy to become ready...") - if _, err := WaitForState(&stateChange); err != nil { + if err := WaitUntilAMIAvailable(ec2conn, *copyResp.ImageId); err != nil { err := fmt.Errorf("Error waiting for AMI Copy: %s", err) state.Put("error", err) ui.Error(err.Error()) diff --git a/builder/amazon/common/step_run_source_instance.go b/builder/amazon/common/step_run_source_instance.go index 4dd8fbe74..a7fb84076 100644 --- a/builder/amazon/common/step_run_source_instance.go +++ b/builder/amazon/common/step_run_source_instance.go @@ -303,14 +303,8 @@ func (s *StepRunSourceInstance) Cleanup(state multistep.StateBag) { ui.Error(fmt.Sprintf("Error terminating instance, may still be around: %s", err)) return } - stateChange := StateChangeConf{ - Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"}, - Refresh: InstanceStateRefreshFunc(ec2conn, s.instanceId), - Target: "terminated", - } - _, err := WaitForState(&stateChange) - if err != nil { + if err := WaitUntilInstanceTerminated(ec2conn, s.instanceId); err != nil { ui.Error(err.Error()) } } diff --git a/builder/amazon/common/step_run_spot_instance.go b/builder/amazon/common/step_run_spot_instance.go index 8c7cbf74a..4b0dc6005 100644 --- a/builder/amazon/common/step_run_spot_instance.go +++ b/builder/amazon/common/step_run_spot_instance.go @@ -202,13 +202,7 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag) spotRequestId := s.spotRequest.SpotInstanceRequestId ui.Message(fmt.Sprintf("Waiting for spot request (%s) to become active...", *spotRequestId)) - stateChange := StateChangeConf{ - Pending: []string{"open"}, - Target: "active", - Refresh: SpotRequestStateRefreshFunc(ec2conn, *spotRequestId), - StepState: state, - } - _, err = WaitForState(&stateChange) + err = WaitUntilSpotRequestFulfilled(ec2conn, *spotRequestId) if err != nil { err := fmt.Errorf("Error waiting for spot request (%s) to become ready: %s", *spotRequestId, err) state.Put("error", err) @@ -344,13 +338,8 @@ func (s *StepRunSpotInstance) Cleanup(state multistep.StateBag) { ui.Error(fmt.Sprintf("Error cancelling the spot request, may still be around: %s", err)) return } - stateChange := StateChangeConf{ - Pending: []string{"active", "open"}, - Refresh: SpotRequestStateRefreshFunc(ec2conn, *s.spotRequest.SpotInstanceRequestId), - Target: "cancelled", - } - _, err := WaitForState(&stateChange) + err := WaitUntilSpotRequestFulfilled(ec2conn, *s.spotRequest.SpotInstanceRequestId) if err != nil { ui.Error(err.Error()) } @@ -364,14 +353,8 @@ func (s *StepRunSpotInstance) Cleanup(state multistep.StateBag) { ui.Error(fmt.Sprintf("Error terminating instance, may still be around: %s", err)) return } - stateChange := StateChangeConf{ - Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"}, - Refresh: InstanceStateRefreshFunc(ec2conn, s.instanceId), - Target: "terminated", - } - _, err := WaitForState(&stateChange) - if err != nil { + if err := WaitUntilInstanceTerminated(ec2conn, s.instanceId); err != nil { ui.Error(err.Error()) } } diff --git a/builder/amazon/ebs/step_create_ami.go b/builder/amazon/ebs/step_create_ami.go index 4782f533b..cb3329325 100644 --- a/builder/amazon/ebs/step_create_ami.go +++ b/builder/amazon/ebs/step_create_ami.go @@ -44,15 +44,8 @@ func (s *stepCreateAMI) Run(_ context.Context, state multistep.StateBag) multist state.Put("amis", amis) // Wait for the image to become ready - stateChange := awscommon.StateChangeConf{ - Pending: []string{"pending"}, - Target: "available", - Refresh: awscommon.AMIStateRefreshFunc(ec2conn, *createResp.ImageId), - StepState: state, - } - ui.Say("Waiting for AMI to become ready...") - if _, err := awscommon.WaitForState(&stateChange); err != nil { + if err := awscommon.WaitUntilAMIAvailable(ec2conn, *createResp.ImageId); err != nil { log.Printf("Error waiting for AMI: %s", err) imagesResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{createResp.ImageId}}) if err != nil { diff --git a/builder/amazon/ebssurrogate/step_register_ami.go b/builder/amazon/ebssurrogate/step_register_ami.go index 4c12adca6..4f6a88667 100644 --- a/builder/amazon/ebssurrogate/step_register_ami.go +++ b/builder/amazon/ebssurrogate/step_register_ami.go @@ -63,15 +63,8 @@ func (s *StepRegisterAMI) Run(_ context.Context, state multistep.StateBag) multi state.Put("amis", amis) // Wait for the image to become ready - stateChange := awscommon.StateChangeConf{ - Pending: []string{"pending"}, - Target: "available", - Refresh: awscommon.AMIStateRefreshFunc(ec2conn, *registerResp.ImageId), - StepState: state, - } - ui.Say("Waiting for AMI to become ready...") - if _, err := awscommon.WaitForState(&stateChange); err != nil { + if err := awscommon.WaitUntilAMIAvailable(ec2conn, *registerResp.ImageId); err != nil { err := fmt.Errorf("Error waiting for AMI: %s", err) state.Put("error", err) ui.Error(err.Error()) diff --git a/builder/amazon/ebssurrogate/step_snapshot_volumes.go b/builder/amazon/ebssurrogate/step_snapshot_volumes.go index 9de755ca5..3beef0a8e 100644 --- a/builder/amazon/ebssurrogate/step_snapshot_volumes.go +++ b/builder/amazon/ebssurrogate/step_snapshot_volumes.go @@ -2,7 +2,6 @@ package ebssurrogate import ( "context" - "errors" "fmt" "sync" "time" @@ -52,29 +51,8 @@ func (s *StepSnapshotVolumes) snapshotVolume(deviceName string, state multistep. // Set the snapshot ID so we can delete it later s.snapshotIds[deviceName] = *createSnapResp.SnapshotId - // Wait for the snapshot to be ready - stateChange := awscommon.StateChangeConf{ - Pending: []string{"pending"}, - StepState: state, - Target: "completed", - Refresh: func() (interface{}, string, error) { - resp, err := ec2conn.DescribeSnapshots(&ec2.DescribeSnapshotsInput{ - SnapshotIds: []*string{createSnapResp.SnapshotId}, - }) - if err != nil { - return nil, "", err - } - - if len(resp.Snapshots) == 0 { - return nil, "", errors.New("No snapshots found.") - } - - s := resp.Snapshots[0] - return s, *s.State, nil - }, - } - - _, err = awscommon.WaitForState(&stateChange) + // Wait for snapshot to be created + err = awscommon.WaitUntilSnapshotDone(ec2conn, *createSnapResp.SnapshotId) return err } diff --git a/builder/amazon/instance/step_register_ami.go b/builder/amazon/instance/step_register_ami.go index c46ff8ae4..7ddb5bd57 100644 --- a/builder/amazon/instance/step_register_ami.go +++ b/builder/amazon/instance/step_register_ami.go @@ -58,15 +58,8 @@ func (s *StepRegisterAMI) Run(_ context.Context, state multistep.StateBag) multi state.Put("amis", amis) // Wait for the image to become ready - stateChange := awscommon.StateChangeConf{ - Pending: []string{"pending"}, - Target: "available", - Refresh: awscommon.AMIStateRefreshFunc(ec2conn, *registerResp.ImageId), - StepState: state, - } - ui.Say("Waiting for AMI to become ready...") - if _, err := awscommon.WaitForState(&stateChange); err != nil { + if err := awscommon.WaitUntilAMIAvailable(ec2conn, *registerResp.ImageId); err != nil { err := fmt.Errorf("Error waiting for AMI: %s", err) state.Put("error", err) ui.Error(err.Error()) diff --git a/post-processor/amazon-import/post-processor.go b/post-processor/amazon-import/post-processor.go index 5be505b0e..2dbbcc6c7 100644 --- a/post-processor/amazon-import/post-processor.go +++ b/post-processor/amazon-import/post-processor.go @@ -186,16 +186,7 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac // Wait for import process to complete, this takes a while ui.Message(fmt.Sprintf("Waiting for task %s to complete (may take a while)", *import_start.ImportTaskId)) - - stateChange := awscommon.StateChangeConf{ - Pending: []string{"pending", "active"}, - Refresh: awscommon.ImportImageRefreshFunc(ec2conn, *import_start.ImportTaskId), - Target: "completed", - } - - // Actually do the wait for state change - // We ignore errors out of this and check job state in AWS API - awscommon.WaitForState(&stateChange) + err = awscommon.WaitUntilImageImported(ec2conn, *import_start.ImportTaskId) // Retrieve what the outcome was for the import task import_result, err := ec2conn.DescribeImportImageTasks(&ec2.DescribeImportImageTasksInput{ @@ -235,13 +226,7 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac ui.Message(fmt.Sprintf("Waiting for AMI rename to complete (may take a while)")) - stateChange := awscommon.StateChangeConf{ - Pending: []string{"pending"}, - Target: "available", - Refresh: awscommon.AMIStateRefreshFunc(ec2conn, *resp.ImageId), - } - - if _, err := awscommon.WaitForState(&stateChange); err != nil { + if err := awscommon.WaitUntilAMIAvailable(ec2conn, *resp.ImageId); err != nil { return nil, false, fmt.Errorf("Error waiting for AMI (%s): %s", *resp.ImageId, err) }