packer-cn/builder/amazon/ebssurrogate/step_snapshot_volumes.go
Joseph Wright cb3699a584 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.
2018-03-25 19:37:44 -04:00

130 lines
3.3 KiB
Go

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))
}
}
}
}