From 19800b8e2652331aaa6ac06183fd59c44298ba51 Mon Sep 17 00:00:00 2001 From: Krzysztof Wilczynski Date: Fri, 5 Aug 2016 09:14:05 +0900 Subject: [PATCH] Add state filter to aws_availability_zones data source. (#7965) * Add state filter to aws_availability_zones data source. This commit adds an ability to filter Availability Zones based on state, where by default it would only list available zones. Be advised that this does not always works reliably for an older accounts which have been created in the pre-VPC era of EC2. These accounts tends to retrieve availability zones that are not VPC-enabled, thus creation of a custom subnet within such Availability Zone would result in a failure. Signed-off-by: Krzysztof Wilczynski * Update documentation for aws_availability_zones data source. Signed-off-by: Krzysztof Wilczynski * Do not filter on state by default. This commit makes the state filter applicable only when set. Signed-off-by: Krzysztof Wilczynski --- .../aws/data_source_availability_zones.go | 51 ++++++++++++++++--- .../data_source_availability_zones_test.go | 42 +++++++++++++-- .../aws/d/availability_zones.html.markdown | 11 ++-- 3 files changed, 89 insertions(+), 15 deletions(-) diff --git a/builtin/providers/aws/data_source_availability_zones.go b/builtin/providers/aws/data_source_availability_zones.go index fd3a6f18a..a9a9d501f 100644 --- a/builtin/providers/aws/data_source_availability_zones.go +++ b/builtin/providers/aws/data_source_availability_zones.go @@ -21,6 +21,11 @@ func dataSourceAwsAvailabilityZones() *schema.Resource { Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, + "state": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateStateType, + }, }, } } @@ -28,25 +33,55 @@ func dataSourceAwsAvailabilityZones() *schema.Resource { func dataSourceAwsAvailabilityZonesRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn - log.Printf("[DEBUG] Reading availability zones") + log.Printf("[DEBUG] Reading Availability Zones.") d.SetId(time.Now().UTC().String()) - req := &ec2.DescribeAvailabilityZonesInput{DryRun: aws.Bool(false)} - azresp, err := conn.DescribeAvailabilityZones(req) - if err != nil { - return fmt.Errorf("Error listing availability zones: %s", err) + request := &ec2.DescribeAvailabilityZonesInput{} + + if v, ok := d.GetOk("state"); ok { + request.Filters = []*ec2.Filter{ + &ec2.Filter{ + Name: aws.String("state"), + Values: []*string{aws.String(v.(string))}, + }, + } } - raw := make([]string, len(azresp.AvailabilityZones)) - for i, v := range azresp.AvailabilityZones { + log.Printf("[DEBUG] Availability Zones request options: %#v", *request) + + resp, err := conn.DescribeAvailabilityZones(request) + if err != nil { + 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 } sort.Strings(raw) if err := d.Set("names", raw); err != nil { - return fmt.Errorf("[WARN] Error setting availability zones") + return fmt.Errorf("[WARN] Error setting Availability Zones: %s", err) } return nil } + +func validateStateType(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + validState := map[string]bool{ + "available": true, + "information": true, + "impaired": true, + "unavailable": true, + } + + if !validState[value] { + errors = append(errors, fmt.Errorf( + "%q contains an invalid Availability Zone state %q. Valid states are: %q, %q, %q and %q.", + k, value, "available", "information", "impaired", "unavailable")) + } + return +} diff --git a/builtin/providers/aws/data_source_availability_zones_test.go b/builtin/providers/aws/data_source_availability_zones_test.go index 86060fb8f..7dbae2398 100644 --- a/builtin/providers/aws/data_source_availability_zones_test.go +++ b/builtin/providers/aws/data_source_availability_zones_test.go @@ -22,6 +22,12 @@ func TestAccAWSAvailabilityZones_basic(t *testing.T) { testAccCheckAwsAvailabilityZonesMeta("data.aws_availability_zones.availability_zones"), ), }, + resource.TestStep{ + Config: testAccCheckAwsAvailabilityZonesStateConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAvailabilityZoneState("data.aws_availability_zones.state_filter"), + ), + }, }, }) } @@ -34,7 +40,7 @@ func testAccCheckAwsAvailabilityZonesMeta(n string) resource.TestCheckFunc { } if rs.Primary.ID == "" { - return fmt.Errorf("AZ resource ID not set") + return fmt.Errorf("AZ resource ID not set.") } actual, err := testAccCheckAwsAvailabilityZonesBuildAvailable(rs.Primary.Attributes) @@ -51,10 +57,33 @@ func testAccCheckAwsAvailabilityZonesMeta(n string) resource.TestCheckFunc { } } +func testAccCheckAwsAvailabilityZoneState(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Can't find AZ resource: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("AZ resource ID not set.") + } + + if _, ok := rs.Primary.Attributes["state"]; !ok { + return fmt.Errorf("AZs state filter is missing, should be set.") + } + + _, err := testAccCheckAwsAvailabilityZonesBuildAvailable(rs.Primary.Attributes) + if err != nil { + return err + } + return nil + } +} + 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 list is missing.") } qty, err := strconv.Atoi(v) if err != nil { @@ -67,7 +96,7 @@ func testAccCheckAwsAvailabilityZonesBuildAvailable(attrs map[string]string) ([] for n := range zones { zone, ok := attrs["names."+strconv.Itoa(n)] if !ok { - return nil, fmt.Errorf("AZ list corrupt, this is definitely a bug") + return nil, fmt.Errorf("AZ list corrupt, this is definitely a bug.") } zones[n] = zone } @@ -75,6 +104,11 @@ func testAccCheckAwsAvailabilityZonesBuildAvailable(attrs map[string]string) ([] } const testAccCheckAwsAvailabilityZonesConfig = ` -data "aws_availability_zones" "availability_zones" { +data "aws_availability_zones" "availability_zones" { } +` + +const testAccCheckAwsAvailabilityZonesStateConfig = ` +data "aws_availability_zones" "state_filter" { + state = "available" } ` diff --git a/website/source/docs/providers/aws/d/availability_zones.html.markdown b/website/source/docs/providers/aws/d/availability_zones.html.markdown index e482a142b..0eb87d781 100644 --- a/website/source/docs/providers/aws/d/availability_zones.html.markdown +++ b/website/source/docs/providers/aws/d/availability_zones.html.markdown @@ -3,7 +3,7 @@ layout: "aws" page_title: "AWS: aws_availability_zones" sidebar_current: "docs-aws-datasource-availability-zones" description: |- - Provides a list of availability zones which can be used by an AWS account + Provides a list of Availability Zones which can be used by an AWS account. --- # aws\_availability\_zones @@ -35,10 +35,15 @@ resource "aws_subnet" "secondary" { ## Argument Reference -There are no arguments for this data source. +The following arguments are supported: + +* `state` - (Optional) Allows to filter list of Availability Zones based on their +current state. Can be either `"available"`, `"information"`, `"impaired"` or +`"unavailable"`. By default the list includes a complete set of Availability Zones +to which the underlying AWS account has access, regardless of their state. ## Attributes Reference The following attributes are exported: -* `names` - A list of the availability zone names available to the account. +* `names` - A list of the Availability Zone names available to the account.