From 55e8436ddfb868b214f16e9de320101d7cf8a20d Mon Sep 17 00:00:00 2001 From: Dave DeRicco <30156588+ddericco@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:48:07 -0400 Subject: [PATCH 01/10] Add support for user-defined IP to VPC endpoint resource --- internal/service/ec2/vpc_endpoint.go | 100 +++++- internal/service/ec2/vpc_endpoint_test.go | 388 ++++++++++++++++++++++ 2 files changed, 487 insertions(+), 1 deletion(-) diff --git a/internal/service/ec2/vpc_endpoint.go b/internal/service/ec2/vpc_endpoint.go index 73b451451d5..985bac9eb44 100644 --- a/internal/service/ec2/vpc_endpoint.go +++ b/internal/service/ec2/vpc_endpoint.go @@ -160,6 +160,27 @@ func ResourceVPCEndpoint() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "subnet_configurations": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv4": { + Type: schema.TypeString, + Optional: true, + }, + "ipv6": { + Type: schema.TypeString, + Optional: true, + }, + // subnet_id in subnet_configurations must have a corresponding subnet in subnet_ids attribute + "subnet_id": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, "subnet_ids": { Type: schema.TypeSet, Optional: true, @@ -238,6 +259,10 @@ func resourceVPCEndpointCreate(ctx context.Context, d *schema.ResourceData, meta input.SecurityGroupIds = flex.ExpandStringSet(v.(*schema.Set)) } + if v, ok := d.GetOk("subnet_configurations"); ok { + input.SubnetConfigurations = expandSubnetConfiguration(v.(*schema.Set).List()) + } + if v, ok := d.GetOk("subnet_ids"); ok && v.(*schema.Set).Len() > 0 { input.SubnetIds = flex.ExpandStringSet(v.(*schema.Set)) } @@ -337,6 +362,16 @@ func resourceVPCEndpointRead(ctx context.Context, d *schema.ResourceData, meta i } d.Set("vpc_id", vpce.VpcId) + if subnetConfig, err := FindSubnetConfigurationsByInterfaceIds(ctx, conn, vpce.NetworkInterfaceIds); err != nil { + if tfresource.NotFound(err) { + d.Set("subnet_configurations", nil) + } else { + return sdkdiag.AppendErrorf(diags, "reading EC2 Subnet Configuration: %s", err) + } + } else { + d.Set("subnet_configurations", subnetConfig) + } + if pl, err := FindPrefixListByName(ctx, conn, serviceName); err != nil { if tfresource.NotFound(err) { d.Set("cidr_blocks", nil) @@ -377,7 +412,7 @@ func resourceVPCEndpointUpdate(ctx context.Context, d *schema.ResourceData, meta } } - if d.HasChanges("dns_options", "ip_address_type", "policy", "private_dns_enabled", "security_group_ids", "route_table_ids", "subnet_ids") { + if d.HasChanges("dns_options", "ip_address_type", "policy", "private_dns_enabled", "security_group_ids", "route_table_ids", "subnet_configurations", "subnet_ids") { privateDNSEnabled := d.Get("private_dns_enabled").(bool) input := &ec2.ModifyVpcEndpointInput{ VpcEndpointId: aws.String(d.Id()), @@ -426,6 +461,10 @@ func resourceVPCEndpointUpdate(ctx context.Context, d *schema.ResourceData, meta } } + if d.HasChange("subnet_configurations") { + input.SubnetConfigurations = expandSubnetConfiguration(d.Get("subnet_configurations").(*schema.Set).List()) + } + _, err := conn.ModifyVpcEndpointWithContext(ctx, input) if err != nil { @@ -530,6 +569,34 @@ func expandDNSOptionsSpecificationWithPrivateDNSOnly(tfMap map[string]interface{ return apiObject } +func expandSubnetConfiguration(l []interface{}) []*ec2.SubnetConfiguration { + if len(l) == 0 || l[0] == nil { + return nil + } + configurations := make([]*ec2.SubnetConfiguration, 0, len(l)) + for _, tfMapRaw := range l { + tfMap, ok := tfMapRaw.(map[string]interface{}) + if !ok { + continue + } + apiObject := &ec2.SubnetConfiguration{ + SubnetId: aws.String(tfMap["subnet_id"].(string)), + } + + if v, ok := tfMap["ipv4"].(string); ok && v != "" { + apiObject.Ipv4 = aws.String(v) + } + + if v, ok := tfMap["ipv6"].(string); ok && v != "" { + apiObject.Ipv6 = aws.String(v) + } + + configurations = append(configurations, apiObject) + + } + return configurations +} + func flattenDNSEntry(apiObject *ec2.DnsEntry) map[string]interface{} { if apiObject == nil { return nil @@ -602,6 +669,37 @@ func flattenSecurityGroupIdentifiers(apiObjects []*ec2.SecurityGroupIdentifier) return tfList } +func FindSubnetConfigurationsByInterfaceIds(ctx context.Context, conn *ec2.EC2, apiObjects []*string) ([]interface{}, error) { + if len(apiObjects) == 0 { + return nil, nil + } + var tfList []interface{} + for _, apiObject := range apiObjects { + v, err := FindNetworkInterfaceByID(ctx, conn, *apiObject) + if err != nil { + return nil, fmt.Errorf("accepting reading network interface: %w", err) + } + tfList = append(tfList, flattenSubnetConfigurations(v.SubnetId, v.PrivateIpAddress, v.Ipv6Address)) + } + + return tfList, nil +} + +func flattenSubnetConfigurations(subnetId, privateIpv4, ipv6Address *string) map[string]interface{} { + tfMap := map[string]interface{}{} + + tfMap["subnet_id"] = aws.StringValue(subnetId) + + if privateIpv4 != nil { + tfMap["ipv4"] = aws.StringValue(privateIpv4) + } + if ipv6Address != nil { + tfMap["ipv6"] = aws.StringValue(ipv6Address) + } + + return tfMap +} + func flattenAddAndRemoveStringLists(d *schema.ResourceData, key string) ([]*string, []*string) { if !d.HasChange(key) { return nil, nil diff --git a/internal/service/ec2/vpc_endpoint_test.go b/internal/service/ec2/vpc_endpoint_test.go index ff7e6d8d81d..9e987f48fde 100644 --- a/internal/service/ec2/vpc_endpoint_test.go +++ b/internal/service/ec2/vpc_endpoint_test.go @@ -534,6 +534,249 @@ func TestAccVPCEndpoint_interfaceNonAWSServiceAcceptOnUpdate(t *testing.T) { // }) } +func TestAccVPCEndpoint_interfaceUserDefinedIPv4(t *testing.T) { + ctx := acctest.Context(t) + var endpoint ec2.VpcEndpoint + resourceName := "aws_vpc_endpoint.test" + ipv4Address1 := "10.0.0.10" + ipv4Address2 := "10.0.1.10" + ipv4Address1Updated := "10.0.0.11" + ipv4Address2Updated := "10.0.1.11" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckVPCEndpointDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccVPCEndpointConfig_interfaceUserDefinedIPv4(rName, ipv4Address1, ipv4Address2), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckVPCEndpointExists(ctx, resourceName, &endpoint), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexache.MustCompile(`vpc-endpoint/vpce-.+`)), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dns_entry.#", "3"), + resource.TestCheckResourceAttr(resourceName, "dns_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "dns_options.0.dns_record_ip_type", "ipv4"), + resource.TestCheckResourceAttr(resourceName, "dns_options.0.private_dns_only_for_inbound_resolver_endpoint", "false"), + resource.TestCheckResourceAttr(resourceName, "ip_address_type", "ipv4"), + resource.TestCheckResourceAttr(resourceName, "network_interface_ids.#", "2"), + acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), + resource.TestCheckResourceAttrSet(resourceName, "policy"), + resource.TestCheckNoResourceAttr(resourceName, "prefix_list_id"), + resource.TestCheckResourceAttr(resourceName, "private_dns_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "requester_managed", "false"), + resource.TestCheckResourceAttr(resourceName, "route_table_ids.#", "0"), + resource.TestCheckResourceAttr(resourceName, "security_group_ids.#", "1"), // Default SG. + resource.TestCheckResourceAttr(resourceName, "subnet_configurations.#", "2"), + resource.TestCheckResourceAttr(resourceName, "subnet_configurations.0.ipv4", ipv4Address1), + resource.TestCheckResourceAttr(resourceName, "subnet_configurations.1.ipv4", ipv4Address2), + resource.TestCheckResourceAttr(resourceName, "subnet_ids.#", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "vpc_endpoint_type", "Interface"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccVPCEndpointConfig_interfaceUserDefinedIPv4(rName, ipv4Address1Updated, ipv4Address2Updated), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckVPCEndpointExists(ctx, resourceName, &endpoint), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexache.MustCompile(`vpc-endpoint/vpce-.+`)), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dns_entry.#", "3"), + resource.TestCheckResourceAttr(resourceName, "dns_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "dns_options.0.dns_record_ip_type", "ipv4"), + resource.TestCheckResourceAttr(resourceName, "dns_options.0.private_dns_only_for_inbound_resolver_endpoint", "false"), + resource.TestCheckResourceAttr(resourceName, "ip_address_type", "ipv4"), + resource.TestCheckResourceAttr(resourceName, "network_interface_ids.#", "2"), + acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), + resource.TestCheckResourceAttrSet(resourceName, "policy"), + resource.TestCheckNoResourceAttr(resourceName, "prefix_list_id"), + resource.TestCheckResourceAttr(resourceName, "private_dns_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "requester_managed", "false"), + resource.TestCheckResourceAttr(resourceName, "route_table_ids.#", "0"), + resource.TestCheckResourceAttr(resourceName, "security_group_ids.#", "1"), // Default SG. + resource.TestCheckResourceAttr(resourceName, "subnet_configurations.#", "2"), + resource.TestCheckResourceAttr(resourceName, "subnet_configurations.0.ipv4", ipv4Address1Updated), + resource.TestCheckResourceAttr(resourceName, "subnet_configurations.1.ipv4", ipv4Address2Updated), + resource.TestCheckResourceAttr(resourceName, "subnet_ids.#", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "vpc_endpoint_type", "Interface"), + ), + }, + }, + }) +} + +func TestAccVPCEndpoint_interfaceUserDefinedIPv6(t *testing.T) { + ctx := acctest.Context(t) + var endpoint ec2.VpcEndpoint + resourceName := "aws_vpc_endpoint.test" + ipv6HostNum1 := 10 + ipv6HostNum2 := 11 + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckVPCEndpointDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccVPCEndpointConfig_interfaceUserDefinedIpv6(rName, ipv6HostNum1), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckVPCEndpointExists(ctx, resourceName, &endpoint), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexache.MustCompile(`vpc-endpoint/vpce-.+`)), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dns_entry.#", "3"), + resource.TestCheckResourceAttr(resourceName, "dns_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "dns_options.0.dns_record_ip_type", "ipv6"), + resource.TestCheckResourceAttr(resourceName, "dns_options.0.private_dns_only_for_inbound_resolver_endpoint", "false"), + resource.TestCheckResourceAttr(resourceName, "ip_address_type", "ipv6"), + resource.TestCheckResourceAttr(resourceName, "network_interface_ids.#", "2"), + acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), + resource.TestCheckResourceAttrSet(resourceName, "policy"), + resource.TestCheckNoResourceAttr(resourceName, "prefix_list_id"), + resource.TestCheckResourceAttr(resourceName, "private_dns_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "requester_managed", "false"), + resource.TestCheckResourceAttr(resourceName, "route_table_ids.#", "0"), + resource.TestCheckResourceAttr(resourceName, "security_group_ids.#", "1"), // Default SG. + resource.TestCheckResourceAttr(resourceName, "subnet_configurations.#", "2"), + // IPv6 VPC CIDR is allocated dynamically by AWS, use regex to match regional prefix with expected subnet and /128 address + resource.TestMatchResourceAttr(resourceName, "subnet_configurations.0.ipv6", regexache.MustCompile(`2600:1f14([0-9a-f:]+)1::a`)), + resource.TestMatchResourceAttr(resourceName, "subnet_configurations.1.ipv6", regexache.MustCompile(`2600:1f14([0-9a-f:]+)2::a`)), + resource.TestCheckResourceAttr(resourceName, "subnet_ids.#", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "vpc_endpoint_type", "Interface"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccVPCEndpointConfig_interfaceUserDefinedIpv6(rName, ipv6HostNum2), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckVPCEndpointExists(ctx, resourceName, &endpoint), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexache.MustCompile(`vpc-endpoint/vpce-.+`)), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dns_entry.#", "3"), + resource.TestCheckResourceAttr(resourceName, "dns_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "dns_options.0.dns_record_ip_type", "ipv6"), + resource.TestCheckResourceAttr(resourceName, "dns_options.0.private_dns_only_for_inbound_resolver_endpoint", "false"), + resource.TestCheckResourceAttr(resourceName, "ip_address_type", "ipv6"), + resource.TestCheckResourceAttr(resourceName, "network_interface_ids.#", "2"), + acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), + resource.TestCheckResourceAttrSet(resourceName, "policy"), + resource.TestCheckNoResourceAttr(resourceName, "prefix_list_id"), + resource.TestCheckResourceAttr(resourceName, "private_dns_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "requester_managed", "false"), + resource.TestCheckResourceAttr(resourceName, "route_table_ids.#", "0"), + resource.TestCheckResourceAttr(resourceName, "security_group_ids.#", "1"), // Default SG. + resource.TestCheckResourceAttr(resourceName, "subnet_configurations.#", "2"), + // IPv6 VPC CIDR is allocated dynamically by AWS, use regex to match regional prefix with expected subnet and /128 address + resource.TestMatchResourceAttr(resourceName, "subnet_configurations.0.ipv6", regexache.MustCompile(`2600:1f14([0-9a-f:]+)1::b`)), + resource.TestMatchResourceAttr(resourceName, "subnet_configurations.1.ipv6", regexache.MustCompile(`2600:1f14([0-9a-f:]+)2::b`)), + resource.TestCheckResourceAttr(resourceName, "subnet_ids.#", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "vpc_endpoint_type", "Interface"), + ), + }, + }, + }) +} + +func TestAccVPCEndpoint_interfaceUserDefinedDualstack(t *testing.T) { + ctx := acctest.Context(t) + var endpoint ec2.VpcEndpoint + resourceName := "aws_vpc_endpoint.test" + ipv4Address1 := "10.0.0.10" + ipv4Address2 := "10.0.1.10" + ipv4Address1Updated := "10.0.0.11" + ipv4Address2Updated := "10.0.1.11" + ipv6HostNum1 := 10 + ipv6HostNum2 := 11 + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckVPCEndpointDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccVPCEndpointConfig_interfaceUserDefinedDualstackCombined(rName, ipv4Address1, ipv4Address2, ipv6HostNum1), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckVPCEndpointExists(ctx, resourceName, &endpoint), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexache.MustCompile(`vpc-endpoint/vpce-.+`)), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dns_entry.#", "3"), + resource.TestCheckResourceAttr(resourceName, "dns_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "dns_options.0.dns_record_ip_type", "dualstack"), + resource.TestCheckResourceAttr(resourceName, "ip_address_type", "dualstack"), + resource.TestCheckResourceAttr(resourceName, "network_interface_ids.#", "2"), + acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), + resource.TestCheckResourceAttrSet(resourceName, "policy"), + resource.TestCheckNoResourceAttr(resourceName, "prefix_list_id"), + resource.TestCheckResourceAttr(resourceName, "private_dns_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "requester_managed", "false"), + resource.TestCheckResourceAttr(resourceName, "route_table_ids.#", "0"), + resource.TestCheckResourceAttr(resourceName, "security_group_ids.#", "1"), // Default SG. + resource.TestCheckResourceAttr(resourceName, "subnet_configurations.#", "2"), + resource.TestCheckResourceAttr(resourceName, "subnet_configurations.0.ipv4", ipv4Address1), + resource.TestCheckResourceAttr(resourceName, "subnet_configurations.1.ipv4", ipv4Address2), + // IPv6 VPC CIDR is allocated dynamically by AWS, use regex to match regional prefix with expected subnet and /128 address + resource.TestMatchResourceAttr(resourceName, "subnet_configurations.0.ipv6", regexache.MustCompile(`2600:1f14([0-9a-f:]+)1::a`)), + resource.TestMatchResourceAttr(resourceName, "subnet_configurations.1.ipv6", regexache.MustCompile(`2600:1f14([0-9a-f:]+)2::a`)), + resource.TestCheckResourceAttr(resourceName, "subnet_ids.#", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "vpc_endpoint_type", "Interface"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccVPCEndpointConfig_interfaceUserDefinedDualstackCombined(rName, ipv4Address1Updated, ipv4Address2Updated, ipv6HostNum2), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckVPCEndpointExists(ctx, resourceName, &endpoint), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexache.MustCompile(`vpc-endpoint/vpce-.+`)), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dns_entry.#", "3"), + resource.TestCheckResourceAttr(resourceName, "dns_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "dns_options.0.dns_record_ip_type", "dualstack"), + resource.TestCheckResourceAttr(resourceName, "ip_address_type", "dualstack"), + resource.TestCheckResourceAttr(resourceName, "network_interface_ids.#", "2"), + acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), + resource.TestCheckResourceAttrSet(resourceName, "policy"), + resource.TestCheckNoResourceAttr(resourceName, "prefix_list_id"), + resource.TestCheckResourceAttr(resourceName, "private_dns_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "requester_managed", "false"), + resource.TestCheckResourceAttr(resourceName, "route_table_ids.#", "0"), + resource.TestCheckResourceAttr(resourceName, "security_group_ids.#", "1"), // Default SG. + resource.TestCheckResourceAttr(resourceName, "subnet_configurations.#", "2"), + resource.TestCheckResourceAttr(resourceName, "subnet_configurations.0.ipv4", ipv4Address1Updated), + resource.TestCheckResourceAttr(resourceName, "subnet_configurations.1.ipv4", ipv4Address2Updated), + // IPv6 VPC CIDR is allocated dynamically by AWS, use regex to match regional prefix with expected subnet and /128 address + resource.TestMatchResourceAttr(resourceName, "subnet_configurations.0.ipv6", regexache.MustCompile(`2600:1f14([0-9a-f:]+)1::b`)), + resource.TestMatchResourceAttr(resourceName, "subnet_configurations.1.ipv6", regexache.MustCompile(`2600:1f14([0-9a-f:]+)2::b`)), + resource.TestCheckResourceAttr(resourceName, "subnet_ids.#", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "vpc_endpoint_type", "Interface"), + ), + }, + }, + }) +} + func TestAccVPCEndpoint_VPCEndpointType_gatewayLoadBalancer(t *testing.T) { ctx := acctest.Context(t) var endpoint ec2.VpcEndpoint @@ -1058,6 +1301,151 @@ resource "aws_vpc_endpoint" "test" { `, rName, autoAccept)) } +func testAccVPCEndpointConfig_interfaceUserDefinedDualstackCombined(rName, ipv4Address1, ipv4Address2 string, ipv6HostNum int) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + assign_generated_ipv6_cidr_block = true + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test1" { + vpc_id = aws_vpc.test.id + availability_zone = "us-west-2a" + cidr_block = "10.0.0.0/24" + ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 1) +} + +resource "aws_subnet" "test2" { + vpc_id = aws_vpc.test.id + availability_zone = "us-west-2b" + cidr_block = "10.0.1.0/24" + ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 2) +} + +data "aws_region" "current" {} + +resource "aws_vpc_endpoint" "test" { + vpc_id = aws_vpc.test.id + service_name = "com.amazonaws.${data.aws_region.current.name}.athena" + vpc_endpoint_type = "Interface" + ip_address_type = "dualstack" + subnet_configurations { + ipv4 = %[2]q + ipv6 = cidrhost(aws_subnet.test1.ipv6_cidr_block, %[4]v) + subnet_id = aws_subnet.test1.id + } + subnet_configurations { + ipv4 = %[3]q + ipv6 = cidrhost(aws_subnet.test2.ipv6_cidr_block, %[4]v) + subnet_id = aws_subnet.test2.id + } + subnet_ids = [ + aws_subnet.test1.id, aws_subnet.test2.id + ] +} +`, rName, ipv4Address1, ipv4Address2, ipv6HostNum) +} + +func testAccVPCEndpointConfig_interfaceUserDefinedIPv4(rName, ipv4Address1, ipv4Address2 string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test1" { + vpc_id = aws_vpc.test.id + availability_zone = "us-west-2a" + cidr_block = "10.0.0.0/24" +} + +resource "aws_subnet" "test2" { + vpc_id = aws_vpc.test.id + availability_zone = "us-west-2b" + cidr_block = "10.0.1.0/24" + } + +data "aws_region" "current" {} + +resource "aws_vpc_endpoint" "test" { + vpc_id = aws_vpc.test.id + service_name = "com.amazonaws.${data.aws_region.current.name}.ec2" + vpc_endpoint_type = "Interface" + subnet_configurations { + ipv4 = %[2]q + subnet_id = aws_subnet.test1.id + } + subnet_configurations { + ipv4 = %[3]q + subnet_id = aws_subnet.test2.id + } + subnet_ids = [ + aws_subnet.test1.id, aws_subnet.test2.id + ] +} +`, rName, ipv4Address1, ipv4Address2) +} + +func testAccVPCEndpointConfig_interfaceUserDefinedIpv6(rName string, ipv6HostNum int) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + assign_generated_ipv6_cidr_block = true + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test1" { + vpc_id = aws_vpc.test.id + availability_zone = "us-west-2a" + assign_ipv6_address_on_creation = true + enable_resource_name_dns_aaaa_record_on_launch = true + ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 1) + ipv6_native = true + private_dns_hostname_type_on_launch = "resource-name" +} + +resource "aws_subnet" "test2" { + vpc_id = aws_vpc.test.id + availability_zone = "us-west-2b" + assign_ipv6_address_on_creation = true + enable_resource_name_dns_aaaa_record_on_launch = true + ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 2) + ipv6_native = true + private_dns_hostname_type_on_launch = "resource-name" +} + +data "aws_region" "current" {} + +resource "aws_vpc_endpoint" "test" { + vpc_id = aws_vpc.test.id + service_name = "com.amazonaws.${data.aws_region.current.name}.athena" + vpc_endpoint_type = "Interface" + ip_address_type = "ipv6" + subnet_configurations { + ipv6 = cidrhost(aws_subnet.test1.ipv6_cidr_block, %[2]v) + subnet_id = aws_subnet.test1.id + } + subnet_configurations { + ipv6 = cidrhost(aws_subnet.test2.ipv6_cidr_block, %[2]v) + subnet_id = aws_subnet.test2.id + } + subnet_ids = [ + aws_subnet.test1.id, aws_subnet.test2.id + ] +} +`, rName, ipv6HostNum) +} + func testAccVPCEndpointConfig_tags1(rName, tagKey1, tagValue1 string) string { return fmt.Sprintf(` resource "aws_vpc" "test" { From 3afe9d8b209475a224b2be738c2d4e202328d6e7 Mon Sep 17 00:00:00 2001 From: Dave DeRicco <30156588+ddericco@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:00:59 -0400 Subject: [PATCH 02/10] Add docs --- website/docs/r/vpc_endpoint.html.markdown | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/website/docs/r/vpc_endpoint.html.markdown b/website/docs/r/vpc_endpoint.html.markdown index 24ee80cd307..2a3da358b2e 100644 --- a/website/docs/r/vpc_endpoint.html.markdown +++ b/website/docs/r/vpc_endpoint.html.markdown @@ -58,6 +58,27 @@ resource "aws_vpc_endpoint" "ec2" { } ``` +### Interface Endpoint Type with User-Defined IP Address + +```terraform +resource "aws_vpc_endpoint" "ec2" { + vpc_id = aws_vpc.example.id + service_name = "com.amazonaws.us-west-2.ec2" + vpc_endpoint_type = "Interface" + subnet_configurations { + ipv4 = "10.0.1.10" + subnet_id = aws_subnet.example1.id + } + subnet_configurations { + ipv4 = "10.0.2.10" + subnet_id = aws_subnet.example2.id + } + subnet_ids = [ + aws_subnet.example1.id, aws_subnet.example2.id + ] +} +``` + ### Gateway Load Balancer Endpoint Type ```terraform @@ -123,6 +144,7 @@ Defaults to `false`. * `dns_options` - (Optional) The DNS options for the endpoint. See dns_options below. * `ip_address_type` - (Optional) The IP address type for the endpoint. Valid values are `ipv4`, `dualstack`, and `ipv6`. * `route_table_ids` - (Optional) One or more route table IDs. Applicable for endpoints of type `Gateway`. +* `subnet_configurations` - (Optional) Subnet configuration for the endpoint, used to select specific IPv4 and/or IPv6 addresses to the endpoint. See subnet_configurations. * `subnet_ids` - (Optional) The ID of one or more subnets in which to create a network interface for the endpoint. Applicable for endpoints of type `GatewayLoadBalancer` and `Interface`. Interface type endpoints cannot function without being assigned to a subnet. * `security_group_ids` - (Optional) The ID of one or more security groups to associate with the network interface. Applicable for endpoints of type `Interface`. If no security groups are specified, the VPC's [default security group](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_SecurityGroups.html#DefaultSecurityGroup) is associated with the endpoint. @@ -134,6 +156,12 @@ If no security groups are specified, the VPC's [default security group](https:// * `dns_record_ip_type` - (Optional) The DNS records created for the endpoint. Valid values are `ipv4`, `dualstack`, `service-defined`, and `ipv6`. * `private_dns_only_for_inbound_resolver_endpoint` - (Optional) Indicates whether to enable private DNS only for inbound endpoints. This option is available only for services that support both gateway and interface endpoints. It routes traffic that originates from the VPC to the gateway endpoint and traffic that originates from on-premises to the interface endpoint. Default is `false`. Can only be specified if private_dns_enabled is `true`. +### subnet_configurations + +* `ipv4` - (Optional) The IPv4 address to assign to the endpoint network interface in the subnet. You must provide an IPv4 address if the VPC endpoint supports IPv4. +* `ipv6` - (Optional) The IPv6 address to assign to the endpoint network interface in the subnet. You must provide an IPv6 address if the VPC endpoint supports IPv6. +* `subnet` - (Optional) The ID of the subnet. Must have a corresponding subnet in the `subnet_ids` argument. + ## Timeouts [Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): From 0ede6b7043bb5602ccc7554bdb5799cbb240140a Mon Sep 17 00:00:00 2001 From: Dave DeRicco <30156588+ddericco@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:31:15 -0400 Subject: [PATCH 03/10] Run linters --- internal/service/ec2/vpc_endpoint_test.go | 74 +++++++++++------------ website/docs/r/vpc_endpoint.html.markdown | 6 +- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/internal/service/ec2/vpc_endpoint_test.go b/internal/service/ec2/vpc_endpoint_test.go index 9e987f48fde..8d350846083 100644 --- a/internal/service/ec2/vpc_endpoint_test.go +++ b/internal/service/ec2/vpc_endpoint_test.go @@ -1313,17 +1313,17 @@ resource "aws_vpc" "test" { } resource "aws_subnet" "test1" { - vpc_id = aws_vpc.test.id + vpc_id = aws_vpc.test.id availability_zone = "us-west-2a" - cidr_block = "10.0.0.0/24" - ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 1) + cidr_block = "10.0.0.0/24" + ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 1) } resource "aws_subnet" "test2" { - vpc_id = aws_vpc.test.id + vpc_id = aws_vpc.test.id availability_zone = "us-west-2b" - cidr_block = "10.0.1.0/24" - ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 2) + cidr_block = "10.0.1.0/24" + ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 2) } data "aws_region" "current" {} @@ -1332,19 +1332,19 @@ resource "aws_vpc_endpoint" "test" { vpc_id = aws_vpc.test.id service_name = "com.amazonaws.${data.aws_region.current.name}.athena" vpc_endpoint_type = "Interface" - ip_address_type = "dualstack" + ip_address_type = "dualstack" subnet_configurations { - ipv4 = %[2]q - ipv6 = cidrhost(aws_subnet.test1.ipv6_cidr_block, %[4]v) + ipv4 = %[2]q + ipv6 = cidrhost(aws_subnet.test1.ipv6_cidr_block, %[4]v) subnet_id = aws_subnet.test1.id } subnet_configurations { - ipv4 = %[3]q - ipv6 = cidrhost(aws_subnet.test2.ipv6_cidr_block, %[4]v) + ipv4 = %[3]q + ipv6 = cidrhost(aws_subnet.test2.ipv6_cidr_block, %[4]v) subnet_id = aws_subnet.test2.id } subnet_ids = [ - aws_subnet.test1.id, aws_subnet.test2.id + aws_subnet.test1.id, aws_subnet.test2.id ] } `, rName, ipv4Address1, ipv4Address2, ipv6HostNum) @@ -1361,16 +1361,16 @@ resource "aws_vpc" "test" { } resource "aws_subnet" "test1" { - vpc_id = aws_vpc.test.id + vpc_id = aws_vpc.test.id availability_zone = "us-west-2a" - cidr_block = "10.0.0.0/24" + cidr_block = "10.0.0.0/24" } resource "aws_subnet" "test2" { - vpc_id = aws_vpc.test.id - availability_zone = "us-west-2b" - cidr_block = "10.0.1.0/24" - } + vpc_id = aws_vpc.test.id + availability_zone = "us-west-2b" + cidr_block = "10.0.1.0/24" +} data "aws_region" "current" {} @@ -1379,15 +1379,15 @@ resource "aws_vpc_endpoint" "test" { service_name = "com.amazonaws.${data.aws_region.current.name}.ec2" vpc_endpoint_type = "Interface" subnet_configurations { - ipv4 = %[2]q + ipv4 = %[2]q subnet_id = aws_subnet.test1.id } subnet_configurations { - ipv4 = %[3]q + ipv4 = %[3]q subnet_id = aws_subnet.test2.id } subnet_ids = [ - aws_subnet.test1.id, aws_subnet.test2.id + aws_subnet.test1.id, aws_subnet.test2.id ] } `, rName, ipv4Address1, ipv4Address2) @@ -1405,23 +1405,23 @@ resource "aws_vpc" "test" { } resource "aws_subnet" "test1" { - vpc_id = aws_vpc.test.id - availability_zone = "us-west-2a" - assign_ipv6_address_on_creation = true + vpc_id = aws_vpc.test.id + availability_zone = "us-west-2a" + assign_ipv6_address_on_creation = true enable_resource_name_dns_aaaa_record_on_launch = true - ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 1) - ipv6_native = true - private_dns_hostname_type_on_launch = "resource-name" + ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 1) + ipv6_native = true + private_dns_hostname_type_on_launch = "resource-name" } resource "aws_subnet" "test2" { - vpc_id = aws_vpc.test.id - availability_zone = "us-west-2b" - assign_ipv6_address_on_creation = true + vpc_id = aws_vpc.test.id + availability_zone = "us-west-2b" + assign_ipv6_address_on_creation = true enable_resource_name_dns_aaaa_record_on_launch = true - ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 2) - ipv6_native = true - private_dns_hostname_type_on_launch = "resource-name" + ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 2) + ipv6_native = true + private_dns_hostname_type_on_launch = "resource-name" } data "aws_region" "current" {} @@ -1430,17 +1430,17 @@ resource "aws_vpc_endpoint" "test" { vpc_id = aws_vpc.test.id service_name = "com.amazonaws.${data.aws_region.current.name}.athena" vpc_endpoint_type = "Interface" - ip_address_type = "ipv6" + ip_address_type = "ipv6" subnet_configurations { - ipv6 = cidrhost(aws_subnet.test1.ipv6_cidr_block, %[2]v) + ipv6 = cidrhost(aws_subnet.test1.ipv6_cidr_block, %[2]v) subnet_id = aws_subnet.test1.id } subnet_configurations { - ipv6 = cidrhost(aws_subnet.test2.ipv6_cidr_block, %[2]v) + ipv6 = cidrhost(aws_subnet.test2.ipv6_cidr_block, %[2]v) subnet_id = aws_subnet.test2.id } subnet_ids = [ - aws_subnet.test1.id, aws_subnet.test2.id + aws_subnet.test1.id, aws_subnet.test2.id ] } `, rName, ipv6HostNum) diff --git a/website/docs/r/vpc_endpoint.html.markdown b/website/docs/r/vpc_endpoint.html.markdown index 2a3da358b2e..9c49e736ed5 100644 --- a/website/docs/r/vpc_endpoint.html.markdown +++ b/website/docs/r/vpc_endpoint.html.markdown @@ -66,15 +66,15 @@ resource "aws_vpc_endpoint" "ec2" { service_name = "com.amazonaws.us-west-2.ec2" vpc_endpoint_type = "Interface" subnet_configurations { - ipv4 = "10.0.1.10" + ipv4 = "10.0.1.10" subnet_id = aws_subnet.example1.id } subnet_configurations { - ipv4 = "10.0.2.10" + ipv4 = "10.0.2.10" subnet_id = aws_subnet.example2.id } subnet_ids = [ - aws_subnet.example1.id, aws_subnet.example2.id + aws_subnet.example1.id, aws_subnet.example2.id ] } ``` From 23990cc2cb33b7498d5b91ee23b362cefeb5387c Mon Sep 17 00:00:00 2001 From: Dave DeRicco <30156588+ddericco@users.noreply.github.com> Date: Thu, 2 May 2024 11:49:17 -0400 Subject: [PATCH 04/10] Update subnet_configurations to Computed --- internal/service/ec2/vpc_endpoint.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/service/ec2/vpc_endpoint.go b/internal/service/ec2/vpc_endpoint.go index 985bac9eb44..876cf9b52fa 100644 --- a/internal/service/ec2/vpc_endpoint.go +++ b/internal/service/ec2/vpc_endpoint.go @@ -163,6 +163,7 @@ func ResourceVPCEndpoint() *schema.Resource { "subnet_configurations": { Type: schema.TypeSet, Optional: true, + Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "ipv4": { From 17d76b6fa34e7dab4f1b7bc961b8b8800d2143d9 Mon Sep 17 00:00:00 2001 From: Dave DeRicco <30156588+ddericco@users.noreply.github.com> Date: Thu, 2 May 2024 18:56:01 -0400 Subject: [PATCH 05/10] Additional linting, use AZ data source in tests --- internal/service/ec2/vpc_endpoint.go | 2 +- internal/service/ec2/vpc_endpoint_test.go | 42 +++++++++++++++-------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/internal/service/ec2/vpc_endpoint.go b/internal/service/ec2/vpc_endpoint.go index 876cf9b52fa..b3c72861595 100644 --- a/internal/service/ec2/vpc_endpoint.go +++ b/internal/service/ec2/vpc_endpoint.go @@ -593,8 +593,8 @@ func expandSubnetConfiguration(l []interface{}) []*ec2.SubnetConfiguration { } configurations = append(configurations, apiObject) - } + return configurations } diff --git a/internal/service/ec2/vpc_endpoint_test.go b/internal/service/ec2/vpc_endpoint_test.go index b5b4e2e3eec..e42f9969a95 100644 --- a/internal/service/ec2/vpc_endpoint_test.go +++ b/internal/service/ec2/vpc_endpoint_test.go @@ -628,7 +628,7 @@ func TestAccVPCEndpoint_interfaceUserDefinedIPv6(t *testing.T) { CheckDestroy: testAccCheckVPCEndpointDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccVPCEndpointConfig_interfaceUserDefinedIpv6(rName, ipv6HostNum1), + Config: testAccVPCEndpointConfig_interfaceUserDefinedIPv6(rName, ipv6HostNum1), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckVPCEndpointExists(ctx, resourceName, &endpoint), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexache.MustCompile(`vpc-endpoint/vpce-.+`)), @@ -661,7 +661,7 @@ func TestAccVPCEndpoint_interfaceUserDefinedIPv6(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccVPCEndpointConfig_interfaceUserDefinedIpv6(rName, ipv6HostNum2), + Config: testAccVPCEndpointConfig_interfaceUserDefinedIPv6(rName, ipv6HostNum2), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckVPCEndpointExists(ctx, resourceName, &endpoint), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexache.MustCompile(`vpc-endpoint/vpce-.+`)), @@ -1304,6 +1304,12 @@ resource "aws_vpc_endpoint" "test" { func testAccVPCEndpointConfig_interfaceUserDefinedDualstackCombined(rName, ipv4Address1, ipv4Address2 string, ipv6HostNum int) string { return fmt.Sprintf(` +data "aws_region" "current" {} + +data "aws_availability_zones" "available" { + state = "available" +} + resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" @@ -1315,20 +1321,18 @@ resource "aws_vpc" "test" { resource "aws_subnet" "test1" { vpc_id = aws_vpc.test.id - availability_zone = "us-west-2a" + availability_zone = data.aws_availability_zones.available.names[0] cidr_block = "10.0.0.0/24" ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 1) } resource "aws_subnet" "test2" { vpc_id = aws_vpc.test.id - availability_zone = "us-west-2b" + availability_zone = data.aws_availability_zones.available.names[1] cidr_block = "10.0.1.0/24" ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 2) } -data "aws_region" "current" {} - resource "aws_vpc_endpoint" "test" { vpc_id = aws_vpc.test.id service_name = "com.amazonaws.${data.aws_region.current.name}.athena" @@ -1353,6 +1357,12 @@ resource "aws_vpc_endpoint" "test" { func testAccVPCEndpointConfig_interfaceUserDefinedIPv4(rName, ipv4Address1, ipv4Address2 string) string { return fmt.Sprintf(` +data "aws_region" "current" {} + +data "aws_availability_zones" "available" { + state = "available" +} + resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" @@ -1363,18 +1373,16 @@ resource "aws_vpc" "test" { resource "aws_subnet" "test1" { vpc_id = aws_vpc.test.id - availability_zone = "us-west-2a" + availability_zone = data.aws_availability_zones.available.names[0] cidr_block = "10.0.0.0/24" } resource "aws_subnet" "test2" { vpc_id = aws_vpc.test.id - availability_zone = "us-west-2b" + availability_zone = data.aws_availability_zones.available.names[1] cidr_block = "10.0.1.0/24" } -data "aws_region" "current" {} - resource "aws_vpc_endpoint" "test" { vpc_id = aws_vpc.test.id service_name = "com.amazonaws.${data.aws_region.current.name}.ec2" @@ -1394,8 +1402,14 @@ resource "aws_vpc_endpoint" "test" { `, rName, ipv4Address1, ipv4Address2) } -func testAccVPCEndpointConfig_interfaceUserDefinedIpv6(rName string, ipv6HostNum int) string { +func testAccVPCEndpointConfig_interfaceUserDefinedIPv6(rName string, ipv6HostNum int) string { return fmt.Sprintf(` +data "aws_region" "current" {} + +data "aws_availability_zones" "available" { + state = "available" +} + resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" @@ -1407,7 +1421,7 @@ resource "aws_vpc" "test" { resource "aws_subnet" "test1" { vpc_id = aws_vpc.test.id - availability_zone = "us-west-2a" + availability_zone = data.aws_availability_zones.available.names[0] assign_ipv6_address_on_creation = true enable_resource_name_dns_aaaa_record_on_launch = true ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 1) @@ -1417,7 +1431,7 @@ resource "aws_subnet" "test1" { resource "aws_subnet" "test2" { vpc_id = aws_vpc.test.id - availability_zone = "us-west-2b" + availability_zone = data.aws_availability_zones.available.names[1] assign_ipv6_address_on_creation = true enable_resource_name_dns_aaaa_record_on_launch = true ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 2) @@ -1425,8 +1439,6 @@ resource "aws_subnet" "test2" { private_dns_hostname_type_on_launch = "resource-name" } -data "aws_region" "current" {} - resource "aws_vpc_endpoint" "test" { vpc_id = aws_vpc.test.id service_name = "com.amazonaws.${data.aws_region.current.name}.athena" From 635ff3c07881ad1b84e42b6ed74b835d534bcfa3 Mon Sep 17 00:00:00 2001 From: Dave DeRicco <30156588+ddericco@users.noreply.github.com> Date: Fri, 3 May 2024 11:40:50 -0400 Subject: [PATCH 06/10] Use base10 integer verb for v6 hostnum --- internal/service/ec2/vpc_endpoint_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/service/ec2/vpc_endpoint_test.go b/internal/service/ec2/vpc_endpoint_test.go index e42f9969a95..977f6461977 100644 --- a/internal/service/ec2/vpc_endpoint_test.go +++ b/internal/service/ec2/vpc_endpoint_test.go @@ -1340,12 +1340,12 @@ resource "aws_vpc_endpoint" "test" { ip_address_type = "dualstack" subnet_configurations { ipv4 = %[2]q - ipv6 = cidrhost(aws_subnet.test1.ipv6_cidr_block, %[4]v) + ipv6 = cidrhost(aws_subnet.test1.ipv6_cidr_block, %[4]d) subnet_id = aws_subnet.test1.id } subnet_configurations { ipv4 = %[3]q - ipv6 = cidrhost(aws_subnet.test2.ipv6_cidr_block, %[4]v) + ipv6 = cidrhost(aws_subnet.test2.ipv6_cidr_block, %[4]d) subnet_id = aws_subnet.test2.id } subnet_ids = [ @@ -1445,11 +1445,11 @@ resource "aws_vpc_endpoint" "test" { vpc_endpoint_type = "Interface" ip_address_type = "ipv6" subnet_configurations { - ipv6 = cidrhost(aws_subnet.test1.ipv6_cidr_block, %[2]v) + ipv6 = cidrhost(aws_subnet.test1.ipv6_cidr_block, %[2]d) subnet_id = aws_subnet.test1.id } subnet_configurations { - ipv6 = cidrhost(aws_subnet.test2.ipv6_cidr_block, %[2]v) + ipv6 = cidrhost(aws_subnet.test2.ipv6_cidr_block, %[2]d) subnet_id = aws_subnet.test2.id } subnet_ids = [ From 8814c209fae2de93dad1e543acd4292c663de7f1 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 13 Jun 2024 08:39:21 -0700 Subject: [PATCH 07/10] Add CHANGELOG entry. --- .changelog/37226.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/37226.txt diff --git a/.changelog/37226.txt b/.changelog/37226.txt new file mode 100644 index 00000000000..6c1ee9548e2 --- /dev/null +++ b/.changelog/37226.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_vpc_endpoint: Add `subnet_configuration` argument to support user defined IP addresses +``` \ No newline at end of file From d37e2f347b14b5b8f4ebecf6acdcee4ce69a8ddb Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 13 Jun 2024 08:41:09 -0700 Subject: [PATCH 08/10] Fix error message mixup. --- internal/service/ec2/vpc_endpoint.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/service/ec2/vpc_endpoint.go b/internal/service/ec2/vpc_endpoint.go index 88120ae174b..83d82bac2e4 100644 --- a/internal/service/ec2/vpc_endpoint.go +++ b/internal/service/ec2/vpc_endpoint.go @@ -371,7 +371,7 @@ func resourceVPCEndpointRead(ctx context.Context, d *schema.ResourceData, meta i if tfresource.NotFound(err) { d.Set("cidr_blocks", nil) } else { - return sdkdiag.AppendErrorf(diags, "reading VPC Endpoint (%s) subnet configurations: %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading EC2 Prefix List (%s): %s", serviceName, err) } } else { d.Set("cidr_blocks", pl.Cidrs) @@ -381,7 +381,7 @@ func resourceVPCEndpointRead(ctx context.Context, d *schema.ResourceData, meta i subnetConfigurations, err := findSubnetConfigurationsByNetworkInterfaceIDs(ctx, conn, vpce.NetworkInterfaceIds) if err != nil { - return sdkdiag.AppendErrorf(diags, "reading EC2 Prefix List (%s): %s", serviceName, err) + return sdkdiag.AppendErrorf(diags, "reading VPC Endpoint (%s) subnet configurations: %s", d.Id(), err) } if err := d.Set("subnet_configuration", flattenSubnetConfigurations(subnetConfigurations)); err != nil { From 6c092688ea9984dadaf909bbacf43ca67ba03488 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 13 Jun 2024 09:17:22 -0700 Subject: [PATCH 09/10] r/aws_vpc_endpoint: Fixup 'subnet_configuration' acceptance tests. --- internal/service/ec2/vpc_endpoint_test.go | 140 +++++++--------------- 1 file changed, 44 insertions(+), 96 deletions(-) diff --git a/internal/service/ec2/vpc_endpoint_test.go b/internal/service/ec2/vpc_endpoint_test.go index 1a58e30a323..3ac153c6cdf 100644 --- a/internal/service/ec2/vpc_endpoint_test.go +++ b/internal/service/ec2/vpc_endpoint_test.go @@ -628,7 +628,7 @@ func TestAccVPCEndpoint_interfaceUserDefinedIPv4(t *testing.T) { "ipv4": ipv4Address2, }), resource.TestCheckResourceAttr(resourceName, "subnet_ids.#", acctest.Ct2), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), resource.TestCheckResourceAttr(resourceName, "vpc_endpoint_type", "Interface"), ), }, @@ -664,7 +664,7 @@ func TestAccVPCEndpoint_interfaceUserDefinedIPv4(t *testing.T) { "ipv4": ipv4Address2Updated, }), resource.TestCheckResourceAttr(resourceName, "subnet_ids.#", acctest.Ct2), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), resource.TestCheckResourceAttr(resourceName, "vpc_endpoint_type", "Interface"), ), }, @@ -707,7 +707,7 @@ func TestAccVPCEndpoint_interfaceUserDefinedIPv6(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "security_group_ids.#", acctest.Ct1), // Default SG. resource.TestCheckResourceAttr(resourceName, "subnet_configuration.#", acctest.Ct2), resource.TestCheckResourceAttr(resourceName, "subnet_ids.#", acctest.Ct2), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), resource.TestCheckResourceAttr(resourceName, "vpc_endpoint_type", "Interface"), ), }, @@ -737,7 +737,7 @@ func TestAccVPCEndpoint_interfaceUserDefinedIPv6(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "security_group_ids.#", acctest.Ct1), // Default SG. resource.TestCheckResourceAttr(resourceName, "subnet_configuration.#", acctest.Ct2), resource.TestCheckResourceAttr(resourceName, "subnet_ids.#", acctest.Ct2), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), resource.TestCheckResourceAttr(resourceName, "vpc_endpoint_type", "Interface"), ), }, @@ -789,7 +789,7 @@ func TestAccVPCEndpoint_interfaceUserDefinedDualstack(t *testing.T) { "ipv4": ipv4Address2, }), resource.TestCheckResourceAttr(resourceName, "subnet_ids.#", acctest.Ct2), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), resource.TestCheckResourceAttr(resourceName, "vpc_endpoint_type", "Interface"), ), }, @@ -824,7 +824,7 @@ func TestAccVPCEndpoint_interfaceUserDefinedDualstack(t *testing.T) { "ipv4": ipv4Address2Updated, }), resource.TestCheckResourceAttr(resourceName, "subnet_ids.#", acctest.Ct2), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), resource.TestCheckResourceAttr(resourceName, "vpc_endpoint_type", "Interface"), ), }, @@ -1379,36 +1379,9 @@ resource "aws_vpc_endpoint" "test" { } func testAccVPCEndpointConfig_interfaceUserDefinedDualstackCombined(rName, ipv4Address1, ipv4Address2 string, ipv6HostNum int) string { - return fmt.Sprintf(` + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnetsIPv6(rName, 2), fmt.Sprintf(` data "aws_region" "current" {} -data "aws_availability_zones" "available" { - state = "available" -} - -resource "aws_vpc" "test" { - cidr_block = "10.0.0.0/16" - - assign_generated_ipv6_cidr_block = true - tags = { - Name = %[1]q - } -} - -resource "aws_subnet" "test1" { - vpc_id = aws_vpc.test.id - availability_zone = data.aws_availability_zones.available.names[0] - cidr_block = "10.0.0.0/24" - ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 1) -} - -resource "aws_subnet" "test2" { - vpc_id = aws_vpc.test.id - availability_zone = data.aws_availability_zones.available.names[1] - cidr_block = "10.0.1.0/24" - ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 2) -} - resource "aws_vpc_endpoint" "test" { vpc_id = aws_vpc.test.id service_name = "com.amazonaws.${data.aws_region.current.name}.athena" @@ -1417,50 +1390,28 @@ resource "aws_vpc_endpoint" "test" { subnet_configuration { ipv4 = %[2]q - ipv6 = cidrhost(aws_subnet.test1.ipv6_cidr_block, %[4]d) - subnet_id = aws_subnet.test1.id + ipv6 = cidrhost(aws_subnet.test[0].ipv6_cidr_block, %[4]d) + subnet_id = aws_subnet.test[0].id } subnet_configuration { ipv4 = %[3]q - ipv6 = cidrhost(aws_subnet.test2.ipv6_cidr_block, %[4]d) - subnet_id = aws_subnet.test2.id + ipv6 = cidrhost(aws_subnet.test[1].ipv6_cidr_block, %[4]d) + subnet_id = aws_subnet.test[1].id } - subnet_ids = [ - aws_subnet.test1.id, aws_subnet.test2.id - ] -} -`, rName, ipv4Address1, ipv4Address2, ipv6HostNum) -} - -func testAccVPCEndpointConfig_interfaceUserDefinedIPv4(rName, ipv4Address1, ipv4Address2 string) string { - return fmt.Sprintf(` -data "aws_region" "current" {} - -data "aws_availability_zones" "available" { - state = "available" -} - -resource "aws_vpc" "test" { - cidr_block = "10.0.0.0/16" + subnet_ids = aws_subnet.test[*].id tags = { Name = %[1]q } } - -resource "aws_subnet" "test1" { - vpc_id = aws_vpc.test.id - availability_zone = data.aws_availability_zones.available.names[0] - cidr_block = "10.0.0.0/24" +`, rName, ipv4Address1, ipv4Address2, ipv6HostNum)) } -resource "aws_subnet" "test2" { - vpc_id = aws_vpc.test.id - availability_zone = data.aws_availability_zones.available.names[1] - cidr_block = "10.0.1.0/24" -} +func testAccVPCEndpointConfig_interfaceUserDefinedIPv4(rName, ipv4Address1, ipv4Address2 string) string { + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 2), fmt.Sprintf(` +data "aws_region" "current" {} resource "aws_vpc_endpoint" "test" { vpc_id = aws_vpc.test.id @@ -1469,56 +1420,51 @@ resource "aws_vpc_endpoint" "test" { subnet_configuration { ipv4 = %[2]q - subnet_id = aws_subnet.test1.id + subnet_id = aws_subnet.test[0].id } subnet_configuration { ipv4 = %[3]q - subnet_id = aws_subnet.test2.id + subnet_id = aws_subnet.test[1].id } - subnet_ids = [ - aws_subnet.test1.id, aws_subnet.test2.id - ] + subnet_ids = aws_subnet.test[*].id + + tags = { + Name = %[1]q + } } -`, rName, ipv4Address1, ipv4Address2) +`, rName, ipv4Address1, ipv4Address2)) } func testAccVPCEndpointConfig_interfaceUserDefinedIPv6(rName string, ipv6HostNum int) string { - return fmt.Sprintf(` + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptInDefaultExclude(), fmt.Sprintf(` data "aws_region" "current" {} -data "aws_availability_zones" "available" { - state = "available" -} - resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" assign_generated_ipv6_cidr_block = true + tags = { Name = %[1]q } } -resource "aws_subnet" "test1" { - vpc_id = aws_vpc.test.id - availability_zone = data.aws_availability_zones.available.names[0] - assign_ipv6_address_on_creation = true - enable_resource_name_dns_aaaa_record_on_launch = true - ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 1) - ipv6_native = true - private_dns_hostname_type_on_launch = "resource-name" -} +resource "aws_subnet" "test" { + count = 2 -resource "aws_subnet" "test2" { vpc_id = aws_vpc.test.id - availability_zone = data.aws_availability_zones.available.names[1] + availability_zone = data.aws_availability_zones.available.names[count.index] assign_ipv6_address_on_creation = true enable_resource_name_dns_aaaa_record_on_launch = true - ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 2) + ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, count.index) ipv6_native = true private_dns_hostname_type_on_launch = "resource-name" + + tags = { + Name = %[1]q + } } resource "aws_vpc_endpoint" "test" { @@ -1528,20 +1474,22 @@ resource "aws_vpc_endpoint" "test" { ip_address_type = "ipv6" subnet_configuration { - ipv6 = cidrhost(aws_subnet.test1.ipv6_cidr_block, %[2]d) - subnet_id = aws_subnet.test1.id + ipv6 = cidrhost(aws_subnet.test[0].ipv6_cidr_block, %[2]d) + subnet_id = aws_subnet.test[0].id } subnet_configuration { - ipv6 = cidrhost(aws_subnet.test2.ipv6_cidr_block, %[2]d) - subnet_id = aws_subnet.test2.id + ipv6 = cidrhost(aws_subnet.test[1].ipv6_cidr_block, %[2]d) + subnet_id = aws_subnet.test[1].id } - subnet_ids = [ - aws_subnet.test1.id, aws_subnet.test2.id - ] + subnet_ids = aws_subnet.test[*].id + + tags = { + Name = %[1]q + } } -`, rName, ipv6HostNum) +`, rName, ipv6HostNum)) } func testAccVPCEndpointConfig_tags1(rName, tagKey1, tagValue1 string) string { From cce73a9c1e31ccddae80cfdc2c3e0b7f3a6b9402 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 13 Jun 2024 10:08:37 -0700 Subject: [PATCH 10/10] Fix 'TestAccVPCEndpoint_interfaceWithSubnetAndSecurityGroup'. --- internal/service/ec2/vpc_endpoint_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/service/ec2/vpc_endpoint_test.go b/internal/service/ec2/vpc_endpoint_test.go index 3ac153c6cdf..65f1269aac7 100644 --- a/internal/service/ec2/vpc_endpoint_test.go +++ b/internal/service/ec2/vpc_endpoint_test.go @@ -514,6 +514,7 @@ func TestAccVPCEndpoint_interfaceWithSubnetAndSecurityGroup(t *testing.T) { // the impacted attribute. // Ref: https://github.com/hashicorp/terraform-plugin-testing/issues/269 "network_interface_ids", + "subnet_configuration", }, }, },