From 5b8dfdf30437fd406fc9563ee06ecfcfff28f7b5 Mon Sep 17 00:00:00 2001 From: Lucas Bajolet <105649352+lbajolet-hashicorp@users.noreply.github.com> Date: Mon, 16 Sep 2024 16:48:44 -0400 Subject: [PATCH] Allow fast boot disable on region (#509) * ebs: add fast-launch toggle on regions The fast-launch configuration allows setupping fast-launch for Windows virtual machines, either with a common configuration, or with a region-by-region configuration. However if someone wants to only enable fast-launch on a subset of regions, there wasn't any way to express that, leading to either a waste of resources, or a need to manually cleanup the extra snapshots created. Therefore this commit adds an extra trilean to the regional configuration for fast-launch, so it can be explicitly disabled on selected regions. * ebs: parallelise fast-launch tests with copy Those tests are long-running, and can run concurrently as they don't use the same resources, so in order to reduce the length of running those tests, we enable parallelisation on them. --- .web-docs/components/builder/ebs/README.md | 12 +++ builder/ebs/builder_acc_test.go | 80 +++++++++++++++++-- builder/ebs/fast_launch_setup.go | 20 +++++ builder/ebs/fast_launch_setup.hcl2spec.go | 10 ++- builder/ebs/step_enable_fast_launch.go | 8 ++ .../ebs/step_prepare_fast_launch_template.go | 14 ++++ .../FastLaunchTemplateConfig-not-required.mdx | 12 +++ 7 files changed, 147 insertions(+), 9 deletions(-) diff --git a/.web-docs/components/builder/ebs/README.md b/.web-docs/components/builder/ebs/README.md index 0e0bd6ae..a089c6a9 100644 --- a/.web-docs/components/builder/ebs/README.md +++ b/.web-docs/components/builder/ebs/README.md @@ -1755,6 +1755,18 @@ https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/win-ami-config-fast-launc +- `enable_fast_launch` (boolean) - Enable fast launch allows you to disable fast launch settings on the region level. + + If unset, the default region behavior will be assumed - i.e. either use + the globally specified template ID/name (if specified), or AWS will set + it for you. + + Using other fast launch options, while unset, will imply enable_fast_launch to be true. + + If this is explicitly set to `false` fast-launch will be + disabled for the specified region and all other options besides region + will be ignored. + - `template_id` (string) - The ID of the launch template to use for the fast launch This cannot be specified in conjunction with the template name. diff --git a/builder/ebs/builder_acc_test.go b/builder/ebs/builder_acc_test.go index afc70d85..49e4a35b 100644 --- a/builder/ebs/builder_acc_test.go +++ b/builder/ebs/builder_acc_test.go @@ -1146,6 +1146,7 @@ func TestAccBuilder_EbsWindowsFastLaunch(t *testing.T) { func TestAccBuilder_EbsWindowsFastLaunchWithAMICopies(t *testing.T) { amiNameWithoutLT := fmt.Sprintf("packer-ebs-windows-fastlaunch-with-copies-%d", time.Now().Unix()) amiNameWithLT := fmt.Sprintf("packer-ebs-windows-fastlaunch-with-copies-and-launch-templates-%d", time.Now().Unix()) + amiNameWithLTOneSkipped := fmt.Sprintf("packer-ebs-windows-fastlaunch-with-one-copy-disabled-%d", time.Now().Unix()) flWithCopiesAMIs := []amazon_acc.AMIHelper{ { @@ -1168,6 +1169,12 @@ func TestAccBuilder_EbsWindowsFastLaunchWithAMICopies(t *testing.T) { Name: amiNameWithLT, }, } + flWithCopiesAMIOneSkipped := []amazon_acc.AMIHelper{ + { + Region: "us-east-1", + Name: amiNameWithLTOneSkipped, + }, + } tests := []struct { name string @@ -1197,17 +1204,31 @@ func TestAccBuilder_EbsWindowsFastLaunchWithAMICopies(t *testing.T) { }, testWindowsFastBootWithAMICopiesAndLTs, }, + { + "ebs-windows-fast-launch-with-copies-one-region-disabled", + amiNameWithLTOneSkipped, + flWithCopiesAMIOneSkipped, + []string{ + "found template in region \"us-east-1\": ID \"lt-0c82d8943c032fc0b\"", + "fast-launch explicitly disabled for region \"us-east-2\"", + }, + testWindowsFastBootWithAMICopiesAndLTsOneDisabled, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + currtest := tt + + t.Parallel() + testcase := &acctest.PluginTestCase{ - Name: tt.name, - Template: fmt.Sprintf(tt.template, tt.amiName), + Name: currtest.name, + Template: fmt.Sprintf(currtest.template, currtest.amiName), Teardown: func() error { var errs error - for _, ami := range tt.amiSpec { + for _, ami := range currtest.amiSpec { err := ami.CleanUpAmi() if err != nil { t.Logf("cleaning up AMI %q in region %q failed: %s. It will need to be manually removed", ami.Name, ami.Region, err) @@ -1222,7 +1243,7 @@ func TestAccBuilder_EbsWindowsFastLaunchWithAMICopies(t *testing.T) { return fmt.Errorf("Bad exit code. Logfile: %s", logfile) } - for _, ami := range tt.amiSpec { + for _, ami := range currtest.amiSpec { amis, err := ami.GetAmi() if err != nil { return fmt.Errorf("failed to get AMI: %s", err) @@ -1270,7 +1291,7 @@ func TestAccBuilder_EbsWindowsFastLaunchWithAMICopies(t *testing.T) { t.Fatalf("failed to read logs from logifle: %s", err) } logStr := string(logs) - for _, str := range tt.stringsToFindInLog { + for _, str := range currtest.stringsToFindInLog { if !strings.Contains(logStr, str) { t.Errorf("exptected to find %q in logs, but did not", str) } @@ -2065,6 +2086,55 @@ build { } ` +const testWindowsFastBootWithAMICopiesAndLTsOneDisabled = ` +data "amazon-ami" "windows-ami" { + filters = { + name = "Windows_Server-2016-English-Core-Base-*" + } + owners = ["801119661308"] + most_recent = true + region = "us-east-1" +} + +source "amazon-ebs" "windows-fastboot" { + ami_name = "%s" + source_ami = data.amazon-ami.windows-ami.id + instance_type = "m3.medium" + region = "us-east-1" + ami_regions = ["us-east-2", "us-east-1"] + communicator = "winrm" + winrm_username = "Administrator" + winrm_password = "e4sypa55!" + user_data_file = "test-fixtures/ps_enable.ps" + + fast_launch { + enable_fast_launch = true + target_resource_count = 1 + + region_launch_templates { + region = "us-east-1" + template_id = "lt-0c82d8943c032fc0b" + } + + region_launch_templates { + region = "us-east-2" + enable_fast_launch = false + } + } +} + +build { + sources = ["amazon-ebs.windows-fastboot"] + + provisioner "powershell" { + inline = [ + "C:/ProgramData/Amazon/EC2-Windows/Launch/Scripts/InitializeInstance.ps1 -Schedule", + "C:/ProgramData/Amazon/EC2-Windows/Launch/Scripts/SysprepInstance.ps1 -NoShutdown" + ] + } +} +` + const testSubnetFilterWithPublicIP = ` source "amazon-ebs" "test-subnet-filter" { subnet_filter { diff --git a/builder/ebs/fast_launch_setup.go b/builder/ebs/fast_launch_setup.go index aac68e41..3746bd2c 100644 --- a/builder/ebs/fast_launch_setup.go +++ b/builder/ebs/fast_launch_setup.go @@ -9,6 +9,8 @@ package ebs import ( "fmt" "log" + + "github.com/hashicorp/packer-plugin-sdk/template/config" ) // FastLaunchTemplateConfig is the launch template configuration for a region. @@ -17,6 +19,18 @@ import ( // in the template, as each fast-launch enablement step occurs after the // copy, and each region may pick their own launch template. type FastLaunchTemplateConfig struct { + // Enable fast launch allows you to disable fast launch settings on the region level. + // + // If unset, the default region behavior will be assumed - i.e. either use + // the globally specified template ID/name (if specified), or AWS will set + // it for you. + // + // Using other fast launch options, while unset, will imply enable_fast_launch to be true. + // + // If this is explicitly set to `false` fast-launch will be + // disabled for the specified region and all other options besides region + // will be ignored. + EnableFalseLaunch config.Trilean `mapstructure:"enable_fast_launch"` // The region in which to find the launch template to use Region string `mapstructure:"region" required:"true"` // The ID of the launch template to use for the fast launch @@ -47,6 +61,12 @@ func (tc *FastLaunchTemplateConfig) Prepare() []error { return append(errs, fmt.Errorf("region cannot be empty for a regional fast template config")) } + // If we disabled fast-launch, we can immediately exit without validating + // the other options. + if tc.EnableFalseLaunch == config.TriFalse { + return errs + } + if tc.LaunchTemplateID != "" && tc.LaunchTemplateName != "" { errs = append(errs, fmt.Errorf("fast_launch_template_config region %q: both template ID and name cannot be specified at the same time", tc.Region)) } diff --git a/builder/ebs/fast_launch_setup.hcl2spec.go b/builder/ebs/fast_launch_setup.hcl2spec.go index 166df038..39fe88be 100644 --- a/builder/ebs/fast_launch_setup.hcl2spec.go +++ b/builder/ebs/fast_launch_setup.hcl2spec.go @@ -45,6 +45,7 @@ func (*FlatFastLaunchConfig) HCL2Spec() map[string]hcldec.Spec { // FlatFastLaunchTemplateConfig is an auto-generated flat version of FastLaunchTemplateConfig. // Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. type FlatFastLaunchTemplateConfig struct { + EnableFalseLaunch *bool `mapstructure:"enable_fast_launch" cty:"enable_fast_launch" hcl:"enable_fast_launch"` Region *string `mapstructure:"region" required:"true" cty:"region" hcl:"region"` LaunchTemplateID *string `mapstructure:"template_id" cty:"template_id" hcl:"template_id"` LaunchTemplateName *string `mapstructure:"template_name" cty:"template_name" hcl:"template_name"` @@ -63,10 +64,11 @@ func (*FastLaunchTemplateConfig) FlatMapstructure() interface{ HCL2Spec() map[st // The decoded values from this spec will then be applied to a FlatFastLaunchTemplateConfig. func (*FlatFastLaunchTemplateConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ - "region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false}, - "template_id": &hcldec.AttrSpec{Name: "template_id", Type: cty.String, Required: false}, - "template_name": &hcldec.AttrSpec{Name: "template_name", Type: cty.String, Required: false}, - "template_version": &hcldec.AttrSpec{Name: "template_version", Type: cty.Number, Required: false}, + "enable_fast_launch": &hcldec.AttrSpec{Name: "enable_fast_launch", Type: cty.Bool, Required: false}, + "region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false}, + "template_id": &hcldec.AttrSpec{Name: "template_id", Type: cty.String, Required: false}, + "template_name": &hcldec.AttrSpec{Name: "template_name", Type: cty.String, Required: false}, + "template_version": &hcldec.AttrSpec{Name: "template_version", Type: cty.Number, Required: false}, } return s } diff --git a/builder/ebs/step_enable_fast_launch.go b/builder/ebs/step_enable_fast_launch.go index 6ca0c8ee..e27aa153 100644 --- a/builder/ebs/step_enable_fast_launch.go +++ b/builder/ebs/step_enable_fast_launch.go @@ -74,6 +74,14 @@ func (s *stepEnableFastLaunch) Run(ctx context.Context, state multistep.StateBag go func(region, ami string) { defer wg.Done() + // Immediately return if we don't want fast-launch for a + // particular region + templateIDsByRegion, ok := state.GetOk("launch_template_version") + if ok && !templateIDsByRegion.(map[string]TemplateSpec)[region].Enabled { + log.Printf("skipping fast launch for region %q", region) + return + } + // Casting is somewhat unsafe, but since the retryer below only // accepts this type, and not ec2iface.EC2API, we can safely // do this here, unless the `GetRegionConn` function evolves diff --git a/builder/ebs/step_prepare_fast_launch_template.go b/builder/ebs/step_prepare_fast_launch_template.go index 273af1c2..457bd0c4 100644 --- a/builder/ebs/step_prepare_fast_launch_template.go +++ b/builder/ebs/step_prepare_fast_launch_template.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/packer-plugin-amazon/builder/common" "github.com/hashicorp/packer-plugin-sdk/multistep" packersdk "github.com/hashicorp/packer-plugin-sdk/packer" + "github.com/hashicorp/packer-plugin-sdk/template/config" ) type stepPrepareFastLaunchTemplate struct { @@ -25,6 +26,10 @@ type stepPrepareFastLaunchTemplate struct { type TemplateSpec struct { TemplateID string Version int + // Since this is what gets forwarded to the step that enables fast launch + // for each region, we have to also forward if the option should be disabled + // for a particular region + Enabled bool } func (s *stepPrepareFastLaunchTemplate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { @@ -49,6 +54,14 @@ func (s *stepPrepareFastLaunchTemplate) Run(ctx context.Context, state multistep for _, templateSpec := range s.RegionTemplates { region := templateSpec.Region + if templateSpec.EnableFalseLaunch == config.TriFalse { + log.Printf("[INFO] fast-launch explicitly disabled for region %q", region) + templateIDsByRegion[region] = TemplateSpec{ + Enabled: false, + } + continue + } + if templateSpec.LaunchTemplateID == "" && templateSpec.LaunchTemplateName == "" { log.Printf("[INFO] No fast-launch template specified for region %q", region) continue @@ -70,6 +83,7 @@ func (s *stepPrepareFastLaunchTemplate) Run(ctx context.Context, state multistep ts := TemplateSpec{ TemplateID: *tmpl.LaunchTemplateId, Version: templateSpec.LaunchTemplateVersion, + Enabled: true, } log.Printf("found template in region %q: ID %q, name %q", region, *tmpl.LaunchTemplateId, *tmpl.LaunchTemplateName) diff --git a/docs-partials/builder/ebs/FastLaunchTemplateConfig-not-required.mdx b/docs-partials/builder/ebs/FastLaunchTemplateConfig-not-required.mdx index b1d1aade..5998dce0 100644 --- a/docs-partials/builder/ebs/FastLaunchTemplateConfig-not-required.mdx +++ b/docs-partials/builder/ebs/FastLaunchTemplateConfig-not-required.mdx @@ -1,5 +1,17 @@ +- `enable_fast_launch` (boolean) - Enable fast launch allows you to disable fast launch settings on the region level. + + If unset, the default region behavior will be assumed - i.e. either use + the globally specified template ID/name (if specified), or AWS will set + it for you. + + Using other fast launch options, while unset, will imply enable_fast_launch to be true. + + If this is explicitly set to `false` fast-launch will be + disabled for the specified region and all other options besides region + will be ignored. + - `template_id` (string) - The ID of the launch template to use for the fast launch This cannot be specified in conjunction with the template name.