From 482a2c8232e50892c386868c1ed7be408ca7b868 Mon Sep 17 00:00:00 2001 From: Tim Dawson Date: Wed, 15 Jul 2020 23:30:36 +1200 Subject: [PATCH] Adding ebs-volume snapshot creation --- builder/amazon/ebsvolume/block_device.go | 2 + builder/amazon/ebsvolume/builder.go | 4 + builder/amazon/ebsvolume/builder.hcl2spec.go | 2 + .../ebsvolume/step_snapshot_ebs_volumes.go | 83 +++++++++++++++++++ .../ebsvolume/BlockDevice-not-required.mdx | 2 + 5 files changed, 93 insertions(+) create mode 100644 builder/amazon/ebsvolume/step_snapshot_ebs_volumes.go diff --git a/builder/amazon/ebsvolume/block_device.go b/builder/amazon/ebsvolume/block_device.go index 49d1067c8..3c8480aa1 100644 --- a/builder/amazon/ebsvolume/block_device.go +++ b/builder/amazon/ebsvolume/block_device.go @@ -11,6 +11,8 @@ import ( type BlockDevice struct { awscommon.BlockDevice `mapstructure:",squash"` + // Create a Snapshot of this Volume and copy all tags. + SnapshotVolume bool `mapstructure:"snapshot_volume" required:"false"` // Key/value pair tags to apply to the volume. These are retained after the builder // completes. This is a [template engine](/docs/templates/legacy_json_templates/engine), see // [Build template data](#build-template-data) for more information. diff --git a/builder/amazon/ebsvolume/builder.go b/builder/amazon/ebsvolume/builder.go index 0afe9eb71..2a04e3ff2 100644 --- a/builder/amazon/ebsvolume/builder.go +++ b/builder/amazon/ebsvolume/builder.go @@ -318,6 +318,10 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) EnableAMISriovNetSupport: b.config.AMISriovNetSupport, EnableAMIENASupport: b.config.AMIENASupport, }, + &stepSnapshotEBSVolumes{ + VolumeMapping: b.config.VolumeMappings, + Ctx: b.config.ctx, + }, } // Run! diff --git a/builder/amazon/ebsvolume/builder.hcl2spec.go b/builder/amazon/ebsvolume/builder.hcl2spec.go index 241b45fd9..c4207ee22 100644 --- a/builder/amazon/ebsvolume/builder.hcl2spec.go +++ b/builder/amazon/ebsvolume/builder.hcl2spec.go @@ -23,6 +23,7 @@ type FlatBlockDevice struct { VolumeType *string `mapstructure:"volume_type" required:"false" cty:"volume_type" hcl:"volume_type"` VolumeSize *int64 `mapstructure:"volume_size" required:"false" cty:"volume_size" hcl:"volume_size"` KmsKeyId *string `mapstructure:"kms_key_id" required:"false" cty:"kms_key_id" hcl:"kms_key_id"` + SnapshotVolume *bool `mapstructure:"snapshot_volume" required:"false" cty:"snapshot_volume" hcl:"snapshot_volume"` Tags map[string]string `mapstructure:"tags" required:"false" cty:"tags" hcl:"tags"` Tag []config.FlatKeyValue `mapstructure:"tag" required:"false" cty:"tag" hcl:"tag"` } @@ -50,6 +51,7 @@ func (*FlatBlockDevice) HCL2Spec() map[string]hcldec.Spec { "volume_type": &hcldec.AttrSpec{Name: "volume_type", Type: cty.String, Required: false}, "volume_size": &hcldec.AttrSpec{Name: "volume_size", Type: cty.Number, Required: false}, "kms_key_id": &hcldec.AttrSpec{Name: "kms_key_id", Type: cty.String, Required: false}, + "snapshot_volume": &hcldec.AttrSpec{Name: "snapshot_volume", Type: cty.Bool, Required: false}, "tags": &hcldec.AttrSpec{Name: "tags", Type: cty.Map(cty.String), Required: false}, "tag": &hcldec.BlockListSpec{TypeName: "tag", Nested: hcldec.ObjectSpec((*config.FlatKeyValue)(nil).HCL2Spec())}, } diff --git a/builder/amazon/ebsvolume/step_snapshot_ebs_volumes.go b/builder/amazon/ebsvolume/step_snapshot_ebs_volumes.go new file mode 100644 index 000000000..417ce1581 --- /dev/null +++ b/builder/amazon/ebsvolume/step_snapshot_ebs_volumes.go @@ -0,0 +1,83 @@ +package ebsvolume + +import ( + "context" + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "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" + "github.com/hashicorp/packer/template/interpolate" +) + +type stepSnapshotEBSVolumes struct { + VolumeMapping []BlockDevice + Ctx interpolate.Context +} + +func (s *stepSnapshotEBSVolumes) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + ec2conn := state.Get("ec2").(*ec2.EC2) + instance := state.Get("instance").(*ec2.Instance) + ui := state.Get("ui").(packer.Ui) + snapshotsIds := make([]string, 0) + + for _, instanceBlockDevices := range instance.BlockDeviceMappings { + for _, configVolumeMapping := range s.VolumeMapping { + //Find the config entry for the instance blockDevice + if configVolumeMapping.DeviceName == *instanceBlockDevices.DeviceName { + if configVolumeMapping.SnapshotVolume != true { + continue + } + + ui.Message(fmt.Sprintf("Compiling list of tags to apply to snapshot from Volume %s...", *instanceBlockDevices.DeviceName)) + tags, err := awscommon.TagMap(configVolumeMapping.Tags).EC2Tags(s.Ctx, *ec2conn.Config.Region, state) + if err != nil { + err := fmt.Errorf("Error generating tags for device %s: %s", *instanceBlockDevices.DeviceName, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + tagSpec := &ec2.TagSpecification{ + ResourceType: aws.String("snapshot"), + Tags: tags, + } + + input := &ec2.CreateSnapshotInput{ + VolumeId: instanceBlockDevices.Ebs.VolumeId, + TagSpecifications: []*ec2.TagSpecification{tagSpec}, + } + snapshot, err := ec2conn.CreateSnapshot(input) + if err != nil { + err := fmt.Errorf("Error generating snapsot for volume %s: %s", *instanceBlockDevices.Ebs.VolumeId, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + snapshotsIds = append(snapshotsIds, *snapshot.SnapshotId) + } + } + } + + ui.Say("Waiting for Snapshots to become ready...") + for _, snapID := range snapshotsIds { + ui.Message(fmt.Sprintf("Waiting for %s to be ready.", snapID)) + err := awscommon.WaitUntilSnapshotDone(ctx, ec2conn, snapID) + if err != nil { + err = fmt.Errorf("Error waiting for snapsot to become ready %s", err) + state.Put("error", err) + ui.Error(err.Error()) + ui.Message("Failed to wait") + return multistep.ActionHalt + } + ui.Message(fmt.Sprintf("Snapshot Ready: %s", snapID)) + } + + return multistep.ActionContinue +} + +func (s *stepSnapshotEBSVolumes) Cleanup(state multistep.StateBag) { + // No cleanup... +} diff --git a/website/content/partials/builder/amazon/ebsvolume/BlockDevice-not-required.mdx b/website/content/partials/builder/amazon/ebsvolume/BlockDevice-not-required.mdx index 9bccc2818..c00a6e377 100644 --- a/website/content/partials/builder/amazon/ebsvolume/BlockDevice-not-required.mdx +++ b/website/content/partials/builder/amazon/ebsvolume/BlockDevice-not-required.mdx @@ -1,5 +1,7 @@ +- `snapshot_volume` (bool) - Create a Snapshot of this Volume and copy all tags. + - `tags` (map[string]string) - Key/value pair tags to apply to the volume. These are retained after the builder completes. This is a [template engine](/docs/templates/legacy_json_templates/engine), see [Build template data](#build-template-data) for more information.