diff --git a/aws/data_source_aws_route.go b/aws/data_source_aws_route.go index 26ebc96405a..bbbe81aeaf6 100644 --- a/aws/data_source_aws_route.go +++ b/aws/data_source_aws_route.go @@ -4,8 +4,10 @@ import ( "fmt" "log" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + tfec2 "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2" ) func dataSourceAwsRoute() *schema.Resource { @@ -190,3 +192,16 @@ func getRoutes(table *ec2.RouteTable, d *schema.ResourceData) []*ec2.Route { } return routes } + +// Helper: Create an ID for a route +func resourceAwsRouteID(d *schema.ResourceData, r *ec2.Route) string { + routeTableID := d.Get("route_table_id").(string) + + if destination := aws.StringValue(r.DestinationCidrBlock); destination != "" { + return tfec2.RouteCreateID(routeTableID, destination) + } else if destination := aws.StringValue(r.DestinationIpv6CidrBlock); destination != "" { + return tfec2.RouteCreateID(routeTableID, destination) + } + + return "" +} diff --git a/aws/internal/net/cidr.go b/aws/internal/net/cidr.go new file mode 100644 index 00000000000..5d4e8db0525 --- /dev/null +++ b/aws/internal/net/cidr.go @@ -0,0 +1,23 @@ +package net + +import ( + "net" +) + +// CIDRBlocksEqual returns whether or not two CIDR blocks are equal: +// - Both CIDR blocks parse to an IP address and network +// - The string representation of the IP addresses are equal +// - The string representation of the networks are equal +// This function is especially useful for IPv6 CIDR blocks which have multiple valid representations. +func CIDRBlocksEqual(cidr1, cidr2 string) bool { + ip1, ipnet1, err := net.ParseCIDR(cidr1) + if err != nil { + return false + } + ip2, ipnet2, err := net.ParseCIDR(cidr2) + if err != nil { + return false + } + + return ip2.String() == ip1.String() && ipnet2.String() == ipnet1.String() +} diff --git a/aws/internal/net/cidr_test.go b/aws/internal/net/cidr_test.go new file mode 100644 index 00000000000..a557b64a579 --- /dev/null +++ b/aws/internal/net/cidr_test.go @@ -0,0 +1,26 @@ +package net + +import ( + "testing" +) + +func Test_CIDRBlocksEqual(t *testing.T) { + for _, ts := range []struct { + cidr1 string + cidr2 string + equal bool + }{ + {"10.2.2.0/24", "10.2.2.0/24", true}, + {"10.2.2.0/1234", "10.2.2.0/24", false}, + {"10.2.2.0/24", "10.2.2.0/1234", false}, + {"2001::/15", "2001::/15", true}, + {"::/0", "2001::/15", false}, + {"::/0", "::0/0", true}, + {"", "", false}, + } { + equal := CIDRBlocksEqual(ts.cidr1, ts.cidr2) + if ts.equal != equal { + t.Fatalf("CIDRBlocksEqual(%q, %q) should be: %t", ts.cidr1, ts.cidr2, ts.equal) + } + } +} diff --git a/aws/internal/service/ec2/errors.go b/aws/internal/service/ec2/errors.go index bde544dbbaa..7c339f0dabb 100644 --- a/aws/internal/service/ec2/errors.go +++ b/aws/internal/service/ec2/errors.go @@ -24,3 +24,13 @@ const ErrCodeClientVpnAuthorizationRuleNotFound = "InvalidClientVpnEndpointAutho const ErrCodeClientVpnAssociationIdNotFound = "InvalidClientVpnAssociationId.NotFound" const ErrCodeClientVpnRouteNotFound = "InvalidClientVpnRouteNotFound" + +const ErrCodeInvalidParameterException = "InvalidParameterException" + +const ErrCodeInvalidParameterValue = "InvalidParameterValue" + +const ErrCodeRouteNotFound = "InvalidRoute.NotFound" + +const ErrCodeRouteTableNotFound = "InvalidRouteTableID.NotFound" + +const ErrCodeTransitGatewayNotFound = "InvalidTransitGatewayID.NotFound" diff --git a/aws/internal/service/ec2/finder/finder.go b/aws/internal/service/ec2/finder/finder.go index 202a1161144..faae90cf033 100644 --- a/aws/internal/service/ec2/finder/finder.go +++ b/aws/internal/service/ec2/finder/finder.go @@ -3,6 +3,7 @@ package finder import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" + tfnet "github.com/terraform-providers/terraform-provider-aws/aws/internal/net" tfec2 "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2" ) @@ -54,3 +55,58 @@ func ClientVpnRouteByID(conn *ec2.EC2, routeID string) (*ec2.DescribeClientVpnRo return ClientVpnRoute(conn, endpointID, targetSubnetID, destinationCidr) } + +// RouteTableByID returns the route table corresponding to the specified identifier. +// Returns nil if no route table is found. +func RouteTableByID(conn *ec2.EC2, routeTableID string) (*ec2.RouteTable, error) { + input := &ec2.DescribeRouteTablesInput{ + RouteTableIds: aws.StringSlice([]string{routeTableID}), + } + + output, err := conn.DescribeRouteTables(input) + if err != nil { + return nil, err + } + + if output == nil || len(output.RouteTables) == 0 || output.RouteTables[0] == nil { + return nil, nil + } + + return output.RouteTables[0], nil +} + +type RouteFinder func(*ec2.EC2, string, string) (*ec2.Route, error) + +// RouteByIpv4Destination returns the route corresponding to the specified IPv4 destination. +// Returns nil if no route is found. +func RouteByIpv4Destination(conn *ec2.EC2, routeTableID, destinationCidr string) (*ec2.Route, error) { + routeTable, err := RouteTableByID(conn, routeTableID) + if err != nil { + return nil, err + } + + for _, route := range routeTable.Routes { + if aws.StringValue(route.DestinationCidrBlock) == destinationCidr { + return route, nil + } + } + + return nil, nil +} + +// RouteByIpv6Destination returns the route corresponding to the specified IPv6 destination. +// Returns nil if no route is found. +func RouteByIpv6Destination(conn *ec2.EC2, routeTableID, destinationIpv6Cidr string) (*ec2.Route, error) { + routeTable, err := RouteTableByID(conn, routeTableID) + if err != nil { + return nil, err + } + + for _, route := range routeTable.Routes { + if tfnet.CIDRBlocksEqual(aws.StringValue(route.DestinationIpv6CidrBlock), destinationIpv6Cidr) { + return route, nil + } + } + + return nil, nil +} diff --git a/aws/internal/service/ec2/id.go b/aws/internal/service/ec2/id.go index 824e23eaead..ea82b270b9f 100644 --- a/aws/internal/service/ec2/id.go +++ b/aws/internal/service/ec2/id.go @@ -3,6 +3,8 @@ package ec2 import ( "fmt" "strings" + + "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" ) const clientVpnAuthorizationRuleIDSeparator = "," @@ -49,3 +51,8 @@ func ClientVpnRouteParseID(id string) (string, string, string, error) { fmt.Errorf("unexpected format for ID (%q), expected endpoint-id"+clientVpnRouteIDSeparator+ "target-subnet-id"+clientVpnRouteIDSeparator+"destination-cidr-block", id) } + +// RouteCreateID returns a route resource ID. +func RouteCreateID(routeTableID, destination string) string { + return fmt.Sprintf("r-%s%d", routeTableID, hashcode.String(destination)) +} diff --git a/aws/resource_aws_route.go b/aws/resource_aws_route.go index 7abbf78c418..0559761f048 100644 --- a/aws/resource_aws_route.go +++ b/aws/resource_aws_route.go @@ -1,7 +1,6 @@ package aws import ( - "errors" "fmt" "log" "strings" @@ -9,18 +8,13 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" - "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + tfec2 "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/finder" ) -// How long to sleep if a limit-exceeded event happens -var routeTargetValidationError = errors.New("Error: more than 1 target specified. Only 1 of gateway_id, " + - "egress_only_gateway_id, nat_gateway_id, instance_id, network_interface_id or " + - "vpc_peering_connection_id is allowed.") - -// AWS Route resource Schema declaration func resourceAwsRoute() *schema.Resource { return &schema.Resource{ Create: resourceAwsRouteCreate, @@ -41,7 +35,7 @@ func resourceAwsRoute() *schema.Resource { } else { d.Set("destination_cidr_block", destination) } - d.SetId(fmt.Sprintf("r-%s%d", routeTableID, hashcode.String(destination))) + d.SetId(tfec2.RouteCreateID(routeTableID, destination)) return []*schema.ResourceData{d}, nil }, }, @@ -144,198 +138,85 @@ func resourceAwsRoute() *schema.Resource { func resourceAwsRouteCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn - var numTargets int - var setTarget string - allowedTargets := []string{ - "egress_only_gateway_id", - "gateway_id", - "nat_gateway_id", - "instance_id", - "network_interface_id", - "transit_gateway_id", - "vpc_peering_connection_id", - } - - // Check if more than 1 target is specified - for _, target := range allowedTargets { - if len(d.Get(target).(string)) > 0 { - numTargets++ - setTarget = target - } - } - if numTargets > 1 { - return routeTargetValidationError + destinationAttr, targetAttr, err := routeDestinationAndTargetAttributes(d) + if err != nil { + return err } - createOpts := &ec2.CreateRouteInput{} - // Formulate CreateRouteInput based on the target type - switch setTarget { - case "gateway_id": - createOpts = &ec2.CreateRouteInput{ - RouteTableId: aws.String(d.Get("route_table_id").(string)), - GatewayId: aws.String(d.Get("gateway_id").(string)), - } + destination := d.Get(destinationAttr).(string) + routeTableID := d.Get("route_table_id").(string) + input := &ec2.CreateRouteInput{ + RouteTableId: aws.String(routeTableID), + } - if v, ok := d.GetOk("destination_cidr_block"); ok { - createOpts.DestinationCidrBlock = aws.String(v.(string)) - } + var routeFinder finder.RouteFinder - if v, ok := d.GetOk("destination_ipv6_cidr_block"); ok { - createOpts.DestinationIpv6CidrBlock = aws.String(v.(string)) - } + switch destinationAttr { + case "destination_cidr_block": + input.DestinationCidrBlock = aws.String(destination) + routeFinder = finder.RouteByIpv4Destination + case "destination_ipv6_cidr_block": + input.DestinationIpv6CidrBlock = aws.String(destination) + routeFinder = finder.RouteByIpv6Destination + default: + return fmt.Errorf("unexpected destination attribute: `%s`", destinationAttr) + } + target := aws.String(d.Get(targetAttr).(string)) + switch targetAttr { case "egress_only_gateway_id": - createOpts = &ec2.CreateRouteInput{ - RouteTableId: aws.String(d.Get("route_table_id").(string)), - DestinationIpv6CidrBlock: aws.String(d.Get("destination_ipv6_cidr_block").(string)), - EgressOnlyInternetGatewayId: aws.String(d.Get("egress_only_gateway_id").(string)), - } - case "nat_gateway_id": - createOpts = &ec2.CreateRouteInput{ - RouteTableId: aws.String(d.Get("route_table_id").(string)), - DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), - NatGatewayId: aws.String(d.Get("nat_gateway_id").(string)), - } + input.EgressOnlyInternetGatewayId = target + case "gateway_id": + input.GatewayId = target case "instance_id": - createOpts = &ec2.CreateRouteInput{ - RouteTableId: aws.String(d.Get("route_table_id").(string)), - InstanceId: aws.String(d.Get("instance_id").(string)), - } - - if v, ok := d.GetOk("destination_cidr_block"); ok { - createOpts.DestinationCidrBlock = aws.String(v.(string)) - } - - if v, ok := d.GetOk("destination_ipv6_cidr_block"); ok { - createOpts.DestinationIpv6CidrBlock = aws.String(v.(string)) - } - + input.InstanceId = target + case "nat_gateway_id": + input.NatGatewayId = target case "network_interface_id": - createOpts = &ec2.CreateRouteInput{ - RouteTableId: aws.String(d.Get("route_table_id").(string)), - NetworkInterfaceId: aws.String(d.Get("network_interface_id").(string)), - } - - if v, ok := d.GetOk("destination_cidr_block"); ok { - createOpts.DestinationCidrBlock = aws.String(v.(string)) - } - - if v, ok := d.GetOk("destination_ipv6_cidr_block"); ok { - createOpts.DestinationIpv6CidrBlock = aws.String(v.(string)) - } - + input.NetworkInterfaceId = target case "transit_gateway_id": - createOpts = &ec2.CreateRouteInput{ - RouteTableId: aws.String(d.Get("route_table_id").(string)), - TransitGatewayId: aws.String(d.Get("transit_gateway_id").(string)), - } - - if v, ok := d.GetOk("destination_cidr_block"); ok { - createOpts.DestinationCidrBlock = aws.String(v.(string)) - } - - if v, ok := d.GetOk("destination_ipv6_cidr_block"); ok { - createOpts.DestinationIpv6CidrBlock = aws.String(v.(string)) - } - + input.TransitGatewayId = target case "vpc_peering_connection_id": - createOpts = &ec2.CreateRouteInput{ - RouteTableId: aws.String(d.Get("route_table_id").(string)), - VpcPeeringConnectionId: aws.String(d.Get("vpc_peering_connection_id").(string)), - } - - if v, ok := d.GetOk("destination_cidr_block"); ok { - createOpts.DestinationCidrBlock = aws.String(v.(string)) - } - - if v, ok := d.GetOk("destination_ipv6_cidr_block"); ok { - createOpts.DestinationIpv6CidrBlock = aws.String(v.(string)) - } - + input.VpcPeeringConnectionId = target default: - return fmt.Errorf("A valid target type is missing. Specify one of the following attributes: %s", strings.Join(allowedTargets, ", ")) + return fmt.Errorf("unexpected target attribute: `%s`", targetAttr) } - log.Printf("[DEBUG] Route create config: %s", createOpts) - // Create the route - var err error + log.Printf("[DEBUG] Creating Route: %s", input) + err = createRoute(conn, input, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return err + } + var route *ec2.Route err = resource.Retry(d.Timeout(schema.TimeoutCreate), func() *resource.RetryError { - _, err = conn.CreateRoute(createOpts) + route, err = routeFinder(conn, routeTableID, destination) - if isAWSErr(err, "InvalidParameterException", "") { - return resource.RetryableError(err) - } - - if isAWSErr(err, "InvalidTransitGatewayID.NotFound", "") { + if err != nil { return resource.RetryableError(err) } - if err != nil { - return resource.NonRetryableError(err) + if route == nil { + return resource.RetryableError(fmt.Errorf("Route not found")) } return nil }) + if isResourceTimeoutError(err) { - _, err = conn.CreateRoute(createOpts) - } - if err != nil { - return fmt.Errorf("Error creating route: %s", err) + route, err = routeFinder(conn, routeTableID, destination) } - var route *ec2.Route - - if v, ok := d.GetOk("destination_cidr_block"); ok { - err = resource.Retry(d.Timeout(schema.TimeoutCreate), func() *resource.RetryError { - route, err = resourceAwsRouteFindRoute(conn, d.Get("route_table_id").(string), v.(string), "") - if err == nil { - if route != nil { - return nil - } else { - err = errors.New("Route not found") - } - } - - return resource.RetryableError(err) - }) - if isResourceTimeoutError(err) { - route, err = resourceAwsRouteFindRoute(conn, d.Get("route_table_id").(string), v.(string), "") - } - if err != nil { - return fmt.Errorf("Error finding route after creating it: %s", err) - } - if route == nil { - return fmt.Errorf("Unable to find matching route for Route Table (%s) and destination CIDR block (%s).", d.Get("route_table_id").(string), v) - } + if err != nil { + return fmt.Errorf("error reading Route for Route Table (%s) with destination (%s): %s", routeTableID, destination, err) } - if v, ok := d.GetOk("destination_ipv6_cidr_block"); ok { - err = resource.Retry(d.Timeout(schema.TimeoutCreate), func() *resource.RetryError { - route, err = resourceAwsRouteFindRoute(conn, d.Get("route_table_id").(string), "", v.(string)) - if err == nil { - if route != nil { - return nil - } else { - err = errors.New("Route not found") - } - } - - return resource.RetryableError(err) - }) - if isResourceTimeoutError(err) { - route, err = resourceAwsRouteFindRoute(conn, d.Get("route_table_id").(string), "", v.(string)) - } - if err != nil { - return fmt.Errorf("Error finding route after creating it: %s", err) - } - if route == nil { - return fmt.Errorf("Unable to find matching route for Route Table (%s) and destination IPv6 CIDR block (%s).", d.Get("route_table_id").(string), v) - } + if route == nil { + return fmt.Errorf("Route in Route Table (%s) with destination (%s) not found", routeTableID, destination) } - d.SetId(resourceAwsRouteID(d, route)) + d.SetId(tfec2.RouteCreateID(routeTableID, destination)) return resourceAwsRouteRead(d, meta) } @@ -343,22 +224,36 @@ func resourceAwsRouteCreate(d *schema.ResourceData, meta interface{}) error { func resourceAwsRouteRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn - routeTableId := d.Get("route_table_id").(string) - destinationCidrBlock := d.Get("destination_cidr_block").(string) - destinationIpv6CidrBlock := d.Get("destination_ipv6_cidr_block").(string) + destinationAttr := routeDestinationAttr(d) - route, err := resourceAwsRouteFindRoute(conn, routeTableId, destinationCidrBlock, destinationIpv6CidrBlock) - if isAWSErr(err, "InvalidRouteTableID.NotFound", "") { - log.Printf("[WARN] Route Table (%s) not found, removing from state", routeTableId) + destination := d.Get(destinationAttr).(string) + routeTableID := d.Get("route_table_id").(string) + + var routeFinder finder.RouteFinder + + switch destinationAttr { + case "destination_cidr_block": + routeFinder = finder.RouteByIpv4Destination + case "destination_ipv6_cidr_block": + routeFinder = finder.RouteByIpv6Destination + default: + return fmt.Errorf("unexpected destination attribute: `%s`", destinationAttr) + } + + route, err := routeFinder(conn, routeTableID, destination) + + if isAWSErr(err, tfec2.ErrCodeRouteTableNotFound, "") { + log.Printf("[WARN] Route Table (%s) not found, removing from state", routeTableID) d.SetId("") return nil } + if err != nil { - return err + return fmt.Errorf("error reading Route for Route Table (%s) with destination (%s): %s", routeTableID, destination, err) } if route == nil { - log.Printf("[WARN] Matching route not found, removing from state") + log.Printf("[WARN] Route in Route Table (%s) with destination (%s) not found, removing from state", routeTableID, destination) d.SetId("") return nil } @@ -382,186 +277,234 @@ func resourceAwsRouteRead(d *schema.ResourceData, meta interface{}) error { func resourceAwsRouteUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn - var numTargets int - var setTarget string - - allowedTargets := []string{ - "egress_only_gateway_id", - "gateway_id", - "nat_gateway_id", - "network_interface_id", - "instance_id", - "transit_gateway_id", - "vpc_peering_connection_id", - } - // Check if more than 1 target is specified - for _, target := range allowedTargets { - if len(d.Get(target).(string)) > 0 { - numTargets++ - setTarget = target - } + + destinationAttr, targetAttr, err := routeDestinationAndTargetAttributes(d) + if err != nil { + return err } - switch setTarget { - //instance_id is a special case due to the fact that AWS will "discover" the network_interface_id - //when it creates the route and return that data. In the case of an update, we should ignore the - //existing network_interface_id - case "instance_id": - if numTargets > 2 || (numTargets == 2 && len(d.Get("network_interface_id").(string)) == 0) { - return routeTargetValidationError - } + destination := d.Get(destinationAttr).(string) + routeTableID := d.Get("route_table_id").(string) + input := &ec2.ReplaceRouteInput{ + RouteTableId: aws.String(routeTableID), + } + + switch destinationAttr { + case "destination_cidr_block": + input.DestinationCidrBlock = aws.String(destination) + case "destination_ipv6_cidr_block": + input.DestinationIpv6CidrBlock = aws.String(destination) default: - if numTargets > 1 { - return routeTargetValidationError - } + return fmt.Errorf("unexpected destination attribute: `%s`", destinationAttr) } - var replaceOpts *ec2.ReplaceRouteInput - // Formulate ReplaceRouteInput based on the target type - switch setTarget { - case "gateway_id": - replaceOpts = &ec2.ReplaceRouteInput{ - RouteTableId: aws.String(d.Get("route_table_id").(string)), - DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), - GatewayId: aws.String(d.Get("gateway_id").(string)), - } + target := aws.String(d.Get(targetAttr).(string)) + switch targetAttr { case "egress_only_gateway_id": - replaceOpts = &ec2.ReplaceRouteInput{ - RouteTableId: aws.String(d.Get("route_table_id").(string)), - DestinationIpv6CidrBlock: aws.String(d.Get("destination_ipv6_cidr_block").(string)), - EgressOnlyInternetGatewayId: aws.String(d.Get("egress_only_gateway_id").(string)), - } - case "nat_gateway_id": - replaceOpts = &ec2.ReplaceRouteInput{ - RouteTableId: aws.String(d.Get("route_table_id").(string)), - DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), - NatGatewayId: aws.String(d.Get("nat_gateway_id").(string)), - } + input.EgressOnlyInternetGatewayId = target + case "gateway_id": + input.GatewayId = target case "instance_id": - replaceOpts = &ec2.ReplaceRouteInput{ - RouteTableId: aws.String(d.Get("route_table_id").(string)), - DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), - InstanceId: aws.String(d.Get("instance_id").(string)), - } + input.InstanceId = target + case "nat_gateway_id": + input.NatGatewayId = target case "network_interface_id": - replaceOpts = &ec2.ReplaceRouteInput{ - RouteTableId: aws.String(d.Get("route_table_id").(string)), - DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), - NetworkInterfaceId: aws.String(d.Get("network_interface_id").(string)), - } + input.NetworkInterfaceId = target case "transit_gateway_id": - replaceOpts = &ec2.ReplaceRouteInput{ - RouteTableId: aws.String(d.Get("route_table_id").(string)), - DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), - TransitGatewayId: aws.String(d.Get("transit_gateway_id").(string)), - } + input.TransitGatewayId = target case "vpc_peering_connection_id": - replaceOpts = &ec2.ReplaceRouteInput{ - RouteTableId: aws.String(d.Get("route_table_id").(string)), - DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), - VpcPeeringConnectionId: aws.String(d.Get("vpc_peering_connection_id").(string)), - } + input.VpcPeeringConnectionId = target default: - return fmt.Errorf("An invalid target type specified: %s", setTarget) + return fmt.Errorf("unexpected target attribute: `%s`", targetAttr) } - log.Printf("[DEBUG] Route replace config: %s", replaceOpts) - // Replace the route - _, err := conn.ReplaceRoute(replaceOpts) - return err + log.Printf("[DEBUG] Updating Route: %s", input) + _, err = conn.ReplaceRoute(input) + + if err != nil { + return fmt.Errorf("error updating Route for Route Table (%s) with destination (%s): %s", routeTableID, destination, err) + } + + return resourceAwsRouteRead(d, meta) } func resourceAwsRouteDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn - deleteOpts := &ec2.DeleteRouteInput{ - RouteTableId: aws.String(d.Get("route_table_id").(string)), - } - if v, ok := d.GetOk("destination_cidr_block"); ok { - deleteOpts.DestinationCidrBlock = aws.String(v.(string)) + destinationAttr := routeDestinationAttr(d) + + destination := d.Get(destinationAttr).(string) + routeTableID := d.Get("route_table_id").(string) + input := &ec2.DeleteRouteInput{ + RouteTableId: aws.String(routeTableID), } - if v, ok := d.GetOk("destination_ipv6_cidr_block"); ok { - deleteOpts.DestinationIpv6CidrBlock = aws.String(v.(string)) + + switch destinationAttr { + case "destination_cidr_block": + input.DestinationCidrBlock = aws.String(destination) + case "destination_ipv6_cidr_block": + input.DestinationIpv6CidrBlock = aws.String(destination) + default: + return fmt.Errorf("unexpected destination attribute: `%s`", destinationAttr) } - log.Printf("[DEBUG] Route delete opts: %s", deleteOpts) err := resource.Retry(d.Timeout(schema.TimeoutDelete), func() *resource.RetryError { - log.Printf("[DEBUG] Trying to delete route with opts %s", deleteOpts) - var err error - _, err = conn.DeleteRoute(deleteOpts) + log.Printf("[DEBUG] Deleting Route (%s)", d.Id()) + _, err := conn.DeleteRoute(input) if err == nil { return nil } - if isAWSErr(err, "InvalidRoute.NotFound", "") { + if isAWSErr(err, tfec2.ErrCodeRouteNotFound, "") { + return nil + } + + // Local routes (which may have been imported) cannot be deleted. Remove from state. + if isAWSErr(err, tfec2.ErrCodeInvalidParameterValue, "cannot remove local route") { return nil } - if isAWSErr(err, "InvalidParameterException", "") { + if isAWSErr(err, tfec2.ErrCodeInvalidParameterException, "") { return resource.RetryableError(err) } return resource.NonRetryableError(err) }) + if isResourceTimeoutError(err) { - _, err = conn.DeleteRoute(deleteOpts) + log.Printf("[DEBUG] Deleting Route (%s)", d.Id()) + _, err = conn.DeleteRoute(input) } - if isAWSErr(err, "InvalidRoute.NotFound", "") { + + if isAWSErr(err, tfec2.ErrCodeRouteNotFound, "") { return nil } + if err != nil { - return fmt.Errorf("Error deleting route: %s", err) + return fmt.Errorf("error deleting Route for Route Table (%s) with destination (%s): %s", routeTableID, destination, err) } + return nil } -// Helper: Create an ID for a route -func resourceAwsRouteID(d *schema.ResourceData, r *ec2.Route) string { +// routeDestinationAttr return the route's destination attribute name. +// No validation is done. +func routeDestinationAttr(d *schema.ResourceData) string { + destinationAttrs := []string{"destination_cidr_block", "destination_ipv6_cidr_block"} - if r.DestinationIpv6CidrBlock != nil && *r.DestinationIpv6CidrBlock != "" { - return fmt.Sprintf("r-%s%d", d.Get("route_table_id").(string), hashcode.String(*r.DestinationIpv6CidrBlock)) + for _, attr := range destinationAttrs { + if v := d.Get(attr).(string); v != "" { + return attr + } } - return fmt.Sprintf("r-%s%d", d.Get("route_table_id").(string), hashcode.String(*r.DestinationCidrBlock)) + return "" } -// resourceAwsRouteFindRoute returns any route whose destination is the specified IPv4 or IPv6 CIDR block. -// Returns nil if the route table exists but no matching destination is found. -func resourceAwsRouteFindRoute(conn *ec2.EC2, rtbid string, cidr string, ipv6cidr string) (*ec2.Route, error) { - routeTableID := rtbid +// routeDestinationAndTargetAttributes return the route's destination and target attribute names. +// It also validates the resource, returning any validation error. +func routeDestinationAndTargetAttributes(d *schema.ResourceData) (string, string, error) { + destinationAttrs := map[string]struct { + ipv4Destination bool + ipv6Destination bool + }{ + "destination_cidr_block": {true, false}, + "destination_ipv6_cidr_block": {false, true}, + } - findOpts := &ec2.DescribeRouteTablesInput{ - RouteTableIds: []*string{&routeTableID}, + destinationAttr := "" + ipv4Destination := false + ipv6Destination := false + for attr, kind := range destinationAttrs { + if v := d.Get(attr).(string); v != "" { + if destinationAttr != "" { + return "", "", fmt.Errorf("`%s` conflicts with `%s`", attr, destinationAttr) + } + + destinationAttr = attr + ipv4Destination = kind.ipv4Destination + ipv6Destination = kind.ipv6Destination + } } - resp, err := conn.DescribeRouteTables(findOpts) - if err != nil { - return nil, err + if destinationAttr == "" { + keys := []string{} + for k := range destinationAttrs { + keys = append(keys, k) + } + + return "", "", fmt.Errorf("one of `%s` must be specified", strings.Join(keys, "`, `")) } - if len(resp.RouteTables) < 1 || resp.RouteTables[0] == nil { - return nil, nil + targetAttrs := map[string]struct { + ipv4Destination bool + ipv6Destination bool + }{ + "egress_only_gateway_id": {false, true}, + "gateway_id": {true, true}, + "instance_id": {true, true}, + "nat_gateway_id": {true, false}, + "network_interface_id": {true, true}, + "transit_gateway_id": {true, false}, + "vpc_peering_connection_id": {true, true}, } - if cidr != "" { - for _, route := range (*resp.RouteTables[0]).Routes { - if route.DestinationCidrBlock != nil && *route.DestinationCidrBlock == cidr { - return route, nil + targetAttr := "" + for attr, allowed := range targetAttrs { + if v := d.Get(attr).(string); v != "" && d.HasChange(attr) { + if targetAttr != "" { + return "", "", fmt.Errorf("`%s` conflicts with `%s`", attr, targetAttr) + } + + if (ipv4Destination && !allowed.ipv4Destination) || (ipv6Destination && !allowed.ipv6Destination) { + return "", "", fmt.Errorf("`%s` not allowed for `%s` target", destinationAttr, attr) } + + targetAttr = attr } + } - return nil, nil + if targetAttr == "" { + keys := []string{} + for k := range targetAttrs { + keys = append(keys, k) + } + + return "", "", fmt.Errorf("one of `%s` must be specified", strings.Join(keys, "`, `")) } - if ipv6cidr != "" { - for _, route := range (*resp.RouteTables[0]).Routes { - if cidrBlocksEqual(aws.StringValue(route.DestinationIpv6CidrBlock), ipv6cidr) { - return route, nil - } + return destinationAttr, targetAttr, nil +} + +// createRoute attempts to create a route. +// The specified eventual consistency timeout is respected. +// Any error is returned. +func createRoute(conn *ec2.EC2, input *ec2.CreateRouteInput, timeout time.Duration) error { + err := resource.Retry(timeout, func() *resource.RetryError { + _, err := conn.CreateRoute(input) + + if isAWSErr(err, tfec2.ErrCodeInvalidParameterException, "") { + return resource.RetryableError(err) + } + + if isAWSErr(err, tfec2.ErrCodeTransitGatewayNotFound, "") { + return resource.RetryableError(err) } - return nil, nil + if err != nil { + return resource.NonRetryableError(err) + } + + return nil + }) + + if isResourceTimeoutError(err) { + _, err = conn.CreateRoute(input) } - return nil, nil + if err != nil { + return fmt.Errorf("error creating Route: %s", err) + } + + return nil } diff --git a/aws/resource_aws_route_table_test.go b/aws/resource_aws_route_table_test.go index 235a70c083f..9dd3d7dcee0 100644 --- a/aws/resource_aws_route_table_test.go +++ b/aws/resource_aws_route_table_test.go @@ -555,6 +555,16 @@ func TestAccAWSRouteTable_ConditionalCidrBlock(t *testing.T) { }) } +func testAccCheckAWSRouteTableNumberOfRoutes(routeTable *ec2.RouteTable, n int) resource.TestCheckFunc { + return func(s *terraform.State) error { + if len := len(routeTable.Routes); len != n { + return fmt.Errorf("Route Table has incorrect number of routes (Expected=%d, Actual=%d)\n", n, len) + } + + return nil + } +} + const testAccRouteTableConfig = ` resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" @@ -994,3 +1004,21 @@ resource "aws_route_table" "test" { } `, rName, ipv6Route) } + +// testAccLatestAmazonNatInstanceAmiConfig returns the configuration for a data source that +// describes the latest Amazon NAT instance AMI. +// See https://docs.aws.amazon.com/vpc/latest/userguide/VPC_NAT_Instance.html#nat-instance-ami. +// The data source is named 'amzn-ami-nat-instance'. +func testAccLatestAmazonNatInstanceAmiConfig() string { + return fmt.Sprintf(` +data "aws_ami" "amzn-ami-nat-instance" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["amzn-ami-vpc-nat-*"] + } +} +`) +} diff --git a/aws/resource_aws_route_test.go b/aws/resource_aws_route_test.go index d6ce63c1089..f240548d74f 100644 --- a/aws/resource_aws_route_test.go +++ b/aws/resource_aws_route_test.go @@ -4,52 +4,54 @@ import ( "fmt" "testing" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/finder" ) +// IPv4 to Internet Gateway. func TestAccAWSRoute_basic(t *testing.T) { var route ec2.Route - - //aws creates a default route - testCheck := func(s *terraform.State) error { - if *route.DestinationCidrBlock != "10.3.0.0/16" { - return fmt.Errorf("Destination Cidr (Expected=%s, Actual=%s)\n", "10.3.0.0/16", *route.DestinationCidrBlock) - } - - name := "aws_internet_gateway.foo" - gwres, ok := s.RootModule().Resources[name] - if !ok { - return fmt.Errorf("Not found: %s\n", name) - } - - if *route.GatewayId != gwres.Primary.ID { - return fmt.Errorf("Internet Gateway Id (Expected=%s, Actual=%s)\n", gwres.Primary.ID, *route.GatewayId) - } - - return nil - } + var routeTable ec2.RouteTable + resourceName := "aws_route.test" + igwResourceName := "aws_internet_gateway.test" + rtResourceName := "aws_route_table.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + destinationCidr := "10.3.0.0/16" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - }, + PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRouteDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSRouteBasicConfig(), + Config: testAccAWSRouteConfigIpv4InternetGateway(rName, destinationCidr), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRouteExists("aws_route.bar", &route), - testCheck, + testAccCheckAWSRouteExists(resourceName, &route), + testAccCheckRouteTableExists(rtResourceName, &routeTable), + testAccCheckAWSRouteTableNumberOfRoutes(&routeTable, 2), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "gateway_id", igwResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "instance_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_owner_id", ""), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "network_interface_id", ""), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), ), }, { - ResourceName: "aws_route.bar", + ResourceName: resourceName, ImportState: true, - ImportStateIdFunc: testAccAWSRouteImportStateIdFunc("aws_route.bar"), + ImportStateIdFunc: testAccAWSRouteImportStateIdFunc(resourceName), ImportStateVerify: true, }, }, @@ -58,19 +60,20 @@ func TestAccAWSRoute_basic(t *testing.T) { func TestAccAWSRoute_disappears(t *testing.T) { var route ec2.Route + resourceName := "aws_route.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + destinationCidr := "10.3.0.0/16" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - }, + PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRouteDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSRouteBasicConfig(), + Config: testAccAWSRouteConfigIpv4InternetGateway(rName, destinationCidr), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRouteExists("aws_route.bar", &route), - testAccCheckResourceDisappears(testAccProvider, resourceAwsRoute(), "aws_route.bar"), + testAccCheckAWSRouteExists(resourceName, &route), + testAccCheckResourceDisappears(testAccProvider, resourceAwsRoute(), resourceName), ), ExpectNonEmptyPlan: true, }, @@ -78,112 +81,164 @@ func TestAccAWSRoute_disappears(t *testing.T) { }) } -func TestAccAWSRoute_ipv6Support(t *testing.T) { +func TestAccAWSRoute_routeTableDisappears(t *testing.T) { var route ec2.Route + resourceName := "aws_route.test" + rtResourceName := "aws_route_table.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + destinationCidr := "10.3.0.0/16" - //aws creates a default route - testCheck := func(s *terraform.State) error { - - name := "aws_egress_only_internet_gateway.foo" - gwres, ok := s.RootModule().Resources[name] - if !ok { - return fmt.Errorf("Not found: %s\n", name) - } - - if *route.EgressOnlyInternetGatewayId != gwres.Primary.ID { - return fmt.Errorf("Egress Only Internet Gateway Id (Expected=%s, Actual=%s)\n", gwres.Primary.ID, *route.EgressOnlyInternetGatewayId) - } + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRouteConfigIpv4InternetGateway(rName, destinationCidr), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRouteExists(resourceName, &route), + testAccCheckResourceDisappears(testAccProvider, resourceAwsRouteTable(), rtResourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} - return nil - } +func TestAccAWSRoute_IPv6_To_EgressOnlyInternetGateway(t *testing.T) { + var route ec2.Route + resourceName := "aws_route.test" + eoigwResourceName := "aws_egress_only_internet_gateway.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + destinationCidr := "::/0" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - }, + PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRouteDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSRouteConfigIpv6(), + Config: testAccAWSRouteConfigIpv6EgressOnlyInternetGateway(rName, destinationCidr), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRouteExists("aws_route.bar", &route), - testCheck, - resource.TestCheckResourceAttr("aws_route.bar", "destination_ipv6_cidr_block", "::/0"), + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "egress_only_gateway_id", eoigwResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_owner_id", ""), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "network_interface_id", ""), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), ), }, { - ResourceName: "aws_route.bar", + ResourceName: resourceName, ImportState: true, - ImportStateIdFunc: testAccAWSRouteImportStateIdFunc("aws_route.bar"), + ImportStateIdFunc: testAccAWSRouteImportStateIdFunc(resourceName), ImportStateVerify: true, }, { - Config: testAccAWSRouteConfigIpv6Expanded(), + // Verify that expanded form of the destination CIDR causes no diff. + Config: testAccAWSRouteConfigIpv6EgressOnlyInternetGateway(rName, "::0/0"), PlanOnly: true, }, }, }) } -func TestAccAWSRoute_ipv6ToInternetGateway(t *testing.T) { +func TestAccAWSRoute_IPv6_To_InternetGateway(t *testing.T) { var route ec2.Route + resourceName := "aws_route.test" + igwResourceName := "aws_internet_gateway.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + destinationCidr := "::/0" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - }, + PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRouteDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSRouteConfigIpv6InternetGateway(), + Config: testAccAWSRouteConfigIpv6InternetGateway(rName, destinationCidr), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRouteExists("aws_route.igw", &route), + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "gateway_id", igwResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "instance_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_owner_id", ""), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "network_interface_id", ""), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), ), }, { - ResourceName: "aws_route.igw", + ResourceName: resourceName, ImportState: true, - ImportStateIdFunc: testAccAWSRouteImportStateIdFunc("aws_route.igw"), + ImportStateIdFunc: testAccAWSRouteImportStateIdFunc(resourceName), ImportStateVerify: true, }, }, }) } -func TestAccAWSRoute_ipv6ToInstance(t *testing.T) { +func TestAccAWSRoute_IPv6_To_Instance(t *testing.T) { var route ec2.Route + resourceName := "aws_route.test" + instanceResourceName := "aws_instance.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + destinationCidr := "::/0" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - }, + PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRouteDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSRouteConfigIpv6Instance(), + Config: testAccAWSRouteConfigIpv6Instance(rName, destinationCidr), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRouteExists("aws_route.internal-default-route-ipv6", &route), + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "instance_id", instanceResourceName, "id"), + testAccCheckResourceAttrAccountID(resourceName, "instance_owner_id"), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "network_interface_id", instanceResourceName, "primary_network_interface_id"), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), ), }, { - ResourceName: "aws_route.internal-default-route-ipv6", + ResourceName: resourceName, ImportState: true, - ImportStateIdFunc: testAccAWSRouteImportStateIdFunc("aws_route.internal-default-route-ipv6"), + ImportStateIdFunc: testAccAWSRouteImportStateIdFunc(resourceName), ImportStateVerify: true, }, - { - Config: testAccAWSRouteConfigIpv6InstanceExpanded(), - PlanOnly: true, - }, }, }) } -func TestAccAWSRoute_ipv6ToNetworkInterface(t *testing.T) { +func TestAccAWSRoute_IPv6_To_NetworkInterface_Unattached(t *testing.T) { var route ec2.Route + resourceName := "aws_route.test" + eniResourceName := "aws_network_interface.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + destinationCidr := "::/0" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { @@ -193,124 +248,204 @@ func TestAccAWSRoute_ipv6ToNetworkInterface(t *testing.T) { CheckDestroy: testAccCheckAWSRouteDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSRouteConfigIpv6NetworkInterface(), + Config: testAccAWSRouteConfigIpv6NetworkInterfaceUnattached(rName, destinationCidr), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRouteExists("aws_route.internal-default-route-ipv6", &route), + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_owner_id", ""), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "network_interface_id", eniResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateBlackhole), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), ), }, { - ResourceName: "aws_route.internal-default-route-ipv6", + ResourceName: resourceName, ImportState: true, - ImportStateIdFunc: testAccAWSRouteImportStateIdFunc("aws_route.internal-default-route-ipv6"), + ImportStateIdFunc: testAccAWSRouteImportStateIdFunc(resourceName), ImportStateVerify: true, }, }, }) } -func TestAccAWSRoute_ipv6ToPeeringConnection(t *testing.T) { +func TestAccAWSRoute_IPv6_To_VpcPeeringConnection(t *testing.T) { var route ec2.Route + resourceName := "aws_route.test" + pcxResourceName := "aws_vpc_peering_connection.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + destinationCidr := "::/0" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - }, + PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRouteDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSRouteConfigIpv6PeeringConnection(), + Config: testAccAWSRouteConfigIpv6VpcPeeringConnection(rName, destinationCidr), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRouteExists("aws_route.pc", &route), + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_owner_id", ""), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "network_interface_id", ""), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "vpc_peering_connection_id", pcxResourceName, "id"), ), }, { - ResourceName: "aws_route.pc", + ResourceName: resourceName, ImportState: true, - ImportStateIdFunc: testAccAWSRouteImportStateIdFunc("aws_route.pc"), + ImportStateIdFunc: testAccAWSRouteImportStateIdFunc(resourceName), ImportStateVerify: true, }, }, }) } -func TestAccAWSRoute_changeRouteTable(t *testing.T) { - var before ec2.Route - var after ec2.Route +func TestAccAWSRoute_IPv6_To_VpnGateway(t *testing.T) { + var route ec2.Route + resourceName := "aws_route.test" + vgwResourceName := "aws_vpn_gateway.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + destinationCidr := "::/0" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - }, + PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRouteDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSRouteBasicConfig(), + Config: testAccAWSRouteConfigIpv6VpnGateway(rName, destinationCidr), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRouteExists("aws_route.bar", &before), + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "gateway_id", vgwResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "instance_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_owner_id", ""), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "network_interface_id", ""), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), ), }, { - Config: testAccAWSRouteNewRouteTable(), + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccAWSRouteImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSRoute_IPv4_To_VpnGateway(t *testing.T) { + var route ec2.Route + resourceName := "aws_route.test" + vgwResourceName := "aws_vpn_gateway.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + destinationCidr := "10.3.0.0/16" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRouteConfigIpv4VpnGateway(rName, destinationCidr), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRouteExists("aws_route.bar", &after), + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "gateway_id", vgwResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "instance_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_owner_id", ""), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "network_interface_id", ""), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), ), }, { - ResourceName: "aws_route.bar", + ResourceName: resourceName, ImportState: true, - ImportStateIdFunc: testAccAWSRouteImportStateIdFunc("aws_route.bar"), + ImportStateIdFunc: testAccAWSRouteImportStateIdFunc(resourceName), ImportStateVerify: true, }, }, }) } -func TestAccAWSRoute_changeCidr(t *testing.T) { +func TestAccAWSRoute_IPv4_To_Instance(t *testing.T) { var route ec2.Route - var routeTable ec2.RouteTable - - //aws creates a default route - testCheck := func(s *terraform.State) error { - if *route.DestinationCidrBlock != "10.3.0.0/16" { - return fmt.Errorf("Destination Cidr (Expected=%s, Actual=%s)\n", "10.3.0.0/16", *route.DestinationCidrBlock) - } - - name := "aws_internet_gateway.foo" - gwres, ok := s.RootModule().Resources[name] - if !ok { - return fmt.Errorf("Not found: %s\n", name) - } - - if *route.GatewayId != gwres.Primary.ID { - return fmt.Errorf("Internet Gateway Id (Expected=%s, Actual=%s)\n", gwres.Primary.ID, *route.GatewayId) - } - - return nil - } - - testCheckChange := func(s *terraform.State) error { - if *route.DestinationCidrBlock != "10.2.0.0/16" { - return fmt.Errorf("Destination Cidr (Expected=%s, Actual=%s)\n", "10.2.0.0/16", *route.DestinationCidrBlock) - } - - name := "aws_internet_gateway.foo" - gwres, ok := s.RootModule().Resources[name] - if !ok { - return fmt.Errorf("Not found: %s\n", name) - } - - if *route.GatewayId != gwres.Primary.ID { - return fmt.Errorf("Internet Gateway Id (Expected=%s, Actual=%s)\n", gwres.Primary.ID, *route.GatewayId) - } + resourceName := "aws_route.test" + instanceResourceName := "aws_instance.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + destinationCidr := "10.3.0.0/16" - if rtlen := len(routeTable.Routes); rtlen != 2 { - return fmt.Errorf("Route Table has too many routes (Expected=%d, Actual=%d)\n", rtlen, 2) - } + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRouteConfigIpv4Instance(rName, destinationCidr), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "instance_id", instanceResourceName, "id"), + testAccCheckResourceAttrAccountID(resourceName, "instance_owner_id"), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "network_interface_id", instanceResourceName, "primary_network_interface_id"), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccAWSRouteImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + }, + }) +} - return nil - } +func TestAccAWSRoute_IPv4_To_NetworkInterface_Unattached(t *testing.T) { + var route ec2.Route + resourceName := "aws_route.test" + eniResourceName := "aws_network_interface.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + destinationCidr := "10.3.0.0/16" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { @@ -320,41 +455,86 @@ func TestAccAWSRoute_changeCidr(t *testing.T) { CheckDestroy: testAccCheckAWSRouteDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSRouteBasicConfig(), + Config: testAccAWSRouteConfigIpv4NetworkInterfaceUnattached(rName, destinationCidr), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRouteExists("aws_route.bar", &route), - testCheck, + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_owner_id", ""), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "network_interface_id", eniResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateBlackhole), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), ), }, { - Config: testAccAWSRouteBasicConfigChangeCidr(), + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccAWSRouteImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSRoute_IPv4_To_NetworkInterface_Attached(t *testing.T) { + var route ec2.Route + resourceName := "aws_route.test" + eniResourceName := "aws_network_interface.test" + instanceResourceName := "aws_instance.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + destinationCidr := "10.3.0.0/16" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRouteConfigIpv4NetworkInterfaceAttached(rName, destinationCidr), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRouteExists("aws_route.bar", &route), - testAccCheckRouteTableExists("aws_route_table.foo", &routeTable), - testCheckChange, + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "instance_id", instanceResourceName, "id"), + testAccCheckResourceAttrAccountID(resourceName, "instance_owner_id"), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "network_interface_id", eniResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), ), }, { - ResourceName: "aws_route.bar", + ResourceName: resourceName, ImportState: true, - ImportStateIdFunc: testAccAWSRouteImportStateIdFunc("aws_route.bar"), + ImportStateIdFunc: testAccAWSRouteImportStateIdFunc(resourceName), ImportStateVerify: true, }, }, }) } -func TestAccAWSRoute_noopdiff(t *testing.T) { +func TestAccAWSRoute_IPv4_To_NetworkInterface_TwoAttachments(t *testing.T) { var route ec2.Route - var routeTable ec2.RouteTable - - testCheck := func(s *terraform.State) error { - return nil - } - - testCheckChange := func(s *terraform.State) error { - return nil - } + resourceName := "aws_route.test" + eni1ResourceName := "aws_network_interface.test1" + eni2ResourceName := "aws_network_interface.test2" + instanceResourceName := "aws_instance.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + destinationCidr := "10.3.0.0/16" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { @@ -364,32 +544,59 @@ func TestAccAWSRoute_noopdiff(t *testing.T) { CheckDestroy: testAccCheckAWSRouteDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSRouteNoopChange(), + Config: testAccAWSRouteConfigIpv4NetworkInterfaceTwoAttachments(rName, destinationCidr, eni1ResourceName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRouteExists("aws_route.test", &route), - testCheck, + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "instance_id", instanceResourceName, "id"), + testAccCheckResourceAttrAccountID(resourceName, "instance_owner_id"), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "network_interface_id", eni1ResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), ), }, { - Config: testAccAWSRouteNoopChange(), + Config: testAccAWSRouteConfigIpv4NetworkInterfaceTwoAttachments(rName, destinationCidr, eni2ResourceName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRouteExists("aws_route.test", &route), - testAccCheckRouteTableExists("aws_route_table.test", &routeTable), - testCheckChange, + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "instance_id", instanceResourceName, "id"), + testAccCheckResourceAttrAccountID(resourceName, "instance_owner_id"), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "network_interface_id", eni2ResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), ), }, { - ResourceName: "aws_route.test", + ResourceName: resourceName, ImportState: true, - ImportStateIdFunc: testAccAWSRouteImportStateIdFunc("aws_route.test"), + ImportStateIdFunc: testAccAWSRouteImportStateIdFunc(resourceName), ImportStateVerify: true, }, }, }) } -func TestAccAWSRoute_doesNotCrashWithVPCEndpoint(t *testing.T) { +func TestAccAWSRoute_IPv4_To_VpcPeeringConnection(t *testing.T) { var route ec2.Route + resourceName := "aws_route.test" + pcxResourceName := "aws_vpc_peering_connection.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + destinationCidr := "10.3.0.0/16" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -397,25 +604,40 @@ func TestAccAWSRoute_doesNotCrashWithVPCEndpoint(t *testing.T) { CheckDestroy: testAccCheckAWSRouteDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSRouteWithVPCEndpoint(), + Config: testAccAWSRouteConfigIpv4VpcPeeringConnection(rName, destinationCidr), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRouteExists("aws_route.bar", &route), + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_owner_id", ""), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "network_interface_id", ""), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "vpc_peering_connection_id", pcxResourceName, "id"), ), }, { - ResourceName: "aws_route.bar", + ResourceName: resourceName, ImportState: true, - ImportStateIdFunc: testAccAWSRouteImportStateIdFunc("aws_route.bar"), + ImportStateIdFunc: testAccAWSRouteImportStateIdFunc(resourceName), ImportStateVerify: true, }, }, }) } -func TestAccAWSRoute_TransitGatewayID_DestinationCidrBlock(t *testing.T) { +func TestAccAWSRoute_IPv4_To_NatGateway(t *testing.T) { var route ec2.Route resourceName := "aws_route.test" - transitGatewayResourceName := "aws_ec2_transit_gateway.test" + ngwResourceName := "aws_nat_gateway.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + destinationCidr := "10.3.0.0/16" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -423,10 +645,22 @@ func TestAccAWSRoute_TransitGatewayID_DestinationCidrBlock(t *testing.T) { CheckDestroy: testAccCheckAWSRouteDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSRouteConfigTransitGatewayIDDestinatationCidrBlock(), + Config: testAccAWSRouteConfigIpv4NatGateway(rName, destinationCidr), Check: resource.ComposeTestCheckFunc( testAccCheckAWSRouteExists(resourceName, &route), - resource.TestCheckResourceAttrPair(resourceName, "transit_gateway_id", transitGatewayResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_owner_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "nat_gateway_id", ngwResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "network_interface_id", ""), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), ), }, { @@ -439,9 +673,11 @@ func TestAccAWSRoute_TransitGatewayID_DestinationCidrBlock(t *testing.T) { }) } -func TestAccAWSRoute_ConditionalCidrBlock(t *testing.T) { +func TestAccAWSRoute_DoesNotCrashWithVpcEndpoint(t *testing.T) { var route ec2.Route + var routeTable ec2.RouteTable resourceName := "aws_route.test" + rtResourceName := "aws_route_table.test" rName := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ @@ -450,19 +686,52 @@ func TestAccAWSRoute_ConditionalCidrBlock(t *testing.T) { CheckDestroy: testAccCheckAWSRouteDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSRouteConfigConditionalIpv4Ipv6(rName, false), + Config: testAccAWSRouteConfigWithVpcEndpoint(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSRouteExists(resourceName, &route), - resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", "0.0.0.0/0"), - resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", ""), + testAccCheckRouteTableExists(rtResourceName, &routeTable), + testAccCheckAWSRouteTableNumberOfRoutes(&routeTable, 3), ), }, { - Config: testAccAWSRouteConfigConditionalIpv4Ipv6(rName, true), + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccAWSRouteImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSRoute_IPv4_To_TransitGateway(t *testing.T) { + var route ec2.Route + resourceName := "aws_route.test" + tgwResourceName := "aws_ec2_transit_gateway.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + destinationCidr := "10.3.0.0/16" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRouteConfigIpv4TransitGateway(rName, destinationCidr), Check: resource.ComposeTestCheckFunc( testAccCheckAWSRouteExists(resourceName, &route), - resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", ""), - resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", "::/0"), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_owner_id", ""), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "network_interface_id", ""), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttrPair(resourceName, "transit_gateway_id", tgwResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), ), }, { @@ -475,9 +744,388 @@ func TestAccAWSRoute_ConditionalCidrBlock(t *testing.T) { }) } -func testAccCheckAWSRouteExists(n string, res *ec2.Route) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] +func TestAccAWSRoute_ConditionalCidrBlock(t *testing.T) { + var route ec2.Route + resourceName := "aws_route.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + destinationCidr := "10.2.0.0/16" + destinationIpv6Cidr := "::/0" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRouteConfigConditionalIpv4Ipv6(rName, destinationCidr, destinationIpv6Cidr, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", ""), + ), + }, + { + Config: testAccAWSRouteConfigConditionalIpv4Ipv6(rName, destinationCidr, destinationIpv6Cidr, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", destinationIpv6Cidr), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccAWSRouteImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSRoute_IPv4_Update_Target(t *testing.T) { + var route ec2.Route + resourceName := "aws_route.test" + vgwResourceName := "aws_vpn_gateway.test" + instanceResourceName := "aws_instance.test" + igwResourceName := "aws_internet_gateway.test" + eniResourceName := "aws_network_interface.test" + pcxResourceName := "aws_vpc_peering_connection.test" + ngwResourceName := "aws_nat_gateway.test" + tgwResourceName := "aws_ec2_transit_gateway.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + destinationCidr := "10.3.0.0/16" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRouteConfigIpv4FlexiTarget(rName, destinationCidr, "instance_id", instanceResourceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "instance_id", instanceResourceName, "id"), + testAccCheckResourceAttrAccountID(resourceName, "instance_owner_id"), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "network_interface_id", instanceResourceName, "primary_network_interface_id"), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), + ), + }, + { + Config: testAccAWSRouteConfigIpv4FlexiTarget(rName, destinationCidr, "gateway_id", vgwResourceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "gateway_id", vgwResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "instance_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_owner_id", ""), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "network_interface_id", ""), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), + ), + }, + { + Config: testAccAWSRouteConfigIpv4FlexiTarget(rName, destinationCidr, "gateway_id", igwResourceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "gateway_id", igwResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "instance_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_owner_id", ""), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "network_interface_id", ""), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), + ), + }, + { + Config: testAccAWSRouteConfigIpv4FlexiTarget(rName, destinationCidr, "nat_gateway_id", ngwResourceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_owner_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "nat_gateway_id", ngwResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "network_interface_id", ""), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), + ), + }, + { + Config: testAccAWSRouteConfigIpv4FlexiTarget(rName, destinationCidr, "network_interface_id", eniResourceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "instance_id", instanceResourceName, "id"), + testAccCheckResourceAttrAccountID(resourceName, "instance_owner_id"), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "network_interface_id", eniResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), + ), + }, + { + Config: testAccAWSRouteConfigIpv4FlexiTarget(rName, destinationCidr, "transit_gateway_id", tgwResourceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_owner_id", ""), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "network_interface_id", ""), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttrPair(resourceName, "transit_gateway_id", tgwResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), + ), + }, + { + Config: testAccAWSRouteConfigIpv4FlexiTarget(rName, destinationCidr, "vpc_peering_connection_id", pcxResourceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_owner_id", ""), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "network_interface_id", ""), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "vpc_peering_connection_id", pcxResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccAWSRouteImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSRoute_IPv6_Update_Target(t *testing.T) { + var route ec2.Route + resourceName := "aws_route.test" + vgwResourceName := "aws_vpn_gateway.test" + instanceResourceName := "aws_instance.test" + igwResourceName := "aws_internet_gateway.test" + eniResourceName := "aws_network_interface.test" + pcxResourceName := "aws_vpc_peering_connection.test" + eoigwResourceName := "aws_egress_only_internet_gateway.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + destinationCidr := "::/0" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRouteConfigIpv6FlexiTarget(rName, destinationCidr, "instance_id", instanceResourceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "instance_id", instanceResourceName, "id"), + testAccCheckResourceAttrAccountID(resourceName, "instance_owner_id"), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "network_interface_id", instanceResourceName, "primary_network_interface_id"), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), + ), + }, + { + Config: testAccAWSRouteConfigIpv6FlexiTarget(rName, destinationCidr, "gateway_id", vgwResourceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "gateway_id", vgwResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "instance_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_owner_id", ""), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "network_interface_id", ""), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), + ), + }, + { + Config: testAccAWSRouteConfigIpv6FlexiTarget(rName, destinationCidr, "gateway_id", igwResourceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "gateway_id", igwResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "instance_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_owner_id", ""), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "network_interface_id", ""), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), + ), + }, + { + Config: testAccAWSRouteConfigIpv6FlexiTarget(rName, destinationCidr, "egress_only_gateway_id", eoigwResourceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "egress_only_gateway_id", eoigwResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_owner_id", ""), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "network_interface_id", ""), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), + ), + }, + { + Config: testAccAWSRouteConfigIpv6FlexiTarget(rName, destinationCidr, "network_interface_id", eniResourceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_owner_id", ""), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "network_interface_id", eniResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateBlackhole), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_peering_connection_id", ""), + ), + }, + { + Config: testAccAWSRouteConfigIpv6FlexiTarget(rName, destinationCidr, "vpc_peering_connection_id", pcxResourceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRouteExists(resourceName, &route), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", ""), + resource.TestCheckResourceAttr(resourceName, "destination_ipv6_cidr_block", destinationCidr), + resource.TestCheckResourceAttr(resourceName, "destination_prefix_list_id", ""), + resource.TestCheckResourceAttr(resourceName, "egress_only_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_id", ""), + resource.TestCheckResourceAttr(resourceName, "instance_owner_id", ""), + resource.TestCheckResourceAttr(resourceName, "nat_gateway_id", ""), + resource.TestCheckResourceAttr(resourceName, "network_interface_id", ""), + resource.TestCheckResourceAttr(resourceName, "origin", ec2.RouteOriginCreateRoute), + resource.TestCheckResourceAttr(resourceName, "state", ec2.RouteStateActive), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_id", ""), + resource.TestCheckResourceAttrPair(resourceName, "vpc_peering_connection_id", pcxResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccAWSRouteImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + }, + }) +} + +// https://github.com/terraform-providers/terraform-provider-aws/issues/11455. +func TestAccAWSRoute_LocalRoute(t *testing.T) { + var routeTable ec2.RouteTable + var vpc ec2.Vpc + resourceName := "aws_route.test" + rtResourceName := "aws_route_table.test" + vpcResourceName := "aws_vpc.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRouteConfigIpv4NoRoute(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckVpcExists(vpcResourceName, &vpc), + testAccCheckRouteTableExists(rtResourceName, &routeTable), + testAccCheckAWSRouteTableNumberOfRoutes(&routeTable, 1), + ), + }, + { + Config: testAccAWSRouteConfigIpv4LocalRoute(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), + // 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, + }, + }, + }) +} + +func testAccCheckAWSRouteExists(n string, res *ec2.Route) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s\n", n) } @@ -487,22 +1135,24 @@ func testAccCheckAWSRouteExists(n string, res *ec2.Route) resource.TestCheckFunc } conn := testAccProvider.Meta().(*AWSClient).ec2conn - r, err := resourceAwsRouteFindRoute( - conn, - rs.Primary.Attributes["route_table_id"], - rs.Primary.Attributes["destination_cidr_block"], - rs.Primary.Attributes["destination_ipv6_cidr_block"], - ) + + var route *ec2.Route + var err error + if v := rs.Primary.Attributes["destination_cidr_block"]; v != "" { + route, err = finder.RouteByIpv4Destination(conn, rs.Primary.Attributes["route_table_id"], v) + } else if v := rs.Primary.Attributes["destination_ipv6_cidr_block"]; v != "" { + route, err = finder.RouteByIpv6Destination(conn, rs.Primary.Attributes["route_table_id"], v) + } if err != nil { return err } - if r == nil { + if route == nil { return fmt.Errorf("Route not found") } - *res = *r + *res = *route return nil } @@ -515,12 +1165,14 @@ func testAccCheckAWSRouteDestroy(s *terraform.State) error { } conn := testAccProvider.Meta().(*AWSClient).ec2conn - route, err := resourceAwsRouteFindRoute( - conn, - rs.Primary.Attributes["route_table_id"], - rs.Primary.Attributes["destination_cidr_block"], - rs.Primary.Attributes["destination_ipv6_cidr_block"], - ) + + var route *ec2.Route + var err error + if v := rs.Primary.Attributes["destination_cidr_block"]; v != "" { + route, err = finder.RouteByIpv4Destination(conn, rs.Primary.Attributes["route_table_id"], v) + } else if v := rs.Primary.Attributes["destination_ipv6_cidr_block"]; v != "" { + route, err = finder.RouteByIpv6Destination(conn, rs.Primary.Attributes["route_table_id"], v) + } if route == nil && err == nil { return nil @@ -546,685 +1198,1032 @@ func testAccAWSRouteImportStateIdFunc(resourceName string) resource.ImportStateI } } -func testAccAWSRouteBasicConfig() string { +func testAccAWSRouteConfigIpv4InternetGateway(rName, destinationCidr string) string { return fmt.Sprintf(` -resource "aws_vpc" "foo" { +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" - + tags = { - Name = "terraform-testacc-route-basic" + Name = %[1]q } } -resource "aws_internet_gateway" "foo" { - vpc_id = aws_vpc.foo.id +resource "aws_internet_gateway" "test" { + vpc_id = aws_vpc.test.id tags = { - Name = "terraform-testacc-route-basic" + Name = %[1]q } } -resource "aws_route_table" "foo" { - vpc_id = aws_vpc.foo.id +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } } -resource "aws_route" "bar" { - route_table_id = aws_route_table.foo.id - destination_cidr_block = "10.3.0.0/16" - gateway_id = aws_internet_gateway.foo.id +resource "aws_route" "test" { + route_table_id = aws_route_table.test.id + destination_cidr_block = %[2]q + gateway_id = aws_internet_gateway.test.id } -`) +`, rName, destinationCidr) } -func testAccAWSRouteConfigIpv6InternetGateway() string { +func testAccAWSRouteConfigIpv6InternetGateway(rName, destinationCidr string) string { return fmt.Sprintf(` -resource "aws_vpc" "foo" { +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" assign_generated_ipv6_cidr_block = true tags = { - Name = "terraform-testacc-route-ipv6-igw" + Name = %[1]q } } -resource "aws_egress_only_internet_gateway" "foo" { - vpc_id = aws_vpc.foo.id -} - -resource "aws_internet_gateway" "foo" { - vpc_id = aws_vpc.foo.id +resource "aws_egress_only_internet_gateway" "test" { + vpc_id = aws_vpc.test.id tags = { - Name = "terraform-testacc-route-ipv6-igw" + Name = %[1]q } } -resource "aws_route_table" "external" { - vpc_id = aws_vpc.foo.id -} - -resource "aws_route" "igw" { - route_table_id = aws_route_table.external.id - destination_ipv6_cidr_block = "::/0" - gateway_id = aws_internet_gateway.foo.id -} -`) -} +resource "aws_internet_gateway" "test" { + vpc_id = aws_vpc.test.id -func testAccAWSRouteConfigIpv6NetworkInterface() string { - return testAccAvailableEc2InstanceTypeForAvailabilityZone("aws_subnet.router-network.availability_zone", "t2.small", "t3.small") + - testAccLatestAmazonLinuxHvmEbsAmiConfig() + - fmt.Sprintf(` -resource "aws_vpc" "examplevpc" { - cidr_block = "10.100.0.0/16" - enable_dns_hostnames = true - assign_generated_ipv6_cidr_block = true - tags = { - Name = "terraform-testacc-route-ipv6-network-interface" - } -} - -data "aws_availability_zones" "available" { - state = "available" - - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] + Name = %[1]q } } -resource "aws_internet_gateway" "internet" { - vpc_id = aws_vpc.examplevpc.id +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id tags = { - Name = "terraform-testacc-route-ipv6-network-interface" + Name = %[1]q } } -resource "aws_route" "igw" { - route_table_id = aws_vpc.examplevpc.main_route_table_id - destination_cidr_block = "0.0.0.0/0" - gateway_id = aws_internet_gateway.internet.id +resource "aws_route" "test" { + route_table_id = aws_route_table.test.id + destination_ipv6_cidr_block = %[2]q + gateway_id = aws_internet_gateway.test.id } - -resource "aws_route" "igw-ipv6" { - route_table_id = aws_vpc.examplevpc.main_route_table_id - destination_ipv6_cidr_block = "::/0" - gateway_id = aws_internet_gateway.internet.id +`, rName, destinationCidr) } -resource "aws_subnet" "router-network" { - cidr_block = "10.100.1.0/24" - vpc_id = aws_vpc.examplevpc.id - ipv6_cidr_block = cidrsubnet(aws_vpc.examplevpc.ipv6_cidr_block, 8, 1) - assign_ipv6_address_on_creation = true - map_public_ip_on_launch = true - availability_zone = data.aws_availability_zones.available.names[0] - - tags = { - Name = "tf-acc-route-ipv6-network-interface-router" - } -} +func testAccAWSRouteConfigIpv6NetworkInterfaceUnattached(rName, destinationCidr string) string { + return composeConfig( + testAccAvailableAZsNoOptInDefaultExcludeConfig(), + fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + assign_generated_ipv6_cidr_block = true -resource "aws_subnet" "client-network" { - cidr_block = "10.100.10.0/24" - vpc_id = aws_vpc.examplevpc.id - ipv6_cidr_block = cidrsubnet(aws_vpc.examplevpc.ipv6_cidr_block, 8, 2) - assign_ipv6_address_on_creation = true - map_public_ip_on_launch = false - availability_zone = data.aws_availability_zones.available.names[0] - tags = { - Name = "tf-acc-route-ipv6-network-interface-client" + Name = %[1]q } } -resource "aws_route_table" "client-routes" { - vpc_id = aws_vpc.examplevpc.id -} +resource "aws_subnet" "test" { + cidr_block = "10.1.1.0/24" + vpc_id = aws_vpc.test.id + availability_zone = data.aws_availability_zones.available.names[0] + ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 1) -resource "aws_route_table_association" "client-routes" { - route_table_id = aws_route_table.client-routes.id - subnet_id = aws_subnet.client-network.id + tags = { + Name = %[1]q + } } -resource "aws_instance" "test-router" { - ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id - instance_type = data.aws_ec2_instance_type_offering.available.instance_type - subnet_id = aws_subnet.router-network.id -} +resource "aws_network_interface" "test" { + subnet_id = aws_subnet.test.id -resource "aws_network_interface" "router-internal" { - subnet_id = aws_subnet.client-network.id - source_dest_check = false + tags = { + Name = %[1]q + } } -resource "aws_network_interface_attachment" "router-internal" { - device_index = 1 - instance_id = aws_instance.test-router.id - network_interface_id = aws_network_interface.router-internal.id -} +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id -resource "aws_route" "internal-default-route" { - route_table_id = aws_route_table.client-routes.id - destination_cidr_block = "0.0.0.0/0" - network_interface_id = aws_network_interface.router-internal.id + tags = { + Name = %[1]q + } } -resource "aws_route" "internal-default-route-ipv6" { - route_table_id = aws_route_table.client-routes.id - destination_ipv6_cidr_block = "::/0" - network_interface_id = aws_network_interface.router-internal.id +resource "aws_route" "test" { + route_table_id = aws_route_table.test.id + destination_ipv6_cidr_block = %[2]q + network_interface_id = aws_network_interface.test.id } -`) +`, rName, destinationCidr)) } -func testAccAWSRouteConfigIpv6Instance() string { - return testAccAvailableEc2InstanceTypeForAvailabilityZone("aws_subnet.router-network.availability_zone", "t2.small", "t3.small") + - testAccLatestAmazonLinuxHvmEbsAmiConfig() + +func testAccAWSRouteConfigIpv6Instance(rName, destinationCidr string) string { + return composeConfig( + testAccLatestAmazonNatInstanceAmiConfig(), + testAccAvailableAZsNoOptInDefaultExcludeConfig(), + testAccAvailableEc2InstanceTypeForRegion("t3.micro", "t2.micro"), fmt.Sprintf(` -resource "aws_vpc" "examplevpc" { - cidr_block = "10.100.0.0/16" - enable_dns_hostnames = true +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" assign_generated_ipv6_cidr_block = true tags = { - Name = "terraform-testacc-route-ipv6-instance" + Name = %[1]q } } -data "aws_availability_zones" "available" { - state = "available" +resource "aws_subnet" "test" { + cidr_block = "10.1.1.0/24" + vpc_id = aws_vpc.test.id + availability_zone = data.aws_availability_zones.available.names[0] + ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 1) - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] + tags = { + Name = %[1]q } } -resource "aws_internet_gateway" "internet" { - vpc_id = aws_vpc.examplevpc.id +resource "aws_instance" "test" { + ami = data.aws_ami.amzn-ami-nat-instance.id + instance_type = data.aws_ec2_instance_type_offering.available.instance_type + subnet_id = aws_subnet.test.id + + ipv6_address_count = 1 tags = { - Name = "terraform-testacc-route-ipv6-instance" + Name = %[1]q } } -resource "aws_route" "igw" { - route_table_id = aws_vpc.examplevpc.main_route_table_id - destination_cidr_block = "0.0.0.0/0" - gateway_id = aws_internet_gateway.internet.id -} +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_route" "test" { + route_table_id = aws_route_table.test.id + destination_ipv6_cidr_block = %[2]q + instance_id = aws_instance.test.id +} +`, rName, destinationCidr)) +} + +func testAccAWSRouteConfigIpv6VpcPeeringConnection(rName, destinationCidr 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_vpc" "target" { + cidr_block = "10.0.0.0/16" + assign_generated_ipv6_cidr_block = true + + tags = { + Name = %[1]q + } +} + +resource "aws_vpc_peering_connection" "test" { + vpc_id = aws_vpc.test.id + peer_vpc_id = aws_vpc.target.id + auto_accept = true + + tags = { + Name = %[1]q + } +} + +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_route" "test" { + route_table_id = aws_route_table.test.id + destination_ipv6_cidr_block = %[2]q + vpc_peering_connection_id = aws_vpc_peering_connection.test.id +} +`, rName, destinationCidr) +} + +func testAccAWSRouteConfigIpv6EgressOnlyInternetGateway(rName, destinationCidr 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_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 + + tags = { + Name = %[1]q + } +} + +resource "aws_route" "test" { + route_table_id = aws_route_table.test.id + destination_ipv6_cidr_block = %[2]q + egress_only_gateway_id = aws_egress_only_internet_gateway.test.id +} +`, rName, destinationCidr) +} + +func testAccAWSRouteConfigWithVpcEndpoint(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_route_table" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_route" "test" { + route_table_id = aws_route_table.test.id + destination_cidr_block = "10.3.0.0/16" + gateway_id = aws_internet_gateway.test.id + + # Forcing endpoint to create before route - without this the crash is a race. + depends_on = [aws_vpc_endpoint.test] +} + +data "aws_region" "current" {} + +resource "aws_vpc_endpoint" "test" { + vpc_id = aws_vpc.test.id + service_name = "com.amazonaws.${data.aws_region.current.name}.s3" + route_table_ids = [aws_route_table.test.id] +} +`, rName) +} + +func testAccAWSRouteConfigIpv4TransitGateway(rName, destinationCidr string) string { + return composeConfig( + testAccAvailableAZsNoOptInDefaultExcludeConfig(), + fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + availability_zone = data.aws_availability_zones.available.names[0] + cidr_block = "10.1.1.0/24" + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_vpc_attachment" "test" { + subnet_ids = [aws_subnet.test.id] + transit_gateway_id = aws_ec2_transit_gateway.test.id + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_route" "test" { + destination_cidr_block = %[2]q + route_table_id = aws_route_table.test.id + transit_gateway_id = aws_ec2_transit_gateway_vpc_attachment.test.transit_gateway_id +} +`, rName, destinationCidr)) +} + +func testAccAWSRouteConfigConditionalIpv4Ipv6(rName, destinationCidr, destinationIpv6Cidr string, ipv6Route bool) 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_internet_gateway" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +locals { + ipv6 = %[4]t + destination = %[2]q + destination_ipv6 = %[3]q +} + +resource "aws_route" "test" { + route_table_id = aws_route_table.test.id + gateway_id = aws_internet_gateway.test.id + + destination_cidr_block = local.ipv6 ? "" : local.destination + destination_ipv6_cidr_block = local.ipv6 ? local.destination_ipv6 : "" +} +`, rName, destinationCidr, destinationIpv6Cidr, ipv6Route) +} + +func testAccAWSRouteConfigIpv4Instance(rName, destinationCidr string) string { + return composeConfig( + testAccLatestAmazonNatInstanceAmiConfig(), + testAccAvailableAZsNoOptInDefaultExcludeConfig(), + testAccAvailableEc2InstanceTypeForRegion("t3.micro", "t2.micro"), + fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + cidr_block = "10.1.1.0/24" + vpc_id = aws_vpc.test.id + availability_zone = data.aws_availability_zones.available.names[0] + + tags = { + Name = %[1]q + } +} + +resource "aws_instance" "test" { + ami = data.aws_ami.amzn-ami-nat-instance.id + instance_type = data.aws_ec2_instance_type_offering.available.instance_type + subnet_id = aws_subnet.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_route" "test" { + route_table_id = aws_route_table.test.id + destination_cidr_block = %[2]q + instance_id = aws_instance.test.id +} +`, rName, destinationCidr)) +} + +func testAccAWSRouteConfigIpv4NetworkInterfaceUnattached(rName, destinationCidr string) string { + return composeConfig( + testAccAvailableAZsNoOptInDefaultExcludeConfig(), + fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + cidr_block = "10.1.1.0/24" + vpc_id = aws_vpc.test.id + availability_zone = data.aws_availability_zones.available.names[0] + + tags = { + Name = %[1]q + } +} + +resource "aws_network_interface" "test" { + subnet_id = aws_subnet.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_route" "test" { + route_table_id = aws_route_table.test.id + destination_cidr_block = %[2]q + network_interface_id = aws_network_interface.test.id +} +`, rName, destinationCidr)) +} + +func testAccAWSRouteConfigIpv4NetworkInterfaceAttached(rName, destinationCidr string) string { + return composeConfig( + testAccLatestAmazonNatInstanceAmiConfig(), + testAccAvailableAZsNoOptInDefaultExcludeConfig(), + testAccAvailableEc2InstanceTypeForRegion("t3.micro", "t2.micro"), + fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + cidr_block = "10.1.1.0/24" + vpc_id = aws_vpc.test.id + availability_zone = data.aws_availability_zones.available.names[0] + + tags = { + Name = %[1]q + } +} + +resource "aws_network_interface" "test" { + subnet_id = aws_subnet.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_instance" "test" { + ami = data.aws_ami.amzn-ami-nat-instance.id + instance_type = data.aws_ec2_instance_type_offering.available.instance_type + + network_interface { + device_index = 0 + network_interface_id = aws_network_interface.test.id + } + + tags = { + Name = %[1]q + } +} + +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_route" "test" { + route_table_id = aws_route_table.test.id + destination_cidr_block = %[2]q + network_interface_id = aws_network_interface.test.id + + # Wait for the ENI attachment. + depends_on = [aws_instance.test] +} +`, rName, destinationCidr)) +} + +func testAccAWSRouteConfigIpv4NetworkInterfaceTwoAttachments(rName, destinationCidr, targetResourceName string) string { + return composeConfig( + testAccLatestAmazonNatInstanceAmiConfig(), + testAccAvailableAZsNoOptInDefaultExcludeConfig(), + testAccAvailableEc2InstanceTypeForRegion("t3.micro", "t2.micro"), + fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + cidr_block = "10.1.1.0/24" + vpc_id = aws_vpc.test.id + availability_zone = data.aws_availability_zones.available.names[0] + + tags = { + Name = %[1]q + } +} + +resource "aws_network_interface" "test1" { + subnet_id = aws_subnet.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_network_interface" "test2" { + subnet_id = aws_subnet.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_instance" "test" { + ami = data.aws_ami.amzn-ami-nat-instance.id + instance_type = data.aws_ec2_instance_type_offering.available.instance_type + + network_interface { + device_index = 0 + network_interface_id = aws_network_interface.test1.id + } -resource "aws_route" "igw-ipv6" { - route_table_id = aws_vpc.examplevpc.main_route_table_id - destination_ipv6_cidr_block = "::/0" - gateway_id = aws_internet_gateway.internet.id -} + network_interface { + device_index = 1 + network_interface_id = aws_network_interface.test2.id + } -resource "aws_subnet" "router-network" { - cidr_block = "10.100.1.0/24" - vpc_id = aws_vpc.examplevpc.id - ipv6_cidr_block = cidrsubnet(aws_vpc.examplevpc.ipv6_cidr_block, 8, 1) - assign_ipv6_address_on_creation = true - map_public_ip_on_launch = true - availability_zone = data.aws_availability_zones.available.names[0] - tags = { - Name = "tf-acc-route-ipv6-instance-router" + Name = %[1]q } } -resource "aws_subnet" "client-network" { - cidr_block = "10.100.10.0/24" - vpc_id = aws_vpc.examplevpc.id - ipv6_cidr_block = cidrsubnet(aws_vpc.examplevpc.ipv6_cidr_block, 8, 2) - assign_ipv6_address_on_creation = true - map_public_ip_on_launch = false - availability_zone = data.aws_availability_zones.available.names[0] - +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id + tags = { - Name = "tf-acc-route-ipv6-instance-client" + Name = %[1]q } } -resource "aws_route_table" "client-routes" { - vpc_id = aws_vpc.examplevpc.id -} +resource "aws_route" "test" { + route_table_id = aws_route_table.test.id + destination_cidr_block = %[2]q + network_interface_id = %[3]s.id -resource "aws_route_table_association" "client-routes" { - route_table_id = aws_route_table.client-routes.id - subnet_id = aws_subnet.client-network.id + # Wait for the ENI attachment. + depends_on = [aws_instance.test] } - -resource "aws_instance" "test-router" { - ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id - instance_type = data.aws_ec2_instance_type_offering.available.instance_type - subnet_id = aws_subnet.router-network.id +`, rName, destinationCidr, targetResourceName)) } -resource "aws_route" "internal-default-route" { - route_table_id = aws_route_table.client-routes.id - destination_cidr_block = "0.0.0.0/0" - instance_id = aws_instance.test-router.id -} +func testAccAWSRouteConfigIpv4VpcPeeringConnection(rName, destinationCidr string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" -resource "aws_route" "internal-default-route-ipv6" { - route_table_id = aws_route_table.client-routes.id - destination_ipv6_cidr_block = "::/0" - instance_id = aws_instance.test-router.id -} -`) + tags = { + Name = %[1]q + } } -func testAccAWSRouteConfigIpv6InstanceExpanded() string { - return testAccAvailableEc2InstanceTypeForAvailabilityZone("aws_subnet.router-network.availability_zone", "t2.small", "t3.small") + - testAccLatestAmazonLinuxHvmEbsAmiConfig() + - fmt.Sprintf(` -resource "aws_vpc" "examplevpc" { - cidr_block = "10.100.0.0/16" - enable_dns_hostnames = true - assign_generated_ipv6_cidr_block = true - +resource "aws_vpc" "target" { + cidr_block = "10.0.0.0/16" + tags = { - Name = "terraform-testacc-route-ipv6-instance" + Name = %[1]q } } -data "aws_availability_zones" "available" { - state = "available" +resource "aws_vpc_peering_connection" "test" { + vpc_id = aws_vpc.test.id + peer_vpc_id = aws_vpc.target.id + auto_accept = true - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] + tags = { + Name = %[1]q } } -resource "aws_internet_gateway" "internet" { - vpc_id = aws_vpc.examplevpc.id +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id tags = { - Name = "terraform-testacc-route-ipv6-instance" + Name = %[1]q } } -resource "aws_route" "igw" { - route_table_id = aws_vpc.examplevpc.main_route_table_id - destination_cidr_block = "0.0.0.0/0" - gateway_id = aws_internet_gateway.internet.id +resource "aws_route" "test" { + route_table_id = aws_route_table.test.id + destination_cidr_block = %[2]q + vpc_peering_connection_id = aws_vpc_peering_connection.test.id } - -resource "aws_route" "igw-ipv6" { - route_table_id = aws_vpc.examplevpc.main_route_table_id - destination_ipv6_cidr_block = "::0/0" - gateway_id = aws_internet_gateway.internet.id +`, rName, destinationCidr) } -resource "aws_subnet" "router-network" { - cidr_block = "10.100.1.0/24" - vpc_id = aws_vpc.examplevpc.id - ipv6_cidr_block = cidrsubnet(aws_vpc.examplevpc.ipv6_cidr_block, 8, 1) - assign_ipv6_address_on_creation = true - map_public_ip_on_launch = true - availability_zone = data.aws_availability_zones.available.names[0] - +func testAccAWSRouteConfigIpv4NatGateway(rName, destinationCidr string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + tags = { - Name = "tf-acc-route-ipv6-instance-router" + Name = %[1]q } } -resource "aws_subnet" "client-network" { - cidr_block = "10.100.10.0/24" - vpc_id = aws_vpc.examplevpc.id - ipv6_cidr_block = cidrsubnet(aws_vpc.examplevpc.ipv6_cidr_block, 8, 2) - assign_ipv6_address_on_creation = true - map_public_ip_on_launch = false - availability_zone = data.aws_availability_zones.available.names[0] - +resource "aws_subnet" "test" { + cidr_block = "10.1.1.0/24" + vpc_id = aws_vpc.test.id + + map_public_ip_on_launch = true + tags = { - Name = "tf-acc-route-ipv6-instance-client" + Name = %[1]q } } -resource "aws_route_table" "client-routes" { - vpc_id = aws_vpc.examplevpc.id -} +resource "aws_internet_gateway" "test" { + vpc_id = aws_vpc.test.id -resource "aws_route_table_association" "client-routes" { - route_table_id = aws_route_table.client-routes.id - subnet_id = aws_subnet.client-network.id + tags = { + Name = %[1]q + } } -resource "aws_instance" "test-router" { - ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id - instance_type = data.aws_ec2_instance_type_offering.available.instance_type - subnet_id = aws_subnet.router-network.id -} +resource "aws_eip" "test" { + vpc = true -resource "aws_route" "internal-default-route" { - route_table_id = aws_route_table.client-routes.id - destination_cidr_block = "0.0.0.0/0" - instance_id = aws_instance.test-router.id + tags = { + Name = %[1]q + } } -resource "aws_route" "internal-default-route-ipv6" { - route_table_id = aws_route_table.client-routes.id - destination_ipv6_cidr_block = "::0/0" - instance_id = aws_instance.test-router.id -} -`) -} +resource "aws_nat_gateway" "test" { + allocation_id = aws_eip.test.id + subnet_id = aws_subnet.test.id -func testAccAWSRouteConfigIpv6PeeringConnection() string { - return fmt.Sprintf(` -resource "aws_vpc" "foo" { - cidr_block = "10.0.0.0/16" - assign_generated_ipv6_cidr_block = true - tags = { - Name = "terraform-testacc-route-ipv6-peering-connection" + Name = %[1]q } -} -resource "aws_vpc" "bar" { - cidr_block = "10.1.0.0/16" - assign_generated_ipv6_cidr_block = true + depends_on = [aws_internet_gateway.test] } -resource "aws_vpc_peering_connection" "foo" { - vpc_id = aws_vpc.foo.id - peer_vpc_id = aws_vpc.bar.id - auto_accept = true -} +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id -resource "aws_route_table" "peering" { - vpc_id = aws_vpc.foo.id + tags = { + Name = %[1]q + } } -resource "aws_route" "pc" { - route_table_id = aws_route_table.peering.id - destination_ipv6_cidr_block = aws_vpc.bar.ipv6_cidr_block - vpc_peering_connection_id = aws_vpc_peering_connection.foo.id +resource "aws_route" "test" { + route_table_id = aws_route_table.test.id + destination_cidr_block = %[2]q + nat_gateway_id = aws_nat_gateway.test.id } -`) +`, rName, destinationCidr) } -func testAccAWSRouteConfigIpv6() string { +func testAccAWSRouteConfigIpv4VpnGateway(rName, destinationCidr string) string { return fmt.Sprintf(` -resource "aws_vpc" "foo" { - cidr_block = "10.1.0.0/16" - assign_generated_ipv6_cidr_block = true +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-route-ipv6" + Name = %[1]q } } -resource "aws_egress_only_internet_gateway" "foo" { - vpc_id = aws_vpc.foo.id +resource "aws_vpn_gateway" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } } -resource "aws_route_table" "foo" { - vpc_id = aws_vpc.foo.id +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } } -resource "aws_route" "bar" { - route_table_id = aws_route_table.foo.id - destination_ipv6_cidr_block = "::/0" - egress_only_gateway_id = aws_egress_only_internet_gateway.foo.id +resource "aws_route" "test" { + route_table_id = aws_route_table.test.id + destination_cidr_block = %[2]q + gateway_id = aws_vpn_gateway.test.id } -`) +`, rName, destinationCidr) } -func testAccAWSRouteConfigIpv6Expanded() string { +func testAccAWSRouteConfigIpv6VpnGateway(rName, destinationCidr string) string { return fmt.Sprintf(` -resource "aws_vpc" "foo" { +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" assign_generated_ipv6_cidr_block = true tags = { - Name = "terraform-testacc-route-ipv6" + Name = %[1]q } } -resource "aws_egress_only_internet_gateway" "foo" { - vpc_id = aws_vpc.foo.id -} - -resource "aws_route_table" "foo" { - vpc_id = aws_vpc.foo.id -} - -resource "aws_route" "bar" { - route_table_id = aws_route_table.foo.id - destination_ipv6_cidr_block = "::0/0" - egress_only_gateway_id = aws_egress_only_internet_gateway.foo.id -} -`) -} +resource "aws_vpn_gateway" "test" { + vpc_id = aws_vpc.test.id -func testAccAWSRouteBasicConfigChangeCidr() string { - return fmt.Sprintf(` -resource "aws_vpc" "foo" { - cidr_block = "10.1.0.0/16" - tags = { - Name = "terraform-testacc-route-change-cidr" + Name = %[1]q } } -resource "aws_internet_gateway" "foo" { - vpc_id = aws_vpc.foo.id +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id tags = { - Name = "terraform-testacc-route-change-cidr" + Name = %[1]q } } -resource "aws_route_table" "foo" { - vpc_id = aws_vpc.foo.id -} - -resource "aws_route" "bar" { - route_table_id = aws_route_table.foo.id - destination_cidr_block = "10.2.0.0/16" - gateway_id = aws_internet_gateway.foo.id +resource "aws_route" "test" { + route_table_id = aws_route_table.test.id + destination_ipv6_cidr_block = %[2]q + gateway_id = aws_vpn_gateway.test.id } -`) +`, rName, destinationCidr) } -func testAccAWSRouteNoopChange() string { - return testAccAvailableEc2InstanceTypeForAvailabilityZone("aws_subnet.test.availability_zone", "t2.nano", "t3.nano") + - testAccLatestAmazonLinuxHvmEbsAmiConfig() + - fmt.Sprint(` -data "aws_availability_zones" "available" { - state = "available" +func testAccAWSRouteConfigIpv4FlexiTarget(rName, destinationCidr, targetAttribute, targetValue string) string { + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAvailableAZsNoOptInDefaultExcludeConfig(), + testAccAvailableEc2InstanceTypeForRegion("t3.micro", "t2.micro"), + fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] + tags = { + Name = %[1]q } } -resource "aws_vpc" "test" { - cidr_block = "10.10.0.0/16" - +resource "aws_vpn_gateway" "test" { + vpc_id = aws_vpc.test.id + tags = { - Name = "terraform-testacc-route-noop-change" + Name = %[1]q } } -resource "aws_route_table" "test" { +resource "aws_internet_gateway" "test" { vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } } resource "aws_subnet" "test" { - availability_zone = data.aws_availability_zones.available.names[0] + cidr_block = "10.1.1.0/24" vpc_id = aws_vpc.test.id - cidr_block = "10.10.10.0/24" - + availability_zone = data.aws_availability_zones.available.names[0] + + map_public_ip_on_launch = true + tags = { - Name = "tf-acc-route-noop-change" + Name = %[1]q } } -resource "aws_route" "test" { - route_table_id = aws_route_table.test.id - destination_cidr_block = "0.0.0.0/0" - instance_id = aws_instance.nat.id -} - -resource "aws_instance" "nat" { +resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = data.aws_ec2_instance_type_offering.available.instance_type - subnet_id = aws_subnet.test.id -} -`) -} -func testAccAWSRouteWithVPCEndpoint() string { - return fmt.Sprintf(` -data "aws_region" "current" {} + network_interface { + device_index = 0 + network_interface_id = aws_network_interface.test.id + } -resource "aws_vpc" "foo" { - cidr_block = "10.1.0.0/16" - tags = { - Name = "terraform-testacc-route-with-vpc-endpoint" + Name = %[1]q } } -resource "aws_internet_gateway" "foo" { - vpc_id = aws_vpc.foo.id - +resource "aws_ec2_transit_gateway" "test" { tags = { - Name = "terraform-testacc-route-with-vpc-endpoint" + Name = %[1]q } } -resource "aws_route_table" "foo" { - vpc_id = aws_vpc.foo.id -} - -resource "aws_route" "bar" { - route_table_id = aws_route_table.foo.id - destination_cidr_block = "10.3.0.0/16" - gateway_id = aws_internet_gateway.foo.id +resource "aws_ec2_transit_gateway_vpc_attachment" "test" { + subnet_ids = [aws_subnet.test.id] + transit_gateway_id = aws_ec2_transit_gateway.test.id + vpc_id = aws_vpc.test.id - # Forcing endpoint to create before route - without this the crash is a race. - depends_on = ["aws_vpc_endpoint.baz"] + tags = { + Name = %[1]q + } } -resource "aws_vpc_endpoint" "baz" { - vpc_id = aws_vpc.foo.id - service_name = "com.amazonaws.${data.aws_region.current.name}.s3" - route_table_ids = [aws_route_table.foo.id] -} -`) -} +resource "aws_network_interface" "test" { + subnet_id = aws_subnet.test.id -func testAccAWSRouteNewRouteTable() string { - return fmt.Sprintf(` -resource "aws_vpc" "foo" { - cidr_block = "10.1.0.0/16" - tags = { - Name = "terraform-testacc-route-basic" + Name = %[1]q } } -resource "aws_vpc" "bar" { - cidr_block = "10.2.0.0/16" - +resource "aws_vpc" "target" { + cidr_block = "10.0.0.0/16" + tags = { - Name = "terraform-testacc-route-new-route-table" + Name = %[1]q } } -resource "aws_internet_gateway" "foo" { - vpc_id = aws_vpc.foo.id +resource "aws_vpc_peering_connection" "test" { + vpc_id = aws_vpc.test.id + peer_vpc_id = aws_vpc.target.id + auto_accept = true tags = { - Name = "terraform-testacc-route-basic" + Name = %[1]q } } -resource "aws_internet_gateway" "bar" { - vpc_id = aws_vpc.bar.id +resource "aws_eip" "test" { + vpc = true tags = { - Name = "terraform-testacc-route-new-route-table" + Name = %[1]q } } -resource "aws_route_table" "foo" { - vpc_id = aws_vpc.foo.id +resource "aws_nat_gateway" "test" { + allocation_id = aws_eip.test.id + subnet_id = aws_subnet.test.id tags = { - Name = "terraform-testacc-route-basic" + Name = %[1]q } + + depends_on = [aws_internet_gateway.test] } -resource "aws_route_table" "bar" { - vpc_id = aws_vpc.bar.id +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id tags = { - Name = "terraform-testacc-route-new-route-table" + Name = %[1]q } } -resource "aws_route" "bar" { - route_table_id = aws_route_table.bar.id - destination_cidr_block = "10.4.0.0/16" - gateway_id = aws_internet_gateway.bar.id +resource "aws_route" "test" { + route_table_id = aws_route_table.test.id + destination_cidr_block = %[2]q + + %[3]s = %[4]s.id } -`) +`, rName, destinationCidr, targetAttribute, targetValue)) } -func testAccAWSRouteConfigTransitGatewayIDDestinatationCidrBlock() string { - return testAccAvailableAZsNoOptInDefaultExcludeConfig() + +func testAccAWSRouteConfigIpv6FlexiTarget(rName, destinationCidr, targetAttribute, targetValue string) string { + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAvailableAZsNoOptInDefaultExcludeConfig(), + testAccAvailableEc2InstanceTypeForRegion("t3.micro", "t2.micro"), fmt.Sprintf(` -# IncorrectState: Transit Gateway is not available in availability zone usw2-az4 - resource "aws_vpc" "test" { - cidr_block = "10.0.0.0/16" + cidr_block = "10.1.0.0/16" + assign_generated_ipv6_cidr_block = true + + tags = { + Name = %[1]q + } +} + +resource "aws_vpn_gateway" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_internet_gateway" "test" { + vpc_id = aws_vpc.test.id tags = { - Name = "tf-acc-test-ec2-route-transit-gateway-id" + Name = %[1]q } } resource "aws_subnet" "test" { - availability_zone = data.aws_availability_zones.available.names[0] - cidr_block = "10.0.0.0/24" + cidr_block = "10.1.1.0/24" vpc_id = aws_vpc.test.id + availability_zone = data.aws_availability_zones.available.names[0] + ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 1) + + map_public_ip_on_launch = true tags = { - Name = "tf-acc-test-ec2-route-transit-gateway-id" + Name = %[1]q } } -resource "aws_ec2_transit_gateway" "test" {} +resource "aws_instance" "test" { + ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id + instance_type = data.aws_ec2_instance_type_offering.available.instance_type + subnet_id = aws_subnet.test.id -resource "aws_ec2_transit_gateway_vpc_attachment" "test" { - subnet_ids = [aws_subnet.test.id] - transit_gateway_id = aws_ec2_transit_gateway.test.id - vpc_id = aws_vpc.test.id + ipv6_address_count = 1 + + tags = { + Name = %[1]q + } } -resource "aws_route" "test" { - destination_cidr_block = "0.0.0.0/0" - route_table_id = aws_vpc.test.default_route_table_id - transit_gateway_id = aws_ec2_transit_gateway_vpc_attachment.test.transit_gateway_id +resource "aws_egress_only_internet_gateway" "test" { + 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 + } } -func testAccAWSRouteConfigConditionalIpv4Ipv6(rName string, ipv6Route bool) string { - return fmt.Sprintf(` -resource "aws_vpc" "test" { - cidr_block = "10.1.0.0/16" +resource "aws_vpc" "target" { + cidr_block = "10.0.0.0/16" assign_generated_ipv6_cidr_block = true tags = { @@ -1232,15 +2231,17 @@ resource "aws_vpc" "test" { } } -resource "aws_egress_only_internet_gateway" "test" { - vpc_id = aws_vpc.test.id +resource "aws_vpc_peering_connection" "test" { + vpc_id = aws_vpc.test.id + peer_vpc_id = aws_vpc.target.id + auto_accept = true tags = { Name = %[1]q } } -resource "aws_internet_gateway" "test" { +resource "aws_route_table" "test" { vpc_id = aws_vpc.test.id tags = { @@ -1248,6 +2249,25 @@ resource "aws_internet_gateway" "test" { } } +resource "aws_route" "test" { + route_table_id = aws_route_table.test.id + destination_ipv6_cidr_block = %[2]q + + %[3]s = %[4]s.id +} +`, rName, destinationCidr, targetAttribute, targetValue)) +} + +func testAccAWSRouteConfigIpv4NoRoute(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + + tags = { + Name = %[1]q + } +} + resource "aws_route_table" "test" { vpc_id = aws_vpc.test.id @@ -1255,19 +2275,17 @@ resource "aws_route_table" "test" { Name = %[1]q } } - -locals { - ipv6 = %[2]t - destination = "0.0.0.0/0" - destination_ipv6 = "::/0" +`, rName) } +func testAccAWSRouteConfigIpv4LocalRoute(rName string) string { + return composeConfig( + testAccAWSRouteConfigIpv4NoRoute(rName), + fmt.Sprintf(` resource "aws_route" "test" { - route_table_id = aws_route_table.test.id - gateway_id = aws_internet_gateway.test.id - - destination_cidr_block = local.ipv6 ? "" : local.destination - destination_ipv6_cidr_block = local.ipv6 ? local.destination_ipv6 : "" + route_table_id = aws_route_table.test.id + destination_cidr_block = aws_vpc.test.cidr_block + gateway_id = "local" } -`, rName, ipv6Route) +`)) } diff --git a/aws/validators.go b/aws/validators.go index 053a92bead9..f0170951344 100644 --- a/aws/validators.go +++ b/aws/validators.go @@ -815,6 +815,7 @@ func validateIpv6CIDRBlock(cidr string) error { return nil } +// TODO Replace with tfnet.CIDRBlocksEqual. // cidrBlocksEqual returns whether or not two CIDR blocks are equal: // - Both CIDR blocks parse to an IP address and network // - The string representation of the IP addresses are equal