From 932d49a2003c32934e6fe9841a5f41078a8298e8 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Sun, 2 Dec 2018 16:32:02 -0500 Subject: [PATCH] Support Availability Zone ID in data sources. --- aws/data_source_aws_availability_zone.go | 17 +++-- aws/data_source_aws_availability_zone_test.go | 42 +++++------- aws/data_source_aws_availability_zones.go | 41 ++++++++++-- ...data_source_aws_availability_zones_test.go | 66 ++++++++++++++++++- .../docs/d/availability_zone.html.markdown | 4 ++ .../docs/d/availability_zones.html.markdown | 3 + 6 files changed, 135 insertions(+), 38 deletions(-) diff --git a/aws/data_source_aws_availability_zone.go b/aws/data_source_aws_availability_zone.go index 2d872b133fb..bc660cfdb9a 100644 --- a/aws/data_source_aws_availability_zone.go +++ b/aws/data_source_aws_availability_zone.go @@ -35,6 +35,12 @@ func dataSourceAwsAvailabilityZone() *schema.Resource { Optional: true, Computed: true, }, + + "zone_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, }, } } @@ -44,10 +50,12 @@ func dataSourceAwsAvailabilityZoneRead(d *schema.ResourceData, meta interface{}) req := &ec2.DescribeAvailabilityZonesInput{} - if name := d.Get("name"); name != "" { - req.ZoneNames = []*string{aws.String(name.(string))} + if v := d.Get("name").(string); v != "" { + req.ZoneNames = []*string{aws.String(v)} + } + if v := d.Get("zone_id").(string); v != "" { + req.ZoneIds = []*string{aws.String(v)} } - req.Filters = buildEC2AttributeFilterList( map[string]string{ "state": d.Get("state").(string), @@ -78,11 +86,12 @@ func dataSourceAwsAvailabilityZoneRead(d *schema.ResourceData, meta interface{}) // work regardless of region. nameSuffix := (*az.ZoneName)[len(*az.RegionName):] - d.SetId(*az.ZoneName) + d.SetId(aws.StringValue(az.ZoneName)) d.Set("name", az.ZoneName) d.Set("name_suffix", nameSuffix) d.Set("region", az.RegionName) d.Set("state", az.State) + d.Set("zone_id", az.ZoneId) return nil } diff --git a/aws/data_source_aws_availability_zone_test.go b/aws/data_source_aws_availability_zone_test.go index e06f2f57d76..0ee6c41d53c 100644 --- a/aws/data_source_aws_availability_zone_test.go +++ b/aws/data_source_aws_availability_zone_test.go @@ -1,14 +1,15 @@ package aws import ( - "fmt" "testing" "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" ) func TestAccDataSourceAwsAvailabilityZone(t *testing.T) { + ds1ResourceName := "data.aws_availability_zone.by_name" + ds2ResourceName := "data.aws_availability_zone.by_zone_id" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -16,36 +17,21 @@ func TestAccDataSourceAwsAvailabilityZone(t *testing.T) { { Config: testAccDataSourceAwsAvailabilityZoneConfig, Check: resource.ComposeTestCheckFunc( - testAccDataSourceAwsAvailabilityZoneCheck("data.aws_availability_zone.by_name"), + resource.TestCheckResourceAttr(ds1ResourceName, "name", "us-west-2a"), + resource.TestCheckResourceAttr(ds1ResourceName, "name_suffix", "a"), + resource.TestCheckResourceAttr(ds1ResourceName, "region", "us-west-2"), + resource.TestCheckResourceAttrSet(ds1ResourceName, "zone_id"), + + resource.TestCheckResourceAttr(ds2ResourceName, "name", "us-west-2a"), + resource.TestCheckResourceAttr(ds2ResourceName, "name_suffix", "a"), + resource.TestCheckResourceAttr(ds2ResourceName, "region", "us-west-2"), + resource.TestCheckResourceAttrPair(ds2ResourceName, "zone_id", ds1ResourceName, "zone_id"), ), }, }, }) } -func testAccDataSourceAwsAvailabilityZoneCheck(name string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[name] - if !ok { - return fmt.Errorf("root module has no resource called %s", name) - } - - attr := rs.Primary.Attributes - - if attr["name"] != "us-west-2a" { - return fmt.Errorf("bad name %s", attr["name"]) - } - if attr["name_suffix"] != "a" { - return fmt.Errorf("bad name_suffix %s", attr["name_suffix"]) - } - if attr["region"] != "us-west-2" { - return fmt.Errorf("bad region %s", attr["region"]) - } - - return nil - } -} - const testAccDataSourceAwsAvailabilityZoneConfig = ` provider "aws" { region = "us-west-2" @@ -54,4 +40,8 @@ provider "aws" { data "aws_availability_zone" "by_name" { name = "us-west-2a" } + +data "aws_availability_zone" "by_zone_id" { + zone_id = "${data.aws_availability_zone.by_name.zone_id}" +} ` diff --git a/aws/data_source_aws_availability_zones.go b/aws/data_source_aws_availability_zones.go index 3d033de6ddb..ceb7d238408 100644 --- a/aws/data_source_aws_availability_zones.go +++ b/aws/data_source_aws_availability_zones.go @@ -32,6 +32,11 @@ func dataSourceAwsAvailabilityZones() *schema.Resource { ec2.AvailabilityZoneStateUnavailable, }, false), }, + "zone_ids": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, }, } } @@ -59,16 +64,38 @@ func dataSourceAwsAvailabilityZonesRead(d *schema.ResourceData, meta interface{} return fmt.Errorf("Error fetching Availability Zones: %s", err) } - raw := make([]string, len(resp.AvailabilityZones)) - for i, v := range resp.AvailabilityZones { - raw[i] = *v.ZoneName - } + azs := resp.AvailabilityZones + sort.Sort(availabilityZones(azs)) - sort.Strings(raw) + names := []string{} + zoneIds := []string{} + for _, v := range azs { + names = append(names, aws.StringValue(v.ZoneName)) + zoneIds = append(zoneIds, aws.StringValue(v.ZoneId)) + } - if err := d.Set("names", raw); err != nil { - return fmt.Errorf("Error setting Availability Zones: %s", err) + if err := d.Set("names", names); err != nil { + return fmt.Errorf("Error setting Availability Zone names: %s", err) + } + if err := d.Set("zone_ids", zoneIds); err != nil { + return fmt.Errorf("Error setting Availability Zone IDs: %s", err) } return nil } + +// Ensure that indexes of returned AZ names and zone IDs correspond. +type availabilityZones []*ec2.AvailabilityZone + +func (azs availabilityZones) Len() int { + return len(azs) +} + +func (azs availabilityZones) Swap(i, j int) { + azs[i], azs[j] = azs[j], azs[i] +} + +func (azs availabilityZones) Less(i, j int) bool { + // Sort by AZ name. + return aws.StringValue(azs[i].ZoneName) < aws.StringValue(azs[j].ZoneName) +} diff --git a/aws/data_source_aws_availability_zones_test.go b/aws/data_source_aws_availability_zones_test.go index 9a3be5cbeba..e6c4c9b97b4 100644 --- a/aws/data_source_aws_availability_zones_test.go +++ b/aws/data_source_aws_availability_zones_test.go @@ -7,10 +7,70 @@ import ( "strconv" "testing" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" ) +func TestAvailabilityZonesSort(t *testing.T) { + azs := []*ec2.AvailabilityZone{ + { + ZoneName: aws.String("name_YYY"), + ZoneId: aws.String("id_YYY"), + }, + { + ZoneName: aws.String("name_AAA"), + ZoneId: aws.String("id_AAA"), + }, + { + ZoneName: aws.String("name_ZZZ"), + ZoneId: aws.String("id_ZZZ"), + }, + { + ZoneName: aws.String("name_BBB"), + ZoneId: aws.String("id_BBB"), + }, + } + sort.Sort(availabilityZones(azs)) + + cases := []struct { + Index int + ZoneName string + ZoneId string + }{ + { + Index: 0, + ZoneName: "name_AAA", + ZoneId: "id_AAA", + }, + { + Index: 1, + ZoneName: "name_BBB", + ZoneId: "id_BBB", + }, + { + Index: 2, + ZoneName: "name_YYY", + ZoneId: "id_YYY", + }, + { + Index: 3, + ZoneName: "name_ZZZ", + ZoneId: "id_ZZZ", + }, + } + for _, tc := range cases { + az := azs[tc.Index] + if aws.StringValue(az.ZoneName) != tc.ZoneName { + t.Fatalf("AvailabilityZones index %d got zone name %s, expected %s", tc.Index, aws.StringValue(az.ZoneName), tc.ZoneName) + } + if aws.StringValue(az.ZoneId) != tc.ZoneId { + t.Fatalf("AvailabilityZones index %d got zone ID %s, expected %s", tc.Index, aws.StringValue(az.ZoneId), tc.ZoneId) + } + } +} + func TestAccAWSAvailabilityZones_basic(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -92,7 +152,7 @@ func testAccCheckAwsAvailabilityZoneState(n string) resource.TestCheckFunc { func testAccCheckAwsAvailabilityZonesBuildAvailable(attrs map[string]string) ([]string, error) { v, ok := attrs["names.#"] if !ok { - return nil, fmt.Errorf("Available AZ list is missing.") + return nil, fmt.Errorf("Available AZ name list is missing.") } qty, err := strconv.Atoi(v) if err != nil { @@ -101,6 +161,10 @@ func testAccCheckAwsAvailabilityZonesBuildAvailable(attrs map[string]string) ([] if qty < 1 { return nil, fmt.Errorf("No AZs found in region, this is probably a bug.") } + _, ok = attrs["zone_ids.#"] + if !ok { + return nil, fmt.Errorf("Available AZ ID list is missing.") + } zones := make([]string, qty) for n := range zones { zone, ok := attrs["names."+strconv.Itoa(n)] diff --git a/website/docs/d/availability_zone.html.markdown b/website/docs/d/availability_zone.html.markdown index c55406218d9..29bb5fecae7 100644 --- a/website/docs/d/availability_zone.html.markdown +++ b/website/docs/d/availability_zone.html.markdown @@ -79,6 +79,8 @@ zone whose data will be exported as attributes. * `state` - (Optional) A specific availability zone state to require. May be any of `"available"`, `"information"` or `"impaired"`. +* `zone_id` - (Optional) The zone ID of the availability zone to select. + All reasonable uses of this data source will specify `name`, since `state` alone would match a single AZ only in a region that itself has only one AZ. @@ -96,3 +98,5 @@ In addition to all arguments above, the following attributes are exported: uniquely identifying the AZ within its region. * `state` - The current state of the AZ. + +* `zone_id` - (Optional) The zone ID of the selected availability zone. diff --git a/website/docs/d/availability_zones.html.markdown b/website/docs/d/availability_zones.html.markdown index b04bfd67c5c..9617b8de899 100644 --- a/website/docs/d/availability_zones.html.markdown +++ b/website/docs/d/availability_zones.html.markdown @@ -50,3 +50,6 @@ to which the underlying AWS account has access, regardless of their state. In addition to all arguments above, the following attributes are exported: * `names` - A list of the Availability Zone names available to the account. +* `zone_ids` - A list of the Availability Zone IDs available to the account. + +Note that the indexes of Availability Zone names and IDs correspond.