From 003a17acf3bfb50394297b923c102a1b0567c5a2 Mon Sep 17 00:00:00 2001 From: Andrew Tulloch Date: Fri, 27 Sep 2024 12:17:48 +0100 Subject: [PATCH] Add support for default route table propagation --- internal/service/ec2/exports_test.go | 3 +- internal/service/ec2/service_package_gen.go | 4 + ...ay_default_route_table_association_test.go | 2 +- ...gateway_default_route_table_propagation.go | 254 ++++++++++++++++++ ...ay_default_route_table_propagation_test.go | 154 +++++++++++ internal/service/ec2/transitgateway_test.go | 4 + ...ault_route_table_propagation.html.markdown | 36 +++ 7 files changed, 455 insertions(+), 2 deletions(-) create mode 100644 internal/service/ec2/transitgateway_default_route_table_propagation.go create mode 100644 internal/service/ec2/transitgateway_default_route_table_propagation_test.go create mode 100644 website/docs/r/ec2_transit_gateway_default_route_table_propagation.html.markdown diff --git a/internal/service/ec2/exports_test.go b/internal/service/ec2/exports_test.go index e792484997e..98f09740503 100644 --- a/internal/service/ec2/exports_test.go +++ b/internal/service/ec2/exports_test.go @@ -81,7 +81,8 @@ var ( ResourceTrafficMirrorSession = resourceTrafficMirrorSession ResourceTrafficMirrorTarget = resourceTrafficMirrorTarget ResourceTransitGatewayConnect = resourceTransitGatewayConnect - ResourceTransitgatewayDefaultRouteTableAssociation = newResourceTransitGatewayDefaultRouteTableAssociation + ResourceTransitGatewayDefaultRouteTableAssociation = newResourceTransitGatewayDefaultRouteTableAssociation + ResourceTransitGatewayDefaultRouteTablePropagation = newResourceTransitGatewayDefaultRouteTablePropagation ResourceTransitGatewayMulticastDomain = resourceTransitGatewayMulticastDomain ResourceTransitGatewayMulticastDomainAssociation = resourceTransitGatewayMulticastDomainAssociation ResourceTransitGatewayMulticastGroupMember = resourceTransitGatewayMulticastGroupMember diff --git a/internal/service/ec2/service_package_gen.go b/internal/service/ec2/service_package_gen.go index 6ef067cdcd4..e46f6ee1554 100644 --- a/internal/service/ec2/service_package_gen.go +++ b/internal/service/ec2/service_package_gen.go @@ -61,6 +61,10 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic Factory: newResourceTransitGatewayDefaultRouteTableAssociation, Name: "Transit Gateway Default Route Table Association", }, + { + Factory: newResourceTransitGatewayDefaultRouteTablePropagation, + Name: "Transit Gateway Default Route Table Propagation", + }, { Factory: newSecurityGroupEgressRuleResource, Name: "Security Group Egress Rule", diff --git a/internal/service/ec2/transitgateway_default_route_table_association_test.go b/internal/service/ec2/transitgateway_default_route_table_association_test.go index dabee867abc..cb442fb22ef 100644 --- a/internal/service/ec2/transitgateway_default_route_table_association_test.go +++ b/internal/service/ec2/transitgateway_default_route_table_association_test.go @@ -76,7 +76,7 @@ func testAccTransitGatewayDefaultRouteTableAssociation_disappears(t *testing.T, Config: testAccTransitgatewayDefaultRouteTableAssociationConfig_basic(), Check: resource.ComposeTestCheckFunc( testAccCheckTransitGatewayDefaultRouteTableAssociationExists(ctx, resourceName, &transitgateway), - acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfec2.ResourceTransitgatewayDefaultRouteTableAssociation, resourceName), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfec2.ResourceTransitGatewayDefaultRouteTableAssociation, resourceName), ), ExpectNonEmptyPlan: true, }, diff --git a/internal/service/ec2/transitgateway_default_route_table_propagation.go b/internal/service/ec2/transitgateway_default_route_table_propagation.go new file mode 100644 index 00000000000..7673b43fea2 --- /dev/null +++ b/internal/service/ec2/transitgateway_default_route_table_propagation.go @@ -0,0 +1,254 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ec2 + +import ( + "context" + "errors" + "time" + + "github.com/aws/aws-sdk-go-v2/service/ec2" + awstypes "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// Function annotations are used for resource registration to the Provider. DO NOT EDIT. +// @FrameworkResource("aws_ec2_transit_gateway_default_route_table_propagation", name="Transit Gateway Default Route Table Propagation") +func newResourceTransitGatewayDefaultRouteTablePropagation(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &resourceTransitGatewayDefaultRouteTablePropagation{} + + r.SetDefaultCreateTimeout(30 * time.Minute) + r.SetDefaultUpdateTimeout(30 * time.Minute) + r.SetDefaultDeleteTimeout(30 * time.Minute) + + return r, nil +} + +const ( + ResNameTransitGatewayDefaultRouteTablePropagation = "Transit Gateway Default Route Table Propagation" +) + +type resourceTransitGatewayDefaultRouteTablePropagation struct { + framework.ResourceWithConfigure + framework.WithTimeouts +} + +func (r *resourceTransitGatewayDefaultRouteTablePropagation) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "aws_ec2_transit_gateway_default_route_table_propagation" +} + +func (r *resourceTransitGatewayDefaultRouteTablePropagation) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + names.AttrID: framework.IDAttribute(), + "original_route_table_id": schema.StringAttribute{ + Computed: true, + }, + "transit_gateway_route_table_id": schema.StringAttribute{ + Required: true, + }, + names.AttrTransitGatewayID: schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + Blocks: map[string]schema.Block{ + names.AttrTimeouts: timeouts.Block(ctx, timeouts.Opts{ + Create: true, + Update: true, + Delete: true, + }), + }, + } +} + +func (r *resourceTransitGatewayDefaultRouteTablePropagation) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + conn := r.Meta().EC2Client(ctx) + + var plan transitgatewayDefaultRouteTablePropagationResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + tgw, err := findTransitGatewayByID(ctx, conn, plan.TransitGatewayId.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.EC2, create.ErrActionCreating, ResNameTransitGatewayDefaultRouteTablePropagation, plan.ID.String(), err), + err.Error(), + ) + return + } + + in := &ec2.ModifyTransitGatewayInput{ + TransitGatewayId: flex.StringFromFramework(ctx, plan.TransitGatewayId), + Options: &awstypes.ModifyTransitGatewayOptions{ + PropagationDefaultRouteTableId: flex.StringFromFramework(ctx, plan.RouteTableId), + }, + } + + out, err := conn.ModifyTransitGateway(ctx, in) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.EC2, create.ErrActionCreating, ResNameTransitGatewayDefaultRouteTablePropagation, plan.ID.String(), err), + err.Error(), + ) + return + } + if out == nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.EC2, create.ErrActionCreating, ResNameTransitGatewayDefaultRouteTablePropagation, plan.ID.String(), nil), + errors.New("empty output").Error(), + ) + return + } + + plan.ID = flex.StringToFramework(ctx, out.TransitGateway.TransitGatewayId) + plan.OriginalRouteTableId = flex.StringToFramework(ctx, tgw.Options.PropagationDefaultRouteTableId) + + createTimeout := r.CreateTimeout(ctx, plan.Timeouts) + _, err = waitTransitGatewayUpdated(ctx, conn, plan.ID.ValueString(), createTimeout) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.EC2, create.ErrActionWaitingForCreation, ResNameTransitGatewayDefaultRouteTablePropagation, plan.ID.String(), err), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *resourceTransitGatewayDefaultRouteTablePropagation) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().EC2Client(ctx) + + var state transitgatewayDefaultRouteTablePropagationResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + out, err := findTransitGatewayByID(ctx, conn, state.ID.ValueString()) + if tfresource.NotFound(err) { + resp.State.RemoveResource(ctx) + return + } + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.EC2, create.ErrActionSetting, ResNameTransitGatewayDefaultRouteTablePropagation, state.ID.String(), err), + err.Error(), + ) + return + } + + state.ID = flex.StringToFramework(ctx, out.TransitGatewayId) + state.TransitGatewayId = flex.StringToFramework(ctx, out.TransitGatewayId) + state.RouteTableId = flex.StringToFramework(ctx, out.Options.PropagationDefaultRouteTableId) + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *resourceTransitGatewayDefaultRouteTablePropagation) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + conn := r.Meta().EC2Client(ctx) + + var plan, state transitgatewayDefaultRouteTablePropagationResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + if !plan.RouteTableId.Equal(state.RouteTableId) { + in := &ec2.ModifyTransitGatewayInput{ + TransitGatewayId: state.ID.ValueStringPointer(), + Options: &awstypes.ModifyTransitGatewayOptions{ + PropagationDefaultRouteTableId: plan.RouteTableId.ValueStringPointer(), + }, + } + + out, err := conn.ModifyTransitGateway(ctx, in) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.EC2, create.ErrActionUpdating, ResNameTransitGatewayDefaultRouteTablePropagation, plan.ID.String(), err), + err.Error(), + ) + return + } + if out == nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.EC2, create.ErrActionUpdating, ResNameTransitGatewayDefaultRouteTablePropagation, plan.ID.String(), nil), + errors.New("empty output").Error(), + ) + return + } + } + + updateTimeout := r.UpdateTimeout(ctx, plan.Timeouts) + _, err := waitTransitGatewayUpdated(ctx, conn, plan.ID.ValueString(), updateTimeout) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.EC2, create.ErrActionWaitingForUpdate, ResNameTransitGatewayDefaultRouteTablePropagation, plan.ID.String(), err), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *resourceTransitGatewayDefaultRouteTablePropagation) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().EC2Client(ctx) + + var state transitgatewayDefaultRouteTablePropagationResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + in := &ec2.ModifyTransitGatewayInput{ + TransitGatewayId: flex.StringFromFramework(ctx, state.TransitGatewayId), + Options: &awstypes.ModifyTransitGatewayOptions{ + PropagationDefaultRouteTableId: flex.StringFromFramework(ctx, state.OriginalRouteTableId), + }, + } + + _, err := conn.ModifyTransitGateway(ctx, in) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.EC2, create.ErrActionDeleting, ResNameTransitGatewayDefaultRouteTablePropagation, state.ID.String(), err), + err.Error(), + ) + return + } + + deleteTimeout := r.DeleteTimeout(ctx, state.Timeouts) + _, err = waitTransitGatewayUpdated(ctx, conn, state.ID.ValueString(), deleteTimeout) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.EC2, create.ErrActionWaitingForDeletion, ResNameTransitGatewayDefaultRouteTablePropagation, state.ID.String(), err), + err.Error(), + ) + return + } +} + +type transitgatewayDefaultRouteTablePropagationResourceModel struct { + ID types.String `tfsdk:"id"` + OriginalRouteTableId types.String `tfsdk:"original_route_table_id"` + RouteTableId types.String `tfsdk:"transit_gateway_route_table_id"` + TransitGatewayId types.String `tfsdk:"transit_gateway_id"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} diff --git a/internal/service/ec2/transitgateway_default_route_table_propagation_test.go b/internal/service/ec2/transitgateway_default_route_table_propagation_test.go new file mode 100644 index 00000000000..cb93a4d9f92 --- /dev/null +++ b/internal/service/ec2/transitgateway_default_route_table_propagation_test.go @@ -0,0 +1,154 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ec2_test + +import ( + "context" + "errors" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + tfsync "github.com/hashicorp/terraform-provider-aws/internal/experimental/sync" + tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func testAccTransitGatewayDefaultRouteTablePropagation_basic(t *testing.T, semaphore tfsync.Semaphore) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var transitgateway types.TransitGateway + resourceName := "aws_ec2_transit_gateway_default_route_table_propagation.test" + resourceRouteTableName := "aws_ec2_transit_gateway_route_table.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheckTransitGatewaySynchronize(t, semaphore) + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTransitGatewayDefaultRouteTablePropagationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTransitgatewayDefaultRouteTablePropagationConfig_basic(), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayDefaultRouteTablePropagationExists(ctx, resourceName, &transitgateway), + resource.TestCheckResourceAttrPair(resourceName, "transit_gateway_route_table_id", resourceRouteTableName, names.AttrID), + ), + }, + }, + }) +} + +func testAccTransitGatewayDefaultRouteTablePropagation_disappears(t *testing.T, semaphore tfsync.Semaphore) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var transitgateway types.TransitGateway + resourceName := "aws_ec2_transit_gateway_default_route_table_propagation.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheckTransitGatewaySynchronize(t, semaphore) + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTransitGatewayDefaultRouteTablePropagationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTransitgatewayDefaultRouteTablePropagationConfig_basic(), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayDefaultRouteTablePropagationExists(ctx, resourceName, &transitgateway), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfec2.ResourceTransitGatewayDefaultRouteTablePropagation, resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckTransitGatewayDefaultRouteTablePropagationDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Client(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ec2_transit_gateway_default_route_table_propagation" { + continue + } + + resp, err := tfec2.FindTransitGatewayByID(ctx, conn, rs.Primary.ID) + + if err != nil { + if errs.IsA[*retry.NotFoundError](err) { + return nil + } + return create.Error(names.EC2, create.ErrActionCheckingDestroyed, tfec2.ResNameTransitGatewayDefaultRouteTablePropagation, rs.Primary.ID, err) + } + + if *resp.Options.PropagationDefaultRouteTableId != *resp.Options.AssociationDefaultRouteTableId { + return create.Error(names.EC2, create.ErrActionCheckingDestroyed, tfec2.ResNameTransitGatewayDefaultRouteTablePropagation, rs.Primary.ID, errors.New("not destroyed")) + } + } + + return nil + } +} + +func testAccCheckTransitGatewayDefaultRouteTablePropagationExists(ctx context.Context, name string, transitgatewaydefaultroutetableassociation *types.TransitGateway) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.EC2, create.ErrActionCheckingExistence, tfec2.ResNameTransitGatewayDefaultRouteTablePropagation, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.EC2, create.ErrActionCheckingExistence, tfec2.ResNameTransitGatewayDefaultRouteTablePropagation, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Client(ctx) + resp, err := tfec2.FindTransitGatewayByID(ctx, conn, rs.Primary.ID) + + if err != nil { + return create.Error(names.EC2, create.ErrActionCheckingExistence, tfec2.ResNameTransitGatewayDefaultRouteTablePropagation, rs.Primary.ID, err) + } + + if *resp.Options.PropagationDefaultRouteTableId == *resp.Options.AssociationDefaultRouteTableId { + return create.Error(names.EC2, create.ErrActionCheckingExistence, tfec2.ResNameTransitGatewayDefaultRouteTablePropagation, name, errors.New("not changed")) + } + + *transitgatewaydefaultroutetableassociation = *resp + + return nil + } +} + +func testAccTransitgatewayDefaultRouteTablePropagationConfig_basic() string { + return ` +resource "aws_ec2_transit_gateway" "test" {} + +resource "aws_ec2_transit_gateway_route_table" "test" { + transit_gateway_id = aws_ec2_transit_gateway.test.id +} + +resource "aws_ec2_transit_gateway_default_route_table_propagation" "test" { + transit_gateway_id = aws_ec2_transit_gateway.test.id + transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.test.id +} +` +} diff --git a/internal/service/ec2/transitgateway_test.go b/internal/service/ec2/transitgateway_test.go index 4a07e6865ef..f4d16f3ef1a 100644 --- a/internal/service/ec2/transitgateway_test.go +++ b/internal/service/ec2/transitgateway_test.go @@ -50,6 +50,10 @@ func TestAccTransitGateway_serial(t *testing.T) { acctest.CtBasic: testAccTransitGatewayDefaultRouteTableAssociation_basic, acctest.CtDisappears: testAccTransitGatewayDefaultRouteTableAssociation_disappears, }, + "DefaultRouteTablePropagation": { + acctest.CtBasic: testAccTransitGatewayDefaultRouteTablePropagation_basic, + acctest.CtDisappears: testAccTransitGatewayDefaultRouteTablePropagation_disappears, + }, "Gateway": { acctest.CtBasic: testAccTransitGateway_basic, acctest.CtDisappears: testAccTransitGateway_disappears, diff --git a/website/docs/r/ec2_transit_gateway_default_route_table_propagation.html.markdown b/website/docs/r/ec2_transit_gateway_default_route_table_propagation.html.markdown new file mode 100644 index 00000000000..c45c9631bc0 --- /dev/null +++ b/website/docs/r/ec2_transit_gateway_default_route_table_propagation.html.markdown @@ -0,0 +1,36 @@ +--- +subcategory: "Transit Gateway" +layout: "aws" +page_title: "AWS: aws_ec2_transit_gateway_default_route_table_propagation" +description: |- + Terraform resource for managing an AWS EC2 (Elastic Compute Cloud) Transit Gateway Default Route Table Propagation. +--- +# Resource: aws_ec2_transit_gateway_default_route_table_propagation + +Terraform resource for managing an AWS EC2 (Elastic Compute Cloud) Transit Gateway Default Route Table Propagation. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_ec2_transit_gateway_default_route_table_propagation" "example" { + transit_gateway_id = aws_ec2_transit_gateway.example.id + transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.example.id +} +``` + +## Argument Reference + +The following arguments are required: + +* `transit_gateway_id` - (Required) ID of the Transit Gateway to change the default association route table on. +* `transit_gateway_route_table_id` - (Required) ID of the Transit Gateway Route Table to be made the default association route table. + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `5m`) +* `update` - (Default `5m`) +* `delete` - (Default `5m`)