diff --git a/.changelog/44542.txt b/.changelog/44542.txt new file mode 100644 index 000000000000..58c07cac6719 --- /dev/null +++ b/.changelog/44542.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/aws_ec2_transit_gateway_route_table_propagation.test: Fix bug causing `inconsistent final plan` errors +``` \ No newline at end of file diff --git a/internal/service/ec2/transitgateway_route_table_association.go b/internal/service/ec2/transitgateway_route_table_association.go index da30d5c815f4..252c0160466c 100644 --- a/internal/service/ec2/transitgateway_route_table_association.go +++ b/internal/service/ec2/transitgateway_route_table_association.go @@ -14,7 +14,6 @@ import ( awstypes "github.com/aws/aws-sdk-go-v2/service/ec2/types" "github.com/hashicorp/aws-sdk-go-base/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -52,6 +51,7 @@ func resourceTransitGatewayRouteTableAssociation() *schema.Resource { names.AttrTransitGatewayAttachmentID: { Type: schema.TypeString, Required: true, + ForceNew: true, ValidateFunc: validation.NoZeroValues, }, "transit_gateway_route_table_id": { @@ -61,43 +61,6 @@ func resourceTransitGatewayRouteTableAssociation() *schema.Resource { ValidateFunc: validation.NoZeroValues, }, }, - - CustomizeDiff: customdiff.Sequence( - func(_ context.Context, d *schema.ResourceDiff, meta any) error { - if !d.HasChange(names.AttrTransitGatewayAttachmentID) { - return nil - } - - // See https://github.com/hashicorp/terraform-provider-aws/issues/30085 - // In all cases, changes should force new except: - // o is not empty string AND - // n is empty string AND - // plan is unknown AND - // state is known - o, n := d.GetChange(names.AttrTransitGatewayAttachmentID) - if o.(string) == "" || n.(string) != "" { - return d.ForceNew(names.AttrTransitGatewayAttachmentID) - } - - rawPlan := d.GetRawPlan() - plan := rawPlan.GetAttr(names.AttrTransitGatewayAttachmentID) - if plan.IsKnown() { - return d.ForceNew(names.AttrTransitGatewayAttachmentID) - } - - rawState := d.GetRawState() - if rawState.IsNull() || !rawState.IsKnown() { - return d.ForceNew(names.AttrTransitGatewayAttachmentID) - } - - state := rawState.GetAttr(names.AttrTransitGatewayAttachmentID) - if !state.IsKnown() { - return d.ForceNew(names.AttrTransitGatewayAttachmentID) - } - - return nil - }, - ), } } diff --git a/internal/service/ec2/transitgateway_route_table_association_test.go b/internal/service/ec2/transitgateway_route_table_association_test.go index 454a82b910e3..9127862709ff 100644 --- a/internal/service/ec2/transitgateway_route_table_association_test.go +++ b/internal/service/ec2/transitgateway_route_table_association_test.go @@ -97,6 +97,49 @@ func testAccTransitGatewayRouteTableAssociation_disappears(t *testing.T, semapho }) } +func testAccTransitGatewayRouteTableAssociation_attachmentChange(t *testing.T, semaphore tfsync.Semaphore) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := acctest.Context(t) + var v awstypes.TransitGatewayRouteTableAssociation + resourceName := "aws_ec2_transit_gateway_route_table_association.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheckTransitGatewaySynchronize(t, semaphore) + acctest.PreCheck(ctx, t) + testAccPreCheckTransitGateway(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTransitGatewayRouteTableAssociationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayRouteTableAssociationConfig_attachmentChange(rName, 0), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayRouteTableAssociationExists(ctx, resourceName, &v), + resource.TestCheckResourceAttrPair(resourceName, names.AttrTransitGatewayAttachmentID, "aws_ec2_transit_gateway_vpc_attachment.test.0", names.AttrID), + ), + }, + { + Config: testAccTransitGatewayRouteTableAssociationConfig_attachmentChange(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayRouteTableAssociationExists(ctx, resourceName, &v), + resource.TestCheckResourceAttrPair(resourceName, names.AttrTransitGatewayAttachmentID, "aws_ec2_transit_gateway_vpc_attachment.test.1", names.AttrID), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionReplace), + }, + }, + }, + }, + }) +} + func testAccTransitGatewayRouteTableAssociation_replaceExistingAssociation(t *testing.T, semaphore tfsync.Semaphore) { if testing.Short() { t.Skip("skipping long-running test in short mode") @@ -142,7 +185,7 @@ func testAccTransitGatewayRouteTableAssociation_replaceExistingAssociation(t *te }) } -func testAccTransitGatewayRouteTableAssociation_notRecreatedDXGateway(t *testing.T, semaphore tfsync.Semaphore) { +func testAccTransitGatewayRouteTableAssociation_recreatedDXGateway(t *testing.T, semaphore tfsync.Semaphore) { if testing.Short() { t.Skip("skipping long-running test in short mode") } @@ -174,11 +217,9 @@ func testAccTransitGatewayRouteTableAssociation_notRecreatedDXGateway(t *testing Check: resource.ComposeTestCheckFunc( testAccCheckTransitGatewayRouteTableAssociationExists(ctx, resourceName, &a), ), - // Calling a NotRecreated function, such as testAccCheckRouteTableAssociationNotRecreated, as is typical, - // won't work here because the recreated resource ID will be the same, because it's two IDs pegged together. ConfigPlanChecks: resource.ConfigPlanChecks{ PreApply: []plancheck.PlanCheck{ - plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate), + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionReplace), }, }, }, @@ -344,3 +385,70 @@ resource "aws_ec2_transit_gateway_route_table_propagation" "test" { } `, rName, rBGPASN, strings.Join(allowedPrefixes, `", "`)) } + +func testAccTransitGatewayRouteTableAssociationConfig_attachmentChange(rName string, attachmentIndex int) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + count = 2 + + cidr_block = "10.${count.index}.0.0/16" + + tags = { + Name = "%[1]s-${count.index}" + } +} + +resource "aws_subnet" "test" { + count = 2 + + vpc_id = aws_vpc.test[count.index].id + cidr_block = "10.${count.index}.0.0/24" + availability_zone = data.aws_availability_zones.available.names[0] + + tags = { + Name = "%[1]s-${count.index}" + } +} + +data "aws_availability_zones" "available" { + state = "available" + + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + +resource "aws_ec2_transit_gateway" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_vpc_attachment" "test" { + count = 2 + + subnet_ids = [aws_subnet.test[count.index].id] + transit_gateway_default_route_table_association = false + transit_gateway_id = aws_ec2_transit_gateway.test.id + vpc_id = aws_vpc.test[count.index].id + + tags = { + Name = "%[1]s-${count.index}" + } +} + +resource "aws_ec2_transit_gateway_route_table" "test" { + transit_gateway_id = aws_ec2_transit_gateway.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_route_table_association" "test" { + transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.test[%[2]d].id + transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.test.id +} +`, rName, attachmentIndex) +} diff --git a/internal/service/ec2/transitgateway_route_table_propagation.go b/internal/service/ec2/transitgateway_route_table_propagation.go index 313e59d48e0b..4da71edad6c9 100644 --- a/internal/service/ec2/transitgateway_route_table_propagation.go +++ b/internal/service/ec2/transitgateway_route_table_propagation.go @@ -13,7 +13,6 @@ import ( "github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/hashicorp/aws-sdk-go-base/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -45,6 +44,7 @@ func resourceTransitGatewayRouteTablePropagation() *schema.Resource { names.AttrTransitGatewayAttachmentID: { Type: schema.TypeString, Required: true, + ForceNew: true, ValidateFunc: validation.NoZeroValues, }, "transit_gateway_route_table_id": { @@ -54,42 +54,6 @@ func resourceTransitGatewayRouteTablePropagation() *schema.Resource { ValidateFunc: validation.NoZeroValues, }, }, - CustomizeDiff: customdiff.Sequence( - func(_ context.Context, d *schema.ResourceDiff, meta any) error { - if !d.HasChange(names.AttrTransitGatewayAttachmentID) { - return nil - } - - // See https://github.com/hashicorp/terraform-provider-aws/issues/30085 - // In all cases, changes should force new except: - // o is not empty string AND - // n is empty string AND - // plan is unknown AND - // state is known - o, n := d.GetChange(names.AttrTransitGatewayAttachmentID) - if o.(string) == "" || n.(string) != "" { - return d.ForceNew(names.AttrTransitGatewayAttachmentID) - } - - rawPlan := d.GetRawPlan() - plan := rawPlan.GetAttr(names.AttrTransitGatewayAttachmentID) - if plan.IsKnown() { - return d.ForceNew(names.AttrTransitGatewayAttachmentID) - } - - rawState := d.GetRawState() - if rawState.IsNull() || !rawState.IsKnown() { - return d.ForceNew(names.AttrTransitGatewayAttachmentID) - } - - state := rawState.GetAttr(names.AttrTransitGatewayAttachmentID) - if !state.IsKnown() { - return d.ForceNew(names.AttrTransitGatewayAttachmentID) - } - - return nil - }, - ), } } diff --git a/internal/service/ec2/transitgateway_route_table_propagation_test.go b/internal/service/ec2/transitgateway_route_table_propagation_test.go index d9ac73a53345..fa04407bf59c 100644 --- a/internal/service/ec2/transitgateway_route_table_propagation_test.go +++ b/internal/service/ec2/transitgateway_route_table_propagation_test.go @@ -87,7 +87,46 @@ func testAccTransitGatewayRouteTablePropagation_disappears(t *testing.T, semapho }) } -func testAccTransitGatewayRouteTablePropagtion_notRecreatedDXGateway(t *testing.T, semaphore tfsync.Semaphore) { +func testAccTransitGatewayRouteTablePropagation_attachmentChange(t *testing.T, semaphore tfsync.Semaphore) { + ctx := acctest.Context(t) + var v awstypes.TransitGatewayRouteTablePropagation + resourceName := "aws_ec2_transit_gateway_route_table_propagation.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheckTransitGatewaySynchronize(t, semaphore) + acctest.PreCheck(ctx, t) + testAccPreCheckTransitGateway(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTransitGatewayRouteTablePropagationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayRouteTablePropagationConfig_attachmentChange(rName, 0), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayRouteTablePropagationExists(ctx, resourceName, &v), + resource.TestCheckResourceAttrPair(resourceName, names.AttrTransitGatewayAttachmentID, "aws_ec2_transit_gateway_vpc_attachment.test.0", names.AttrID), + ), + }, + { + Config: testAccTransitGatewayRouteTablePropagationConfig_attachmentChange(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayRouteTablePropagationExists(ctx, resourceName, &v), + resource.TestCheckResourceAttrPair(resourceName, names.AttrTransitGatewayAttachmentID, "aws_ec2_transit_gateway_vpc_attachment.test.1", names.AttrID), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionReplace), + }, + }, + }, + }, + }) +} + +func testAccTransitGatewayRouteTablePropagtion_recreatedDXGateway(t *testing.T, semaphore tfsync.Semaphore) { if testing.Short() { t.Skip("skipping long-running test in short mode") } @@ -119,11 +158,9 @@ func testAccTransitGatewayRouteTablePropagtion_notRecreatedDXGateway(t *testing. Check: resource.ComposeTestCheckFunc( testAccCheckTransitGatewayRouteTablePropagationExists(ctx, resourceName, &a), ), - // Calling a NotRecreated function, such as testAccCheckRouteTableAssociationNotRecreated, as is typical, - // won't work here because the recreated resource ID will be the same, because it's two IDs pegged together. ConfigPlanChecks: resource.ConfigPlanChecks{ PreApply: []plancheck.PlanCheck{ - plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate), + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionReplace), }, }, }, @@ -262,3 +299,69 @@ resource "aws_ec2_transit_gateway_route_table_propagation" "test" { } `, rName, rBGPASN, strings.Join(allowedPrefixes, `", "`)) } + +func testAccTransitGatewayRouteTablePropagationConfig_attachmentChange(rName string, attachmentIndex int) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + count = 2 + + cidr_block = "10.${count.index}.0.0/16" + + tags = { + Name = "%[1]s-${count.index}" + } +} + +resource "aws_subnet" "test" { + count = 2 + + vpc_id = aws_vpc.test[count.index].id + cidr_block = "10.${count.index}.0.0/24" + availability_zone = data.aws_availability_zones.available.names[0] + + tags = { + Name = "%[1]s-${count.index}" + } +} + +data "aws_availability_zones" "available" { + state = "available" + + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + +resource "aws_ec2_transit_gateway" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_vpc_attachment" "test" { + count = 2 + + subnet_ids = [aws_subnet.test[count.index].id] + transit_gateway_id = aws_ec2_transit_gateway.test.id + vpc_id = aws_vpc.test[count.index].id + + tags = { + Name = "%[1]s-${count.index}" + } +} + +resource "aws_ec2_transit_gateway_route_table" "test" { + transit_gateway_id = aws_ec2_transit_gateway.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_route_table_propagation" "test" { + transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.test[%[2]d].id + transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.test.id +} +`, rName, attachmentIndex) +} diff --git a/internal/service/ec2/transitgateway_test.go b/internal/service/ec2/transitgateway_test.go index 3e7e05534649..742315607124 100644 --- a/internal/service/ec2/transitgateway_test.go +++ b/internal/service/ec2/transitgateway_test.go @@ -145,12 +145,14 @@ func TestAccTransitGateway_serial(t *testing.T) { acctest.CtBasic: testAccTransitGatewayRouteTableAssociation_basic, acctest.CtDisappears: testAccTransitGatewayRouteTableAssociation_disappears, "replaceExistingAssociation": testAccTransitGatewayRouteTableAssociation_replaceExistingAssociation, - "notRecreatedDXGateway": testAccTransitGatewayRouteTableAssociation_notRecreatedDXGateway, + "attachmentChange": testAccTransitGatewayRouteTableAssociation_attachmentChange, + "recreatedDXGateway": testAccTransitGatewayRouteTableAssociation_recreatedDXGateway, }, "RouteTablePropagation": { - acctest.CtBasic: testAccTransitGatewayRouteTablePropagation_basic, - acctest.CtDisappears: testAccTransitGatewayRouteTablePropagation_disappears, - "notRecreatedDXGateway": testAccTransitGatewayRouteTablePropagtion_notRecreatedDXGateway, + acctest.CtBasic: testAccTransitGatewayRouteTablePropagation_basic, + acctest.CtDisappears: testAccTransitGatewayRouteTablePropagation_disappears, + "attachmentChange": testAccTransitGatewayRouteTablePropagation_attachmentChange, + "recreatedDXGateway": testAccTransitGatewayRouteTablePropagtion_recreatedDXGateway, }, "VPCAttachment": { acctest.CtBasic: testAccTransitGatewayVPCAttachment_basic, diff --git a/website/docs/d/ec2_transit_gateway_dx_gateway_attachment.html.markdown b/website/docs/d/ec2_transit_gateway_dx_gateway_attachment.html.markdown index afd93419e51a..e2c89e3568ed 100644 --- a/website/docs/d/ec2_transit_gateway_dx_gateway_attachment.html.markdown +++ b/website/docs/d/ec2_transit_gateway_dx_gateway_attachment.html.markdown @@ -10,6 +10,8 @@ description: |- Get information on an EC2 Transit Gateway's attachment to a Direct Connect Gateway. +!> **Warning:** Using the `aws_ec2_transit_gateway_dx_gateway_attachment` data source in combination with `aws_ec2_transit_gateway_route_table_propagation` or `aws_ec2_transit_gateway_route_table_association` may result in lost connectivity due to unnecessary resource re-creation. To avoid this, use the `transit_gateway_attachment_id` attribute directly from the `aws_dx_gateway_association` resource. For example, `transit_gateway_attachment_id = aws_dx_gateway_association.example.transit_gateway_attachment_id`. + ## Example Usage ### By Transit Gateway and Direct Connect Gateway Identifiers diff --git a/website/docs/d/ec2_transit_gateway_vpc_attachment.html.markdown b/website/docs/d/ec2_transit_gateway_vpc_attachment.html.markdown index 35852ec457b8..d9f5fc00cb1c 100644 --- a/website/docs/d/ec2_transit_gateway_vpc_attachment.html.markdown +++ b/website/docs/d/ec2_transit_gateway_vpc_attachment.html.markdown @@ -10,6 +10,8 @@ description: |- Get information on an EC2 Transit Gateway VPC Attachment. +!> **Warning:** Using the `aws_ec2_transit_gateway_vpc_attachment` data source in combination with `aws_ec2_transit_gateway_route_table_propagation` or `aws_ec2_transit_gateway_route_table_association` may result in lost connectivity due to unnecessary resource re-creation. To avoid this, use the `id` attribute directly from the `aws_ec2_transit_gateway_vpc_attachment` _resource_. For example, `transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.example.id`. + ## Example Usage ### By Filter diff --git a/website/docs/r/ec2_transit_gateway_route_table_association.html.markdown b/website/docs/r/ec2_transit_gateway_route_table_association.html.markdown index cdb3d5fe382a..1df9dd4e5220 100644 --- a/website/docs/r/ec2_transit_gateway_route_table_association.html.markdown +++ b/website/docs/r/ec2_transit_gateway_route_table_association.html.markdown @@ -19,6 +19,79 @@ resource "aws_ec2_transit_gateway_route_table_association" "example" { } ``` +### Direct Connect Gateway Association + +When associating a Direct Connect Gateway attachment, reference the `transit_gateway_attachment_id` attribute directly from the `aws_dx_gateway_association` resource (available in v6.5.0+): + +```terraform +resource "aws_dx_gateway" "example" { + name = "example" + amazon_side_asn = 64512 +} + +resource "aws_ec2_transit_gateway" "example" { + description = "example" +} + +resource "aws_dx_gateway_association" "example" { + dx_gateway_id = aws_dx_gateway.example.id + associated_gateway_id = aws_ec2_transit_gateway.example.id + + allowed_prefixes = [ + "10.0.0.0/16", + ] +} + +resource "aws_ec2_transit_gateway_route_table" "example" { + transit_gateway_id = aws_ec2_transit_gateway.example.id +} + +# Correct: Reference the attachment ID directly from the association resource +resource "aws_ec2_transit_gateway_route_table_association" "example" { + transit_gateway_attachment_id = aws_dx_gateway_association.example.transit_gateway_attachment_id + transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.example.id +} +``` + +~> **NOTE:** Avoid using the `aws_ec2_transit_gateway_dx_gateway_attachment` data source to retrieve the attachment ID, as this can cause unnecessary resource recreation when unrelated attributes of the Direct Connect Gateway association change (such as `allowed_prefixes`). Always reference the `transit_gateway_attachment_id` attribute directly from the `aws_dx_gateway_association` resource when available. + +### VPC Attachment Association + +For VPC attachments, always reference the attachment resource's `id` attribute directly. Avoid using data sources or lifecycle rules that might cause the attachment ID to become unknown during planning: + +```terraform +resource "aws_vpc" "example" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_subnet" "example" { + vpc_id = aws_vpc.example.id + cidr_block = "10.0.1.0/24" +} + +resource "aws_ec2_transit_gateway" "example" { + description = "example" +} + +resource "aws_ec2_transit_gateway_vpc_attachment" "example" { + subnet_ids = [aws_subnet.example.id] + transit_gateway_id = aws_ec2_transit_gateway.example.id + vpc_id = aws_vpc.example.id +} + +resource "aws_ec2_transit_gateway_route_table" "example" { + transit_gateway_id = aws_ec2_transit_gateway.example.id +} + +# Correct: Reference the VPC attachment ID directly +resource "aws_ec2_transit_gateway_route_table_association" "example" { + transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.example.id + transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.example.id +} +``` + +~> **NOTE:** When the `transit_gateway_attachment_id` changes (for example, when a VPC attachment is replaced), this resource will be recreated. This is the correct behavior to maintain consistency between the attachment and its route table association. + ## Argument Reference This resource supports the following arguments: diff --git a/website/docs/r/ec2_transit_gateway_route_table_propagation.html.markdown b/website/docs/r/ec2_transit_gateway_route_table_propagation.html.markdown index a6139698b0e9..0ecef0cce785 100644 --- a/website/docs/r/ec2_transit_gateway_route_table_propagation.html.markdown +++ b/website/docs/r/ec2_transit_gateway_route_table_propagation.html.markdown @@ -19,6 +19,79 @@ resource "aws_ec2_transit_gateway_route_table_propagation" "example" { } ``` +### Direct Connect Gateway Propagation + +When propagating routes from a Direct Connect Gateway attachment, reference the `transit_gateway_attachment_id` attribute directly from the `aws_dx_gateway_association` resource (available in v6.5.0+): + +```terraform +resource "aws_dx_gateway" "example" { + name = "example" + amazon_side_asn = 64512 +} + +resource "aws_ec2_transit_gateway" "example" { + description = "example" +} + +resource "aws_dx_gateway_association" "example" { + dx_gateway_id = aws_dx_gateway.example.id + associated_gateway_id = aws_ec2_transit_gateway.example.id + + allowed_prefixes = [ + "10.0.0.0/16", + ] +} + +resource "aws_ec2_transit_gateway_route_table" "example" { + transit_gateway_id = aws_ec2_transit_gateway.example.id +} + +# Correct: Reference the attachment ID directly from the association resource +resource "aws_ec2_transit_gateway_route_table_propagation" "example" { + transit_gateway_attachment_id = aws_dx_gateway_association.example.transit_gateway_attachment_id + transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.example.id +} +``` + +~> **NOTE:** Avoid using the `aws_ec2_transit_gateway_dx_gateway_attachment` data source to retrieve the attachment ID, as this can cause unnecessary resource recreation when unrelated attributes of the Direct Connect Gateway association change (such as `allowed_prefixes`). Always reference the `transit_gateway_attachment_id` attribute directly from the `aws_dx_gateway_association` resource when available. + +### VPC Attachment Propagation + +For VPC attachments, always reference the attachment resource's `id` attribute directly. Avoid using data sources or lifecycle rules that might cause the attachment ID to become unknown during planning: + +```terraform +resource "aws_vpc" "example" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_subnet" "example" { + vpc_id = aws_vpc.example.id + cidr_block = "10.0.1.0/24" +} + +resource "aws_ec2_transit_gateway" "example" { + description = "example" +} + +resource "aws_ec2_transit_gateway_vpc_attachment" "example" { + subnet_ids = [aws_subnet.example.id] + transit_gateway_id = aws_ec2_transit_gateway.example.id + vpc_id = aws_vpc.example.id +} + +resource "aws_ec2_transit_gateway_route_table" "example" { + transit_gateway_id = aws_ec2_transit_gateway.example.id +} + +# Correct: Reference the VPC attachment ID directly +resource "aws_ec2_transit_gateway_route_table_propagation" "example" { + transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.example.id + transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.example.id +} +``` + +~> **NOTE:** When the `transit_gateway_attachment_id` changes (for example, when a VPC attachment is replaced), this resource will be recreated. This is the correct behavior to maintain consistency between the attachment and its route table propagation. + ## Argument Reference This resource supports the following arguments: