diff --git a/builder/amazon/common/step_create_tags.go b/builder/amazon/common/step_create_tags.go index a3346c76e..7c89e5a59 100644 --- a/builder/amazon/common/step_create_tags.go +++ b/builder/amazon/common/step_create_tags.go @@ -25,19 +25,56 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction { 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}) + ec2Tags = append(ec2Tags, &ec2.Tag{ + Key: aws.String(key), + Value: aws.String(value), + }) + } + + // Declare list of resources to tag + resourceIds := []*string{&ami} + + // Retrieve image list for given AMI + 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 + } + + 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) + } } regionconn := ec2.New(&aws.Config{ Credentials: ec2conn.Config.Credentials, Region: region, }) - _, err := regionconn.CreateTags(&ec2.CreateTagsInput{ - Resources: []*string{&ami}, + + _, 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 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" + } + } + ] +} +` diff --git a/test/builder_amazon_ebs.bats b/test/builder_amazon_ebs.bats deleted file mode 100755 index 7dd17acbd..000000000 --- a/test/builder_amazon_ebs.bats +++ /dev/null @@ -1,36 +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 -} - -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" ] -} diff --git a/website/source/docs/builders/amazon-ebs.html.markdown b/website/source/docs/builders/amazon-ebs.html.markdown index a79a27f94..af3ece59e 100644 --- a/website/source/docs/builders/amazon-ebs.html.markdown +++ b/website/source/docs/builders/amazon-ebs.html.markdown @@ -150,7 +150,8 @@ AMI if one with the same name already exists. Default `false`. "subnet-12345def", where Packer will launch the EC2 instance. This field is required if you are using an non-default VPC. -* `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.