From 2aac8fb8fcfdf4145c98d7e0d0a53446edec9492 Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Fri, 21 Apr 2017 23:37:26 +1000 Subject: [PATCH] Add `aws_ami_ids` and `aws_ebs_snapshot_ids` resources (#13844) Fixes #12081. Adds new `aws_ami_ids` and `aws_ebs_snapshot_ids` resources. --- .../providers/aws/data_source_aws_ami_ids.go | 112 ++++++++++++++++++ .../aws/data_source_aws_ami_ids_test.go | 58 +++++++++ .../aws/data_source_aws_ebs_snapshot_ids.go | 78 ++++++++++++ .../data_source_aws_ebs_snapshot_ids_test.go | 59 +++++++++ .../aws/data_source_aws_ebs_snapshot_test.go | 2 +- builtin/providers/aws/provider.go | 2 + .../docs/providers/aws/d/ami.html.markdown | 3 +- .../providers/aws/d/ami_ids.html.markdown | 51 ++++++++ .../aws/d/ebs_snapshot_ids.html.markdown | 48 ++++++++ 9 files changed, 411 insertions(+), 2 deletions(-) create mode 100644 builtin/providers/aws/data_source_aws_ami_ids.go create mode 100644 builtin/providers/aws/data_source_aws_ami_ids_test.go create mode 100644 builtin/providers/aws/data_source_aws_ebs_snapshot_ids.go create mode 100644 builtin/providers/aws/data_source_aws_ebs_snapshot_ids_test.go create mode 100644 website/source/docs/providers/aws/d/ami_ids.html.markdown create mode 100644 website/source/docs/providers/aws/d/ebs_snapshot_ids.html.markdown diff --git a/builtin/providers/aws/data_source_aws_ami_ids.go b/builtin/providers/aws/data_source_aws_ami_ids.go new file mode 100644 index 000000000..bbf4438d5 --- /dev/null +++ b/builtin/providers/aws/data_source_aws_ami_ids.go @@ -0,0 +1,112 @@ +package aws + +import ( + "fmt" + "log" + "regexp" + + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceAwsAmiIds() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsAmiIdsRead, + + Schema: map[string]*schema.Schema{ + "filter": dataSourceFiltersSchema(), + "executable_users": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "name_regex": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validateNameRegex, + }, + "owners": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "tags": dataSourceTagsSchema(), + "ids": &schema.Schema{ + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + }, + } +} + +func dataSourceAwsAmiIdsRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + executableUsers, executableUsersOk := d.GetOk("executable_users") + filters, filtersOk := d.GetOk("filter") + nameRegex, nameRegexOk := d.GetOk("name_regex") + owners, ownersOk := d.GetOk("owners") + + if executableUsersOk == false && filtersOk == false && nameRegexOk == false && ownersOk == false { + return fmt.Errorf("One of executable_users, filters, name_regex, or owners must be assigned") + } + + params := &ec2.DescribeImagesInput{} + + if executableUsersOk { + params.ExecutableUsers = expandStringList(executableUsers.([]interface{})) + } + if filtersOk { + params.Filters = buildAwsDataSourceFilters(filters.(*schema.Set)) + } + if ownersOk { + o := expandStringList(owners.([]interface{})) + + if len(o) > 0 { + params.Owners = o + } + } + + resp, err := conn.DescribeImages(params) + if err != nil { + return err + } + + var filteredImages []*ec2.Image + imageIds := make([]string, 0) + + if nameRegexOk { + r := regexp.MustCompile(nameRegex.(string)) + for _, image := range resp.Images { + // Check for a very rare case where the response would include no + // image name. No name means nothing to attempt a match against, + // therefore we are skipping such image. + if image.Name == nil || *image.Name == "" { + log.Printf("[WARN] Unable to find AMI name to match against "+ + "for image ID %q owned by %q, nothing to do.", + *image.ImageId, *image.OwnerId) + continue + } + if r.MatchString(*image.Name) { + filteredImages = append(filteredImages, image) + } + } + } else { + filteredImages = resp.Images[:] + } + + for _, image := range filteredImages { + imageIds = append(imageIds, *image.ImageId) + } + + d.SetId(fmt.Sprintf("%d", hashcode.String(params.String()))) + d.Set("ids", imageIds) + + return nil +} diff --git a/builtin/providers/aws/data_source_aws_ami_ids_test.go b/builtin/providers/aws/data_source_aws_ami_ids_test.go new file mode 100644 index 000000000..e2a7ac2d8 --- /dev/null +++ b/builtin/providers/aws/data_source_aws_ami_ids_test.go @@ -0,0 +1,58 @@ +package aws + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccDataSourceAwsAmiIds_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAwsAmiIdsConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAmiDataSourceID("data.aws_ami_ids.ubuntu"), + ), + }, + }, + }) +} + +func TestAccDataSourceAwsAmiIds_empty(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAwsAmiIdsConfig_empty, + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAmiDataSourceID("data.aws_ami_ids.empty"), + resource.TestCheckResourceAttr("data.aws_ami_ids.empty", "ids.#", "0"), + ), + }, + }, + }) +} + +const testAccDataSourceAwsAmiIdsConfig_basic = ` +data "aws_ami_ids" "ubuntu" { + owners = ["099720109477"] + + filter { + name = "name" + values = ["ubuntu/images/ubuntu-*-*-amd64-server-*"] + } +} +` + +const testAccDataSourceAwsAmiIdsConfig_empty = ` +data "aws_ami_ids" "empty" { + filter { + name = "name" + values = [] + } +} +` diff --git a/builtin/providers/aws/data_source_aws_ebs_snapshot_ids.go b/builtin/providers/aws/data_source_aws_ebs_snapshot_ids.go new file mode 100644 index 000000000..57dc20e9c --- /dev/null +++ b/builtin/providers/aws/data_source_aws_ebs_snapshot_ids.go @@ -0,0 +1,78 @@ +package aws + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceAwsEbsSnapshotIds() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsEbsSnapshotIdsRead, + + Schema: map[string]*schema.Schema{ + "filter": dataSourceFiltersSchema(), + "owners": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "restorable_by_user_ids": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "tags": dataSourceTagsSchema(), + "ids": &schema.Schema{ + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + }, + } +} + +func dataSourceAwsEbsSnapshotIdsRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + restorableUsers, restorableUsersOk := d.GetOk("restorable_by_user_ids") + filters, filtersOk := d.GetOk("filter") + owners, ownersOk := d.GetOk("owners") + + if restorableUsers == false && filtersOk == false && ownersOk == false { + return fmt.Errorf("One of filters, restorable_by_user_ids, or owners must be assigned") + } + + params := &ec2.DescribeSnapshotsInput{} + + if restorableUsersOk { + params.RestorableByUserIds = expandStringList(restorableUsers.([]interface{})) + } + if filtersOk { + params.Filters = buildAwsDataSourceFilters(filters.(*schema.Set)) + } + if ownersOk { + params.OwnerIds = expandStringList(owners.([]interface{})) + } + + resp, err := conn.DescribeSnapshots(params) + if err != nil { + return err + } + + snapshotIds := make([]string, 0) + + for _, snapshot := range resp.Snapshots { + snapshotIds = append(snapshotIds, *snapshot.SnapshotId) + } + + d.SetId(fmt.Sprintf("%d", hashcode.String(params.String()))) + d.Set("ids", snapshotIds) + + return nil +} diff --git a/builtin/providers/aws/data_source_aws_ebs_snapshot_ids_test.go b/builtin/providers/aws/data_source_aws_ebs_snapshot_ids_test.go new file mode 100644 index 000000000..869152ac4 --- /dev/null +++ b/builtin/providers/aws/data_source_aws_ebs_snapshot_ids_test.go @@ -0,0 +1,59 @@ +package aws + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccDataSourceAwsEbsSnapshotIds_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAwsEbsSnapshotIdsConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsEbsSnapshotDataSourceID("data.aws_ebs_snapshot_ids.test"), + ), + }, + }, + }) +} + +func TestAccDataSourceAwsEbsSnapshotIds_empty(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAwsEbsSnapshotIdsConfig_empty, + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsEbsSnapshotDataSourceID("data.aws_ebs_snapshot_ids.empty"), + resource.TestCheckResourceAttr("data.aws_ebs_snapshot_ids.empty", "ids.#", "0"), + ), + }, + }, + }) +} + +const testAccDataSourceAwsEbsSnapshotIdsConfig_basic = ` +resource "aws_ebs_volume" "test" { + availability_zone = "us-west-2a" + size = 40 +} + +resource "aws_ebs_snapshot" "test" { + volume_id = "${aws_ebs_volume.test.id}" +} + +data "aws_ebs_snapshot_ids" "test" { + owners = ["self"] +} +` + +const testAccDataSourceAwsEbsSnapshotIdsConfig_empty = ` +data "aws_ebs_snapshot_ids" "empty" { + owners = ["000000000000"] +} +` diff --git a/builtin/providers/aws/data_source_aws_ebs_snapshot_test.go b/builtin/providers/aws/data_source_aws_ebs_snapshot_test.go index 682e758cc..58a20165a 100644 --- a/builtin/providers/aws/data_source_aws_ebs_snapshot_test.go +++ b/builtin/providers/aws/data_source_aws_ebs_snapshot_test.go @@ -44,7 +44,7 @@ func testAccCheckAwsEbsSnapshotDataSourceID(n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("Can't find Volume data source: %s", n) + return fmt.Errorf("Can't find snapshot data source: %s", n) } if rs.Primary.ID == "" { diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index a353d5eb3..ca136e200 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -163,6 +163,7 @@ func Provider() terraform.ResourceProvider { "aws_alb": dataSourceAwsAlb(), "aws_alb_listener": dataSourceAwsAlbListener(), "aws_ami": dataSourceAwsAmi(), + "aws_ami_ids": dataSourceAwsAmiIds(), "aws_autoscaling_groups": dataSourceAwsAutoscalingGroups(), "aws_availability_zone": dataSourceAwsAvailabilityZone(), "aws_availability_zones": dataSourceAwsAvailabilityZones(), @@ -172,6 +173,7 @@ func Provider() terraform.ResourceProvider { "aws_cloudformation_stack": dataSourceAwsCloudFormationStack(), "aws_db_instance": dataSourceAwsDbInstance(), "aws_ebs_snapshot": dataSourceAwsEbsSnapshot(), + "aws_ebs_snapshot_ids": dataSourceAwsEbsSnapshotIds(), "aws_ebs_volume": dataSourceAwsEbsVolume(), "aws_ecs_cluster": dataSourceAwsEcsCluster(), "aws_ecs_container_definition": dataSourceAwsEcsContainerDefinition(), diff --git a/website/source/docs/providers/aws/d/ami.html.markdown b/website/source/docs/providers/aws/d/ami.html.markdown index a3dae6508..a91c5dbc2 100644 --- a/website/source/docs/providers/aws/d/ami.html.markdown +++ b/website/source/docs/providers/aws/d/ami.html.markdown @@ -59,7 +59,8 @@ options to narrow down the list AWS returns. ~> **NOTE:** If more or less than a single match is returned by the search, Terraform will fail. Ensure that your search is specific enough to return -a single AMI ID only, or use `most_recent` to choose the most recent one. +a single AMI ID only, or use `most_recent` to choose the most recent one. If +you want to match multiple AMIs, use the `aws_ami_ids` data source instead. ## Attributes Reference diff --git a/website/source/docs/providers/aws/d/ami_ids.html.markdown b/website/source/docs/providers/aws/d/ami_ids.html.markdown new file mode 100644 index 000000000..526977bdc --- /dev/null +++ b/website/source/docs/providers/aws/d/ami_ids.html.markdown @@ -0,0 +1,51 @@ +--- +layout: "aws" +page_title: "AWS: aws_ami_ids" +sidebar_current: "docs-aws-datasource-ami-ids" +description: |- + Provides a list of AMI IDs. +--- + +# aws\_ami_ids + +Use this data source to get a list of AMI IDs matching the specified criteria. + +## Example Usage + +```hcl +data "aws_ami_ids" "ubuntu" { + owners = ["099720109477"] + + filter { + name = "name" + values = ["ubuntu/images/ubuntu-*-*-amd64-server-*"] + } +} +``` + +## Argument Reference + +* `executable_users` - (Optional) Limit search to users with *explicit* launch +permission on the image. Valid items are the numeric account ID or `self`. + +* `filter` - (Optional) One or more name/value pairs to filter off of. There +are several valid keys, for a full reference, check out +[describe-images in the AWS CLI reference][1]. + +* `owners` - (Optional) Limit search to specific AMI owners. Valid items are +the numeric account ID, `amazon`, or `self`. + +* `name_regex` - (Optional) A regex string to apply to the AMI list returned +by AWS. This allows more advanced filtering not supported from the AWS API. +This filtering is done locally on what AWS returns, and could have a performance +impact if the result is large. It is recommended to combine this with other +options to narrow down the list AWS returns. + +~> **NOTE:** At least one of `executable_users`, `filter`, `owners` or +`name_regex` must be specified. + +## Attributes Reference + +`ids` is set to the list of AMI IDs. + +[1]: http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-images.html diff --git a/website/source/docs/providers/aws/d/ebs_snapshot_ids.html.markdown b/website/source/docs/providers/aws/d/ebs_snapshot_ids.html.markdown new file mode 100644 index 000000000..6d4ef617d --- /dev/null +++ b/website/source/docs/providers/aws/d/ebs_snapshot_ids.html.markdown @@ -0,0 +1,48 @@ +--- +layout: "aws" +page_title: "AWS: aws_ebs_snapshot_ids" +sidebar_current: "docs-aws-datasource-ebs-snapshot-ids" +description: |- + Provides a list of EBS snapshot IDs. +--- + +# aws\_ebs\_snapshot\_ids + +Use this data source to get a list of EBS Snapshot IDs matching the specified +criteria. + +## Example Usage + +```hcl +data "aws_ebs_snapshot_ids" "ebs_volumes" { + owners = ["self"] + + filter { + name = "volume-size" + values = ["40"] + } + + filter { + name = "tag:Name" + values = ["Example"] + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `owners` - (Optional) Returns the snapshots owned by the specified owner id. Multiple owners can be specified. + +* `restorable_by_user_ids` - (Optional) One or more AWS accounts IDs that can create volumes from the snapshot. + +* `filter` - (Optional) One or more name/value pairs to filter off of. There are +several valid keys, for a full reference, check out +[describe-volumes in the AWS CLI reference][1]. + +## Attributes Reference + +`ids` is set to the list of EBS snapshot IDs. + +[1]: http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-snapshots.html