From 6967e021033319e250a3e00a836aa6b4ad6c1481 Mon Sep 17 00:00:00 2001 From: Evan Pipho Date: Sun, 11 Oct 2020 05:54:22 +0000 Subject: [PATCH 1/4] Add support for source instance tenancy to amazon builders --- builder/amazon/common/run_config.go | 4 ++++ builder/amazon/common/step_run_source_instance.go | 5 +++++ builder/amazon/ebs/builder.go | 1 + builder/amazon/ebs/builder.hcl2spec.go | 2 ++ builder/amazon/ebssurrogate/builder.go | 1 + builder/amazon/ebssurrogate/builder.hcl2spec.go | 2 ++ builder/amazon/ebsvolume/builder.go | 1 + builder/amazon/ebsvolume/builder.hcl2spec.go | 2 ++ builder/amazon/instance/builder.go | 1 + builder/amazon/instance/builder.hcl2spec.go | 2 ++ .../builder/amazon/common/RunConfig-not-required.mdx | 4 ++++ 11 files changed, 25 insertions(+) diff --git a/builder/amazon/common/run_config.go b/builder/amazon/common/run_config.go index 54985cac8..b5e620039 100644 --- a/builder/amazon/common/run_config.go +++ b/builder/amazon/common/run_config.go @@ -376,6 +376,10 @@ type RunConfig struct { // subnet-12345def, where Packer will launch the EC2 instance. This field is // required if you are using an non-default VPC. SubnetId string `mapstructure:"subnet_id" required:"false"` + // [Tenancy](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/dedicated-instance.html) used + // when Packer launches the EC2 instance, allowing it to be launched on dedicated hardware. + // If unset, the default shared tenancy will be used. + Tenancy string `mapstructure:"tenancy" required:"false"` // The name of the temporary key pair to // generate. By default, Packer generates a name that looks like // `packer_`, where <UUID> is a 36 character unique identifier. diff --git a/builder/amazon/common/step_run_source_instance.go b/builder/amazon/common/step_run_source_instance.go index 76939768e..71e42c261 100644 --- a/builder/amazon/common/step_run_source_instance.go +++ b/builder/amazon/common/step_run_source_instance.go @@ -33,6 +33,7 @@ type StepRunSourceInstance struct { IsRestricted bool SourceAMI string Tags map[string]string + Tenancy string UserData string UserDataFile string VolumeTags map[string]string @@ -195,6 +196,10 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa runOpts.InstanceInitiatedShutdownBehavior = &s.InstanceInitiatedShutdownBehavior } + if s.Tenancy != "" { + runOpts.Placement.Tenancy = aws.String(s.Tenancy) + } + var runResp *ec2.Reservation err = retry.Config{ Tries: 11, diff --git a/builder/amazon/ebs/builder.go b/builder/amazon/ebs/builder.go index 56f48c49d..7d99facf1 100644 --- a/builder/amazon/ebs/builder.go +++ b/builder/amazon/ebs/builder.go @@ -206,6 +206,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(), SourceAMI: b.config.SourceAmi, Tags: b.config.RunTags, + Tenancy: b.config.Tenancy, UserData: b.config.UserData, UserDataFile: b.config.UserDataFile, VolumeTags: b.config.VolumeRunTags, diff --git a/builder/amazon/ebs/builder.hcl2spec.go b/builder/amazon/ebs/builder.hcl2spec.go index 571d93717..ac5fec820 100644 --- a/builder/amazon/ebs/builder.hcl2spec.go +++ b/builder/amazon/ebs/builder.hcl2spec.go @@ -81,6 +81,7 @@ type FlatConfig struct { SpotTag []hcl2template.FlatKeyValue `mapstructure:"spot_tag" required:"false" cty:"spot_tag" hcl:"spot_tag"` SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter" hcl:"subnet_filter"` SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"` + Tenancy *string `mapstructure:"tenancy" required:"false" cty:"tenancy" hcl:"tenancy"` TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"` TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs" hcl:"temporary_security_group_source_cidrs"` UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"` @@ -226,6 +227,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "spot_tag": &hcldec.BlockListSpec{TypeName: "spot_tag", Nested: hcldec.ObjectSpec((*hcl2template.FlatKeyValue)(nil).HCL2Spec())}, "subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())}, "subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false}, + "tenancy": &hcldec.AttrSpec{Name: "tenancy", Type: cty.String, Required: false}, "temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false}, "temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false}, "user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false}, diff --git a/builder/amazon/ebssurrogate/builder.go b/builder/amazon/ebssurrogate/builder.go index d1620854f..d938cdd46 100644 --- a/builder/amazon/ebssurrogate/builder.go +++ b/builder/amazon/ebssurrogate/builder.go @@ -228,6 +228,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(), SourceAMI: b.config.SourceAmi, Tags: b.config.RunTags, + Tenancy: b.config.Tenancy, UserData: b.config.UserData, UserDataFile: b.config.UserDataFile, VolumeTags: b.config.VolumeRunTags, diff --git a/builder/amazon/ebssurrogate/builder.hcl2spec.go b/builder/amazon/ebssurrogate/builder.hcl2spec.go index deac89fac..3c6dad9a2 100644 --- a/builder/amazon/ebssurrogate/builder.hcl2spec.go +++ b/builder/amazon/ebssurrogate/builder.hcl2spec.go @@ -103,6 +103,7 @@ type FlatConfig struct { SpotTag []hcl2template.FlatKeyValue `mapstructure:"spot_tag" required:"false" cty:"spot_tag" hcl:"spot_tag"` SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter" hcl:"subnet_filter"` SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"` + Tenancy *string `mapstructure:"tenancy" required:"false" cty:"tenancy" hcl:"tenancy"` TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"` TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs" hcl:"temporary_security_group_source_cidrs"` UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"` @@ -249,6 +250,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "spot_tag": &hcldec.BlockListSpec{TypeName: "spot_tag", Nested: hcldec.ObjectSpec((*hcl2template.FlatKeyValue)(nil).HCL2Spec())}, "subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())}, "subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false}, + "tenancy": &hcldec.AttrSpec{Name: "tenancy", Type: cty.String, Required: false}, "temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false}, "temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false}, "user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false}, diff --git a/builder/amazon/ebsvolume/builder.go b/builder/amazon/ebsvolume/builder.go index a1ed2a935..3ff801907 100644 --- a/builder/amazon/ebsvolume/builder.go +++ b/builder/amazon/ebsvolume/builder.go @@ -209,6 +209,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(), SourceAMI: b.config.SourceAmi, Tags: b.config.RunTags, + Tenancy: b.config.Tenancy, UserData: b.config.UserData, UserDataFile: b.config.UserDataFile, VolumeTags: b.config.VolumeRunTags, diff --git a/builder/amazon/ebsvolume/builder.hcl2spec.go b/builder/amazon/ebsvolume/builder.hcl2spec.go index fa5aad217..dde662e16 100644 --- a/builder/amazon/ebsvolume/builder.hcl2spec.go +++ b/builder/amazon/ebsvolume/builder.hcl2spec.go @@ -105,6 +105,7 @@ type FlatConfig struct { SpotTag []hcl2template.FlatKeyValue `mapstructure:"spot_tag" required:"false" cty:"spot_tag" hcl:"spot_tag"` SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter" hcl:"subnet_filter"` SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"` + Tenancy *string `mapstructure:"tenancy" required:"false" cty:"tenancy" hcl:"tenancy"` TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"` TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs" hcl:"temporary_security_group_source_cidrs"` UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"` @@ -229,6 +230,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "spot_tag": &hcldec.BlockListSpec{TypeName: "spot_tag", Nested: hcldec.ObjectSpec((*hcl2template.FlatKeyValue)(nil).HCL2Spec())}, "subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())}, "subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false}, + "tenancy": &hcldec.AttrSpec{Name: "tenancy", Type: cty.String, Required: false}, "temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false}, "temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false}, "user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false}, diff --git a/builder/amazon/instance/builder.go b/builder/amazon/instance/builder.go index bc8680c5b..7e3256d22 100644 --- a/builder/amazon/instance/builder.go +++ b/builder/amazon/instance/builder.go @@ -286,6 +286,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(), SourceAMI: b.config.SourceAmi, Tags: b.config.RunTags, + Tenancy: b.config.Tenancy, UserData: b.config.UserData, UserDataFile: b.config.UserDataFile, } diff --git a/builder/amazon/instance/builder.hcl2spec.go b/builder/amazon/instance/builder.hcl2spec.go index fa967cdb5..2dbb722d5 100644 --- a/builder/amazon/instance/builder.hcl2spec.go +++ b/builder/amazon/instance/builder.hcl2spec.go @@ -81,6 +81,7 @@ type FlatConfig struct { SpotTag []hcl2template.FlatKeyValue `mapstructure:"spot_tag" required:"false" cty:"spot_tag" hcl:"spot_tag"` SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter" hcl:"subnet_filter"` SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"` + Tenancy *string `mapstructure:"tenancy" required:"false" cty:"tenancy" hcl:"tenancy"` TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"` TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs" hcl:"temporary_security_group_source_cidrs"` UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"` @@ -232,6 +233,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "spot_tag": &hcldec.BlockListSpec{TypeName: "spot_tag", Nested: hcldec.ObjectSpec((*hcl2template.FlatKeyValue)(nil).HCL2Spec())}, "subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())}, "subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false}, + "tenancy": &hcldec.AttrSpec{Name: "tenancy", Type: cty.String, Required: false}, "temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false}, "temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false}, "user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false}, diff --git a/website/pages/partials/builder/amazon/common/RunConfig-not-required.mdx b/website/pages/partials/builder/amazon/common/RunConfig-not-required.mdx index 4d66cb0e1..54e131096 100644 --- a/website/pages/partials/builder/amazon/common/RunConfig-not-required.mdx +++ b/website/pages/partials/builder/amazon/common/RunConfig-not-required.mdx @@ -291,6 +291,10 @@ subnet-12345def, where Packer will launch the EC2 instance. This field is required if you are using an non-default VPC. +- `tenancy` (string) - [Tenancy](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/dedicated-instance.html) used + when Packer launches the EC2 instance, allowing it to be launched on dedicated hardware. + If unset, the default shared tenancy will be used. + - `temporary_key_pair_name` (string) - The name of the temporary key pair to generate. By default, Packer generates a name that looks like `packer_`, where <UUID> is a 36 character unique identifier. From d5d1a8708e6b86290892be063fde6903a26c2ba6 Mon Sep 17 00:00:00 2001 From: Evan Pipho Date: Sun, 11 Oct 2020 06:06:28 +0000 Subject: [PATCH 2/4] Add tests for Tenancy vs Spot Price --- builder/amazon/common/run_config.go | 6 ++++++ builder/amazon/common/run_config_test.go | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/builder/amazon/common/run_config.go b/builder/amazon/common/run_config.go index b5e620039..f2aea20a6 100644 --- a/builder/amazon/common/run_config.go +++ b/builder/amazon/common/run_config.go @@ -635,6 +635,12 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { } } + if c.Tenancy != "" && c.Tenancy != "default" { + if c.SpotPrice != "" { + errs = append(errs, fmt.Errorf("Error: Non-default tenancy cannot be used in conjunction with Spot Instances")) + } + } + return errs } diff --git a/builder/amazon/common/run_config_test.go b/builder/amazon/common/run_config_test.go index 23e28adf4..546ede250 100644 --- a/builder/amazon/common/run_config_test.go +++ b/builder/amazon/common/run_config_test.go @@ -232,3 +232,23 @@ func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) { t.Fatal("keypair name does not match") } } + +func TestRunConfigPrepare_TenancySpot(t *testing.T) { + c := testConfig() + c.Tenancy = "dedicated" + c.SpotPrice = "1" + + if err := c.Prepare(nil); len(err) != 1 { + t.Fatal("Should error if non-default tenancy and spot price are both set") + } +} + +func TestRunConfigPrepare_TenancySpotDefault(t *testing.T) { + c := testConfig() + c.Tenancy = "default" + c.SpotPrice = "1" + + if err := c.Prepare(nil); len(err) != 0 { + t.Fatal("Should not error if tenancy is set to default with spot price") + } +} From 608307cd1ec2c4d8197d4e93e9849c266d78545e Mon Sep 17 00:00:00 2001 From: Evan Pipho Date: Mon, 12 Oct 2020 22:05:58 +0000 Subject: [PATCH 3/4] Re-allow spot + tenancy. Validate tenancy is set to a usable value --- builder/amazon/common/run_config.go | 13 ++++++++----- builder/amazon/common/run_config_test.go | 22 +++++++++++----------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/builder/amazon/common/run_config.go b/builder/amazon/common/run_config.go index f2aea20a6..932e416ad 100644 --- a/builder/amazon/common/run_config.go +++ b/builder/amazon/common/run_config.go @@ -378,7 +378,9 @@ type RunConfig struct { SubnetId string `mapstructure:"subnet_id" required:"false"` // [Tenancy](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/dedicated-instance.html) used // when Packer launches the EC2 instance, allowing it to be launched on dedicated hardware. - // If unset, the default shared tenancy will be used. + // + // The default is "default", meaning shared tenancy. Allowed values are "default", + // "dedicated" and "host". Tenancy string `mapstructure:"tenancy" required:"false"` // The name of the temporary key pair to // generate. By default, Packer generates a name that looks like @@ -635,10 +637,11 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { } } - if c.Tenancy != "" && c.Tenancy != "default" { - if c.SpotPrice != "" { - errs = append(errs, fmt.Errorf("Error: Non-default tenancy cannot be used in conjunction with Spot Instances")) - } + if c.Tenancy != "" && + c.Tenancy != "default" && + c.Tenancy != "dedicated" && + c.Tenancy != "host" { + errs = append(errs, fmt.Errorf("Error: Unknown tenancy type %s", c.Tenancy)) } return errs diff --git a/builder/amazon/common/run_config_test.go b/builder/amazon/common/run_config_test.go index 546ede250..452cdab22 100644 --- a/builder/amazon/common/run_config_test.go +++ b/builder/amazon/common/run_config_test.go @@ -233,22 +233,22 @@ func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) { } } -func TestRunConfigPrepare_TenancySpot(t *testing.T) { +func TestRunConfigPrepare_TenancyBad(t *testing.T) { c := testConfig() - c.Tenancy = "dedicated" - c.SpotPrice = "1" + c.Tenancy = "not_real" if err := c.Prepare(nil); len(err) != 1 { - t.Fatal("Should error if non-default tenancy and spot price are both set") + t.Fatal("Should error if tenancy is set to an invalid type") } } -func TestRunConfigPrepare_TenancySpotDefault(t *testing.T) { - c := testConfig() - c.Tenancy = "default" - c.SpotPrice = "1" - - if err := c.Prepare(nil); len(err) != 0 { - t.Fatal("Should not error if tenancy is set to default with spot price") +func TestRunConfigPrepare_TenancyGood(t *testing.T) { + validTenancy := []string{"", "default", "dedicated", "host"} + for _, vt := range validTenancy { + c := testConfig() + c.Tenancy = vt + if err := c.Prepare(nil); len(err) != 0 { + t.Fatalf("Should not error if tenancy is set to %s", vt) + } } } From dd068acfd14d0003e10daeb088f0e25c6fb1eb5d Mon Sep 17 00:00:00 2001 From: Evan Pipho Date: Fri, 16 Oct 2020 23:44:58 +0000 Subject: [PATCH 4/4] Re-generate docs --- .../partials/builder/amazon/common/RunConfig-not-required.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/pages/partials/builder/amazon/common/RunConfig-not-required.mdx b/website/pages/partials/builder/amazon/common/RunConfig-not-required.mdx index 54e131096..083e499eb 100644 --- a/website/pages/partials/builder/amazon/common/RunConfig-not-required.mdx +++ b/website/pages/partials/builder/amazon/common/RunConfig-not-required.mdx @@ -293,7 +293,9 @@ - `tenancy` (string) - [Tenancy](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/dedicated-instance.html) used when Packer launches the EC2 instance, allowing it to be launched on dedicated hardware. - If unset, the default shared tenancy will be used. + + The default is "default", meaning shared tenancy. Allowed values are "default", + "dedicated" and "host". - `temporary_key_pair_name` (string) - The name of the temporary key pair to generate. By default, Packer generates a name that looks like