Merge pull request #4015 from artburkart:closes_3908

Closes #3908: Adds snapshot tag overrides
This commit is contained in:
Rickard von Essen 2016-11-26 17:54:00 +01:00
commit a09f20f996
10 changed files with 164 additions and 90 deletions

View File

@ -262,7 +262,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ProductCodes: b.config.AMIProductCodes,
},
&awscommon.StepCreateTags{
Tags: b.config.AMITags,
Tags: b.config.AMITags,
SnapshotTags: b.config.SnapshotTags,
},
)

View File

@ -20,6 +20,7 @@ type AMIConfig struct {
AMIEnhancedNetworking bool `mapstructure:"enhanced_networking"`
AMIForceDeregister bool `mapstructure:"force_deregister"`
AMIEncryptBootVolume bool `mapstructure:"encrypt_boot"`
SnapshotTags map[string]string `mapstructure:"snapshot_tags"`
}
func (c *AMIConfig) Prepare(ctx *interpolate.Context) []error {

View File

@ -79,6 +79,19 @@ func (a *Artifact) Destroy() error {
}
regionConn := ec2.New(session)
// Get image metadata
imageResp, err := regionConn.DescribeImages(&ec2.DescribeImagesInput{
ImageIds: []*string{&imageId},
})
if err != nil {
errors = append(errors, err)
}
if len(imageResp.Images) == 0 {
err := fmt.Errorf("Error retrieving details for AMI (%s), no images found", imageId)
errors = append(errors, err)
}
// Deregister ami
input := &ec2.DeregisterImageInput{
ImageId: &imageId,
}

View File

@ -13,7 +13,8 @@ import (
)
type StepCreateTags struct {
Tags map[string]string
Tags map[string]string
SnapshotTags map[string]string
}
func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction {
@ -21,88 +22,100 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
amis := state.Get("amis").(map[string]string)
if len(s.Tags) > 0 {
for region, ami := range amis {
ui.Say(fmt.Sprintf("Adding tags to AMI (%s)...", ami))
if len(s.Tags) == 0 && len(s.SnapshotTags) == 0 {
return multistep.ActionContinue
}
var ec2Tags []*ec2.Tag
for key, value := range s.Tags {
ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"", key, value))
ec2Tags = append(ec2Tags, &ec2.Tag{
Key: aws.String(key),
Value: aws.String(value),
})
// Adds tags to AMIs and snapshots
for region, ami := range amis {
ui.Say(fmt.Sprintf("Adding tags to AMI (%s)...", ami))
// Convert tags to ec2.Tag format
amiTags := ConvertToEC2Tags(s.Tags, ui)
ui.Say(fmt.Sprintf("Snapshot tags:"))
snapshotTags := ConvertToEC2Tags(s.SnapshotTags, ui)
// Declare list of resources to tag
awsConfig := aws.Config{
Credentials: ec2conn.Config.Credentials,
Region: aws.String(region),
}
session, err := session.NewSession(&awsConfig)
if err != nil {
err := fmt.Errorf("Error creating AWS session: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
regionconn := ec2.New(session)
// Retrieve image list for given AMI
resourceIds := []*string{&ami}
imageResp, err := regionconn.DescribeImages(&ec2.DescribeImagesInput{
ImageIds: resourceIds,
})
if err != nil {
err := fmt.Errorf("Error retrieving details for AMI (%s): %s", ami, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(imageResp.Images) == 0 {
err := fmt.Errorf("Error retrieving details for AMI (%s), no images found", ami)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
image := imageResp.Images[0]
snapshotIds := []*string{}
// Add only those with a Snapshot ID, i.e. not Ephemeral
for _, device := range image.BlockDeviceMappings {
if device.Ebs != nil && device.Ebs.SnapshotId != nil {
ui.Say(fmt.Sprintf("Tagging snapshot: %s", *device.Ebs.SnapshotId))
resourceIds = append(resourceIds, device.Ebs.SnapshotId)
snapshotIds = append(snapshotIds, device.Ebs.SnapshotId)
}
}
// Declare list of resources to tag
resourceIds := []*string{&ami}
awsConfig := aws.Config{
Credentials: ec2conn.Config.Credentials,
Region: aws.String(region),
}
session, err := session.NewSession(&awsConfig)
if err != nil {
err := fmt.Errorf("Error creating AWS session: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
regionconn := ec2.New(session)
// Retrieve image list for given AMI
imageResp, err := regionconn.DescribeImages(&ec2.DescribeImagesInput{
ImageIds: resourceIds,
// Retry creating tags for about 2.5 minutes
err = retry.Retry(0.2, 30, 11, func() (bool, error) {
// Tag images and snapshots
_, err := regionconn.CreateTags(&ec2.CreateTagsInput{
Resources: resourceIds,
Tags: amiTags,
})
if err != nil {
err := fmt.Errorf("Error retrieving details for AMI (%s): %s", ami, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(imageResp.Images) == 0 {
err := fmt.Errorf("Error retrieving details for AMI (%s), no images found", ami)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
image := imageResp.Images[0]
// Add only those with a Snapshot ID, i.e. not Ephemeral
for _, device := range image.BlockDeviceMappings {
if device.Ebs != nil && device.Ebs.SnapshotId != nil {
ui.Say(fmt.Sprintf("Tagging snapshot: %s", *device.Ebs.SnapshotId))
resourceIds = append(resourceIds, device.Ebs.SnapshotId)
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() == "InvalidAMIID.NotFound" ||
awsErr.Code() == "InvalidSnapshot.NotFound" {
return false, nil
}
}
// Retry creating tags for about 2.5 minutes
err = retry.Retry(0.2, 30, 11, func() (bool, error) {
_, err := regionconn.CreateTags(&ec2.CreateTagsInput{
Resources: resourceIds,
Tags: ec2Tags,
})
if err == nil {
return true, nil
}
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() == "InvalidAMIID.NotFound" ||
awsErr.Code() == "InvalidSnapshot.NotFound" {
return false, nil
}
}
return true, err
// Override tags on snapshots
_, err = regionconn.CreateTags(&ec2.CreateTagsInput{
Resources: snapshotIds,
Tags: snapshotTags,
})
if err != nil {
err := fmt.Errorf("Error adding tags to Resources (%#v): %s", resourceIds, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
if err == nil {
return true, nil
}
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() == "InvalidSnapshot.NotFound" {
return false, nil
}
}
return true, err
})
if err != nil {
err := fmt.Errorf("Error adding tags to Resources (%#v): %s", resourceIds, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
@ -112,3 +125,15 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction {
func (s *StepCreateTags) Cleanup(state multistep.StateBag) {
// No cleanup...
}
func ConvertToEC2Tags(tags map[string]string, ui packer.Ui) []*ec2.Tag {
var amiTags []*ec2.Tag
for key, value := range tags {
ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"", key, value))
amiTags = append(amiTags, &ec2.Tag{
Key: aws.String(key),
Value: aws.String(value),
})
}
return amiTags
}

View File

@ -182,7 +182,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ProductCodes: b.config.AMIProductCodes,
},
&awscommon.StepCreateTags{
Tags: b.config.AMITags,
Tags: b.config.AMITags,
SnapshotTags: b.config.SnapshotTags,
},
}

View File

@ -1,6 +1,7 @@
package ebs
import (
"encoding/json"
"fmt"
"testing"
@ -11,6 +12,21 @@ import (
"github.com/mitchellh/packer/packer"
)
type TFBuilder struct {
Type string `json:"type"`
Region string `json:"region"`
SourceAmi string `json:"source_ami"`
InstanceType string `json:"instance_type"`
SshUsername string `json:"ssh_username"`
AmiName string `json:"ami_name"`
Tags map[string]string `json:"tags"`
SnapshotTags map[string]string `json:"snapshot_tags"`
}
type TFConfig struct {
Builders []TFBuilder `json:"builders"`
}
func TestBuilderTagsAcc_basic(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -26,9 +42,10 @@ func checkTags() builderT.TestCheckFunc {
return fmt.Errorf("more than 1 artifact")
}
tags := make(map[string]string)
tags["OS_Version"] = "Ubuntu"
tags["Release"] = "Latest"
config := TFConfig{}
json.Unmarshal([]byte(testBuilderTagsAccBasic), &config)
tags := config.Builders[0].Tags
snapshotTags := config.Builders[0].SnapshotTags
// Get the actual *Artifact pointer so we can access the AMIs directly
artifactRaw := artifacts[0]
@ -37,18 +54,18 @@ func checkTags() builderT.TestCheckFunc {
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
}
// describe the image, get block devices with a snapshot
// Describe the image, get block devices with a snapshot
ec2conn, _ := testEC2Conn()
imageResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
ImageIds: []*string{aws.String(artifact.Amis["us-east-1"])},
})
if err != nil {
return fmt.Errorf("Error retrieving details for AMI Artifcat (%#v) in Tags Test: %s", artifact, err)
return fmt.Errorf("Error retrieving details for AMI Artifact (%#v) in Tags Test: %s", artifact, err)
}
if len(imageResp.Images) == 0 {
return fmt.Errorf("No images found for AMI Artifcat (%#v) in Tags Test: %s", artifact, err)
return fmt.Errorf("No images found for AMI Artifact (%#v) in Tags Test: %s", artifact, err)
}
image := imageResp.Images[0]
@ -61,7 +78,7 @@ func checkTags() builderT.TestCheckFunc {
}
}
// grab matching snapshot info
// Grab matching snapshot info
resp, err := ec2conn.DescribeSnapshots(&ec2.DescribeSnapshotsInput{
SnapshotIds: snapshots,
})
@ -74,12 +91,14 @@ func checkTags() builderT.TestCheckFunc {
return fmt.Errorf("No Snapshots found for AMI Artifcat (%#v) in Tags Test", artifact)
}
// grab the snapshots, check the tags
// Grab the snapshots, check the tags
for _, s := range resp.Snapshots {
expected := len(tags)
for _, t := range s.Tags {
for key, value := range tags {
if key == *t.Key && value == *t.Value {
if val, ok := snapshotTags[key]; ok && val == *t.Value {
expected--
} else if key == *t.Key && value == *t.Value {
expected--
}
}
@ -106,7 +125,11 @@ const testBuilderTagsAccBasic = `
"ami_name": "packer-tags-testing-{{timestamp}}",
"tags": {
"OS_Version": "Ubuntu",
"Release": "Latest"
"Release": "Latest",
"Name": "Bleep"
},
"snapshot_tags": {
"Name": "Foobar"
}
}
]

View File

@ -263,7 +263,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ProductCodes: b.config.AMIProductCodes,
},
&awscommon.StepCreateTags{
Tags: b.config.AMITags,
Tags: b.config.AMITags,
SnapshotTags: b.config.SnapshotTags,
},
}

View File

@ -200,6 +200,9 @@ 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

@ -173,7 +173,7 @@ builder.
described above. Note that if this is specified, you must omit the
`security_group_id`.
- `skip_region_validation` (boolean) - Set to true if you want to skip
- `skip_region_validation` (boolean) - Set to true if you want to skip
validation of the region configuration option. Defaults to false.
- `source_ami_filter` (object) - Filters used to populate the `source_ami` field.
@ -204,6 +204,9 @@ 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

View File

@ -188,7 +188,7 @@ builder.
described above. Note that if this is specified, you must omit the
`security_group_id`.
- `skip_region_validation` (boolean) - Set to true if you want to skip
- `skip_region_validation` (boolean) - Set to true if you want to skip
validation of the region configuration option. Defaults to false.
- `source_ami_filter` (object) - Filters used to populate the `source_ami` field.
@ -219,6 +219,9 @@ 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 launch a spot instance
to create the AMI. It is a type of instances that EC2 starts when the
maximum price that you specify exceeds the current spot price. Spot price
@ -247,7 +250,7 @@ builder.
AMI, leave the `ssh_keypair_name` blank. To associate an existing key pair
in AWS with the source instance, set the `ssh_keypair_name` field to the name
of the key pair.
- `ssh_private_ip` (boolean) - If true, then SSH will always use the private
IP if available.