From 02be4e4bb6fb3efb1d1fc760be249bf6c7226147 Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Mon, 15 Jun 2020 21:03:08 -0400 Subject: [PATCH] New Resource: aws_ec2_local_gateway_route Reference: https://github.com/terraform-providers/terraform-provider-aws/issues/13503 Output from acceptance testing: ``` --- PASS: TestAccAWSEc2LocalGatewayRoute_disappears (22.00s) --- PASS: TestAccAWSEc2LocalGatewayRoute_basic (22.43s) ``` --- aws/provider.go | 3 +- aws/resource_aws_ec2_local_gateway_route.go | 205 ++++++++++++++++++ ...source_aws_ec2_local_gateway_route_test.go | 167 ++++++++++++++ website/aws.erb | 3 + .../r/ec2_local_gateway_route.html.markdown | 43 ++++ 5 files changed, 420 insertions(+), 1 deletion(-) create mode 100644 aws/resource_aws_ec2_local_gateway_route.go create mode 100644 aws/resource_aws_ec2_local_gateway_route_test.go create mode 100644 website/docs/r/ec2_local_gateway_route.html.markdown diff --git a/aws/provider.go b/aws/provider.go index a9fc198e8e2..6ad62569de8 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -538,8 +538,9 @@ func Provider() terraform.ResourceProvider { "aws_ec2_capacity_reservation": resourceAwsEc2CapacityReservation(), "aws_ec2_client_vpn_endpoint": resourceAwsEc2ClientVpnEndpoint(), "aws_ec2_client_vpn_network_association": resourceAwsEc2ClientVpnNetworkAssociation(), - "aws_ec2_local_gateway_route_table_vpc_association": resourceAwsEc2LocalGatewayRouteTableVpcAssociation(), "aws_ec2_fleet": resourceAwsEc2Fleet(), + "aws_ec2_local_gateway_route": resourceAwsEc2LocalGatewayRoute(), + "aws_ec2_local_gateway_route_table_vpc_association": resourceAwsEc2LocalGatewayRouteTableVpcAssociation(), "aws_ec2_tag": resourceAwsEc2Tag(), "aws_ec2_traffic_mirror_filter": resourceAwsEc2TrafficMirrorFilter(), "aws_ec2_traffic_mirror_filter_rule": resourceAwsEc2TrafficMirrorFilterRule(), diff --git a/aws/resource_aws_ec2_local_gateway_route.go b/aws/resource_aws_ec2_local_gateway_route.go new file mode 100644 index 00000000000..a39f96aa3be --- /dev/null +++ b/aws/resource_aws_ec2_local_gateway_route.go @@ -0,0 +1,205 @@ +package aws + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +const ( + ec2LocalGatewayRouteEventualConsistencyTimeout = 1 * time.Minute +) + +func resourceAwsEc2LocalGatewayRoute() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsEc2LocalGatewayRouteCreate, + Read: resourceAwsEc2LocalGatewayRouteRead, + Delete: resourceAwsEc2LocalGatewayRouteDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "destination_cidr_block": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateCIDRNetworkAddress, + }, + "local_gateway_route_table_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "local_gateway_virtual_interface_group_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceAwsEc2LocalGatewayRouteCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + destination := d.Get("destination_cidr_block").(string) + localGatewayRouteTableID := d.Get("local_gateway_route_table_id").(string) + + input := &ec2.CreateLocalGatewayRouteInput{ + DestinationCidrBlock: aws.String(destination), + LocalGatewayRouteTableId: aws.String(localGatewayRouteTableID), + LocalGatewayVirtualInterfaceGroupId: aws.String(d.Get("local_gateway_virtual_interface_group_id").(string)), + } + + _, err := conn.CreateLocalGatewayRoute(input) + + if err != nil { + return fmt.Errorf("error creating EC2 Local Gateway Route: %s", err) + } + + d.SetId(fmt.Sprintf("%s_%s", localGatewayRouteTableID, destination)) + + return resourceAwsEc2LocalGatewayRouteRead(d, meta) +} + +func resourceAwsEc2LocalGatewayRouteRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + localGatewayRouteTableID, destination, err := decodeEc2LocalGatewayRouteID(d.Id()) + if err != nil { + return err + } + + var localGatewayRoute *ec2.LocalGatewayRoute + err = resource.Retry(ec2LocalGatewayRouteEventualConsistencyTimeout, func() *resource.RetryError { + var err error + localGatewayRoute, err = getEc2LocalGatewayRoute(conn, localGatewayRouteTableID, destination) + + if err != nil { + return resource.NonRetryableError(err) + } + + if d.IsNewResource() && localGatewayRoute == nil { + return resource.RetryableError(&resource.NotFoundError{}) + } + + return nil + }) + + if isResourceTimeoutError(err) { + localGatewayRoute, err = getEc2LocalGatewayRoute(conn, localGatewayRouteTableID, destination) + } + + if isAWSErr(err, "InvalidRouteTableID.NotFound", "") { + log.Printf("[WARN] EC2 Local Gateway Route Table (%s) not found, removing from state", localGatewayRouteTableID) + d.SetId("") + return nil + } + + if !d.IsNewResource() && isResourceNotFoundError(err) { + log.Printf("[WARN] EC2 Local Gateway Route (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading EC2 Local Gateway Route: %s", err) + } + + if localGatewayRoute == nil { + log.Printf("[WARN] EC2 Local Gateway Route (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + state := aws.StringValue(localGatewayRoute.State) + if state == ec2.LocalGatewayRouteStateDeleted || state == ec2.LocalGatewayRouteStateDeleting { + log.Printf("[WARN] EC2 Local Gateway Route (%s) deleted, removing from state", d.Id()) + d.SetId("") + return nil + } + + d.Set("destination_cidr_block", localGatewayRoute.DestinationCidrBlock) + d.Set("local_gateway_virtual_interface_group_id", localGatewayRoute.LocalGatewayVirtualInterfaceGroupId) + d.Set("local_gateway_route_table_id", localGatewayRoute.LocalGatewayRouteTableId) + + return nil +} + +func resourceAwsEc2LocalGatewayRouteDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + localGatewayRouteTableID, destination, err := decodeEc2LocalGatewayRouteID(d.Id()) + if err != nil { + return err + } + + input := &ec2.DeleteLocalGatewayRouteInput{ + DestinationCidrBlock: aws.String(destination), + LocalGatewayRouteTableId: aws.String(localGatewayRouteTableID), + } + + log.Printf("[DEBUG] Deleting EC2 Local Gateway Route (%s): %s", d.Id(), input) + _, err = conn.DeleteLocalGatewayRoute(input) + + if isAWSErr(err, "InvalidRoute.NotFound", "") || isAWSErr(err, "InvalidRouteTableID.NotFound", "") { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting EC2 Local Gateway Route: %s", err) + } + + return nil +} + +func decodeEc2LocalGatewayRouteID(id string) (string, string, error) { + parts := strings.Split(id, "_") + + if len(parts) != 2 { + return "", "", fmt.Errorf("Unexpected format of ID (%q), expected tgw-rtb-ID_DESTINATION", id) + } + + return parts[0], parts[1], nil +} + +func getEc2LocalGatewayRoute(conn *ec2.EC2, localGatewayRouteTableID, destination string) (*ec2.LocalGatewayRoute, error) { + input := &ec2.SearchLocalGatewayRoutesInput{ + Filters: []*ec2.Filter{ + { + Name: aws.String("type"), + Values: aws.StringSlice([]string{"static"}), + }, + }, + LocalGatewayRouteTableId: aws.String(localGatewayRouteTableID), + } + + output, err := conn.SearchLocalGatewayRoutes(input) + + if err != nil { + return nil, err + } + + if output == nil || len(output.Routes) == 0 { + return nil, nil + } + + for _, route := range output.Routes { + if route == nil { + continue + } + + if aws.StringValue(route.DestinationCidrBlock) == destination { + return route, nil + } + } + + return nil, nil +} diff --git a/aws/resource_aws_ec2_local_gateway_route_test.go b/aws/resource_aws_ec2_local_gateway_route_test.go new file mode 100644 index 00000000000..712ce7e4237 --- /dev/null +++ b/aws/resource_aws_ec2_local_gateway_route_test.go @@ -0,0 +1,167 @@ +package aws + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccAWSEc2LocalGatewayRoute_basic(t *testing.T) { + // Hide Outposts testing behind consistent environment variable + outpostArn := os.Getenv("AWS_OUTPOST_ARN") + if outpostArn == "" { + t.Skip( + "Environment variable AWS_OUTPOST_ARN is not set. " + + "This environment variable must be set to the ARN of " + + "a deployed Outpost to enable this test.") + } + + rInt := randIntRange(0, 255) + destinationCidrBlock := fmt.Sprintf("172.16.%d.0/24", rInt) + localGatewayRouteTableDataSourceName := "data.aws_ec2_local_gateway_route_table.test" + localGatewayVirtualInterfaceGroupDataSourceName := "data.aws_ec2_local_gateway_virtual_interface_group.test" + resourceName := "aws_ec2_local_gateway_route.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2LocalGatewayRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2LocalGatewayRouteConfigDestinationCidrBlock(destinationCidrBlock), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2LocalGatewayRouteExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", destinationCidrBlock), + resource.TestCheckResourceAttrPair(resourceName, "local_gateway_route_table_id", localGatewayRouteTableDataSourceName, "id"), + resource.TestCheckResourceAttrPair(resourceName, "local_gateway_virtual_interface_group_id", localGatewayVirtualInterfaceGroupDataSourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSEc2LocalGatewayRoute_disappears(t *testing.T) { + // Hide Outposts testing behind consistent environment variable + outpostArn := os.Getenv("AWS_OUTPOST_ARN") + if outpostArn == "" { + t.Skip( + "Environment variable AWS_OUTPOST_ARN is not set. " + + "This environment variable must be set to the ARN of " + + "a deployed Outpost to enable this test.") + } + + rInt := randIntRange(0, 255) + destinationCidrBlock := fmt.Sprintf("172.16.%d.0/24", rInt) + resourceName := "aws_ec2_local_gateway_route.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2LocalGatewayRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2LocalGatewayRouteConfigDestinationCidrBlock(destinationCidrBlock), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2LocalGatewayRouteExists(resourceName), + testAccCheckResourceDisappears(testAccProvider, resourceAwsEc2LocalGatewayRoute(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckAWSEc2LocalGatewayRouteExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No EC2 Local Gateway Route ID is set") + } + + localGatewayRouteTableID, destination, err := decodeEc2LocalGatewayRouteID(rs.Primary.ID) + + if err != nil { + return err + } + + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + route, err := getEc2LocalGatewayRoute(conn, localGatewayRouteTableID, destination) + + if err != nil { + return err + } + + if route == nil { + return fmt.Errorf("EC2 Local Gateway Route (%s) not found", rs.Primary.ID) + } + + return nil + } +} + +func testAccCheckAWSEc2LocalGatewayRouteDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ec2_local_gateway_route" { + continue + } + + localGatewayRouteTableID, destination, err := decodeEc2LocalGatewayRouteID(rs.Primary.ID) + + if err != nil { + return err + } + + route, err := getEc2LocalGatewayRoute(conn, localGatewayRouteTableID, destination) + + if isAWSErr(err, "InvalidRouteTableID.NotFound", "") { + continue + } + + if err != nil { + return err + } + + if route == nil { + continue + } + + return fmt.Errorf("EC2 Local Gateway Route (%s) still exists", rs.Primary.ID) + } + + return nil +} + +func testAccAWSEc2LocalGatewayRouteConfigDestinationCidrBlock(destinationCidrBlock string) string { + return fmt.Sprintf(` +data "aws_ec2_local_gateways" "test" {} + +data "aws_ec2_local_gateway_route_table" "test" { + local_gateway_id = tolist(data.aws_ec2_local_gateways.test.ids)[0] +} + +data "aws_ec2_local_gateway_virtual_interface_group" "test" { + local_gateway_id = tolist(data.aws_ec2_local_gateways.test.ids)[0] +} + +resource "aws_ec2_local_gateway_route" "test" { + destination_cidr_block = %[1]q + local_gateway_route_table_id = data.aws_ec2_local_gateway_route_table.test.id + local_gateway_virtual_interface_group_id = data.aws_ec2_local_gateway_virtual_interface_group.test.id +} +`, destinationCidrBlock) +} diff --git a/website/aws.erb b/website/aws.erb index 7fbcd93aaa1..b9a6dece7f6 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -1216,6 +1216,9 @@
  • aws_ec2_client_vpn_network_association
  • +
  • + aws_ec2_local_gateway_route +
  • aws_ec2_local_gateway_route_table_vpc_association
  • diff --git a/website/docs/r/ec2_local_gateway_route.html.markdown b/website/docs/r/ec2_local_gateway_route.html.markdown new file mode 100644 index 00000000000..f6312550dc1 --- /dev/null +++ b/website/docs/r/ec2_local_gateway_route.html.markdown @@ -0,0 +1,43 @@ +--- +subcategory: "EC2" +layout: "aws" +page_title: "AWS: aws_ec2_local_gateway_route" +description: |- + Manages an EC2 Local Gateway Route +--- + +# Resource: aws_ec2_local_gateway_route + +Manages an EC2 Local Gateway Route. More information can be found in the [Outposts User Guide](https://docs.aws.amazon.com/outposts/latest/userguide/outposts-networking-components.html#routing). + +## Example Usage + +```hcl +resource "aws_ec2_local_gateway_route" "example" { + destination_cidr_block = "172.16.0.0/16" + local_gateway_route_table_id = data.aws_ec2_local_gateway_route_table.example.id + local_gateway_virtual_interface_group_id = data.aws_ec2_local_gateway_virtual_interface_group.example.id +} +``` + +## Argument Reference + +The following arguments are required: + +* `destination_cidr_block` - (Required) IPv4 CIDR range used for destination matches. Routing decisions are based on the most specific match. +* `local_gateway_route_table_id` - (Required) Identifier of EC2 Local Gateway Route Table. +* `local_gateway_virtual_interface_group_id` - (Required) Identifier of EC2 Local Gateway Virtual Interface Group. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - EC2 Local Gateway Route Table identifier and destination CIDR block separated by underscores (`_`) + +## Import + +`aws_ec2_local_gateway_route` can be imported by using the EC2 Local Gateway Route Table identifier and destination CIDR block separated by underscores (`_`), e.g. + +``` +$ terraform import aws_ec2_local_gateway_route.example lgw-rtb-12345678_172.16.0.0/16 +```