Implements Snapshot tagging

While implementing my acceptance test, I stumbled upon a comment stating
that snapshot deletion should also be implemented, so I snuck that in. I
can't help but wonder if there is some generic logic that is implemented
a few times throughout the packer code base that could maybe better serve
us if it were abstracted to the common package.
This commit is contained in:
Arthur Burkart 2016-10-18 22:46:41 -04:00 committed by Rickard von Essen
parent 2e65867cba
commit 0c7e73b1cf
No known key found for this signature in database
GPG Key ID: E0C0327388876CBA
3 changed files with 133 additions and 96 deletions

View File

@ -79,6 +79,19 @@ func (a *Artifact) Destroy() error {
} }
regionConn := ec2.New(session) 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{ input := &ec2.DeregisterImageInput{
ImageId: &imageId, ImageId: &imageId,
} }

View File

@ -22,99 +22,100 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
amis := state.Get("amis").(map[string]string) amis := state.Get("amis").(map[string]string)
if len(s.Tags) > 0 || len(s.SnapshotTags) > 0 { if len(s.Tags) == 0 && len(s.SnapshotTags) == 0 {
for region, ami := range amis { return multistep.ActionContinue
ui.Say(fmt.Sprintf("Adding tags to AMI (%s)...", ami)) }
// Convert tags to ec2.Tag format // Adds tags to AMIs and snapshots
ec2Tags := ConvertToEC2Tags(s.Tags, ui) for region, ami := range amis {
snapshotTags := ConvertToEC2Tags(s.SnapshotTags, ui) ui.Say(fmt.Sprintf("Adding tags to AMI (%s)...", ami))
// Declare list of resources to tag // Convert tags to ec2.Tag format
awsConfig := aws.Config{ amiTags := ConvertToEC2Tags(s.Tags, ui)
Credentials: ec2conn.Config.Credentials, ui.Say(fmt.Sprintf("Snapshot tags:"))
Region: aws.String(region), 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)
} }
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 // Retry creating tags for about 2.5 minutes
resourceIds := []*string{&ami} err = retry.Retry(0.2, 30, 11, func() (bool, error) {
imageResp, err := regionconn.DescribeImages(&ec2.DescribeImagesInput{ // Tag images and snapshots
ImageIds: resourceIds, _, err := regionconn.CreateTags(&ec2.CreateTagsInput{
Resources: resourceIds,
Tags: amiTags,
}) })
if awsErr, ok := err.(awserr.Error); ok {
if err != nil { if awsErr.Code() == "InvalidAMIID.NotFound" ||
err := fmt.Errorf("Error retrieving details for AMI (%s): %s", ami, err) awsErr.Code() == "InvalidSnapshot.NotFound" {
state.Put("error", err) return false, nil
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)
} }
} }
// Retry creating tags for about 2.5 minutes // Override tags on snapshots
err = retry.Retry(0.2, 30, 11, func() (bool, error) { _, err = regionconn.CreateTags(&ec2.CreateTagsInput{
// Tag images and snapshots Resources: snapshotIds,
_, err := regionconn.CreateTags(&ec2.CreateTagsInput{ Tags: snapshotTags,
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
}
}
// Override tags on snapshots
_, err = regionconn.CreateTags(&ec2.CreateTagsInput{
Resources: snapshotIds,
Tags: snapshotTags,
})
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 {
if err != nil { return true, 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 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
} }
} }
@ -126,13 +127,13 @@ func (s *StepCreateTags) Cleanup(state multistep.StateBag) {
} }
func ConvertToEC2Tags(tags map[string]string, ui packer.Ui) []*ec2.Tag { func ConvertToEC2Tags(tags map[string]string, ui packer.Ui) []*ec2.Tag {
var ec2Tags []*ec2.Tag var amiTags []*ec2.Tag
for key, value := range tags { for key, value := range tags {
ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"", key, value)) ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"", key, value))
ec2Tags = append(ec2Tags, &ec2.Tag{ amiTags = append(amiTags, &ec2.Tag{
Key: aws.String(key), Key: aws.String(key),
Value: aws.String(value), Value: aws.String(value),
}) })
} }
return ec2Tags return amiTags
} }

View File

@ -1,6 +1,7 @@
package ebs package ebs
import ( import (
"encoding/json"
"fmt" "fmt"
"testing" "testing"
@ -11,6 +12,21 @@ import (
"github.com/mitchellh/packer/packer" "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) { func TestBuilderTagsAcc_basic(t *testing.T) {
builderT.Test(t, builderT.TestCase{ builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
@ -26,9 +42,10 @@ func checkTags() builderT.TestCheckFunc {
return fmt.Errorf("more than 1 artifact") return fmt.Errorf("more than 1 artifact")
} }
tags := make(map[string]string) config := TFConfig{}
tags["OS_Version"] = "Ubuntu" json.Unmarshal([]byte(testBuilderTagsAccBasic), &config)
tags["Release"] = "Latest" tags := config.Builders[0].Tags
snapshotTags := config.Builders[0].SnapshotTags
// Get the actual *Artifact pointer so we can access the AMIs directly // Get the actual *Artifact pointer so we can access the AMIs directly
artifactRaw := artifacts[0] artifactRaw := artifacts[0]
@ -37,18 +54,18 @@ func checkTags() builderT.TestCheckFunc {
return fmt.Errorf("unknown artifact: %#v", artifactRaw) 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() ec2conn, _ := testEC2Conn()
imageResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ imageResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
ImageIds: []*string{aws.String(artifact.Amis["us-east-1"])}, ImageIds: []*string{aws.String(artifact.Amis["us-east-1"])},
}) })
if err != nil { 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 { 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] 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{ resp, err := ec2conn.DescribeSnapshots(&ec2.DescribeSnapshotsInput{
SnapshotIds: snapshots, SnapshotIds: snapshots,
}) })
@ -74,12 +91,14 @@ func checkTags() builderT.TestCheckFunc {
return fmt.Errorf("No Snapshots found for AMI Artifcat (%#v) in Tags Test", artifact) 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 { for _, s := range resp.Snapshots {
expected := len(tags) expected := len(tags)
for _, t := range s.Tags { for _, t := range s.Tags {
for key, value := range 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-- expected--
} }
} }
@ -106,7 +125,11 @@ const testBuilderTagsAccBasic = `
"ami_name": "packer-tags-testing-{{timestamp}}", "ami_name": "packer-tags-testing-{{timestamp}}",
"tags": { "tags": {
"OS_Version": "Ubuntu", "OS_Version": "Ubuntu",
"Release": "Latest" "Release": "Latest",
"Name": "Bleep"
},
"snapshot_tags": {
"Name": "Foobar"
} }
} }
] ]