From 64fda44b00c320237d89b770e164a4fc8c477be2 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 Feb 2017 11:24:55 -0500 Subject: [PATCH] Add 'aws_vpn_gateway' data source. (#11886) --- .../aws/data_source_aws_vpn_gateway.go | 105 ++++++++++++++++ .../aws/data_source_aws_vpn_gateway_test.go | 114 ++++++++++++++++++ builtin/providers/aws/provider.go | 3 +- helper/resource/testing.go | 105 ++++++++++------ .../providers/aws/d/vpn_gateway.html.markdown | 49 ++++++++ website/source/layouts/aws.erb | 3 + 6 files changed, 341 insertions(+), 38 deletions(-) create mode 100644 builtin/providers/aws/data_source_aws_vpn_gateway.go create mode 100644 builtin/providers/aws/data_source_aws_vpn_gateway_test.go create mode 100644 website/source/docs/providers/aws/d/vpn_gateway.html.markdown diff --git a/builtin/providers/aws/data_source_aws_vpn_gateway.go b/builtin/providers/aws/data_source_aws_vpn_gateway.go new file mode 100644 index 000000000..5d088e548 --- /dev/null +++ b/builtin/providers/aws/data_source_aws_vpn_gateway.go @@ -0,0 +1,105 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceAwsVpnGateway() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsVpnGatewayRead, + + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "state": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "attached_vpc_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "availability_zone": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "filter": ec2CustomFiltersSchema(), + "tags": tagsSchemaComputed(), + }, + } +} + +func dataSourceAwsVpnGatewayRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + log.Printf("[DEBUG] Reading VPN Gateways.") + + req := &ec2.DescribeVpnGatewaysInput{} + + if id, ok := d.GetOk("id"); ok { + req.VpnGatewayIds = aws.StringSlice([]string{id.(string)}) + } + + req.Filters = buildEC2AttributeFilterList( + map[string]string{ + "state": d.Get("state").(string), + "availability-zone": d.Get("availability_zone").(string), + }, + ) + if id, ok := d.GetOk("attached_vpc_id"); ok { + req.Filters = append(req.Filters, buildEC2AttributeFilterList( + map[string]string{ + "attachment.state": "attached", + "attachment.vpc-id": id.(string), + }, + )...) + } + req.Filters = append(req.Filters, buildEC2TagFilterList( + tagsFromMap(d.Get("tags").(map[string]interface{})), + )...) + req.Filters = append(req.Filters, buildEC2CustomFilterList( + d.Get("filter").(*schema.Set), + )...) + if len(req.Filters) == 0 { + // Don't send an empty filters list; the EC2 API won't accept it. + req.Filters = nil + } + + resp, err := conn.DescribeVpnGateways(req) + if err != nil { + return err + } + if resp == nil || len(resp.VpnGateways) == 0 { + return fmt.Errorf("no matching VPN gateway found: %#v", req) + } + if len(resp.VpnGateways) > 1 { + return fmt.Errorf("multiple VPN gateways matched; use additional constraints to reduce matches to a single VPN gateway") + } + + vgw := resp.VpnGateways[0] + + d.SetId(aws.StringValue(vgw.VpnGatewayId)) + d.Set("state", vgw.State) + d.Set("availability_zone", vgw.AvailabilityZone) + d.Set("tags", tagsToMap(vgw.Tags)) + + for _, attachment := range vgw.VpcAttachments { + if *attachment.State == "attached" { + d.Set("attached_vpc_id", attachment.VpcId) + break + } + } + + return nil +} diff --git a/builtin/providers/aws/data_source_aws_vpn_gateway_test.go b/builtin/providers/aws/data_source_aws_vpn_gateway_test.go new file mode 100644 index 000000000..e082e844e --- /dev/null +++ b/builtin/providers/aws/data_source_aws_vpn_gateway_test.go @@ -0,0 +1,114 @@ +// make testacc TEST=./builtin/providers/aws/ TESTARGS='-run=TestAccDataSourceAwsVpnGateway_' +package aws + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccDataSourceAwsVpnGateway_unattached(t *testing.T) { + rInt := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataSourceAwsVpnGatewayUnattachedConfig(rInt), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair( + "data.aws_vpn_gateway.test_by_id", "id", + "aws_vpn_gateway.unattached", "id"), + resource.TestCheckResourceAttrPair( + "data.aws_vpn_gateway.test_by_tags", "id", + "aws_vpn_gateway.unattached", "id"), + resource.TestCheckResourceAttrSet("data.aws_vpn_gateway.test_by_id", "state"), + resource.TestCheckResourceAttr("data.aws_vpn_gateway.test_by_tags", "tags.%", "3"), + resource.TestCheckNoResourceAttr("data.aws_vpn_gateway.test_by_id", "attached_vpc_id"), + ), + }, + }, + }) +} + +func TestAccDataSourceAwsVpnGateway_attached(t *testing.T) { + rInt := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataSourceAwsVpnGatewayAttachedConfig(rInt), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair( + "data.aws_vpn_gateway.test_by_attached_vpc_id", "id", + "aws_vpn_gateway.attached", "id"), + resource.TestCheckResourceAttrPair( + "data.aws_vpn_gateway.test_by_attached_vpc_id", "attached_vpc_id", + "aws_vpc.foo", "id"), + resource.TestMatchResourceAttr("data.aws_vpn_gateway.test_by_attached_vpc_id", "state", regexp.MustCompile("(?i)available")), + ), + }, + }, + }) +} + +func testAccDataSourceAwsVpnGatewayUnattachedConfig(rInt int) string { + return fmt.Sprintf(` +provider "aws" { + region = "us-west-2" +} + +resource "aws_vpn_gateway" "unattached" { + tags { + Name = "terraform-testacc-vpn-gateway-data-source-unattached-%d" + ABC = "testacc-%d" + XYZ = "testacc-%d" + } +} + +data "aws_vpn_gateway" "test_by_id" { + id = "${aws_vpn_gateway.unattached.id}" +} + +data "aws_vpn_gateway" "test_by_tags" { + tags = "${aws_vpn_gateway.unattached.tags}" +} +`, rInt, rInt+1, rInt-1) +} + +func testAccDataSourceAwsVpnGatewayAttachedConfig(rInt int) string { + return fmt.Sprintf(` +provider "aws" { + region = "us-west-2" +} + +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" + + tags { + Name = "terraform-testacc-vpn-gateway-data-source-foo-%d" + } +} + +resource "aws_vpn_gateway" "attached" { + tags { + Name = "terraform-testacc-vpn-gateway-data-source-attached-%d" + } +} + +resource "aws_vpn_gateway_attachment" "vpn_attachment" { + vpc_id = "${aws_vpc.foo.id}" + vpn_gateway_id = "${aws_vpn_gateway.attached.id}" +} + +data "aws_vpn_gateway" "test_by_attached_vpc_id" { + attached_vpc_id = "${aws_vpn_gateway_attachment.vpn_attachment.vpc_id}" +} +`, rInt, rInt) +} diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index fe99bc38c..308d01a27 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -177,6 +177,7 @@ func Provider() terraform.ResourceProvider { "aws_iam_server_certificate": dataSourceAwsIAMServerCertificate(), "aws_instance": dataSourceAwsInstance(), "aws_ip_ranges": dataSourceAwsIPRanges(), + "aws_kms_secret": dataSourceAwsKmsSecret(), "aws_partition": dataSourceAwsPartition(), "aws_prefix_list": dataSourceAwsPrefixList(), "aws_redshift_service_account": dataSourceAwsRedshiftServiceAccount(), @@ -190,7 +191,7 @@ func Provider() terraform.ResourceProvider { "aws_vpc_endpoint": dataSourceAwsVpcEndpoint(), "aws_vpc_endpoint_service": dataSourceAwsVpcEndpointService(), "aws_vpc_peering_connection": dataSourceAwsVpcPeeringConnection(), - "aws_kms_secret": dataSourceAwsKmsSecret(), + "aws_vpn_gateway": dataSourceAwsVpnGateway(), }, ResourcesMap: map[string]*schema.Resource{ diff --git a/helper/resource/testing.go b/helper/resource/testing.go index 014e5411d..6d2deb9e5 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -549,15 +549,9 @@ func ComposeAggregateTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc { // know ahead of time what the values will be. func TestCheckResourceAttrSet(name, key string) TestCheckFunc { return func(s *terraform.State) error { - ms := s.RootModule() - rs, ok := ms.Resources[name] - if !ok { - return fmt.Errorf("Not found: %s", name) - } - - is := rs.Primary - if is == nil { - return fmt.Errorf("No primary instance: %s", name) + is, err := primaryInstanceState(s, name) + if err != nil { + return err } if val, ok := is.Attributes[key]; ok && val != "" { @@ -568,17 +562,13 @@ func TestCheckResourceAttrSet(name, key string) TestCheckFunc { } } +// TestCheckResourceAttr is a TestCheckFunc which validates +// the value in state for the given name/key combination. func TestCheckResourceAttr(name, key, value string) TestCheckFunc { return func(s *terraform.State) error { - ms := s.RootModule() - rs, ok := ms.Resources[name] - if !ok { - return fmt.Errorf("Not found: %s", name) - } - - is := rs.Primary - if is == nil { - return fmt.Errorf("No primary instance: %s", name) + is, err := primaryInstanceState(s, name) + if err != nil { + return err } if v, ok := is.Attributes[key]; !ok || v != value { @@ -591,7 +581,7 @@ func TestCheckResourceAttr(name, key, value string) TestCheckFunc { name, key, value, - is.Attributes[key]) + v) } return nil @@ -602,15 +592,9 @@ func TestCheckResourceAttr(name, key, value string) TestCheckFunc { // NO value exists in state for the given name/key combination. func TestCheckNoResourceAttr(name, key string) TestCheckFunc { return func(s *terraform.State) error { - ms := s.RootModule() - rs, ok := ms.Resources[name] - if !ok { - return fmt.Errorf("Not found: %s", name) - } - - is := rs.Primary - if is == nil { - return fmt.Errorf("No primary instance: %s", name) + is, err := primaryInstanceState(s, name) + if err != nil { + return err } if _, ok := is.Attributes[key]; ok { @@ -621,17 +605,13 @@ func TestCheckNoResourceAttr(name, key string) TestCheckFunc { } } +// TestMatchResourceAttr is a TestCheckFunc which checks that the value +// in state for the given name/key combination matches the given regex. func TestMatchResourceAttr(name, key string, r *regexp.Regexp) TestCheckFunc { return func(s *terraform.State) error { - ms := s.RootModule() - rs, ok := ms.Resources[name] - if !ok { - return fmt.Errorf("Not found: %s", name) - } - - is := rs.Primary - if is == nil { - return fmt.Errorf("No primary instance: %s", name) + is, err := primaryInstanceState(s, name) + if err != nil { + return err } if !r.MatchString(is.Attributes[key]) { @@ -656,6 +636,41 @@ func TestCheckResourceAttrPtr(name string, key string, value *string) TestCheckF } } +// TestCheckResourceAttrPair is a TestCheckFunc which validates that the values +// in state for a pair of name/key combinations are equal. +func TestCheckResourceAttrPair(nameFirst, keyFirst, nameSecond, keySecond string) TestCheckFunc { + return func(s *terraform.State) error { + isFirst, err := primaryInstanceState(s, nameFirst) + if err != nil { + return err + } + vFirst, ok := isFirst.Attributes[keyFirst] + if !ok { + return fmt.Errorf("%s: Attribute '%s' not found", nameFirst, keyFirst) + } + + isSecond, err := primaryInstanceState(s, nameSecond) + if err != nil { + return err + } + vSecond, ok := isSecond.Attributes[keySecond] + if !ok { + return fmt.Errorf("%s: Attribute '%s' not found", nameSecond, keySecond) + } + + if vFirst != vSecond { + return fmt.Errorf( + "%s: Attribute '%s' expected %#v, got %#v", + nameFirst, + keyFirst, + vSecond, + vFirst) + } + + return nil + } +} + // TestCheckOutput checks an output in the Terraform configuration func TestCheckOutput(name, value string) TestCheckFunc { return func(s *terraform.State) error { @@ -708,3 +723,19 @@ type TestT interface { // This is set to true by unit tests to alter some behavior var testTesting = false + +// primaryInstanceState returns the primary instance state for the given resource name. +func primaryInstanceState(s *terraform.State, name string) (*terraform.InstanceState, error) { + ms := s.RootModule() + rs, ok := ms.Resources[name] + if !ok { + return nil, fmt.Errorf("Not found: %s", name) + } + + is := rs.Primary + if is == nil { + return nil, fmt.Errorf("No primary instance: %s", name) + } + + return is, nil +} diff --git a/website/source/docs/providers/aws/d/vpn_gateway.html.markdown b/website/source/docs/providers/aws/d/vpn_gateway.html.markdown new file mode 100644 index 000000000..150578552 --- /dev/null +++ b/website/source/docs/providers/aws/d/vpn_gateway.html.markdown @@ -0,0 +1,49 @@ +--- +layout: "aws" +page_title: "AWS: aws_vpn_gateway" +sidebar_current: "docs-aws-datasource-vpn-gateway" +description: |- + Provides details about a specific VPN gateway. +--- + +# aws\_vpn\_gateway + +The VPN Gateway data source provides details about +a specific VPN gateway. + +## Example Usage + +``` + +``` + +## Argument Reference + +The arguments of this data source act as filters for querying the available VPN gateways. +The given filters must match exactly one VPN gateway whose data will be exported as attributes. + +* `id` - (Optional) The ID of the specific VPN Gateway to retrieve. + +* `state` - (Optional) The state of the specific VPN Gateway to retrieve. + +* `availability_zone` - (Optional) The Availability Zone of the specific VPN Gateway to retrieve. + +* `attached_vpc_id` - (Optional) The ID of a VPC attached to the specific VPN Gateway to retrieve. + +* `filter` - (Optional) Custom filter block as described below. + +* `tags` - (Optional) A mapping of tags, each pair of which must exactly match + a pair on the desired VPN Gateway. + +More complex filters can be expressed using one or more `filter` sub-blocks, +which take the following arguments: + +* `name` - (Required) The name of the field to filter by, as defined by + [the underlying AWS API](http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeVpnGateways.html). + +* `values` - (Required) Set of values that are accepted for the given field. + A VPN Gateway will be selected if any one of the given values matches. + +## Attributes Reference + +All of the argument attributes are also exported as result attributes. diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 3d71f30f8..cb3bb07e6 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -122,6 +122,9 @@ > aws_vpc_peering_connection + > + aws_vpn_gateway +