diff --git a/builder/amazon/common/run_config.go b/builder/amazon/common/run_config.go index f647182aa..129d4d541 100644 --- a/builder/amazon/common/run_config.go +++ b/builder/amazon/common/run_config.go @@ -1,11 +1,11 @@ package common import ( - "errors" "fmt" "net" "os" "regexp" + "strings" "time" "github.com/hashicorp/packer/common/uuid" @@ -30,25 +30,26 @@ func (d *AmiFilterOptions) Empty() bool { type RunConfig struct { AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"` AvailabilityZone string `mapstructure:"availability_zone"` + DisableStopInstance bool `mapstructure:"disable_stop_instance"` EbsOptimized bool `mapstructure:"ebs_optimized"` + EnableT2Unlimited bool `mapstructure:"enable_t2_unlimited"` IamInstanceProfile string `mapstructure:"iam_instance_profile"` + InstanceInitiatedShutdownBehavior string `mapstructure:"shutdown_behavior"` InstanceType string `mapstructure:"instance_type"` RunTags map[string]string `mapstructure:"run_tags"` + SecurityGroupId string `mapstructure:"security_group_id"` + SecurityGroupIds []string `mapstructure:"security_group_ids"` SourceAmi string `mapstructure:"source_ami"` SourceAmiFilter AmiFilterOptions `mapstructure:"source_ami_filter"` SpotPrice string `mapstructure:"spot_price"` SpotPriceAutoProduct string `mapstructure:"spot_price_auto_product"` - DisableStopInstance bool `mapstructure:"disable_stop_instance"` - SecurityGroupId string `mapstructure:"security_group_id"` - SecurityGroupIds []string `mapstructure:"security_group_ids"` - TemporarySGSourceCidr string `mapstructure:"temporary_security_group_source_cidr"` SubnetId string `mapstructure:"subnet_id"` TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"` + TemporarySGSourceCidr string `mapstructure:"temporary_security_group_source_cidr"` UserData string `mapstructure:"user_data"` UserDataFile string `mapstructure:"user_data_file"` - WindowsPasswordTimeout time.Duration `mapstructure:"windows_password_timeout"` VpcId string `mapstructure:"vpc_id"` - InstanceInitiatedShutdownBehavior string `mapstructure:"shutdown_behavior"` + WindowsPasswordTimeout time.Duration `mapstructure:"windows_password_timeout"` // Communicator settings Comm communicator.Config `mapstructure:",squash"` @@ -84,32 +85,39 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { c.SSHInterface != "public_dns" && c.SSHInterface != "private_dns" && c.SSHInterface != "" { - errs = append(errs, errors.New(fmt.Sprintf("Unknown interface type: %s", c.SSHInterface))) + errs = append(errs, fmt.Errorf("Unknown interface type: %s", c.SSHInterface)) } if c.SSHKeyPairName != "" { if c.Comm.Type == "winrm" && c.Comm.WinRMPassword == "" && c.Comm.SSHPrivateKey == "" { - errs = append(errs, errors.New("ssh_private_key_file must be provided to retrieve the winrm password when using ssh_keypair_name.")) + errs = append(errs, fmt.Errorf("ssh_private_key_file must be provided to retrieve the winrm password when using ssh_keypair_name.")) } else if c.Comm.SSHPrivateKey == "" && !c.Comm.SSHAgentAuth { - errs = append(errs, errors.New("ssh_private_key_file must be provided or ssh_agent_auth enabled when ssh_keypair_name is specified.")) + errs = append(errs, fmt.Errorf("ssh_private_key_file must be provided or ssh_agent_auth enabled when ssh_keypair_name is specified.")) } } if c.SourceAmi == "" && c.SourceAmiFilter.Empty() { - errs = append(errs, errors.New("A source_ami or source_ami_filter must be specified")) + errs = append(errs, fmt.Errorf("A source_ami or source_ami_filter must be specified")) } if c.InstanceType == "" { - errs = append(errs, errors.New("An instance_type must be specified")) + errs = append(errs, fmt.Errorf("An instance_type must be specified")) } if c.SpotPrice == "auto" { if c.SpotPriceAutoProduct == "" { - errs = append(errs, errors.New( + errs = append(errs, fmt.Errorf( "spot_price_auto_product must be specified when spot_price is auto")) } } + if c.SpotPriceAutoProduct != "" { + if c.SpotPrice != "auto" { + errs = append(errs, fmt.Errorf( + "spot_price should be set to auto when spot_price_auto_product is specified")) + } + } + if c.UserData != "" && c.UserDataFile != "" { errs = append(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified.")) } else if c.UserDataFile != "" { @@ -141,6 +149,18 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { errs = append(errs, fmt.Errorf("shutdown_behavior only accepts 'stop' or 'terminate' values.")) } + if c.EnableT2Unlimited { + if c.SpotPrice != "" { + errs = append(errs, fmt.Errorf("Error: T2 Unlimited cannot be used in conjuction with Spot Instances")) + } + firstDotIndex := strings.Index(c.InstanceType, ".") + if firstDotIndex == -1 { + errs = append(errs, fmt.Errorf("Error determining main Instance Type from: %s", c.InstanceType)) + } else if c.InstanceType[0:firstDotIndex] != "t2" { + errs = append(errs, fmt.Errorf("Error: T2 Unlimited enabled with a non-T2 Instance Type: %s", c.InstanceType)) + } + } + return errs } diff --git a/builder/amazon/common/run_config_test.go b/builder/amazon/common/run_config_test.go index a88730e82..fde9ef760 100644 --- a/builder/amazon/common/run_config_test.go +++ b/builder/amazon/common/run_config_test.go @@ -48,7 +48,7 @@ func TestRunConfigPrepare_InstanceType(t *testing.T) { c := testConfig() c.InstanceType = "" if err := c.Prepare(nil); len(err) != 1 { - t.Fatalf("err: %s", err) + t.Fatalf("Should error if an instance_type is not specified") } } @@ -56,14 +56,14 @@ func TestRunConfigPrepare_SourceAmi(t *testing.T) { c := testConfig() c.SourceAmi = "" if err := c.Prepare(nil); len(err) != 1 { - t.Fatalf("err: %s", err) + t.Fatalf("Should error if a source_ami (or source_ami_filter) is not specified") } } func TestRunConfigPrepare_SourceAmiFilterBlank(t *testing.T) { c := testConfigFilter() if err := c.Prepare(nil); len(err) != 1 { - t.Fatalf("err: %s", err) + t.Fatalf("Should error if source_ami_filter is empty or not specified (and source_ami is not specified)") } } @@ -79,17 +79,58 @@ func TestRunConfigPrepare_SourceAmiFilterGood(t *testing.T) { } } +func TestRunConfigPrepare_EnableT2UnlimitedGood(t *testing.T) { + c := testConfig() + // Must have a T2 instance type if T2 Unlimited is enabled + c.InstanceType = "t2.micro" + c.EnableT2Unlimited = true + err := c.Prepare(nil) + if len(err) > 0 { + t.Fatalf("err: %s", err) + } +} + +func TestRunConfigPrepare_EnableT2UnlimitedBadInstanceType(t *testing.T) { + c := testConfig() + // T2 Unlimited cannot be used with instance types other than T2 + c.InstanceType = "m5.large" + c.EnableT2Unlimited = true + err := c.Prepare(nil) + if len(err) != 1 { + t.Fatalf("Should error if T2 Unlimited is enabled with non-T2 instance_type") + } +} + +func TestRunConfigPrepare_EnableT2UnlimitedBadWithSpotInstanceRequest(t *testing.T) { + c := testConfig() + // T2 Unlimited cannot be used with Spot Instances + c.InstanceType = "t2.micro" + c.EnableT2Unlimited = true + c.SpotPrice = "auto" + c.SpotPriceAutoProduct = "Linux/UNIX" + err := c.Prepare(nil) + if len(err) != 1 { + t.Fatalf("Should error if T2 Unlimited has been used in conjuntion with a Spot Price request") + } +} + func TestRunConfigPrepare_SpotAuto(t *testing.T) { c := testConfig() c.SpotPrice = "auto" if err := c.Prepare(nil); len(err) != 1 { - t.Fatalf("err: %s", err) + t.Fatalf("Should error if spot_price_auto_product is not set and spot_price is set to auto") } + // Good - SpotPrice and SpotPriceAutoProduct are correctly set c.SpotPriceAutoProduct = "foo" if err := c.Prepare(nil); len(err) != 0 { t.Fatalf("err: %s", err) } + + c.SpotPrice = "" + if err := c.Prepare(nil); len(err) != 1 { + t.Fatalf("Should error if spot_price is not set to auto and spot_price_auto_product is set") + } } func TestRunConfigPrepare_SSHPort(t *testing.T) { @@ -125,7 +166,7 @@ func TestRunConfigPrepare_UserData(t *testing.T) { c.UserData = "foo" c.UserDataFile = tf.Name() if err := c.Prepare(nil); len(err) != 1 { - t.Fatalf("err: %s", err) + t.Fatalf("Should error if user_data string and user_data_file have both been specified") } } @@ -137,7 +178,7 @@ func TestRunConfigPrepare_UserDataFile(t *testing.T) { c.UserDataFile = "idontexistidontthink" if err := c.Prepare(nil); len(err) != 1 { - t.Fatalf("err: %s", err) + t.Fatalf("Should error if the file specified by user_data_file does not exist") } tf, err := ioutil.TempFile("", "packer") diff --git a/builder/amazon/common/step_run_source_instance.go b/builder/amazon/common/step_run_source_instance.go index 114da38e1..4dd8fbe74 100644 --- a/builder/amazon/common/step_run_source_instance.go +++ b/builder/amazon/common/step_run_source_instance.go @@ -24,6 +24,7 @@ type StepRunSourceInstance struct { Ctx interpolate.Context Debug bool EbsOptimized bool + EnableT2Unlimited bool ExpectedRootDevice string IamInstanceProfile string InstanceInitiatedShutdownBehavior string @@ -116,6 +117,11 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa EbsOptimized: &s.EbsOptimized, } + if s.EnableT2Unlimited { + creditOption := "unlimited" + runOpts.CreditSpecification = &ec2.CreditSpecificationRequest{CpuCredits: &creditOption} + } + // Collect tags for tagging on resource creation var tagSpecs []*ec2.TagSpecification diff --git a/builder/amazon/ebs/builder.go b/builder/amazon/ebs/builder.go index 5e63b05c8..665bf8098 100644 --- a/builder/amazon/ebs/builder.go +++ b/builder/amazon/ebs/builder.go @@ -148,6 +148,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Ctx: b.config.ctx, Debug: b.config.PackerDebug, EbsOptimized: b.config.EbsOptimized, + EnableT2Unlimited: b.config.EnableT2Unlimited, ExpectedRootDevice: "ebs", IamInstanceProfile: b.config.IamInstanceProfile, InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior, diff --git a/builder/amazon/ebssurrogate/builder.go b/builder/amazon/ebssurrogate/builder.go index 31f47164f..52a151b22 100644 --- a/builder/amazon/ebssurrogate/builder.go +++ b/builder/amazon/ebssurrogate/builder.go @@ -162,6 +162,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Ctx: b.config.ctx, Debug: b.config.PackerDebug, EbsOptimized: b.config.EbsOptimized, + EnableT2Unlimited: b.config.EnableT2Unlimited, ExpectedRootDevice: "ebs", IamInstanceProfile: b.config.IamInstanceProfile, InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior, diff --git a/builder/amazon/ebsvolume/builder.go b/builder/amazon/ebsvolume/builder.go index a1cc1fd2a..1a79b964e 100644 --- a/builder/amazon/ebsvolume/builder.go +++ b/builder/amazon/ebsvolume/builder.go @@ -145,6 +145,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Ctx: b.config.ctx, Debug: b.config.PackerDebug, EbsOptimized: b.config.EbsOptimized, + EnableT2Unlimited: b.config.EnableT2Unlimited, ExpectedRootDevice: "ebs", IamInstanceProfile: b.config.IamInstanceProfile, InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior, diff --git a/builder/amazon/instance/builder.go b/builder/amazon/instance/builder.go index eb3ecdbda..c197cadeb 100644 --- a/builder/amazon/instance/builder.go +++ b/builder/amazon/instance/builder.go @@ -230,6 +230,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Ctx: b.config.ctx, Debug: b.config.PackerDebug, EbsOptimized: b.config.EbsOptimized, + EnableT2Unlimited: b.config.EnableT2Unlimited, IamInstanceProfile: b.config.IamInstanceProfile, InstanceType: b.config.InstanceType, IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(), diff --git a/website/source/docs/builders/amazon-ebs.html.md b/website/source/docs/builders/amazon-ebs.html.md index d5ed5b497..2901bd249 100644 --- a/website/source/docs/builders/amazon-ebs.html.md +++ b/website/source/docs/builders/amazon-ebs.html.md @@ -169,6 +169,30 @@ builder. Note: you must make sure enhanced networking is enabled on your instance. See [Amazon's documentation on enabling enhanced networking](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enhanced-networking.html#enabling_enhanced_networking). Default `false`. +- `enable_t2_unlimited` (boolean) - Enabling T2 Unlimited allows the source + instance to burst additional CPU beyond its available [CPU Credits] + (https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/t2-credits-baseline-concepts.html) + for as long as the demand exists. + This is in contrast to the standard configuration that only allows an + instance to consume up to its available CPU Credits. + See the AWS documentation for [T2 Unlimited] + (https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/t2-unlimited.html) + and the 'T2 Unlimited Pricing' section of the [Amazon EC2 On-Demand + Pricing](https://aws.amazon.com/ec2/pricing/on-demand/) document for more + information. + By default this option is disabled and Packer will set up a [T2 + Standard](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/t2-std.html) + instance instead. + + To use T2 Unlimited you must use a T2 instance type e.g. t2.micro. + Additionally, T2 Unlimited cannot be used in conjunction with Spot + Instances e.g. when the `spot_price` option has been configured. + Attempting to do so will cause an error. + + !> **Warning!** Additional costs may be incurred by enabling T2 + Unlimited - even for instances that would usually qualify for the + [AWS Free Tier](https://aws.amazon.com/free/). + - `force_deregister` (boolean) - Force Packer to first deregister an existing AMI if one with the same name already exists. Default `false`. diff --git a/website/source/docs/builders/amazon-ebssurrogate.html.md b/website/source/docs/builders/amazon-ebssurrogate.html.md index 8cf9508b4..720521536 100644 --- a/website/source/docs/builders/amazon-ebssurrogate.html.md +++ b/website/source/docs/builders/amazon-ebssurrogate.html.md @@ -162,6 +162,30 @@ builder. Note: you must make sure enhanced networking is enabled on your instance. See [Amazon's documentation on enabling enhanced networking](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enhanced-networking.html#enabling_enhanced_networking). Default `false`. +- `enable_t2_unlimited` (boolean) - Enabling T2 Unlimited allows the source + instance to burst additional CPU beyond its available [CPU Credits] + (https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/t2-credits-baseline-concepts.html) + for as long as the demand exists. + This is in contrast to the standard configuration that only allows an + instance to consume up to its available CPU Credits. + See the AWS documentation for [T2 Unlimited] + (https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/t2-unlimited.html) + and the 'T2 Unlimited Pricing' section of the [Amazon EC2 On-Demand + Pricing](https://aws.amazon.com/ec2/pricing/on-demand/) document for more + information. + By default this option is disabled and Packer will set up a [T2 + Standard](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/t2-std.html) + instance instead. + + To use T2 Unlimited you must use a T2 instance type e.g. t2.micro. + Additionally, T2 Unlimited cannot be used in conjunction with Spot + Instances e.g. when the `spot_price` option has been configured. + Attempting to do so will cause an error. + + !> **Warning!** Additional costs may be incurred by enabling T2 + Unlimited - even for instances that would usually qualify for the + [AWS Free Tier](https://aws.amazon.com/free/). + - `force_deregister` (boolean) - Force Packer to first deregister an existing AMI if one with the same name already exists. Default `false`. diff --git a/website/source/docs/builders/amazon-ebsvolume.html.md b/website/source/docs/builders/amazon-ebsvolume.html.md index a39a31fcd..1bab5eb62 100644 --- a/website/source/docs/builders/amazon-ebsvolume.html.md +++ b/website/source/docs/builders/amazon-ebsvolume.html.md @@ -120,6 +120,30 @@ builder. Note: you must make sure enhanced networking is enabled on your instance. See [Amazon's documentation on enabling enhanced networking](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enhanced-networking.html#enabling_enhanced_networking). Default `false`. +- `enable_t2_unlimited` (boolean) - Enabling T2 Unlimited allows the source + instance to burst additional CPU beyond its available [CPU Credits] + (https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/t2-credits-baseline-concepts.html) + for as long as the demand exists. + This is in contrast to the standard configuration that only allows an + instance to consume up to its available CPU Credits. + See the AWS documentation for [T2 Unlimited] + (https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/t2-unlimited.html) + and the 'T2 Unlimited Pricing' section of the [Amazon EC2 On-Demand + Pricing](https://aws.amazon.com/ec2/pricing/on-demand/) document for more + information. + By default this option is disabled and Packer will set up a [T2 + Standard](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/t2-std.html) + instance instead. + + To use T2 Unlimited you must use a T2 instance type e.g. t2.micro. + Additionally, T2 Unlimited cannot be used in conjunction with Spot + Instances e.g. when the `spot_price` option has been configured. + Attempting to do so will cause an error. + + !> **Warning!** Additional costs may be incurred by enabling T2 + Unlimited - even for instances that would usually qualify for the + [AWS Free Tier](https://aws.amazon.com/free/). + - `iam_instance_profile` (string) - The name of an [IAM instance profile](https://docs.aws.amazon.com/IAM/latest/UserGuide/instance-profiles.html) to launch the EC2 instance with. diff --git a/website/source/docs/builders/amazon-instance.html.md b/website/source/docs/builders/amazon-instance.html.md index 77d7c2f5b..52686aeec 100644 --- a/website/source/docs/builders/amazon-instance.html.md +++ b/website/source/docs/builders/amazon-instance.html.md @@ -193,6 +193,30 @@ builder. Note: you must make sure enhanced networking is enabled on your instance. See [Amazon's documentation on enabling enhanced networking](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enhanced-networking.html#enabling_enhanced_networking). Default `false`. +- `enable_t2_unlimited` (boolean) - Enabling T2 Unlimited allows the source + instance to burst additional CPU beyond its available [CPU Credits] + (https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/t2-credits-baseline-concepts.html) + for as long as the demand exists. + This is in contrast to the standard configuration that only allows an + instance to consume up to its available CPU Credits. + See the AWS documentation for [T2 Unlimited] + (https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/t2-unlimited.html) + and the 'T2 Unlimited Pricing' section of the [Amazon EC2 On-Demand + Pricing](https://aws.amazon.com/ec2/pricing/on-demand/) document for more + information. + By default this option is disabled and Packer will set up a [T2 + Standard](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/t2-std.html) + instance instead. + + To use T2 Unlimited you must use a T2 instance type e.g. t2.micro. + Additionally, T2 Unlimited cannot be used in conjunction with Spot + Instances e.g. when the `spot_price` option has been configured. + Attempting to do so will cause an error. + + !> **Warning!** Additional costs may be incurred by enabling T2 + Unlimited - even for instances that would usually qualify for the + [AWS Free Tier](https://aws.amazon.com/free/). + - `force_deregister` (boolean) - Force Packer to first deregister an existing AMI if one with the same name already exists. Defaults to `false`.