Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

d/aws_route53_resolver_endpoint: Add Route53resolver endpoint datasource #8628

Merged
merged 8 commits into from
Nov 5, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 178 additions & 0 deletions aws/data_source_aws_route53_resolver_endpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package aws

import (
"fmt"

"github.com/aws/aws-sdk-go/service/route53resolver"

"github.com/aws/aws-sdk-go/aws"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func dataSourceAwsRoute53ResolverEndpoint() *schema.Resource {
return &schema.Resource{
Read: dataSourceAwsRoute53ResolverEndpointRead,

Schema: map[string]*schema.Schema{
"filter": {
Type: schema.TypeSet,
Optional: true,
MinItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"values": {
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
},
},

"direction": {
Type: schema.TypeString,
Computed: true,
},

"name": {
Type: schema.TypeString,
Computed: true,
},

"arn": {
Type: schema.TypeString,
Computed: true,
},

"id": {
Type: schema.TypeString,
Optional: true,
},
Copy link
Contributor

@ewbankkit ewbankkit Oct 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rename to resolver_endpoint_id. We don't like to have explicit id attributes.

Suggested change
"id": {
Type: schema.TypeString,
Optional: true,
},
"resolver_endpoint_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},


"status": {
Type: schema.TypeString,
Computed: true,
},
"vpc_id": {
Type: schema.TypeString,
Computed: true,
},
"ip_addresses": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Computed: true,
Set: schema.HashString,
},
},
}
}

func dataSourceAwsRoute53ResolverEndpointRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).route53resolverconn
req := &route53resolver.ListResolverEndpointsInput{}

resolvers := make([]*route53resolver.ResolverEndpoint, 0)

rID, rIDOk := d.GetOk("id")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
rID, rIDOk := d.GetOk("id")
rID, rIDOk := d.GetOk("resolver_endpoint_id")

filters, filtersOk := d.GetOk("filter")

if filtersOk {
req.Filters = buildR53ResolverTagFilters(filters.(*schema.Set))
}

for {
resp, err := conn.ListResolverEndpoints(req)

if err != nil {
return fmt.Errorf("Error Reading Route53 Resolver Endpoints: %s", req)
}

if len(resp.ResolverEndpoints) == 0 && filtersOk {
return fmt.Errorf("Your query returned no results. Please change your search criteria and try again")
}

if len(resp.ResolverEndpoints) > 1 && !rIDOk {
return fmt.Errorf("your query returned more than one resolver. Please change your search criteria and try again")
}

if rIDOk {
for _, r := range resp.ResolverEndpoints {
if aws.StringValue(r.Id) == rID {
resolvers = append(resolvers, r)
break
}
}
} else {
resolvers = append(resolvers, resp.ResolverEndpoints[0])
}

if len(resolvers) == 0 {
return fmt.Errorf("The ID provided could not be found")
}

resolver := resolvers[0]

d.SetId(aws.StringValue(resolver.Id))
robh007 marked this conversation as resolved.
Show resolved Hide resolved
d.Set("arn", aws.StringValue(resolver.Arn))
d.Set("status", aws.StringValue(resolver.Status))
d.Set("name", aws.StringValue(resolver.Name))
d.Set("vpc_id", aws.StringValue(resolver.HostVPCId))
d.Set("direction", aws.StringValue(resolver.Direction))

if resp.NextToken == nil {
break
}

req.NextToken = resp.NextToken
}

params := &route53resolver.ListResolverEndpointIpAddressesInput{
ResolverEndpointId: aws.String(d.Id()),
}

ipAddresses := []interface{}{}

for {
ip, err := conn.ListResolverEndpointIpAddresses(params)

if err != nil {
return fmt.Errorf("error getting Route53 Resolver endpoint (%s) IP Addresses: %s", d.Id(), err)
}

for _, vIPAddresses := range ip.IpAddresses {
ipAddresses = append(ipAddresses, aws.StringValue(vIPAddresses.Ip))
}

d.Set("ip_addresses", ipAddresses)

if ip.NextToken == nil {
break
}

params.NextToken = ip.NextToken
}

return nil
}

func buildR53ResolverTagFilters(set *schema.Set) []*route53resolver.Filter {
var filters []*route53resolver.Filter

for _, v := range set.List() {
m := v.(map[string]interface{})
var filterValues []*string
for _, e := range m["values"].([]interface{}) {
filterValues = append(filterValues, aws.String(e.(string)))
}
filters = append(filters, &route53resolver.Filter{
Name: aws.String(m["name"].(string)),
Values: filterValues,
})
}

return filters
}
217 changes: 217 additions & 0 deletions aws/data_source_aws_route53_resolver_endpoint_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
package aws

import (
"fmt"
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func TestAccDataSourceAwsRoute53ResolverEndpoint_Basic(t *testing.T) {
name := acctest.RandomWithPrefix("tf-acc-test")
rInt := acctest.RandInt()
direction := "INBOUND"
resourceName := "aws_route53_resolver_endpoint.foo"
datasourceName := "data.aws_route53_resolver_endpoint.foo"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourceAwsRoute53ResolverEndpointConfig_NonExistent,
ExpectError: regexp.MustCompile(`The ID provided could not be found`),
},
{
Config: testAccDataSourceRoute53ResolverEndpointConfig_initial(rInt, direction, name),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrPair(datasourceName, "name", resourceName, "name"),
resource.TestCheckResourceAttrPair(datasourceName, "id", resourceName, "id"),
robh007 marked this conversation as resolved.
Show resolved Hide resolved
resource.TestCheckResourceAttr(datasourceName, "ip_addresses.#", "2"),
),
},
},
})
}

func TestAccDataSourceAwsRoute53ResolverEndpoint_Filter(t *testing.T) {
name := acctest.RandomWithPrefix("tf-acc-test")
rInt := acctest.RandInt()
direction := "OUTBOUND"
resourceName := "aws_route53_resolver_endpoint.foo"
datasourceName := "data.aws_route53_resolver_endpoint.foo"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourceAwsRoute53ResolverEndpointConfig_NonExistentFilter,
ExpectError: regexp.MustCompile("Your query returned no results. Please change your search criteria and try again"),
},
{
Config: testAccDataSourceRoute53ResolverEndpointConfig_filter(rInt, direction, name),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrPair(datasourceName, "name", resourceName, "name"),
resource.TestCheckResourceAttrPair(datasourceName, "id", resourceName, "id"),
resource.TestCheckResourceAttr(datasourceName, "ip_addresses.#", "2"),
),
},
},
})
}

func testAccDataSourceRoute53ResolverEndpointConfig_base(rInt int) string {
return fmt.Sprintf(`
resource "aws_vpc" "foo" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true

tags = {
Name = "terraform-testacc-r53-resolver-vpc-%d"
}
}

data "aws_availability_zones" "available" {}
Copy link
Contributor

@ewbankkit ewbankkit Oct 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use testAccAvailableAZsNoOptInConfig

https://github.com/terraform-providers/terraform-provider-aws/blob/aff071d49120c136dcb4917133b4eaa6512f0482/aws/resource_aws_instance_test.go#L3301-L3312

otherwise the default acceptance test regions may use Local Zone AZs and get errors like:

=== CONT  TestAccDataSourceAwsRoute53ResolverEndpoint_Basic
    data_source_aws_route53_resolver_endpoint_test.go:19: Step 2/2 error: Error running apply: 2020/10/29 10:40:11 [DEBUG] Using modified User-Agent: Terraform/0.12.26 HashiCorp-terraform-exec/0.10.0
        
        Error: error creating Route53 Resolver endpoint: InvalidRequestException: [RSLVR-00419] There are no resolver resources in the Availability Zone containing subnet: "subnet-055bec065a57e028c"
        
        
--- FAIL: TestAccDataSourceAwsRoute53ResolverEndpoint_Basic (23.32s)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ewbankkit I'll get these resolved today.


resource "aws_subnet" "sn1" {
vpc_id = aws_vpc.foo.id
cidr_block = cidrsubnet(aws_vpc.foo.cidr_block, 2, 0)
availability_zone = data.aws_availability_zones.available.names[0]

tags = {
Name = "tf-acc-r53-resolver-sn1-%d"
}
}

resource "aws_subnet" "sn2" {
vpc_id = aws_vpc.foo.id

cidr_block = cidrsubnet(aws_vpc.foo.cidr_block, 2, 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alignment.

Suggested change
vpc_id = aws_vpc.foo.id
cidr_block = cidrsubnet(aws_vpc.foo.cidr_block, 2, 1)
vpc_id = aws_vpc.foo.id
cidr_block = cidrsubnet(aws_vpc.foo.cidr_block, 2, 1)

availability_zone = data.aws_availability_zones.available.names[1]

tags = {
Name = "tf-acc-r53-resolver-sn2-%d"
}
}

resource "aws_subnet" "sn3" {
vpc_id = aws_vpc.foo.id
cidr_block = cidrsubnet(aws_vpc.foo.cidr_block, 2, 2)
availability_zone = data.aws_availability_zones.available.names[2]

tags = {
Name = "tf-acc-r53-resolver-sn3-%d"
}
}

resource "aws_security_group" "sg1" {
vpc_id = aws_vpc.foo.id
name = "tf-acc-r53-resolver-sg1-%d"

tags = {
Name = "tf-acc-r53-resolver-sg1-%d"
}
}

resource "aws_security_group" "sg2" {
vpc_id = aws_vpc.foo.id
name = "tf-acc-r53-resolver-sg2-%d"

tags = {
Name = "tf-acc-r53-resolver-sg2-%d"
}
}
`, rInt, rInt, rInt, rInt, rInt, rInt, rInt, rInt)
}

func testAccDataSourceRoute53ResolverEndpointConfig_initial(rInt int, direction, name string) string {
return fmt.Sprintf(`
%s
Copy link
Contributor

@ewbankkit ewbankkit Oct 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can use composeConfig here.


resource "aws_route53_resolver_endpoint" "foo" {
direction = "%s"
name = "%s"

security_group_ids = [
aws_security_group.sg1.id,
aws_security_group.sg2.id,
]

ip_address {
subnet_id = aws_subnet.sn1.id
}

ip_address {
subnet_id = aws_subnet.sn2.id
ip = cidrhost(aws_subnet.sn2.cidr_block, 8)
}

tags = {
Environment = "production"
Usage = "original"
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alignment.

Suggested change
tags = {
Environment = "production"
Usage = "original"
}
tags = {
Environment = "production"
Usage = "original"
}

}

data "aws_route53_resolver_endpoint" "foo" {
id = aws_route53_resolver_endpoint.foo.id
}
Copy link
Contributor

@ewbankkit ewbankkit Oct 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spaces not tabs.

Suggested change
data "aws_route53_resolver_endpoint" "foo" {
id = aws_route53_resolver_endpoint.foo.id
}
data "aws_route53_resolver_endpoint" "foo" {
resolver_endpoint_id = aws_route53_resolver_endpoint.foo.id
}

`, testAccDataSourceRoute53ResolverEndpointConfig_base(rInt), direction, name)
}

func testAccDataSourceRoute53ResolverEndpointConfig_filter(rInt int, direction, name string) string {
return fmt.Sprintf(`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can use composeConfig here.

%s

resource "aws_route53_resolver_endpoint" "foo" {
direction = "%s"
name = "%s"

security_group_ids = [
aws_security_group.sg1.id,
aws_security_group.sg2.id,
]

ip_address {
subnet_id = aws_subnet.sn1.id
}

ip_address {
subnet_id = aws_subnet.sn2.id
ip = cidrhost(aws_subnet.sn2.cidr_block, 8)
}

tags = {
Environment = "production"
Usage = "original"
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alignment.

Suggested change
tags = {
Environment = "production"
Usage = "original"
}
tags = {
Environment = "production"
Usage = "original"
}

}

data "aws_route53_resolver_endpoint" "foo" {
filter {
name = "Name"
values = [aws_route53_resolver_endpoint.foo.name]
}

depends_on = [aws_route53_resolver_endpoint.foo]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 spaces, not 3.

Suggested change
depends_on = [aws_route53_resolver_endpoint.foo]
depends_on = [aws_route53_resolver_endpoint.foo]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, remove the depends_on line otherwise I get:

=== CONT  TestAccDataSourceAwsRoute53ResolverEndpoint_Filter
    data_source_aws_route53_resolver_endpoint_test.go:47: Step 2/2 error: After applying this test step and performing a `terraform refresh`, the plan was not empty.
        stdout
        
        
        An execution plan has been generated and is shown below.
        Resource actions are indicated with the following symbols:
         <= read (data resources)
        
        Terraform will perform the following actions:
        
          # data.aws_route53_resolver_endpoint.foo will be read during apply
          # (config refers to values not yet known)
         <= data "aws_route53_resolver_endpoint" "foo"  {
              + arn          = (known after apply)
              + direction    = (known after apply)
              + id           = (known after apply)
              + ip_addresses = (known after apply)
              + name         = (known after apply)
              + status       = (known after apply)
              + vpc_id       = (known after apply)
        
              + filter {
                  + name   = "Name"
                  + values = [
                      + "tf-acc-test-6750279126925066157",
                    ]
                }
            }
        
        Plan: 0 to add, 0 to change, 0 to destroy.
--- FAIL: TestAccDataSourceAwsRoute53ResolverEndpoint_Filter (243.61s)

Copy link
Contributor Author

@robh007 robh007 Nov 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your patience @ewbankkit, when I did this work last year I didn't have a depends_on within the filter test, but when I came back to pick this up after your initial review the filter test started to fail on the data lookup. Which I found slightly odd. The AWS api had since changed the filter values, I think initially they where uppercase. However when I read the docs & both case types are supported. When I added the depends_on the error went away. I assumed something as changed within terraform at this point & not the provider.

If I remove the depends_on I get the following output.

=== CONT  TestAccDataSourceAwsRoute53ResolverEndpoint_Filter
    data_source_aws_route53_resolver_endpoint_test.go:47: Step 2/2 error: Error running pre-apply plan: 
        Error: Your query returned no results. Please change your search criteria and try again
        
        
--- FAIL: TestAccDataSourceAwsRoute53ResolverEndpoint_Filter (16.33s)
--- PASS: TestAccDataSourceAwsRoute53ResolverEndpoint_Basic (117.56s)
FAIL
FAIL    github.com/terraform-providers/terraform-provider-aws/aws       119.311s
FAIL

If I leave the depends_on in the test passes.

TF_ACC=1 go test ./aws -v -count 1 -parallel 20 -run=TestAccDataSourceAwsRoute53ResolverEndpoint -timeout 120m
=== RUN   TestAccDataSourceAwsRoute53ResolverEndpoint_Basic
=== PAUSE TestAccDataSourceAwsRoute53ResolverEndpoint_Basic
=== RUN   TestAccDataSourceAwsRoute53ResolverEndpoint_Filter
=== PAUSE TestAccDataSourceAwsRoute53ResolverEndpoint_Filter
=== CONT  TestAccDataSourceAwsRoute53ResolverEndpoint_Basic
=== CONT  TestAccDataSourceAwsRoute53ResolverEndpoint_Filter
--- PASS: TestAccDataSourceAwsRoute53ResolverEndpoint_Basic (107.22s)
--- PASS: TestAccDataSourceAwsRoute53ResolverEndpoint_Filter (239.47s)
PASS
ok      github.com/terraform-providers/terraform-provider-aws/aws       241.261s

Not sure where to go with this now, I noticed the name parameter is optional on the resource so I'm not sure if that causes a potential issue with the initial plan. If I add a second filter using SecurityGroupIds I don't see the failure.

TF_ACC=1 go test ./aws -v -count 1 -parallel 20 -run=TestAccDataSourceAwsRoute53ResolverEndpoint -timeout 120m
=== RUN   TestAccDataSourceAwsRoute53ResolverEndpoint_Basic
=== PAUSE TestAccDataSourceAwsRoute53ResolverEndpoint_Basic
=== RUN   TestAccDataSourceAwsRoute53ResolverEndpoint_Filter
=== PAUSE TestAccDataSourceAwsRoute53ResolverEndpoint_Filter
=== CONT  TestAccDataSourceAwsRoute53ResolverEndpoint_Basic
=== CONT  TestAccDataSourceAwsRoute53ResolverEndpoint_Filter
--- PASS: TestAccDataSourceAwsRoute53ResolverEndpoint_Basic (118.19s)
--- PASS: TestAccDataSourceAwsRoute53ResolverEndpoint_Filter (250.14s)
PASS
ok      github.com/terraform-providers/terraform-provider-aws/aws       252.067s
data "aws_route53_resolver_endpoint" "foo" {
    filter {
      name   = "Name"
      values = [aws_route53_resolver_endpoint.foo.name]
    }

    filter {
      name = "SecurityGroupIds"
      values = [aws_security_group.sg1.id, aws_security_group.sg2.id]
    }
}

}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spaces not tabs.

Suggested change
data "aws_route53_resolver_endpoint" "foo" {
filter {
name = "Name"
values = [aws_route53_resolver_endpoint.foo.name]
}
depends_on = [aws_route53_resolver_endpoint.foo]
}
data "aws_route53_resolver_endpoint" "foo" {
filter {
name = "Name"
values = [aws_route53_resolver_endpoint.foo.name]
}
depends_on = [aws_route53_resolver_endpoint.foo]
}

`, testAccDataSourceRoute53ResolverEndpointConfig_base(rInt), direction, name)
}

const testAccDataSourceAwsRoute53ResolverEndpointConfig_NonExistent = `
data "aws_route53_resolver_endpoint" "foo" {
id = "rslvr-in-8g85830108dd4c82b"
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spaces not tabs.

Suggested change
data "aws_route53_resolver_endpoint" "foo" {
id = "rslvr-in-8g85830108dd4c82b"
}
data "aws_route53_resolver_endpoint" "foo" {
id = "rslvr-in-8g85830108dd4c82b"
}

`

const testAccDataSourceAwsRoute53ResolverEndpointConfig_NonExistentFilter = `
data "aws_route53_resolver_endpoint" "foo" {
filter {
name = "Name"
values = ["None-Existent-Resource"]
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spaces not tabs.

Suggested change
data "aws_route53_resolver_endpoint" "foo" {
filter {
name = "Name"
values = ["None-Existent-Resource"]
}
}
data "aws_route53_resolver_endpoint" "foo" {
filter {
name = "Name"
values = ["None-Existent-Resource"]
}
}

`
Loading