Skip to content

Commit

Permalink
Merge pull request #32794 from hashicorp/f-add-local-gateway-to-route…
Browse files Browse the repository at this point in the history
…-table

vpc/route_table: Allow local inline routes
  • Loading branch information
YakDriver authored Aug 3, 2023
2 parents 54bbc29 + fc41f7a commit 68281a9
Show file tree
Hide file tree
Showing 6 changed files with 428 additions and 10 deletions.
3 changes: 3 additions & 0 deletions .changelog/32794.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_route_table: Allow an existing local route to be adopted or imported and the target to be updated
```
2 changes: 1 addition & 1 deletion internal/service/ec2/vpc_route_data_source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ resource "aws_instance" "test" {
resource "aws_route" "instance" {
route_table_id = aws_route_table.test.id
destination_cidr_block = "10.0.1.0/24"
instance_id = aws_instance.test.id
network_interface_id = aws_instance.test.primary_network_interface_id
}
data "aws_route" "by_peering_connection_id" {
Expand Down
66 changes: 62 additions & 4 deletions internal/service/ec2/vpc_route_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ func resourceRouteTableRead(ctx context.Context, d *schema.ResourceData, meta in
if err := d.Set("propagating_vgws", propagatingVGWs); err != nil {
return sdkdiag.AppendErrorf(diags, "setting propagating_vgws: %s", err)
}
if err := d.Set("route", flattenRoutes(ctx, conn, routeTable.Routes)); err != nil {
if err := d.Set("route", flattenRoutes(ctx, conn, d, routeTable.Routes)); err != nil {
return sdkdiag.AppendErrorf(diags, "setting route: %s", err)
}
d.Set("vpc_id", routeTable.VpcId)
Expand Down Expand Up @@ -480,6 +480,25 @@ func routeTableAddRoute(ctx context.Context, conn *ec2.EC2, routeTableID string,

input.RouteTableId = aws.String(routeTableID)

_, target := routeTableRouteTargetAttribute(tfMap)

if target == gatewayIDLocal {
// created by AWS so probably doesn't need a retry but just to be sure
// we provide a small one
_, err := tfresource.RetryWhenAWSErrCodeEquals(ctx, time.Second*15,
func() (interface{}, error) {
return routeFinder(ctx, conn, routeTableID, destination)
},
errCodeInvalidRouteNotFound,
)

if tfresource.NotFound(err) {
return fmt.Errorf("local route cannot be created but must exist to be adopted, %s %s does not exist", target, destination)
}

return nil
}

_, err := tfresource.RetryWhenAWSErrCodeEquals(ctx, timeout,
func() (interface{}, error) {
return conn.CreateRouteWithContext(ctx, input)
Expand Down Expand Up @@ -719,7 +738,11 @@ func expandReplaceRouteInput(tfMap map[string]interface{}) *ec2.ReplaceRouteInpu
}

if v, ok := tfMap["gateway_id"].(string); ok && v != "" {
apiObject.GatewayId = aws.String(v)
if v == gatewayIDLocal {
apiObject.LocalTarget = aws.Bool(true)
} else {
apiObject.GatewayId = aws.String(v)
}
}

if v, ok := tfMap["local_gateway_id"].(string); ok && v != "" {
Expand Down Expand Up @@ -811,7 +834,7 @@ func flattenRoute(apiObject *ec2.Route) map[string]interface{} {
return tfMap
}

func flattenRoutes(ctx context.Context, conn *ec2.EC2, apiObjects []*ec2.Route) []interface{} {
func flattenRoutes(ctx context.Context, conn *ec2.EC2, d *schema.ResourceData, apiObjects []*ec2.Route) []interface{} {
if len(apiObjects) == 0 {
return nil
}
Expand All @@ -823,7 +846,13 @@ func flattenRoutes(ctx context.Context, conn *ec2.EC2, apiObjects []*ec2.Route)
continue
}

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

// local routes from config need to be included but not default local routes, as determined by hasLocalConfig
// see local route tests
if gatewayID := aws.StringValue(apiObject.GatewayId); gatewayID == gatewayIDLocal && !hasLocalConfig(d, apiObject) {
continue
}

Expand Down Expand Up @@ -854,6 +883,35 @@ func flattenRoutes(ctx context.Context, conn *ec2.EC2, apiObjects []*ec2.Route)
return tfList
}

// hasLocalConfig along with flattenRoutes prevents default local routes from
// being stored in state but allows configured local routes to be stored in
// state. hasLocalConfig checks the ResourceData and flattenRoutes skips or
// includes the route. Normally, you can't count on ResourceData to represent
// config. However, in this case, a local gateway route in ResourceData must
// come from config because of the gatekeeping done by hasLocalConfig and
// flattenRoutes.
func hasLocalConfig(d *schema.ResourceData, apiObject *ec2.Route) bool {
if apiObject.GatewayId == nil {
return false
}
if v, ok := d.GetOk("route"); ok && v.(*schema.Set).Len() > 0 {
for _, v := range v.(*schema.Set).List() {
v := v.(map[string]interface{})
if v["cidr_block"].(string) != aws.StringValue(apiObject.DestinationCidrBlock) &&
v["destination_prefix_list_id"] != aws.StringValue(apiObject.DestinationPrefixListId) &&
v["ipv6_cidr_block"] != aws.StringValue(apiObject.DestinationIpv6CidrBlock) {
continue
}

if v["gateway_id"].(string) == gatewayIDLocal {
return true
}
}
}

return false
}

// routeTableRouteDestinationAttribute returns the attribute key and value of the route table route's destination.
func routeTableRouteDestinationAttribute(m map[string]interface{}) (string, string) {
for _, key := range routeTableValidDestinations {
Expand Down
Loading

0 comments on commit 68281a9

Please sign in to comment.