From 5b59f56cdb6cbb4bd4f7c3fc147e3edb655eafd3 Mon Sep 17 00:00:00 2001 From: Arthur Burkart Date: Tue, 29 Nov 2016 23:16:01 -0500 Subject: [PATCH] Adds `force_delete_snapshot` flag This PR adds the ability for Packer to clean up snapshots in addition to deregistering AMIs at build time. To test this, I used the following `test.json` file: ```json { "builders": [ { "type": "amazon-ebs", "region": "us-east-1", "source_ami": "ami-fce3c696", "ami_name": "packer-test", "instance_type": "m3.medium", "ssh_username": "ubuntu", "vpc_id": "some-vpc-id", "subnet_id": "some-subnet-routed-through-igw", "security_group_id": "some-security-group-with-port-22-access", "force_delete_snapshot": true } ], "provisioners": [ { "type": "shell-local", "command": "echo 'hello'" } ] } ``` I appreciate any constructive feedbakc that can be given. Cheers! --- builder/amazon/chroot/builder.go | 5 ++- builder/amazon/common/ami_config.go | 1 + builder/amazon/common/step_deregister_ami.go | 28 +++++++++++-- builder/amazon/ebs/builder.go | 5 ++- builder/amazon/ebs/builder_acc_test.go | 39 ++++++++++++++++++- builder/amazon/instance/builder.go | 5 ++- .../docs/builders/amazon-chroot.html.md | 3 ++ .../source/docs/builders/amazon-ebs.html.md | 3 ++ .../docs/builders/amazon-instance.html.md | 3 ++ 9 files changed, 80 insertions(+), 12 deletions(-) diff --git a/builder/amazon/chroot/builder.go b/builder/amazon/chroot/builder.go index c9f56289a..8239584a3 100644 --- a/builder/amazon/chroot/builder.go +++ b/builder/amazon/chroot/builder.go @@ -244,8 +244,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &StepEarlyCleanup{}, &StepSnapshot{}, &awscommon.StepDeregisterAMI{ - ForceDeregister: b.config.AMIForceDeregister, - AMIName: b.config.AMIName, + ForceDeregister: b.config.AMIForceDeregister, + ForceDeleteSnapshot: b.config.AMIForceDeleteSnapshot, + AMIName: b.config.AMIName, }, &StepRegisterAMI{ RootVolumeSize: b.config.RootVolumeSize, diff --git a/builder/amazon/common/ami_config.go b/builder/amazon/common/ami_config.go index 28eafb7fd..c59653863 100644 --- a/builder/amazon/common/ami_config.go +++ b/builder/amazon/common/ami_config.go @@ -19,6 +19,7 @@ type AMIConfig struct { AMITags map[string]string `mapstructure:"tags"` AMIEnhancedNetworking bool `mapstructure:"enhanced_networking"` AMIForceDeregister bool `mapstructure:"force_deregister"` + AMIForceDeleteSnapshot bool `mapstructure:"force_delete_snapshot"` AMIEncryptBootVolume bool `mapstructure:"encrypt_boot"` SnapshotTags map[string]string `mapstructure:"snapshot_tags"` } diff --git a/builder/amazon/common/step_deregister_ami.go b/builder/amazon/common/step_deregister_ami.go index f92de2797..97f4257ff 100644 --- a/builder/amazon/common/step_deregister_ami.go +++ b/builder/amazon/common/step_deregister_ami.go @@ -10,15 +10,16 @@ import ( ) type StepDeregisterAMI struct { - ForceDeregister bool - AMIName string + ForceDeregister bool + ForceDeleteSnapshot bool + AMIName string } func (s *StepDeregisterAMI) Run(state multistep.StateBag) multistep.StepAction { ec2conn := state.Get("ec2").(*ec2.EC2) ui := state.Get("ui").(packer.Ui) - // check for force deregister + // Check for force deregister if s.ForceDeregister { resp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ Filters: []*ec2.Filter{{ @@ -33,7 +34,7 @@ func (s *StepDeregisterAMI) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - // deregister image(s) by that name + // Deregister image(s) by name for _, i := range resp.Images { _, err := ec2conn.DeregisterImage(&ec2.DeregisterImageInput{ ImageId: i.ImageId, @@ -46,6 +47,25 @@ func (s *StepDeregisterAMI) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } ui.Say(fmt.Sprintf("Deregistered AMI %s, id: %s", s.AMIName, *i.ImageId)) + + // Delete snapshot(s) by image + if s.ForceDeleteSnapshot { + for _, b := range i.BlockDeviceMappings { + if b.Ebs != nil { + _, err := ec2conn.DeleteSnapshot(&ec2.DeleteSnapshotInput{ + SnapshotId: b.Ebs.SnapshotId, + }) + + if err != nil { + err := fmt.Errorf("Error deleting existing snapshot: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + ui.Say(fmt.Sprintf("Deleted snapshot: %s", *b.Ebs.SnapshotId)) + } + } + } } } diff --git a/builder/amazon/ebs/builder.go b/builder/amazon/ebs/builder.go index dd4ff246d..5335fa579 100644 --- a/builder/amazon/ebs/builder.go +++ b/builder/amazon/ebs/builder.go @@ -165,8 +165,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe EnableEnhancedNetworking: b.config.AMIEnhancedNetworking, }, &awscommon.StepDeregisterAMI{ - ForceDeregister: b.config.AMIForceDeregister, - AMIName: b.config.AMIName, + ForceDeregister: b.config.AMIForceDeregister, + ForceDeleteSnapshot: b.config.AMIForceDeleteSnapshot, + AMIName: b.config.AMIName, }, &stepCreateAMI{}, &stepCreateEncryptedAMICopy{}, diff --git a/builder/amazon/ebs/builder_acc_test.go b/builder/amazon/ebs/builder_acc_test.go index e6ac29c3c..c4fe64e5d 100644 --- a/builder/amazon/ebs/builder_acc_test.go +++ b/builder/amazon/ebs/builder_acc_test.go @@ -46,6 +46,22 @@ func TestBuilderAcc_forceDeregister(t *testing.T) { }) } +func TestBuilderAcc_forceDeleteSnapshot(t *testing.T) { + // Build the same AMI name twice, with force_delete_snapshot on the second run + builderT.Test(t, builderT.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Builder: &Builder{}, + Template: buildForceDeleteSnapshotConfig("false", "dereg"), + SkipArtifactTeardown: true, + }) + + builderT.Test(t, builderT.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Builder: &Builder{}, + Template: buildForceDeleteSnapshotConfig("true", "dereg"), + }) +} + func TestBuilderAcc_amiSharing(t *testing.T) { builderT.Test(t, builderT.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -254,6 +270,21 @@ const testBuilderAccForceDeregister = ` } ` +const testBuilderAccForceDeleteSnapshot = ` +{ + "builders": [{ + "type": "test", + "region": "us-east-1", + "instance_type": "m3.medium", + "source_ami": "ami-76b2a71e", + "ssh_username": "ubuntu", + "force_deregister": "%s", + "force_delete_snapshot": "%s", + "ami_name": "packer-test-%s" + }] +} +` + // share with catsby const testBuilderAccSharing = ` { @@ -284,6 +315,10 @@ const testBuilderAccEncrypted = ` } ` -func buildForceDeregisterConfig(name, flag string) string { - return fmt.Sprintf(testBuilderAccForceDeregister, name, flag) +func buildForceDeregisterConfig(val, name string) string { + return fmt.Sprintf(testBuilderAccForceDeregister, val, name) +} + +func buildForceDeleteSnapshotConfig(val, name string) string { + return fmt.Sprintf(testBuilderAccForceDeleteSnapshot, val, val, name) } diff --git a/builder/amazon/instance/builder.go b/builder/amazon/instance/builder.go index 230dcaf34..0d6513c9b 100644 --- a/builder/amazon/instance/builder.go +++ b/builder/amazon/instance/builder.go @@ -247,8 +247,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Debug: b.config.PackerDebug, }, &awscommon.StepDeregisterAMI{ - ForceDeregister: b.config.AMIForceDeregister, - AMIName: b.config.AMIName, + ForceDeregister: b.config.AMIForceDeregister, + ForceDeleteSnapshot: b.config.AMIForceDeleteSnapshot, + AMIName: b.config.AMIName, }, &StepRegisterAMI{}, &awscommon.StepAMIRegionCopy{ diff --git a/website/source/docs/builders/amazon-chroot.html.md b/website/source/docs/builders/amazon-chroot.html.md index d90353909..1afbea8c2 100644 --- a/website/source/docs/builders/amazon-chroot.html.md +++ b/website/source/docs/builders/amazon-chroot.html.md @@ -124,6 +124,9 @@ each category, the available configuration keys are alphabetized. - `force_deregister` (boolean) - Force Packer to first deregister an existing AMI if one with the same name already exists. Default `false`. +- `force_delete_snapshot` (boolean) - Force Packer to delete snapshots associated with + AMIs, which have been deregistered by `force_deregister`. Default `false`. + - `from_scratch` (boolean) - Build a new volume instead of starting from an existing AMI root volume snapshot. Default `false`. If true, `source_ami` is no longer used and the following options become required: diff --git a/website/source/docs/builders/amazon-ebs.html.md b/website/source/docs/builders/amazon-ebs.html.md index 2f2ccc80d..94784986a 100644 --- a/website/source/docs/builders/amazon-ebs.html.md +++ b/website/source/docs/builders/amazon-ebs.html.md @@ -155,6 +155,9 @@ builder. - `force_deregister` (boolean) - Force Packer to first deregister an existing AMI if one with the same name already exists. Default `false`. +- `force_delete_snapshot` (boolean) - Force Packer to delete snapshots associated with + AMIs, which have been deregistered by `force_deregister`. Default `false`. + - `encrypt_boot` (boolean) - Instruct packer to automatically create a copy of the AMI with an encrypted boot volume (discarding the initial unencrypted AMI in the process). Default `false`. diff --git a/website/source/docs/builders/amazon-instance.html.md b/website/source/docs/builders/amazon-instance.html.md index 241c9aeb4..f3bbdadc9 100644 --- a/website/source/docs/builders/amazon-instance.html.md +++ b/website/source/docs/builders/amazon-instance.html.md @@ -179,6 +179,9 @@ builder. - `force_deregister` (boolean) - Force Packer to first deregister an existing AMI if one with the same name already exists. Default `false`. +- `force_delete_snapshot` (boolean) - Force Packer to delete snapshots associated with + AMIs, which have been deregistered by `force_deregister`. Default `false`. + - `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.