From 54ded390d012f41b91dd15194b97c535de1eaa09 Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Mon, 29 Mar 2021 18:34:35 -0400 Subject: [PATCH] resource/aws_ec2_transit_gateway_route_table_propagation: Wait for enable and disable operations to complete Reference: https://github.com/hashicorp/terraform-provider-aws/issues/14043 Reference: https://github.com/hashicorp/terraform-provider-aws/issues/16796 The waiter should help prevent the read-after-create eventual consistency issue, but also added the `d.IsNewResource()` checks to ensure the confusing Terraform CLI error is prevented. Output from acceptance testing in AWS Commercial: ``` --- PASS: TestAccAWSEc2TransitGatewayRouteTablePropagation_basic (385.06s) ``` Output from acceptance testing in AWS GovCloud (US): ``` --- PASS: TestAccAWSEc2TransitGatewayRouteTablePropagation_basic (348.55s) ``` --- .changelog/pending.txt | 3 ++ aws/internal/service/ec2/finder/finder.go | 35 ++++++++++++++++ aws/internal/service/ec2/waiter/status.go | 16 +++++++ aws/internal/service/ec2/waiter/waiter.go | 42 +++++++++++++++++++ ...transit_gateway_route_table_propagation.go | 17 +++++++- ...it_gateway_route_table_propagation_test.go | 5 +++ 6 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 .changelog/pending.txt diff --git a/.changelog/pending.txt b/.changelog/pending.txt new file mode 100644 index 00000000000..3b7dec11347 --- /dev/null +++ b/.changelog/pending.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/aws_ec2_transit_gateway_route_table_propagation: Wait for enable and disable operations to complete +``` diff --git a/aws/internal/service/ec2/finder/finder.go b/aws/internal/service/ec2/finder/finder.go index cbc272faeb1..8d960821c3b 100644 --- a/aws/internal/service/ec2/finder/finder.go +++ b/aws/internal/service/ec2/finder/finder.go @@ -351,6 +351,41 @@ func TransitGatewayPrefixListReferenceByID(conn *ec2.EC2, resourceID string) (*e return TransitGatewayPrefixListReference(conn, transitGatewayRouteTableID, prefixListID) } +func TransitGatewayRouteTablePropagation(conn *ec2.EC2, transitGatewayRouteTableID string, transitGatewayAttachmentID string) (*ec2.TransitGatewayRouteTablePropagation, error) { + input := &ec2.GetTransitGatewayRouteTablePropagationsInput{ + Filters: []*ec2.Filter{ + { + Name: aws.String("transit-gateway-attachment-id"), + Values: aws.StringSlice([]string{transitGatewayAttachmentID}), + }, + }, + TransitGatewayRouteTableId: aws.String(transitGatewayRouteTableID), + } + + var result *ec2.TransitGatewayRouteTablePropagation + + err := conn.GetTransitGatewayRouteTablePropagationsPages(input, func(page *ec2.GetTransitGatewayRouteTablePropagationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, transitGatewayRouteTablePropagation := range page.TransitGatewayRouteTablePropagations { + if transitGatewayRouteTablePropagation == nil { + continue + } + + if aws.StringValue(transitGatewayRouteTablePropagation.TransitGatewayAttachmentId) == transitGatewayAttachmentID { + result = transitGatewayRouteTablePropagation + return false + } + } + + return !lastPage + }) + + return result, err +} + // VpcAttribute looks up a VPC attribute. func VpcAttribute(conn *ec2.EC2, vpcID string, attribute string) (*bool, error) { input := &ec2.DescribeVpcAttributeInput{ diff --git a/aws/internal/service/ec2/waiter/status.go b/aws/internal/service/ec2/waiter/status.go index 9e1e7e54fcd..f5d57b00850 100644 --- a/aws/internal/service/ec2/waiter/status.go +++ b/aws/internal/service/ec2/waiter/status.go @@ -331,6 +331,22 @@ func TransitGatewayPrefixListReferenceState(conn *ec2.EC2, transitGatewayRouteTa } } +func TransitGatewayRouteTablePropagationState(conn *ec2.EC2, transitGatewayRouteTableID string, transitGatewayAttachmentID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + transitGatewayRouteTablePropagation, err := finder.TransitGatewayRouteTablePropagation(conn, transitGatewayRouteTableID, transitGatewayAttachmentID) + + if err != nil { + return nil, "", err + } + + if transitGatewayRouteTablePropagation == nil { + return nil, "", nil + } + + return transitGatewayRouteTablePropagation, aws.StringValue(transitGatewayRouteTablePropagation.State), nil + } +} + // VpcAttribute fetches the Vpc and its attribute value func VpcAttribute(conn *ec2.EC2, id string, attribute string) resource.StateRefreshFunc { return func() (interface{}, string, error) { diff --git a/aws/internal/service/ec2/waiter/waiter.go b/aws/internal/service/ec2/waiter/waiter.go index a7385d40cef..18087f3fb98 100644 --- a/aws/internal/service/ec2/waiter/waiter.go +++ b/aws/internal/service/ec2/waiter/waiter.go @@ -374,6 +374,48 @@ func TransitGatewayPrefixListReferenceStateUpdated(conn *ec2.EC2, transitGateway return nil, err } +const ( + TransitGatewayRouteTablePropagationTimeout = 5 * time.Minute +) + +func TransitGatewayRouteTablePropagationStateEnabled(conn *ec2.EC2, transitGatewayRouteTableID string, transitGatewayAttachmentID string) (*ec2.TransitGatewayRouteTablePropagation, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.TransitGatewayPropagationStateEnabling}, + Target: []string{ec2.TransitGatewayPropagationStateEnabled}, + Timeout: TransitGatewayRouteTablePropagationTimeout, + Refresh: TransitGatewayRouteTablePropagationState(conn, transitGatewayRouteTableID, transitGatewayAttachmentID), + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*ec2.TransitGatewayRouteTablePropagation); ok { + return output, err + } + + return nil, err +} + +func TransitGatewayRouteTablePropagationStateDisabled(conn *ec2.EC2, transitGatewayRouteTableID string, transitGatewayAttachmentID string) (*ec2.TransitGatewayRouteTablePropagation, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.TransitGatewayPropagationStateDisabling}, + Target: []string{}, + Timeout: TransitGatewayRouteTablePropagationTimeout, + Refresh: TransitGatewayRouteTablePropagationState(conn, transitGatewayRouteTableID, transitGatewayAttachmentID), + } + + outputRaw, err := stateConf.WaitForState() + + if tfawserr.ErrCodeEquals(err, tfec2.ErrCodeInvalidRouteTableIDNotFound) { + return nil, nil + } + + if output, ok := outputRaw.(*ec2.TransitGatewayRouteTablePropagation); ok { + return output, err + } + + return nil, err +} + const ( VpcPropagationTimeout = 2 * time.Minute VpcAttributePropagationTimeout = 5 * time.Minute diff --git a/aws/resource_aws_ec2_transit_gateway_route_table_propagation.go b/aws/resource_aws_ec2_transit_gateway_route_table_propagation.go index 65cc21127d4..bb8d9259839 100644 --- a/aws/resource_aws_ec2_transit_gateway_route_table_propagation.go +++ b/aws/resource_aws_ec2_transit_gateway_route_table_propagation.go @@ -6,8 +6,11 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/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/waiter" ) func resourceAwsEc2TransitGatewayRouteTablePropagation() *schema.Resource { @@ -62,6 +65,10 @@ func resourceAwsEc2TransitGatewayRouteTablePropagationCreate(d *schema.ResourceD d.SetId(fmt.Sprintf("%s_%s", transitGatewayRouteTableID, transitGatewayAttachmentID)) + if _, err := waiter.TransitGatewayRouteTablePropagationStateEnabled(conn, transitGatewayRouteTableID, transitGatewayAttachmentID); err != nil { + return fmt.Errorf("error waiting for EC2 Transit Gateway Route Table (%s) propagation (%s) to enable: %w", transitGatewayRouteTableID, transitGatewayAttachmentID, err) + } + return resourceAwsEc2TransitGatewayRouteTablePropagationRead(d, meta) } @@ -75,7 +82,7 @@ func resourceAwsEc2TransitGatewayRouteTablePropagationRead(d *schema.ResourceDat transitGatewayPropagation, err := ec2DescribeTransitGatewayRouteTablePropagation(conn, transitGatewayRouteTableID, transitGatewayAttachmentID) - if isAWSErr(err, "InvalidRouteTableID.NotFound", "") { + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, tfec2.ErrCodeInvalidRouteTableIDNotFound) { log.Printf("[WARN] EC2 Transit Gateway Route Table (%s) not found, removing from state", transitGatewayRouteTableID) d.SetId("") return nil @@ -86,6 +93,10 @@ func resourceAwsEc2TransitGatewayRouteTablePropagationRead(d *schema.ResourceDat } if transitGatewayPropagation == nil { + if d.IsNewResource() { + return fmt.Errorf("error reading EC2 Transit Gateway Route Table (%s) Propagation (%s): not found after creation", transitGatewayRouteTableID, transitGatewayAttachmentID) + } + log.Printf("[WARN] EC2 Transit Gateway Route Table (%s) Propagation (%s) not found, removing from state", transitGatewayRouteTableID, transitGatewayAttachmentID) d.SetId("") return nil @@ -123,5 +134,9 @@ func resourceAwsEc2TransitGatewayRouteTablePropagationDelete(d *schema.ResourceD return fmt.Errorf("error disabling EC2 Transit Gateway Route Table (%s) Propagation (%s): %s", transitGatewayRouteTableID, transitGatewayAttachmentID, err) } + if _, err := waiter.TransitGatewayRouteTablePropagationStateDisabled(conn, transitGatewayRouteTableID, transitGatewayAttachmentID); err != nil { + return fmt.Errorf("error waiting for EC2 Transit Gateway Route Table (%s) propagation (%s) to disable: %w", transitGatewayRouteTableID, transitGatewayAttachmentID, err) + } + return nil } diff --git a/aws/resource_aws_ec2_transit_gateway_route_table_propagation_test.go b/aws/resource_aws_ec2_transit_gateway_route_table_propagation_test.go index f4bf3cc9e27..bc3b136e220 100644 --- a/aws/resource_aws_ec2_transit_gateway_route_table_propagation_test.go +++ b/aws/resource_aws_ec2_transit_gateway_route_table_propagation_test.go @@ -4,6 +4,7 @@ import ( "fmt" "testing" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" @@ -69,6 +70,10 @@ func testAccCheckAWSEc2TransitGatewayRouteTablePropagationExists(resourceName st return fmt.Errorf("EC2 Transit Gateway Route Table Propagation not found") } + if aws.StringValue(propagation.State) != ec2.TransitGatewayPropagationStateEnabled { + return fmt.Errorf("EC2 Transit Gateway Route Table Propagation not in enabled state: %s", aws.StringValue(propagation.State)) + } + *transitGatewayRouteTablePropagation = *propagation return nil