Merge pull request #6056 from rjosephwright/snapshots
Modify ebssurrogate builder to snapshot all launch devices
This commit is contained in:
commit
f2fc064037
|
@ -176,6 +176,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
}
|
||||
}
|
||||
|
||||
amiDevices := b.config.BuildAMIDevices()
|
||||
launchDevices := b.config.BuildLaunchDevices()
|
||||
|
||||
// Build the steps
|
||||
steps := []multistep.Step{
|
||||
&awscommon.StepPreValidate{
|
||||
|
@ -227,8 +230,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
|
||||
EnableAMIENASupport: b.config.AMIENASupport,
|
||||
},
|
||||
&StepSnapshotNewRootVolume{
|
||||
NewRootMountPoint: b.config.RootDevice.SourceDeviceName,
|
||||
&StepSnapshotVolumes{
|
||||
LaunchDevices: launchDevices,
|
||||
},
|
||||
&awscommon.StepDeregisterAMI{
|
||||
AccessConfig: &b.config.AccessConfig,
|
||||
|
@ -239,7 +242,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
},
|
||||
&StepRegisterAMI{
|
||||
RootDevice: b.config.RootDevice,
|
||||
BlockDevices: b.config.BlockDevices.BuildAMIDevices(),
|
||||
AMIDevices: amiDevices,
|
||||
LaunchDevices: launchDevices,
|
||||
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
|
||||
EnableAMIENASupport: b.config.AMIENASupport,
|
||||
},
|
||||
|
|
|
@ -3,8 +3,6 @@ package ebssurrogate
|
|||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
|
@ -46,21 +44,3 @@ func (c *RootBlockDevice) Prepare(ctx *interpolate.Context) []error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *RootBlockDevice) createBlockDeviceMapping(snapshotId string) *ec2.BlockDeviceMapping {
|
||||
rootBlockDevice := &ec2.EbsBlockDevice{
|
||||
SnapshotId: aws.String(snapshotId),
|
||||
VolumeType: aws.String(d.VolumeType),
|
||||
VolumeSize: aws.Int64(d.VolumeSize),
|
||||
DeleteOnTermination: aws.Bool(d.DeleteOnTermination),
|
||||
}
|
||||
|
||||
if d.IOPS != 0 {
|
||||
rootBlockDevice.Iops = aws.Int64(d.IOPS)
|
||||
}
|
||||
|
||||
return &ec2.BlockDeviceMapping{
|
||||
DeviceName: aws.String(d.DeviceName),
|
||||
Ebs: rootBlockDevice,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,8 @@ import (
|
|||
// StepRegisterAMI creates the AMI.
|
||||
type StepRegisterAMI struct {
|
||||
RootDevice RootBlockDevice
|
||||
BlockDevices []*ec2.BlockDeviceMapping
|
||||
AMIDevices []*ec2.BlockDeviceMapping
|
||||
LaunchDevices []*ec2.BlockDeviceMapping
|
||||
EnableAMIENASupport bool
|
||||
EnableAMISriovNetSupport bool
|
||||
image *ec2.Image
|
||||
|
@ -23,19 +24,19 @@ type StepRegisterAMI struct {
|
|||
func (s *StepRegisterAMI) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
snapshotId := state.Get("snapshot_id").(string)
|
||||
snapshotIds := state.Get("snapshot_ids").(map[string]string)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Registering the AMI...")
|
||||
|
||||
blockDevicesExcludingRoot := DeduplicateRootVolume(s.BlockDevices, s.RootDevice, snapshotId)
|
||||
blockDevices := s.combineDevices(snapshotIds)
|
||||
|
||||
registerOpts := &ec2.RegisterImageInput{
|
||||
Name: &config.AMIName,
|
||||
Architecture: aws.String(ec2.ArchitectureValuesX8664),
|
||||
RootDeviceName: aws.String(s.RootDevice.DeviceName),
|
||||
VirtualizationType: aws.String(config.AMIVirtType),
|
||||
BlockDeviceMappings: blockDevicesExcludingRoot,
|
||||
BlockDeviceMappings: blockDevices,
|
||||
}
|
||||
|
||||
if s.EnableAMISriovNetSupport {
|
||||
|
@ -120,17 +121,34 @@ func (s *StepRegisterAMI) Cleanup(state multistep.StateBag) {
|
|||
}
|
||||
}
|
||||
|
||||
func DeduplicateRootVolume(BlockDevices []*ec2.BlockDeviceMapping, RootDevice RootBlockDevice, snapshotId string) []*ec2.BlockDeviceMapping {
|
||||
// Defensive coding to make sure we only add the root volume once
|
||||
blockDevicesExcludingRoot := make([]*ec2.BlockDeviceMapping, 0, len(BlockDevices))
|
||||
for _, blockDevice := range BlockDevices {
|
||||
if *blockDevice.DeviceName == RootDevice.SourceDeviceName {
|
||||
continue
|
||||
}
|
||||
func (s *StepRegisterAMI) combineDevices(snapshotIds map[string]string) []*ec2.BlockDeviceMapping {
|
||||
devices := map[string]*ec2.BlockDeviceMapping{}
|
||||
|
||||
blockDevicesExcludingRoot = append(blockDevicesExcludingRoot, blockDevice)
|
||||
for _, device := range s.AMIDevices {
|
||||
devices[*device.DeviceName] = device
|
||||
}
|
||||
|
||||
blockDevicesExcludingRoot = append(blockDevicesExcludingRoot, RootDevice.createBlockDeviceMapping(snapshotId))
|
||||
return blockDevicesExcludingRoot
|
||||
// Devices in launch_block_device_mappings override any with
|
||||
// the same name in ami_block_device_mappings, except for the
|
||||
// one designated as the root device in ami_root_device
|
||||
for _, device := range s.LaunchDevices {
|
||||
snapshotId, ok := snapshotIds[*device.DeviceName]
|
||||
if ok {
|
||||
device.Ebs.SnapshotId = aws.String(snapshotId)
|
||||
// Block devices with snapshot inherit
|
||||
// encryption settings from the snapshot
|
||||
device.Ebs.Encrypted = nil
|
||||
device.Ebs.KmsKeyId = nil
|
||||
}
|
||||
if *device.DeviceName == s.RootDevice.SourceDeviceName {
|
||||
device.DeviceName = aws.String(s.RootDevice.DeviceName)
|
||||
}
|
||||
devices[*device.DeviceName] = device
|
||||
}
|
||||
|
||||
blockDevices := []*ec2.BlockDeviceMapping{}
|
||||
for _, device := range devices {
|
||||
blockDevices = append(blockDevices, device)
|
||||
}
|
||||
return blockDevices
|
||||
}
|
||||
|
|
|
@ -1,37 +1,247 @@
|
|||
package ebssurrogate
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
)
|
||||
|
||||
func GetStringPointer() *string {
|
||||
tmp := "/dev/name"
|
||||
return &tmp
|
||||
}
|
||||
const sourceDeviceName = "/dev/xvdf"
|
||||
const rootDeviceName = "/dev/xvda"
|
||||
|
||||
func GetTestDevice() *ec2.BlockDeviceMapping {
|
||||
TestDev := ec2.BlockDeviceMapping{
|
||||
DeviceName: GetStringPointer(),
|
||||
}
|
||||
return &TestDev
|
||||
}
|
||||
|
||||
func TestStepRegisterAmi_DeduplicateRootVolume(t *testing.T) {
|
||||
TestRootDevice := RootBlockDevice{}
|
||||
TestRootDevice.SourceDeviceName = "/dev/name"
|
||||
|
||||
blockDevices := []*ec2.BlockDeviceMapping{}
|
||||
blockDevicesExcludingRoot := DeduplicateRootVolume(blockDevices, TestRootDevice, "12342351")
|
||||
if len(blockDevicesExcludingRoot) != 1 {
|
||||
t.Fatalf("Unexpected length of block devices list")
|
||||
}
|
||||
|
||||
TestBlockDevice := GetTestDevice()
|
||||
blockDevices = append(blockDevices, TestBlockDevice)
|
||||
blockDevicesExcludingRoot = DeduplicateRootVolume(blockDevices, TestRootDevice, "12342351")
|
||||
if len(blockDevicesExcludingRoot) != 1 {
|
||||
t.Fatalf("Unexpected length of block devices list")
|
||||
func newStepRegisterAMI(amiDevices, launchDevices []*ec2.BlockDeviceMapping) *StepRegisterAMI {
|
||||
return &StepRegisterAMI{
|
||||
RootDevice: RootBlockDevice{
|
||||
SourceDeviceName: sourceDeviceName,
|
||||
DeviceName: rootDeviceName,
|
||||
DeleteOnTermination: true,
|
||||
VolumeType: "ebs",
|
||||
VolumeSize: 10,
|
||||
},
|
||||
AMIDevices: amiDevices,
|
||||
LaunchDevices: launchDevices,
|
||||
}
|
||||
}
|
||||
|
||||
func sorted(devices []*ec2.BlockDeviceMapping) []*ec2.BlockDeviceMapping {
|
||||
sort.SliceStable(devices, func(i, j int) bool {
|
||||
return *devices[i].DeviceName < *devices[j].DeviceName
|
||||
})
|
||||
return devices
|
||||
}
|
||||
|
||||
func TestStepRegisterAmi_combineDevices(t *testing.T) {
|
||||
cases := []struct {
|
||||
snapshotIds map[string]string
|
||||
amiDevices []*ec2.BlockDeviceMapping
|
||||
launchDevices []*ec2.BlockDeviceMapping
|
||||
allDevices []*ec2.BlockDeviceMapping
|
||||
}{
|
||||
{
|
||||
snapshotIds: map[string]string{},
|
||||
amiDevices: []*ec2.BlockDeviceMapping{},
|
||||
launchDevices: []*ec2.BlockDeviceMapping{},
|
||||
allDevices: []*ec2.BlockDeviceMapping{},
|
||||
},
|
||||
{
|
||||
snapshotIds: map[string]string{},
|
||||
amiDevices: []*ec2.BlockDeviceMapping{},
|
||||
launchDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{},
|
||||
DeviceName: aws.String(sourceDeviceName),
|
||||
},
|
||||
},
|
||||
allDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{},
|
||||
DeviceName: aws.String(rootDeviceName),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Minimal single device
|
||||
snapshotIds: map[string]string{
|
||||
sourceDeviceName: "snap-0123456789abcdef1",
|
||||
},
|
||||
amiDevices: []*ec2.BlockDeviceMapping{},
|
||||
launchDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{},
|
||||
DeviceName: aws.String(sourceDeviceName),
|
||||
},
|
||||
},
|
||||
allDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
SnapshotId: aws.String("snap-0123456789abcdef1"),
|
||||
},
|
||||
DeviceName: aws.String(rootDeviceName),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Single launch device with AMI device
|
||||
snapshotIds: map[string]string{
|
||||
sourceDeviceName: "snap-0123456789abcdef1",
|
||||
},
|
||||
amiDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{},
|
||||
DeviceName: aws.String("/dev/xvdg"),
|
||||
},
|
||||
},
|
||||
launchDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{},
|
||||
DeviceName: aws.String(sourceDeviceName),
|
||||
},
|
||||
},
|
||||
allDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
SnapshotId: aws.String("snap-0123456789abcdef1"),
|
||||
},
|
||||
DeviceName: aws.String(rootDeviceName),
|
||||
},
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{},
|
||||
DeviceName: aws.String("/dev/xvdg"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Multiple launch devices
|
||||
snapshotIds: map[string]string{
|
||||
sourceDeviceName: "snap-0123456789abcdef1",
|
||||
"/dev/xvdg": "snap-0123456789abcdef2",
|
||||
},
|
||||
amiDevices: []*ec2.BlockDeviceMapping{},
|
||||
launchDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{},
|
||||
DeviceName: aws.String(sourceDeviceName),
|
||||
},
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{},
|
||||
DeviceName: aws.String("/dev/xvdg"),
|
||||
},
|
||||
},
|
||||
allDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
SnapshotId: aws.String("snap-0123456789abcdef1"),
|
||||
},
|
||||
DeviceName: aws.String(rootDeviceName),
|
||||
},
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
SnapshotId: aws.String("snap-0123456789abcdef2"),
|
||||
},
|
||||
DeviceName: aws.String("/dev/xvdg"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Multiple launch devices with encryption
|
||||
snapshotIds: map[string]string{
|
||||
sourceDeviceName: "snap-0123456789abcdef1",
|
||||
"/dev/xvdg": "snap-0123456789abcdef2",
|
||||
},
|
||||
amiDevices: []*ec2.BlockDeviceMapping{},
|
||||
launchDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
Encrypted: aws.Bool(true),
|
||||
},
|
||||
DeviceName: aws.String(sourceDeviceName),
|
||||
},
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
Encrypted: aws.Bool(true),
|
||||
},
|
||||
DeviceName: aws.String("/dev/xvdg"),
|
||||
},
|
||||
},
|
||||
allDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
SnapshotId: aws.String("snap-0123456789abcdef1"),
|
||||
// Encrypted: true stripped from snapshotted devices
|
||||
},
|
||||
DeviceName: aws.String(rootDeviceName),
|
||||
},
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
SnapshotId: aws.String("snap-0123456789abcdef2"),
|
||||
},
|
||||
DeviceName: aws.String("/dev/xvdg"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Multiple launch devices and AMI devices with encryption
|
||||
snapshotIds: map[string]string{
|
||||
sourceDeviceName: "snap-0123456789abcdef1",
|
||||
"/dev/xvdg": "snap-0123456789abcdef2",
|
||||
},
|
||||
amiDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
Encrypted: aws.Bool(true),
|
||||
KmsKeyId: aws.String("keyId"),
|
||||
},
|
||||
// Source device name can be used in AMI devices
|
||||
// since launch device of same name gets renamed
|
||||
// to root device name
|
||||
DeviceName: aws.String(sourceDeviceName),
|
||||
},
|
||||
},
|
||||
launchDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
Encrypted: aws.Bool(true),
|
||||
},
|
||||
DeviceName: aws.String(sourceDeviceName),
|
||||
},
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
Encrypted: aws.Bool(true),
|
||||
},
|
||||
DeviceName: aws.String("/dev/xvdg"),
|
||||
},
|
||||
},
|
||||
allDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
Encrypted: aws.Bool(true),
|
||||
KmsKeyId: aws.String("keyId"),
|
||||
},
|
||||
DeviceName: aws.String(sourceDeviceName),
|
||||
},
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
SnapshotId: aws.String("snap-0123456789abcdef1"),
|
||||
},
|
||||
DeviceName: aws.String(rootDeviceName),
|
||||
},
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
SnapshotId: aws.String("snap-0123456789abcdef2"),
|
||||
},
|
||||
DeviceName: aws.String("/dev/xvdg"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
stepRegisterAmi := newStepRegisterAMI(tc.amiDevices, tc.launchDevices)
|
||||
allDevices := stepRegisterAmi.combineDevices(tc.snapshotIds)
|
||||
if !reflect.DeepEqual(sorted(allDevices), sorted(tc.allDevices)) {
|
||||
t.Fatalf("Unexpected output from combineDevices")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
package ebssurrogate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
// StepSnapshotNewRootVolume creates a snapshot of the created volume.
|
||||
//
|
||||
// Produces:
|
||||
// snapshot_id string - ID of the created snapshot
|
||||
type StepSnapshotNewRootVolume struct {
|
||||
NewRootMountPoint string
|
||||
snapshotId string
|
||||
}
|
||||
|
||||
func (s *StepSnapshotNewRootVolume) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
|
||||
var newRootVolume string
|
||||
for _, volume := range instance.BlockDeviceMappings {
|
||||
if *volume.DeviceName == s.NewRootMountPoint {
|
||||
newRootVolume = *volume.Ebs.VolumeId
|
||||
}
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating snapshot of EBS Volume %s...", newRootVolume))
|
||||
description := fmt.Sprintf("Packer: %s", time.Now().String())
|
||||
|
||||
createSnapResp, err := ec2conn.CreateSnapshot(&ec2.CreateSnapshotInput{
|
||||
VolumeId: &newRootVolume,
|
||||
Description: &description,
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating snapshot: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Set the snapshot ID so we can delete it later
|
||||
s.snapshotId = *createSnapResp.SnapshotId
|
||||
ui.Message(fmt.Sprintf("Snapshot ID: %s", s.snapshotId))
|
||||
|
||||
// Wait for the snapshot to be ready
|
||||
stateChange := awscommon.StateChangeConf{
|
||||
Pending: []string{"pending"},
|
||||
StepState: state,
|
||||
Target: "completed",
|
||||
Refresh: func() (interface{}, string, error) {
|
||||
resp, err := ec2conn.DescribeSnapshots(&ec2.DescribeSnapshotsInput{SnapshotIds: []*string{&s.snapshotId}})
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if len(resp.Snapshots) == 0 {
|
||||
return nil, "", errors.New("No snapshots found.")
|
||||
}
|
||||
|
||||
s := resp.Snapshots[0]
|
||||
return s, *s.State, nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err = awscommon.WaitForState(&stateChange)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for snapshot: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state.Put("snapshot_id", s.snapshotId)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepSnapshotNewRootVolume) Cleanup(state multistep.StateBag) {
|
||||
if s.snapshotId == "" {
|
||||
return
|
||||
}
|
||||
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
|
||||
if cancelled || halted {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
ui.Say("Removing snapshot since we cancelled or halted...")
|
||||
_, err := ec2conn.DeleteSnapshot(&ec2.DeleteSnapshotInput{SnapshotId: &s.snapshotId})
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error: %s", err))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package ebssurrogate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
multierror "github.com/hashicorp/go-multierror"
|
||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
// StepSnapshotVolumes creates snapshots of the created volumes.
|
||||
//
|
||||
// Produces:
|
||||
// snapshot_ids map[string]string - IDs of the created snapshots
|
||||
type StepSnapshotVolumes struct {
|
||||
LaunchDevices []*ec2.BlockDeviceMapping
|
||||
snapshotIds map[string]string
|
||||
}
|
||||
|
||||
func (s *StepSnapshotVolumes) snapshotVolume(deviceName string, state multistep.StateBag) error {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
|
||||
var volumeId string
|
||||
for _, volume := range instance.BlockDeviceMappings {
|
||||
if *volume.DeviceName == deviceName {
|
||||
volumeId = *volume.Ebs.VolumeId
|
||||
}
|
||||
}
|
||||
if volumeId == "" {
|
||||
return fmt.Errorf("Volume ID for device %s not found", deviceName)
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating snapshot of EBS Volume %s...", volumeId))
|
||||
description := fmt.Sprintf("Packer: %s", time.Now().String())
|
||||
|
||||
createSnapResp, err := ec2conn.CreateSnapshot(&ec2.CreateSnapshotInput{
|
||||
VolumeId: &volumeId,
|
||||
Description: &description,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the snapshot ID so we can delete it later
|
||||
s.snapshotIds[deviceName] = *createSnapResp.SnapshotId
|
||||
|
||||
// Wait for the snapshot to be ready
|
||||
stateChange := awscommon.StateChangeConf{
|
||||
Pending: []string{"pending"},
|
||||
StepState: state,
|
||||
Target: "completed",
|
||||
Refresh: func() (interface{}, string, error) {
|
||||
resp, err := ec2conn.DescribeSnapshots(&ec2.DescribeSnapshotsInput{
|
||||
SnapshotIds: []*string{createSnapResp.SnapshotId},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if len(resp.Snapshots) == 0 {
|
||||
return nil, "", errors.New("No snapshots found.")
|
||||
}
|
||||
|
||||
s := resp.Snapshots[0]
|
||||
return s, *s.State, nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err = awscommon.WaitForState(&stateChange)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StepSnapshotVolumes) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
s.snapshotIds = map[string]string{}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var errs *multierror.Error
|
||||
for _, device := range s.LaunchDevices {
|
||||
wg.Add(1)
|
||||
go func(device *ec2.BlockDeviceMapping) {
|
||||
defer wg.Done()
|
||||
if err := s.snapshotVolume(*device.DeviceName, state); err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
}(device)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if errs != nil {
|
||||
state.Put("error", errs)
|
||||
ui.Error(errs.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state.Put("snapshot_ids", s.snapshotIds)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepSnapshotVolumes) Cleanup(state multistep.StateBag) {
|
||||
if len(s.snapshotIds) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
|
||||
if cancelled || halted {
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
ui.Say("Removing snapshots since we cancelled or halted...")
|
||||
for _, snapshotId := range s.snapshotIds {
|
||||
_, err := ec2conn.DeleteSnapshot(&ec2.DeleteSnapshotInput{SnapshotId: &snapshotId})
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error: %s", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue