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-06-01 19:17:30 -04:00
func WaitUntilAMIAvailable ( ctx aws . Context , conn * ec2 . EC2 , imageId string ) error {
2018-05-31 14:29:04 -04:00
imageInput := ec2 . DescribeImagesInput {
ImageIds : [ ] * string { & imageId } ,
2013-09-12 23:33:32 -04:00
}
2018-08-17 16:27:19 -04:00
waitOpts := getWaiterOptions ( )
if len ( waitOpts ) == 0 {
// Bump this default to 25 minutes because the aws default
// of ten minutes doesn't work for some of our long-running copies.
waitOpts = append ( waitOpts , request . WithWaiterMaxAttempts ( 100 ) )
}
2018-06-01 19:17:30 -04:00
err := conn . WaitUntilImageAvailableWithContext (
ctx ,
2018-05-31 14:29:04 -04:00
& imageInput ,
2018-08-17 16:27:19 -04:00
waitOpts ... )
2018-05-31 14:29:04 -04:00
return err
}
2013-07-29 21:47:43 -04:00
2018-06-01 19:17:30 -04:00
func WaitUntilInstanceTerminated ( ctx aws . Context , 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
2018-06-01 19:17:30 -04:00
err := conn . WaitUntilInstanceTerminatedWithContext (
ctx ,
2018-05-31 14:29:04 -04:00
& 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.
2018-06-01 19:17:30 -04:00
func WaitUntilSpotRequestFulfilled ( ctx aws . Context , conn * ec2 . EC2 , spotRequestId string ) error {
2018-05-31 14:29:04 -04:00
spotRequestInput := ec2 . DescribeSpotInstanceRequestsInput {
SpotInstanceRequestIds : [ ] * string { & spotRequestId } ,
}
2014-05-07 13:13:27 -04:00
2018-06-01 19:17:30 -04:00
err := conn . WaitUntilSpotInstanceRequestFulfilledWithContext (
ctx ,
2018-05-31 14:29:04 -04:00
& spotRequestInput ,
getWaiterOptions ( ) ... )
return err
}
2014-05-07 13:13:27 -04:00
2018-06-01 19:17:30 -04:00
func WaitUntilVolumeAvailable ( ctx aws . Context , conn * ec2 . EC2 , volumeId string ) error {
2018-05-31 14:29:04 -04:00
volumeInput := ec2 . DescribeVolumesInput {
VolumeIds : [ ] * string { & volumeId } ,
2014-05-07 13:13:27 -04:00
}
2018-05-31 14:29:04 -04:00
2018-06-01 19:17:30 -04:00
err := conn . WaitUntilVolumeAvailableWithContext (
ctx ,
2018-05-31 14:29:04 -04:00
& volumeInput ,
getWaiterOptions ( ) ... )
return err
2014-05-07 13:13:27 -04:00
}
2018-06-01 19:17:30 -04:00
func WaitUntilSnapshotDone ( ctx aws . Context , conn * ec2 . EC2 , snapshotID string ) error {
2018-05-31 14:29:04 -04:00
snapInput := ec2 . DescribeSnapshotsInput {
SnapshotIds : [ ] * string { & snapshotID } ,
}
2018-06-01 19:17:30 -04:00
err := conn . WaitUntilSnapshotCompletedWithContext (
ctx ,
2018-05-31 14:29:04 -04:00
& 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-06-01 19:17:30 -04:00
func WaitUntilVolumeAttached ( ctx aws . Context , conn * ec2 . EC2 , volumeId string ) error {
2018-05-31 14:29:04 -04:00
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 ,
2018-06-01 19:17:30 -04:00
ctx ,
2018-05-31 14:29:04 -04:00
& volumeInput ,
getWaiterOptions ( ) ... )
return err
2015-11-22 21:55:09 -05:00
}
2018-06-01 19:17:30 -04:00
func WaitUntilVolumeDetached ( ctx aws . Context , conn * ec2 . EC2 , volumeId string ) error {
2018-05-31 14:29:04 -04:00
volumeInput := ec2 . DescribeVolumesInput {
VolumeIds : [ ] * string { & volumeId } ,
}
2013-09-12 23:37:14 -04:00
2018-06-01 19:17:30 -04:00
err := WaitForVolumeToBeDetached ( conn ,
ctx ,
2018-05-31 14:29:04 -04:00
& volumeInput ,
getWaiterOptions ( ) ... )
return err
}
2013-07-29 21:47:43 -04:00
2018-06-01 19:17:30 -04:00
func WaitUntilImageImported ( ctx aws . Context , conn * ec2 . EC2 , taskID string ) error {
2018-05-31 14:29:04 -04:00
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 ,
2018-06-01 19:17:30 -04:00
ctx ,
2018-05-31 14:29:04 -04:00
& 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 ,
2018-06-04 18:34:20 -04:00
Argument : "Volumes[].Attachments[].State" ,
2018-05-31 14:29:04 -04:00
Expected : "attached" ,
} ,
} ,
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 ,
2018-06-04 18:34:20 -04:00
Argument : "length(Volumes[].Attachments[]) == `0`" ,
Expected : true ,
2018-05-31 14:29:04 -04:00
} ,
} ,
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" ,
2018-06-04 18:34:20 -04:00
MaxAttempts : 300 ,
2018-05-31 14:29:04 -04:00
Delay : request . ConstantWaiterDelay ( 5 * time . Second ) ,
Acceptors : [ ] request . WaiterAcceptor {
{
State : request . SuccessWaiterState ,
Matcher : request . PathAllWaiterMatch ,
2018-06-04 18:34:20 -04:00
Argument : "ImportImageTasks[].Status" ,
2018-05-31 14:29:04 -04:00
Expected : "completed" ,
} ,
} ,
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.
2018-06-04 18:34:20 -04:00
// DEFAULTING BEHAVIOR:
// if AWS_POLL_DELAY_SECONDS is set but the others are not, Packer will set this
// poll delay and use the waiter-specific default
// if AWS_TIMEOUT_SECONDS is set but AWS_MAX_ATTEMPTS is not, Packer will use
// AWS_TIMEOUT_SECONDS and _either_ AWS_POLL_DELAY_SECONDS _or_ 2 if the user has not set AWS_POLL_DELAY_SECONDS, to determine a max number of attempts to make.
// if AWS_TIMEOUT_SECONDS, _and_ AWS_MAX_ATTEMPTS are both set,
// AWS_TIMEOUT_SECONDS will be ignored.
// if AWS_MAX_ATTEMPTS is set but AWS_POLL_DELAY_SECONDS is not, then we will
// use waiter-specific defaults.
2018-06-29 19:33:26 -04:00
type envInfo struct {
envKey string
Val int
overridden bool
}
type overridableWaitVars struct {
awsPollDelaySeconds envInfo
awsMaxAttempts envInfo
awsTimeoutSeconds envInfo
}
2018-05-31 14:29:04 -04:00
func getWaiterOptions ( ) [ ] request . WaiterOption {
2018-06-29 19:33:26 -04:00
envOverrides := getEnvOverrides ( )
waitOpts := applyEnvOverrides ( envOverrides )
return waitOpts
}
2018-07-11 16:10:38 -04:00
func getOverride ( varInfo envInfo ) envInfo {
override := os . Getenv ( varInfo . envKey )
if override != "" {
n , err := strconv . Atoi ( override )
if err != nil {
log . Printf ( "Invalid %s '%s', using default" , varInfo . envKey , override )
} else {
varInfo . overridden = true
varInfo . Val = n
}
}
return varInfo
}
2018-06-29 19:33:26 -04:00
func getEnvOverrides ( ) overridableWaitVars {
// Load env vars from environment, and use them to override defaults
envValues := overridableWaitVars {
envInfo { "AWS_POLL_DELAY_SECONDS" , 2 , false } ,
envInfo { "AWS_MAX_ATTEMPTS" , 0 , false } ,
envInfo { "AWS_TIMEOUT_SECONDS" , 300 , false } ,
}
2018-07-11 16:10:38 -04:00
envValues . awsMaxAttempts = getOverride ( envValues . awsMaxAttempts )
envValues . awsPollDelaySeconds = getOverride ( envValues . awsPollDelaySeconds )
envValues . awsTimeoutSeconds = getOverride ( envValues . awsTimeoutSeconds )
2018-06-29 19:33:26 -04:00
return envValues
}
func applyEnvOverrides ( envOverrides overridableWaitVars ) [ ] request . WaiterOption {
2018-06-04 18:34:20 -04:00
waitOpts := make ( [ ] request . WaiterOption , 0 )
// If user has set poll delay seconds, overwrite it. If user has NOT,
// default to a poll delay of 2 seconds
2018-06-29 19:33:26 -04:00
if envOverrides . awsPollDelaySeconds . overridden {
delaySeconds := request . ConstantWaiterDelay ( time . Duration ( envOverrides . awsPollDelaySeconds . Val ) * time . Second )
2018-06-04 18:34:20 -04:00
waitOpts = append ( waitOpts , request . WithWaiterDelay ( delaySeconds ) )
}
2014-09-29 21:14:52 -04:00
2018-06-04 18:34:20 -04:00
// If user has set max attempts, overwrite it. If user hasn't set max
// attempts, default to whatever the waiter has set as a default.
2018-06-29 19:33:26 -04:00
if envOverrides . awsMaxAttempts . overridden {
waitOpts = append ( waitOpts , request . WithWaiterMaxAttempts ( envOverrides . awsMaxAttempts . Val ) )
2018-06-04 18:34:20 -04:00
}
2014-09-29 21:14:52 -04:00
2018-06-29 19:33:26 -04:00
if envOverrides . awsMaxAttempts . overridden && envOverrides . awsTimeoutSeconds . overridden {
2018-06-04 18:34:20 -04:00
log . Printf ( "WARNING: AWS_MAX_ATTEMPTS and AWS_TIMEOUT_SECONDS are" +
" both set. Packer will be using AWS_MAX_ATTEMPTS and discarding " +
"AWS_TIMEOUT_SECONDS. If you have not set AWS_POLL_DELAY_SECONDS, " +
"Packer will default to a 2 second poll delay." )
2018-06-29 19:33:26 -04:00
} else if envOverrides . awsTimeoutSeconds . overridden {
2018-06-04 18:34:20 -04:00
log . Printf ( "DEPRECATION WARNING: env var AWS_TIMEOUT_SECONDS is " +
"deprecated in favor of AWS_MAX_ATTEMPTS. If you have not " +
"explicitly set AWS_POLL_DELAY_SECONDS, we are defaulting to a " +
"poll delay of 2 seconds, regardless of the AWS waiter's default." )
2018-06-29 19:33:26 -04:00
maxAttempts := envOverrides . awsTimeoutSeconds . Val / envOverrides . awsPollDelaySeconds . Val
2018-06-04 18:34:20 -04:00
// override the delay so we can get the timeout right
2018-06-29 19:33:26 -04:00
if ! envOverrides . awsPollDelaySeconds . overridden {
delaySeconds := request . ConstantWaiterDelay ( time . Duration ( envOverrides . awsPollDelaySeconds . Val ) * time . Second )
2018-06-04 18:34:20 -04:00
waitOpts = append ( waitOpts , request . WithWaiterDelay ( delaySeconds ) )
2014-09-29 21:14:52 -04:00
}
2018-06-04 18:34:20 -04:00
waitOpts = append ( waitOpts , request . WithWaiterMaxAttempts ( maxAttempts ) )
2014-09-29 21:14:52 -04:00
}
2018-06-07 12:30:49 -04:00
if len ( waitOpts ) == 0 {
log . Printf ( "No AWS timeout and polling overrides have been set. " +
2018-07-18 16:00:45 -04:00
"Packer will default to waiter-specific delays and timeouts. If you would " +
2018-06-07 12:30:49 -04:00
"like to customize the length of time between retries and max " +
"number of retries you may do so by setting the environment " +
"variables AWS_POLL_DELAY_SECONDS and AWS_MAX_ATTEMPTS to your " +
"desired values." )
}
2014-09-29 21:14:52 -04:00
2018-06-04 18:34:20 -04:00
return waitOpts
2014-09-29 21:14:52 -04:00
}