Skip to content

Commit

Permalink
Merge pull request #15467 from terraform-providers/f-aws_vpc_endpoint…
Browse files Browse the repository at this point in the history
…_service-service_type-argument

data-source/aws_vpc_endpoint_service: Accept service_type as argument
  • Loading branch information
gdavison authored Oct 8, 2020
2 parents 571d897 + a2cb192 commit b7c6594
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 11 deletions.
23 changes: 21 additions & 2 deletions aws/data_source_aws_vpc_endpoint_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func dataSourceAwsVpcEndpointService() *schema.Resource {
},
"service_type": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"tags": tagsSchemaComputed(),
Expand Down Expand Up @@ -133,11 +134,29 @@ func dataSourceAwsVpcEndpointServiceRead(d *schema.ResourceData, meta interface{
return fmt.Errorf("no matching VPC Endpoint Service found")
}

if len(resp.ServiceDetails) > 1 {
var serviceDetails []*ec2.ServiceDetail

// Client-side filtering. When the EC2 API supports this functionality
// server-side it should be moved.
for _, serviceDetail := range resp.ServiceDetails {
if serviceDetail == nil {
continue
}

if v, ok := d.GetOk("service_type"); ok {
if len(serviceDetail.ServiceType) > 0 && serviceDetail.ServiceType[0] != nil && v.(string) != aws.StringValue(serviceDetail.ServiceType[0].ServiceType) {
continue
}
}

serviceDetails = append(serviceDetails, serviceDetail)
}

if len(serviceDetails) > 1 {
return fmt.Errorf("multiple VPC Endpoint Services matched; use additional constraints to reduce matches to a single VPC Endpoint Service")
}

sd := resp.ServiceDetails[0]
sd := serviceDetails[0]
serviceId := aws.StringValue(sd.ServiceId)
serviceName = aws.StringValue(sd.ServiceName)

Expand Down
55 changes: 49 additions & 6 deletions aws/data_source_aws_vpc_endpoint_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (

func TestAccDataSourceAwsVpcEndpointService_gateway(t *testing.T) {
datasourceName := "data.aws_vpc_endpoint_service.test"
region := testAccGetRegion()

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Expand All @@ -20,7 +19,7 @@ func TestAccDataSourceAwsVpcEndpointService_gateway(t *testing.T) {
{
Config: testAccDataSourceAwsVpcEndpointServiceGatewayConfig,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(datasourceName, "service_name", fmt.Sprintf("com.amazonaws.%s.s3", region)),
testAccCheckResourceAttrRegionalReverseDnsService(datasourceName, "service_name", "dynamodb"),
resource.TestCheckResourceAttr(datasourceName, "acceptance_required", "false"),
resource.TestCheckResourceAttrPair(datasourceName, "availability_zones.#", "data.aws_availability_zones.available", "names.#"),
resource.TestCheckResourceAttr(datasourceName, "base_endpoint_dns_names.#", "1"),
Expand All @@ -39,7 +38,6 @@ func TestAccDataSourceAwsVpcEndpointService_gateway(t *testing.T) {

func TestAccDataSourceAwsVpcEndpointService_interface(t *testing.T) {
datasourceName := "data.aws_vpc_endpoint_service.test"
region := testAccGetRegion()

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Expand All @@ -48,12 +46,12 @@ func TestAccDataSourceAwsVpcEndpointService_interface(t *testing.T) {
{
Config: testAccDataSourceAwsVpcEndpointServiceInterfaceConfig,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(datasourceName, "service_name", fmt.Sprintf("com.amazonaws.%s.ec2", region)),
testAccCheckResourceAttrRegionalReverseDnsService(datasourceName, "service_name", "ec2"),
resource.TestCheckResourceAttr(datasourceName, "acceptance_required", "false"),
resource.TestCheckResourceAttr(datasourceName, "base_endpoint_dns_names.#", "1"),
resource.TestCheckResourceAttr(datasourceName, "manages_vpc_endpoints", "false"),
resource.TestCheckResourceAttr(datasourceName, "owner", "amazon"),
resource.TestCheckResourceAttr(datasourceName, "private_dns_name", fmt.Sprintf("ec2.%s.%s", region, testAccGetPartitionDNSSuffix())),
testAccCheckResourceAttrRegionalHostnameService(datasourceName, "private_dns_name", "ec2"),
resource.TestCheckResourceAttr(datasourceName, "service_type", "Interface"),
resource.TestCheckResourceAttr(datasourceName, "vpc_endpoint_policy_supported", "true"),
resource.TestCheckResourceAttr(datasourceName, "tags.%", "0"),
Expand Down Expand Up @@ -142,11 +140,47 @@ func TestAccDataSourceAwsVpcEndpointService_custom_filter_tags(t *testing.T) {
})
}

func TestAccDataSourceAwsVpcEndpointService_ServiceType_Gateway(t *testing.T) {
datasourceName := "data.aws_vpc_endpoint_service.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourceAwsVpcEndpointServiceConfig_ServiceType("s3", "Gateway"),
Check: resource.ComposeTestCheckFunc(
testAccCheckResourceAttrRegionalReverseDnsService(datasourceName, "service_name", "s3"),
resource.TestCheckResourceAttr(datasourceName, "service_type", "Gateway"),
),
},
},
})
}

func TestAccDataSourceAwsVpcEndpointService_ServiceType_Interface(t *testing.T) {
datasourceName := "data.aws_vpc_endpoint_service.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourceAwsVpcEndpointServiceConfig_ServiceType("ec2", "Interface"),
Check: resource.ComposeTestCheckFunc(
testAccCheckResourceAttrRegionalReverseDnsService(datasourceName, "service_name", "ec2"),
resource.TestCheckResourceAttr(datasourceName, "service_type", "Interface"),
),
},
},
})
}

const testAccDataSourceAwsVpcEndpointServiceGatewayConfig = `
data "aws_availability_zones" "available" {}
data "aws_vpc_endpoint_service" "test" {
service = "s3"
service = "dynamodb"
}
`

Expand All @@ -156,6 +190,15 @@ data "aws_vpc_endpoint_service" "test" {
}
`

func testAccDataSourceAwsVpcEndpointServiceConfig_ServiceType(service string, serviceType string) string {
return fmt.Sprintf(`
data "aws_vpc_endpoint_service" "test" {
service = %[1]q
service_type = %[2]q
}
`, service, serviceType)
}

func testAccDataSourceAwsVpcEndpointServiceCustomConfigBase(rName string) string {
return fmt.Sprintf(`
resource "aws_vpc" "test" {
Expand Down
33 changes: 33 additions & 0 deletions aws/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"reflect"
"regexp"
"sort"
"strings"
"testing"

Expand Down Expand Up @@ -163,6 +164,28 @@ func testAccCheckResourceAttrRegionalHostname(resourceName, attributeName, servi
}
}

// testAccCheckResourceAttrRegionalHostnameService ensures the Terraform state exactly matches a service DNS hostname with region and partition DNS suffix
//
// For example: ec2.us-west-2.amazonaws.com
func testAccCheckResourceAttrRegionalHostnameService(resourceName, attributeName, serviceName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
hostname := fmt.Sprintf("%s.%s.%s", serviceName, testAccGetRegion(), testAccGetPartitionDNSSuffix())

return resource.TestCheckResourceAttr(resourceName, attributeName, hostname)(s)
}
}

// testAccCheckResourceAttrRegionalReverseDnsService ensures the Terraform state exactly matches a service reverse DNS hostname with region and partition DNS suffix
//
// For example: com.amazonaws.us-west-2.s3
func testAccCheckResourceAttrRegionalReverseDnsService(resourceName, attributeName, serviceName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
reverseDns := fmt.Sprintf("%s.%s.%s", testAccGetPartitionReverseDNSPrefix(), testAccGetRegion(), serviceName)

return resource.TestCheckResourceAttr(resourceName, attributeName, reverseDns)(s)
}
}

// testAccCheckResourceAttrHostnameWithPort ensures the Terraform state regexp matches a formatted DNS hostname with prefix, partition DNS suffix, and given port
func testAccCheckResourceAttrHostnameWithPort(resourceName, attributeName, serviceName, hostnamePrefix string, port int) resource.TestCheckFunc {
return func(s *terraform.State) error {
Expand Down Expand Up @@ -450,6 +473,16 @@ func testAccGetPartitionDNSSuffix() string {
return "amazonaws.com"
}

func testAccGetPartitionReverseDNSPrefix() string {
if partition, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), testAccGetRegion()); ok {
dnsParts := strings.Split(partition.DNSSuffix(), ".")
sort.Sort(sort.Reverse(sort.StringSlice(dnsParts)))
return strings.Join(dnsParts, ".")
}

return "com.amazonaws"
}

func testAccGetAlternateRegionPartition() string {
if partition, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), testAccGetAlternateRegion()); ok {
return partition.ID()
Expand Down
7 changes: 4 additions & 3 deletions website/docs/d/vpc_endpoint_service.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ can be specified when creating a VPC endpoint within the region configured in th
```hcl
# Declare the data source
data "aws_vpc_endpoint_service" "s3" {
service = "s3"
service = "s3"
service_type = "Gateway"
}
# Create a VPC
Expand Down Expand Up @@ -57,9 +58,10 @@ data "aws_vpc_endpoint_service" "test" {
The arguments of this data source act as filters for querying the available VPC endpoint services.
The given filters must match exactly one VPC endpoint service whose data will be exported as attributes.

* `filter` - (Optional) Configuration block(s) for filtering. Detailed below.
* `service` - (Optional) The common name of an AWS service (e.g. `s3`).
* `service_name` - (Optional) The service name that is specified when creating a VPC endpoint. For AWS services the service name is usually in the form `com.amazonaws.<region>.<service>` (the SageMaker Notebook service is an exception to this rule, the service name is in the form `aws.sagemaker.<region>.notebook`).
* `filter` - (Optional) Configuration block(s) for filtering. Detailed below.
* `service_type` - (Optional) The service type, `Gateway` or `Interface`.
* `tags` - (Optional) A map of tags, each pair of which must exactly match a pair on the desired VPC Endpoint Service.

~> **NOTE:** Specifying `service` will not work for non-AWS services or AWS services that don't follow the standard `service_name` pattern of `com.amazonaws.<region>.<service>`.
Expand All @@ -83,6 +85,5 @@ In addition to all arguments above, the following attributes are exported:
* `owner` - The AWS account ID of the service owner or `amazon`.
* `private_dns_name` - The private DNS name for the service.
* `service_id` - The ID of the endpoint service.
* `service_type` - The service type, `Gateway` or `Interface`.
* `tags` - A map of tags assigned to the resource.
* `vpc_endpoint_policy_supported` - Whether or not the service supports endpoint policies - `true` or `false`.

0 comments on commit b7c6594

Please sign in to comment.