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

data-source/aws_vpc_endpoint_service: Accept service_type as argument #15467

Merged
merged 1 commit into from
Oct 8, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
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`.