Skip to content

Commit

Permalink
Merge pull request #24507 from ddericco/f-aws_route-add_local_route_t…
Browse files Browse the repository at this point in the history
…arget

Adding local as supported target for gateway_id
  • Loading branch information
ewbankkit committed Apr 17, 2023
2 parents 4c0c86b + ce8cf0e commit 5226913
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 23 deletions.
3 changes: 3 additions & 0 deletions .changelog/24507.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_route: Allow `gateway_id` value of `local` when updating a Route
```
5 changes: 5 additions & 0 deletions internal/service/ec2/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,3 +281,8 @@ const (
ResInstance = "Instance"
ResInstanceState = "Instance State"
)

const (
gatewayIDLocal = "local"
gatewayIDVPCLattice = "VpcLattice"
)
2 changes: 1 addition & 1 deletion internal/service/ec2/sweep.go
Original file line number Diff line number Diff line change
Expand Up @@ -1428,7 +1428,7 @@ func sweepRouteTables(region string) error {
continue
}

if gatewayID := aws.StringValue(route.GatewayId); gatewayID == "local" || gatewayID == "VpcLattice" {
if gatewayID := aws.StringValue(route.GatewayId); gatewayID == gatewayIDLocal || gatewayID == gatewayIDVPCLattice {
continue
}

Expand Down
2 changes: 1 addition & 1 deletion internal/service/ec2/vpc_default_route_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ func resourceDefaultRouteTableCreate(ctx context.Context, d *schema.ResourceData

// Delete all existing routes.
for _, v := range routeTable.Routes {
if gatewayID := aws.StringValue(v.GatewayId); gatewayID == "local" || gatewayID == "VpcLattice" {
if gatewayID := aws.StringValue(v.GatewayId); gatewayID == gatewayIDLocal || gatewayID == gatewayIDVPCLattice {
continue
}

Expand Down
32 changes: 18 additions & 14 deletions internal/service/ec2/vpc_route.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func ResourceRoute() *schema.Resource {
ReadWithoutTimeout: resourceRouteRead,
UpdateWithoutTimeout: resourceRouteUpdate,
DeleteWithoutTimeout: resourceRouteDelete,

Importer: &schema.ResourceImporter{
StateContext: resourceRouteImport,
},
Expand Down Expand Up @@ -243,7 +244,6 @@ func resourceRouteCreate(ctx context.Context, d *schema.ResourceData, meta inter
return sdkdiag.AppendErrorf(diags, "creating Route: unexpected route target attribute: %q", targetAttributeKey)
}

log.Printf("[DEBUG] Creating Route: %s", input)
_, err = tfresource.RetryWhenAWSErrCodeEquals(ctx, d.Timeout(schema.TimeoutCreate),
func() (interface{}, error) {
return conn.CreateRouteWithContext(ctx, input)
Expand All @@ -252,16 +252,19 @@ func resourceRouteCreate(ctx context.Context, d *schema.ResourceData, meta inter
errCodeInvalidTransitGatewayIDNotFound,
)

// Local routes cannot be created manually.
if tfawserr.ErrMessageContains(err, errCodeInvalidGatewayIDNotFound, "The gateway ID 'local' does not exist") {
return sdkdiag.AppendErrorf(diags, "cannot create local Route, use `terraform import` to manage existing local Routes")
}

if err != nil {
return sdkdiag.AppendErrorf(diags, "creating Route in Route Table (%s) with destination (%s): %s", routeTableID, destination, err)
}

d.SetId(RouteCreateID(routeTableID, destination))

_, err = WaitRouteReady(ctx, conn, routeFinder, routeTableID, destination, d.Timeout(schema.TimeoutCreate))

if err != nil {
return sdkdiag.AppendErrorf(diags, "waiting for Route in Route Table (%s) with destination (%s) to become available: %s", routeTableID, destination, err)
if _, err := WaitRouteReady(ctx, conn, routeFinder, routeTableID, destination, d.Timeout(schema.TimeoutCreate)); err != nil {
return sdkdiag.AppendErrorf(diags, "waiting for Route in Route Table (%s) with destination (%s) create: %s", routeTableID, destination, err)
}

return append(diags, resourceRouteRead(ctx, d, meta)...)
Expand Down Expand Up @@ -368,6 +371,7 @@ func resourceRouteUpdate(ctx context.Context, d *schema.ResourceData, meta inter
return sdkdiag.AppendErrorf(diags, "updating Route: unexpected route destination attribute: %q", destinationAttributeKey)
}

localTarget := target == gatewayIDLocal
switch target := aws.String(target); targetAttributeKey {
case "carrier_gateway_id":
input.CarrierGatewayId = target
Expand All @@ -376,7 +380,11 @@ func resourceRouteUpdate(ctx context.Context, d *schema.ResourceData, meta inter
case "egress_only_gateway_id":
input.EgressOnlyInternetGatewayId = target
case "gateway_id":
input.GatewayId = target
if localTarget {
input.LocalTarget = aws.Bool(true)
} else {
input.GatewayId = target
}
case "instance_id":
input.InstanceId = target
case "local_gateway_id":
Expand All @@ -402,10 +410,8 @@ func resourceRouteUpdate(ctx context.Context, d *schema.ResourceData, meta inter
return sdkdiag.AppendErrorf(diags, "updating Route in Route Table (%s) with destination (%s): %s", routeTableID, destination, err)
}

_, err = WaitRouteReady(ctx, conn, routeFinder, routeTableID, destination, d.Timeout(schema.TimeoutUpdate))

if err != nil {
return sdkdiag.AppendErrorf(diags, "waiting for Route in Route Table (%s) with destination (%s) to become available: %s", routeTableID, destination, err)
if _, err := WaitRouteReady(ctx, conn, routeFinder, routeTableID, destination, d.Timeout(schema.TimeoutUpdate)); err != nil {
return sdkdiag.AppendErrorf(diags, "waiting for Route in Route Table (%s) with destination (%s) update: %s", routeTableID, destination, err)
}

return append(diags, resourceRouteRead(ctx, d, meta)...)
Expand Down Expand Up @@ -463,10 +469,8 @@ func resourceRouteDelete(ctx context.Context, d *schema.ResourceData, meta inter
return sdkdiag.AppendErrorf(diags, "deleting Route in Route Table (%s) with destination (%s): %s", routeTableID, destination, err)
}

_, err = WaitRouteDeleted(ctx, conn, routeFinder, routeTableID, destination, d.Timeout(schema.TimeoutDelete))

if err != nil {
return sdkdiag.AppendErrorf(diags, "waiting for Route in Route Table (%s) with destination (%s) to delete: %s", routeTableID, destination, err)
if _, err := WaitRouteDeleted(ctx, conn, routeFinder, routeTableID, destination, d.Timeout(schema.TimeoutDelete)); err != nil {
return sdkdiag.AppendErrorf(diags, "waiting for Route in Route Table (%s) with destination (%s) delete: %s", routeTableID, destination, err)
}

return diags
Expand Down
2 changes: 1 addition & 1 deletion internal/service/ec2/vpc_route_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -840,7 +840,7 @@ func flattenRoutes(ctx context.Context, conn *ec2.EC2, apiObjects []*ec2.Route)
continue
}

if gatewayID := aws.StringValue(apiObject.GatewayId); gatewayID == "local" || gatewayID == "VpcLattice" {
if gatewayID := aws.StringValue(apiObject.GatewayId); gatewayID == gatewayIDLocal || gatewayID == gatewayIDVPCLattice {
continue
}

Expand Down
2 changes: 1 addition & 1 deletion internal/service/ec2/vpc_route_table_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ func dataSourceRoutesRead(ctx context.Context, conn *ec2.EC2, ec2Routes []*ec2.R
routes := make([]map[string]interface{}, 0, len(ec2Routes))
// Loop through the routes and add them to the set
for _, r := range ec2Routes {
if gatewayID := aws.StringValue(r.GatewayId); gatewayID == "local" || gatewayID == "VpcLattice" {
if gatewayID := aws.StringValue(r.GatewayId); gatewayID == gatewayIDLocal || gatewayID == gatewayIDVPCLattice {
continue
}

Expand Down
120 changes: 117 additions & 3 deletions internal/service/ec2/vpc_route_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1657,6 +1657,68 @@ func TestAccVPCRoute_localRoute(t *testing.T) {
})
}

// https://github.com/hashicorp/terraform-provider-aws/issues/21350.
func TestAccVPCRoute_localRouteUpdate(t *testing.T) {
ctx := acctest.Context(t)
var routeTable ec2.RouteTable
var vpc ec2.Vpc
resourceName := "aws_route.test"
rtResourceName := "aws_route_table.test"
vpcResourceName := "aws_vpc.test"
eniResourceName := "aws_network_interface.test"
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckRouteDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccVPCRouteConfig_ipv4NoRoute(rName),
Check: resource.ComposeTestCheckFunc(
acctest.CheckVPCExists(ctx, vpcResourceName, &vpc),
testAccCheckRouteTableExists(ctx, rtResourceName, &routeTable),
testAccCheckRouteTableNumberOfRoutes(&routeTable, 1),
),
},
{
Config: testAccVPCRouteConfig_ipv4Local(rName),
ResourceName: resourceName,
ImportState: true,
ImportStateIdFunc: func(rt *ec2.RouteTable, v *ec2.Vpc) resource.ImportStateIdFunc {
return func(s *terraform.State) (string, error) {
return fmt.Sprintf("%s_%s", aws.StringValue(rt.RouteTableId), aws.StringValue(v.CidrBlock)), nil
}
}(&routeTable, &vpc),
ImportStatePersist: true,
// Don't verify the state as the local route isn't actually in the pre-import state.
// Just running ImportState verifies that we can import a local route.
ImportStateVerify: false,
},
{
Config: testAccVPCRouteConfig_ipv4LocalToNetworkInterface(rName),
Check: resource.ComposeAggregateTestCheckFunc(
acctest.CheckVPCExists(ctx, vpcResourceName, &vpc),
testAccCheckRouteTableExists(ctx, rtResourceName, &routeTable),
testAccCheckRouteTableNumberOfRoutes(&routeTable, 1),
resource.TestCheckResourceAttr(resourceName, "gateway_id", ""),
resource.TestCheckResourceAttrPair(resourceName, "network_interface_id", eniResourceName, "id"),
),
},
{
Config: testAccVPCRouteConfig_ipv4LocalRestore(rName),
Check: resource.ComposeAggregateTestCheckFunc(
acctest.CheckVPCExists(ctx, vpcResourceName, &vpc),
testAccCheckRouteTableExists(ctx, rtResourceName, &routeTable),
testAccCheckRouteTableNumberOfRoutes(&routeTable, 1),
resource.TestCheckResourceAttr(resourceName, "gateway_id", "local"),
),
},
},
})
}

func TestAccVPCRoute_prefixListToInternetGateway(t *testing.T) {
ctx := acctest.Context(t)
var route ec2.Route
Expand Down Expand Up @@ -3750,9 +3812,7 @@ resource "aws_route_table" "test" {
}

func testAccVPCRouteConfig_ipv4Local(rName string) string {
return acctest.ConfigCompose(
testAccVPCRouteConfig_ipv4NoRoute(rName),
`
return acctest.ConfigCompose(testAccVPCRouteConfig_ipv4NoRoute(rName), `
resource "aws_route" "test" {
route_table_id = aws_route_table.test.id
destination_cidr_block = aws_vpc.test.cidr_block
Expand All @@ -3761,6 +3821,60 @@ resource "aws_route" "test" {
`)
}

func testAccVPCRouteConfig_ipv4LocalToNetworkInterface(rName string) string {
return acctest.ConfigCompose(testAccVPCRouteConfig_ipv4NoRoute(rName), fmt.Sprintf(`
resource "aws_route" "test" {
route_table_id = aws_route_table.test.id
destination_cidr_block = aws_vpc.test.cidr_block
network_interface_id = aws_network_interface.test.id
}
resource "aws_subnet" "test" {
cidr_block = "10.1.1.0/24"
vpc_id = aws_vpc.test.id
tags = {
Name = %[1]q
}
}
resource "aws_network_interface" "test" {
subnet_id = aws_subnet.test.id
tags = {
Name = %[1]q
}
}
`, rName))
}

func testAccVPCRouteConfig_ipv4LocalRestore(rName string) string {
return acctest.ConfigCompose(testAccVPCRouteConfig_ipv4NoRoute(rName), fmt.Sprintf(`
resource "aws_route" "test" {
route_table_id = aws_route_table.test.id
destination_cidr_block = aws_vpc.test.cidr_block
gateway_id = "local"
}
resource "aws_subnet" "test" {
cidr_block = "10.1.1.0/24"
vpc_id = aws_vpc.test.id
tags = {
Name = %[1]q
}
}
resource "aws_network_interface" "test" {
subnet_id = aws_subnet.test.id
tags = {
Name = %[1]q
}
}
`, rName))
}

func testAccVPCRouteConfig_prefixListInternetGateway(rName string) string {
return fmt.Sprintf(`
resource "aws_vpc" "test" {
Expand Down
4 changes: 2 additions & 2 deletions website/docs/r/route.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ One of the following target arguments must be supplied:
* `carrier_gateway_id` - (Optional) Identifier of a carrier gateway. This attribute can only be used when the VPC contains a subnet which is associated with a Wavelength Zone.
* `core_network_arn` - (Optional) The Amazon Resource Name (ARN) of a core network.
* `egress_only_gateway_id` - (Optional) Identifier of a VPC Egress Only Internet Gateway.
* `gateway_id` - (Optional) Identifier of a VPC internet gateway or a virtual private gateway.
* `gateway_id` - (Optional) Identifier of a VPC internet gateway or a virtual private gateway. Specify `local` when updating a previously [imported](#import) local route.
* `instance_id` - (Optional, **Deprecated** use `network_interface_id` instead) Identifier of an EC2 instance.
* `nat_gateway_id` - (Optional) Identifier of a VPC NAT gateway.
* `local_gateway_id` - (Optional) Identifier of a Outpost local gateway.
Expand Down Expand Up @@ -94,7 +94,7 @@ In addition to all arguments above, the following attributes are exported:
## Import

Individual routes can be imported using `ROUTETABLEID_DESTINATION`.

[Local routes](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Route_Tables.html#RouteTables) can be imported using the VPC's IPv4 or IPv6 CIDR blocks.
For example, import a route in route table `rtb-656C65616E6F72` with an IPv4 destination CIDR of `10.42.0.0/16` like this:

```console
Expand Down

0 comments on commit 5226913

Please sign in to comment.