Merge pull request #4243 from rickard-von-essen/aws-snapshot-users-groups

WIP: amazon: Add snapshot_users and snapshot_groups
This commit is contained in:
Rickard von Essen 2016-12-06 10:24:07 +01:00 committed by GitHub
commit 56f2bfa5ff
13 changed files with 210 additions and 58 deletions

View File

@ -257,10 +257,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Name: b.config.AMIName,
},
&awscommon.StepModifyAMIAttributes{
Description: b.config.AMIDescription,
Users: b.config.AMIUsers,
Groups: b.config.AMIGroups,
ProductCodes: b.config.AMIProductCodes,
Description: b.config.AMIDescription,
Users: b.config.AMIUsers,
Groups: b.config.AMIGroups,
ProductCodes: b.config.AMIProductCodes,
SnapshotUsers: b.config.SnapshotUsers,
SnapshotGroups: b.config.SnapshotGroups,
},
&awscommon.StepCreateTags{
Tags: b.config.AMITags,

View File

@ -23,6 +23,8 @@ type AMIConfig struct {
AMIEncryptBootVolume bool `mapstructure:"encrypt_boot"`
AMIKmsKeyId string `mapstructure:"kms_key_id"`
SnapshotTags map[string]string `mapstructure:"snapshot_tags"`
SnapshotUsers []string `mapstructure:"snapshot_users"`
SnapshotGroups []string `mapstructure:"snapshot_groups"`
}
func (c *AMIConfig) Prepare(ctx *interpolate.Context) []error {
@ -62,6 +64,10 @@ func (c *AMIConfig) Prepare(ctx *interpolate.Context) []error {
errs = append(errs, fmt.Errorf("Cannot share AMI with encrypted boot volume"))
}
if len(c.SnapshotUsers) > 0 && len(c.AMIKmsKeyId) == 0 && c.AMIEncryptBootVolume {
errs = append(errs, fmt.Errorf("Cannot share snapshot encrypted with default KMS key"))
}
if len(errs) > 0 {
return errs
}

View File

@ -23,6 +23,7 @@ func (s *StepAMIRegionCopy) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
amis := state.Get("amis").(map[string]string)
snapshots := state.Get("snapshots").(map[string][]string)
ami := amis[*ec2conn.Config.Region]
if len(s.Regions) == 0 {
@ -46,11 +47,12 @@ func (s *StepAMIRegionCopy) Run(state multistep.StateBag) multistep.StepAction {
go func(region string) {
defer wg.Done()
id, err := amiRegionCopy(state, s.AccessConfig, s.Name, ami, region, *ec2conn.Config.Region)
id, snapshotIds, err := amiRegionCopy(state, s.AccessConfig, s.Name, ami, region, *ec2conn.Config.Region)
lock.Lock()
defer lock.Unlock()
amis[region] = id
snapshots[region] = snapshotIds
if err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
@ -77,20 +79,21 @@ func (s *StepAMIRegionCopy) Cleanup(state multistep.StateBag) {
}
// amiRegionCopy does a copy for the given AMI to the target region and
// returns the resulting ID or error.
// returns the resulting ID and snapshot IDs, or error.
func amiRegionCopy(state multistep.StateBag, config *AccessConfig, name string, imageId string,
target string, source string) (string, error) {
target string, source string) (string, []string, error) {
snapshotIds := []string{}
// Connect to the region where the AMI will be copied to
awsConfig, err := config.Config()
if err != nil {
return "", err
return "", snapshotIds, err
}
awsConfig.Region = aws.String(target)
session, err := session.NewSession(awsConfig)
if err != nil {
return "", err
return "", snapshotIds, err
}
regionconn := ec2.New(session)
@ -101,7 +104,7 @@ func amiRegionCopy(state multistep.StateBag, config *AccessConfig, name string,
})
if err != nil {
return "", fmt.Errorf("Error Copying AMI (%s) to region (%s): %s",
return "", snapshotIds, fmt.Errorf("Error Copying AMI (%s) to region (%s): %s",
imageId, target, err)
}
@ -113,9 +116,22 @@ func amiRegionCopy(state multistep.StateBag, config *AccessConfig, name string,
}
if _, err := WaitForState(&stateChange); err != nil {
return "", fmt.Errorf("Error waiting for AMI (%s) in region (%s): %s",
return "", snapshotIds, fmt.Errorf("Error waiting for AMI (%s) in region (%s): %s",
*resp.ImageId, target, err)
}
return *resp.ImageId, nil
// Getting snapshot IDs out of the copied AMI
describeImageResp, err := regionconn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{resp.ImageId}})
if err != nil {
return "", snapshotIds, fmt.Errorf("Error describing copied AMI (%s) in region (%s): %s",
imageId, target, err)
}
for _, blockDeviceMapping := range describeImageResp.Images[0].BlockDeviceMappings {
if blockDeviceMapping.Ebs != nil {
snapshotIds = append(snapshotIds, *blockDeviceMapping.Ebs.SnapshotId)
}
}
return *resp.ImageId, snapshotIds, nil
}

View File

@ -11,16 +11,19 @@ import (
)
type StepModifyAMIAttributes struct {
Users []string
Groups []string
ProductCodes []string
Description string
Users []string
Groups []string
SnapshotUsers []string
SnapshotGroups []string
ProductCodes []string
Description string
}
func (s *StepModifyAMIAttributes) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
amis := state.Get("amis").(map[string]string)
snapshots := state.Get("snapshots").(map[string][]string)
// Determine if there is any work to do.
valid := false
@ -28,51 +31,89 @@ func (s *StepModifyAMIAttributes) Run(state multistep.StateBag) multistep.StepAc
valid = valid || (s.Users != nil && len(s.Users) > 0)
valid = valid || (s.Groups != nil && len(s.Groups) > 0)
valid = valid || (s.ProductCodes != nil && len(s.ProductCodes) > 0)
valid = valid || (s.SnapshotUsers != nil && len(s.SnapshotUsers) > 0)
valid = valid || (s.SnapshotGroups != nil && len(s.SnapshotGroups) > 0)
if !valid {
return multistep.ActionContinue
}
// Construct the modify image attribute requests we're going to make.
// We need to make each separately since the EC2 API only allows changing
// one type at a kind currently.
// Construct the modify image and snapshot attribute requests we're going
// to make. We need to make each separately since the EC2 API only allows
// changing one type at a kind currently.
options := make(map[string]*ec2.ModifyImageAttributeInput)
if s.Description != "" {
options["description"] = &ec2.ModifyImageAttributeInput{
Description: &ec2.AttributeValue{Value: &s.Description},
}
}
snapshotOptions := make(map[string]*ec2.ModifySnapshotAttributeInput)
if len(s.Groups) > 0 {
groups := make([]*string, len(s.Groups))
adds := make([]*ec2.LaunchPermission, len(s.Groups))
addsImage := make([]*ec2.LaunchPermission, len(s.Groups))
addGroups := &ec2.ModifyImageAttributeInput{
LaunchPermission: &ec2.LaunchPermissionModifications{},
}
for i, g := range s.Groups {
groups[i] = aws.String(g)
adds[i] = &ec2.LaunchPermission{
addsImage[i] = &ec2.LaunchPermission{
Group: aws.String(g),
}
}
addGroups.UserGroups = groups
addGroups.LaunchPermission.Add = adds
addGroups.UserGroups = groups
addGroups.LaunchPermission.Add = addsImage
options["groups"] = addGroups
}
if len(s.SnapshotGroups) > 0 {
groups := make([]*string, len(s.SnapshotGroups))
addsSnapshot := make([]*ec2.CreateVolumePermission, len(s.SnapshotGroups))
addSnapshotGroups := &ec2.ModifySnapshotAttributeInput{
CreateVolumePermission: &ec2.CreateVolumePermissionModifications{},
}
for i, g := range s.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(s.Users) > 0 {
users := make([]*string, len(s.Users))
adds := make([]*ec2.LaunchPermission, len(s.Users))
addsImage := make([]*ec2.LaunchPermission, len(s.Users))
for i, u := range s.Users {
users[i] = aws.String(u)
adds[i] = &ec2.LaunchPermission{UserId: aws.String(u)}
addsImage[i] = &ec2.LaunchPermission{UserId: aws.String(u)}
}
options["users"] = &ec2.ModifyImageAttributeInput{
UserIds: users,
LaunchPermission: &ec2.LaunchPermissionModifications{
Add: adds,
Add: addsImage,
},
}
}
if len(s.SnapshotUsers) > 0 {
users := make([]*string, len(s.SnapshotUsers))
addsSnapshot := make([]*ec2.CreateVolumePermission, len(s.SnapshotUsers))
for i, u := range s.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,
},
}
}
@ -87,6 +128,7 @@ func (s *StepModifyAMIAttributes) Run(state multistep.StateBag) multistep.StepAc
}
}
// Modifying image attributes
for region, ami := range amis {
ui.Say(fmt.Sprintf("Modifying attributes on AMI (%s)...", ami))
awsConfig := aws.Config{
@ -114,6 +156,30 @@ func (s *StepModifyAMIAttributes) Run(state multistep.StateBag) multistep.StepAc
}
}
// Modifying snapshot attributes
for region, region_snapshots := range snapshots {
for _, snapshot := range region_snapshots {
ui.Say(fmt.Sprintf("Modifying attributes on snapshot (%s)...", snapshot))
awsConfig := aws.Config{
Credentials: ec2conn.Config.Credentials,
Region: aws.String(region),
}
session := session.New(&awsConfig)
regionconn := ec2.New(session)
for name, input := range snapshotOptions {
ui.Message(fmt.Sprintf("Modifying: %s", name))
input.SnapshotId = &snapshot
_, err := regionconn.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

@ -178,10 +178,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Name: b.config.AMIName,
},
&awscommon.StepModifyAMIAttributes{
Description: b.config.AMIDescription,
Users: b.config.AMIUsers,
Groups: b.config.AMIGroups,
ProductCodes: b.config.AMIProductCodes,
Description: b.config.AMIDescription,
Users: b.config.AMIUsers,
Groups: b.config.AMIGroups,
ProductCodes: b.config.AMIProductCodes,
SnapshotUsers: b.config.SnapshotUsers,
SnapshotGroups: b.config.SnapshotGroups,
},
&awscommon.StepCreateTags{
Tags: b.config.AMITags,

View File

@ -66,6 +66,14 @@ func (s *stepCreateAMI) Run(state multistep.StateBag) multistep.StepAction {
}
s.image = imagesResp.Images[0]
snapshots := make(map[string][]string)
for _, blockDeviceMapping := range imagesResp.Images[0].BlockDeviceMappings {
if blockDeviceMapping.Ebs != nil {
snapshots[*ec2conn.Config.Region] = append(snapshots[*ec2conn.Config.Region], *blockDeviceMapping.Ebs.SnapshotId)
}
}
state.Put("snapshots", snapshots)
return multistep.ActionContinue
}

View File

@ -77,6 +77,22 @@ func (s *stepCreateEncryptedAMICopy) Run(state multistep.StateBag) multistep.Ste
return multistep.ActionHalt
}
// Get the encrypted AMI image, we need the new snapshot id's
encImagesResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{aws.String(*copyResp.ImageId)}})
if err != nil {
err := fmt.Errorf("Error searching for AMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
encImage := encImagesResp.Images[0]
var encSnapshots []string
for _, blockDevice := range encImage.BlockDeviceMappings {
if blockDevice.Ebs != nil && blockDevice.Ebs.SnapshotId != nil {
encSnapshots = append(encSnapshots, *blockDevice.Ebs.SnapshotId)
}
}
// Get the unencrypted AMI image
unencImagesResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{aws.String(id)}})
if err != nil {
@ -97,25 +113,26 @@ func (s *stepCreateEncryptedAMICopy) Run(state multistep.StateBag) multistep.Ste
// Remove associated unencrypted snapshot(s)
ui.Say("Deleting unencrypted snapshots")
snapshots := state.Get("snapshots").(map[string][]string)
for _, blockDevice := range unencImage.BlockDeviceMappings {
if blockDevice.Ebs != nil {
if blockDevice.Ebs.SnapshotId != nil {
ui.Message(fmt.Sprintf("Snapshot ID: %s", *blockDevice.Ebs.SnapshotId))
deleteSnapOpts := &ec2.DeleteSnapshotInput{
SnapshotId: aws.String(*blockDevice.Ebs.SnapshotId),
}
if _, err := ec2conn.DeleteSnapshot(deleteSnapOpts); err != nil {
ui.Error(fmt.Sprintf("Error deleting snapshot, may still be around: %s", err))
return multistep.ActionHalt
}
if blockDevice.Ebs != nil && blockDevice.Ebs.SnapshotId != nil {
ui.Message(fmt.Sprintf("Snapshot ID: %s", *blockDevice.Ebs.SnapshotId))
deleteSnapOpts := &ec2.DeleteSnapshotInput{
SnapshotId: aws.String(*blockDevice.Ebs.SnapshotId),
}
if _, err := ec2conn.DeleteSnapshot(deleteSnapOpts); err != nil {
ui.Error(fmt.Sprintf("Error deleting snapshot, may still be around: %s", err))
return multistep.ActionHalt
}
}
}
// Replace original AMI ID with Encrypted ID in state
amis[region] = *copyResp.ImageId
snapshots[region] = encSnapshots
state.Put("amis", amis)
state.Put("snapshots", snapshots)
imagesResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{copyResp.ImageId}})
if err != nil {

View File

@ -259,10 +259,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Name: b.config.AMIName,
},
&awscommon.StepModifyAMIAttributes{
Description: b.config.AMIDescription,
Users: b.config.AMIUsers,
Groups: b.config.AMIGroups,
ProductCodes: b.config.AMIProductCodes,
Description: b.config.AMIDescription,
Users: b.config.AMIUsers,
Groups: b.config.AMIGroups,
ProductCodes: b.config.AMIProductCodes,
SnapshotUsers: b.config.SnapshotUsers,
SnapshotGroups: b.config.SnapshotGroups,
},
&awscommon.StepCreateTags{
Tags: b.config.AMITags,

View File

@ -207,6 +207,17 @@ each category, the available configuration keys are alphabetized.
- `skip_region_validation` (boolean) - Set to true if you want to skip
validation of the `ami_regions` configuration option. Default `false`.
- `snapshot_tags` (object of key/value strings) - Tags to apply to snapshot.
They will override AMI tags if already applied to snapshot.
- `snapshot_groups` (array of strings) - A list of groups that have access to
create volumes from the snapshot(s). By default no groups have permission to create
volumes form the snapshot(s). `all` will make the snapshot publicly accessible.
- `snapshot_users` (array of strings) - 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).
- `source_ami_filter` (object) - Filters used to populate the `source_ami` field.
Example:
@ -237,9 +248,6 @@ each category, the available configuration keys are alphabetized.
- `most_recent` (bool) - Selects the newest created image when true.
This is most useful for selecting a daily distro build.
- `snapshot_tags` (object of key/value strings) - Tags to apply to snapshot.
They will override AMI tags if already applied to snapshot.
- `tags` (object of key/value strings) - Tags applied to the AMI.
## Basic Example

View File

@ -114,9 +114,21 @@ builder.
described above. Note that if this is specified, you must omit the
`security_group_id`.
- `shutdown_behaviour` (string) - Automatically terminate instances on shutdown
incase packer exits ungracefully. Possible values are "stop" and "terminate",
default is stop.
- `skip_region_validation` (boolean) - Set to true if you want to skip
validation of the region configuration option. Defaults to false.
- `snapshot_groups` (array of strings) - A list of groups that have access to
create volumes from the snapshot(s). By default no groups have permission to create
volumes form the snapshot(s). `all` will make the snapshot publicly accessible.
- `snapshot_users` (array of strings) - 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).
- `source_ami_filter` (object) - Filters used to populate the `source_ami` field.
Example:
@ -196,10 +208,6 @@ builder.
- `windows_password_timeout` (string) - The timeout for waiting for a Windows
password for Windows instances. Defaults to 20 minutes. Example value: "10m"
- `shutdown_behaviour` (string) - Automatically terminate instances on shutdown
incase packer exits ungracefully. Possible values are "stop" and "terminate",
default is stop.
## Basic Example
```

View File

@ -190,9 +190,24 @@ builder.
described above. Note that if this is specified, you must omit the
`security_group_id`.
- `shutdown_behaviour` (string) - Automatically terminate instances on shutdown
incase packer exits ungracefully. Possible values are "stop" and "terminate",
default is stop.
- `skip_region_validation` (boolean) - Set to true if you want to skip
validation of the region configuration option. Default `false`.
- `snapshot_groups` (array of strings) - A list of groups that have access to
create volumes from the snapshot(s). By default no groups have permission to create
volumes form the snapshot(s). `all` will make the snapshot publicly accessible.
- `snapshot_users` (array of strings) - 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_tags` (object of key/value strings) - Tags to apply to snapshot.
They will override AMI tags if already applied to snapshot.
- `source_ami_filter` (object) - Filters used to populate the `source_ami` field.
Example:
@ -223,9 +238,6 @@ builder.
- `most_recent` (bool) - Selects the newest created image when true.
This is most useful for selecting a daily distro build.
- `snapshot_tags` (object of key/value strings) - Tags to apply to snapshot.
They will override AMI tags if already applied to snapshot.
- `spot_price` (string) - The maximum hourly price to pay for a spot instance
to create the AMI. Spot instances are a type of instance that EC2 starts
when the current spot price is less than the maximum price you specify. Spot
@ -287,10 +299,6 @@ builder.
- `windows_password_timeout` (string) - The timeout for waiting for a Windows
password for Windows instances. Defaults to 20 minutes. Example value: "10m"
- `shutdown_behaviour` (string) - Automatically terminate instances on shutdown
incase packer exits ungracefully. Possible values are "stop" and "terminate",
default is stop.
## Basic Example
Here is a basic example. You will need to provide access keys, and may need to change the AMI IDs according to what images exist at the time the template is run:

View File

@ -207,6 +207,14 @@ builder.
- `skip_region_validation` (boolean) - Set to true if you want to skip
validation of the region configuration option. Default `false`.
- `snapshot_groups` (array of strings) - A list of groups that have access to
create volumes from the snapshot(s). By default no groups have permission to create
volumes form the snapshot(s). `all` will make the snapshot publicly accessible.
- `snapshot_users` (array of strings) - 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).
- `source_ami_filter` (object) - Filters used to populate the `source_ami` field.
Example:

View File

@ -129,6 +129,7 @@ Packer to work:
"ec2:GetPasswordData",
"ec2:ModifyImageAttribute",
"ec2:ModifyInstanceAttribute",
"ec2:ModifySnapshotAttribute",
"ec2:RegisterImage",
"ec2:RunInstances",
"ec2:StopInstances",