commit
65bd7207e8
|
@ -73,6 +73,7 @@ type RunConfig struct {
|
||||||
SecurityGroupIds []string `mapstructure:"security_group_ids"`
|
SecurityGroupIds []string `mapstructure:"security_group_ids"`
|
||||||
SourceAmi string `mapstructure:"source_ami"`
|
SourceAmi string `mapstructure:"source_ami"`
|
||||||
SourceAmiFilter AmiFilterOptions `mapstructure:"source_ami_filter"`
|
SourceAmiFilter AmiFilterOptions `mapstructure:"source_ami_filter"`
|
||||||
|
SpotInstanceTypes []string `mapstructure:"spot_instance_types"`
|
||||||
SpotPrice string `mapstructure:"spot_price"`
|
SpotPrice string `mapstructure:"spot_price"`
|
||||||
SpotPriceAutoProduct string `mapstructure:"spot_price_auto_product"`
|
SpotPriceAutoProduct string `mapstructure:"spot_price_auto_product"`
|
||||||
SpotTags map[string]string `mapstructure:"spot_tags"`
|
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."))
|
errs = append(errs, fmt.Errorf("For security reasons, your source AMI filter must declare an owner."))
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.InstanceType == "" {
|
if c.InstanceType == "" && len(c.SpotInstanceTypes) == 0 {
|
||||||
errs = append(errs, fmt.Errorf("An instance_type must be specified"))
|
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 {
|
if c.BlockDurationMinutes%60 != 0 {
|
||||||
|
|
|
@ -14,10 +14,6 @@ import (
|
||||||
"github.com/hashicorp/packer/packer"
|
"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.
|
// Define a mock struct to be used in unit tests for common aws steps.
|
||||||
type mockEC2Conn struct {
|
type mockEC2Conn struct {
|
||||||
ec2iface.EC2API
|
ec2iface.EC2API
|
||||||
|
@ -120,7 +116,7 @@ func TestStepAmiRegionCopy_false_encryption(t *testing.T) {
|
||||||
Regions: make([]string, 0),
|
Regions: make([]string, 0),
|
||||||
AMIKmsKeyId: "",
|
AMIKmsKeyId: "",
|
||||||
RegionKeyIds: make(map[string]string),
|
RegionKeyIds: make(map[string]string),
|
||||||
EncryptBootVolume: boolPointer(false),
|
EncryptBootVolume: aws.Bool(false),
|
||||||
Name: "fake-ami-name",
|
Name: "fake-ami-name",
|
||||||
OriginalRegion: "us-east-1",
|
OriginalRegion: "us-east-1",
|
||||||
}
|
}
|
||||||
|
@ -145,7 +141,7 @@ func TestStepAmiRegionCopy_true_encryption(t *testing.T) {
|
||||||
Regions: make([]string, 0),
|
Regions: make([]string, 0),
|
||||||
AMIKmsKeyId: "",
|
AMIKmsKeyId: "",
|
||||||
RegionKeyIds: make(map[string]string),
|
RegionKeyIds: make(map[string]string),
|
||||||
EncryptBootVolume: boolPointer(true),
|
EncryptBootVolume: aws.Bool(true),
|
||||||
Name: "fake-ami-name",
|
Name: "fake-ami-name",
|
||||||
OriginalRegion: "us-east-1",
|
OriginalRegion: "us-east-1",
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"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/common/retry"
|
||||||
"github.com/hashicorp/packer/helper/communicator"
|
"github.com/hashicorp/packer/helper/communicator"
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
@ -35,6 +36,7 @@ type StepRunSpotInstance struct {
|
||||||
SpotPrice string
|
SpotPrice string
|
||||||
SpotPriceProduct string
|
SpotPriceProduct string
|
||||||
SpotTags TagMap
|
SpotTags TagMap
|
||||||
|
SpotInstanceTypes []string
|
||||||
Tags TagMap
|
Tags TagMap
|
||||||
VolumeTags TagMap
|
VolumeTags TagMap
|
||||||
UserData string
|
UserData string
|
||||||
|
@ -45,17 +47,130 @@ type StepRunSpotInstance struct {
|
||||||
spotRequest *ec2.SpotInstanceRequest
|
spotRequest *ec2.SpotInstanceRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepRunSpotInstance) CalculateSpotPrice(az string, ec2conn ec2iface.EC2API) (string, error) {
|
||||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
// Calculate the spot price for a given availability zone
|
||||||
securityGroupIds := aws.StringSlice(state.Get("securityGroupIds").([]string))
|
spotPrice := s.SpotPrice
|
||||||
ui := state.Get("ui").(packer.Ui)
|
|
||||||
|
|
||||||
|
if spotPrice == "auto" {
|
||||||
|
// Detect the spot price
|
||||||
|
startTime := time.Now().Add(-1 * time.Hour)
|
||||||
|
resp, err := ec2conn.DescribeSpotPriceHistory(&ec2.DescribeSpotPriceHistoryInput{
|
||||||
|
InstanceTypes: []*string{&s.InstanceType},
|
||||||
|
ProductDescriptions: []*string{&s.SpotPriceProduct},
|
||||||
|
AvailabilityZone: &az,
|
||||||
|
StartTime: &startTime,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Error finding spot price: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var price float64
|
||||||
|
for _, history := range resp.SpotPriceHistory {
|
||||||
|
log.Printf("[INFO] Candidate spot price: %s", *history.SpotPrice)
|
||||||
|
current, err := strconv.ParseFloat(*history.SpotPrice, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERR] Error parsing spot price: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if price == 0 || current < price {
|
||||||
|
price = current
|
||||||
|
if az == "" {
|
||||||
|
az = *history.AvailabilityZone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if price == 0 {
|
||||||
|
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
|
||||||
|
price = price + 0.005
|
||||||
|
}
|
||||||
|
|
||||||
|
spotPrice = strconv.FormatFloat(price, 'f', -1, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.SpotPrice = spotPrice
|
||||||
|
|
||||||
|
return spotPrice, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
},
|
||||||
|
UserData: userData,
|
||||||
|
}
|
||||||
|
// Create a network interface
|
||||||
|
securityGroupIds := aws.StringSlice(state.Get("securityGroupIds").([]string))
|
||||||
|
subnetId := state.Get("subnet_id").(string)
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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 != "" {
|
||||||
|
templateData.SetKeyName(s.Comm.SSHKeyPairName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &templateData
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepRunSpotInstance) LoadUserData() (string, error) {
|
||||||
userData := s.UserData
|
userData := s.UserData
|
||||||
if s.UserDataFile != "" {
|
if s.UserDataFile != "" {
|
||||||
contents, err := ioutil.ReadFile(s.UserDataFile)
|
contents, err := ioutil.ReadFile(s.UserDataFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
state.Put("error", fmt.Errorf("Problem reading user data file: %s", err))
|
return "", fmt.Errorf("Problem reading user data file: %s", err)
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
userData = string(contents)
|
userData = string(contents)
|
||||||
|
@ -66,8 +181,16 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||||
log.Printf("[DEBUG] base64 encoding user data...")
|
log.Printf("[DEBUG] base64 encoding user data...")
|
||||||
userData = base64.StdEncoding.EncodeToString([]byte(userData))
|
userData = base64.StdEncoding.EncodeToString([]byte(userData))
|
||||||
}
|
}
|
||||||
|
return userData, nil
|
||||||
|
}
|
||||||
|
|
||||||
ui.Say("Launching a source AWS instance...")
|
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...")
|
||||||
|
|
||||||
|
// Get and validate the source AMI
|
||||||
image, ok := state.Get("source_image").(*ec2.Image)
|
image, ok := state.Get("source_image").(*ec2.Image)
|
||||||
if !ok {
|
if !ok {
|
||||||
state.Put("error", fmt.Errorf("source_image type assertion failed"))
|
state.Put("error", fmt.Errorf("source_image type assertion failed"))
|
||||||
|
@ -83,157 +206,116 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
spotPrice := s.SpotPrice
|
|
||||||
azConfig := ""
|
azConfig := ""
|
||||||
if azRaw, ok := state.GetOk("availability_zone"); ok {
|
if azRaw, ok := state.GetOk("availability_zone"); ok {
|
||||||
azConfig = azRaw.(string)
|
azConfig = azRaw.(string)
|
||||||
}
|
}
|
||||||
az := azConfig
|
az := azConfig
|
||||||
|
|
||||||
if spotPrice == "auto" {
|
ui.Message(fmt.Sprintf("Finding spot price for %s %s...",
|
||||||
ui.Message(fmt.Sprintf(
|
s.SpotPriceProduct, s.InstanceType))
|
||||||
"Finding spot price for %s %s...",
|
spotPrice, err := s.CalculateSpotPrice(az, ec2conn)
|
||||||
s.SpotPriceProduct, s.InstanceType))
|
if err != nil {
|
||||||
|
state.Put("error", err)
|
||||||
// Detect the spot price
|
ui.Error(err.Error())
|
||||||
startTime := time.Now().Add(-1 * time.Hour)
|
return multistep.ActionHalt
|
||||||
resp, err := ec2conn.DescribeSpotPriceHistory(&ec2.DescribeSpotPriceHistoryInput{
|
|
||||||
InstanceTypes: []*string{&s.InstanceType},
|
|
||||||
ProductDescriptions: []*string{&s.SpotPriceProduct},
|
|
||||||
AvailabilityZone: &az,
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
var price float64
|
|
||||||
for _, history := range resp.SpotPriceHistory {
|
|
||||||
log.Printf("[INFO] Candidate spot price: %s", *history.SpotPrice)
|
|
||||||
current, err := strconv.ParseFloat(*history.SpotPrice, 64)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[ERR] Error parsing spot price: %s", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if price == 0 || current < price {
|
|
||||||
price = current
|
|
||||||
if azConfig == "" {
|
|
||||||
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
|
|
||||||
} 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
|
|
||||||
price = price + 0.005
|
|
||||||
}
|
|
||||||
|
|
||||||
spotPrice = strconv.FormatFloat(price, 'f', -1, 64)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ui.Message(fmt.Sprintf("Determined spot instance price of: %s.", spotPrice))
|
||||||
|
|
||||||
var instanceId string
|
var instanceId string
|
||||||
|
|
||||||
ui.Say("Adding tags to source instance")
|
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 {
|
if _, exists := s.Tags["Name"]; !exists {
|
||||||
s.Tags["Name"] = "Packer Builder"
|
s.Tags["Name"] = "Packer Builder"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
ec2Tags, err := s.Tags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error tagging source instance: %s", err)
|
err := fmt.Errorf("Error generating tags for source instance: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
// This prints the tags to the ui; it doesn't actually add them to the
|
||||||
|
// instance yet
|
||||||
ec2Tags.Report(ui)
|
ec2Tags.Report(ui)
|
||||||
|
|
||||||
ui.Message(fmt.Sprintf(
|
spotOptions := ec2.LaunchTemplateSpotMarketOptionsRequest{
|
||||||
"Requesting spot instance '%s' for: %s",
|
MaxPrice: &s.SpotPrice,
|
||||||
s.InstanceType, spotPrice))
|
|
||||||
|
|
||||||
runOpts := &ec2.RequestSpotLaunchSpecification{
|
|
||||||
ImageId: &s.SourceAMI,
|
|
||||||
InstanceType: &s.InstanceType,
|
|
||||||
UserData: &userData,
|
|
||||||
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile},
|
|
||||||
Placement: &ec2.SpotPlacement{
|
|
||||||
AvailabilityZone: &az,
|
|
||||||
},
|
|
||||||
BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(),
|
|
||||||
EbsOptimized: &s.EbsOptimized,
|
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
runOpts.SubnetId = &subnetId
|
|
||||||
runOpts.SecurityGroupIds = securityGroupIds
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Comm.SSHKeyPairName != "" {
|
|
||||||
runOpts.KeyName = &s.Comm.SSHKeyPairName
|
|
||||||
}
|
|
||||||
spotInstanceInput := &ec2.RequestSpotInstancesInput{
|
|
||||||
LaunchSpecification: runOpts,
|
|
||||||
SpotPrice: &spotPrice,
|
|
||||||
}
|
}
|
||||||
if s.BlockDurationMinutes != 0 {
|
if s.BlockDurationMinutes != 0 {
|
||||||
spotInstanceInput.BlockDurationMinutes = &s.BlockDurationMinutes
|
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"),
|
||||||
}
|
}
|
||||||
|
|
||||||
runSpotResp, err := ec2conn.RequestSpotInstances(spotInstanceInput)
|
// Tell EC2 to create the template
|
||||||
|
_, err = ec2conn.CreateLaunchTemplate(launchTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error launching source spot instance: %s", err)
|
err := fmt.Errorf("Error creating launch template for spot instance: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
s.spotRequest = runSpotResp.SpotInstanceRequests[0]
|
// Add overrides for each user-provided instance type
|
||||||
|
var overrides []*ec2.FleetLaunchTemplateOverridesRequest
|
||||||
spotRequestId := s.spotRequest.SpotInstanceRequestId
|
for _, instanceType := range s.SpotInstanceTypes {
|
||||||
ui.Message(fmt.Sprintf("Waiting for spot request (%s) to become active...", *spotRequestId))
|
override := ec2.FleetLaunchTemplateOverridesRequest{
|
||||||
err = WaitUntilSpotRequestFulfilled(ctx, ec2conn, *spotRequestId)
|
InstanceType: aws.String(instanceType),
|
||||||
if err != nil {
|
}
|
||||||
err := fmt.Errorf("Error waiting for spot request (%s) to become ready: %s", *spotRequestId, err)
|
overrides = append(overrides, &override)
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
spotResp, err := ec2conn.DescribeSpotInstanceRequests(&ec2.DescribeSpotInstanceRequestsInput{
|
createFleetInput := &ec2.CreateFleetInput{
|
||||||
SpotInstanceRequestIds: []*string{spotRequestId},
|
LaunchTemplateConfigs: []*ec2.FleetLaunchTemplateConfigRequest{
|
||||||
})
|
{
|
||||||
if err != nil {
|
LaunchTemplateSpecification: &ec2.FleetLaunchTemplateSpecificationRequest{
|
||||||
err := fmt.Errorf("Error finding spot request (%s): %s", *spotRequestId, err)
|
LaunchTemplateName: aws.String("packer-fleet-launch-template"),
|
||||||
state.Put("error", err)
|
Version: aws.String("1"),
|
||||||
ui.Error(err.Error())
|
},
|
||||||
return multistep.ActionHalt
|
Overrides: overrides,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReplaceUnhealthyInstances: aws.Bool(false),
|
||||||
|
TargetCapacitySpecification: &ec2.TargetCapacitySpecificationRequest{
|
||||||
|
TotalTargetCapacity: aws.Int64(1),
|
||||||
|
DefaultTargetCapacityType: aws.String("spot"),
|
||||||
|
},
|
||||||
|
Type: aws.String("instant"),
|
||||||
}
|
}
|
||||||
instanceId = *spotResp.SpotInstanceRequests[0].InstanceId
|
|
||||||
|
|
||||||
// Tag spot instance request
|
// 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)
|
spotTags, err := s.SpotTags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
|
||||||
if err != nil {
|
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)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
|
@ -248,7 +330,7 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||||
}.Run(ctx, func(ctx context.Context) error {
|
}.Run(ctx, func(ctx context.Context) error {
|
||||||
_, err := ec2conn.CreateTags(&ec2.CreateTagsInput{
|
_, err := ec2conn.CreateTags(&ec2.CreateTagsInput{
|
||||||
Tags: spotTags,
|
Tags: spotTags,
|
||||||
Resources: []*string{spotRequestId},
|
Resources: []*string{aws.String(req.RequestID)},
|
||||||
})
|
})
|
||||||
return err
|
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
|
// Actually send the spot connection request.
|
||||||
s.instanceId = instanceId
|
err = req.Send()
|
||||||
|
if err != nil {
|
||||||
ui.Message(fmt.Sprintf("Instance ID: %s", instanceId))
|
err := fmt.Errorf("Error waiting for spot request (%s) to become ready: %s", req.RequestID, err)
|
||||||
ui.Say(fmt.Sprintf("Waiting for instance (%v) to become ready...", instanceId))
|
|
||||||
describeInstance := &ec2.DescribeInstancesInput{
|
|
||||||
InstanceIds: []*string{aws.String(instanceId)},
|
|
||||||
}
|
|
||||||
if err := ec2conn.WaitUntilInstanceRunningWithContext(ctx, describeInstance); err != nil {
|
|
||||||
err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", instanceId, err)
|
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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{
|
r, err := ec2conn.DescribeInstances(&ec2.DescribeInstancesInput{
|
||||||
InstanceIds: []*string{aws.String(instanceId)},
|
InstanceIds: []*string{aws.String(instanceId)},
|
||||||
})
|
})
|
||||||
|
@ -401,4 +491,12 @@ func (s *StepRunSpotInstance) Cleanup(state multistep.StateBag) {
|
||||||
ui.Error(err.Error())
|
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,
|
SpotPriceProduct: b.config.SpotPriceAutoProduct,
|
||||||
SpotTags: b.config.SpotTags,
|
SpotTags: b.config.SpotTags,
|
||||||
Tags: b.config.RunTags,
|
Tags: b.config.RunTags,
|
||||||
|
SpotInstanceTypes: b.config.SpotInstanceTypes,
|
||||||
UserData: b.config.UserData,
|
UserData: b.config.UserData,
|
||||||
UserDataFile: b.config.UserDataFile,
|
UserDataFile: b.config.UserDataFile,
|
||||||
VolumeTags: b.config.VolumeRunTags,
|
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,
|
SourceAMI: b.config.SourceAmi,
|
||||||
SpotPrice: b.config.SpotPrice,
|
SpotPrice: b.config.SpotPrice,
|
||||||
SpotPriceProduct: b.config.SpotPriceAutoProduct,
|
SpotPriceProduct: b.config.SpotPriceAutoProduct,
|
||||||
|
SpotInstanceTypes: b.config.SpotInstanceTypes,
|
||||||
SpotTags: b.config.SpotTags,
|
SpotTags: b.config.SpotTags,
|
||||||
Tags: b.config.RunTags,
|
Tags: b.config.RunTags,
|
||||||
UserData: b.config.UserData,
|
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,
|
SourceAMI: b.config.SourceAmi,
|
||||||
SpotPrice: b.config.SpotPrice,
|
SpotPrice: b.config.SpotPrice,
|
||||||
SpotPriceProduct: b.config.SpotPriceAutoProduct,
|
SpotPriceProduct: b.config.SpotPriceAutoProduct,
|
||||||
|
SpotInstanceTypes: b.config.SpotInstanceTypes,
|
||||||
SpotTags: b.config.SpotTags,
|
SpotTags: b.config.SpotTags,
|
||||||
Tags: b.config.RunTags,
|
Tags: b.config.RunTags,
|
||||||
UserData: b.config.UserData,
|
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,
|
InstanceType: b.config.InstanceType,
|
||||||
SourceAMI: b.config.SourceAmi,
|
SourceAMI: b.config.SourceAmi,
|
||||||
SpotPrice: b.config.SpotPrice,
|
SpotPrice: b.config.SpotPrice,
|
||||||
|
SpotInstanceTypes: b.config.SpotInstanceTypes,
|
||||||
SpotPriceProduct: b.config.SpotPriceAutoProduct,
|
SpotPriceProduct: b.config.SpotPriceAutoProduct,
|
||||||
Tags: b.config.RunTags,
|
Tags: b.config.RunTags,
|
||||||
SpotTags: b.config.SpotTags,
|
SpotTags: b.config.SpotTags,
|
||||||
|
|
|
@ -112,8 +112,8 @@ builder.
|
||||||
of `source_ami`. Can be `paravirtual` or `hvm`.
|
of `source_ami`. Can be `paravirtual` or `hvm`.
|
||||||
|
|
||||||
- `associate_public_ip_address` (boolean) - If using a non-default VPC,
|
- `associate_public_ip_address` (boolean) - If using a non-default VPC,
|
||||||
public IP addresses are not provided by default. If this is toggled, your
|
public IP addresses are not provided by default. If this is `true`, your
|
||||||
new instance will get a Public IP.
|
new instance will get a Public IP. default: `false`
|
||||||
|
|
||||||
- `availability_zone` (string) - Destination availability zone to launch
|
- `availability_zone` (string) - Destination availability zone to launch
|
||||||
instance in. Leave this empty to allow Amazon to auto-assign.
|
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
|
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.
|
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
|
<%= partial "partials/builders/aws-spot-docs" %>
|
||||||
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.
|
|
||||||
|
|
||||||
- `sriov_support` (boolean) - Enable enhanced networking (SriovNetSupport but
|
- `sriov_support` (boolean) - Enable enhanced networking (SriovNetSupport but
|
||||||
not ENA) on HVM-compatible AMIs. If true, add `ec2:ModifyInstanceAttribute`
|
not ENA) on HVM-compatible AMIs. If true, add `ec2:ModifyInstanceAttribute`
|
||||||
|
|
|
@ -104,8 +104,8 @@ builder.
|
||||||
of `source_ami`. Can be `paravirtual` or `hvm`.
|
of `source_ami`. Can be `paravirtual` or `hvm`.
|
||||||
|
|
||||||
- `associate_public_ip_address` (boolean) - If using a non-default VPC,
|
- `associate_public_ip_address` (boolean) - If using a non-default VPC,
|
||||||
public IP addresses are not provided by default. If this is toggled, your
|
public IP addresses are not provided by default. If this is `true`, your
|
||||||
new instance will get a Public IP.
|
new instance will get a Public IP. default: `false`
|
||||||
|
|
||||||
- `availability_zone` (string) - Destination availability zone to launch
|
- `availability_zone` (string) - Destination availability zone to launch
|
||||||
instance in. Leave this empty to allow Amazon to auto-assign.
|
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
|
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.
|
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
|
<%= partial "partials/builders/aws-spot-docs" %>
|
||||||
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.
|
|
||||||
|
|
||||||
- `sriov_support` (boolean) - Enable enhanced networking (SriovNetSupport but
|
- `sriov_support` (boolean) - Enable enhanced networking (SriovNetSupport but
|
||||||
not ENA) on HVM-compatible AMIs. If true, add `ec2:ModifyInstanceAttribute`
|
not ENA) on HVM-compatible AMIs. If true, add `ec2:ModifyInstanceAttribute`
|
||||||
|
|
|
@ -107,8 +107,8 @@ builder.
|
||||||
data](#build-template-data) for more information.
|
data](#build-template-data) for more information.
|
||||||
|
|
||||||
- `associate_public_ip_address` (boolean) - If using a non-default VPC,
|
- `associate_public_ip_address` (boolean) - If using a non-default VPC,
|
||||||
public IP addresses are not provided by default. If this is toggled, your
|
public IP addresses are not provided by default. If this is `true`, your
|
||||||
new instance will get a Public IP.
|
new instance will get a Public IP. default: `false`
|
||||||
|
|
||||||
- `availability_zone` (string) - Destination availability zone to launch
|
- `availability_zone` (string) - Destination availability zone to launch
|
||||||
instance in. Leave this empty to allow Amazon to auto-assign.
|
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
|
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.
|
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
|
<%= partial "partials/builders/aws-spot-docs" %>
|
||||||
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.
|
|
||||||
|
|
||||||
- `sriov_support` (boolean) - Enable enhanced networking (SriovNetSupport but
|
- `sriov_support` (boolean) - Enable enhanced networking (SriovNetSupport but
|
||||||
not ENA) on HVM-compatible AMIs. If true, add `ec2:ModifyInstanceAttribute`
|
not ENA) on HVM-compatible AMIs. If true, add `ec2:ModifyInstanceAttribute`
|
||||||
|
|
|
@ -131,8 +131,8 @@ builder.
|
||||||
`paravirtual` (default) or `hvm`.
|
`paravirtual` (default) or `hvm`.
|
||||||
|
|
||||||
- `associate_public_ip_address` (boolean) - If using a non-default VPC,
|
- `associate_public_ip_address` (boolean) - If using a non-default VPC,
|
||||||
public IP addresses are not provided by default. If this is toggled, your
|
public IP addresses are not provided by default. If this is `true`, your
|
||||||
new instance will get a Public IP.
|
new instance will get a Public IP. default: `false`
|
||||||
|
|
||||||
- `availability_zone` (string) - Destination availability zone to launch
|
- `availability_zone` (string) - Destination availability zone to launch
|
||||||
instance in. Leave this empty to allow Amazon to auto-assign.
|
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.
|
- `snapshot_tags` (object of key/value strings) - Tags to apply to snapshot.
|
||||||
They will override AMI tags if already applied to snapshot.
|
They will override AMI tags if already applied to snapshot.
|
||||||
|
|
||||||
- `spot_price` (string) - The maximum hourly price to launch a spot instance
|
<%= partial "partials/builders/aws-spot-docs" %>
|
||||||
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.
|
|
||||||
|
|
||||||
- `sriov_support` (boolean) - Enable enhanced networking (SriovNetSupport but
|
- `sriov_support` (boolean) - Enable enhanced networking (SriovNetSupport but
|
||||||
not ENA) on HVM-compatible AMIs. If true, add `ec2:ModifyInstanceAttribute`
|
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…
Reference in New Issue