2013-07-20 22:58:27 -04:00
|
|
|
package common
|
2013-05-21 03:55:32 -04:00
|
|
|
|
|
|
|
import (
|
|
|
|
"log"
|
2014-09-29 21:14:52 -04:00
|
|
|
"os"
|
|
|
|
"strconv"
|
2016-02-12 02:53:40 -05:00
|
|
|
"time"
|
2015-04-05 17:58:48 -04:00
|
|
|
|
2018-05-31 14:29:04 -04:00
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
|
|
"github.com/aws/aws-sdk-go/aws/request"
|
2015-06-03 17:13:52 -04:00
|
|
|
"github.com/aws/aws-sdk-go/service/ec2"
|
2018-01-19 19:18:44 -05:00
|
|
|
"github.com/hashicorp/packer/helper/multistep"
|
2013-05-21 03:55:32 -04:00
|
|
|
)
|
|
|
|
|
2013-08-03 19:21:01 -04:00
|
|
|
// StateRefreshFunc is a function type used for StateChangeConf that is
|
|
|
|
// responsible for refreshing the item being watched for a state change.
|
|
|
|
//
|
|
|
|
// It returns three results. `result` is any object that will be returned
|
|
|
|
// as the final object after waiting for state change. This allows you to
|
|
|
|
// return the final updated object, for example an EC2 instance after refreshing
|
|
|
|
// it.
|
|
|
|
//
|
|
|
|
// `state` is the latest state of that object. And `err` is any error that
|
|
|
|
// may have happened while refreshing the state.
|
|
|
|
type StateRefreshFunc func() (result interface{}, state string, err error)
|
|
|
|
|
|
|
|
// StateChangeConf is the configuration struct used for `WaitForState`.
|
2013-07-25 21:49:15 -04:00
|
|
|
type StateChangeConf struct {
|
|
|
|
Pending []string
|
2013-08-03 19:21:01 -04:00
|
|
|
Refresh StateRefreshFunc
|
2013-08-31 15:58:55 -04:00
|
|
|
StepState multistep.StateBag
|
2013-07-25 21:49:15 -04:00
|
|
|
Target string
|
|
|
|
}
|
|
|
|
|
2018-05-31 14:29:04 -04:00
|
|
|
// Following are wrapper functions that use Packer's environment-variables to
|
|
|
|
// determing retry logic, then call the AWS SDK's built-in waiters.
|
2013-09-12 23:33:32 -04:00
|
|
|
|
2018-05-31 14:29:04 -04:00
|
|
|
func WaitUntilAMIAvailable(conn *ec2.EC2, imageId string) error {
|
|
|
|
imageInput := ec2.DescribeImagesInput{
|
|
|
|
ImageIds: []*string{&imageId},
|
2013-09-12 23:33:32 -04:00
|
|
|
}
|
|
|
|
|
2018-05-31 14:29:04 -04:00
|
|
|
err := conn.WaitUntilImageAvailableWithContext(aws.BackgroundContext(),
|
|
|
|
&imageInput,
|
|
|
|
getWaiterOptions()...)
|
|
|
|
return err
|
|
|
|
}
|
2013-07-29 21:47:43 -04:00
|
|
|
|
2018-05-31 14:29:04 -04:00
|
|
|
func WaitUntilInstanceTerminated(conn *ec2.EC2, instanceId string) error {
|
2013-09-05 20:19:23 -04:00
|
|
|
|
2018-05-31 14:29:04 -04:00
|
|
|
instanceInput := ec2.DescribeInstancesInput{
|
|
|
|
InstanceIds: []*string{&instanceId},
|
2013-07-29 21:47:43 -04:00
|
|
|
}
|
2018-05-31 14:29:04 -04:00
|
|
|
|
|
|
|
err := conn.WaitUntilInstanceTerminatedWithContext(aws.BackgroundContext(),
|
|
|
|
&instanceInput,
|
|
|
|
getWaiterOptions()...)
|
|
|
|
return err
|
2013-07-29 21:47:43 -04:00
|
|
|
}
|
|
|
|
|
2018-05-31 14:29:04 -04:00
|
|
|
// This function works for both requesting and cancelling spot instances.
|
|
|
|
func WaitUntilSpotRequestFulfilled(conn *ec2.EC2, spotRequestId string) error {
|
|
|
|
spotRequestInput := ec2.DescribeSpotInstanceRequestsInput{
|
|
|
|
SpotInstanceRequestIds: []*string{&spotRequestId},
|
|
|
|
}
|
2014-05-07 13:13:27 -04:00
|
|
|
|
2018-05-31 14:29:04 -04:00
|
|
|
err := conn.WaitUntilSpotInstanceRequestFulfilledWithContext(aws.BackgroundContext(),
|
|
|
|
&spotRequestInput,
|
|
|
|
getWaiterOptions()...)
|
|
|
|
return err
|
|
|
|
}
|
2014-05-07 13:13:27 -04:00
|
|
|
|
2018-05-31 14:29:04 -04:00
|
|
|
func WaitUntilVolumeAvailable(conn *ec2.EC2, volumeId string) error {
|
|
|
|
volumeInput := ec2.DescribeVolumesInput{
|
|
|
|
VolumeIds: []*string{&volumeId},
|
2014-05-07 13:13:27 -04:00
|
|
|
}
|
2018-05-31 14:29:04 -04:00
|
|
|
|
|
|
|
err := conn.WaitUntilVolumeAvailableWithContext(aws.BackgroundContext(),
|
|
|
|
&volumeInput,
|
|
|
|
getWaiterOptions()...)
|
|
|
|
return err
|
2014-05-07 13:13:27 -04:00
|
|
|
}
|
|
|
|
|
2018-05-31 14:29:04 -04:00
|
|
|
func WaitUntilSnapshotDone(conn *ec2.EC2, snapshotID string) error {
|
|
|
|
snapInput := ec2.DescribeSnapshotsInput{
|
|
|
|
SnapshotIds: []*string{&snapshotID},
|
|
|
|
}
|
|
|
|
|
|
|
|
err := conn.WaitUntilSnapshotCompletedWithContext(aws.BackgroundContext(),
|
|
|
|
&snapInput,
|
|
|
|
getWaiterOptions()...)
|
|
|
|
return err
|
|
|
|
}
|
2016-02-12 02:53:40 -05:00
|
|
|
|
2018-05-31 14:29:04 -04:00
|
|
|
// Wrappers for our custom AWS waiters
|
2016-02-12 02:53:40 -05:00
|
|
|
|
2018-05-31 14:29:04 -04:00
|
|
|
func WaitUntilVolumeAttached(conn *ec2.EC2, volumeId string) error {
|
|
|
|
volumeInput := ec2.DescribeVolumesInput{
|
|
|
|
VolumeIds: []*string{&volumeId},
|
2016-02-12 02:53:40 -05:00
|
|
|
}
|
2018-05-31 14:29:04 -04:00
|
|
|
|
|
|
|
err := WaitForVolumeToBeAttached(conn,
|
|
|
|
aws.BackgroundContext(),
|
|
|
|
&volumeInput,
|
|
|
|
getWaiterOptions()...)
|
|
|
|
return err
|
2015-11-22 21:55:09 -05:00
|
|
|
}
|
|
|
|
|
2018-05-31 14:29:04 -04:00
|
|
|
func WaitUntilVolumeDetached(conn *ec2.EC2, volumeId string) error {
|
|
|
|
volumeInput := ec2.DescribeVolumesInput{
|
|
|
|
VolumeIds: []*string{&volumeId},
|
|
|
|
}
|
2013-09-12 23:37:14 -04:00
|
|
|
|
2018-05-31 14:29:04 -04:00
|
|
|
err := WaitForVolumeToBeAttached(conn,
|
|
|
|
aws.BackgroundContext(),
|
|
|
|
&volumeInput,
|
|
|
|
getWaiterOptions()...)
|
|
|
|
return err
|
|
|
|
}
|
2013-07-29 21:47:43 -04:00
|
|
|
|
2018-05-31 14:29:04 -04:00
|
|
|
func WaitUntilImageImported(conn *ec2.EC2, taskID string) error {
|
|
|
|
importInput := ec2.DescribeImportImageTasksInput{
|
|
|
|
ImportTaskIds: []*string{&taskID},
|
|
|
|
}
|
2013-05-21 03:55:32 -04:00
|
|
|
|
2018-05-31 14:29:04 -04:00
|
|
|
err := WaitForImageToBeImported(conn,
|
|
|
|
aws.BackgroundContext(),
|
|
|
|
&importInput,
|
|
|
|
getWaiterOptions()...)
|
|
|
|
return err
|
|
|
|
}
|
2013-06-04 14:29:59 -04:00
|
|
|
|
2018-05-31 14:29:04 -04:00
|
|
|
// 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",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Logger: c.Config.Logger,
|
|
|
|
NewRequest: func(opts []request.Option) (*request.Request, error) {
|
|
|
|
var inCpy *ec2.DescribeVolumesInput
|
|
|
|
if input != nil {
|
|
|
|
tmp := *input
|
|
|
|
inCpy = &tmp
|
2013-09-05 20:19:23 -04:00
|
|
|
}
|
2018-05-31 14:29:04 -04:00
|
|
|
req, _ := c.DescribeVolumesRequest(inCpy)
|
|
|
|
req.SetContext(ctx)
|
|
|
|
req.ApplyOptions(opts...)
|
|
|
|
return req, nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return w.WaitWithContext(ctx)
|
|
|
|
}
|
2013-06-04 14:29:59 -04:00
|
|
|
|
2018-05-31 14:29:04 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
req, _ := c.DescribeVolumesRequest(inCpy)
|
|
|
|
req.SetContext(ctx)
|
|
|
|
req.ApplyOptions(opts...)
|
|
|
|
return req, nil
|
|
|
|
},
|
2013-05-21 03:55:32 -04:00
|
|
|
}
|
2018-05-31 14:29:04 -04:00
|
|
|
return w.WaitWithContext(ctx)
|
2013-05-21 03:55:32 -04:00
|
|
|
}
|
2014-10-14 18:39:13 -04:00
|
|
|
|
2018-05-31 14:29:04 -04:00
|
|
|
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
|
|
|
|
},
|
2014-10-14 18:39:13 -04:00
|
|
|
}
|
2018-05-31 14:29:04 -04:00
|
|
|
return w.WaitWithContext(ctx)
|
|
|
|
}
|
2014-10-14 18:39:13 -04:00
|
|
|
|
2018-05-31 14:29:04 -04:00
|
|
|
// 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)}
|
2014-10-14 18:39:13 -04:00
|
|
|
}
|
2014-09-29 21:14:52 -04:00
|
|
|
|
|
|
|
// Returns 300 seconds (5 minutes) by default
|
|
|
|
// Some AWS operations, like copying an AMI to a distant region, take a very long time
|
|
|
|
// Allow user to override with AWS_TIMEOUT_SECONDS environment variable
|
|
|
|
func TimeoutSeconds() (seconds int) {
|
|
|
|
seconds = 300
|
|
|
|
|
|
|
|
override := os.Getenv("AWS_TIMEOUT_SECONDS")
|
|
|
|
if override != "" {
|
|
|
|
n, err := strconv.Atoi(override)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Invalid timeout seconds '%s', using default", override)
|
|
|
|
} else {
|
|
|
|
seconds = n
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("Allowing %ds to complete (change with AWS_TIMEOUT_SECONDS)", seconds)
|
|
|
|
return seconds
|
|
|
|
}
|
2016-10-28 03:28:12 -04:00
|
|
|
|
|
|
|
// Returns 2 seconds by default
|
|
|
|
// AWS async operations sometimes takes long times, if there are multiple parallel builds,
|
|
|
|
// polling at 2 second frequency will exceed the request limit. Allow 2 seconds to be
|
|
|
|
// overwritten with AWS_POLL_DELAY_SECONDS
|
|
|
|
func SleepSeconds() (seconds int) {
|
|
|
|
seconds = 2
|
|
|
|
|
|
|
|
override := os.Getenv("AWS_POLL_DELAY_SECONDS")
|
|
|
|
if override != "" {
|
|
|
|
n, err := strconv.Atoi(override)
|
|
|
|
if err != nil {
|
2016-10-31 19:44:03 -04:00
|
|
|
log.Printf("Invalid sleep seconds '%s', using default", override)
|
2016-10-28 03:28:12 -04:00
|
|
|
} else {
|
|
|
|
seconds = n
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("Using %ds as polling delay (change with AWS_POLL_DELAY_SECONDS)", seconds)
|
|
|
|
return seconds
|
|
|
|
}
|