From c9714ce69e5a68ef673b2455909cfee4070cc4e3 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Thu, 18 Jun 2015 13:23:48 -0500 Subject: [PATCH 1/4] builder/amazon-ebs: Clean up orphan volumes Fixes #1783 --- builder/amazon/common/block_device.go | 2 +- builder/amazon/ebs/builder.go | 3 + builder/amazon/ebs/step_cleanup_volumes.go | 117 +++++++++++++++++++++ 3 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 builder/amazon/ebs/step_cleanup_volumes.go diff --git a/builder/amazon/common/block_device.go b/builder/amazon/common/block_device.go index e97cd4107..a01dfc83d 100644 --- a/builder/amazon/common/block_device.go +++ b/builder/amazon/common/block_device.go @@ -42,7 +42,7 @@ func buildBlockDevices(b []BlockDevice) []*ec2.BlockDeviceMapping { // You cannot specify Encrypted if you specify a Snapshot ID if blockDevice.SnapshotId != "" { ebsBlockDevice.SnapshotID = &blockDevice.SnapshotId - } else { + } else if blockDevice.Encrypted { ebsBlockDevice.Encrypted = &blockDevice.Encrypted } diff --git a/builder/amazon/ebs/builder.go b/builder/amazon/ebs/builder.go index c13947ac4..35dc98a6d 100644 --- a/builder/amazon/ebs/builder.go +++ b/builder/amazon/ebs/builder.go @@ -99,6 +99,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe CommConfig: &b.config.RunConfig.Comm, VpcId: b.config.VpcId, }, + &stepCleanupVolumes{ + BlockDevices: b.config.BlockDevices, + }, &awscommon.StepRunSourceInstance{ Debug: b.config.PackerDebug, ExpectedRootDevice: "ebs", diff --git a/builder/amazon/ebs/step_cleanup_volumes.go b/builder/amazon/ebs/step_cleanup_volumes.go new file mode 100644 index 000000000..c66f9d786 --- /dev/null +++ b/builder/amazon/ebs/step_cleanup_volumes.go @@ -0,0 +1,117 @@ +package ebs + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/builder/amazon/common" + "github.com/mitchellh/packer/packer" +) + +// stepCleanupVolumes cleans up any orphaned volumes that were not designated to +// remain after termination of the instance. These volumes are typically ones +// that are marked as "delete on terminate:false" in the source_ami of a build. +type stepCleanupVolumes struct { + BlockDevices common.BlockDevices +} + +func (s *stepCleanupVolumes) Run(state multistep.StateBag) multistep.StepAction { + // stepCleanupVolumes is for Cleanup only + return multistep.ActionContinue +} + +func (s *stepCleanupVolumes) Cleanup(state multistep.StateBag) { + ec2conn := state.Get("ec2").(*ec2.EC2) + instanceRaw := state.Get("instance") + var instance *ec2.Instance + if instanceRaw != nil { + instance = instanceRaw.(*ec2.Instance) + } + ui := state.Get("ui").(packer.Ui) + amisRaw := state.Get("amis") + if amisRaw == nil { + ui.Say("No AMIs to cleanup") + return + } + + if instance == nil { + ui.Say("No volumes to clean up, skipping") + return + } + + ui.Say("Cleaning up any extra volumes...") + + save := make(map[string]bool) + for _, b := range s.BlockDevices.AMIMappings { + if !b.DeleteOnTermination { + save[b.DeviceName] = true + } + } + + for _, b := range s.BlockDevices.LaunchMappings { + if !b.DeleteOnTermination { + save[b.DeviceName] = true + } + } + + // Collect Volume information from the cached Instance as a map of volume-id + // to device name, to compare with save list above + var vl []*string + volList := make(map[string]string) + for _, bdm := range instance.BlockDeviceMappings { + if bdm.EBS != nil { + vl = append(vl, bdm.EBS.VolumeID) + volList[*bdm.EBS.VolumeID] = *bdm.DeviceName + } + } + + // Using the volume list from the cached Instance, check with AWS for up to + // date information on them + resp, err := ec2conn.DescribeVolumes(&ec2.DescribeVolumesInput{ + Filters: []*ec2.Filter{ + &ec2.Filter{ + Name: aws.String("volume-id"), + Values: vl, + }, + }, + }) + + if err != nil { + ui.Say(fmt.Sprintf("Error describing volumes: %s", err)) + return + } + + // If any of the returned volumes are in a "deleting" stage or otherwise not + // available, remove them from the list of volumes + for _, v := range resp.Volumes { + if v.State != nil && *v.State != "available" { + delete(volList, *v.VolumeID) + } + } + + if len(resp.Volumes) == 0 { + ui.Say("No volumes to clean up, skipping") + return + } + + // Filter out any devices marked for saving + for saveName, _ := range save { + for volKey, volName := range volList { + if volName == saveName { + delete(volList, volKey) + } + } + } + + // Destroy remaining volumes + for k, _ := range volList { + ui.Say(fmt.Sprintf("Destroying volume (%s)...", k)) + _, err := ec2conn.DeleteVolume(&ec2.DeleteVolumeInput{VolumeID: aws.String(k)}) + if err != nil { + ui.Say(fmt.Sprintf("Error deleting volume: %s", k)) + } + + } +} From 7165156b646fc159ba2a62175e82ea652f4c1e37 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Fri, 19 Jun 2015 15:56:05 -0500 Subject: [PATCH 2/4] add note on EBS cleanup behavior --- website/.ruby-version | 1 + website/source/docs/builders/amazon-ebs.html.markdown | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 website/.ruby-version diff --git a/website/.ruby-version b/website/.ruby-version new file mode 100644 index 000000000..b1b25a5ff --- /dev/null +++ b/website/.ruby-version @@ -0,0 +1 @@ +2.2.2 diff --git a/website/source/docs/builders/amazon-ebs.html.markdown b/website/source/docs/builders/amazon-ebs.html.markdown index 5420c10fd..4fc48c441 100644 --- a/website/source/docs/builders/amazon-ebs.html.markdown +++ b/website/source/docs/builders/amazon-ebs.html.markdown @@ -278,6 +278,13 @@ Here is an example using the optional AMI tags. This will add the tags } ``` +-> **Note:** Packer users pre-built AMIs as the source for building images. +These source AMIs may include volumes that are not flagged to be destroyed on +termiation of the instance building the new image. Packer will attempt to clean +up all residual volumes that are not designated by the user to remain after +termination. If you need to preserve those source volumes, you can overwrite the +termination setting by specifying `delete_on_termination=false` in the +`launch_device_mappings` block for the device. [1]: http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_BlockDeviceMapping.html [2]: http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_EbsBlockDevice.html From 93b5ae5b3c460184d16258a50df889281ae76cd2 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Mon, 22 Jun 2015 10:33:35 -0500 Subject: [PATCH 3/4] fix typo --- website/source/docs/builders/amazon-ebs.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/builders/amazon-ebs.html.markdown b/website/source/docs/builders/amazon-ebs.html.markdown index 4fc48c441..b7f16eef9 100644 --- a/website/source/docs/builders/amazon-ebs.html.markdown +++ b/website/source/docs/builders/amazon-ebs.html.markdown @@ -278,7 +278,7 @@ Here is an example using the optional AMI tags. This will add the tags } ``` --> **Note:** Packer users pre-built AMIs as the source for building images. +-> **Note:** Packer uses pre-built AMIs as the source for building images. These source AMIs may include volumes that are not flagged to be destroyed on termiation of the instance building the new image. Packer will attempt to clean up all residual volumes that are not designated by the user to remain after From dff6cf1a83d9be8ea211f150db2c60d875ddafb9 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Mon, 22 Jun 2015 10:48:54 -0500 Subject: [PATCH 4/4] code tweak after review --- builder/amazon/ebs/step_cleanup_volumes.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/builder/amazon/ebs/step_cleanup_volumes.go b/builder/amazon/ebs/step_cleanup_volumes.go index c66f9d786..56ebe5527 100644 --- a/builder/amazon/ebs/step_cleanup_volumes.go +++ b/builder/amazon/ebs/step_cleanup_volumes.go @@ -43,16 +43,17 @@ func (s *stepCleanupVolumes) Cleanup(state multistep.StateBag) { ui.Say("Cleaning up any extra volumes...") - save := make(map[string]bool) + // We don't actually care about the value here, but we need Set behavior + save := make(map[string]struct{}) for _, b := range s.BlockDevices.AMIMappings { if !b.DeleteOnTermination { - save[b.DeviceName] = true + save[b.DeviceName] = struct{}{} } } for _, b := range s.BlockDevices.LaunchMappings { if !b.DeleteOnTermination { - save[b.DeviceName] = true + save[b.DeviceName] = struct{}{} } }