Refactored SnapshotConfig

Added Group and user permission to each snapshot
This commit is contained in:
Tim Dawson 2020-07-16 15:21:34 +12:00
parent 482a2c8232
commit 7f0b41bb0e
16 changed files with 162 additions and 59 deletions

View File

@ -135,26 +135,8 @@ type AMIConfig struct {
// the intermediary AMI into any regions provided in `ami_regions`, then
// delete the intermediary AMI. Default `false`.
AMISkipBuildRegion bool `mapstructure:"skip_save_build_region"`
// Key/value pair tags to apply to snapshot. They will override AMI tags if
// already applied to snapshot. This is a [template
// engine](/docs/templates/legacy_json_templates/engine), see [Build template
// data](#build-template-data) for more information.
SnapshotTags map[string]string `mapstructure:"snapshot_tags" required:"false"`
// Same as [`snapshot_tags`](#snapshot_tags) but defined as a singular
// repeatable block containing a `key` and a `value` field. In HCL2 mode the
// [`dynamic_block`](/docs/templates/hcl_templates/expressions#dynamic-blocks)
// will allow you to create those programatically.
SnapshotTag config.KeyValues `mapstructure:"snapshot_tag" required:"false"`
// A list of account IDs that have
// access to create volumes from the snapshot(s). By default no additional
// users other than the user creating the AMI has permissions to create
// volumes from the backing snapshot(s).
SnapshotUsers []string `mapstructure:"snapshot_users" required:"false"`
// A list of groups that have access to
// create volumes from the snapshot(s). By default no groups have permission
// to create volumes from the snapshot(s). all will make the snapshot
// publicly accessible.
SnapshotGroups []string `mapstructure:"snapshot_groups" required:"false"`
SnapshotConfig `mapstructure:",squash"`
}
func stringInSlice(s []string, searchstr string) bool {

View File

@ -0,0 +1,29 @@
//go:generate struct-markdown
package common
import "github.com/hashicorp/packer-plugin-sdk/template/config"
// SnapshotConfig is for common configuration related to creating AMIs.
type SnapshotConfig struct {
// Key/value pair tags to apply to snapshot. They will override AMI tags if
// already applied to snapshot. This is a [template
// engine](/docs/templates/legacy_json_templates/engine), see [Build template
// data](#build-template-data) for more information.
SnapshotTags map[string]string `mapstructure:"snapshot_tags" required:"false"`
// Same as [`snapshot_tags`](#snapshot_tags) but defined as a singular
// repeatable block containing a `key` and a `value` field. In HCL2 mode the
// [`dynamic_block`](/docs/templates/hcl_templates/expressions#dynamic-blocks)
// will allow you to create those programatically.
SnapshotTag config.KeyValues `mapstructure:"snapshot_tag" required:"false"`
// A list of account IDs that have
// access to create volumes from the snapshot(s). By default no additional
// users other than the user creating the AMI has permissions to create
// volumes from the backing snapshot(s).
SnapshotUsers []string `mapstructure:"snapshot_users" required:"false"`
// A list of groups that have access to
// create volumes from the snapshot(s). By default no groups have permission
// to create volumes from the snapshot(s). all will make the snapshot
// publicly accessible.
SnapshotGroups []string `mapstructure:"snapshot_groups" required:"false"`
}

View File

@ -11,8 +11,6 @@ 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.
@ -22,6 +20,11 @@ type BlockDevice struct {
// [`dynamic_block`](/docs/templates/hcl_templates/expressions#dynamic-blocks)
// will allow you to create those programatically.
Tag config.KeyValues `mapstructure:"tag" required:"false"`
// Create a Snapshot of this Volume.
SnapshotVolume bool `mapstructure:"snapshot_volume" required:"false"`
awscommon.SnapshotConfig `mapstructure:",squash"`
}
type BlockDevices []BlockDevice
@ -40,6 +43,7 @@ func (bds BlockDevices) Prepare(ctx *interpolate.Context) (errs []error) {
for _, block := range bds {
errs = append(errs, block.Tag.CopyOn(&block.Tags)...)
errs = append(errs, block.SnapshotTag.CopyOn(&block.SnapshotTags)...)
if err := block.Prepare(ctx); err != nil {
errs = append(errs, err)

View File

@ -23,9 +23,13 @@ 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"`
SnapshotVolume *bool `mapstructure:"snapshot_volume" required:"false" cty:"snapshot_volume" hcl:"snapshot_volume"`
SnapshotTags map[string]string `mapstructure:"snapshot_tags" required:"false" cty:"snapshot_tags" hcl:"snapshot_tags"`
SnapshotTag []config.FlatKeyValue `mapstructure:"snapshot_tag" required:"false" cty:"snapshot_tag" hcl:"snapshot_tag"`
SnapshotUsers []string `mapstructure:"snapshot_users" required:"false" cty:"snapshot_users" hcl:"snapshot_users"`
SnapshotGroups []string `mapstructure:"snapshot_groups" required:"false" cty:"snapshot_groups" hcl:"snapshot_groups"`
}
// FlatMapstructure returns a new FlatBlockDevice.
@ -51,9 +55,13 @@ 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())},
"snapshot_volume": &hcldec.AttrSpec{Name: "snapshot_volume", Type: cty.Bool, Required: false},
"snapshot_tags": &hcldec.AttrSpec{Name: "snapshot_tags", Type: cty.Map(cty.String), Required: false},
"snapshot_tag": &hcldec.BlockListSpec{TypeName: "snapshot_tag", Nested: hcldec.ObjectSpec((*config.FlatKeyValue)(nil).HCL2Spec())},
"snapshot_users": &hcldec.AttrSpec{Name: "snapshot_users", Type: cty.List(cty.String), Required: false},
"snapshot_groups": &hcldec.AttrSpec{Name: "snapshot_groups", Type: cty.List(cty.String), Required: false},
}
return s
}

View File

@ -14,31 +14,35 @@ import (
type stepSnapshotEBSVolumes struct {
VolumeMapping []BlockDevice
Ctx interpolate.Context
//Map of SnapshotID: BlockDevice, Where *BlockDevice is in VolumeMapping
SnapshotMap map[string]*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)
s.SnapshotMap = make(map[string]*BlockDevice)
for _, instanceBlockDevices := range instance.BlockDeviceMappings {
for _, configVolumeMapping := range s.VolumeMapping {
//Find the config entry for the instance blockDevice
if configVolumeMapping.DeviceName == *instanceBlockDevices.DeviceName {
//Skip Volumes that are not set to create snapshot
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)
tags, err := awscommon.TagMap(configVolumeMapping.SnapshotTags).EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
if err != nil {
err := fmt.Errorf("Error generating tags for device %s: %s", *instanceBlockDevices.DeviceName, err)
err := fmt.Errorf("Error generating tags for snapshot %s: %s", *instanceBlockDevices.DeviceName, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
tags.Report(ui)
tagSpec := &ec2.TagSpecification{
ResourceType: aws.String("snapshot"),
@ -46,23 +50,31 @@ func (s *stepSnapshotEBSVolumes) Run(ctx context.Context, state multistep.StateB
}
input := &ec2.CreateSnapshotInput{
VolumeId: instanceBlockDevices.Ebs.VolumeId,
VolumeId: aws.String(*instanceBlockDevices.Ebs.VolumeId),
TagSpecifications: []*ec2.TagSpecification{tagSpec},
}
//Dont try to set an empty tag spec
if len(tags) == 0 {
input.TagSpecifications = nil
}
ui.Message(fmt.Sprintf("Requesting snapshot of volume: %s...", *instanceBlockDevices.Ebs.VolumeId))
snapshot, err := ec2conn.CreateSnapshot(input)
if err != nil {
if err != nil || snapshot == 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.Message(fmt.Sprintf("Requested Snapshot of Volume %s: %s", *instanceBlockDevices.Ebs.VolumeId, *snapshot.SnapshotId))
s.SnapshotMap[*snapshot.SnapshotId] = &configVolumeMapping
}
}
}
ui.Say("Waiting for Snapshots to become ready...")
for _, snapID := range snapshotsIds {
for snapID := range s.SnapshotMap {
ui.Message(fmt.Sprintf("Waiting for %s to be ready.", snapID))
err := awscommon.WaitUntilSnapshotDone(ctx, ec2conn, snapID)
if err != nil {
@ -75,6 +87,62 @@ func (s *stepSnapshotEBSVolumes) Run(ctx context.Context, state multistep.StateB
ui.Message(fmt.Sprintf("Snapshot Ready: %s", snapID))
}
//Attach User and Group permissions to snapshots
ui.Say("Setting User/Group Permissions for Snapshots...")
for snapID, bd := range s.SnapshotMap {
snapshotOptions := make(map[string]*ec2.ModifySnapshotAttributeInput)
if len(bd.SnapshotGroups) > 0 {
groups := make([]*string, len(bd.SnapshotGroups))
addsSnapshot := make([]*ec2.CreateVolumePermission, len(bd.SnapshotGroups))
addSnapshotGroups := &ec2.ModifySnapshotAttributeInput{
CreateVolumePermission: &ec2.CreateVolumePermissionModifications{},
}
for i, g := range bd.SnapshotGroups {
groups[i] = aws.String(g)
addsSnapshot[i] = &ec2.CreateVolumePermission{
Group: aws.String(g),
}
}
addSnapshotGroups.GroupNames = groups
addSnapshotGroups.CreateVolumePermission.Add = addsSnapshot
snapshotOptions["groups"] = addSnapshotGroups
}
if len(bd.SnapshotUsers) > 0 {
users := make([]*string, len(bd.SnapshotUsers))
addsSnapshot := make([]*ec2.CreateVolumePermission, len(bd.SnapshotUsers))
for i, u := range bd.SnapshotUsers {
users[i] = aws.String(u)
addsSnapshot[i] = &ec2.CreateVolumePermission{UserId: aws.String(u)}
}
snapshotOptions["users"] = &ec2.ModifySnapshotAttributeInput{
UserIds: users,
CreateVolumePermission: &ec2.CreateVolumePermissionModifications{
Add: addsSnapshot,
},
}
}
//Todo: Copy to other regions and repeat this block in all regions.
for name, input := range snapshotOptions {
ui.Message(fmt.Sprintf("Modifying: %s", name))
input.SnapshotId = &snapID
_, err := ec2conn.ModifySnapshotAttribute(input)
if err != nil {
err := fmt.Errorf("Error modify snapshot attributes: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
}
return multistep.ActionContinue
}

View File

@ -3,7 +3,7 @@
package common
import (
"os"
"io/fs"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
@ -13,7 +13,7 @@ import (
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatOutputConfig struct {
OutputDir *string `mapstructure:"output_directory" required:"false" cty:"output_directory" hcl:"output_directory"`
DirPerm *os.FileMode `mapstructure:"directory_permission" required:"false" cty:"directory_permission" hcl:"directory_permission"`
DirPerm *fs.FileMode `mapstructure:"directory_permission" required:"false" cty:"directory_permission" hcl:"directory_permission"`
}
// FlatMapstructure returns a new FlatOutputConfig.

View File

@ -3,7 +3,7 @@
package common
import (
"os"
"io/fs"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
@ -17,7 +17,7 @@ type FlatExportConfig struct {
Images *bool `mapstructure:"images" cty:"images" hcl:"images"`
Manifest *string `mapstructure:"manifest" cty:"manifest" hcl:"manifest"`
OutputDir *string `mapstructure:"output_directory" required:"false" cty:"output_directory" hcl:"output_directory"`
DirPerm *os.FileMode `mapstructure:"directory_permission" required:"false" cty:"directory_permission" hcl:"directory_permission"`
DirPerm *fs.FileMode `mapstructure:"directory_permission" required:"false" cty:"directory_permission" hcl:"directory_permission"`
Options []string `mapstructure:"options" cty:"options" hcl:"options"`
}

View File

@ -85,6 +85,8 @@ builders.
@include 'builder/amazon/common/AMIConfig-not-required.mdx'
@include 'builder/amazon/common/SnapshotConfig-not-required.mdx'
### Block Devices Configuration
Block devices can be nested in the

View File

@ -59,6 +59,8 @@ necessary for this build to succeed and can be found further down the page.
@include 'builder/amazon/common/AMIConfig-not-required.mdx'
@include 'builder/amazon/common/SnapshotConfig-not-required.mdx'
### Access Configuration
#### Required:

View File

@ -55,6 +55,8 @@ necessary for this build to succeed and can be found further down the page.
@include 'builder/amazon/common/AMIConfig-not-required.mdx'
@include 'builder/amazon/common/SnapshotConfig-not-required.mdx'
### Access Configuration
#### Required:

View File

@ -96,6 +96,8 @@ Block devices can be nested in the
@include 'builder/amazon/ebsvolume/BlockDevice-not-required.mdx'
@include 'builder/amazon/common/SnapshotConfig-not-required.mdx'
### Run Configuration
#### Required:

View File

@ -44,7 +44,7 @@ filesystem and data.
- [amazon-ebsvolume](/docs/builders/amazon/ebsvolume) - Create EBS
volumes by launching a source AMI with block devices mapped. Provision the
instance, then destroy it, retaining the EBS volumes.
instance, then destroy it, retaining the EBS volumes and or Snapshot.
<!-- TODO: fix -->

View File

@ -116,23 +116,3 @@
which it will not convert to an AMI in the build region. It will copy
the intermediary AMI into any regions provided in `ami_regions`, then
delete the intermediary AMI. Default `false`.
- `snapshot_tags` (map[string]string) - Key/value pair tags to apply to snapshot. They will override AMI tags if
already applied to snapshot. This is a [template
engine](/docs/templates/legacy_json_templates/engine), see [Build template
data](#build-template-data) for more information.
- `snapshot_tag` ([]{key string, value string}) - Same as [`snapshot_tags`](#snapshot_tags) but defined as a singular
repeatable block containing a `key` and a `value` field. In HCL2 mode the
[`dynamic_block`](/docs/templates/hcl_templates/expressions#dynamic-blocks)
will allow you to create those programatically.
- `snapshot_users` ([]string) - A list of account IDs that have
access to create volumes from the snapshot(s). By default no additional
users other than the user creating the AMI has permissions to create
volumes from the backing snapshot(s).
- `snapshot_groups` ([]string) - A list of groups that have access to
create volumes from the snapshot(s). By default no groups have permission
to create volumes from the snapshot(s). all will make the snapshot
publicly accessible.

View File

@ -0,0 +1,21 @@
<!-- Code generated from the comments of the SnapshotConfig struct in builder/amazon/common/snapshot_config.go; DO NOT EDIT MANUALLY -->
- `snapshot_tags` (map[string]string) - Key/value pair tags to apply to snapshot. They will override AMI tags if
already applied to snapshot. This is a [template
engine](/docs/templates/legacy_json_templates/engine), see [Build template
data](#build-template-data) for more information.
- `snapshot_tag` ([]{key string, value string}) - Same as [`snapshot_tags`](#snapshot_tags) but defined as a singular
repeatable block containing a `key` and a `value` field. In HCL2 mode the
[`dynamic_block`](/docs/templates/hcl_templates/expressions#dynamic-blocks)
will allow you to create those programatically.
- `snapshot_users` ([]string) - A list of account IDs that have
access to create volumes from the snapshot(s). By default no additional
users other than the user creating the AMI has permissions to create
volumes from the backing snapshot(s).
- `snapshot_groups` ([]string) - A list of groups that have access to
create volumes from the snapshot(s). By default no groups have permission
to create volumes from the snapshot(s). all will make the snapshot
publicly accessible.

View File

@ -0,0 +1,3 @@
<!-- Code generated from the comments of the SnapshotConfig struct in builder/amazon/common/snapshot_config.go; DO NOT EDIT MANUALLY -->
SnapshotConfig is for common configuration related to creating AMIs.

View File

@ -1,7 +1,5 @@
<!-- Code generated from the comments of the BlockDevice struct in builder/amazon/ebsvolume/block_device.go; DO NOT EDIT MANUALLY -->
- `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.
@ -10,3 +8,5 @@
containing a `key` and a `value` field. In HCL2 mode the
[`dynamic_block`](/docs/templates/hcl_templates/expressions#dynamic-blocks)
will allow you to create those programatically.
- `snapshot_volume` (bool) - Create a Snapshot of this Volume.