Merge pull request #7682 from hashicorp/do_7648

Do 7648
pull/7694/head
Adrien Delorme 7 years ago committed by GitHub
commit 65bd7207e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -73,6 +73,7 @@ type RunConfig struct {
SecurityGroupIds []string `mapstructure:"security_group_ids"`
SourceAmi string `mapstructure:"source_ami"`
SourceAmiFilter AmiFilterOptions `mapstructure:"source_ami_filter"`
SpotInstanceTypes []string `mapstructure:"spot_instance_types"`
SpotPrice string `mapstructure:"spot_price"`
SpotPriceAutoProduct string `mapstructure:"spot_price_auto_product"`
SpotTags map[string]string `mapstructure:"spot_tags"`
@ -137,8 +138,14 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
errs = append(errs, fmt.Errorf("For security reasons, your source AMI filter must declare an owner."))
}
if c.InstanceType == "" {
errs = append(errs, fmt.Errorf("An instance_type must be specified"))
if c.InstanceType == "" && len(c.SpotInstanceTypes) == 0 {
errs = append(errs, fmt.Errorf("either instance_type or "+
"spot_instance_types must be specified"))
}
if c.InstanceType != "" && len(c.SpotInstanceTypes) > 0 {
errs = append(errs, fmt.Errorf("either instance_type or "+
"spot_instance_types must be specified, not both"))
}
if c.BlockDurationMinutes%60 != 0 {

@ -14,10 +14,6 @@ import (
"github.com/hashicorp/packer/packer"
)
func boolPointer(tf bool) *bool {
return &tf
}
// Define a mock struct to be used in unit tests for common aws steps.
type mockEC2Conn struct {
ec2iface.EC2API
@ -120,7 +116,7 @@ func TestStepAmiRegionCopy_false_encryption(t *testing.T) {
Regions: make([]string, 0),
AMIKmsKeyId: "",
RegionKeyIds: make(map[string]string),
EncryptBootVolume: boolPointer(false),
EncryptBootVolume: aws.Bool(false),
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
}
@ -145,7 +141,7 @@ func TestStepAmiRegionCopy_true_encryption(t *testing.T) {
Regions: make([]string, 0),
AMIKmsKeyId: "",
RegionKeyIds: make(map[string]string),
EncryptBootVolume: boolPointer(true),
EncryptBootVolume: aws.Bool(true),
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
}

@ -13,6 +13,7 @@ import (
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/hashicorp/packer/common/retry"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
@ -35,6 +36,7 @@ type StepRunSpotInstance struct {
SpotPrice string
SpotPriceProduct string
SpotTags TagMap
SpotInstanceTypes []string
Tags TagMap
VolumeTags TagMap
UserData string
@ -45,56 +47,11 @@ type StepRunSpotInstance struct {
spotRequest *ec2.SpotInstanceRequest
}
func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
securityGroupIds := aws.StringSlice(state.Get("securityGroupIds").([]string))
ui := state.Get("ui").(packer.Ui)
userData := s.UserData
if s.UserDataFile != "" {
contents, err := ioutil.ReadFile(s.UserDataFile)
if err != nil {
state.Put("error", fmt.Errorf("Problem reading user data file: %s", err))
return multistep.ActionHalt
}
userData = string(contents)
}
// Test if it is encoded already, and if not, encode it
if _, err := base64.StdEncoding.DecodeString(userData); err != nil {
log.Printf("[DEBUG] base64 encoding user data...")
userData = base64.StdEncoding.EncodeToString([]byte(userData))
}
ui.Say("Launching a source AWS instance...")
image, ok := state.Get("source_image").(*ec2.Image)
if !ok {
state.Put("error", fmt.Errorf("source_image type assertion failed"))
return multistep.ActionHalt
}
s.SourceAMI = *image.ImageId
if s.ExpectedRootDevice != "" && *image.RootDeviceType != s.ExpectedRootDevice {
state.Put("error", fmt.Errorf(
"The provided source AMI has an invalid root device type.\n"+
"Expected '%s', got '%s'.",
s.ExpectedRootDevice, *image.RootDeviceType))
return multistep.ActionHalt
}
func (s *StepRunSpotInstance) CalculateSpotPrice(az string, ec2conn ec2iface.EC2API) (string, error) {
// Calculate the spot price for a given availability zone
spotPrice := s.SpotPrice
azConfig := ""
if azRaw, ok := state.GetOk("availability_zone"); ok {
azConfig = azRaw.(string)
}
az := azConfig
if spotPrice == "auto" {
ui.Message(fmt.Sprintf(
"Finding spot price for %s %s...",
s.SpotPriceProduct, s.InstanceType))
// Detect the spot price
startTime := time.Now().Add(-1 * time.Hour)
resp, err := ec2conn.DescribeSpotPriceHistory(&ec2.DescribeSpotPriceHistoryInput{
@ -104,10 +61,7 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
StartTime: &startTime,
})
if err != nil {
err := fmt.Errorf("Error finding spot price: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
return "", fmt.Errorf("Error finding spot price: %s", err)
}
var price float64
@ -120,16 +74,13 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
}
if price == 0 || current < price {
price = current
if azConfig == "" {
if az == "" {
az = *history.AvailabilityZone
}
}
}
if price == 0 {
err := fmt.Errorf("No candidate spot prices found!")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
return "", fmt.Errorf("No candidate spot prices found!")
} else {
// Add 0.5 cents to minimum spot bid to ensure capacity will be available
// Avoids price-too-low error in active markets which can fluctuate
@ -139,101 +90,232 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
spotPrice = strconv.FormatFloat(price, 'f', -1, 64)
}
var instanceId string
s.SpotPrice = spotPrice
ui.Say("Adding tags to source instance")
if _, exists := s.Tags["Name"]; !exists {
s.Tags["Name"] = "Packer Builder"
}
return spotPrice, nil
ec2Tags, err := s.Tags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
if err != nil {
err := fmt.Errorf("Error tagging source instance: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ec2Tags.Report(ui)
}
ui.Message(fmt.Sprintf(
"Requesting spot instance '%s' for: %s",
s.InstanceType, spotPrice))
func (s *StepRunSpotInstance) CreateTemplateData(userData *string, az string,
state multistep.StateBag, marketOptions *ec2.LaunchTemplateInstanceMarketOptionsRequest) *ec2.RequestLaunchTemplateData {
// Convert the BlockDeviceMapping into a
// LaunchTemplateBlockDeviceMappingRequest. These structs are identical,
// except for the EBS field -- on one, that field contains a
// LaunchTemplateEbsBlockDeviceRequest, and on the other, it contains an
// EbsBlockDevice. The EbsBlockDevice and
// LaunchTemplateEbsBlockDeviceRequest structs are themselves
// identical except for the struct's name, so you can cast one directly
// into the other.
blockDeviceMappings := s.BlockDevices.BuildLaunchDevices()
var launchMappingRequests []*ec2.LaunchTemplateBlockDeviceMappingRequest
for _, mapping := range blockDeviceMappings {
launchRequest := &ec2.LaunchTemplateBlockDeviceMappingRequest{
DeviceName: mapping.DeviceName,
Ebs: (*ec2.LaunchTemplateEbsBlockDeviceRequest)(mapping.Ebs),
NoDevice: mapping.NoDevice,
VirtualName: mapping.VirtualName,
}
launchMappingRequests = append(launchMappingRequests, launchRequest)
}
runOpts := &ec2.RequestSpotLaunchSpecification{
ImageId: &s.SourceAMI,
InstanceType: &s.InstanceType,
UserData: &userData,
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile},
Placement: &ec2.SpotPlacement{
// Create a launch template.
templateData := ec2.RequestLaunchTemplateData{
BlockDeviceMappings: launchMappingRequests,
DisableApiTermination: aws.Bool(false),
EbsOptimized: &s.EbsOptimized,
IamInstanceProfile: &ec2.LaunchTemplateIamInstanceProfileSpecificationRequest{Name: &s.IamInstanceProfile},
ImageId: &s.SourceAMI,
InstanceMarketOptions: marketOptions,
Placement: &ec2.LaunchTemplatePlacementRequest{
AvailabilityZone: &az,
},
BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(),
EbsOptimized: &s.EbsOptimized,
UserData: userData,
}
// Create a network interface
securityGroupIds := aws.StringSlice(state.Get("securityGroupIds").([]string))
subnetId := state.Get("subnet_id").(string)
if subnetId != "" && s.AssociatePublicIpAddress {
runOpts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{
{
DeviceIndex: aws.Int64(0),
AssociatePublicIpAddress: &s.AssociatePublicIpAddress,
SubnetId: &subnetId,
Groups: securityGroupIds,
DeleteOnTermination: aws.Bool(true),
},
if subnetId != "" {
// Set up a full network interface
networkInterface := ec2.LaunchTemplateInstanceNetworkInterfaceSpecificationRequest{
Groups: securityGroupIds,
DeleteOnTermination: aws.Bool(true),
DeviceIndex: aws.Int64(0),
SubnetId: aws.String(subnetId),
}
if s.AssociatePublicIpAddress {
networkInterface.SetAssociatePublicIpAddress(s.AssociatePublicIpAddress)
}
templateData.SetNetworkInterfaces([]*ec2.LaunchTemplateInstanceNetworkInterfaceSpecificationRequest{&networkInterface})
} else {
runOpts.SubnetId = &subnetId
runOpts.SecurityGroupIds = securityGroupIds
templateData.SetSecurityGroupIds(securityGroupIds)
}
// If instance type is not set, we'll just pick the lowest priced instance
// available.
if s.InstanceType != "" {
templateData.SetInstanceType(s.InstanceType)
}
if s.Comm.SSHKeyPairName != "" {
runOpts.KeyName = &s.Comm.SSHKeyPairName
templateData.SetKeyName(s.Comm.SSHKeyPairName)
}
spotInstanceInput := &ec2.RequestSpotInstancesInput{
LaunchSpecification: runOpts,
SpotPrice: &spotPrice,
return &templateData
}
func (s *StepRunSpotInstance) LoadUserData() (string, error) {
userData := s.UserData
if s.UserDataFile != "" {
contents, err := ioutil.ReadFile(s.UserDataFile)
if err != nil {
return "", fmt.Errorf("Problem reading user data file: %s", err)
}
userData = string(contents)
}
if s.BlockDurationMinutes != 0 {
spotInstanceInput.BlockDurationMinutes = &s.BlockDurationMinutes
// Test if it is encoded already, and if not, encode it
if _, err := base64.StdEncoding.DecodeString(userData); err != nil {
log.Printf("[DEBUG] base64 encoding user data...")
userData = base64.StdEncoding.EncodeToString([]byte(userData))
}
return userData, nil
}
func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ui.Say("Launching a spot AWS instance...")
runSpotResp, err := ec2conn.RequestSpotInstances(spotInstanceInput)
// Get and validate the source AMI
image, ok := state.Get("source_image").(*ec2.Image)
if !ok {
state.Put("error", fmt.Errorf("source_image type assertion failed"))
return multistep.ActionHalt
}
s.SourceAMI = *image.ImageId
if s.ExpectedRootDevice != "" && *image.RootDeviceType != s.ExpectedRootDevice {
state.Put("error", fmt.Errorf(
"The provided source AMI has an invalid root device type.\n"+
"Expected '%s', got '%s'.",
s.ExpectedRootDevice, *image.RootDeviceType))
return multistep.ActionHalt
}
azConfig := ""
if azRaw, ok := state.GetOk("availability_zone"); ok {
azConfig = azRaw.(string)
}
az := azConfig
ui.Message(fmt.Sprintf("Finding spot price for %s %s...",
s.SpotPriceProduct, s.InstanceType))
spotPrice, err := s.CalculateSpotPrice(az, ec2conn)
if err != nil {
err := fmt.Errorf("Error launching source spot instance: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.spotRequest = runSpotResp.SpotInstanceRequests[0]
ui.Message(fmt.Sprintf("Determined spot instance price of: %s.", spotPrice))
var instanceId string
ui.Say("Interpolating tags for spot instance...")
// s.Tags will tag the eventually launched instance
// s.SpotTags apply to the spot request itself, and do not automatically
// get applied to the spot instance that is launched once the request is
// fulfilled
if _, exists := s.Tags["Name"]; !exists {
s.Tags["Name"] = "Packer Builder"
}
spotRequestId := s.spotRequest.SpotInstanceRequestId
ui.Message(fmt.Sprintf("Waiting for spot request (%s) to become active...", *spotRequestId))
err = WaitUntilSpotRequestFulfilled(ctx, ec2conn, *spotRequestId)
// Convert tags from the tag map provided by the user into *ec2.Tag s
ec2Tags, err := s.Tags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
if err != nil {
err := fmt.Errorf("Error waiting for spot request (%s) to become ready: %s", *spotRequestId, err)
err := fmt.Errorf("Error generating tags for source instance: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// This prints the tags to the ui; it doesn't actually add them to the
// instance yet
ec2Tags.Report(ui)
spotResp, err := ec2conn.DescribeSpotInstanceRequests(&ec2.DescribeSpotInstanceRequestsInput{
SpotInstanceRequestIds: []*string{spotRequestId},
})
spotOptions := ec2.LaunchTemplateSpotMarketOptionsRequest{
MaxPrice: &s.SpotPrice,
}
if s.BlockDurationMinutes != 0 {
spotOptions.BlockDurationMinutes = &s.BlockDurationMinutes
}
marketOptions := &ec2.LaunchTemplateInstanceMarketOptionsRequest{
SpotOptions: &spotOptions,
}
marketOptions.SetMarketType(ec2.MarketTypeSpot)
// Create a launch template for the instance
ui.Message("Loading User Data File...")
userData, err := s.LoadUserData()
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
ui.Message("Creating Spot Fleet launch template...")
templateData := s.CreateTemplateData(&userData, az, state, marketOptions)
launchTemplate := &ec2.CreateLaunchTemplateInput{
LaunchTemplateData: templateData,
LaunchTemplateName: aws.String("packer-fleet-launch-template"),
VersionDescription: aws.String("template generated by packer for launching spot instances"),
}
// Tell EC2 to create the template
_, err = ec2conn.CreateLaunchTemplate(launchTemplate)
if err != nil {
err := fmt.Errorf("Error finding spot request (%s): %s", *spotRequestId, err)
err := fmt.Errorf("Error creating launch template for spot instance: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
instanceId = *spotResp.SpotInstanceRequests[0].InstanceId
// Tag spot instance request
// Add overrides for each user-provided instance type
var overrides []*ec2.FleetLaunchTemplateOverridesRequest
for _, instanceType := range s.SpotInstanceTypes {
override := ec2.FleetLaunchTemplateOverridesRequest{
InstanceType: aws.String(instanceType),
}
overrides = append(overrides, &override)
}
createFleetInput := &ec2.CreateFleetInput{
LaunchTemplateConfigs: []*ec2.FleetLaunchTemplateConfigRequest{
{
LaunchTemplateSpecification: &ec2.FleetLaunchTemplateSpecificationRequest{
LaunchTemplateName: aws.String("packer-fleet-launch-template"),
Version: aws.String("1"),
},
Overrides: overrides,
},
},
ReplaceUnhealthyInstances: aws.Bool(false),
TargetCapacitySpecification: &ec2.TargetCapacitySpecificationRequest{
TotalTargetCapacity: aws.Int64(1),
DefaultTargetCapacityType: aws.String("spot"),
},
Type: aws.String("instant"),
}
// Create the request for the spot instance.
req, createOutput := ec2conn.CreateFleetRequest(createFleetInput)
ui.Message(fmt.Sprintf("Sending spot request (%s)...", req.RequestID))
// Tag the spot instance request (not the eventual spot instance)
spotTags, err := s.SpotTags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
if err != nil {
err := fmt.Errorf("Error tagging spot request: %s", err)
err := fmt.Errorf("Error generating tags for spot request: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
@ -248,7 +330,7 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
}.Run(ctx, func(ctx context.Context) error {
_, err := ec2conn.CreateTags(&ec2.CreateTagsInput{
Tags: spotTags,
Resources: []*string{spotRequestId},
Resources: []*string{aws.String(req.RequestID)},
})
return err
})
@ -260,21 +342,29 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
}
}
// Set the instance ID so that the cleanup works properly
s.instanceId = instanceId
ui.Message(fmt.Sprintf("Instance ID: %s", instanceId))
ui.Say(fmt.Sprintf("Waiting for instance (%v) to become ready...", instanceId))
describeInstance := &ec2.DescribeInstancesInput{
InstanceIds: []*string{aws.String(instanceId)},
// Actually send the spot connection request.
err = req.Send()
if err != nil {
err := fmt.Errorf("Error waiting for spot request (%s) to become ready: %s", req.RequestID, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if err := ec2conn.WaitUntilInstanceRunningWithContext(ctx, describeInstance); err != nil {
err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", instanceId, err)
if len(createOutput.Errors) > 0 {
err := fmt.Errorf("error sending spot request: %s", *createOutput.Errors[0])
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
instanceId = *createOutput.Instances[0].InstanceIds[0]
// Set the instance ID so that the cleanup works properly
s.instanceId = instanceId
ui.Message(fmt.Sprintf("Instance ID: %s", instanceId))
r, err := ec2conn.DescribeInstances(&ec2.DescribeInstancesInput{
InstanceIds: []*string{aws.String(instanceId)},
})
@ -401,4 +491,12 @@ func (s *StepRunSpotInstance) Cleanup(state multistep.StateBag) {
ui.Error(err.Error())
}
}
// Delete the launch template used to create the spot fleet
deleteInput := &ec2.DeleteLaunchTemplateInput{
LaunchTemplateName: aws.String("packer-fleet-launch-template"),
}
if _, err := ec2conn.DeleteLaunchTemplate(deleteInput); err != nil {
ui.Error(err.Error())
}
}

@ -0,0 +1,131 @@
package common
import (
"bytes"
"strconv"
"testing"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// Define a mock struct to be used in unit tests for common aws steps.
type mockEC2ConnSpot struct {
ec2iface.EC2API
Config *aws.Config
// Counters to figure out what code path was taken
describeSpotPriceHistoryCount int
}
// Generates fake SpotPriceHistory data and returns it in the expected output
// format. Also increments a
func (m *mockEC2ConnSpot) DescribeSpotPriceHistory(copyInput *ec2.DescribeSpotPriceHistoryInput) (*ec2.DescribeSpotPriceHistoryOutput, error) {
m.describeSpotPriceHistoryCount++
testTime := time.Now().Add(-1 * time.Hour)
sp := []*ec2.SpotPrice{
{
AvailabilityZone: aws.String("us-east-1c"),
InstanceType: aws.String("t2.micro"),
ProductDescription: aws.String("Linux/UNIX"),
SpotPrice: aws.String("0.003500"),
Timestamp: &testTime,
},
{
AvailabilityZone: aws.String("us-east-1f"),
InstanceType: aws.String("t2.micro"),
ProductDescription: aws.String("Linux/UNIX"),
SpotPrice: aws.String("0.003500"),
Timestamp: &testTime,
},
{
AvailabilityZone: aws.String("us-east-1b"),
InstanceType: aws.String("t2.micro"),
ProductDescription: aws.String("Linux/UNIX"),
SpotPrice: aws.String("0.003500"),
Timestamp: &testTime,
},
}
output := &ec2.DescribeSpotPriceHistoryOutput{SpotPriceHistory: sp}
return output, nil
}
func getMockConnSpot() ec2iface.EC2API {
mockConn := &mockEC2ConnSpot{
Config: aws.NewConfig(),
}
return mockConn
}
// Create statebag for running test
func tStateSpot() multistep.StateBag {
state := new(multistep.BasicStateBag)
state.Put("ui", &packer.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
})
state.Put("availability_zone", "us-east-1c")
state.Put("securityGroupIds", []string{"sg-0b8984db72f213dc3"})
state.Put("subnet_id", "subnet-077fde4e")
state.Put("source_image", "")
return state
}
func getBasicStep() *StepRunSpotInstance {
stepRunSpotInstance := StepRunSpotInstance{
AssociatePublicIpAddress: false,
BlockDevices: BlockDevices{
AMIBlockDevices: AMIBlockDevices{
AMIMappings: []BlockDevice(nil),
},
LaunchBlockDevices: LaunchBlockDevices{
LaunchMappings: []BlockDevice(nil),
},
},
BlockDurationMinutes: 0,
Debug: false,
Comm: &communicator.Config{
SSHKeyPairName: "foo",
},
EbsOptimized: false,
ExpectedRootDevice: "ebs",
IamInstanceProfile: "",
InstanceInitiatedShutdownBehavior: "stop",
InstanceType: "t2.micro",
SourceAMI: "",
SpotPrice: "auto",
SpotPriceProduct: "Linux/UNIX",
SpotTags: TagMap(nil),
Tags: TagMap{},
VolumeTags: TagMap(nil),
UserData: "",
UserDataFile: "",
}
return &stepRunSpotInstance
}
func TestCalculateSpotPrice(t *testing.T) {
stepRunSpotInstance := getBasicStep()
// Set spot price and spot price product
stepRunSpotInstance.SpotPrice = "auto"
stepRunSpotInstance.SpotPriceProduct = "Linux/UNIX"
ec2conn := getMockConnSpot()
// state := tStateSpot()
spotPrice, err := stepRunSpotInstance.CalculateSpotPrice("", ec2conn)
if err != nil {
t.Fatalf("Should not have had an error calculating spot price")
}
sp, _ := strconv.ParseFloat(spotPrice, 64)
expected := 0.008500
if sp != expected { // 0.003500 (from spot history) + .005
t.Fatalf("Expected spot price of \"0.008500\", not %s", spotPrice)
}
}

@ -124,6 +124,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
SpotPriceProduct: b.config.SpotPriceAutoProduct,
SpotTags: b.config.SpotTags,
Tags: b.config.RunTags,
SpotInstanceTypes: b.config.SpotInstanceTypes,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
VolumeTags: b.config.VolumeRunTags,

@ -152,6 +152,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
SourceAMI: b.config.SourceAmi,
SpotPrice: b.config.SpotPrice,
SpotPriceProduct: b.config.SpotPriceAutoProduct,
SpotInstanceTypes: b.config.SpotInstanceTypes,
SpotTags: b.config.SpotTags,
Tags: b.config.RunTags,
UserData: b.config.UserData,

@ -122,6 +122,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
SourceAMI: b.config.SourceAmi,
SpotPrice: b.config.SpotPrice,
SpotPriceProduct: b.config.SpotPriceAutoProduct,
SpotInstanceTypes: b.config.SpotInstanceTypes,
SpotTags: b.config.SpotTags,
Tags: b.config.RunTags,
UserData: b.config.UserData,

@ -202,6 +202,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
InstanceType: b.config.InstanceType,
SourceAMI: b.config.SourceAmi,
SpotPrice: b.config.SpotPrice,
SpotInstanceTypes: b.config.SpotInstanceTypes,
SpotPriceProduct: b.config.SpotPriceAutoProduct,
Tags: b.config.RunTags,
SpotTags: b.config.SpotTags,

@ -112,8 +112,8 @@ builder.
of `source_ami`. Can be `paravirtual` or `hvm`.
- `associate_public_ip_address` (boolean) - If using a non-default VPC,
public IP addresses are not provided by default. If this is toggled, your
new instance will get a Public IP.
public IP addresses are not provided by default. If this is `true`, your
new instance will get a Public IP. default: `false`
- `availability_zone` (string) - Destination availability zone to launch
instance in. Leave this empty to allow Amazon to auto-assign.
@ -352,22 +352,7 @@ builder.
criteria provided in `source_ami_filter`; this pins the AMI returned by the
filter, but will cause Packer to fail if the `source_ami` does not exist.
- `spot_price` (string) - The maximum hourly price to pay for a spot instance
to create the AMI. Spot instances are a type of instance that EC2 starts
when the current spot price is less than the maximum price you specify.
Spot price will be updated based on available spot instance capacity and
current spot instance requests. It may save you some costs. You can set
this to `auto` for Packer to automatically discover the best spot price or
to "0" to use an on demand instance (default).
- `spot_price_auto_product` (string) - Required if `spot_price` is set to
`auto`. This tells Packer what sort of AMI you're launching to find the
best spot price. This must be one of: `Linux/UNIX`, `SUSE Linux`,
`Windows`, `Linux/UNIX (Amazon VPC)`, `SUSE Linux (Amazon VPC)`,
`Windows (Amazon VPC)`
- `spot_tags` (object of key/value strings) - Requires `spot_price` to be
set. This tells Packer to apply tags to the spot request that is issued.
<%= partial "partials/builders/aws-spot-docs" %>
- `sriov_support` (boolean) - Enable enhanced networking (SriovNetSupport but
not ENA) on HVM-compatible AMIs. If true, add `ec2:ModifyInstanceAttribute`

@ -104,8 +104,8 @@ builder.
of `source_ami`. Can be `paravirtual` or `hvm`.
- `associate_public_ip_address` (boolean) - If using a non-default VPC,
public IP addresses are not provided by default. If this is toggled, your
new instance will get a Public IP.
public IP addresses are not provided by default. If this is `true`, your
new instance will get a Public IP. default: `false`
- `availability_zone` (string) - Destination availability zone to launch
instance in. Leave this empty to allow Amazon to auto-assign.
@ -353,22 +353,7 @@ builder.
criteria provided in `source_ami_filter`; this pins the AMI returned by the
filter, but will cause Packer to fail if the `source_ami` does not exist.
- `spot_price` (string) - The maximum hourly price to pay for a spot instance
to create the AMI. Spot instances are a type of instance that EC2 starts
when the current spot price is less than the maximum price you specify.
Spot price will be updated based on available spot instance capacity and
current spot instance requests. It may save you some costs. You can set
this to `auto` for Packer to automatically discover the best spot price or
to "0" to use an on demand instance (default).
- `spot_price_auto_product` (string) - Required if `spot_price` is set to
`auto`. This tells Packer what sort of AMI you're launching to find the
best spot price. This must be one of: `Linux/UNIX`, `SUSE Linux`,
`Windows`, `Linux/UNIX (Amazon VPC)`, `SUSE Linux (Amazon VPC)`,
`Windows (Amazon VPC)`
- `spot_tags` (object of key/value strings) - Requires `spot_price` to be
set. This tells Packer to apply tags to the spot request that is issued.
<%= partial "partials/builders/aws-spot-docs" %>
- `sriov_support` (boolean) - Enable enhanced networking (SriovNetSupport but
not ENA) on HVM-compatible AMIs. If true, add `ec2:ModifyInstanceAttribute`

@ -107,8 +107,8 @@ builder.
data](#build-template-data) for more information.
- `associate_public_ip_address` (boolean) - If using a non-default VPC,
public IP addresses are not provided by default. If this is toggled, your
new instance will get a Public IP.
public IP addresses are not provided by default. If this is `true`, your
new instance will get a Public IP. default: `false`
- `availability_zone` (string) - Destination availability zone to launch
instance in. Leave this empty to allow Amazon to auto-assign.
@ -302,22 +302,7 @@ builder.
criteria provided in `source_ami_filter`; this pins the AMI returned by the
filter, but will cause Packer to fail if the `source_ami` does not exist.
- `spot_price` (string) - The maximum hourly price to pay for a spot instance
to create the AMI. Spot instances are a type of instance that EC2 starts
when the current spot price is less than the maximum price you specify.
Spot price will be updated based on available spot instance capacity and
current spot instance requests. It may save you some costs. You can set
this to `auto` for Packer to automatically discover the best spot price or
to `0` to use an on-demand instance (default).
- `spot_price_auto_product` (string) - Required if `spot_price` is set to
`auto`. This tells Packer what sort of AMI you're launching to find the
best spot price. This must be one of: `Linux/UNIX`, `SUSE Linux`,
`Windows`, `Linux/UNIX (Amazon VPC)`, `SUSE Linux (Amazon VPC)` or
`Windows (Amazon VPC)`
- `spot_tags` (object of key/value strings) - Requires `spot_price` to be
set. This tells Packer to apply tags to the spot request that is issued.
<%= partial "partials/builders/aws-spot-docs" %>
- `sriov_support` (boolean) - Enable enhanced networking (SriovNetSupport but
not ENA) on HVM-compatible AMIs. If true, add `ec2:ModifyInstanceAttribute`

@ -131,8 +131,8 @@ builder.
`paravirtual` (default) or `hvm`.
- `associate_public_ip_address` (boolean) - If using a non-default VPC,
public IP addresses are not provided by default. If this is toggled, your
new instance will get a Public IP.
public IP addresses are not provided by default. If this is `true`, your
new instance will get a Public IP. default: `false`
- `availability_zone` (string) - Destination availability zone to launch
instance in. Leave this empty to allow Amazon to auto-assign.
@ -343,22 +343,7 @@ builder.
- `snapshot_tags` (object of key/value strings) - Tags to apply to snapshot.
They will override AMI tags if already applied to snapshot.
- `spot_price` (string) - The maximum hourly price to launch a spot instance
to create the AMI. It is a type of instances that EC2 starts when the
maximum price that you specify exceeds the current spot price. Spot price
will be updated based on available spot instance capacity and current spot
Instance requests. It may save you some costs. You can set this to `auto`
for Packer to automatically discover the best spot price or to `0` to use
an on-demand instance (default).
- `spot_price_auto_product` (string) - Required if `spot_price` is set to
`auto`. This tells Packer what sort of AMI you're launching to find the
best spot price. This must be one of: `Linux/UNIX`, `SUSE Linux`,
`Windows`, `Linux/UNIX (Amazon VPC)`, `SUSE Linux (Amazon VPC)`,
`Windows (Amazon VPC)`
- `spot_tags` (object of key/value strings) - Requires `spot_price` to be
set. This tells Packer to apply tags to the spot request that is issued.
<%= partial "partials/builders/aws-spot-docs" %>
- `sriov_support` (boolean) - Enable enhanced networking (SriovNetSupport but
not ENA) on HVM-compatible AMIs. If true, add `ec2:ModifyInstanceAttribute`

@ -0,0 +1,26 @@
- `spot_instance_types` (array of strings) - a list of acceptable instance
types to run your build on. We will request a spot instance using the max
price of `spot_price` and the allocation strategy of "lowest price".
Your instance will be launched on an instance type of the lowest available
price that you have in your list. This is used in place of instance_type.
You may only set either spot_instance_types or instance_type, not both.
This feature exists to help prevent situations where a Packer build fails
because a particular availability zone does not have capacity for the
specific instance_type requested in instance_type.
- `spot_price` (string) - The maximum hourly price to pay for a spot instance
to create the AMI. Spot instances are a type of instance that EC2 starts
when the current spot price is less than the maximum price you specify.
Spot price will be updated based on available spot instance capacity and
current spot instance requests. It may save you some costs. You can set
this to `auto` for Packer to automatically discover the best spot price or
to "0" to use an on demand instance (default).
- `spot_price_auto_product` (string) - Required if `spot_price` is set to
`auto`. This tells Packer what sort of AMI you're launching to find the
best spot price. This must be one of: `Linux/UNIX`, `SUSE Linux`,
`Windows`, `Linux/UNIX (Amazon VPC)`, `SUSE Linux (Amazon VPC)`,
`Windows (Amazon VPC)`
- `spot_tags` (object of key/value strings) - Requires `spot_price` to be
set. This tells Packer to apply tags to the spot request that is issued.
Loading…
Cancel
Save