diff --git a/.changelog/17319.txt b/.changelog/17319.txt new file mode 100644 index 00000000000..01a6b8f59b9 --- /dev/null +++ b/.changelog/17319.txt @@ -0,0 +1,19 @@ +```release-notes:enhancement +resource/aws_default_route_table: Add `destination_prefix_list_id` attribute +``` + +```release-notes:enhancement +resource/aws_route_table: Add `destination_prefix_list_id` attribute +``` + +```release-note:bug +resource/aws_vpn_gateway_route_propagation: Improve eventual consistency handling and handling of out-of-band resource removal +``` + +```release-note:bug +resource/aws_route_table: Improve eventual consistency handling and handling of out-of-band resource removal +``` + +```release-note:bug +resource/aws_route_table_association: Improve eventual consistency handling and handling of out-of-band resource removal +``` \ No newline at end of file diff --git a/aws/internal/service/ec2/waiter/status.go b/aws/internal/service/ec2/waiter/status.go index 9e1e7e54fcd..dc7c7b6114a 100644 --- a/aws/internal/service/ec2/waiter/status.go +++ b/aws/internal/service/ec2/waiter/status.go @@ -12,6 +12,7 @@ import ( tfec2 "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/finder" tfiam "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) const ( @@ -245,6 +246,32 @@ func InstanceIamInstanceProfile(conn *ec2.EC2, id string) resource.StateRefreshF } } +const ( + ErrCodeInvalidRouteTableIDNotFound = "InvalidRouteTableID.NotFound" + + RouteTableStatusReady = "ready" +) + +func RouteTableStatus(conn *ec2.EC2, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := finder.RouteTableByID(conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + if output == nil { + return nil, "", nil + } + + return output, RouteTableStatusReady, nil + } +} + const ( SecurityGroupStatusCreated = "Created" diff --git a/aws/internal/service/ec2/waiter/waiter.go b/aws/internal/service/ec2/waiter/waiter.go index a7385d40cef..21a74f381a9 100644 --- a/aws/internal/service/ec2/waiter/waiter.go +++ b/aws/internal/service/ec2/waiter/waiter.go @@ -257,6 +257,49 @@ const ( NetworkAclEntryPropagationTimeout = 5 * time.Minute ) +const ( + RouteTableReadyTimeout = 10 * time.Minute + RouteTableDeletedTimeout = 5 * time.Minute + RouteTableUpdateTimeout = 5 * time.Minute + + RouteTableNotFoundChecks = 40 +) + +func RouteTableReady(conn *ec2.EC2, id string) (*ec2.RouteTable, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{}, + Target: []string{RouteTableStatusReady}, + Refresh: RouteTableStatus(conn, id), + Timeout: RouteTableReadyTimeout, + NotFoundChecks: RouteTableNotFoundChecks, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*ec2.RouteTable); ok { + return output, err + } + + return nil, err +} + +func RouteTableDeleted(conn *ec2.EC2, id string) (*ec2.RouteTable, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{RouteTableStatusReady}, + Target: []string{}, + Refresh: RouteTableStatus(conn, id), + Timeout: RouteTableDeletedTimeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*ec2.RouteTable); ok { + return output, err + } + + return nil, err +} + func SecurityGroupCreated(conn *ec2.EC2, id string, timeout time.Duration) (*ec2.SecurityGroup, error) { stateConf := &resource.StateChangeConf{ Pending: []string{SecurityGroupStatusNotFound}, diff --git a/aws/resource_aws_default_route_table.go b/aws/resource_aws_default_route_table.go index 3ee9440552f..fdf9f14d686 100644 --- a/aws/resource_aws_default_route_table.go +++ b/aws/resource_aws_default_route_table.go @@ -3,12 +3,14 @@ package aws import ( "fmt" "log" + "strings" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/finder" ) func resourceAwsDefaultRouteTable() *schema.Resource { @@ -50,6 +52,9 @@ func resourceAwsDefaultRouteTable() *schema.Resource { Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + /// + // Destinations. + /// "cidr_block": { Type: schema.TypeString, Optional: true, @@ -68,6 +73,14 @@ func resourceAwsDefaultRouteTable() *schema.Resource { ), }, + "destination_prefix_list_id": { + Type: schema.TypeString, + Optional: true, + }, + + // + // Targets. + // "egress_only_gateway_id": { Type: schema.TypeString, Optional: true, @@ -88,22 +101,22 @@ func resourceAwsDefaultRouteTable() *schema.Resource { Optional: true, }, - "transit_gateway_id": { + "network_interface_id": { Type: schema.TypeString, Optional: true, }, - "vpc_endpoint_id": { + "transit_gateway_id": { Type: schema.TypeString, Optional: true, }, - "vpc_peering_connection_id": { + "vpc_endpoint_id": { Type: schema.TypeString, Optional: true, }, - "network_interface_id": { + "vpc_peering_connection_id": { Type: schema.TypeString, Optional: true, }, @@ -128,31 +141,28 @@ func resourceAwsDefaultRouteTable() *schema.Resource { } func resourceAwsDefaultRouteTableCreate(d *schema.ResourceData, meta interface{}) error { - d.SetId(d.Get("default_route_table_id").(string)) - conn := meta.(*AWSClient).ec2conn - rtRaw, _, err := resourceAwsRouteTableStateRefreshFunc(conn, d.Id())() + routeTableID := d.Get("default_route_table_id").(string) + + routeTable, err := finder.RouteTableByID(conn, routeTableID) + if err != nil { - return fmt.Errorf("error reading EC2 Default Route Table (%s): %s", d.Id(), err) - } - if rtRaw == nil { - return fmt.Errorf("error reading EC2 Default Route Table (%s): not found", d.Id()) + return fmt.Errorf("error reading EC2 Default Route Table (%s): %w", routeTableID, err) } - rt := rtRaw.(*ec2.RouteTable) - - d.Set("vpc_id", rt.VpcId) + d.SetId(routeTableID) + d.Set("vpc_id", routeTable.VpcId) // revoke all default and pre-existing routes on the default route table. // In the UPDATE method, we'll apply only the rules in the configuration. log.Printf("[DEBUG] Revoking default routes for Default Route Table for %s", d.Id()) - if err := revokeAllRouteTableRules(d.Id(), meta); err != nil { + if err := revokeAllRouteTableRules(conn, routeTable); err != nil { return err } if v := d.Get("tags").(map[string]interface{}); len(v) > 0 { if err := keyvaluetags.Ec2CreateTags(conn, d.Id(), v); err != nil { - return fmt.Errorf("error adding tags: %s", err) + return fmt.Errorf("error adding tags: %w", err) } } @@ -203,75 +213,68 @@ func resourceAwsDefaultRouteTableDelete(d *schema.ResourceData, meta interface{} // revokeAllRouteTableRules revoke all routes on the Default Route Table // This should only be ran once at creation time of this resource -func revokeAllRouteTableRules(defaultRouteTableId string, meta interface{}) error { - conn := meta.(*AWSClient).ec2conn - log.Printf("\n***\nrevokeAllRouteTableRules\n***\n") - - resp, err := conn.DescribeRouteTables(&ec2.DescribeRouteTablesInput{ - RouteTableIds: []*string{aws.String(defaultRouteTableId)}, - }) - if err != nil { - return err - } - - if len(resp.RouteTables) < 1 || resp.RouteTables[0] == nil { - return fmt.Errorf("Default Route table not found") - } - - rt := resp.RouteTables[0] - +func revokeAllRouteTableRules(conn *ec2.EC2, routeTable *ec2.RouteTable) error { // Remove all Gateway association - for _, r := range rt.PropagatingVgws { - log.Printf( - "[INFO] Deleting VGW propagation from %s: %s", - defaultRouteTableId, *r.GatewayId) + for _, r := range routeTable.PropagatingVgws { _, err := conn.DisableVgwRoutePropagation(&ec2.DisableVgwRoutePropagationInput{ - RouteTableId: aws.String(defaultRouteTableId), + RouteTableId: routeTable.RouteTableId, GatewayId: r.GatewayId, }) + if err != nil { return err } } // Delete all routes - for _, r := range rt.Routes { + for _, r := range routeTable.Routes { // you cannot delete the local route - if r.GatewayId != nil && *r.GatewayId == "local" { + if aws.StringValue(r.GatewayId) == "local" { continue } - if r.DestinationPrefixListId != nil { + + if aws.StringValue(r.Origin) == ec2.RouteOriginEnableVgwRoutePropagation { + continue + } + + if r.DestinationPrefixListId != nil && strings.HasPrefix(aws.StringValue(r.GatewayId), "vpce-") { // Skipping because VPC endpoint routes are handled separately // See aws_vpc_endpoint continue } if r.DestinationCidrBlock != nil { - log.Printf( - "[INFO] Deleting route from %s: %s", - defaultRouteTableId, *r.DestinationCidrBlock) _, err := conn.DeleteRoute(&ec2.DeleteRouteInput{ - RouteTableId: aws.String(defaultRouteTableId), + RouteTableId: routeTable.RouteTableId, DestinationCidrBlock: r.DestinationCidrBlock, }) + if err != nil { return err } } if r.DestinationIpv6CidrBlock != nil { - log.Printf( - "[INFO] Deleting route from %s: %s", - defaultRouteTableId, *r.DestinationIpv6CidrBlock) _, err := conn.DeleteRoute(&ec2.DeleteRouteInput{ - RouteTableId: aws.String(defaultRouteTableId), + RouteTableId: routeTable.RouteTableId, DestinationIpv6CidrBlock: r.DestinationIpv6CidrBlock, }) + if err != nil { return err } } + if r.DestinationPrefixListId != nil { + _, err := conn.DeleteRoute(&ec2.DeleteRouteInput{ + RouteTableId: routeTable.RouteTableId, + DestinationPrefixListId: r.DestinationPrefixListId, + }) + + if err != nil { + return err + } + } } return nil diff --git a/aws/resource_aws_default_route_table_test.go b/aws/resource_aws_default_route_table_test.go index 054960468ab..91102044022 100644 --- a/aws/resource_aws_default_route_table_test.go +++ b/aws/resource_aws_default_route_table_test.go @@ -27,12 +27,12 @@ func TestAccAWSDefaultRouteTable_basic(t *testing.T) { // Verify non-existent Route Table ID behavior { Config: testAccDefaultRouteTableConfigDefaultRouteTableId("rtb-00000000"), - ExpectError: regexp.MustCompile(`EC2 Default Route Table \(rtb-00000000\): not found`), + ExpectError: regexp.MustCompile(`EC2 Default Route Table \(rtb-00000000\): couldn't find resource`), }, // Verify invalid Route Table ID behavior { Config: testAccDefaultRouteTableConfigDefaultRouteTableId("vpc-00000000"), - ExpectError: regexp.MustCompile(`EC2 Default Route Table \(vpc-00000000\): not found`), + ExpectError: regexp.MustCompile(`EC2 Default Route Table \(vpc-00000000\): couldn't find resource`), }, { Config: testAccDefaultRouteTableConfigBasic(rName), @@ -105,7 +105,7 @@ func TestAccAWSDefaultRouteTable_Route_ConfigMode(t *testing.T) { testAccCheckResourceAttrAccountID(resourceName, "owner_id"), resource.TestCheckResourceAttr(resourceName, "propagating_vgws.#", "0"), resource.TestCheckResourceAttr(resourceName, "route.#", "1"), - testAccCheckDefaultRouteTableRoute(resourceName, "cidr_block", destinationCidr, "gateway_id", igwResourceName, "id"), + testAccCheckAWSRouteTableRoute(resourceName, "cidr_block", destinationCidr, "gateway_id", igwResourceName, "id"), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), ), @@ -127,7 +127,7 @@ func TestAccAWSDefaultRouteTable_Route_ConfigMode(t *testing.T) { // The route block from the previous step should still be // present, because no blocks means "ignore existing blocks". resource.TestCheckResourceAttr(resourceName, "route.#", "1"), - testAccCheckDefaultRouteTableRoute(resourceName, "cidr_block", destinationCidr, "gateway_id", igwResourceName, "id"), + testAccCheckAWSRouteTableRoute(resourceName, "cidr_block", destinationCidr, "gateway_id", igwResourceName, "id"), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), ), @@ -176,7 +176,7 @@ func TestAccAWSDefaultRouteTable_swap(t *testing.T) { testAccCheckResourceAttrAccountID(resourceName, "owner_id"), resource.TestCheckResourceAttr(resourceName, "propagating_vgws.#", "0"), resource.TestCheckResourceAttr(resourceName, "route.#", "1"), - testAccCheckDefaultRouteTableRoute(resourceName, "cidr_block", destinationCidr1, "gateway_id", igwResourceName, "id"), + testAccCheckAWSRouteTableRoute(resourceName, "cidr_block", destinationCidr1, "gateway_id", igwResourceName, "id"), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), ), @@ -199,7 +199,7 @@ func TestAccAWSDefaultRouteTable_swap(t *testing.T) { testAccCheckRouteTableExists(resourceName, &routeTable), testAccCheckAWSRouteTableNumberOfRoutes(&routeTable, 2), resource.TestCheckResourceAttr(resourceName, "route.#", "1"), - testAccCheckDefaultRouteTableRoute(resourceName, "cidr_block", destinationCidr1, "gateway_id", igwResourceName, "id"), + testAccCheckAWSRouteTableRoute(resourceName, "cidr_block", destinationCidr1, "gateway_id", igwResourceName, "id"), ), ExpectNonEmptyPlan: true, }, @@ -209,7 +209,7 @@ func TestAccAWSDefaultRouteTable_swap(t *testing.T) { testAccCheckRouteTableExists(resourceName, &routeTable), testAccCheckAWSRouteTableNumberOfRoutes(&routeTable, 2), resource.TestCheckResourceAttr(resourceName, "route.#", "1"), - testAccCheckDefaultRouteTableRoute(resourceName, "cidr_block", destinationCidr1, "gateway_id", igwResourceName, "id"), + testAccCheckAWSRouteTableRoute(resourceName, "cidr_block", destinationCidr1, "gateway_id", igwResourceName, "id"), resource.TestCheckResourceAttrPair(resourceName, "id", rtResourceName, "id"), ), // Follow up plan will now show a diff as the destination CIDR on the aws_route_table @@ -239,7 +239,7 @@ func TestAccAWSDefaultRouteTable_IPv4_To_TransitGateway(t *testing.T) { testAccCheckRouteTableExists(resourceName, &routeTable), testAccCheckAWSRouteTableNumberOfRoutes(&routeTable, 2), resource.TestCheckResourceAttr(resourceName, "route.#", "1"), - testAccCheckDefaultRouteTableRoute(resourceName, "cidr_block", destinationCidr, "transit_gateway_id", tgwResourceName, "id"), + testAccCheckAWSRouteTableRoute(resourceName, "cidr_block", destinationCidr, "transit_gateway_id", tgwResourceName, "id"), ), }, { @@ -271,7 +271,7 @@ func TestAccAWSDefaultRouteTable_IPv4_To_VpcEndpoint(t *testing.T) { testAccCheckRouteTableExists(resourceName, &routeTable), testAccCheckAWSRouteTableNumberOfRoutes(&routeTable, 2), resource.TestCheckResourceAttr(resourceName, "route.#", "1"), - testAccCheckDefaultRouteTableRoute(resourceName, "cidr_block", destinationCidr, "vpc_endpoint_id", vpceResourceName, "id"), + testAccCheckAWSRouteTableRoute(resourceName, "cidr_block", destinationCidr, "vpc_endpoint_id", vpceResourceName, "id"), ), }, { @@ -313,7 +313,7 @@ func TestAccAWSDefaultRouteTable_VpcEndpointAssociation(t *testing.T) { testAccCheckRouteTableExists(resourceName, &routeTable), testAccCheckAWSRouteTableNumberOfRoutes(&routeTable, 3), resource.TestCheckResourceAttr(resourceName, "route.#", "1"), - testAccCheckDefaultRouteTableRoute(resourceName, "cidr_block", destinationCidr, "gateway_id", igwResourceName, "id"), + testAccCheckAWSRouteTableRoute(resourceName, "cidr_block", destinationCidr, "gateway_id", igwResourceName, "id"), ), }, { @@ -384,14 +384,14 @@ func TestAccAWSDefaultRouteTable_ConditionalCidrBlock(t *testing.T) { Config: testAccDefaultRouteTableConfigConditionalIpv4Ipv6(rName, destinationCidr, destinationIpv6Cidr, false), Check: resource.ComposeTestCheckFunc( testAccCheckRouteTableExists(resourceName, &routeTable), - testAccCheckDefaultRouteTableRoute(resourceName, "cidr_block", destinationCidr, "gateway_id", igwResourceName, "id"), + testAccCheckAWSRouteTableRoute(resourceName, "cidr_block", destinationCidr, "gateway_id", igwResourceName, "id"), ), }, { Config: testAccDefaultRouteTableConfigConditionalIpv4Ipv6(rName, destinationCidr, destinationIpv6Cidr, true), Check: resource.ComposeTestCheckFunc( testAccCheckRouteTableExists(resourceName, &routeTable), - testAccCheckDefaultRouteTableRoute(resourceName, "ipv6_cidr_block", destinationIpv6Cidr, "gateway_id", igwResourceName, "id"), + testAccCheckAWSRouteTableRoute(resourceName, "ipv6_cidr_block", destinationIpv6Cidr, "gateway_id", igwResourceName, "id"), ), }, { @@ -404,6 +404,108 @@ func TestAccAWSDefaultRouteTable_ConditionalCidrBlock(t *testing.T) { }) } +func TestAccAWSDefaultRouteTable_PrefixList_To_InternetGateway(t *testing.T) { + var routeTable ec2.RouteTable + resourceName := "aws_default_route_table.test" + igwResourceName := "aws_internet_gateway.test" + plResourceName := "aws_ec2_managed_prefix_list.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckEc2ManagedPrefixList(t) }, + ErrorCheck: testAccErrorCheck(t, ec2.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckRouteTableDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDefaultRouteTableConfigPrefixListInternetGateway(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRouteTableExists(resourceName, &routeTable), + testAccCheckAWSRouteTableNumberOfRoutes(&routeTable, 2), + resource.TestCheckResourceAttr(resourceName, "route.#", "1"), + testAccCheckAWSRouteTablePrefixListRoute(resourceName, plResourceName, "gateway_id", igwResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccAWSDefaultRouteTableImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + // Default route tables do not currently have a method to remove routes during deletion. + // Managed prefix lists will not delete unless the route is removed prior, otherwise will error: + // "unexpected state 'delete-failed', wanted target 'delete-complete'" + { + Config: testAccDefaultRouteTableConfigPrefixListInternetGatewayNoRoute(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRouteTableExists(resourceName, &routeTable), + ), + }, + }, + }) +} + +func TestAccAWSDefaultRouteTable_RevokeExistingRules(t *testing.T) { + var routeTable ec2.RouteTable + resourceName := "aws_default_route_table.test" + rtResourceName := "aws_route_table.test" + eoigwResourceName := "aws_egress_only_internet_gateway.test" + igwResourceName := "aws_internet_gateway.test" + vgwResourceName := "aws_vpn_gateway.test" + + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, ec2.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckRouteTableDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDefaultRouteTableConfigRevokeExistingRulesCustomRouteTable(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRouteTableExists(rtResourceName, &routeTable), + testAccCheckAWSRouteTableNumberOfRoutes(&routeTable, 3), + resource.TestCheckResourceAttr(rtResourceName, "propagating_vgws.#", "1"), + resource.TestCheckTypeSetElemAttrPair(rtResourceName, "propagating_vgws.*", vgwResourceName, "id"), + resource.TestCheckResourceAttr(rtResourceName, "route.#", "1"), + testAccCheckAWSRouteTableRoute(rtResourceName, "ipv6_cidr_block", "::/0", "egress_only_gateway_id", eoigwResourceName, "id"), + resource.TestCheckResourceAttr(rtResourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(rtResourceName, "tags.Name", rName), + ), + }, + { + Config: testAccDefaultRouteTableConfigRevokeExistingRulesCustomRouteTableToMain(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRouteTableExists(rtResourceName, &routeTable), + testAccCheckAWSRouteTableNumberOfRoutes(&routeTable, 3), + resource.TestCheckResourceAttr(rtResourceName, "propagating_vgws.#", "1"), + resource.TestCheckTypeSetElemAttrPair(rtResourceName, "propagating_vgws.*", vgwResourceName, "id"), + resource.TestCheckResourceAttr(rtResourceName, "route.#", "1"), + testAccCheckAWSRouteTableRoute(rtResourceName, "ipv6_cidr_block", "::/0", "egress_only_gateway_id", eoigwResourceName, "id"), + resource.TestCheckResourceAttr(rtResourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(rtResourceName, "tags.Name", rName), + ), + }, + { + Config: testAccDefaultRouteTableConfigRevokeExistingRulesDefaultRouteTableOverlaysCustomRouteTable(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRouteTableExists(resourceName, &routeTable), + testAccCheckAWSRouteTableNumberOfRoutes(&routeTable, 3), + resource.TestCheckResourceAttr(resourceName, "propagating_vgws.#", "0"), + resource.TestCheckResourceAttr(resourceName, "route.#", "1"), + testAccCheckAWSRouteTableRoute(resourceName, "cidr_block", "0.0.0.0/0", "gateway_id", igwResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), + ), + // The plan on refresh will not be empty as the custom route table resource's routes and propagating VGWs have + // been modified since the default route table's routes and propagating VGWs now overlay the custom route table. + ExpectNonEmptyPlan: true, + }, + }, + }) +} + func testAccCheckDefaultRouteTableDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).ec2conn @@ -433,22 +535,14 @@ func testAccCheckDefaultRouteTableDestroy(s *terraform.State) error { return nil } -func testAccCheckDefaultRouteTableRoute(resourceName, destinationAttr, destination, targetAttr, targetResourceName, targetResourceAttr string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[targetResourceName] +func testAccAWSDefaultRouteTableImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] if !ok { - return fmt.Errorf("Not found: %s", targetResourceName) - } - - target := rs.Primary.Attributes[targetResourceAttr] - if target == "" { - return fmt.Errorf("Not found: %s.%s", targetResourceName, targetResourceAttr) + return "", fmt.Errorf("Not found: %s", resourceName) } - return resource.TestCheckTypeSetElemNestedAttrs(resourceName, "route.*", map[string]string{ - destinationAttr: destination, - targetAttr: target, - })(s) + return rs.Primary.Attributes["vpc_id"], nil } } @@ -955,13 +1049,166 @@ resource "aws_default_route_table" "test" { `, rName, destinationCidr, destinationIpv6Cidr, ipv6Route) } -func testAccAWSDefaultRouteTableImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { - return func(s *terraform.State) (string, error) { - rs, ok := s.RootModule().Resources[resourceName] - if !ok { - return "", fmt.Errorf("Not found: %s", resourceName) - } +func testAccDefaultRouteTableConfigPrefixListInternetGateway(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" - return rs.Primary.Attributes["vpc_id"], nil - } + tags = { + Name = %[1]q + } +} + +resource "aws_internet_gateway" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_managed_prefix_list" "test" { + address_family = "IPv4" + max_entries = 1 + name = %[1]q +} + +resource "aws_default_route_table" "test" { + default_route_table_id = aws_vpc.test.default_route_table_id + + route { + destination_prefix_list_id = aws_ec2_managed_prefix_list.test.id + gateway_id = aws_internet_gateway.test.id + } + + tags = { + Name = %[1]q + } +} +`, rName) +} + +func testAccDefaultRouteTableConfigPrefixListInternetGatewayNoRoute(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_internet_gateway" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_managed_prefix_list" "test" { + address_family = "IPv4" + max_entries = 1 + name = %[1]q +} + +resource "aws_default_route_table" "test" { + default_route_table_id = aws_vpc.test.default_route_table_id + + route { + cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.test.id + } + + tags = { + Name = %[1]q + } +} +`, rName) +} + +func testAccDefaultRouteTableConfigRevokeExistingRulesCustomRouteTable(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + + assign_generated_ipv6_cidr_block = true + + tags = { + Name = %[1]q + } +} + +resource "aws_vpn_gateway" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_vpn_gateway_attachment" "test" { + vpc_id = aws_vpc.test.id + vpn_gateway_id = aws_vpn_gateway.test.id +} + +resource "aws_egress_only_internet_gateway" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id + + propagating_vgws = [aws_vpn_gateway_attachment.test.vpn_gateway_id] + + route { + ipv6_cidr_block = "::/0" + egress_only_gateway_id = aws_egress_only_internet_gateway.test.id + } + + tags = { + Name = %[1]q + } +} +`, rName) +} + +func testAccDefaultRouteTableConfigRevokeExistingRulesCustomRouteTableToMain(rName string) string { + return composeConfig( + testAccDefaultRouteTableConfigRevokeExistingRulesCustomRouteTable(rName), + ` +resource "aws_main_route_table_association" "test" { + vpc_id = aws_vpc.test.id + route_table_id = aws_route_table.test.id +} +`) +} + +func testAccDefaultRouteTableConfigRevokeExistingRulesDefaultRouteTableOverlaysCustomRouteTable(rName string) string { + return composeConfig( + testAccDefaultRouteTableConfigRevokeExistingRulesCustomRouteTableToMain(rName), + fmt.Sprintf(` +resource "aws_internet_gateway" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_default_route_table" "test" { + default_route_table_id = aws_route_table.test.id + + route { + cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.test.id + } + + tags = { + Name = %[1]q + } +} +`, rName)) } diff --git a/aws/resource_aws_route_table.go b/aws/resource_aws_route_table.go index 625658fc9dd..a4a3941b661 100644 --- a/aws/resource_aws_route_table.go +++ b/aws/resource_aws_route_table.go @@ -15,11 +15,15 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-aws/aws/internal/hashcode" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/waiter" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) var routeTableValidDestinations = []string{ "cidr_block", "ipv6_cidr_block", + "destination_prefix_list_id", } var routeTableValidTargets = []string{ @@ -46,14 +50,14 @@ func resourceAwsRouteTable() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "vpc_id": { + "arn": { Type: schema.TypeString, - Required: true, - ForceNew: true, + Computed: true, + }, + "owner_id": { + Type: schema.TypeString, + Computed: true, }, - - "tags": tagsSchema(), - "propagating_vgws": { Type: schema.TypeSet, Optional: true, @@ -61,7 +65,6 @@ func resourceAwsRouteTable() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, Set: schema.HashString, }, - "route": { Type: schema.TypeSet, Computed: true, @@ -80,7 +83,10 @@ func resourceAwsRouteTable() *schema.Resource { validateIpv4CIDRNetworkAddress, ), }, - + "destination_prefix_list_id": { + Type: schema.TypeString, + Optional: true, + }, "ipv6_cidr_block": { Type: schema.TypeString, Optional: true, @@ -97,47 +103,38 @@ func resourceAwsRouteTable() *schema.Resource { Type: schema.TypeString, Optional: true, }, - "egress_only_gateway_id": { Type: schema.TypeString, Optional: true, }, - "gateway_id": { Type: schema.TypeString, Optional: true, }, - "instance_id": { Type: schema.TypeString, Optional: true, }, - "local_gateway_id": { Type: schema.TypeString, Optional: true, }, - "nat_gateway_id": { Type: schema.TypeString, Optional: true, }, - "network_interface_id": { Type: schema.TypeString, Optional: true, }, - "transit_gateway_id": { Type: schema.TypeString, Optional: true, }, - "vpc_endpoint_id": { Type: schema.TypeString, Optional: true, }, - "vpc_peering_connection_id": { Type: schema.TypeString, Optional: true, @@ -146,15 +143,11 @@ func resourceAwsRouteTable() *schema.Resource { }, Set: resourceAwsRouteTableHash, }, - - "arn": { - Type: schema.TypeString, - Computed: true, - }, - - "owner_id": { + "tags": tagsSchema(), + "vpc_id": { Type: schema.TypeString, - Computed: true, + Required: true, + ForceNew: true, }, }, } @@ -181,20 +174,9 @@ func resourceAwsRouteTableCreate(d *schema.ResourceData, meta interface{}) error log.Printf("[INFO] Route Table ID: %s", d.Id()) // Wait for the route table to become available - log.Printf( - "[DEBUG] Waiting for route table (%s) to become available", - d.Id()) - stateConf := &resource.StateChangeConf{ - Pending: []string{"pending"}, - Target: []string{"ready"}, - Refresh: resourceAwsRouteTableStateRefreshFunc(conn, d.Id()), - Timeout: 10 * time.Minute, - NotFoundChecks: 40, - } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf( - "Error waiting for route table (%s) to become available: %s", - d.Id(), err) + log.Printf("[DEBUG] Waiting for route table (%s) to become available", d.Id()) + if _, err := waiter.RouteTableReady(conn, d.Id()); err != nil { + return fmt.Errorf("error waiting for route table (%s) to become available: %w", d.Id(), err) } return resourceAwsRouteTableUpdate(d, meta) @@ -204,20 +186,22 @@ func resourceAwsRouteTableRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig - rtRaw, _, err := resourceAwsRouteTableStateRefreshFunc(conn, d.Id())() - if err != nil { - return err - } - if rtRaw == nil { + routeTable, err := finder.RouteTableByID(conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Route table (%s) not found, removing from state", d.Id()) d.SetId("") return nil } - rt := rtRaw.(*ec2.RouteTable) - d.Set("vpc_id", rt.VpcId) + if err != nil { + return fmt.Errorf("error reading route table (%s): %w", d.Id(), err) + } + + d.Set("vpc_id", routeTable.VpcId) - propagatingVGWs := make([]string, 0, len(rt.PropagatingVgws)) - for _, vgw := range rt.PropagatingVgws { + propagatingVGWs := make([]string, 0, len(routeTable.PropagatingVgws)) + for _, vgw := range routeTable.PropagatingVgws { propagatingVGWs = append(propagatingVGWs, aws.StringValue(vgw.GatewayId)) } d.Set("propagating_vgws", propagatingVGWs) @@ -226,7 +210,7 @@ func resourceAwsRouteTableRead(d *schema.ResourceData, meta interface{}) error { route := &schema.Set{F: resourceAwsRouteTableHash} // Loop through the routes and add them to the set - for _, r := range rt.Routes { + for _, r := range routeTable.Routes { if aws.StringValue(r.GatewayId) == "local" { continue } @@ -235,7 +219,7 @@ func resourceAwsRouteTableRead(d *schema.ResourceData, meta interface{}) error { continue } - if r.DestinationPrefixListId != nil { + if r.DestinationPrefixListId != nil && strings.HasPrefix(aws.StringValue(r.GatewayId), "vpce-") { // Skipping because VPC endpoint routes are handled separately // See aws_vpc_endpoint continue @@ -249,6 +233,9 @@ func resourceAwsRouteTableRead(d *schema.ResourceData, meta interface{}) error { if r.DestinationIpv6CidrBlock != nil { m["ipv6_cidr_block"] = aws.StringValue(r.DestinationIpv6CidrBlock) } + if r.DestinationPrefixListId != nil { + m["destination_prefix_list_id"] = aws.StringValue(r.DestinationPrefixListId) + } if r.CarrierGatewayId != nil { m["carrier_gateway_id"] = aws.StringValue(r.CarrierGatewayId) } @@ -286,11 +273,11 @@ func resourceAwsRouteTableRead(d *schema.ResourceData, meta interface{}) error { d.Set("route", route) // Tags - if err := d.Set("tags", keyvaluetags.Ec2KeyValueTags(rt.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + if err := d.Set("tags", keyvaluetags.Ec2KeyValueTags(routeTable.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { return fmt.Errorf("error setting tags: %w", err) } - ownerID := aws.StringValue(rt.OwnerId) + ownerID := aws.StringValue(routeTable.OwnerId) arn := arn.ARN{ Partition: meta.(*AWSClient).partition, Service: ec2.ServiceName, @@ -319,9 +306,7 @@ func resourceAwsRouteTableUpdate(d *schema.ResourceData, meta interface{}) error id := vgw.(string) // Disable the propagation as it no longer exists in the config - log.Printf( - "[INFO] Deleting VGW propagation from %s: %s", - d.Id(), id) + log.Printf("[INFO] Deleting VGW propagation from %s: %s", d.Id(), id) _, err := conn.DisableVgwRoutePropagation(&ec2.DisableVgwRoutePropagationInput{ RouteTableId: aws.String(d.Id()), GatewayId: aws.String(id), @@ -384,9 +369,7 @@ func resourceAwsRouteTableUpdate(d *schema.ResourceData, meta interface{}) error if s, ok := m["ipv6_cidr_block"].(string); ok && s != "" { deleteOpts.DestinationIpv6CidrBlock = aws.String(s) - log.Printf( - "[INFO] Deleting route from %s: %s", - d.Id(), m["ipv6_cidr_block"].(string)) + log.Printf("[INFO] Deleting route from %s: %s", d.Id(), m["ipv6_cidr_block"].(string)) } if s, ok := m["cidr_block"].(string); ok && s != "" { @@ -395,6 +378,12 @@ func resourceAwsRouteTableUpdate(d *schema.ResourceData, meta interface{}) error log.Printf("[INFO] Deleting route from %s: %s", d.Id(), m["cidr_block"].(string)) } + if s, ok := m["destination_prefix_list_id"].(string); ok && s != "" { + deleteOpts.DestinationPrefixListId = aws.String(s) + + log.Printf("[INFO] Deleting route from %s: %s", d.Id(), m["destination_prefix_list_id"].(string)) + } + _, err := conn.DeleteRoute(deleteOpts) if err != nil { return err @@ -448,6 +437,10 @@ func resourceAwsRouteTableUpdate(d *schema.ResourceData, meta interface{}) error opts.DestinationCidrBlock = aws.String(s) } + if s, ok := m["destination_prefix_list_id"].(string); ok && s != "" { + opts.DestinationPrefixListId = aws.String(s) + } + if s, ok := m["gateway_id"].(string); ok && s != "" { opts.GatewayId = aws.String(s) } @@ -469,7 +462,7 @@ func resourceAwsRouteTableUpdate(d *schema.ResourceData, meta interface{}) error } log.Printf("[INFO] Creating route for %s: %#v", d.Id(), opts) - err := resource.Retry(5*time.Minute, func() *resource.RetryError { + err := resource.Retry(waiter.RouteTableUpdateTimeout, func() *resource.RetryError { _, err := conn.CreateRoute(&opts) if isAWSErr(err, "InvalidRouteTableID.NotFound", "") { @@ -513,14 +506,11 @@ func resourceAwsRouteTableDelete(d *schema.ResourceData, meta interface{}) error // First request the routing table since we'll have to disassociate // all the subnets first. - rtRaw, _, err := resourceAwsRouteTableStateRefreshFunc(conn, d.Id())() + rt, err := waiter.RouteTableReady(conn, d.Id()) + if err != nil { - return err + return fmt.Errorf("error getting route table (%s) prior to disassociating associations: %w", d.Id(), err) } - if rtRaw == nil { - return nil - } - rt := rtRaw.(*ec2.RouteTable) // Do all the disassociations for _, a := range rt.Associations { @@ -555,20 +545,9 @@ func resourceAwsRouteTableDelete(d *schema.ResourceData, meta interface{}) error } // Wait for the route table to really destroy - log.Printf( - "[DEBUG] Waiting for route table (%s) to become destroyed", - d.Id()) - - stateConf := &resource.StateChangeConf{ - Pending: []string{"ready"}, - Target: []string{}, - Refresh: resourceAwsRouteTableStateRefreshFunc(conn, d.Id()), - Timeout: 5 * time.Minute, - } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf( - "Error waiting for route table (%s) to become destroyed: %s", - d.Id(), err) + log.Printf("[DEBUG] Waiting for route table (%s) deletion", d.Id()) + if _, err := waiter.RouteTableDeleted(conn, d.Id()); err != nil { + return fmt.Errorf("error waiting for route table (%s) deletion: %w", d.Id(), err) } return nil @@ -589,6 +568,10 @@ func resourceAwsRouteTableHash(v interface{}) int { buf.WriteString(fmt.Sprintf("%s-", v.(string))) } + if v, ok := m["destination_prefix_list_id"]; ok { + buf.WriteString(fmt.Sprintf("%s-", v.(string))) + } + if v, ok := m["gateway_id"]; ok { buf.WriteString(fmt.Sprintf("%s-", v.(string))) } @@ -635,30 +618,3 @@ func resourceAwsRouteTableHash(v interface{}) int { return hashcode.String(buf.String()) } - -// resourceAwsRouteTableStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch -// a RouteTable. -func resourceAwsRouteTableStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - resp, err := conn.DescribeRouteTables(&ec2.DescribeRouteTablesInput{ - RouteTableIds: []*string{aws.String(id)}, - }) - if err != nil { - if isAWSErr(err, "InvalidRouteTableID.NotFound", "") { - resp = nil - } else { - log.Printf("Error on RouteTableStateRefresh: %s", err) - return nil, "", err - } - } - - if resp == nil { - // Sometimes AWS just has consistency issues and doesn't see - // our instance yet. Return an empty state. - return nil, "", nil - } - - rt := resp.RouteTables[0] - return rt, "ready", nil - } -} diff --git a/aws/resource_aws_route_table_association.go b/aws/resource_aws_route_table_association.go index 2dd3602b7a2..3b970da09dd 100644 --- a/aws/resource_aws_route_table_association.go +++ b/aws/resource_aws_route_table_association.go @@ -11,6 +11,8 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/waiter" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) func resourceAwsRouteTableAssociation() *schema.Resource { @@ -99,15 +101,18 @@ func resourceAwsRouteTableAssociationRead(d *schema.ResourceData, meta interface conn := meta.(*AWSClient).ec2conn // Get the routing table that this association belongs to - rtRaw, _, err := resourceAwsRouteTableStateRefreshFunc( - conn, d.Get("route_table_id").(string))() - if err != nil { - return err - } - if rtRaw == nil { + rtID := d.Get("route_table_id").(string) + rt, err := waiter.RouteTableReady(conn, rtID) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Route table (%s) not found, removing route table association (%s) from state", rtID, d.Id()) + d.SetId("") return nil } - rt := rtRaw.(*ec2.RouteTable) + + if err != nil { + return fmt.Errorf("error getting route table (%s) status while reading route table association: %w", rtID, err) + } // Inspect that the association exists found := false diff --git a/aws/resource_aws_route_table_test.go b/aws/resource_aws_route_table_test.go index 00ea7daeaac..8839a03103c 100644 --- a/aws/resource_aws_route_table_test.go +++ b/aws/resource_aws_route_table_test.go @@ -1046,6 +1046,43 @@ func TestAccAWSRouteTable_MultipleRoutes(t *testing.T) { }) } +func TestAccAWSRouteTable_PrefixList_To_InternetGateway(t *testing.T) { + var routeTable ec2.RouteTable + resourceName := "aws_route_table.test" + igwResourceName := "aws_internet_gateway.test" + plResourceName := "aws_ec2_managed_prefix_list.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckEc2ManagedPrefixList(t) }, + ErrorCheck: testAccErrorCheck(t, ec2.EndpointsID), + IDRefreshName: resourceName, + Providers: testAccProviders, + CheckDestroy: testAccCheckRouteTableDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRouteTableConfigPrefixListInternetGateway(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRouteTableExists(resourceName, &routeTable), + testAccCheckAWSRouteTableNumberOfRoutes(&routeTable, 2), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`route-table/.+$`)), + testAccCheckResourceAttrAccountID(resourceName, "owner_id"), + resource.TestCheckResourceAttr(resourceName, "propagating_vgws.#", "0"), + resource.TestCheckResourceAttr(resourceName, "route.#", "1"), + testAccCheckAWSRouteTablePrefixListRoute(resourceName, plResourceName, "gateway_id", igwResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccCheckRouteTableExists(n string, v *ec2.RouteTable) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -1132,6 +1169,35 @@ func testAccCheckAWSRouteTableRoute(resourceName, destinationAttr, destination, } } +func testAccCheckAWSRouteTablePrefixListRoute(resourceName, prefixListResourceName, targetAttr, targetResourceName, targetResourceAttr string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rsPrefixList, ok := s.RootModule().Resources[prefixListResourceName] + if !ok { + return fmt.Errorf("Not found: %s", prefixListResourceName) + } + + destination := rsPrefixList.Primary.Attributes["id"] + if destination == "" { + return fmt.Errorf("Not found: %s.id", prefixListResourceName) + } + + rsTarget, ok := s.RootModule().Resources[targetResourceName] + if !ok { + return fmt.Errorf("Not found: %s", targetResourceName) + } + + target := rsTarget.Primary.Attributes[targetResourceAttr] + if target == "" { + return fmt.Errorf("Not found: %s.%s", targetResourceName, targetResourceAttr) + } + + return resource.TestCheckTypeSetElemNestedAttrs(resourceName, "route.*", map[string]string{ + "destination_prefix_list_id": destination, + targetAttr: target, + })(s) + } +} + // testAccCheckAWSRouteTableWaitForVpcEndpointRoute returns a TestCheckFunc which waits for // a route to the specified VPC endpoint's prefix list to appear in the specified route table. func testAccCheckAWSRouteTableWaitForVpcEndpointRoute(routeTable *ec2.RouteTable, vpce *ec2.VpcEndpoint) resource.TestCheckFunc { @@ -2178,3 +2244,42 @@ data "aws_ami" "amzn-ami-nat-instance" { } ` } + +func testAccAWSRouteTableConfigPrefixListInternetGateway(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_internet_gateway" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_managed_prefix_list" "test" { + address_family = "IPv4" + max_entries = 1 + name = %[1]q +} + +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id + + route { + destination_prefix_list_id = aws_ec2_managed_prefix_list.test.id + gateway_id = aws_internet_gateway.test.id + } + + tags = { + Name = %[1]q + } +} +`, rName) +} diff --git a/aws/resource_aws_vpn_gateway_route_propagation.go b/aws/resource_aws_vpn_gateway_route_propagation.go index 93d3031e78d..5bdc6988ded 100644 --- a/aws/resource_aws_vpn_gateway_route_propagation.go +++ b/aws/resource_aws_vpn_gateway_route_propagation.go @@ -7,6 +7,8 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/waiter" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) func resourceAwsVpnGatewayRoutePropagation() *schema.Resource { @@ -74,17 +76,24 @@ func resourceAwsVpnGatewayRoutePropagationRead(d *schema.ResourceData, meta inte rtID := d.Get("route_table_id").(string) log.Printf("[INFO] Reading route table %s to check for VPN gateway %s", rtID, gwID) - rtRaw, _, err := resourceAwsRouteTableStateRefreshFunc(conn, rtID)() + rt, err := waiter.RouteTableReady(conn, rtID) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Route table (%s) not found, removing VPN gateway route propagation (%s) from state", rtID, d.Id()) + d.SetId("") + return nil + } + if err != nil { - return err + return fmt.Errorf("error getting route table (%s) status while reading VPN gateway route propagation: %w", rtID, err) } - if rtRaw == nil { + + if rt == nil { log.Printf("[INFO] Route table %q doesn't exist, so dropping %q route propagation from state", rtID, gwID) d.SetId("") return nil } - rt := rtRaw.(*ec2.RouteTable) exists := false for _, vgw := range rt.PropagatingVgws { if aws.StringValue(vgw.GatewayId) == gwID { diff --git a/aws/resource_aws_vpn_gateway_route_propagation_test.go b/aws/resource_aws_vpn_gateway_route_propagation_test.go index ff9f4087edb..cbde36a7185 100644 --- a/aws/resource_aws_vpn_gateway_route_propagation_test.go +++ b/aws/resource_aws_vpn_gateway_route_propagation_test.go @@ -8,6 +8,7 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/waiter" ) func TestAccAWSVPNGatewayRoutePropagation_basic(t *testing.T) { @@ -32,15 +33,16 @@ func TestAccAWSVPNGatewayRoutePropagation_basic(t *testing.T) { rtID = rs.Primary.Attributes["route_table_id"] gwID = rs.Primary.Attributes["vpn_gateway_id"] - rtRaw, _, err := resourceAwsRouteTableStateRefreshFunc(conn, rtID)() + rt, err := waiter.RouteTableReady(conn, rtID) + if err != nil { - return fmt.Errorf("failed to read route table: %s", err) + return fmt.Errorf("error getting route table (%s) while checking VPN gateway route propagation: %w", rtID, err) } - if rtRaw == nil { + + if rt == nil { return errors.New("route table doesn't exist") } - rt := rtRaw.(*ec2.RouteTable) exists := false for _, vgw := range rt.PropagatingVgws { if *vgw.GatewayId == gwID { @@ -58,11 +60,13 @@ func TestAccAWSVPNGatewayRoutePropagation_basic(t *testing.T) { CheckDestroy: func(state *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).ec2conn - rtRaw, _, err := resourceAwsRouteTableStateRefreshFunc(conn, rtID)() + rt, err := waiter.RouteTableDeleted(conn, rtID) + if err != nil { - return fmt.Errorf("failed to read route table: %s", err) + return fmt.Errorf("error getting route table (%s) status while checking destroy: %w", rtID, err) } - if rtRaw != nil { + + if rt != nil { return errors.New("route table still exists") } return nil diff --git a/website/docs/r/default_route_table.html.markdown b/website/docs/r/default_route_table.html.markdown index 1a43ee8ff6d..23f5199cbe9 100644 --- a/website/docs/r/default_route_table.html.markdown +++ b/website/docs/r/default_route_table.html.markdown @@ -72,6 +72,7 @@ One of the following destination arguments must be supplied: * `cidr_block` - (Required) The CIDR block of the route. * `ipv6_cidr_block` - (Optional) The Ipv6 CIDR block of the route +* `destination_prefix_list_id` - (Optional) The ID of a [managed prefix list](ec2_managed_prefix_list.html) destination of the route. One of the following target arguments must be supplied: diff --git a/website/docs/r/route_table.html.markdown b/website/docs/r/route_table.html.markdown index db094952204..c2a61864226 100644 --- a/website/docs/r/route_table.html.markdown +++ b/website/docs/r/route_table.html.markdown @@ -81,6 +81,7 @@ One of the following destination arguments must be supplied: * `cidr_block` - (Required) The CIDR block of the route. * `ipv6_cidr_block` - (Optional) The Ipv6 CIDR block of the route. +* `destination_prefix_list_id` - (Optional) The ID of a [managed prefix list](ec2_managed_prefix_list.html) destination of the route. One of the following target arguments must be supplied: