Commit 65792190 authored by Clint's avatar Clint

Merge pull request #2282 from mitchellh/f-amazon-cleanup-volumes

builder/amazon-ebs: Clean up orphan volumes
parents 93bb0d86 dff6cf1a
...@@ -42,7 +42,7 @@ func buildBlockDevices(b []BlockDevice) []*ec2.BlockDeviceMapping { ...@@ -42,7 +42,7 @@ func buildBlockDevices(b []BlockDevice) []*ec2.BlockDeviceMapping {
// You cannot specify Encrypted if you specify a Snapshot ID // You cannot specify Encrypted if you specify a Snapshot ID
if blockDevice.SnapshotId != "" { if blockDevice.SnapshotId != "" {
ebsBlockDevice.SnapshotID = &blockDevice.SnapshotId ebsBlockDevice.SnapshotID = &blockDevice.SnapshotId
} else { } else if blockDevice.Encrypted {
ebsBlockDevice.Encrypted = &blockDevice.Encrypted ebsBlockDevice.Encrypted = &blockDevice.Encrypted
} }
......
...@@ -99,6 +99,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -99,6 +99,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
CommConfig: &b.config.RunConfig.Comm, CommConfig: &b.config.RunConfig.Comm,
VpcId: b.config.VpcId, VpcId: b.config.VpcId,
}, },
&stepCleanupVolumes{
BlockDevices: b.config.BlockDevices,
},
&awscommon.StepRunSourceInstance{ &awscommon.StepRunSourceInstance{
Debug: b.config.PackerDebug, Debug: b.config.PackerDebug,
ExpectedRootDevice: "ebs", ExpectedRootDevice: "ebs",
......
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...")
// 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] = struct{}{}
}
}
for _, b := range s.BlockDevices.LaunchMappings {
if !b.DeleteOnTermination {
save[b.DeviceName] = struct{}{}
}
}
// 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))
}
}
}
...@@ -278,6 +278,13 @@ Here is an example using the optional AMI tags. This will add the tags ...@@ -278,6 +278,13 @@ Here is an example using the optional AMI tags. This will add the tags
} }
``` ```
-> **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
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 [1]: http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_BlockDeviceMapping.html
[2]: http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_EbsBlockDevice.html [2]: http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_EbsBlockDevice.html
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment