Modify ebssurrogate builder to snapshot all launch devices
Documentation for ebssurrogate states that all of the devices in `launch_block_device_mappings` are snapshotted and included in the image. In fact, only the device that was designated as the root device was snapshotted. This patch modifies the builder to create snapshots of all the devices and include them in the image. This allows creating images with separate filesystems preconfigured, rather than having to add volumes to `ami_block_device_mappings` and configure them after boot.
This commit is contained in:
parent
b5635ac393
commit
cb3699a584
|
@ -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{
|
||||
&ec2.BlockDeviceMapping{
|
||||
Ebs: &ec2.EbsBlockDevice{},
|
||||
DeviceName: aws.String(sourceDeviceName),
|
||||
},
|
||||
},
|
||||
allDevices: []*ec2.BlockDeviceMapping{
|
||||
&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{
|
||||
&ec2.BlockDeviceMapping{
|
||||
Ebs: &ec2.EbsBlockDevice{},
|
||||
DeviceName: aws.String(sourceDeviceName),
|
||||
},
|
||||
},
|
||||
allDevices: []*ec2.BlockDeviceMapping{
|
||||
&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{
|
||||
&ec2.BlockDeviceMapping{
|
||||
Ebs: &ec2.EbsBlockDevice{},
|
||||
DeviceName: aws.String("/dev/xvdg"),
|
||||
},
|
||||
},
|
||||
launchDevices: []*ec2.BlockDeviceMapping{
|
||||
&ec2.BlockDeviceMapping{
|
||||
Ebs: &ec2.EbsBlockDevice{},
|
||||
DeviceName: aws.String(sourceDeviceName),
|
||||
},
|
||||
},
|
||||
allDevices: []*ec2.BlockDeviceMapping{
|
||||
&ec2.BlockDeviceMapping{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
SnapshotId: aws.String("snap-0123456789abcdef1"),
|
||||
},
|
||||
DeviceName: aws.String(rootDeviceName),
|
||||
},
|
||||
&ec2.BlockDeviceMapping{
|
||||
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{
|
||||
&ec2.BlockDeviceMapping{
|
||||
Ebs: &ec2.EbsBlockDevice{},
|
||||
DeviceName: aws.String(sourceDeviceName),
|
||||
},
|
||||
&ec2.BlockDeviceMapping{
|
||||
Ebs: &ec2.EbsBlockDevice{},
|
||||
DeviceName: aws.String("/dev/xvdg"),
|
||||
},
|
||||
},
|
||||
allDevices: []*ec2.BlockDeviceMapping{
|
||||
&ec2.BlockDeviceMapping{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
SnapshotId: aws.String("snap-0123456789abcdef1"),
|
||||
},
|
||||
DeviceName: aws.String(rootDeviceName),
|
||||
},
|
||||
&ec2.BlockDeviceMapping{
|
||||
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{
|
||||
&ec2.BlockDeviceMapping{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
Encrypted: aws.Bool(true),
|
||||
},
|
||||
DeviceName: aws.String(sourceDeviceName),
|
||||
},
|
||||
&ec2.BlockDeviceMapping{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
Encrypted: aws.Bool(true),
|
||||
},
|
||||
DeviceName: aws.String("/dev/xvdg"),
|
||||
},
|
||||
},
|
||||
allDevices: []*ec2.BlockDeviceMapping{
|
||||
&ec2.BlockDeviceMapping{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
SnapshotId: aws.String("snap-0123456789abcdef1"),
|
||||
// Encrypted: true stripped from snapshotted devices
|
||||
},
|
||||
DeviceName: aws.String(rootDeviceName),
|
||||
},
|
||||
&ec2.BlockDeviceMapping{
|
||||
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{
|
||||
&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{
|
||||
&ec2.BlockDeviceMapping{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
Encrypted: aws.Bool(true),
|
||||
},
|
||||
DeviceName: aws.String(sourceDeviceName),
|
||||
},
|
||||
&ec2.BlockDeviceMapping{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
Encrypted: aws.Bool(true),
|
||||
},
|
||||
DeviceName: aws.String("/dev/xvdg"),
|
||||
},
|
||||
},
|
||||
allDevices: []*ec2.BlockDeviceMapping{
|
||||
&ec2.BlockDeviceMapping{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
Encrypted: aws.Bool(true),
|
||||
KmsKeyId: aws.String("keyId"),
|
||||
},
|
||||
DeviceName: aws.String(sourceDeviceName),
|
||||
},
|
||||
&ec2.BlockDeviceMapping{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
SnapshotId: aws.String("snap-0123456789abcdef1"),
|
||||
},
|
||||
DeviceName: aws.String(rootDeviceName),
|
||||
},
|
||||
&ec2.BlockDeviceMapping{
|
||||
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