github.phpd.cn/hashicorp/packer@v1.3.2/builder/amazon/common/state.go (about)

     1  package common
     2  
     3  import (
     4  	"log"
     5  	"os"
     6  	"strconv"
     7  	"time"
     8  
     9  	"github.com/aws/aws-sdk-go/aws"
    10  	"github.com/aws/aws-sdk-go/aws/request"
    11  	"github.com/aws/aws-sdk-go/service/ec2"
    12  	"github.com/hashicorp/packer/helper/multistep"
    13  )
    14  
    15  // StateRefreshFunc is a function type used for StateChangeConf that is
    16  // responsible for refreshing the item being watched for a state change.
    17  //
    18  // It returns three results. `result` is any object that will be returned
    19  // as the final object after waiting for state change. This allows you to
    20  // return the final updated object, for example an EC2 instance after refreshing
    21  // it.
    22  //
    23  // `state` is the latest state of that object. And `err` is any error that
    24  // may have happened while refreshing the state.
    25  type StateRefreshFunc func() (result interface{}, state string, err error)
    26  
    27  // StateChangeConf is the configuration struct used for `WaitForState`.
    28  type StateChangeConf struct {
    29  	Pending   []string
    30  	Refresh   StateRefreshFunc
    31  	StepState multistep.StateBag
    32  	Target    string
    33  }
    34  
    35  // Following are wrapper functions that use Packer's environment-variables to
    36  // determing retry logic, then call the AWS SDK's built-in waiters.
    37  
    38  func WaitUntilAMIAvailable(ctx aws.Context, conn *ec2.EC2, imageId string) error {
    39  	imageInput := ec2.DescribeImagesInput{
    40  		ImageIds: []*string{&imageId},
    41  	}
    42  
    43  	waitOpts := getWaiterOptions()
    44  	if len(waitOpts) == 0 {
    45  		// Bump this default to 30 minutes because the aws default
    46  		// of ten minutes doesn't work for some of our long-running copies.
    47  		waitOpts = append(waitOpts, request.WithWaiterMaxAttempts(120))
    48  	}
    49  	err := conn.WaitUntilImageAvailableWithContext(
    50  		ctx,
    51  		&imageInput,
    52  		waitOpts...)
    53  	return err
    54  }
    55  
    56  func WaitUntilInstanceTerminated(ctx aws.Context, conn *ec2.EC2, instanceId string) error {
    57  
    58  	instanceInput := ec2.DescribeInstancesInput{
    59  		InstanceIds: []*string{&instanceId},
    60  	}
    61  
    62  	err := conn.WaitUntilInstanceTerminatedWithContext(
    63  		ctx,
    64  		&instanceInput,
    65  		getWaiterOptions()...)
    66  	return err
    67  }
    68  
    69  // This function works for both requesting and cancelling spot instances.
    70  func WaitUntilSpotRequestFulfilled(ctx aws.Context, conn *ec2.EC2, spotRequestId string) error {
    71  	spotRequestInput := ec2.DescribeSpotInstanceRequestsInput{
    72  		SpotInstanceRequestIds: []*string{&spotRequestId},
    73  	}
    74  
    75  	err := conn.WaitUntilSpotInstanceRequestFulfilledWithContext(
    76  		ctx,
    77  		&spotRequestInput,
    78  		getWaiterOptions()...)
    79  	return err
    80  }
    81  
    82  func WaitUntilVolumeAvailable(ctx aws.Context, conn *ec2.EC2, volumeId string) error {
    83  	volumeInput := ec2.DescribeVolumesInput{
    84  		VolumeIds: []*string{&volumeId},
    85  	}
    86  
    87  	err := conn.WaitUntilVolumeAvailableWithContext(
    88  		ctx,
    89  		&volumeInput,
    90  		getWaiterOptions()...)
    91  	return err
    92  }
    93  
    94  func WaitUntilSnapshotDone(ctx aws.Context, conn *ec2.EC2, snapshotID string) error {
    95  	snapInput := ec2.DescribeSnapshotsInput{
    96  		SnapshotIds: []*string{&snapshotID},
    97  	}
    98  
    99  	err := conn.WaitUntilSnapshotCompletedWithContext(
   100  		ctx,
   101  		&snapInput,
   102  		getWaiterOptions()...)
   103  	return err
   104  }
   105  
   106  // Wrappers for our custom AWS waiters
   107  
   108  func WaitUntilVolumeAttached(ctx aws.Context, conn *ec2.EC2, volumeId string) error {
   109  	volumeInput := ec2.DescribeVolumesInput{
   110  		VolumeIds: []*string{&volumeId},
   111  	}
   112  
   113  	err := WaitForVolumeToBeAttached(conn,
   114  		ctx,
   115  		&volumeInput,
   116  		getWaiterOptions()...)
   117  	return err
   118  }
   119  
   120  func WaitUntilVolumeDetached(ctx aws.Context, conn *ec2.EC2, volumeId string) error {
   121  	volumeInput := ec2.DescribeVolumesInput{
   122  		VolumeIds: []*string{&volumeId},
   123  	}
   124  
   125  	err := WaitForVolumeToBeDetached(conn,
   126  		ctx,
   127  		&volumeInput,
   128  		getWaiterOptions()...)
   129  	return err
   130  }
   131  
   132  func WaitUntilImageImported(ctx aws.Context, conn *ec2.EC2, taskID string) error {
   133  	importInput := ec2.DescribeImportImageTasksInput{
   134  		ImportTaskIds: []*string{&taskID},
   135  	}
   136  
   137  	err := WaitForImageToBeImported(conn,
   138  		ctx,
   139  		&importInput,
   140  		getWaiterOptions()...)
   141  	return err
   142  }
   143  
   144  // Custom waiters using AWS's request.Waiter
   145  
   146  func WaitForVolumeToBeAttached(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeVolumesInput, opts ...request.WaiterOption) error {
   147  	w := request.Waiter{
   148  		Name:        "DescribeVolumes",
   149  		MaxAttempts: 40,
   150  		Delay:       request.ConstantWaiterDelay(5 * time.Second),
   151  		Acceptors: []request.WaiterAcceptor{
   152  			{
   153  				State:    request.SuccessWaiterState,
   154  				Matcher:  request.PathAllWaiterMatch,
   155  				Argument: "Volumes[].Attachments[].State",
   156  				Expected: "attached",
   157  			},
   158  		},
   159  		Logger: c.Config.Logger,
   160  		NewRequest: func(opts []request.Option) (*request.Request, error) {
   161  			var inCpy *ec2.DescribeVolumesInput
   162  			if input != nil {
   163  				tmp := *input
   164  				inCpy = &tmp
   165  			}
   166  			req, _ := c.DescribeVolumesRequest(inCpy)
   167  			req.SetContext(ctx)
   168  			req.ApplyOptions(opts...)
   169  			return req, nil
   170  		},
   171  	}
   172  	w.ApplyOptions(opts...)
   173  
   174  	return w.WaitWithContext(ctx)
   175  }
   176  
   177  func WaitForVolumeToBeDetached(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeVolumesInput, opts ...request.WaiterOption) error {
   178  	w := request.Waiter{
   179  		Name:        "DescribeVolumes",
   180  		MaxAttempts: 40,
   181  		Delay:       request.ConstantWaiterDelay(5 * time.Second),
   182  		Acceptors: []request.WaiterAcceptor{
   183  			{
   184  				State:    request.SuccessWaiterState,
   185  				Matcher:  request.PathAllWaiterMatch,
   186  				Argument: "length(Volumes[].Attachments[]) == `0`",
   187  				Expected: true,
   188  			},
   189  		},
   190  		Logger: c.Config.Logger,
   191  		NewRequest: func(opts []request.Option) (*request.Request, error) {
   192  			var inCpy *ec2.DescribeVolumesInput
   193  			if input != nil {
   194  				tmp := *input
   195  				inCpy = &tmp
   196  			}
   197  			req, _ := c.DescribeVolumesRequest(inCpy)
   198  			req.SetContext(ctx)
   199  			req.ApplyOptions(opts...)
   200  			return req, nil
   201  		},
   202  	}
   203  	w.ApplyOptions(opts...)
   204  
   205  	return w.WaitWithContext(ctx)
   206  }
   207  
   208  func WaitForImageToBeImported(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeImportImageTasksInput, opts ...request.WaiterOption) error {
   209  	w := request.Waiter{
   210  		Name:        "DescribeImages",
   211  		MaxAttempts: 720,
   212  		Delay:       request.ConstantWaiterDelay(5 * time.Second),
   213  		Acceptors: []request.WaiterAcceptor{
   214  			{
   215  				State:    request.SuccessWaiterState,
   216  				Matcher:  request.PathAllWaiterMatch,
   217  				Argument: "ImportImageTasks[].Status",
   218  				Expected: "completed",
   219  			},
   220  			{
   221  				State:    request.FailureWaiterState,
   222  				Matcher:  request.PathAnyWaiterMatch,
   223  				Argument: "ImportImageTasks[].Status",
   224  				Expected: "deleted",
   225  			},
   226  		},
   227  		Logger: c.Config.Logger,
   228  		NewRequest: func(opts []request.Option) (*request.Request, error) {
   229  			var inCpy *ec2.DescribeImportImageTasksInput
   230  			if input != nil {
   231  				tmp := *input
   232  				inCpy = &tmp
   233  			}
   234  			req, _ := c.DescribeImportImageTasksRequest(inCpy)
   235  			req.SetContext(ctx)
   236  			req.ApplyOptions(opts...)
   237  			return req, nil
   238  		},
   239  	}
   240  	w.ApplyOptions(opts...)
   241  
   242  	return w.WaitWithContext(ctx)
   243  }
   244  
   245  // This helper function uses the environment variables AWS_TIMEOUT_SECONDS and
   246  // AWS_POLL_DELAY_SECONDS to generate waiter options that can be passed into any
   247  // request.Waiter function. These options will control how many times the waiter
   248  // will retry the request, as well as how long to wait between the retries.
   249  
   250  // DEFAULTING BEHAVIOR:
   251  // if AWS_POLL_DELAY_SECONDS is set but the others are not, Packer will set this
   252  // poll delay and use the waiter-specific default
   253  
   254  // if AWS_TIMEOUT_SECONDS is set but AWS_MAX_ATTEMPTS is not, Packer will use
   255  // 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.
   256  
   257  // if AWS_TIMEOUT_SECONDS, _and_ AWS_MAX_ATTEMPTS are both set,
   258  // AWS_TIMEOUT_SECONDS will be ignored.
   259  
   260  // if AWS_MAX_ATTEMPTS is set but AWS_POLL_DELAY_SECONDS is not, then we will
   261  // use waiter-specific defaults.
   262  
   263  type envInfo struct {
   264  	envKey     string
   265  	Val        int
   266  	overridden bool
   267  }
   268  
   269  type overridableWaitVars struct {
   270  	awsPollDelaySeconds envInfo
   271  	awsMaxAttempts      envInfo
   272  	awsTimeoutSeconds   envInfo
   273  }
   274  
   275  func getWaiterOptions() []request.WaiterOption {
   276  	envOverrides := getEnvOverrides()
   277  	waitOpts := applyEnvOverrides(envOverrides)
   278  	return waitOpts
   279  }
   280  
   281  func getOverride(varInfo envInfo) envInfo {
   282  	override := os.Getenv(varInfo.envKey)
   283  	if override != "" {
   284  		n, err := strconv.Atoi(override)
   285  		if err != nil {
   286  			log.Printf("Invalid %s '%s', using default", varInfo.envKey, override)
   287  		} else {
   288  			varInfo.overridden = true
   289  			varInfo.Val = n
   290  		}
   291  	}
   292  
   293  	return varInfo
   294  }
   295  func getEnvOverrides() overridableWaitVars {
   296  	// Load env vars from environment.
   297  	envValues := overridableWaitVars{
   298  		envInfo{"AWS_POLL_DELAY_SECONDS", 2, false},
   299  		envInfo{"AWS_MAX_ATTEMPTS", 0, false},
   300  		envInfo{"AWS_TIMEOUT_SECONDS", 0, false},
   301  	}
   302  
   303  	envValues.awsMaxAttempts = getOverride(envValues.awsMaxAttempts)
   304  	envValues.awsPollDelaySeconds = getOverride(envValues.awsPollDelaySeconds)
   305  	envValues.awsTimeoutSeconds = getOverride(envValues.awsTimeoutSeconds)
   306  
   307  	return envValues
   308  }
   309  
   310  func applyEnvOverrides(envOverrides overridableWaitVars) []request.WaiterOption {
   311  	waitOpts := make([]request.WaiterOption, 0)
   312  	// If user has set poll delay seconds, overwrite it. If user has NOT,
   313  	// default to a poll delay of 2 seconds
   314  	if envOverrides.awsPollDelaySeconds.overridden {
   315  		delaySeconds := request.ConstantWaiterDelay(time.Duration(envOverrides.awsPollDelaySeconds.Val) * time.Second)
   316  		waitOpts = append(waitOpts, request.WithWaiterDelay(delaySeconds))
   317  	}
   318  
   319  	// If user has set max attempts, overwrite it. If user hasn't set max
   320  	// attempts, default to whatever the waiter has set as a default.
   321  	if envOverrides.awsMaxAttempts.overridden {
   322  		waitOpts = append(waitOpts, request.WithWaiterMaxAttempts(envOverrides.awsMaxAttempts.Val))
   323  	}
   324  
   325  	if envOverrides.awsMaxAttempts.overridden && envOverrides.awsTimeoutSeconds.overridden {
   326  		log.Printf("WARNING: AWS_MAX_ATTEMPTS and AWS_TIMEOUT_SECONDS are" +
   327  			" both set. Packer will be using AWS_MAX_ATTEMPTS and discarding " +
   328  			"AWS_TIMEOUT_SECONDS. If you have not set AWS_POLL_DELAY_SECONDS, " +
   329  			"Packer will default to a 2 second poll delay.")
   330  	} else if envOverrides.awsTimeoutSeconds.overridden {
   331  		log.Printf("DEPRECATION WARNING: env var AWS_TIMEOUT_SECONDS is " +
   332  			"deprecated in favor of AWS_MAX_ATTEMPTS. If you have not " +
   333  			"explicitly set AWS_POLL_DELAY_SECONDS, we are defaulting to a " +
   334  			"poll delay of 2 seconds, regardless of the AWS waiter's default.")
   335  		maxAttempts := envOverrides.awsTimeoutSeconds.Val / envOverrides.awsPollDelaySeconds.Val
   336  		// override the delay so we can get the timeout right
   337  		if !envOverrides.awsPollDelaySeconds.overridden {
   338  			delaySeconds := request.ConstantWaiterDelay(time.Duration(envOverrides.awsPollDelaySeconds.Val) * time.Second)
   339  			waitOpts = append(waitOpts, request.WithWaiterDelay(delaySeconds))
   340  		}
   341  		waitOpts = append(waitOpts, request.WithWaiterMaxAttempts(maxAttempts))
   342  	}
   343  	if len(waitOpts) == 0 {
   344  		log.Printf("No AWS timeout and polling overrides have been set. " +
   345  			"Packer will default to waiter-specific delays and timeouts. If you would " +
   346  			"like to customize the length of time between retries and max " +
   347  			"number of retries you may do so by setting the environment " +
   348  			"variables AWS_POLL_DELAY_SECONDS and AWS_MAX_ATTEMPTS to your " +
   349  			"desired values.")
   350  	}
   351  
   352  	return waitOpts
   353  }