From 361e859556ace480bf6dcec922ac0a0662b6388e Mon Sep 17 00:00:00 2001 From: Ash Caire Date: Sun, 1 Mar 2015 10:20:37 +1100 Subject: [PATCH 1/5] Add EBS snapshot tags --- builder/amazon/common/step_create_tags.go | 25 +++++++++++++++++-- test/builder_amazon_ebs.bats | 18 +++++++++++++ .../amazon-ebs/ami_snapshot_tags.json | 20 +++++++++++++++ .../docs/builders/amazon-ebs.html.markdown | 3 ++- 4 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 test/fixtures/amazon-ebs/ami_snapshot_tags.json diff --git a/builder/amazon/common/step_create_tags.go b/builder/amazon/common/step_create_tags.go index a204ca321..1d8b80ca7 100644 --- a/builder/amazon/common/step_create_tags.go +++ b/builder/amazon/common/step_create_tags.go @@ -19,7 +19,28 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction { if len(s.Tags) > 0 { for region, ami := range amis { - ui.Say(fmt.Sprintf("Adding tags to AMI (%s)...", ami)) + ui.Say(fmt.Sprintf("Preparing tags for AMI (%s) and related snapshots", ami)) + + // Declare list of resources to tag + resourceIds := []string{ami} + + // Retrieve image list for given AMI + imageResp, err := ec2conn.Images([]string{ami}, ec2.NewFilter()) + 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 + } + image := &imageResp.Images[0] + + // Add only those with a Snapshot ID, i.e. not Ephemeral + for _, device := range image.BlockDevices { + if device.SnapshotId != "" { + ui.Say(fmt.Sprintf("Tagging snapshot: %s", device.SnapshotId)) + resourceIds = append(resourceIds, device.SnapshotId) + } + } var ec2Tags []ec2.Tag for key, value := range s.Tags { @@ -28,7 +49,7 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction { } regionconn := ec2.New(ec2conn.Auth, aws.Regions[region]) - _, err := regionconn.CreateTags([]string{ami}, ec2Tags) + _, err = regionconn.CreateTags(resourceIds, ec2Tags) if err != nil { err := fmt.Errorf("Error adding tags to AMI (%s): %s", ami, err) state.Put("error", err) diff --git a/test/builder_amazon_ebs.bats b/test/builder_amazon_ebs.bats index 7dd17acbd..89f32a4a0 100755 --- a/test/builder_amazon_ebs.bats +++ b/test/builder_amazon_ebs.bats @@ -15,6 +15,18 @@ aws_ami_region_copy_count() { | wc -l } +# This verifies AMI tags are correctly applied to relevant snapshots +aws_ami_snapshot_tags_count() { + filter='Name=tag:packer-id,Values=ami_snapshot_tags' + aws ec2 describe-images --region $1 --owners self --output text \ + --filters "$filter" \ + --query "Images[*].BlockDeviceMappings[*].Ebs.SnapshotId" \ + | aws ec2 describe-snapshots --region $1 --owners self --output text \ + --filters "$filter" \ + --snapshot-ids \ + | wc -l +} + teardown() { aws_ami_cleanup 'us-east-1' aws_ami_cleanup 'us-west-1' @@ -34,3 +46,9 @@ teardown() { [ "$(aws_ami_region_copy_count 'us-west-1')" -eq "1" ] [ "$(aws_ami_region_copy_count 'us-west-2')" -eq "1" ] } + +@test "amazon-ebs: AMI snapshot tags" { + run packer build $FIXTURE_ROOT/ami_snapshot_tags.json + [ "$status" -eq 0 ] + [ "$(aws_ami_snapshot_tags)" -eq "2" ] +} diff --git a/test/fixtures/amazon-ebs/ami_snapshot_tags.json b/test/fixtures/amazon-ebs/ami_snapshot_tags.json new file mode 100644 index 000000000..278474a32 --- /dev/null +++ b/test/fixtures/amazon-ebs/ami_snapshot_tags.json @@ -0,0 +1,20 @@ +{ + "builders": [{ + "type": "amazon-ebs", + "ami_name": "packer-test {{timestamp}}", + "instance_type": "m1.small", + "region": "us-east-1", + "ssh_username": "ubuntu", + "source_ami": "ami-0568456c", + "tags": { + "packer-test": "true", + "packer-id": "ami_snapshot_tags" + }, + "ami_block_device_mappings": [ + { + "device_name": "/dev/sde", + "volume_type": "standard" + } + ] + }] +} diff --git a/website/source/docs/builders/amazon-ebs.html.markdown b/website/source/docs/builders/amazon-ebs.html.markdown index 5e2c31e90..946b18eb9 100644 --- a/website/source/docs/builders/amazon-ebs.html.markdown +++ b/website/source/docs/builders/amazon-ebs.html.markdown @@ -146,7 +146,8 @@ each category, the available configuration keys are alphabetized. * `subnet_id` (string) - If using VPC, the ID of the subnet, such as "subnet-12345def", where Packer will launch the EC2 instance. -* `tags` (object of key/value strings) - Tags applied to the AMI. +* `tags` (object of key/value strings) - Tags applied to the AMI and + relevant snapshots. * `temporary_key_pair_name` (string) - The name of the temporary keypair to generate. By default, Packer generates a name with a UUID. From 1fbf8b7f32beaf98a1a85f396a0e98eecfd12ca4 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Thu, 11 Jun 2015 10:43:27 -0500 Subject: [PATCH 2/5] update create_tags for new sdk --- builder/amazon/common/step_create_tags.go | 51 ++++++++++++++++------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/builder/amazon/common/step_create_tags.go b/builder/amazon/common/step_create_tags.go index 3a31202ff..7c89e5a59 100644 --- a/builder/amazon/common/step_create_tags.go +++ b/builder/amazon/common/step_create_tags.go @@ -3,6 +3,7 @@ package common import ( "fmt" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" @@ -19,41 +20,61 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction { if len(s.Tags) > 0 { for region, ami := range amis { - ui.Say(fmt.Sprintf("Preparing tags for AMI (%s) and related snapshots", ami)) + ui.Say(fmt.Sprintf("Adding tags to AMI (%s)...", ami)) + + 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), + }) + } // Declare list of resources to tag - resourceIds := []string{ami} + resourceIds := []*string{&ami} // Retrieve image list for given AMI - imageResp, err := ec2conn.Images([]string{ami}, ec2.NewFilter()) + imageResp, err := ec2conn.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 } - image := &imageResp.Images[0] + + 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.BlockDevices { - if device.SnapshotId != "" { - ui.Say(fmt.Sprintf("Tagging snapshot: %s", device.SnapshotId)) - resourceIds = append(resourceIds, device.SnapshotId) + 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) } } - 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: &key, Value: &value}) - } + regionconn := ec2.New(&aws.Config{ + Credentials: ec2conn.Config.Credentials, + Region: region, + }) - _, err := regionconn.CreateTags(&ec2.CreateTagsInput{ + _, err = regionconn.CreateTags(&ec2.CreateTagsInput{ Resources: resourceIds, Tags: ec2Tags, }) + if err != nil { - err := fmt.Errorf("Error adding tags to AMI (%s): %s", ami, err) + err := fmt.Errorf("Error adding tags to Resources (%#v): %s", resourceIds, err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt From 86206e316db6b1a23dc67c43324af75e7b2860a5 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Fri, 12 Jun 2015 10:39:37 -0500 Subject: [PATCH 3/5] add tags test --- builder/amazon/ebs/tags_acc_test.go | 114 ++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 builder/amazon/ebs/tags_acc_test.go diff --git a/builder/amazon/ebs/tags_acc_test.go b/builder/amazon/ebs/tags_acc_test.go new file mode 100644 index 000000000..606bb89ee --- /dev/null +++ b/builder/amazon/ebs/tags_acc_test.go @@ -0,0 +1,114 @@ +package ebs + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/mitchellh/packer/builder/amazon/common" + builderT "github.com/mitchellh/packer/helper/builder/testing" + "github.com/mitchellh/packer/packer" +) + +func TestBuilderTagsAcc_basic(t *testing.T) { + builderT.Test(t, builderT.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Builder: &Builder{}, + Template: testBuilderTagsAccBasic, + Check: checkTags(), + }) +} + +func checkTags() builderT.TestCheckFunc { + return func(artifacts []packer.Artifact) error { + if len(artifacts) > 1 { + return fmt.Errorf("more than 1 artifact") + } + + tags := make(map[string]string) + tags["OS_Version"] = "Ubuntu" + tags["Release"] = "Latest" + + // Get the actual *Artifact pointer so we can access the AMIs directly + artifactRaw := artifacts[0] + artifact, ok := artifactRaw.(*common.Artifact) + if !ok { + return fmt.Errorf("unknown artifact: %#v", artifactRaw) + } + + // 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) + } + + if len(imageResp.Images) == 0 { + return fmt.Errorf("No images found for AMI Artifcat (%#v) in Tags Test: %s", artifact, err) + } + + image := imageResp.Images[0] + + // Check only those with a Snapshot ID, i.e. not Ephemeral + var snapshots []*string + for _, device := range image.BlockDeviceMappings { + if device.EBS != nil && device.EBS.SnapshotID != nil { + snapshots = append(snapshots, device.EBS.SnapshotID) + } + } + + // grab matching snapshot info + resp, err := ec2conn.DescribeSnapshots(&ec2.DescribeSnapshotsInput{ + SnapshotIDs: snapshots, + }) + + if err != nil { + return fmt.Errorf("Error retreiving Snapshots for AMI Artifcat (%#v) in Tags Test: %s", artifact, err) + } + + if len(resp.Snapshots) == 0 { + return fmt.Errorf("No Snapshots found for AMI Artifcat (%#v) in Tags Test", artifact) + } + + // 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 { + expected-- + } + } + } + + if expected > 0 { + return fmt.Errorf("Not all tags found") + } + } + + return nil + } +} + +const testBuilderTagsAccBasic = ` +{ + "builders": [ + { + "type": "test", + "region": "us-east-1", + "source_ami": "ami-9eaa1cf6", + "instance_type": "t2.micro", + "ssh_username": "ubuntu", + "ami_name": "packer-tags-testing-{{timestamp}}", + "tags": { + "OS_Version": "Ubuntu", + "Release": "Latest" + } + } + ] +} +` From 85db8abe8daab3413868aa48591356ea015342c1 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Fri, 12 Jun 2015 10:40:55 -0500 Subject: [PATCH 4/5] remove old bats test --- test/builder_amazon_ebs.bats | 54 ------------------------------------ 1 file changed, 54 deletions(-) delete mode 100755 test/builder_amazon_ebs.bats diff --git a/test/builder_amazon_ebs.bats b/test/builder_amazon_ebs.bats deleted file mode 100755 index 89f32a4a0..000000000 --- a/test/builder_amazon_ebs.bats +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env bats -# -# This tests the amazon-ebs builder. The teardown function will automatically -# delete any AMIs with a tag of `packer-test` being equal to "true" so -# be sure any test cases set this. - -load test_helper -fixtures amazon-ebs - -# This counts how many AMIs were copied to another region -aws_ami_region_copy_count() { - aws ec2 describe-images --region $1 --owners self --output text \ - --filters 'Name=tag:packer-id,Values=ami_region_copy' \ - --query "Images[*].ImageId" \ - | wc -l -} - -# This verifies AMI tags are correctly applied to relevant snapshots -aws_ami_snapshot_tags_count() { - filter='Name=tag:packer-id,Values=ami_snapshot_tags' - aws ec2 describe-images --region $1 --owners self --output text \ - --filters "$filter" \ - --query "Images[*].BlockDeviceMappings[*].Ebs.SnapshotId" \ - | aws ec2 describe-snapshots --region $1 --owners self --output text \ - --filters "$filter" \ - --snapshot-ids \ - | wc -l -} - -teardown() { - aws_ami_cleanup 'us-east-1' - aws_ami_cleanup 'us-west-1' - aws_ami_cleanup 'us-west-2' -} - -@test "amazon-ebs: build minimal.json" { - run packer build $FIXTURE_ROOT/minimal.json - [ "$status" -eq 0 ] -} - -# @unit-testable -@test "amazon-ebs: AMI region copy" { - run packer build $FIXTURE_ROOT/ami_region_copy.json - [ "$status" -eq 0 ] - [ "$(aws_ami_region_copy_count 'us-east-1')" -eq "1" ] - [ "$(aws_ami_region_copy_count 'us-west-1')" -eq "1" ] - [ "$(aws_ami_region_copy_count 'us-west-2')" -eq "1" ] -} - -@test "amazon-ebs: AMI snapshot tags" { - run packer build $FIXTURE_ROOT/ami_snapshot_tags.json - [ "$status" -eq 0 ] - [ "$(aws_ami_snapshot_tags)" -eq "2" ] -} From c875d40b2cbf6407379cf2b4f0ecba24c86e4f2f Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Fri, 12 Jun 2015 10:41:44 -0500 Subject: [PATCH 5/5] remove bats test fixture --- .../amazon-ebs/ami_snapshot_tags.json | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 test/fixtures/amazon-ebs/ami_snapshot_tags.json diff --git a/test/fixtures/amazon-ebs/ami_snapshot_tags.json b/test/fixtures/amazon-ebs/ami_snapshot_tags.json deleted file mode 100644 index 278474a32..000000000 --- a/test/fixtures/amazon-ebs/ami_snapshot_tags.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "builders": [{ - "type": "amazon-ebs", - "ami_name": "packer-test {{timestamp}}", - "instance_type": "m1.small", - "region": "us-east-1", - "ssh_username": "ubuntu", - "source_ami": "ami-0568456c", - "tags": { - "packer-test": "true", - "packer-id": "ami_snapshot_tags" - }, - "ami_block_device_mappings": [ - { - "device_name": "/dev/sde", - "volume_type": "standard" - } - ] - }] -}