From 2e094957c187c7961cf0af65ee8c0716495803e4 Mon Sep 17 00:00:00 2001 From: Dave Kujawski Date: Tue, 18 May 2021 20:49:29 +0000 Subject: [PATCH 01/27] don't report diff when proposal is gone --- aws/resource_aws_dx_gateway_association_proposal.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/aws/resource_aws_dx_gateway_association_proposal.go b/aws/resource_aws_dx_gateway_association_proposal.go index 9716ff0c908e..3c626c4a6d57 100644 --- a/aws/resource_aws_dx_gateway_association_proposal.go +++ b/aws/resource_aws_dx_gateway_association_proposal.go @@ -32,7 +32,12 @@ func resourceAwsDxGatewayAssociationProposal() *schema.Resource { return false } - return proposal != nil && aws.StringValue(proposal.ProposalState) == directconnect.GatewayAssociationProposalStateRequested + if proposal == nil { + // Don't report as a diff when the proposal is gone. + return true + } + + return aws.StringValue(proposal.ProposalState) == directconnect.GatewayAssociationProposalStateRequested }), ), From 8c198a13354167c255af4f53d7c986dacdbcf8fb Mon Sep 17 00:00:00 2001 From: Dave Kujawski Date: Tue, 18 May 2021 23:28:28 +0000 Subject: [PATCH 02/27] Populate proposal state using found association Once the proposal has been accepted and deleted, locate the association, populate the proposal state to prevent recreating the proposal unnecessarily. --- ...rce_aws_dx_gateway_association_proposal.go | 94 +++++++++++++++---- 1 file changed, 74 insertions(+), 20 deletions(-) diff --git a/aws/resource_aws_dx_gateway_association_proposal.go b/aws/resource_aws_dx_gateway_association_proposal.go index 3c626c4a6d57..ae52d5c2a502 100644 --- a/aws/resource_aws_dx_gateway_association_proposal.go +++ b/aws/resource_aws_dx_gateway_association_proposal.go @@ -8,6 +8,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/directconnect" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -109,31 +110,52 @@ func resourceAwsDxGatewayAssociationProposalRead(d *schema.ResourceData, meta in } if proposal == nil { - log.Printf("[WARN] Direct Connect Gateway Association Proposal (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil - } + associatedGatewayId, ok := d.GetOk("associated_gateway_id") - if aws.StringValue(proposal.ProposalState) == directconnect.GatewayAssociationProposalStateDeleted { - log.Printf("[WARN] Direct Connect Gateway Association Proposal (%s) deleted, removing from state", d.Id()) - d.SetId("") - return nil - } + if !ok || associatedGatewayId == nil { + return fmt.Errorf("error reading Direct Connect Associated Gateway Id (%s): %s", d.Id(), err) + } - if proposal.AssociatedGateway == nil { - return fmt.Errorf("error reading Direct Connect Gateway Association Proposal (%s): missing associated gateway information", d.Id()) - } + assocRaw, state, err := getDxGatewayAssociation(conn, associatedGatewayId.(string))() - if err := d.Set("allowed_prefixes", flattenDirectConnectGatewayAssociationProposalAllowedPrefixes(proposal.RequestedAllowedPrefixesToDirectConnectGateway)); err != nil { - return fmt.Errorf("error setting allowed_prefixes: %s", err) - } + if err != nil { + return fmt.Errorf("error reading Direct Connect gateway association (%s): %s", d.Id(), err) + } + + if state == gatewayAssociationStateDeleted { + log.Printf("[WARN] Direct Connect gateway association (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + log.Printf("[INFO] Direct Connect Gateway Association Proposal (%s) has been accepted", d.Id()) + assoc := assocRaw.(*directconnect.GatewayAssociation) + d.Set("associated_gateway_id", assoc.AssociatedGateway.Id) + d.Set("dx_gateway_id", assoc.DirectConnectGatewayId) + d.Set("dx_gateway_owner_account_id", assoc.DirectConnectGatewayOwnerAccount) + } else { + + if aws.StringValue(proposal.ProposalState) == directconnect.GatewayAssociationProposalStateDeleted { + log.Printf("[WARN] Direct Connect Gateway Association Proposal (%s) deleted, removing from state", d.Id()) + d.SetId("") + return nil + } + + if proposal.AssociatedGateway == nil { + return fmt.Errorf("error reading Direct Connect Gateway Association Proposal (%s): missing associated gateway information", d.Id()) + } - d.Set("associated_gateway_id", proposal.AssociatedGateway.Id) - d.Set("associated_gateway_owner_account_id", proposal.AssociatedGateway.OwnerAccount) - d.Set("associated_gateway_type", proposal.AssociatedGateway.Type) - d.Set("dx_gateway_id", proposal.DirectConnectGatewayId) - d.Set("dx_gateway_owner_account_id", proposal.DirectConnectGatewayOwnerAccount) + if err := d.Set("allowed_prefixes", flattenDirectConnectGatewayAssociationProposalAllowedPrefixes(proposal.RequestedAllowedPrefixesToDirectConnectGateway)); err != nil { + return fmt.Errorf("error setting allowed_prefixes: %s", err) + } + + d.Set("associated_gateway_id", proposal.AssociatedGateway.Id) + d.Set("associated_gateway_owner_account_id", proposal.AssociatedGateway.OwnerAccount) + d.Set("associated_gateway_type", proposal.AssociatedGateway.Type) + d.Set("dx_gateway_id", proposal.DirectConnectGatewayId) + d.Set("dx_gateway_owner_account_id", proposal.DirectConnectGatewayOwnerAccount) + } return nil } @@ -228,3 +250,35 @@ func flattenDirectConnectGatewayAssociationProposalAllowedPrefixes(routeFilterPr return allowedPrefixes } + +func getDxGatewayAssociation(conn *directconnect.DirectConnect, associatedGatewayId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + resp, err := conn.DescribeDirectConnectGatewayAssociations(&directconnect.DescribeDirectConnectGatewayAssociationsInput{ + AssociatedGatewayId: aws.String(associatedGatewayId), + }) + if err != nil { + return nil, "", err + } + + n := len(resp.DirectConnectGatewayAssociations) + switch n { + case 0: + return "", gatewayAssociationStateDeleted, nil + + case 1: + assoc := resp.DirectConnectGatewayAssociations[0] + + if stateChangeError := aws.StringValue(assoc.StateChangeError); stateChangeError != "" { + id := dxGatewayAssociationId( + aws.StringValue(resp.DirectConnectGatewayAssociations[0].DirectConnectGatewayId), + aws.StringValue(resp.DirectConnectGatewayAssociations[0].AssociatedGateway.Id)) + log.Printf("[INFO] Direct Connect gateway association (%s) state change error: %s", id, stateChangeError) + } + + return assoc, aws.StringValue(assoc.AssociationState), nil + + default: + return nil, "", fmt.Errorf("Found %d Direct Connect gateway associations for %s, expected 1", n, associatedGatewayId) + } + } +} From 7f5ae58483e5c737905d66d619d618058792449b Mon Sep 17 00:00:00 2001 From: Dave Kujawski Date: Mon, 24 May 2021 15:43:38 +0000 Subject: [PATCH 03/27] WIP activate debug code path for testing --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 3f721524f186..b33c29c4b924 100644 --- a/main.go +++ b/main.go @@ -12,7 +12,7 @@ import ( func main() { var debugMode bool - flag.BoolVar(&debugMode, "debug", false, "set to true to run the provider with support for debuggers like delve") + flag.BoolVar(&debugMode, "debug", true, "set to true to run the provider with support for debuggers like delve") flag.Parse() opts := &plugin.ServeOpts{ProviderFunc: aws.Provider} From 19b40437567ca43132bb66a91bc3473e1a88dfd9 Mon Sep 17 00:00:00 2001 From: Dave Kujawski Date: Mon, 24 May 2021 15:45:16 +0000 Subject: [PATCH 04/27] collect state information from associated gateway to determine if proposal needs to be recreated. --- ...ource_aws_dx_gateway_association_proposal.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/aws/resource_aws_dx_gateway_association_proposal.go b/aws/resource_aws_dx_gateway_association_proposal.go index ae52d5c2a502..7764f816f9c5 100644 --- a/aws/resource_aws_dx_gateway_association_proposal.go +++ b/aws/resource_aws_dx_gateway_association_proposal.go @@ -34,7 +34,22 @@ func resourceAwsDxGatewayAssociationProposal() *schema.Resource { } if proposal == nil { - // Don't report as a diff when the proposal is gone. + // Don't report as a diff when the proposal is gone unless the association is gone too. + associatedGatewayId, ok := d.GetOk("associated_gateway_id") + + if !ok || associatedGatewayId == nil { + return false + } + + _, state, err := getDxGatewayAssociation(conn, associatedGatewayId.(string))() + + if err != nil { + return false + } + + if state == gatewayAssociationStateDeleted { + return false + } return true } From 363a76288d2e03a38e4d0aa7bab4d1f5ee34f619 Mon Sep 17 00:00:00 2001 From: Dave Kujawski Date: Mon, 24 May 2021 18:36:35 +0000 Subject: [PATCH 05/27] when associated gateway is not found or errors, assume we need the proposal --- aws/resource_aws_dx_gateway_association_proposal.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aws/resource_aws_dx_gateway_association_proposal.go b/aws/resource_aws_dx_gateway_association_proposal.go index 7764f816f9c5..01596db393df 100644 --- a/aws/resource_aws_dx_gateway_association_proposal.go +++ b/aws/resource_aws_dx_gateway_association_proposal.go @@ -128,12 +128,14 @@ func resourceAwsDxGatewayAssociationProposalRead(d *schema.ResourceData, meta in associatedGatewayId, ok := d.GetOk("associated_gateway_id") if !ok || associatedGatewayId == nil { + d.SetId("") return fmt.Errorf("error reading Direct Connect Associated Gateway Id (%s): %s", d.Id(), err) } assocRaw, state, err := getDxGatewayAssociation(conn, associatedGatewayId.(string))() if err != nil { + d.SetId("") return fmt.Errorf("error reading Direct Connect gateway association (%s): %s", d.Id(), err) } From 76c016d912c830fc8a6f0cda62f3d2e82260c261 Mon Sep 17 00:00:00 2001 From: Dave Kujawski Date: Wed, 9 Jun 2021 20:38:49 +0000 Subject: [PATCH 06/27] provide some commentary for clarity --- aws/resource_aws_dx_gateway_association_proposal.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/aws/resource_aws_dx_gateway_association_proposal.go b/aws/resource_aws_dx_gateway_association_proposal.go index 01596db393df..3ccbf3192526 100644 --- a/aws/resource_aws_dx_gateway_association_proposal.go +++ b/aws/resource_aws_dx_gateway_association_proposal.go @@ -145,6 +145,9 @@ func resourceAwsDxGatewayAssociationProposalRead(d *schema.ResourceData, meta in return nil } + // once accepted, AWS will delete the proposal after after some time (days?) + // in this case we don't need to create a new proposal, use metadata from the association + // to artificially populate the missing proposal in state as if it was still there. log.Printf("[INFO] Direct Connect Gateway Association Proposal (%s) has been accepted", d.Id()) assoc := assocRaw.(*directconnect.GatewayAssociation) d.Set("associated_gateway_id", assoc.AssociatedGateway.Id) From 1f410034d67b57c6254b10cf4c45a497eb91232f Mon Sep 17 00:00:00 2001 From: Dave Kujawski Date: Wed, 9 Jun 2021 20:44:21 +0000 Subject: [PATCH 07/27] add test to cover proposal end of life --- ...ws_dx_gateway_association_proposal_test.go | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/aws/resource_aws_dx_gateway_association_proposal_test.go b/aws/resource_aws_dx_gateway_association_proposal_test.go index bf834a4897af..c53cb4276e42 100644 --- a/aws/resource_aws_dx_gateway_association_proposal_test.go +++ b/aws/resource_aws_dx_gateway_association_proposal_test.go @@ -185,6 +185,34 @@ func TestAccAwsDxGatewayAssociationProposal_disappears(t *testing.T) { }) } +func TestAccAwsDxGatewayAssociationProposal_endOfLife(t *testing.T) { + var proposal1 directconnect.GatewayAssociationProposal + var providers []*schema.Provider + rBgpAsn := acctest.RandIntRange(64512, 65534) + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_dx_gateway_association_proposal.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccAlternateAccountPreCheck(t) + }, + ErrorCheck: testAccErrorCheck(t, directconnect.EndpointsID), + ProviderFactories: testAccProviderFactoriesAlternate(&providers), + CheckDestroy: testAccCheckAwsDxGatewayAssociationProposalDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDxGatewayAssociationProposalConfig_endOfLife(rName, rBgpAsn), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsDxGatewayAssociationProposalExists(resourceName, &proposal1), + testAccCheckAwsDxGatewayAssociationProposalAccepted(resourceName), + testAccCheckAwsDxGatewayAssociationProposalDisappears(&proposal1), + ), + }, + }, + }) +} + func TestAccAwsDxGatewayAssociationProposal_AllowedPrefixes(t *testing.T) { var proposal1, proposal2 directconnect.GatewayAssociationProposal var providers []*schema.Provider @@ -336,6 +364,20 @@ resource "aws_dx_gateway_association_proposal" "test" { ` } +func testAccDxGatewayAssociationProposalConfig_endOfLife(rName string, rBgpAsn int) string { + return testAccDxGatewayAssociationProposalConfig_basicVpnGateway(rName, rBgpAsn) + ` +data "aws_caller_identity" "current" {} + +resource "aws_dx_gateway_association" "test" { +provider = "awsalternate" + +proposal_id = aws_dx_gateway_association_proposal.test.id +dx_gateway_id = aws_dx_gateway.test.id +associated_gateway_owner_account_id = data.aws_caller_identity.current.account_id +} +` +} + func testAccDxGatewayAssociationProposalConfig_basicTransitGateway(rName string, rBgpAsn int) string { return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` resource "aws_dx_gateway" "test" { @@ -385,3 +427,29 @@ resource "aws_dx_gateway_association_proposal" "test" { } ` } + +func testAccCheckAwsDxGatewayAssociationProposalAccepted(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) + } + + conn := testAccProvider.Meta().(*AWSClient).dxconn + + proposal, err := describeDirectConnectGatewayAssociationProposal(conn, rs.Primary.ID) + + if err != nil { + return err + } + + if proposal == nil { + return fmt.Errorf("Direct Connect Gateway Association Proposal (%s) not found", rs.Primary.ID) + } + + if aws.StringValue(proposal.ProposalState) != "accepted" { + return fmt.Errorf("Direct Connect Gateway Association Proposal (%s) not accepted", rs.Primary.ID) + } + return nil + } +} From 69feb2ffbe1c3ed80e3753ab00e4086560b8bc3d Mon Sep 17 00:00:00 2001 From: Dave Kujawski Date: Wed, 9 Jun 2021 21:55:31 +0000 Subject: [PATCH 08/27] reset debug mode default --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index b33c29c4b924..3f721524f186 100644 --- a/main.go +++ b/main.go @@ -12,7 +12,7 @@ import ( func main() { var debugMode bool - flag.BoolVar(&debugMode, "debug", true, "set to true to run the provider with support for debuggers like delve") + flag.BoolVar(&debugMode, "debug", false, "set to true to run the provider with support for debuggers like delve") flag.Parse() opts := &plugin.ServeOpts{ProviderFunc: aws.Provider} From 69767a47896686ed1f4c4ed3a729009d0cc11035 Mon Sep 17 00:00:00 2001 From: Dave Kujawski Date: Mon, 21 Jun 2021 15:12:03 -0700 Subject: [PATCH 09/27] allow import dx gateway assoc poroposal after EOL accept an import string composed of the proposal id, dx gateway id, and target associated gateway id when the proposal has been removed by AWS due to it being accepted. --- ...rce_aws_dx_gateway_association_proposal.go | 64 +++++++++++++++- ...ws_dx_gateway_association_proposal_test.go | 75 ++++++++++++------- 2 files changed, 112 insertions(+), 27 deletions(-) diff --git a/aws/resource_aws_dx_gateway_association_proposal.go b/aws/resource_aws_dx_gateway_association_proposal.go index 3ccbf3192526..18bd1a6184fd 100644 --- a/aws/resource_aws_dx_gateway_association_proposal.go +++ b/aws/resource_aws_dx_gateway_association_proposal.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log" + "strings" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/directconnect" @@ -18,7 +19,7 @@ func resourceAwsDxGatewayAssociationProposal() *schema.Resource { Read: resourceAwsDxGatewayAssociationProposalRead, Delete: resourceAwsDxGatewayAssociationProposalDelete, Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, + State: dxGatewayAssociationProposalImport, }, CustomizeDiff: customdiff.Sequence( @@ -271,6 +272,67 @@ func flattenDirectConnectGatewayAssociationProposalAllowedPrefixes(routeFilterPr return allowedPrefixes } +func dxGatewayAssociationProposalImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + // example: c2ede9b4-bbc6-4d33-923c-bc4feEXAMPLE/186c8187-36f4-472e-9268-107aaEXAMPLE/0d95359280EXAMPLE + + errStr := "unexpected format of import string (%q), expected PROPOSALID/DXGATEWAYID/TARGETGATEWAYID]*: %s" + importStr := d.Id() + log.Printf("[DEBUG] Validating import string %s", importStr) + + parts := strings.Split(strings.ToLower(importStr), "/") + if len(parts) < 1 { + return nil, fmt.Errorf(errStr, importStr, "too few parts") + } + var propId, dxgwId, gwId string + propId = parts[0] + + conn := meta.(*AWSClient).dxconn + if propId != "" { + p, err := describeDirectConnectGatewayAssociationProposal(conn, propId) + if err != nil { + return nil, err + } + if p != nil { + // proposal still exists normal import + return schema.ImportStatePassthrough(d, meta) + } + // proposal may not exist, but that's fine + } + d.SetId(propId) + + if len(parts) == 1 { + // requesting just the prop id + return schema.ImportStatePassthrough(d, meta) + } else if len(parts) < 3 { + return nil, fmt.Errorf(errStr, importStr, "too few parts") + } + + dxgwId = parts[1] + gwId = parts[2] + + if gwId != "" && dxgwId != "" { + input := directconnect.DescribeDirectConnectGatewayAssociationsInput{ + AssociatedGatewayId: aws.String(gwId), + DirectConnectGatewayId: aws.String(dxgwId), + } + resp, err := conn.DescribeDirectConnectGatewayAssociations(&input) + if err != nil { + return nil, err + } + + id := dxGatewayAssociationId(dxgwId, gwId) + if n := len(resp.DirectConnectGatewayAssociations); n != 1 { + return nil, fmt.Errorf("Found %d Direct Connect gateway associations for %s, expected 1", n, id) + } + d.Set("associated_gateway_id", gwId) + d.Set("dx_gateway_id", dxgwId) + } else { + return nil, fmt.Errorf(errStr, importStr, "missing parts") + } + + return []*schema.ResourceData{d}, nil +} + func getDxGatewayAssociation(conn *directconnect.DirectConnect, associatedGatewayId string) resource.StateRefreshFunc { return func() (interface{}, string, error) { resp, err := conn.DescribeDirectConnectGatewayAssociations(&directconnect.DescribeDirectConnectGatewayAssociationsInput{ diff --git a/aws/resource_aws_dx_gateway_association_proposal_test.go b/aws/resource_aws_dx_gateway_association_proposal_test.go index c53cb4276e42..cc66d2e9e461 100644 --- a/aws/resource_aws_dx_gateway_association_proposal_test.go +++ b/aws/resource_aws_dx_gateway_association_proposal_test.go @@ -3,6 +3,7 @@ package aws import ( "fmt" "log" + "strings" "testing" "github.com/aws/aws-sdk-go/aws" @@ -209,6 +210,18 @@ func TestAccAwsDxGatewayAssociationProposal_endOfLife(t *testing.T) { testAccCheckAwsDxGatewayAssociationProposalDisappears(&proposal1), ), }, + { + ResourceName: resourceName, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + return strings.Join([]string{ + aws.StringValue(proposal1.ProposalId), + aws.StringValue(proposal1.DirectConnectGatewayId), + aws.StringValue(proposal1.AssociatedGateway.Id), + }, "/"), nil + }, + ImportState: true, + ImportStateVerify: false, // proposal attributes not applicable when it does not exist + }, }, }) } @@ -327,6 +340,42 @@ func testAccCheckAwsDxGatewayAssociationProposalRecreated(i, j *directconnect.Ga } } +func testAccCheckAwsDxGatewayAssociationProposalAccepted(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) + } + + conn := testAccProvider.Meta().(*AWSClient).dxconn + + proposal, err := describeDirectConnectGatewayAssociationProposal(conn, rs.Primary.ID) + + if err != nil { + return err + } + + if proposal == nil { + return fmt.Errorf("Direct Connect Gateway Association Proposal (%s) not found", rs.Primary.ID) + } + + if aws.StringValue(proposal.ProposalState) != "accepted" { + return fmt.Errorf("Direct Connect Gateway Association Proposal (%s) not accepted", rs.Primary.ID) + } + return nil + } +} + +func testAccDxGatewayAssociationProposalImportStateIdFunc(proposal *directconnect.GatewayAssociationProposal) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + return strings.Join([]string{ + aws.StringValue(proposal.ProposalId), + aws.StringValue(proposal.DirectConnectGatewayId), + aws.StringValue(proposal.AssociatedGateway.Id), + }, "/"), nil + } +} + func testAccDxGatewayAssociationProposalConfigBase_vpnGateway(rName string, rBgpAsn int) string { return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` resource "aws_dx_gateway" "test" { @@ -427,29 +476,3 @@ resource "aws_dx_gateway_association_proposal" "test" { } ` } - -func testAccCheckAwsDxGatewayAssociationProposalAccepted(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) - } - - conn := testAccProvider.Meta().(*AWSClient).dxconn - - proposal, err := describeDirectConnectGatewayAssociationProposal(conn, rs.Primary.ID) - - if err != nil { - return err - } - - if proposal == nil { - return fmt.Errorf("Direct Connect Gateway Association Proposal (%s) not found", rs.Primary.ID) - } - - if aws.StringValue(proposal.ProposalState) != "accepted" { - return fmt.Errorf("Direct Connect Gateway Association Proposal (%s) not accepted", rs.Primary.ID) - } - return nil - } -} From 925c9dff1670a049b001a2b2fc322e4c555be0db Mon Sep 17 00:00:00 2001 From: Dave Kujawski Date: Tue, 22 Jun 2021 15:29:14 -0700 Subject: [PATCH 10/27] defer existance checks to Read function move proposal existance logic out of custom diff function and over into the Read function. add more debug logs to track when proposal id values exist --- ...rce_aws_dx_gateway_association_proposal.go | 78 ++++++++++++------- 1 file changed, 49 insertions(+), 29 deletions(-) diff --git a/aws/resource_aws_dx_gateway_association_proposal.go b/aws/resource_aws_dx_gateway_association_proposal.go index 18bd1a6184fd..588c5db09cc9 100644 --- a/aws/resource_aws_dx_gateway_association_proposal.go +++ b/aws/resource_aws_dx_gateway_association_proposal.go @@ -26,6 +26,15 @@ func resourceAwsDxGatewayAssociationProposal() *schema.Resource { // Accepting the proposal with overridden prefixes changes the returned RequestedAllowedPrefixesToDirectConnectGateway value (allowed_prefixes attribute). // We only want to force a new resource if this value changes and the current proposal state is "requested". customdiff.ForceNewIf("allowed_prefixes", func(_ context.Context, d *schema.ResourceDiff, meta interface{}) bool { + + log.Printf("[DEBUG] Checking diff for Direct Connect Gateway Association Proposal (%s) allowed_prefixes", d.Id()) + + if len(strings.Join(strings.Fields(d.Id()), "")) < 1 { + log.Printf("[WARN] Direct Connect Gateway Association Proposal Id not available (%s)", d.Id()) + // assume proposal is end-of-life, rely on Read func to test + return false + } + conn := meta.(*AWSClient).dxconn proposal, err := describeDirectConnectGatewayAssociationProposal(conn, d.Id()) @@ -35,23 +44,8 @@ func resourceAwsDxGatewayAssociationProposal() *schema.Resource { } if proposal == nil { - // Don't report as a diff when the proposal is gone unless the association is gone too. - associatedGatewayId, ok := d.GetOk("associated_gateway_id") - - if !ok || associatedGatewayId == nil { - return false - } - - _, state, err := getDxGatewayAssociation(conn, associatedGatewayId.(string))() - - if err != nil { - return false - } - - if state == gatewayAssociationStateDeleted { - return false - } - return true + // proposal maybe end-of-life and removed by AWS, existence checked in Read func + return false } return aws.StringValue(proposal.ProposalState) == directconnect.GatewayAssociationProposalStateRequested @@ -117,27 +111,50 @@ func resourceAwsDxGatewayAssociationProposalCreate(d *schema.ResourceData, meta } func resourceAwsDxGatewayAssociationProposalRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Read Direct Connect Gateway Association Proposal: %s", d.Id()) + + var proposal *directconnect.GatewayAssociationProposal + conn := meta.(*AWSClient).dxconn - proposal, err := describeDirectConnectGatewayAssociationProposal(conn, d.Id()) + trimmedId := strings.Join(strings.Fields(d.Id()), "") + if len(trimmedId) > 0 { + var err error + proposal, err = describeDirectConnectGatewayAssociationProposal(conn, d.Id()) - if err != nil { - return fmt.Errorf("error reading Direct Connect Gateway Association Proposal (%s): %s", d.Id(), err) + if err != nil { + return fmt.Errorf("error reading Direct Connect Gateway Association Proposal (%s): %s", d.Id(), err) + } + } else { + log.Printf("[WARN] Direct Connect Gateway Association Proposal Id not available (%s)", d.Id()) + d.SetId("0xda5e") // placeholder value } - if proposal == nil { - associatedGatewayId, ok := d.GetOk("associated_gateway_id") + if proposal == nil || len(trimmedId) < 1 { + log.Printf("[WARN] Direct Connect Gateway Association Proposal (%s) not found, checking for associated gateway", d.Id()) - if !ok || associatedGatewayId == nil { + var dxGatewayId string + if rawDGId, ok := d.GetOk("dx_gateway_id"); ok { + dxGatewayId = rawDGId.(string) + } else if rawDGId == nil { d.SetId("") - return fmt.Errorf("error reading Direct Connect Associated Gateway Id (%s): %s", d.Id(), err) + return fmt.Errorf("error reading dx_gateway_id (%s) from Proposal state", d.Id()) } - assocRaw, state, err := getDxGatewayAssociation(conn, associatedGatewayId.(string))() + var associatedGatewayId string + if rawAGId, ok := d.GetOk("associated_gateway_id"); ok { + associatedGatewayId = rawAGId.(string) + } else if rawAGId == nil { + d.SetId("") + return fmt.Errorf("error reading associated_gateway_id (%s) from Proposal state", d.Id()) + } + + log.Printf("[DEBUG] looking for Direct Connect Gateway Association using dx_gateway_id (%s) and associated_gateway_id (%s) to validate Proposal state data", dxGatewayId, associatedGatewayId) + assocRaw, state, err := getDxGatewayAssociation(conn, dxGatewayId, associatedGatewayId)() if err != nil { d.SetId("") - return fmt.Errorf("error reading Direct Connect gateway association (%s): %s", d.Id(), err) + return fmt.Errorf("error reading Direct Connect gateway association (%s) from Proposal state: %s", d.Id(), err) } if state == gatewayAssociationStateDeleted { @@ -149,7 +166,7 @@ func resourceAwsDxGatewayAssociationProposalRead(d *schema.ResourceData, meta in // once accepted, AWS will delete the proposal after after some time (days?) // in this case we don't need to create a new proposal, use metadata from the association // to artificially populate the missing proposal in state as if it was still there. - log.Printf("[INFO] Direct Connect Gateway Association Proposal (%s) has been accepted", d.Id()) + log.Printf("[INFO] Direct Connect Gateway Association Proposal (%s) has reached end-of-life and has been removed by AWS.", d.Id()) assoc := assocRaw.(*directconnect.GatewayAssociation) d.Set("associated_gateway_id", assoc.AssociatedGateway.Id) d.Set("dx_gateway_id", assoc.DirectConnectGatewayId) @@ -333,11 +350,14 @@ func dxGatewayAssociationProposalImport(d *schema.ResourceData, meta interface{} return []*schema.ResourceData{d}, nil } -func getDxGatewayAssociation(conn *directconnect.DirectConnect, associatedGatewayId string) resource.StateRefreshFunc { +func getDxGatewayAssociation(conn *directconnect.DirectConnect, dxGatewayId, associatedGatewayId string) resource.StateRefreshFunc { return func() (interface{}, string, error) { + resp, err := conn.DescribeDirectConnectGatewayAssociations(&directconnect.DescribeDirectConnectGatewayAssociationsInput{ - AssociatedGatewayId: aws.String(associatedGatewayId), + AssociatedGatewayId: &associatedGatewayId, + DirectConnectGatewayId: &dxGatewayId, }) + if err != nil { return nil, "", err } From 2710bca6c2c97790632aa87b8b3c6d989314b2dd Mon Sep 17 00:00:00 2001 From: Dave Kujawski Date: Tue, 22 Jun 2021 15:31:35 -0700 Subject: [PATCH 11/27] remove test function no longer needed --- ...esource_aws_dx_gateway_association_proposal_test.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/aws/resource_aws_dx_gateway_association_proposal_test.go b/aws/resource_aws_dx_gateway_association_proposal_test.go index cc66d2e9e461..ef7e36705e3d 100644 --- a/aws/resource_aws_dx_gateway_association_proposal_test.go +++ b/aws/resource_aws_dx_gateway_association_proposal_test.go @@ -366,16 +366,6 @@ func testAccCheckAwsDxGatewayAssociationProposalAccepted(resourceName string) re } } -func testAccDxGatewayAssociationProposalImportStateIdFunc(proposal *directconnect.GatewayAssociationProposal) resource.ImportStateIdFunc { - return func(s *terraform.State) (string, error) { - return strings.Join([]string{ - aws.StringValue(proposal.ProposalId), - aws.StringValue(proposal.DirectConnectGatewayId), - aws.StringValue(proposal.AssociatedGateway.Id), - }, "/"), nil - } -} - func testAccDxGatewayAssociationProposalConfigBase_vpnGateway(rName string, rBgpAsn int) string { return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` resource "aws_dx_gateway" "test" { From f7501bea86460ea0200d8760d4336ea8ab1bbbf8 Mon Sep 17 00:00:00 2001 From: Dave Kujawski Date: Wed, 23 Jun 2021 11:04:01 -0700 Subject: [PATCH 12/27] include allowed_prefixes when atrificially populating proposal --- aws/resource_aws_dx_gateway_association_proposal.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/aws/resource_aws_dx_gateway_association_proposal.go b/aws/resource_aws_dx_gateway_association_proposal.go index 588c5db09cc9..249ad9ff0bbe 100644 --- a/aws/resource_aws_dx_gateway_association_proposal.go +++ b/aws/resource_aws_dx_gateway_association_proposal.go @@ -31,6 +31,7 @@ func resourceAwsDxGatewayAssociationProposal() *schema.Resource { if len(strings.Join(strings.Fields(d.Id()), "")) < 1 { log.Printf("[WARN] Direct Connect Gateway Association Proposal Id not available (%s)", d.Id()) + log.Printf("[DEBUG] Direct Connect Gateway Association Proposal UpdatedKeys (%s)", strings.Join(d.UpdatedKeys(), "/")) // assume proposal is end-of-life, rely on Read func to test return false } @@ -127,7 +128,6 @@ func resourceAwsDxGatewayAssociationProposalRead(d *schema.ResourceData, meta in } } else { log.Printf("[WARN] Direct Connect Gateway Association Proposal Id not available (%s)", d.Id()) - d.SetId("0xda5e") // placeholder value } if proposal == nil || len(trimmedId) < 1 { @@ -158,7 +158,7 @@ func resourceAwsDxGatewayAssociationProposalRead(d *schema.ResourceData, meta in } if state == gatewayAssociationStateDeleted { - log.Printf("[WARN] Direct Connect gateway association (%s) not found, removing from state", d.Id()) + log.Printf("[WARN] Direct Connect gateway association (%s/%s/%s) not found, removing from state", d.Id(), dxGatewayId, associatedGatewayId) d.SetId("") return nil } @@ -168,10 +168,17 @@ func resourceAwsDxGatewayAssociationProposalRead(d *schema.ResourceData, meta in // to artificially populate the missing proposal in state as if it was still there. log.Printf("[INFO] Direct Connect Gateway Association Proposal (%s) has reached end-of-life and has been removed by AWS.", d.Id()) assoc := assocRaw.(*directconnect.GatewayAssociation) + + err = d.Set("allowed_prefixes", flattenDxRouteFilterPrefixes(assoc.AllowedPrefixesToDirectConnectGateway)) + if err != nil { + return fmt.Errorf("error setting allowed_prefixes: %s", err) + } + d.Set("associated_gateway_id", assoc.AssociatedGateway.Id) d.Set("dx_gateway_id", assoc.DirectConnectGatewayId) d.Set("dx_gateway_owner_account_id", assoc.DirectConnectGatewayOwnerAccount) } else { + log.Printf("[DEBUG] Direct Connect Gateway Association Proposal (%s) found, continuing as normal: %s", d.Id(), proposal.String()) if aws.StringValue(proposal.ProposalState) == directconnect.GatewayAssociationProposalStateDeleted { log.Printf("[WARN] Direct Connect Gateway Association Proposal (%s) deleted, removing from state", d.Id()) @@ -294,7 +301,7 @@ func dxGatewayAssociationProposalImport(d *schema.ResourceData, meta interface{} errStr := "unexpected format of import string (%q), expected PROPOSALID/DXGATEWAYID/TARGETGATEWAYID]*: %s" importStr := d.Id() - log.Printf("[DEBUG] Validating import string %s", importStr) + log.Printf("[DEBUG] Validating import string (%s) for Direct Connect Gateway Association Proposal", importStr) parts := strings.Split(strings.ToLower(importStr), "/") if len(parts) < 1 { From e8b8e03a18686dcfaac58fdafcd0053ce157f872 Mon Sep 17 00:00:00 2001 From: Dave Kujawski Date: Wed, 23 Jun 2021 11:04:19 -0700 Subject: [PATCH 13/27] include tgw for acc tests --- ...ws_dx_gateway_association_proposal_test.go | 60 ++++++++++++++++++- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/aws/resource_aws_dx_gateway_association_proposal_test.go b/aws/resource_aws_dx_gateway_association_proposal_test.go index ef7e36705e3d..3ef3932720cd 100644 --- a/aws/resource_aws_dx_gateway_association_proposal_test.go +++ b/aws/resource_aws_dx_gateway_association_proposal_test.go @@ -186,7 +186,7 @@ func TestAccAwsDxGatewayAssociationProposal_disappears(t *testing.T) { }) } -func TestAccAwsDxGatewayAssociationProposal_endOfLife(t *testing.T) { +func TestAccAwsDxGatewayAssociationProposal_endOfLifeVpn(t *testing.T) { var proposal1 directconnect.GatewayAssociationProposal var providers []*schema.Provider rBgpAsn := acctest.RandIntRange(64512, 65534) @@ -203,7 +203,47 @@ func TestAccAwsDxGatewayAssociationProposal_endOfLife(t *testing.T) { CheckDestroy: testAccCheckAwsDxGatewayAssociationProposalDestroy, Steps: []resource.TestStep{ { - Config: testAccDxGatewayAssociationProposalConfig_endOfLife(rName, rBgpAsn), + Config: testAccDxGatewayAssociationProposalConfig_endOfLifeVpn(rName, rBgpAsn), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsDxGatewayAssociationProposalExists(resourceName, &proposal1), + testAccCheckAwsDxGatewayAssociationProposalAccepted(resourceName), + testAccCheckAwsDxGatewayAssociationProposalDisappears(&proposal1), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + return strings.Join([]string{ + aws.StringValue(proposal1.ProposalId), + aws.StringValue(proposal1.DirectConnectGatewayId), + aws.StringValue(proposal1.AssociatedGateway.Id), + }, "/"), nil + }, + ImportState: true, + ImportStateVerify: false, // proposal attributes not applicable when it does not exist + }, + }, + }) +} + +func TestAccAwsDxGatewayAssociationProposal_endOfLifeTgw(t *testing.T) { + var proposal1 directconnect.GatewayAssociationProposal + var providers []*schema.Provider + rBgpAsn := acctest.RandIntRange(64512, 65534) + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_dx_gateway_association_proposal.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccAlternateAccountPreCheck(t) + }, + ErrorCheck: testAccErrorCheck(t, directconnect.EndpointsID), + ProviderFactories: testAccProviderFactoriesAlternate(&providers), + CheckDestroy: testAccCheckAwsDxGatewayAssociationProposalDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDxGatewayAssociationProposalConfig_endOfLifeTgw(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationProposalExists(resourceName, &proposal1), testAccCheckAwsDxGatewayAssociationProposalAccepted(resourceName), @@ -403,7 +443,7 @@ resource "aws_dx_gateway_association_proposal" "test" { ` } -func testAccDxGatewayAssociationProposalConfig_endOfLife(rName string, rBgpAsn int) string { +func testAccDxGatewayAssociationProposalConfig_endOfLifeVpn(rName string, rBgpAsn int) string { return testAccDxGatewayAssociationProposalConfig_basicVpnGateway(rName, rBgpAsn) + ` data "aws_caller_identity" "current" {} @@ -417,6 +457,20 @@ associated_gateway_owner_account_id = data.aws_caller_identity.current.account_i ` } +func testAccDxGatewayAssociationProposalConfig_endOfLifeTgw(rName string, rBgpAsn int) string { + return testAccDxGatewayAssociationProposalConfig_basicTransitGateway(rName, rBgpAsn) + ` +data "aws_caller_identity" "current" {} + +resource "aws_dx_gateway_association" "test" { +provider = "awsalternate" + +proposal_id = aws_dx_gateway_association_proposal.test.id +dx_gateway_id = aws_dx_gateway.test.id +associated_gateway_owner_account_id = data.aws_caller_identity.current.account_id +} +` +} + func testAccDxGatewayAssociationProposalConfig_basicTransitGateway(rName string, rBgpAsn int) string { return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` resource "aws_dx_gateway" "test" { From 10830b084b3822ff84d301bfe5ac3097c260b84c Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 7 Jul 2021 14:11:26 -0400 Subject: [PATCH 14/27] Add DirectConnect internal finder and waiter packages. --- .../service/directconnect/finder/finder.go | 44 ++++++++++++ .../service/directconnect/waiter/status.go | 25 +++++++ .../service/directconnect/waiter/waiter.go | 68 +++++++++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 aws/internal/service/directconnect/finder/finder.go create mode 100644 aws/internal/service/directconnect/waiter/status.go create mode 100644 aws/internal/service/directconnect/waiter/waiter.go diff --git a/aws/internal/service/directconnect/finder/finder.go b/aws/internal/service/directconnect/finder/finder.go new file mode 100644 index 000000000000..559ce5ef3ac5 --- /dev/null +++ b/aws/internal/service/directconnect/finder/finder.go @@ -0,0 +1,44 @@ +package finder + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/directconnect" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func GatewayAssociationByID(conn *directconnect.DirectConnect, id string) (*directconnect.GatewayAssociation, error) { + input := &directconnect.DescribeDirectConnectGatewayAssociationsInput{ + AssociationId: aws.String(id), + } + + output, err := conn.DescribeDirectConnectGatewayAssociations(input) + + if err != nil { + return nil, err + } + + if output == nil || len(output.DirectConnectGatewayAssociations) == 0 || output.DirectConnectGatewayAssociations[0] == nil { + return nil, &resource.NotFoundError{ + Message: "Empty result", + LastRequest: input, + } + } + + gatewayAssociation := output.DirectConnectGatewayAssociations[0] + + if state := aws.StringValue(gatewayAssociation.AssociationState); state == directconnect.GatewayAssociationStateDisassociated { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(gatewayAssociation.AssociationId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return gatewayAssociation, nil +} diff --git a/aws/internal/service/directconnect/waiter/status.go b/aws/internal/service/directconnect/waiter/status.go new file mode 100644 index 000000000000..98e90d4097b4 --- /dev/null +++ b/aws/internal/service/directconnect/waiter/status.go @@ -0,0 +1,25 @@ +package waiter + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/directconnect" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/directconnect/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" +) + +func GatewayAssociationState(conn *directconnect.DirectConnect, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := finder.GatewayAssociationByID(conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.AssociationState), nil + } +} diff --git a/aws/internal/service/directconnect/waiter/waiter.go b/aws/internal/service/directconnect/waiter/waiter.go new file mode 100644 index 000000000000..ab54c49cb62a --- /dev/null +++ b/aws/internal/service/directconnect/waiter/waiter.go @@ -0,0 +1,68 @@ +package waiter + +import ( + "errors" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/directconnect" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" +) + +func GatewayAssociationCreated(conn *directconnect.DirectConnect, id string, timeout time.Duration) (*directconnect.GatewayAssociation, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{directconnect.GatewayAssociationStateAssociating}, + Target: []string{directconnect.GatewayAssociationStateAssociated}, + Refresh: GatewayAssociationState(conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*directconnect.GatewayAssociation); ok { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.StateChangeError))) + + return output, err + } + + return nil, err +} + +func GatewayAssociationUpdated(conn *directconnect.DirectConnect, id string, timeout time.Duration) (*directconnect.GatewayAssociation, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{directconnect.GatewayAssociationStateUpdating}, + Target: []string{directconnect.GatewayAssociationStateAssociated}, + Refresh: GatewayAssociationState(conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*directconnect.GatewayAssociation); ok { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.StateChangeError))) + + return output, err + } + + return nil, err +} + +func GatewayAssociationDeleted(conn *directconnect.DirectConnect, id string, timeout time.Duration) (*directconnect.GatewayAssociation, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{directconnect.GatewayAssociationStateDisassociating}, + Target: []string{}, + Refresh: GatewayAssociationState(conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*directconnect.GatewayAssociation); ok { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.StateChangeError))) + + return output, err + } + + return nil, err +} From c28188296fde0f2e8cf0c5985822296573899985 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 7 Jul 2021 14:49:07 -0400 Subject: [PATCH 15/27] Use finder package in aws_dx_gateway_association & aws_dx_gateway_association_proposal acceptance tests. --- .../service/directconnect/finder/finder.go | 48 +++++++++++++++++-- ...ws_dx_gateway_association_proposal_test.go | 26 +++++----- ...esource_aws_dx_gateway_association_test.go | 37 +++++++------- 3 files changed, 79 insertions(+), 32 deletions(-) diff --git a/aws/internal/service/directconnect/finder/finder.go b/aws/internal/service/directconnect/finder/finder.go index 559ce5ef3ac5..f8af3c5a4bd5 100644 --- a/aws/internal/service/directconnect/finder/finder.go +++ b/aws/internal/service/directconnect/finder/finder.go @@ -11,6 +11,19 @@ func GatewayAssociationByID(conn *directconnect.DirectConnect, id string) (*dire AssociationId: aws.String(id), } + return GatewayAssociation(conn, input) +} + +func GatewayAssociationByAssociatedGatewayIDAndDirectConnectGatewayID(conn *directconnect.DirectConnect, associatedGatewayID, directConnectGatewayID string) (*directconnect.GatewayAssociation, error) { + input := &directconnect.DescribeDirectConnectGatewayAssociationsInput{ + AssociatedGatewayId: aws.String(associatedGatewayID), + DirectConnectGatewayId: aws.String(directConnectGatewayID), + } + + return GatewayAssociation(conn, input) +} + +func GatewayAssociation(conn *directconnect.DirectConnect, input *directconnect.DescribeDirectConnectGatewayAssociationsInput) (*directconnect.GatewayAssociation, error) { output, err := conn.DescribeDirectConnectGatewayAssociations(input) if err != nil { @@ -24,6 +37,9 @@ func GatewayAssociationByID(conn *directconnect.DirectConnect, id string) (*dire } } + // TODO Check for multiple results. + // TODO https://github.com/hashicorp/terraform-provider-aws/pull/17613. + gatewayAssociation := output.DirectConnectGatewayAssociations[0] if state := aws.StringValue(gatewayAssociation.AssociationState); state == directconnect.GatewayAssociationStateDisassociated { @@ -33,12 +49,38 @@ func GatewayAssociationByID(conn *directconnect.DirectConnect, id string) (*dire } } - // Eventual consistency check. - if aws.StringValue(gatewayAssociation.AssociationId) != id { + return gatewayAssociation, nil +} + +func GatewayAssociationProposalByID(conn *directconnect.DirectConnect, id string) (*directconnect.GatewayAssociationProposal, error) { + input := &directconnect.DescribeDirectConnectGatewayAssociationProposalsInput{ + ProposalId: aws.String(id), + } + + output, err := conn.DescribeDirectConnectGatewayAssociationProposals(input) + + if err != nil { + return nil, err + } + + if output == nil || len(output.DirectConnectGatewayAssociationProposals) == 0 || output.DirectConnectGatewayAssociationProposals[0] == nil { + return nil, &resource.NotFoundError{ + Message: "Empty result", + LastRequest: input, + } + } + + // TODO Check for multiple results. + // TODO https://github.com/hashicorp/terraform-provider-aws/pull/17613. + + proposal := output.DirectConnectGatewayAssociationProposals[0] + + if state := aws.StringValue(proposal.ProposalState); state == directconnect.GatewayAssociationProposalStateDeleted { return nil, &resource.NotFoundError{ + Message: state, LastRequest: input, } } - return gatewayAssociation, nil + return proposal, nil } diff --git a/aws/resource_aws_dx_gateway_association_proposal_test.go b/aws/resource_aws_dx_gateway_association_proposal_test.go index 3ef3932720cd..2368ef63528e 100644 --- a/aws/resource_aws_dx_gateway_association_proposal_test.go +++ b/aws/resource_aws_dx_gateway_association_proposal_test.go @@ -12,6 +12,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/directconnect/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) func init() { @@ -315,17 +317,17 @@ func testAccCheckAwsDxGatewayAssociationProposalDestroy(s *terraform.State) erro continue } - proposal, err := describeDirectConnectGatewayAssociationProposal(conn, rs.Primary.ID) + _, err := finder.GatewayAssociationProposalByID(conn, rs.Primary.ID) - if err != nil { - return err + if tfresource.NotFound(err) { + continue } - if proposal == nil { - continue + if err != nil { + return err } - return fmt.Errorf("Direct Connect Gateway Association Proposal (%s) still exists", rs.Primary.ID) + return fmt.Errorf("Direct Connect Gateway Association Proposal %s still exists", rs.Primary.ID) } return nil @@ -338,19 +340,19 @@ func testAccCheckAwsDxGatewayAssociationProposalExists(resourceName string, gate return fmt.Errorf("Not found: %s", resourceName) } + if rs.Primary.ID == "" { + return fmt.Errorf("No Direct Connect Gateway Association Proposal ID is set") + } + conn := testAccProvider.Meta().(*AWSClient).dxconn - proposal, err := describeDirectConnectGatewayAssociationProposal(conn, rs.Primary.ID) + output, err := finder.GatewayAssociationProposalByID(conn, rs.Primary.ID) if err != nil { return err } - if proposal == nil { - return fmt.Errorf("Direct Connect Gateway Association Proposal (%s) not found", rs.Primary.ID) - } - - *gatewayAssociationProposal = *proposal + *gatewayAssociationProposal = *output return nil } diff --git a/aws/resource_aws_dx_gateway_association_test.go b/aws/resource_aws_dx_gateway_association_test.go index 0a4355f702a2..22be438e4109 100644 --- a/aws/resource_aws_dx_gateway_association_test.go +++ b/aws/resource_aws_dx_gateway_association_test.go @@ -14,6 +14,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/directconnect/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) func init() { @@ -543,18 +545,17 @@ func testAccCheckAwsDxGatewayAssociationDestroy(s *terraform.State) error { continue } - resp, err := conn.DescribeDirectConnectGatewayAssociations(&directconnect.DescribeDirectConnectGatewayAssociationsInput{ - AssociationId: aws.String(rs.Primary.Attributes["dx_gateway_association_id"]), - }) + _, err := finder.GatewayAssociationByID(conn, rs.Primary.Attributes["dx_gateway_association_id"]) + + if tfresource.NotFound(err) { + continue + } + if err != nil { return err } - if len(resp.DirectConnectGatewayAssociations) > 0 { - return fmt.Errorf("Direct Connect Gateway (%s) is not dissociated from GW %s", - aws.StringValue(resp.DirectConnectGatewayAssociations[0].DirectConnectGatewayId), - aws.StringValue(resp.DirectConnectGatewayAssociations[0].AssociatedGateway.Id)) - } + return fmt.Errorf("Direct Connect Gateway Association %s still exists", rs.Primary.ID) } return nil } @@ -565,29 +566,31 @@ func testAccCheckAwsDxGatewayAssociationExists(name string, ga *directconnect.Ga if !ok { return fmt.Errorf("Not found: %s", name) } - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") + + if rs.Primary.Attributes["dx_gateway_association_id"] == "" { + return fmt.Errorf("No Direct Connect Gateway Association ID is set") } conn := testAccProvider.Meta().(*AWSClient).dxconn - resp, err := conn.DescribeDirectConnectGatewayAssociations(&directconnect.DescribeDirectConnectGatewayAssociationsInput{ - AssociationId: aws.String(rs.Primary.Attributes["dx_gateway_association_id"]), - }) + + output, err := finder.GatewayAssociationByID(conn, rs.Primary.Attributes["dx_gateway_association_id"]) + if err != nil { return err } - *ga = *resp.DirectConnectGatewayAssociations[0] + if proposalID := rs.Primary.Attributes["proposal_id"]; proposalID != "" { + output, err := finder.GatewayAssociationProposalByID(conn, proposalID) - if proposalId := rs.Primary.Attributes["proposal_id"]; proposalId != "" && gap != nil { - v, err := describeDirectConnectGatewayAssociationProposal(conn, proposalId) if err != nil { return err } - *gap = *v + *gap = *output } + *ga = *output + return nil } } From 9a5b53ce89fc6fd9a107ba1d1a65646a08fe1f13 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 7 Jul 2021 15:31:11 -0400 Subject: [PATCH 16/27] Add DirectConnect internal lister package and use in (some) test sweepers. --- aws/internal/service/directconnect/id.go | 9 ++ .../service/directconnect/lister/list.go | 3 + .../directconnect/lister/list_pages_gen.go | 73 +++++++++ ...ws_dx_gateway_association_proposal_test.go | 67 ++++---- ...esource_aws_dx_gateway_association_test.go | 144 ++++++++---------- 5 files changed, 181 insertions(+), 115 deletions(-) create mode 100644 aws/internal/service/directconnect/id.go create mode 100644 aws/internal/service/directconnect/lister/list.go create mode 100644 aws/internal/service/directconnect/lister/list_pages_gen.go diff --git a/aws/internal/service/directconnect/id.go b/aws/internal/service/directconnect/id.go new file mode 100644 index 000000000000..d1cfd0dd0618 --- /dev/null +++ b/aws/internal/service/directconnect/id.go @@ -0,0 +1,9 @@ +package directconnect + +import ( + "fmt" +) + +func GatewayAssociationCreateResourceID(directConnectGatewayID, associatedGatewayID string) string { + return fmt.Sprintf("ga-%s%s", directConnectGatewayID, associatedGatewayID) +} diff --git a/aws/internal/service/directconnect/lister/list.go b/aws/internal/service/directconnect/lister/list.go new file mode 100644 index 000000000000..5fa5e04b3413 --- /dev/null +++ b/aws/internal/service/directconnect/lister/list.go @@ -0,0 +1,3 @@ +//go:generate go run ../../../generators/listpages/main.go -function=DescribeDirectConnectGateways,DescribeDirectConnectGatewayAssociations,DescribeDirectConnectGatewayAssociationProposals -paginator=NextToken github.com/aws/aws-sdk-go/service/directconnect + +package lister diff --git a/aws/internal/service/directconnect/lister/list_pages_gen.go b/aws/internal/service/directconnect/lister/list_pages_gen.go new file mode 100644 index 000000000000..e28a109e6c22 --- /dev/null +++ b/aws/internal/service/directconnect/lister/list_pages_gen.go @@ -0,0 +1,73 @@ +// Code generated by "aws/internal/generators/listpages/main.go -function=DescribeDirectConnectGateways,DescribeDirectConnectGatewayAssociations,DescribeDirectConnectGatewayAssociationProposals -paginator=NextToken github.com/aws/aws-sdk-go/service/directconnect"; DO NOT EDIT. + +package lister + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/directconnect" +) + +func DescribeDirectConnectGatewayAssociationProposalsPages(conn *directconnect.DirectConnect, input *directconnect.DescribeDirectConnectGatewayAssociationProposalsInput, fn func(*directconnect.DescribeDirectConnectGatewayAssociationProposalsOutput, bool) bool) error { + return DescribeDirectConnectGatewayAssociationProposalsPagesWithContext(context.Background(), conn, input, fn) +} + +func DescribeDirectConnectGatewayAssociationProposalsPagesWithContext(ctx context.Context, conn *directconnect.DirectConnect, input *directconnect.DescribeDirectConnectGatewayAssociationProposalsInput, fn func(*directconnect.DescribeDirectConnectGatewayAssociationProposalsOutput, bool) bool) error { + for { + output, err := conn.DescribeDirectConnectGatewayAssociationProposalsWithContext(ctx, input) + if err != nil { + return err + } + + lastPage := aws.StringValue(output.NextToken) == "" + if !fn(output, lastPage) || lastPage { + break + } + + input.NextToken = output.NextToken + } + return nil +} + +func DescribeDirectConnectGatewayAssociationsPages(conn *directconnect.DirectConnect, input *directconnect.DescribeDirectConnectGatewayAssociationsInput, fn func(*directconnect.DescribeDirectConnectGatewayAssociationsOutput, bool) bool) error { + return DescribeDirectConnectGatewayAssociationsPagesWithContext(context.Background(), conn, input, fn) +} + +func DescribeDirectConnectGatewayAssociationsPagesWithContext(ctx context.Context, conn *directconnect.DirectConnect, input *directconnect.DescribeDirectConnectGatewayAssociationsInput, fn func(*directconnect.DescribeDirectConnectGatewayAssociationsOutput, bool) bool) error { + for { + output, err := conn.DescribeDirectConnectGatewayAssociationsWithContext(ctx, input) + if err != nil { + return err + } + + lastPage := aws.StringValue(output.NextToken) == "" + if !fn(output, lastPage) || lastPage { + break + } + + input.NextToken = output.NextToken + } + return nil +} + +func DescribeDirectConnectGatewaysPages(conn *directconnect.DirectConnect, input *directconnect.DescribeDirectConnectGatewaysInput, fn func(*directconnect.DescribeDirectConnectGatewaysOutput, bool) bool) error { + return DescribeDirectConnectGatewaysPagesWithContext(context.Background(), conn, input, fn) +} + +func DescribeDirectConnectGatewaysPagesWithContext(ctx context.Context, conn *directconnect.DirectConnect, input *directconnect.DescribeDirectConnectGatewaysInput, fn func(*directconnect.DescribeDirectConnectGatewaysOutput, bool) bool) error { + for { + output, err := conn.DescribeDirectConnectGatewaysWithContext(ctx, input) + if err != nil { + return err + } + + lastPage := aws.StringValue(output.NextToken) == "" + if !fn(output, lastPage) || lastPage { + break + } + + input.NextToken = output.NextToken + } + return nil +} diff --git a/aws/resource_aws_dx_gateway_association_proposal_test.go b/aws/resource_aws_dx_gateway_association_proposal_test.go index 2368ef63528e..2f30084a2c01 100644 --- a/aws/resource_aws_dx_gateway_association_proposal_test.go +++ b/aws/resource_aws_dx_gateway_association_proposal_test.go @@ -8,11 +8,13 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/directconnect" + multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/directconnect/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/directconnect/lister" "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) @@ -25,59 +27,58 @@ func init() { func testSweepDirectConnectGatewayAssociationProposals(region string) error { client, err := sharedClientForRegion(region) - if err != nil { - return fmt.Errorf("error getting client: %s", err) + return fmt.Errorf("error getting client: %w", err) } - conn := client.(*AWSClient).dxconn input := &directconnect.DescribeDirectConnectGatewayAssociationProposalsInput{} + var sweeperErrs *multierror.Error + sweepResources := make([]*testSweepResource, 0) - for { - output, err := conn.DescribeDirectConnectGatewayAssociationProposals(input) - - if testSweepSkipSweepError(err) { - log.Printf("[WARN] Skipping Direct Connect Gateway sweep for %s: %s", region, err) - return nil - } - - if err != nil { - return fmt.Errorf("error retrieving Direct Connect Gateway Association Proposals: %s", err) + lister.DescribeDirectConnectGatewayAssociationProposalsPages(conn, input, func(page *directconnect.DescribeDirectConnectGatewayAssociationProposalsOutput, lastPage bool) bool { + if page == nil { + return !lastPage } - for _, gatewayAssociationProposal := range output.DirectConnectGatewayAssociationProposals { - proposalID := aws.StringValue(gatewayAssociationProposal.ProposalId) + for _, proposal := range page.DirectConnectGatewayAssociationProposals { + proposalID := aws.StringValue(proposal.ProposalId) - if aws.StringValue(gatewayAssociationProposal.AssociatedGateway.Region) != region { - log.Printf("[INFO] Skipping Direct Connect Gateway Association Proposal (%s) in different home region: %s", proposalID, aws.StringValue(gatewayAssociationProposal.AssociatedGateway.Region)) + if proposalRegion := aws.StringValue(proposal.AssociatedGateway.Region); proposalRegion != region { + log.Printf("[INFO] Skipping Direct Connect Gateway Association Proposal (%s) in different home region: %s", proposalID, proposalRegion) continue } - if aws.StringValue(gatewayAssociationProposal.ProposalState) != directconnect.GatewayAssociationProposalStateAccepted { - log.Printf("[INFO] Skipping Direct Connect Gateway Association Proposal (%s) in non-accepted (%s) state", proposalID, aws.StringValue(gatewayAssociationProposal.ProposalState)) + if state := aws.StringValue(proposal.ProposalState); state != directconnect.GatewayAssociationProposalStateAccepted { + log.Printf("[INFO] Skipping Direct Connect Gateway Association Proposal (%s) in non-accepted (%s) state", proposalID, state) continue } - input := &directconnect.DeleteDirectConnectGatewayAssociationProposalInput{ - ProposalId: gatewayAssociationProposal.ProposalId, - } - - log.Printf("[INFO] Deleting Direct Connect Gateway Association Proposal: %s", proposalID) - _, err := conn.DeleteDirectConnectGatewayAssociationProposal(input) + r := resourceAwsDxGatewayAssociationProposal() + d := r.Data(nil) + d.SetId(proposalID) - if err != nil { - return fmt.Errorf("error deleting Direct Connect Gateway Association Proposal (%s): %s", proposalID, err) - } + sweepResources = append(sweepResources, NewTestSweepResource(r, d, client)) } - if aws.StringValue(output.NextToken) == "" { - break - } + return !lastPage + }) - input.NextToken = output.NextToken + if testSweepSkipSweepError(err) { + log.Print(fmt.Errorf("[WARN] Skipping Direct Connect Gateway Association Proposal sweep for %s: %w", region, err)) + return sweeperErrs // In case we have completed some pages, but had errors } - return nil + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing Direct Connect Gateway Association Proposals (%s): %w", region, err)) + } + + err = testSweepResourceOrchestrator(sweepResources) + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error sweeping Direct Connect Gateway Association Proposals (%s): %w", region, err)) + } + + return sweeperErrs.ErrorOrNil() } func TestAccAwsDxGatewayAssociationProposal_basicVpnGateway(t *testing.T) { diff --git a/aws/resource_aws_dx_gateway_association_test.go b/aws/resource_aws_dx_gateway_association_test.go index 22be438e4109..d85ed68d5bab 100644 --- a/aws/resource_aws_dx_gateway_association_test.go +++ b/aws/resource_aws_dx_gateway_association_test.go @@ -5,16 +5,18 @@ import ( "fmt" "log" "testing" - "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/directconnect" "github.com/aws/aws-sdk-go/service/ec2" + multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + tfdirectconnect "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/directconnect" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/directconnect/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/directconnect/lister" "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) @@ -31,38 +33,31 @@ func init() { func testSweepDirectConnectGatewayAssociations(region string) error { client, err := sharedClientForRegion(region) if err != nil { - return fmt.Errorf("error getting client: %s", err) + return fmt.Errorf("error getting client: %w", err) } conn := client.(*AWSClient).dxconn - gatewayInput := &directconnect.DescribeDirectConnectGatewaysInput{} + input := &directconnect.DescribeDirectConnectGatewaysInput{} + var sweeperErrs *multierror.Error + sweepResources := make([]*testSweepResource, 0) - for { - gatewayOutput, err := conn.DescribeDirectConnectGateways(gatewayInput) - - if testSweepSkipSweepError(err) { - log.Printf("[WARN] Skipping Direct Connect Gateway sweep for %s: %s", region, err) - return nil - } - - if err != nil { - return fmt.Errorf("error retrieving Direct Connect Gateways: %s", err) + lister.DescribeDirectConnectGatewaysPages(conn, input, func(page *directconnect.DescribeDirectConnectGatewaysOutput, lastPage bool) bool { + if page == nil { + return !lastPage } - for _, gateway := range gatewayOutput.DirectConnectGateways { + for _, gateway := range page.DirectConnectGateways { directConnectGatewayID := aws.StringValue(gateway.DirectConnectGatewayId) - associationInput := &directconnect.DescribeDirectConnectGatewayAssociationsInput{ - DirectConnectGatewayId: gateway.DirectConnectGatewayId, + input := &directconnect.DescribeDirectConnectGatewayAssociationsInput{ + DirectConnectGatewayId: aws.String(directConnectGatewayID), } - for { - associationOutput, err := conn.DescribeDirectConnectGatewayAssociations(associationInput) - - if err != nil { - return fmt.Errorf("error retrieving Direct Connect Gateway (%s) Associations: %s", directConnectGatewayID, err) + err := lister.DescribeDirectConnectGatewayAssociationsPages(conn, input, func(page *directconnect.DescribeDirectConnectGatewayAssociationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage } - for _, association := range associationOutput.DirectConnectGatewayAssociations { + for _, association := range page.DirectConnectGatewayAssociations { gatewayID := aws.StringValue(association.AssociatedGateway.Id) if aws.StringValue(association.AssociatedGateway.Region) != region { @@ -70,44 +65,36 @@ func testSweepDirectConnectGatewayAssociations(region string) error { continue } - if aws.StringValue(association.AssociationState) != directconnect.GatewayAssociationStateAssociated { - log.Printf("[INFO] Skipping Direct Connect Gateway (%s) Association in non-available (%s) state: %s", directConnectGatewayID, aws.StringValue(association.AssociationState), gatewayID) + if state := aws.StringValue(association.AssociationState); state != directconnect.GatewayAssociationStateAssociated { + log.Printf("[INFO] Skipping Direct Connect Gateway (%s) Association in non-available (%s) state: %s", directConnectGatewayID, state, gatewayID) continue } - input := &directconnect.DeleteDirectConnectGatewayAssociationInput{ - AssociationId: association.AssociationId, - } - - log.Printf("[INFO] Deleting Direct Connect Gateway (%s) Association: %s", directConnectGatewayID, gatewayID) - _, err := conn.DeleteDirectConnectGatewayAssociation(input) + r := resourceAwsDxGatewayAssociation() + d := r.Data(nil) + d.SetId(tfdirectconnect.GatewayAssociationCreateResourceID(directConnectGatewayID, gatewayID)) - if isAWSErr(err, directconnect.ErrCodeClientException, "No association exists") { - continue - } - - if err != nil { - return fmt.Errorf("error deleting Direct Connect Gateway (%s) Association (%s): %s", directConnectGatewayID, gatewayID, err) - } - - if err := waitForDirectConnectGatewayAssociationDeletion(conn, aws.StringValue(association.AssociationId), 20*time.Minute); err != nil { - return fmt.Errorf("error waiting for Direct Connect Gateway (%s) Association (%s) to be deleted: %s", directConnectGatewayID, gatewayID, err) - } + sweepResources = append(sweepResources, NewTestSweepResource(r, d, client)) } - if aws.StringValue(associationOutput.NextToken) == "" { - break - } + return !lastPage + }) - associationInput.NextToken = associationOutput.NextToken + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing Direct Connect Gateway Associations (%s): %w", region, err)) } } - if aws.StringValue(gatewayOutput.NextToken) == "" { - break - } + return !lastPage + }) - gatewayInput.NextToken = gatewayOutput.NextToken + if testSweepSkipSweepError(err) { + log.Print(fmt.Errorf("[WARN] Skipping Direct Connect Gateway Association sweep for %s: %w", region, err)) + return sweeperErrs // In case we have completed some pages, but had errors + } + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing Direct Connect Gateways (%s): %w", region, err)) } // Handle cross-account EC2 Transit Gateway associations. @@ -127,61 +114,54 @@ func testSweepDirectConnectGatewayAssociations(region string) error { continue } - associationInput := &directconnect.DescribeDirectConnectGatewayAssociationsInput{ - AssociatedGatewayId: transitGateway.TransitGatewayId, - } transitGatewayID := aws.StringValue(transitGateway.TransitGatewayId) - associationOutput, err := conn.DescribeDirectConnectGatewayAssociations(associationInput) - - if err != nil { - log.Printf("[ERROR] error retrieving EC2 Transit Gateway (%s) Direct Connect Gateway Associations: %s", transitGatewayID, err) - continue + input := &directconnect.DescribeDirectConnectGatewayAssociationsInput{ + AssociatedGatewayId: aws.String(transitGatewayID), } - for _, association := range associationOutput.DirectConnectGatewayAssociations { - associationID := aws.StringValue(association.AssociationId) - - if aws.StringValue(association.AssociationState) != directconnect.GatewayAssociationStateAssociated { - log.Printf("[INFO] Skipping EC2 Transit Gateway (%s) Direct Connect Gateway Association (%s) in non-available state: %s", transitGatewayID, associationID, aws.StringValue(association.AssociationState)) - continue + err := lister.DescribeDirectConnectGatewayAssociationsPages(conn, input, func(page *directconnect.DescribeDirectConnectGatewayAssociationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage } - input := &directconnect.DeleteDirectConnectGatewayAssociationInput{ - AssociationId: association.AssociationId, - } + for _, association := range page.DirectConnectGatewayAssociations { + directConnectGatewayID := aws.StringValue(association.DirectConnectGatewayId) + + if state := aws.StringValue(association.AssociationState); state != directconnect.GatewayAssociationStateAssociated { + log.Printf("[INFO] Skipping Direct Connect Gateway (%s) Association in non-available (%s) state: %s", directConnectGatewayID, state, transitGatewayID) + continue + } - log.Printf("[INFO] Deleting EC2 Transit Gateway (%s) Direct Connect Gateway Association: %s", transitGatewayID, associationID) - _, err := conn.DeleteDirectConnectGatewayAssociation(input) + r := resourceAwsDxGatewayAssociation() + d := r.Data(nil) + d.SetId(tfdirectconnect.GatewayAssociationCreateResourceID(directConnectGatewayID, transitGatewayID)) - if isAWSErr(err, directconnect.ErrCodeClientException, "No association exists") { - continue + sweepResources = append(sweepResources, NewTestSweepResource(r, d, client)) } - if err != nil { - log.Printf("[ERROR] error deleting EC2 Transit Gateway (%s) Direct Connect Gateway Association (%s): %s", transitGatewayID, associationID, err) - continue - } + return !lastPage + }) - if err := waitForDirectConnectGatewayAssociationDeletion(conn, associationID, 30*time.Minute); err != nil { - log.Printf("[ERROR] error waiting for EC2 Transit Gateway (%s) Direct Connect Gateway Association (%s) to be deleted: %s", transitGatewayID, associationID, err) - } + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing Direct Connect Gateway Associations (%s): %w", region, err)) } } return !lastPage }) - if testSweepSkipSweepError(err) { - log.Printf("[WARN] Skipping EC2 Transit Gateway Direct Connect Gateway Association sweep for %s: %s", region, err) - return nil + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing EC2 Transit Gateways (%s): %w", region, err)) } + err = testSweepResourceOrchestrator(sweepResources) + if err != nil { - return fmt.Errorf("error retrieving EC2 Transit Gateways: %s", err) + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error sweeping Direct Connect Gateway Associations (%s): %w", region, err)) } - return nil + return sweeperErrs.ErrorOrNil() } // V0 state upgrade testing must be done via acceptance testing due to API call From 5d7606d0149d13d45156a7c574b88332e822f6fb Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 7 Jul 2021 16:53:43 -0400 Subject: [PATCH 17/27] r/aws_dx_gateway_association_proposal: Document alternate resource import format. --- .../service/directconnect/finder/finder.go | 2 +- ...rce_aws_dx_gateway_association_proposal.go | 87 +++++++++---------- ...gateway_association_proposal.html.markdown | 11 ++- 3 files changed, 52 insertions(+), 48 deletions(-) diff --git a/aws/internal/service/directconnect/finder/finder.go b/aws/internal/service/directconnect/finder/finder.go index f8af3c5a4bd5..9bcf768f73e2 100644 --- a/aws/internal/service/directconnect/finder/finder.go +++ b/aws/internal/service/directconnect/finder/finder.go @@ -14,7 +14,7 @@ func GatewayAssociationByID(conn *directconnect.DirectConnect, id string) (*dire return GatewayAssociation(conn, input) } -func GatewayAssociationByAssociatedGatewayIDAndDirectConnectGatewayID(conn *directconnect.DirectConnect, associatedGatewayID, directConnectGatewayID string) (*directconnect.GatewayAssociation, error) { +func GatewayAssociationByDirectConnectGatewayIDAndAssociatedGatewayID(conn *directconnect.DirectConnect, directConnectGatewayID, associatedGatewayID string) (*directconnect.GatewayAssociation, error) { input := &directconnect.DescribeDirectConnectGatewayAssociationsInput{ AssociatedGatewayId: aws.String(associatedGatewayID), DirectConnectGatewayId: aws.String(directConnectGatewayID), diff --git a/aws/resource_aws_dx_gateway_association_proposal.go b/aws/resource_aws_dx_gateway_association_proposal.go index 249ad9ff0bbe..6218cde43ea0 100644 --- a/aws/resource_aws_dx_gateway_association_proposal.go +++ b/aws/resource_aws_dx_gateway_association_proposal.go @@ -11,6 +11,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/directconnect/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) func resourceAwsDxGatewayAssociationProposal() *schema.Resource { @@ -18,8 +20,9 @@ func resourceAwsDxGatewayAssociationProposal() *schema.Resource { Create: resourceAwsDxGatewayAssociationProposalCreate, Read: resourceAwsDxGatewayAssociationProposalRead, Delete: resourceAwsDxGatewayAssociationProposalDelete, + Importer: &schema.ResourceImporter{ - State: dxGatewayAssociationProposalImport, + State: resourceAwsDxGatewayAssociationProposalImport, }, CustomizeDiff: customdiff.Sequence( @@ -296,64 +299,56 @@ func flattenDirectConnectGatewayAssociationProposalAllowedPrefixes(routeFilterPr return allowedPrefixes } -func dxGatewayAssociationProposalImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - // example: c2ede9b4-bbc6-4d33-923c-bc4feEXAMPLE/186c8187-36f4-472e-9268-107aaEXAMPLE/0d95359280EXAMPLE +func resourceAwsDxGatewayAssociationProposalImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + parts := strings.Split(strings.ToLower(d.Id()), "/") - errStr := "unexpected format of import string (%q), expected PROPOSALID/DXGATEWAYID/TARGETGATEWAYID]*: %s" - importStr := d.Id() - log.Printf("[DEBUG] Validating import string (%s) for Direct Connect Gateway Association Proposal", importStr) + var proposalID, directConnectGatewayID, associatedGatewayID string + switch n := len(parts); n { + case 1: + return []*schema.ResourceData{d}, nil - parts := strings.Split(strings.ToLower(importStr), "/") - if len(parts) < 1 { - return nil, fmt.Errorf(errStr, importStr, "too few parts") - } - var propId, dxgwId, gwId string - propId = parts[0] + case 3: + proposalID = parts[0] + directConnectGatewayID = parts[1] + associatedGatewayID = parts[2] - conn := meta.(*AWSClient).dxconn - if propId != "" { - p, err := describeDirectConnectGatewayAssociationProposal(conn, propId) - if err != nil { - return nil, err + if directConnectGatewayID == "" || associatedGatewayID == "" { + return nil, fmt.Errorf("Incorrect resource ID format: %q. DXGATEWAYID and TARGETGATEWAYID must not be empty strings", d.Id()) } - if p != nil { - // proposal still exists normal import - return schema.ImportStatePassthrough(d, meta) - } - // proposal may not exist, but that's fine - } - d.SetId(propId) - if len(parts) == 1 { - // requesting just the prop id - return schema.ImportStatePassthrough(d, meta) - } else if len(parts) < 3 { - return nil, fmt.Errorf(errStr, importStr, "too few parts") + break + + default: + return nil, fmt.Errorf("Incorrect resource ID format: %q. Expected PROPOSALID or PROPOSALID/DXGATEWAYID/TARGETGATEWAYID", d.Id()) } - dxgwId = parts[1] - gwId = parts[2] + conn := meta.(*AWSClient).dxconn - if gwId != "" && dxgwId != "" { - input := directconnect.DescribeDirectConnectGatewayAssociationsInput{ - AssociatedGatewayId: aws.String(gwId), - DirectConnectGatewayId: aws.String(dxgwId), - } - resp, err := conn.DescribeDirectConnectGatewayAssociations(&input) - if err != nil { + if proposalID != "" { + _, err := finder.GatewayAssociationProposalByID(conn, proposalID) + + if tfresource.NotFound(err) { + // Proposal not found. + } else if err != nil { return nil, err - } + } else { + // Proposal still exists. + d.SetId(proposalID) - id := dxGatewayAssociationId(dxgwId, gwId) - if n := len(resp.DirectConnectGatewayAssociations); n != 1 { - return nil, fmt.Errorf("Found %d Direct Connect gateway associations for %s, expected 1", n, id) + return []*schema.ResourceData{d}, nil } - d.Set("associated_gateway_id", gwId) - d.Set("dx_gateway_id", dxgwId) - } else { - return nil, fmt.Errorf(errStr, importStr, "missing parts") } + _, err := finder.GatewayAssociationByDirectConnectGatewayIDAndAssociatedGatewayID(conn, directConnectGatewayID, associatedGatewayID) + + if err != nil { + return nil, err + } + + d.SetId(proposalID) + d.Set("associated_gateway_id", associatedGatewayID) + d.Set("dx_gateway_id", directConnectGatewayID) + return []*schema.ResourceData{d}, nil } diff --git a/website/docs/r/dx_gateway_association_proposal.html.markdown b/website/docs/r/dx_gateway_association_proposal.html.markdown index a64c7e4608f6..5701772d2b41 100644 --- a/website/docs/r/dx_gateway_association_proposal.html.markdown +++ b/website/docs/r/dx_gateway_association_proposal.html.markdown @@ -41,8 +41,17 @@ In addition to all arguments above, the following attributes are exported: ## Import -Direct Connect Gateway Association Proposals can be imported using the proposal ID, e.g. +Direct Connect Gateway Association Proposals can be imported using either a proposal ID or proposal ID, Direct Connect Gateway ID and associated gateway ID separated by `/`, e.g. ``` $ terraform import aws_dx_gateway_association_proposal.example ac90e981-b718-4364-872d-65478c84fafe ``` + +or + +``` +$ terraform import aws_dx_gateway_association_proposal.example ac90e981-b718-4364-872d-65478c84fafe/abcd1234-dcba-5678-be23-cdef9876ab45/vgw-12345678 +``` + +The latter case is useful when a previous proposal has been accepted and deleted by AWS. +The `aws_dx_gateway_association_proposal` resource will then represent a pseudo-proposal for the same Direct Connect Gateway and associated gateway. From cc094523ef5de16c899080a04cb6d9100412dc71 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 8 Jul 2021 10:50:20 -0400 Subject: [PATCH 18/27] r/aws_dx_gateway_association_proposal: Ensure that any pseudo-proposal ID is not blank. --- .../service/directconnect/finder/finder.go | 20 +- aws/resource_aws_dx_gateway_association.go | 17 +- ...rce_aws_dx_gateway_association_proposal.go | 310 +++++------------- ...ws_dx_gateway_association_proposal_test.go | 15 +- ...gateway_association_proposal.html.markdown | 1 + 5 files changed, 124 insertions(+), 239 deletions(-) diff --git a/aws/internal/service/directconnect/finder/finder.go b/aws/internal/service/directconnect/finder/finder.go index 9bcf768f73e2..9a97f05d34d1 100644 --- a/aws/internal/service/directconnect/finder/finder.go +++ b/aws/internal/service/directconnect/finder/finder.go @@ -40,16 +40,23 @@ func GatewayAssociation(conn *directconnect.DirectConnect, input *directconnect. // TODO Check for multiple results. // TODO https://github.com/hashicorp/terraform-provider-aws/pull/17613. - gatewayAssociation := output.DirectConnectGatewayAssociations[0] + association := output.DirectConnectGatewayAssociations[0] - if state := aws.StringValue(gatewayAssociation.AssociationState); state == directconnect.GatewayAssociationStateDisassociated { + if state := aws.StringValue(association.AssociationState); state == directconnect.GatewayAssociationStateDisassociated { return nil, &resource.NotFoundError{ Message: state, LastRequest: input, } } - return gatewayAssociation, nil + if association.AssociatedGateway == nil { + return nil, &resource.NotFoundError{ + Message: "Empty AssociatedGateway", + LastRequest: input, + } + } + + return association, nil } func GatewayAssociationProposalByID(conn *directconnect.DirectConnect, id string) (*directconnect.GatewayAssociationProposal, error) { @@ -82,5 +89,12 @@ func GatewayAssociationProposalByID(conn *directconnect.DirectConnect, id string } } + if proposal.AssociatedGateway == nil { + return nil, &resource.NotFoundError{ + Message: "Empty AssociatedGateway", + LastRequest: input, + } + } + return proposal, nil } diff --git a/aws/resource_aws_dx_gateway_association.go b/aws/resource_aws_dx_gateway_association.go index a574640275ee..f9f6ea4d1b5b 100644 --- a/aws/resource_aws_dx_gateway_association.go +++ b/aws/resource_aws_dx_gateway_association.go @@ -8,6 +8,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/directconnect" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -233,21 +234,23 @@ func resourceAwsDxGatewayAssociationUpdate(d *schema.ResourceData, meta interfac func resourceAwsDxGatewayAssociationDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).dxconn - associationId := d.Get("dx_gateway_association_id").(string) + associationID := d.Get("dx_gateway_association_id").(string) - log.Printf("[DEBUG] Deleting Direct Connect gateway association: %s", d.Id()) + log.Printf("[DEBUG] Deleting Direct Connect Gateway Association: %s", d.Id()) _, err := conn.DeleteDirectConnectGatewayAssociation(&directconnect.DeleteDirectConnectGatewayAssociationInput{ - AssociationId: aws.String(associationId), + AssociationId: aws.String(associationID), }) - if isAWSErr(err, directconnect.ErrCodeClientException, "No association exists") { + + if tfawserr.ErrMessageContains(err, directconnect.ErrCodeClientException, "does not exist") { return nil } + if err != nil { - return fmt.Errorf("error deleting Direct Connect gateway association: %s", err) + return fmt.Errorf("error deleting Direct Connect Gateway Association (%s): %w", d.Id(), err) } - if err := waitForDirectConnectGatewayAssociationDeletion(conn, associationId, d.Timeout(schema.TimeoutDelete)); err != nil { - return fmt.Errorf("error waiting for Direct Connect gateway association (%s) to be deleted: %s", d.Id(), err) + if err := waitForDirectConnectGatewayAssociationDeletion(conn, associationID, d.Timeout(schema.TimeoutDelete)); err != nil { + return fmt.Errorf("error waiting for Direct Connect Gateway Association (%s) to delete: %w", d.Id(), err) } return nil diff --git a/aws/resource_aws_dx_gateway_association_proposal.go b/aws/resource_aws_dx_gateway_association_proposal.go index 6218cde43ea0..b5fc3d409971 100644 --- a/aws/resource_aws_dx_gateway_association_proposal.go +++ b/aws/resource_aws_dx_gateway_association_proposal.go @@ -8,8 +8,8 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/directconnect" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/directconnect/finder" "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" @@ -29,30 +29,23 @@ func resourceAwsDxGatewayAssociationProposal() *schema.Resource { // Accepting the proposal with overridden prefixes changes the returned RequestedAllowedPrefixesToDirectConnectGateway value (allowed_prefixes attribute). // We only want to force a new resource if this value changes and the current proposal state is "requested". customdiff.ForceNewIf("allowed_prefixes", func(_ context.Context, d *schema.ResourceDiff, meta interface{}) bool { + conn := meta.(*AWSClient).dxconn + + log.Printf("[DEBUG] CustomizeDiff for Direct Connect Gateway Association Proposal (%s) allowed_prefixes", d.Id()) - log.Printf("[DEBUG] Checking diff for Direct Connect Gateway Association Proposal (%s) allowed_prefixes", d.Id()) + output, err := finder.GatewayAssociationProposalByID(conn, d.Id()) - if len(strings.Join(strings.Fields(d.Id()), "")) < 1 { - log.Printf("[WARN] Direct Connect Gateway Association Proposal Id not available (%s)", d.Id()) - log.Printf("[DEBUG] Direct Connect Gateway Association Proposal UpdatedKeys (%s)", strings.Join(d.UpdatedKeys(), "/")) - // assume proposal is end-of-life, rely on Read func to test + if tfresource.NotFound(err) { + // Proposal may be end-of-life and removed by AWS. return false } - conn := meta.(*AWSClient).dxconn - - proposal, err := describeDirectConnectGatewayAssociationProposal(conn, d.Id()) if err != nil { log.Printf("[ERROR] Error reading Direct Connect Gateway Association Proposal (%s): %s", d.Id(), err) return false } - if proposal == nil { - // proposal maybe end-of-life and removed by AWS, existence checked in Read func - return false - } - - return aws.StringValue(proposal.ProposalState) == directconnect.GatewayAssociationProposalStateRequested + return aws.StringValue(output.ProposalState) == directconnect.GatewayAssociationProposalStateRequested }), ), @@ -63,24 +56,29 @@ func resourceAwsDxGatewayAssociationProposal() *schema.Resource { Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, + "associated_gateway_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, + "associated_gateway_owner_account_id": { Type: schema.TypeString, Computed: true, }, + "associated_gateway_type": { Type: schema.TypeString, Computed: true, }, + "dx_gateway_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, + "dx_gateway_owner_account_id": { Type: schema.TypeString, Required: true, @@ -94,19 +92,23 @@ func resourceAwsDxGatewayAssociationProposal() *schema.Resource { func resourceAwsDxGatewayAssociationProposalCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).dxconn - allowedPrefixes := expandDirectConnectGatewayAssociationProposalAllowedPrefixes(d.Get("allowed_prefixes").(*schema.Set).List()) + directConnectGatewayID := d.Get("dx_gateway_id").(string) + associatedGatewayID := d.Get("associated_gateway_id").(string) input := &directconnect.CreateDirectConnectGatewayAssociationProposalInput{ - AddAllowedPrefixesToDirectConnectGateway: allowedPrefixes, - DirectConnectGatewayId: aws.String(d.Get("dx_gateway_id").(string)), - DirectConnectGatewayOwnerAccount: aws.String(d.Get("dx_gateway_owner_account_id").(string)), - GatewayId: aws.String(d.Get("associated_gateway_id").(string)), + DirectConnectGatewayId: aws.String(directConnectGatewayID), + DirectConnectGatewayOwnerAccount: aws.String(d.Get("dx_gateway_owner_account_id").(string)), + GatewayId: aws.String(associatedGatewayID), + } + + if v, ok := d.GetOk("allowed_prefixes"); ok && v.(*schema.Set).Len() > 0 { + input.AddAllowedPrefixesToDirectConnectGateway = expandDirectConnectRouteFilterPrefixes(v.(*schema.Set).List()) } log.Printf("[DEBUG] Creating Direct Connect Gateway Association Proposal: %s", input) output, err := conn.CreateDirectConnectGatewayAssociationProposal(input) if err != nil { - return fmt.Errorf("error creating Direct Connect Gateway Association Proposal: %s", err) + return fmt.Errorf("error creating Direct Connect Gateway Association Proposal (%s/%s): %w", directConnectGatewayID, associatedGatewayID, err) } d.SetId(aws.StringValue(output.DirectConnectGatewayAssociationProposal.ProposalId)) @@ -115,274 +117,142 @@ func resourceAwsDxGatewayAssociationProposalCreate(d *schema.ResourceData, meta } func resourceAwsDxGatewayAssociationProposalRead(d *schema.ResourceData, meta interface{}) error { - log.Printf("[DEBUG] Read Direct Connect Gateway Association Proposal: %s", d.Id()) - - var proposal *directconnect.GatewayAssociationProposal - conn := meta.(*AWSClient).dxconn - trimmedId := strings.Join(strings.Fields(d.Id()), "") - if len(trimmedId) > 0 { - var err error - proposal, err = describeDirectConnectGatewayAssociationProposal(conn, d.Id()) + // First attempt to find by proposal ID. + output, err := finder.GatewayAssociationProposalByID(conn, d.Id()) - if err != nil { - return fmt.Errorf("error reading Direct Connect Gateway Association Proposal (%s): %s", d.Id(), err) - } - } else { - log.Printf("[WARN] Direct Connect Gateway Association Proposal Id not available (%s)", d.Id()) - } - - if proposal == nil || len(trimmedId) < 1 { - log.Printf("[WARN] Direct Connect Gateway Association Proposal (%s) not found, checking for associated gateway", d.Id()) + if tfresource.NotFound(err) { + // Attempt to find an existing association. + directConnectGatewayID := d.Get("dx_gateway_id").(string) + associatedGatewayID := d.Get("associated_gateway_id").(string) - var dxGatewayId string - if rawDGId, ok := d.GetOk("dx_gateway_id"); ok { - dxGatewayId = rawDGId.(string) - } else if rawDGId == nil { - d.SetId("") - return fmt.Errorf("error reading dx_gateway_id (%s) from Proposal state", d.Id()) - } + output, err := finder.GatewayAssociationByDirectConnectGatewayIDAndAssociatedGatewayID(conn, directConnectGatewayID, associatedGatewayID) - var associatedGatewayId string - if rawAGId, ok := d.GetOk("associated_gateway_id"); ok { - associatedGatewayId = rawAGId.(string) - } else if rawAGId == nil { + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Direct Connect Gateway Association Proposal (%s) not found, removing from state", d.Id()) d.SetId("") - return fmt.Errorf("error reading associated_gateway_id (%s) from Proposal state", d.Id()) + return nil } - log.Printf("[DEBUG] looking for Direct Connect Gateway Association using dx_gateway_id (%s) and associated_gateway_id (%s) to validate Proposal state data", dxGatewayId, associatedGatewayId) - assocRaw, state, err := getDxGatewayAssociation(conn, dxGatewayId, associatedGatewayId)() - if err != nil { - d.SetId("") - return fmt.Errorf("error reading Direct Connect gateway association (%s) from Proposal state: %s", d.Id(), err) - } - - if state == gatewayAssociationStateDeleted { - log.Printf("[WARN] Direct Connect gateway association (%s/%s/%s) not found, removing from state", d.Id(), dxGatewayId, associatedGatewayId) - d.SetId("") - return nil + return fmt.Errorf("error reading Direct Connect Gateway Association (%s/%s): %w", directConnectGatewayID, associatedGatewayID, err) } - // once accepted, AWS will delete the proposal after after some time (days?) - // in this case we don't need to create a new proposal, use metadata from the association + // Once accepted, AWS will delete the proposal after after some time (days?). + // In this case we don't need to create a new proposal, use metadata from the association // to artificially populate the missing proposal in state as if it was still there. - log.Printf("[INFO] Direct Connect Gateway Association Proposal (%s) has reached end-of-life and has been removed by AWS.", d.Id()) - assoc := assocRaw.(*directconnect.GatewayAssociation) + log.Printf("[INFO] Direct Connect Gateway Association Proposal (%s) has reached end-of-life and has been removed by AWS", d.Id()) - err = d.Set("allowed_prefixes", flattenDxRouteFilterPrefixes(assoc.AllowedPrefixesToDirectConnectGateway)) - if err != nil { - return fmt.Errorf("error setting allowed_prefixes: %s", err) + if err := d.Set("allowed_prefixes", flattenDirectConnectRouteFilterPrefixes(output.AllowedPrefixesToDirectConnectGateway)); err != nil { + return fmt.Errorf("error setting allowed_prefixes: %w", err) } - d.Set("associated_gateway_id", assoc.AssociatedGateway.Id) - d.Set("dx_gateway_id", assoc.DirectConnectGatewayId) - d.Set("dx_gateway_owner_account_id", assoc.DirectConnectGatewayOwnerAccount) + d.Set("associated_gateway_id", output.AssociatedGateway.Id) + d.Set("associated_gateway_owner_account_id", output.AssociatedGateway.OwnerAccount) + d.Set("associated_gateway_type", output.AssociatedGateway.Type) + d.Set("dx_gateway_id", output.DirectConnectGatewayId) + d.Set("dx_gateway_owner_account_id", output.DirectConnectGatewayOwnerAccount) + } else if err != nil { + return fmt.Errorf("error reading Direct Connect Gateway Association Proposal (%s): %w", d.Id(), err) } else { - log.Printf("[DEBUG] Direct Connect Gateway Association Proposal (%s) found, continuing as normal: %s", d.Id(), proposal.String()) - - if aws.StringValue(proposal.ProposalState) == directconnect.GatewayAssociationProposalStateDeleted { - log.Printf("[WARN] Direct Connect Gateway Association Proposal (%s) deleted, removing from state", d.Id()) - d.SetId("") - return nil + if err := d.Set("allowed_prefixes", flattenDirectConnectRouteFilterPrefixes(output.RequestedAllowedPrefixesToDirectConnectGateway)); err != nil { + return fmt.Errorf("error setting allowed_prefixes: %w", err) } - if proposal.AssociatedGateway == nil { - return fmt.Errorf("error reading Direct Connect Gateway Association Proposal (%s): missing associated gateway information", d.Id()) - } - - if err := d.Set("allowed_prefixes", flattenDirectConnectGatewayAssociationProposalAllowedPrefixes(proposal.RequestedAllowedPrefixesToDirectConnectGateway)); err != nil { - return fmt.Errorf("error setting allowed_prefixes: %s", err) - } - - d.Set("associated_gateway_id", proposal.AssociatedGateway.Id) - d.Set("associated_gateway_owner_account_id", proposal.AssociatedGateway.OwnerAccount) - d.Set("associated_gateway_type", proposal.AssociatedGateway.Type) - d.Set("dx_gateway_id", proposal.DirectConnectGatewayId) - d.Set("dx_gateway_owner_account_id", proposal.DirectConnectGatewayOwnerAccount) - + d.Set("associated_gateway_id", output.AssociatedGateway.Id) + d.Set("associated_gateway_owner_account_id", output.AssociatedGateway.OwnerAccount) + d.Set("associated_gateway_type", output.AssociatedGateway.Type) + d.Set("dx_gateway_id", output.DirectConnectGatewayId) + d.Set("dx_gateway_owner_account_id", output.DirectConnectGatewayOwnerAccount) } + return nil } func resourceAwsDxGatewayAssociationProposalDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).dxconn - input := &directconnect.DeleteDirectConnectGatewayAssociationProposalInput{ - ProposalId: aws.String(d.Id()), - } - log.Printf("[DEBUG] Deleting Direct Connect Gateway Association Proposal: %s", d.Id()) + _, err := conn.DeleteDirectConnectGatewayAssociationProposal(&directconnect.DeleteDirectConnectGatewayAssociationProposalInput{ + ProposalId: aws.String(d.Id()), + }) - _, err := conn.DeleteDirectConnectGatewayAssociationProposal(input) + if tfawserr.ErrMessageContains(err, directconnect.ErrCodeClientException, "is not found") { + return nil + } if err != nil { - return fmt.Errorf("error deleting Direct Connect Gateway Association Proposal (%s): %s", d.Id(), err) + return fmt.Errorf("error deleting Direct Connect Gateway Association Proposal (%s): %w", d.Id(), err) } return nil } -func describeDirectConnectGatewayAssociationProposal(conn *directconnect.DirectConnect, proposalID string) (*directconnect.GatewayAssociationProposal, error) { - input := &directconnect.DescribeDirectConnectGatewayAssociationProposalsInput{ - ProposalId: aws.String(proposalID), +func expandDirectConnectRouteFilterPrefixes(tfList []interface{}) []*directconnect.RouteFilterPrefix { + if len(tfList) == 0 { + return nil } - for { - output, err := conn.DescribeDirectConnectGatewayAssociationProposals(input) + var apiObjects []*directconnect.RouteFilterPrefix - if err != nil { - return nil, err - } + for _, tfStringRaw := range tfList { + tfString, ok := tfStringRaw.(string) - if output == nil { + if !ok { continue } - for _, proposal := range output.DirectConnectGatewayAssociationProposals { - if aws.StringValue(proposal.ProposalId) == proposalID { - return proposal, nil - } + apiObject := &directconnect.RouteFilterPrefix{ + Cidr: aws.String(tfString), } - if aws.StringValue(output.NextToken) == "" { - break - } - - input.NextToken = output.NextToken + apiObjects = append(apiObjects, apiObject) } - return nil, nil + return apiObjects } -func expandDirectConnectGatewayAssociationProposalAllowedPrefixes(allowedPrefixes []interface{}) []*directconnect.RouteFilterPrefix { - if len(allowedPrefixes) == 0 { +func flattenDirectConnectRouteFilterPrefixes(apiObjects []*directconnect.RouteFilterPrefix) []interface{} { + if len(apiObjects) == 0 { return nil } - var routeFilterPrefixes []*directconnect.RouteFilterPrefix + var tfList []interface{} - for _, allowedPrefixRaw := range allowedPrefixes { - if allowedPrefixRaw == nil { + for _, apiObject := range apiObjects { + if apiObject == nil { continue } - routeFilterPrefix := &directconnect.RouteFilterPrefix{ - Cidr: aws.String(allowedPrefixRaw.(string)), - } - - routeFilterPrefixes = append(routeFilterPrefixes, routeFilterPrefix) + tfList = append(tfList, aws.StringValue(apiObject.Cidr)) } - return routeFilterPrefixes -} - -func flattenDirectConnectGatewayAssociationProposalAllowedPrefixes(routeFilterPrefixes []*directconnect.RouteFilterPrefix) []interface{} { - if len(routeFilterPrefixes) == 0 { - return []interface{}{} - } - - var allowedPrefixes []interface{} - - for _, routeFilterPrefix := range routeFilterPrefixes { - if routeFilterPrefix == nil { - continue - } - - allowedPrefix := aws.StringValue(routeFilterPrefix.Cidr) - - allowedPrefixes = append(allowedPrefixes, allowedPrefix) - } - - return allowedPrefixes + return tfList } func resourceAwsDxGatewayAssociationProposalImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - parts := strings.Split(strings.ToLower(d.Id()), "/") - - var proposalID, directConnectGatewayID, associatedGatewayID string - switch n := len(parts); n { + switch parts := strings.Split(strings.ToLower(d.Id()), "/"); len(parts) { case 1: - return []*schema.ResourceData{d}, nil + break case 3: - proposalID = parts[0] - directConnectGatewayID = parts[1] - associatedGatewayID = parts[2] + proposalID := parts[0] + directConnectGatewayID := parts[1] + associatedGatewayID := parts[2] - if directConnectGatewayID == "" || associatedGatewayID == "" { - return nil, fmt.Errorf("Incorrect resource ID format: %q. DXGATEWAYID and TARGETGATEWAYID must not be empty strings", d.Id()) + if proposalID == "" || directConnectGatewayID == "" || associatedGatewayID == "" { + return nil, fmt.Errorf("Incorrect resource ID format: %q. PROPOSALID, DXGATEWAYID and TARGETGATEWAYID must not be empty strings", d.Id()) } - break + // Use pseudo-proposal ID and actual DirectConnectGatewayId and AssociatedGatewayId. + d.SetId(proposalID) + d.Set("associated_gateway_id", associatedGatewayID) + d.Set("dx_gateway_id", directConnectGatewayID) default: return nil, fmt.Errorf("Incorrect resource ID format: %q. Expected PROPOSALID or PROPOSALID/DXGATEWAYID/TARGETGATEWAYID", d.Id()) } - conn := meta.(*AWSClient).dxconn - - if proposalID != "" { - _, err := finder.GatewayAssociationProposalByID(conn, proposalID) - - if tfresource.NotFound(err) { - // Proposal not found. - } else if err != nil { - return nil, err - } else { - // Proposal still exists. - d.SetId(proposalID) - - return []*schema.ResourceData{d}, nil - } - } - - _, err := finder.GatewayAssociationByDirectConnectGatewayIDAndAssociatedGatewayID(conn, directConnectGatewayID, associatedGatewayID) - - if err != nil { - return nil, err - } - - d.SetId(proposalID) - d.Set("associated_gateway_id", associatedGatewayID) - d.Set("dx_gateway_id", directConnectGatewayID) - return []*schema.ResourceData{d}, nil } - -func getDxGatewayAssociation(conn *directconnect.DirectConnect, dxGatewayId, associatedGatewayId string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - - resp, err := conn.DescribeDirectConnectGatewayAssociations(&directconnect.DescribeDirectConnectGatewayAssociationsInput{ - AssociatedGatewayId: &associatedGatewayId, - DirectConnectGatewayId: &dxGatewayId, - }) - - if err != nil { - return nil, "", err - } - - n := len(resp.DirectConnectGatewayAssociations) - switch n { - case 0: - return "", gatewayAssociationStateDeleted, nil - - case 1: - assoc := resp.DirectConnectGatewayAssociations[0] - - if stateChangeError := aws.StringValue(assoc.StateChangeError); stateChangeError != "" { - id := dxGatewayAssociationId( - aws.StringValue(resp.DirectConnectGatewayAssociations[0].DirectConnectGatewayId), - aws.StringValue(resp.DirectConnectGatewayAssociations[0].AssociatedGateway.Id)) - log.Printf("[INFO] Direct Connect gateway association (%s) state change error: %s", id, stateChangeError) - } - - return assoc, aws.StringValue(assoc.AssociationState), nil - - default: - return nil, "", fmt.Errorf("Found %d Direct Connect gateway associations for %s, expected 1", n, associatedGatewayId) - } - } -} diff --git a/aws/resource_aws_dx_gateway_association_proposal_test.go b/aws/resource_aws_dx_gateway_association_proposal_test.go index 2f30084a2c01..9e17079a0b18 100644 --- a/aws/resource_aws_dx_gateway_association_proposal_test.go +++ b/aws/resource_aws_dx_gateway_association_proposal_test.go @@ -373,10 +373,10 @@ func testAccCheckAwsDxGatewayAssociationProposalDisappears(proposal *directconne } } -func testAccCheckAwsDxGatewayAssociationProposalRecreated(i, j *directconnect.GatewayAssociationProposal) resource.TestCheckFunc { +func testAccCheckAwsDxGatewayAssociationProposalRecreated(old, new *directconnect.GatewayAssociationProposal) resource.TestCheckFunc { return func(s *terraform.State) error { - if aws.StringValue(i.ProposalId) == aws.StringValue(j.ProposalId) { - return fmt.Errorf("Direct Connect Gateway Association Proposal not recreated") + if old, new := aws.StringValue(old.ProposalId), aws.StringValue(new.ProposalId); old == new { + return fmt.Errorf("Direct Connect Gateway Association Proposal (%s) not recreated", old) } return nil @@ -392,19 +392,16 @@ func testAccCheckAwsDxGatewayAssociationProposalAccepted(resourceName string) re conn := testAccProvider.Meta().(*AWSClient).dxconn - proposal, err := describeDirectConnectGatewayAssociationProposal(conn, rs.Primary.ID) + output, err := finder.GatewayAssociationProposalByID(conn, rs.Primary.ID) if err != nil { return err } - if proposal == nil { - return fmt.Errorf("Direct Connect Gateway Association Proposal (%s) not found", rs.Primary.ID) - } - - if aws.StringValue(proposal.ProposalState) != "accepted" { + if aws.StringValue(output.ProposalState) != directconnect.GatewayAssociationProposalStateAccepted { return fmt.Errorf("Direct Connect Gateway Association Proposal (%s) not accepted", rs.Primary.ID) } + return nil } } diff --git a/website/docs/r/dx_gateway_association_proposal.html.markdown b/website/docs/r/dx_gateway_association_proposal.html.markdown index 5701772d2b41..74ce04dc4a82 100644 --- a/website/docs/r/dx_gateway_association_proposal.html.markdown +++ b/website/docs/r/dx_gateway_association_proposal.html.markdown @@ -55,3 +55,4 @@ $ terraform import aws_dx_gateway_association_proposal.example ac90e981-b718-436 The latter case is useful when a previous proposal has been accepted and deleted by AWS. The `aws_dx_gateway_association_proposal` resource will then represent a pseudo-proposal for the same Direct Connect Gateway and associated gateway. +If no previous proposal is available, use a tool like [`uuidgen`](http://manpages.ubuntu.com/manpages/bionic/man1/uuidgen.1.html) to generate a new random pseudo-proposal ID. From 50910413e150f74fcb2f78c1a1b9bef3616f710f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 8 Jul 2021 11:14:15 -0400 Subject: [PATCH 19/27] Tweak acceptance test configurations. --- ...ws_dx_gateway_association_proposal_test.go | 126 +++++++----------- ...esource_aws_dx_gateway_association_test.go | 22 +-- 2 files changed, 53 insertions(+), 95 deletions(-) diff --git a/aws/resource_aws_dx_gateway_association_proposal_test.go b/aws/resource_aws_dx_gateway_association_proposal_test.go index 9e17079a0b18..de71096ffd73 100644 --- a/aws/resource_aws_dx_gateway_association_proposal_test.go +++ b/aws/resource_aws_dx_gateway_association_proposal_test.go @@ -82,7 +82,7 @@ func testSweepDirectConnectGatewayAssociationProposals(region string) error { } func TestAccAwsDxGatewayAssociationProposal_basicVpnGateway(t *testing.T) { - var proposal1 directconnect.GatewayAssociationProposal + var proposal directconnect.GatewayAssociationProposal var providers []*schema.Provider rBgpAsn := acctest.RandIntRange(64512, 65534) rName := acctest.RandomWithPrefix("tf-acc-test") @@ -91,10 +91,7 @@ func TestAccAwsDxGatewayAssociationProposal_basicVpnGateway(t *testing.T) { resourceNameVgw := "aws_vpn_gateway.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - testAccAlternateAccountPreCheck(t) - }, + PreCheck: func() { testAccPreCheck(t); testAccAlternateAccountPreCheck(t) }, ErrorCheck: testAccErrorCheck(t, directconnect.EndpointsID), ProviderFactories: testAccProviderFactoriesAlternate(&providers), CheckDestroy: testAccCheckAwsDxGatewayAssociationProposalDestroy, @@ -102,12 +99,12 @@ func TestAccAwsDxGatewayAssociationProposal_basicVpnGateway(t *testing.T) { { Config: testAccDxGatewayAssociationProposalConfig_basicVpnGateway(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsDxGatewayAssociationProposalExists(resourceName, &proposal1), - resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), + testAccCheckAwsDxGatewayAssociationProposalExists(resourceName, &proposal), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "1"), resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameVgw, "id"), testAccCheckResourceAttrAccountID(resourceName, "associated_gateway_owner_account_id"), resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "virtualPrivateGateway"), - resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), ), }, { @@ -121,7 +118,7 @@ func TestAccAwsDxGatewayAssociationProposal_basicVpnGateway(t *testing.T) { } func TestAccAwsDxGatewayAssociationProposal_basicTransitGateway(t *testing.T) { - var proposal1 directconnect.GatewayAssociationProposal + var proposal directconnect.GatewayAssociationProposal var providers []*schema.Provider rBgpAsn := acctest.RandIntRange(64512, 65534) rName := acctest.RandomWithPrefix("tf-acc-test") @@ -130,10 +127,7 @@ func TestAccAwsDxGatewayAssociationProposal_basicTransitGateway(t *testing.T) { resourceNameTgw := "aws_ec2_transit_gateway.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - testAccAlternateAccountPreCheck(t) - }, + PreCheck: func() { testAccPreCheck(t); testAccAlternateAccountPreCheck(t) }, ErrorCheck: testAccErrorCheck(t, directconnect.EndpointsID), ProviderFactories: testAccProviderFactoriesAlternate(&providers), CheckDestroy: testAccCheckAwsDxGatewayAssociationProposalDestroy, @@ -141,14 +135,14 @@ func TestAccAwsDxGatewayAssociationProposal_basicTransitGateway(t *testing.T) { { Config: testAccDxGatewayAssociationProposalConfig_basicTransitGateway(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsDxGatewayAssociationProposalExists(resourceName, &proposal1), - resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), - resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameTgw, "id"), - testAccCheckResourceAttrAccountID(resourceName, "associated_gateway_owner_account_id"), - resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "transitGateway"), + testAccCheckAwsDxGatewayAssociationProposalExists(resourceName, &proposal), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "2"), resource.TestCheckTypeSetElemAttr(resourceName, "allowed_prefixes.*", "10.255.255.0/30"), resource.TestCheckTypeSetElemAttr(resourceName, "allowed_prefixes.*", "10.255.255.8/30"), + resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameTgw, "id"), + testAccCheckResourceAttrAccountID(resourceName, "associated_gateway_owner_account_id"), + resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "transitGateway"), + resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), ), }, { @@ -162,17 +156,14 @@ func TestAccAwsDxGatewayAssociationProposal_basicTransitGateway(t *testing.T) { } func TestAccAwsDxGatewayAssociationProposal_disappears(t *testing.T) { - var proposal1 directconnect.GatewayAssociationProposal + var proposal directconnect.GatewayAssociationProposal var providers []*schema.Provider rBgpAsn := acctest.RandIntRange(64512, 65534) rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_dx_gateway_association_proposal.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - testAccAlternateAccountPreCheck(t) - }, + PreCheck: func() { testAccPreCheck(t); testAccAlternateAccountPreCheck(t) }, ErrorCheck: testAccErrorCheck(t, directconnect.EndpointsID), ProviderFactories: testAccProviderFactoriesAlternate(&providers), CheckDestroy: testAccCheckAwsDxGatewayAssociationProposalDestroy, @@ -180,8 +171,8 @@ func TestAccAwsDxGatewayAssociationProposal_disappears(t *testing.T) { { Config: testAccDxGatewayAssociationProposalConfig_basicVpnGateway(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsDxGatewayAssociationProposalExists(resourceName, &proposal1), - testAccCheckAwsDxGatewayAssociationProposalDisappears(&proposal1), + testAccCheckAwsDxGatewayAssociationProposalExists(resourceName, &proposal), + testAccCheckResourceDisappears(testAccProvider, resourceAwsDxGatewayAssociationProposal(), resourceName), ), ExpectNonEmptyPlan: true, }, @@ -190,17 +181,14 @@ func TestAccAwsDxGatewayAssociationProposal_disappears(t *testing.T) { } func TestAccAwsDxGatewayAssociationProposal_endOfLifeVpn(t *testing.T) { - var proposal1 directconnect.GatewayAssociationProposal + var proposal directconnect.GatewayAssociationProposal var providers []*schema.Provider rBgpAsn := acctest.RandIntRange(64512, 65534) rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_dx_gateway_association_proposal.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - testAccAlternateAccountPreCheck(t) - }, + PreCheck: func() { testAccPreCheck(t); testAccAlternateAccountPreCheck(t) }, ErrorCheck: testAccErrorCheck(t, directconnect.EndpointsID), ProviderFactories: testAccProviderFactoriesAlternate(&providers), CheckDestroy: testAccCheckAwsDxGatewayAssociationProposalDestroy, @@ -208,39 +196,36 @@ func TestAccAwsDxGatewayAssociationProposal_endOfLifeVpn(t *testing.T) { { Config: testAccDxGatewayAssociationProposalConfig_endOfLifeVpn(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsDxGatewayAssociationProposalExists(resourceName, &proposal1), + testAccCheckAwsDxGatewayAssociationProposalExists(resourceName, &proposal), testAccCheckAwsDxGatewayAssociationProposalAccepted(resourceName), - testAccCheckAwsDxGatewayAssociationProposalDisappears(&proposal1), + testAccCheckResourceDisappears(testAccProvider, resourceAwsDxGatewayAssociationProposal(), resourceName), ), }, { ResourceName: resourceName, ImportStateIdFunc: func(s *terraform.State) (string, error) { return strings.Join([]string{ - aws.StringValue(proposal1.ProposalId), - aws.StringValue(proposal1.DirectConnectGatewayId), - aws.StringValue(proposal1.AssociatedGateway.Id), + aws.StringValue(proposal.ProposalId), + aws.StringValue(proposal.DirectConnectGatewayId), + aws.StringValue(proposal.AssociatedGateway.Id), }, "/"), nil }, ImportState: true, - ImportStateVerify: false, // proposal attributes not applicable when it does not exist + ImportStateVerify: true, }, }, }) } func TestAccAwsDxGatewayAssociationProposal_endOfLifeTgw(t *testing.T) { - var proposal1 directconnect.GatewayAssociationProposal + var proposal directconnect.GatewayAssociationProposal var providers []*schema.Provider rBgpAsn := acctest.RandIntRange(64512, 65534) rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_dx_gateway_association_proposal.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - testAccAlternateAccountPreCheck(t) - }, + PreCheck: func() { testAccPreCheck(t); testAccAlternateAccountPreCheck(t) }, ErrorCheck: testAccErrorCheck(t, directconnect.EndpointsID), ProviderFactories: testAccProviderFactoriesAlternate(&providers), CheckDestroy: testAccCheckAwsDxGatewayAssociationProposalDestroy, @@ -248,22 +233,22 @@ func TestAccAwsDxGatewayAssociationProposal_endOfLifeTgw(t *testing.T) { { Config: testAccDxGatewayAssociationProposalConfig_endOfLifeTgw(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsDxGatewayAssociationProposalExists(resourceName, &proposal1), + testAccCheckAwsDxGatewayAssociationProposalExists(resourceName, &proposal), testAccCheckAwsDxGatewayAssociationProposalAccepted(resourceName), - testAccCheckAwsDxGatewayAssociationProposalDisappears(&proposal1), + testAccCheckResourceDisappears(testAccProvider, resourceAwsDxGatewayAssociationProposal(), resourceName), ), }, { ResourceName: resourceName, ImportStateIdFunc: func(s *terraform.State) (string, error) { return strings.Join([]string{ - aws.StringValue(proposal1.ProposalId), - aws.StringValue(proposal1.DirectConnectGatewayId), - aws.StringValue(proposal1.AssociatedGateway.Id), + aws.StringValue(proposal.ProposalId), + aws.StringValue(proposal.DirectConnectGatewayId), + aws.StringValue(proposal.AssociatedGateway.Id), }, "/"), nil }, ImportState: true, - ImportStateVerify: false, // proposal attributes not applicable when it does not exist + ImportStateVerify: true, }, }, }) @@ -277,10 +262,7 @@ func TestAccAwsDxGatewayAssociationProposal_AllowedPrefixes(t *testing.T) { resourceName := "aws_dx_gateway_association_proposal.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - testAccAlternateAccountPreCheck(t) - }, + PreCheck: func() { testAccPreCheck(t); testAccAlternateAccountPreCheck(t) }, ErrorCheck: testAccErrorCheck(t, directconnect.EndpointsID), ProviderFactories: testAccProviderFactoriesAlternate(&providers), CheckDestroy: testAccCheckAwsDxGatewayAssociationProposalDestroy, @@ -359,20 +341,6 @@ func testAccCheckAwsDxGatewayAssociationProposalExists(resourceName string, gate } } -func testAccCheckAwsDxGatewayAssociationProposalDisappears(proposal *directconnect.GatewayAssociationProposal) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).dxconn - - input := &directconnect.DeleteDirectConnectGatewayAssociationProposalInput{ - ProposalId: proposal.ProposalId, - } - - _, err := conn.DeleteDirectConnectGatewayAssociationProposal(input) - - return err - } -} - func testAccCheckAwsDxGatewayAssociationProposalRecreated(old, new *directconnect.GatewayAssociationProposal) resource.TestCheckFunc { return func(s *terraform.State) error { if old, new := aws.StringValue(old.ProposalId), aws.StringValue(new.ProposalId); old == new { @@ -407,7 +375,7 @@ func testAccCheckAwsDxGatewayAssociationProposalAccepted(resourceName string) re } func testAccDxGatewayAssociationProposalConfigBase_vpnGateway(rName string, rBgpAsn int) string { - return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` + return composeConfig(testAccAlternateAccountProviderConfig(), fmt.Sprintf(` resource "aws_dx_gateway" "test" { provider = "awsalternate" @@ -430,21 +398,21 @@ resource "aws_vpn_gateway" "test" { Name = %[1]q } } -`, rName, rBgpAsn) +`, rName, rBgpAsn)) } func testAccDxGatewayAssociationProposalConfig_basicVpnGateway(rName string, rBgpAsn int) string { - return testAccDxGatewayAssociationProposalConfigBase_vpnGateway(rName, rBgpAsn) + ` + return composeConfig(testAccDxGatewayAssociationProposalConfigBase_vpnGateway(rName, rBgpAsn), ` resource "aws_dx_gateway_association_proposal" "test" { dx_gateway_id = aws_dx_gateway.test.id dx_gateway_owner_account_id = aws_dx_gateway.test.owner_account_id associated_gateway_id = aws_vpn_gateway.test.id } -` +`) } func testAccDxGatewayAssociationProposalConfig_endOfLifeVpn(rName string, rBgpAsn int) string { - return testAccDxGatewayAssociationProposalConfig_basicVpnGateway(rName, rBgpAsn) + ` + return composeConfig(testAccDxGatewayAssociationProposalConfig_basicVpnGateway(rName, rBgpAsn), ` data "aws_caller_identity" "current" {} resource "aws_dx_gateway_association" "test" { @@ -454,11 +422,11 @@ proposal_id = aws_dx_gateway_association_proposal.test.i dx_gateway_id = aws_dx_gateway.test.id associated_gateway_owner_account_id = data.aws_caller_identity.current.account_id } -` +`) } func testAccDxGatewayAssociationProposalConfig_endOfLifeTgw(rName string, rBgpAsn int) string { - return testAccDxGatewayAssociationProposalConfig_basicTransitGateway(rName, rBgpAsn) + ` + return composeConfig(testAccDxGatewayAssociationProposalConfig_basicTransitGateway(rName, rBgpAsn), ` data "aws_caller_identity" "current" {} resource "aws_dx_gateway_association" "test" { @@ -468,11 +436,11 @@ proposal_id = aws_dx_gateway_association_proposal.test.i dx_gateway_id = aws_dx_gateway.test.id associated_gateway_owner_account_id = data.aws_caller_identity.current.account_id } -` +`) } func testAccDxGatewayAssociationProposalConfig_basicTransitGateway(rName string, rBgpAsn int) string { - return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` + return composeConfig(testAccAlternateAccountProviderConfig(), fmt.Sprintf(` resource "aws_dx_gateway" "test" { provider = "awsalternate" @@ -496,27 +464,27 @@ resource "aws_dx_gateway_association_proposal" "test" { "10.255.255.8/30", ] } -`, rName, rBgpAsn) +`, rName, rBgpAsn)) } func testAccDxGatewayAssociationProposalConfigAllowedPrefixes1(rName string, rBgpAsn int) string { - return testAccDxGatewayAssociationProposalConfigBase_vpnGateway(rName, rBgpAsn) + ` + return composeConfig(testAccDxGatewayAssociationProposalConfigBase_vpnGateway(rName, rBgpAsn), ` resource "aws_dx_gateway_association_proposal" "test" { allowed_prefixes = ["10.0.0.0/16"] dx_gateway_id = aws_dx_gateway.test.id dx_gateway_owner_account_id = aws_dx_gateway.test.owner_account_id associated_gateway_id = aws_vpn_gateway.test.id } -` +`) } func testAccDxGatewayAssociationProposalConfigAllowedPrefixes2(rName string, rBgpAsn int) string { - return testAccDxGatewayAssociationProposalConfigBase_vpnGateway(rName, rBgpAsn) + ` + return composeConfig(testAccDxGatewayAssociationProposalConfigBase_vpnGateway(rName, rBgpAsn), ` resource "aws_dx_gateway_association_proposal" "test" { allowed_prefixes = ["10.0.0.0/24", "10.0.1.0/24"] dx_gateway_id = aws_dx_gateway.test.id dx_gateway_owner_account_id = aws_dx_gateway.test.owner_account_id associated_gateway_id = aws_vpn_gateway.test.id } -` +`) } diff --git a/aws/resource_aws_dx_gateway_association_test.go b/aws/resource_aws_dx_gateway_association_test.go index d85ed68d5bab..ff4229816343 100644 --- a/aws/resource_aws_dx_gateway_association_test.go +++ b/aws/resource_aws_dx_gateway_association_test.go @@ -490,7 +490,7 @@ func TestAccAwsDxGatewayAssociation_recreateProposal(t *testing.T) { Config: testAccDxGatewayAssociationConfig_basicVpnGatewayCrossAccount(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName, &ga1, &gap1), - testAccCheckAwsDxGatewayAssociationProposalDisappears(&gap1), + testAccCheckResourceDisappears(testAccProvider, resourceAwsDxGatewayAssociationProposal(), resourceName), ), ExpectNonEmptyPlan: true, }, @@ -498,8 +498,8 @@ func TestAccAwsDxGatewayAssociation_recreateProposal(t *testing.T) { Config: testAccDxGatewayAssociationConfig_basicVpnGatewayCrossAccount(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName, &ga2, &gap2), - testAccCheckAwsDxGatewayAssociationSameAssociation(&ga1, &ga2), - testAccCheckAwsDxGatewayAssociationDifferentProposal(&gap1, &gap2), + testAccCheckAwsDxGatewayAssociationNotRecreated(&ga1, &ga2), + testAccCheckAwsDxGatewayAssociationProposalRecreated(&gap1, &gap2), ), }, }, @@ -575,20 +575,10 @@ func testAccCheckAwsDxGatewayAssociationExists(name string, ga *directconnect.Ga } } -func testAccCheckAwsDxGatewayAssociationSameAssociation(ga1, ga2 *directconnect.GatewayAssociation) resource.TestCheckFunc { +func testAccCheckAwsDxGatewayAssociationNotRecreated(old, new *directconnect.GatewayAssociation) resource.TestCheckFunc { return func(s *terraform.State) error { - if aws.StringValue(ga1.AssociationId) != aws.StringValue(ga2.AssociationId) { - return fmt.Errorf("Association IDs differ") - } - - return nil - } -} - -func testAccCheckAwsDxGatewayAssociationDifferentProposal(gap1, gap2 *directconnect.GatewayAssociationProposal) resource.TestCheckFunc { - return func(s *terraform.State) error { - if aws.StringValue(gap1.ProposalId) == aws.StringValue(gap2.ProposalId) { - return fmt.Errorf("Proposals IDs are equal") + if old, new := aws.StringValue(old.AssociationId), aws.StringValue(new.AssociationId); old == new { + return fmt.Errorf("Direct Connect Gateway Association (%s) recreated (%s)", old, new) } return nil From 847d868699f34843817e1baa3e7cf40f0f404628 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 8 Jul 2021 11:39:18 -0400 Subject: [PATCH 20/27] Fix 'errcheck' linter errors. --- aws/resource_aws_dx_gateway_association_proposal_test.go | 2 +- aws/resource_aws_dx_gateway_association_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aws/resource_aws_dx_gateway_association_proposal_test.go b/aws/resource_aws_dx_gateway_association_proposal_test.go index de71096ffd73..c77eed169a12 100644 --- a/aws/resource_aws_dx_gateway_association_proposal_test.go +++ b/aws/resource_aws_dx_gateway_association_proposal_test.go @@ -35,7 +35,7 @@ func testSweepDirectConnectGatewayAssociationProposals(region string) error { var sweeperErrs *multierror.Error sweepResources := make([]*testSweepResource, 0) - lister.DescribeDirectConnectGatewayAssociationProposalsPages(conn, input, func(page *directconnect.DescribeDirectConnectGatewayAssociationProposalsOutput, lastPage bool) bool { + err = lister.DescribeDirectConnectGatewayAssociationProposalsPages(conn, input, func(page *directconnect.DescribeDirectConnectGatewayAssociationProposalsOutput, lastPage bool) bool { if page == nil { return !lastPage } diff --git a/aws/resource_aws_dx_gateway_association_test.go b/aws/resource_aws_dx_gateway_association_test.go index ff4229816343..c57fc0efaacd 100644 --- a/aws/resource_aws_dx_gateway_association_test.go +++ b/aws/resource_aws_dx_gateway_association_test.go @@ -40,7 +40,7 @@ func testSweepDirectConnectGatewayAssociations(region string) error { var sweeperErrs *multierror.Error sweepResources := make([]*testSweepResource, 0) - lister.DescribeDirectConnectGatewaysPages(conn, input, func(page *directconnect.DescribeDirectConnectGatewaysOutput, lastPage bool) bool { + err = lister.DescribeDirectConnectGatewaysPages(conn, input, func(page *directconnect.DescribeDirectConnectGatewaysOutput, lastPage bool) bool { if page == nil { return !lastPage } From 1a884d2a7934a7ab746e26403b6a061a5872a91c Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 8 Jul 2021 11:59:46 -0400 Subject: [PATCH 21/27] r/aws_dx_gateway_association: Use internal waiter package. --- aws/resource_aws_dx_gateway_association.go | 61 ++++------------------ 1 file changed, 11 insertions(+), 50 deletions(-) diff --git a/aws/resource_aws_dx_gateway_association.go b/aws/resource_aws_dx_gateway_association.go index f9f6ea4d1b5b..19ba70b50bf0 100644 --- a/aws/resource_aws_dx_gateway_association.go +++ b/aws/resource_aws_dx_gateway_association.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/directconnect/waiter" ) const ( @@ -23,6 +24,7 @@ func resourceAwsDxGatewayAssociation() *schema.Resource { Read: resourceAwsDxGatewayAssociationRead, Update: resourceAwsDxGatewayAssociationUpdate, Delete: resourceAwsDxGatewayAssociationDelete, + Importer: &schema.ResourceImporter{ State: resourceAwsDxGatewayAssociationImport, }, @@ -50,6 +52,7 @@ func resourceAwsDxGatewayAssociation() *schema.Resource { Computed: true, ForceNew: true, ConflictsWith: []string{"associated_gateway_owner_account_id", "proposal_id"}, + AtLeastOneOf: []string{"associated_gateway_id", "associated_gateway_owner_account_id", "proposal_id"}, }, "associated_gateway_owner_account_id": { @@ -59,6 +62,8 @@ func resourceAwsDxGatewayAssociation() *schema.Resource { ForceNew: true, ValidateFunc: validateAwsAccountId, ConflictsWith: []string{"associated_gateway_id"}, + RequiredWith: []string{"proposal_id"}, + AtLeastOneOf: []string{"associated_gateway_id", "associated_gateway_owner_account_id", "proposal_id"}, }, "associated_gateway_type": { @@ -86,6 +91,7 @@ func resourceAwsDxGatewayAssociation() *schema.Resource { Type: schema.TypeString, Optional: true, ConflictsWith: []string{"associated_gateway_id", "vpn_gateway_id"}, + AtLeastOneOf: []string{"associated_gateway_id", "associated_gateway_owner_account_id", "proposal_id"}, }, "vpn_gateway_id": { @@ -161,8 +167,8 @@ func resourceAwsDxGatewayAssociationCreate(d *schema.ResourceData, meta interfac } d.Set("dx_gateway_association_id", associationId) - if err := waitForDirectConnectGatewayAssociationAvailabilityOnCreate(conn, associationId, d.Timeout(schema.TimeoutCreate)); err != nil { - return fmt.Errorf("error waiting for Direct Connect gateway association (%s) to become available: %s", d.Id(), err) + if _, err := waiter.GatewayAssociationCreated(conn, associationId, d.Timeout(schema.TimeoutCreate)); err != nil { + return fmt.Errorf("error waiting for Direct Connect Gateway Association (%s) to create: %w", d.Id(), err) } return resourceAwsDxGatewayAssociationRead(d, meta) @@ -223,8 +229,8 @@ func resourceAwsDxGatewayAssociationUpdate(d *schema.ResourceData, meta interfac return fmt.Errorf("error updating Direct Connect gateway association (%s): %s", d.Id(), err) } - if err := waitForDirectConnectGatewayAssociationAvailabilityOnUpdate(conn, associationId, d.Timeout(schema.TimeoutUpdate)); err != nil { - return fmt.Errorf("error waiting for Direct Connect gateway association (%s) to become available: %s", d.Id(), err) + if _, err := waiter.GatewayAssociationUpdated(conn, associationId, d.Timeout(schema.TimeoutUpdate)); err != nil { + return fmt.Errorf("error waiting for Direct Connect Gateway Association (%s) to update: %w", d.Id(), err) } } @@ -249,7 +255,7 @@ func resourceAwsDxGatewayAssociationDelete(d *schema.ResourceData, meta interfac return fmt.Errorf("error deleting Direct Connect Gateway Association (%s): %w", d.Id(), err) } - if err := waitForDirectConnectGatewayAssociationDeletion(conn, associationID, d.Timeout(schema.TimeoutDelete)); err != nil { + if _, err := waiter.GatewayAssociationDeleted(conn, associationID, d.Timeout(schema.TimeoutDelete)); err != nil { return fmt.Errorf("error waiting for Direct Connect Gateway Association (%s) to delete: %w", d.Id(), err) } @@ -323,48 +329,3 @@ func dxGatewayAssociationStateRefresh(conn *directconnect.DirectConnect, associa func dxGatewayAssociationId(dxgwId, gwId string) string { return fmt.Sprintf("ga-%s%s", dxgwId, gwId) } - -func waitForDirectConnectGatewayAssociationAvailabilityOnCreate(conn *directconnect.DirectConnect, associationId string, timeout time.Duration) error { - stateConf := &resource.StateChangeConf{ - Pending: []string{directconnect.GatewayAssociationStateAssociating}, - Target: []string{directconnect.GatewayAssociationStateAssociated}, - Refresh: dxGatewayAssociationStateRefresh(conn, associationId), - Timeout: timeout, - Delay: 10 * time.Second, - MinTimeout: 5 * time.Second, - } - - _, err := stateConf.WaitForState() - - return err -} - -func waitForDirectConnectGatewayAssociationAvailabilityOnUpdate(conn *directconnect.DirectConnect, associationId string, timeout time.Duration) error { - stateConf := &resource.StateChangeConf{ - Pending: []string{directconnect.GatewayAssociationStateUpdating}, - Target: []string{directconnect.GatewayAssociationStateAssociated}, - Refresh: dxGatewayAssociationStateRefresh(conn, associationId), - Timeout: timeout, - Delay: 10 * time.Second, - MinTimeout: 5 * time.Second, - } - - _, err := stateConf.WaitForState() - - return err -} - -func waitForDirectConnectGatewayAssociationDeletion(conn *directconnect.DirectConnect, associationId string, timeout time.Duration) error { - stateConf := &resource.StateChangeConf{ - Pending: []string{directconnect.GatewayAssociationStateDisassociating}, - Target: []string{directconnect.GatewayAssociationStateDisassociated, gatewayAssociationStateDeleted}, - Refresh: dxGatewayAssociationStateRefresh(conn, associationId), - Timeout: timeout, - Delay: 10 * time.Second, - MinTimeout: 5 * time.Second, - } - - _, err := stateConf.WaitForState() - - return err -} From ccddd60945c379e41167259fa62cf402a6a7c1ef Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 8 Jul 2021 12:43:42 -0400 Subject: [PATCH 22/27] r/aws_dx_gateway_association: Use internal finder and waiter packages. --- .../service/directconnect/finder/finder.go | 9 + aws/resource_aws_dx_gateway_association.go | 213 +++++++----------- ...urce_aws_dx_gateway_association_migrate.go | 17 +- ...rce_aws_dx_gateway_association_proposal.go | 4 +- ...ws_dx_gateway_association_proposal_test.go | 2 +- ...esource_aws_dx_gateway_association_test.go | 91 ++++---- 6 files changed, 141 insertions(+), 195 deletions(-) diff --git a/aws/internal/service/directconnect/finder/finder.go b/aws/internal/service/directconnect/finder/finder.go index 9a97f05d34d1..a688455108f5 100644 --- a/aws/internal/service/directconnect/finder/finder.go +++ b/aws/internal/service/directconnect/finder/finder.go @@ -23,6 +23,15 @@ func GatewayAssociationByDirectConnectGatewayIDAndAssociatedGatewayID(conn *dire return GatewayAssociation(conn, input) } +func GatewayAssociationByDirectConnectGatewayIDAndVirtualGatewayID(conn *directconnect.DirectConnect, directConnectGatewayID, virtualGatewayID string) (*directconnect.GatewayAssociation, error) { + input := &directconnect.DescribeDirectConnectGatewayAssociationsInput{ + DirectConnectGatewayId: aws.String(directConnectGatewayID), + VirtualGatewayId: aws.String(virtualGatewayID), + } + + return GatewayAssociation(conn, input) +} + func GatewayAssociation(conn *directconnect.DirectConnect, input *directconnect.DescribeDirectConnectGatewayAssociationsInput) (*directconnect.GatewayAssociation, error) { output, err := conn.DescribeDirectConnectGatewayAssociations(input) diff --git a/aws/resource_aws_dx_gateway_association.go b/aws/resource_aws_dx_gateway_association.go index 19ba70b50bf0..a11e6276b916 100644 --- a/aws/resource_aws_dx_gateway_association.go +++ b/aws/resource_aws_dx_gateway_association.go @@ -9,13 +9,11 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/directconnect" "github.com/hashicorp/aws-sdk-go-base/tfawserr" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + tfdirectconnect "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/directconnect" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/directconnect/finder" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/directconnect/waiter" -) - -const ( - gatewayAssociationStateDeleted = "deleted" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) func resourceAwsDxGatewayAssociation() *schema.Resource { @@ -114,60 +112,57 @@ func resourceAwsDxGatewayAssociation() *schema.Resource { func resourceAwsDxGatewayAssociationCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).dxconn - dxgwId := d.Get("dx_gateway_id").(string) - gwIdRaw, gwIdOk := d.GetOk("associated_gateway_id") - gwAcctIdRaw, gwAcctIdOk := d.GetOk("associated_gateway_owner_account_id") - proposalIdRaw, proposalIdOk := d.GetOk("proposal_id") + var associationID string + directConnectGatewayID := d.Get("dx_gateway_id").(string) - if gwAcctIdOk || proposalIdOk { - // Cross-account association. - if !(gwAcctIdOk && proposalIdOk) { - return fmt.Errorf("associated_gateway_owner_account_id and proposal_id must be configured") + if associatedGatewayOwnerAccount := d.Get("associated_gateway_owner_account_id").(string); associatedGatewayOwnerAccount != "" { + proposalID := d.Get("proposal_id").(string) + input := &directconnect.AcceptDirectConnectGatewayAssociationProposalInput{ + AssociatedGatewayOwnerAccount: aws.String(associatedGatewayOwnerAccount), + DirectConnectGatewayId: aws.String(directConnectGatewayID), + ProposalId: aws.String(proposalID), } - } else if !gwIdOk { - return fmt.Errorf("either associated_gateway_owner_account_id and proposal_id, or associated_gateway_id, must be configured") - } - associationId := "" - if gwAcctIdOk { - req := &directconnect.AcceptDirectConnectGatewayAssociationProposalInput{ - AssociatedGatewayOwnerAccount: aws.String(gwAcctIdRaw.(string)), - DirectConnectGatewayId: aws.String(dxgwId), - OverrideAllowedPrefixesToDirectConnectGateway: expandDxRouteFilterPrefixes(d.Get("allowed_prefixes").(*schema.Set)), - ProposalId: aws.String(proposalIdRaw.(string)), + if v, ok := d.GetOk("allowed_prefixes"); ok && v.(*schema.Set).Len() > 0 { + input.OverrideAllowedPrefixesToDirectConnectGateway = expandDirectConnectRouteFilterPrefixes(v.(*schema.Set).List()) } - log.Printf("[DEBUG] Accepting Direct Connect gateway association proposal: %#v", req) - resp, err := conn.AcceptDirectConnectGatewayAssociationProposal(req) + log.Printf("[DEBUG] Accepting Direct Connect Gateway Association Proposal: %s", input) + output, err := conn.AcceptDirectConnectGatewayAssociationProposal(input) + if err != nil { - return fmt.Errorf("error accepting Direct Connect gateway association proposal: %s", err) + return fmt.Errorf("error accepting Direct Connect Gateway Association Proposal (%s): %w", proposalID, err) } // For historical reasons the resource ID isn't set to the association ID returned from the API. - associationId = aws.StringValue(resp.DirectConnectGatewayAssociation.AssociationId) - d.SetId(dxGatewayAssociationId(dxgwId, aws.StringValue(resp.DirectConnectGatewayAssociation.AssociatedGateway.Id))) + associationID = aws.StringValue(output.DirectConnectGatewayAssociation.AssociationId) + d.SetId(tfdirectconnect.GatewayAssociationCreateResourceID(directConnectGatewayID, aws.StringValue(output.DirectConnectGatewayAssociation.AssociatedGateway.Id))) } else { - gwId := gwIdRaw.(string) + associatedGatewayID := d.Get("associated_gateway_id").(string) + input := &directconnect.CreateDirectConnectGatewayAssociationInput{ + DirectConnectGatewayId: aws.String(directConnectGatewayID), + GatewayId: aws.String(associatedGatewayID), + } - req := &directconnect.CreateDirectConnectGatewayAssociationInput{ - AddAllowedPrefixesToDirectConnectGateway: expandDxRouteFilterPrefixes(d.Get("allowed_prefixes").(*schema.Set)), - DirectConnectGatewayId: aws.String(dxgwId), - GatewayId: aws.String(gwId), + if v, ok := d.GetOk("allowed_prefixes"); ok && v.(*schema.Set).Len() > 0 { + input.AddAllowedPrefixesToDirectConnectGateway = expandDirectConnectRouteFilterPrefixes(v.(*schema.Set).List()) } - log.Printf("[DEBUG] Creating Direct Connect gateway association: %#v", req) - resp, err := conn.CreateDirectConnectGatewayAssociation(req) + log.Printf("[DEBUG] Creating Direct Connect Gateway Association: %s", input) + output, err := conn.CreateDirectConnectGatewayAssociation(input) + if err != nil { - return fmt.Errorf("error creating Direct Connect gateway association: %s", err) + return fmt.Errorf("error creating Direct Connect Gateway Association (%s/%s): %w", directConnectGatewayID, associatedGatewayID, err) } // For historical reasons the resource ID isn't set to the association ID returned from the API. - associationId = aws.StringValue(resp.DirectConnectGatewayAssociation.AssociationId) - d.SetId(dxGatewayAssociationId(dxgwId, gwId)) + associationID = aws.StringValue(output.DirectConnectGatewayAssociation.AssociationId) + d.SetId(tfdirectconnect.GatewayAssociationCreateResourceID(directConnectGatewayID, associatedGatewayID)) } - d.Set("dx_gateway_association_id", associationId) - if _, err := waiter.GatewayAssociationCreated(conn, associationId, d.Timeout(schema.TimeoutCreate)); err != nil { + d.Set("dx_gateway_association_id", associationID) + + if _, err := waiter.GatewayAssociationCreated(conn, associationID, d.Timeout(schema.TimeoutCreate)); err != nil { return fmt.Errorf("error waiting for Direct Connect Gateway Association (%s) to create: %w", d.Id(), err) } @@ -177,30 +172,30 @@ func resourceAwsDxGatewayAssociationCreate(d *schema.ResourceData, meta interfac func resourceAwsDxGatewayAssociationRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).dxconn - associationId := d.Get("dx_gateway_association_id").(string) - assocRaw, state, err := dxGatewayAssociationStateRefresh(conn, associationId)() - if err != nil { - return fmt.Errorf("error reading Direct Connect gateway association (%s): %s", d.Id(), err) - } - if state == gatewayAssociationStateDeleted { - log.Printf("[WARN] Direct Connect gateway association (%s) not found, removing from state", d.Id()) + associationID := d.Get("dx_gateway_association_id").(string) + + output, err := finder.GatewayAssociationByID(conn, associationID) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Direct Connect Gateway Association (%s) not found, removing from state", d.Id()) d.SetId("") return nil } - assoc := assocRaw.(*directconnect.GatewayAssociation) - - err = d.Set("allowed_prefixes", flattenDxRouteFilterPrefixes(assoc.AllowedPrefixesToDirectConnectGateway)) if err != nil { - return fmt.Errorf("error setting allowed_prefixes: %s", err) + return fmt.Errorf("error reading Direct Connect Gateway Association (%s): %w", d.Id(), err) + } + + if err := d.Set("allowed_prefixes", flattenDirectConnectRouteFilterPrefixes(output.AllowedPrefixesToDirectConnectGateway)); err != nil { + return fmt.Errorf("error setting allowed_prefixes: %w", err) } - d.Set("associated_gateway_id", assoc.AssociatedGateway.Id) - d.Set("associated_gateway_owner_account_id", assoc.AssociatedGateway.OwnerAccount) - d.Set("associated_gateway_type", assoc.AssociatedGateway.Type) - d.Set("dx_gateway_association_id", assoc.AssociationId) - d.Set("dx_gateway_id", assoc.DirectConnectGatewayId) - d.Set("dx_gateway_owner_account_id", assoc.DirectConnectGatewayOwnerAccount) + d.Set("associated_gateway_id", output.AssociatedGateway.Id) + d.Set("associated_gateway_owner_account_id", output.AssociatedGateway.OwnerAccount) + d.Set("associated_gateway_type", output.AssociatedGateway.Type) + d.Set("dx_gateway_association_id", output.AssociationId) + d.Set("dx_gateway_id", output.DirectConnectGatewayId) + d.Set("dx_gateway_owner_account_id", output.DirectConnectGatewayOwnerAccount) return nil } @@ -208,30 +203,31 @@ func resourceAwsDxGatewayAssociationRead(d *schema.ResourceData, meta interface{ func resourceAwsDxGatewayAssociationUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).dxconn - if d.HasChange("allowed_prefixes") { - associationId := d.Get("dx_gateway_association_id").(string) + associationID := d.Get("dx_gateway_association_id").(string) + input := &directconnect.UpdateDirectConnectGatewayAssociationInput{ + AssociationId: aws.String(associationID), + } - oraw, nraw := d.GetChange("allowed_prefixes") - o := oraw.(*schema.Set) - n := nraw.(*schema.Set) - del := o.Difference(n) - add := n.Difference(o) + oraw, nraw := d.GetChange("allowed_prefixes") + o, n := oraw.(*schema.Set), nraw.(*schema.Set) - req := &directconnect.UpdateDirectConnectGatewayAssociationInput{ - AddAllowedPrefixesToDirectConnectGateway: expandDxRouteFilterPrefixes(add), - AssociationId: aws.String(associationId), - RemoveAllowedPrefixesToDirectConnectGateway: expandDxRouteFilterPrefixes(del), - } + if add := n.Difference(o); add.Len() > 0 { + input.AddAllowedPrefixesToDirectConnectGateway = expandDirectConnectRouteFilterPrefixes(add.List()) + } - log.Printf("[DEBUG] Updating Direct Connect gateway association: %#v", req) - _, err := conn.UpdateDirectConnectGatewayAssociation(req) - if err != nil { - return fmt.Errorf("error updating Direct Connect gateway association (%s): %s", d.Id(), err) - } + if del := o.Difference(n); del.Len() > 0 { + input.RemoveAllowedPrefixesToDirectConnectGateway = expandDirectConnectRouteFilterPrefixes(del.List()) + } - if _, err := waiter.GatewayAssociationUpdated(conn, associationId, d.Timeout(schema.TimeoutUpdate)); err != nil { - return fmt.Errorf("error waiting for Direct Connect Gateway Association (%s) to update: %w", d.Id(), err) - } + log.Printf("[DEBUG] Updating Direct Connect Gateway Association: %s", input) + _, err := conn.UpdateDirectConnectGatewayAssociation(input) + + if err != nil { + return fmt.Errorf("error updating Direct Connect Gateway Association (%s): %w", d.Id(), err) + } + + if _, err := waiter.GatewayAssociationUpdated(conn, associationID, d.Timeout(schema.TimeoutUpdate)); err != nil { + return fmt.Errorf("error waiting for Direct Connect Gateway Association (%s) to update: %w", d.Id(), err) } return resourceAwsDxGatewayAssociationRead(d, meta) @@ -263,69 +259,26 @@ func resourceAwsDxGatewayAssociationDelete(d *schema.ResourceData, meta interfac } func resourceAwsDxGatewayAssociationImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + conn := meta.(*AWSClient).dxconn + parts := strings.Split(d.Id(), "/") + if len(parts) != 2 { - return []*schema.ResourceData{}, fmt.Errorf("Wrong format of resource: %s. Please follow 'dx-gw-id/gw-id'", d.Id()) + return nil, fmt.Errorf("Incorrect resource ID format: %q. Expected DXGATEWAYID/ASSOCIATEDGATEWAYID", d.Id()) } - dxgwId := parts[0] - gwId := parts[1] - id := dxGatewayAssociationId(dxgwId, gwId) - log.Printf("[DEBUG] Importing Direct Connect gateway association %s/%s", dxgwId, gwId) + directConnectGatewayID := parts[0] + associatedGatewayID := parts[1] - conn := meta.(*AWSClient).dxconn + output, err := finder.GatewayAssociationByDirectConnectGatewayIDAndAssociatedGatewayID(conn, directConnectGatewayID, associatedGatewayID) - resp, err := conn.DescribeDirectConnectGatewayAssociations(&directconnect.DescribeDirectConnectGatewayAssociationsInput{ - AssociatedGatewayId: aws.String(gwId), - DirectConnectGatewayId: aws.String(dxgwId), - }) if err != nil { return nil, err } - if n := len(resp.DirectConnectGatewayAssociations); n != 1 { - return nil, fmt.Errorf("Found %d Direct Connect gateway associations for %s, expected 1", n, id) - } - d.SetId(id) - d.Set("dx_gateway_id", resp.DirectConnectGatewayAssociations[0].DirectConnectGatewayId) - d.Set("dx_gateway_association_id", resp.DirectConnectGatewayAssociations[0].AssociationId) + d.SetId(tfdirectconnect.GatewayAssociationCreateResourceID(directConnectGatewayID, associatedGatewayID)) + d.Set("dx_gateway_id", output.DirectConnectGatewayId) + d.Set("dx_gateway_association_id", output.AssociationId) return []*schema.ResourceData{d}, nil } - -func dxGatewayAssociationStateRefresh(conn *directconnect.DirectConnect, associationId string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - resp, err := conn.DescribeDirectConnectGatewayAssociations(&directconnect.DescribeDirectConnectGatewayAssociationsInput{ - AssociationId: aws.String(associationId), - }) - if err != nil { - return nil, "", err - } - - n := len(resp.DirectConnectGatewayAssociations) - switch n { - case 0: - return "", gatewayAssociationStateDeleted, nil - - case 1: - assoc := resp.DirectConnectGatewayAssociations[0] - - if stateChangeError := aws.StringValue(assoc.StateChangeError); stateChangeError != "" { - id := dxGatewayAssociationId( - aws.StringValue(resp.DirectConnectGatewayAssociations[0].DirectConnectGatewayId), - aws.StringValue(resp.DirectConnectGatewayAssociations[0].AssociatedGateway.Id)) - log.Printf("[INFO] Direct Connect gateway association (%s) state change error: %s", id, stateChangeError) - } - - return assoc, aws.StringValue(assoc.AssociationState), nil - - default: - return nil, "", fmt.Errorf("Found %d Direct Connect gateway associations for %s, expected 1", n, associationId) - } - } -} - -// Terraform resource ID. -func dxGatewayAssociationId(dxgwId, gwId string) string { - return fmt.Sprintf("ga-%s%s", dxgwId, gwId) -} diff --git a/aws/resource_aws_dx_gateway_association_migrate.go b/aws/resource_aws_dx_gateway_association_migrate.go index 2fd6cfd57b4c..1920c97153e0 100644 --- a/aws/resource_aws_dx_gateway_association_migrate.go +++ b/aws/resource_aws_dx_gateway_association_migrate.go @@ -2,12 +2,11 @@ package aws import ( "context" - "fmt" "log" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/directconnect" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/directconnect/finder" ) func resourceAwsDxGatewayAssociationResourceV0() *schema.Resource { @@ -78,23 +77,17 @@ func resourceAwsDxGatewayAssociationResourceV0() *schema.Resource { func resourceAwsDxGatewayAssociationStateUpgradeV0(_ context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { conn := meta.(*AWSClient).dxconn - log.Println("[INFO] Found Direct Connect gateway association state v0; migrating to v1") + log.Println("[INFO] Found Direct Connect Gateway Association state v0; migrating to v1") // dx_gateway_association_id was introduced in v2.8.0. Handle the case where it's not yet present. if v, ok := rawState["dx_gateway_association_id"]; !ok || v == nil { - resp, err := conn.DescribeDirectConnectGatewayAssociations(&directconnect.DescribeDirectConnectGatewayAssociationsInput{ - DirectConnectGatewayId: aws.String(rawState["dx_gateway_id"].(string)), - VirtualGatewayId: aws.String(rawState["vpn_gateway_id"].(string)), - }) + output, err := finder.GatewayAssociationByDirectConnectGatewayIDAndVirtualGatewayID(conn, rawState["dx_gateway_id"].(string), rawState["vpn_gateway_id"].(string)) + if err != nil { return nil, err } - if len(resp.DirectConnectGatewayAssociations) == 0 { - return nil, fmt.Errorf("Direct Connect gateway association not found, remove from state using 'terraform state rm'") - } - - rawState["dx_gateway_association_id"] = aws.StringValue(resp.DirectConnectGatewayAssociations[0].AssociationId) + rawState["dx_gateway_association_id"] = aws.StringValue(output.AssociationId) } return rawState, nil diff --git a/aws/resource_aws_dx_gateway_association_proposal.go b/aws/resource_aws_dx_gateway_association_proposal.go index b5fc3d409971..07be87a10da8 100644 --- a/aws/resource_aws_dx_gateway_association_proposal.go +++ b/aws/resource_aws_dx_gateway_association_proposal.go @@ -242,7 +242,7 @@ func resourceAwsDxGatewayAssociationProposalImport(d *schema.ResourceData, meta associatedGatewayID := parts[2] if proposalID == "" || directConnectGatewayID == "" || associatedGatewayID == "" { - return nil, fmt.Errorf("Incorrect resource ID format: %q. PROPOSALID, DXGATEWAYID and TARGETGATEWAYID must not be empty strings", d.Id()) + return nil, fmt.Errorf("Incorrect resource ID format: %q. PROPOSALID, DXGATEWAYID and ASSOCIATEDGATEWAYID must not be empty strings", d.Id()) } // Use pseudo-proposal ID and actual DirectConnectGatewayId and AssociatedGatewayId. @@ -251,7 +251,7 @@ func resourceAwsDxGatewayAssociationProposalImport(d *schema.ResourceData, meta d.Set("dx_gateway_id", directConnectGatewayID) default: - return nil, fmt.Errorf("Incorrect resource ID format: %q. Expected PROPOSALID or PROPOSALID/DXGATEWAYID/TARGETGATEWAYID", d.Id()) + return nil, fmt.Errorf("Incorrect resource ID format: %q. Expected PROPOSALID or PROPOSALID/DXGATEWAYID/ASSOCIATEDGATEWAYID", d.Id()) } return []*schema.ResourceData{d}, nil diff --git a/aws/resource_aws_dx_gateway_association_proposal_test.go b/aws/resource_aws_dx_gateway_association_proposal_test.go index c77eed169a12..a696eddccb37 100644 --- a/aws/resource_aws_dx_gateway_association_proposal_test.go +++ b/aws/resource_aws_dx_gateway_association_proposal_test.go @@ -324,7 +324,7 @@ func testAccCheckAwsDxGatewayAssociationProposalExists(resourceName string, gate } if rs.Primary.ID == "" { - return fmt.Errorf("No Direct Connect Gateway Association Proposal ID is set") + return fmt.Errorf("No ID is set") } conn := testAccProvider.Meta().(*AWSClient).dxconn diff --git a/aws/resource_aws_dx_gateway_association_test.go b/aws/resource_aws_dx_gateway_association_test.go index c57fc0efaacd..cbfe01d13035 100644 --- a/aws/resource_aws_dx_gateway_association_test.go +++ b/aws/resource_aws_dx_gateway_association_test.go @@ -208,14 +208,14 @@ func TestAccAwsDxGatewayAssociation_basicVpnGatewaySingleAccount(t *testing.T) { Config: testAccDxGatewayAssociationConfig_basicVpnGatewaySingleAccount(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName, &ga, &gap), - resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "allowed_prefixes.*", "10.255.255.0/28"), resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameVgw, "id"), - resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), - resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "virtualPrivateGateway"), testAccCheckResourceAttrAccountID(resourceName, "associated_gateway_owner_account_id"), + resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "virtualPrivateGateway"), + resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), + resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), testAccCheckResourceAttrAccountID(resourceName, "dx_gateway_owner_account_id"), - resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "1"), - resource.TestCheckTypeSetElemAttr(resourceName, "allowed_prefixes.*", "10.255.255.0/28"), ), }, { @@ -239,10 +239,7 @@ func TestAccAwsDxGatewayAssociation_basicVpnGatewayCrossAccount(t *testing.T) { var gap directconnect.GatewayAssociationProposal resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - testAccAlternateAccountPreCheck(t) - }, + PreCheck: func() { testAccPreCheck(t); testAccAlternateAccountPreCheck(t) }, ErrorCheck: testAccErrorCheck(t, directconnect.EndpointsID), ProviderFactories: testAccProviderFactoriesAlternate(&providers), CheckDestroy: testAccCheckAwsDxGatewayAssociationDestroy, @@ -251,15 +248,15 @@ func TestAccAwsDxGatewayAssociation_basicVpnGatewayCrossAccount(t *testing.T) { Config: testAccDxGatewayAssociationConfig_basicVpnGatewayCrossAccount(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName, &ga, &gap), - resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "allowed_prefixes.*", "10.255.255.0/28"), resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameVgw, "id"), - resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), - resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "virtualPrivateGateway"), testAccCheckResourceAttrAccountID(resourceName, "associated_gateway_owner_account_id"), + resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "virtualPrivateGateway"), + resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), + resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), // dx_gateway_owner_account_id is the "awsalternate" provider's account ID. // testAccCheckResourceAttrAccountID(resourceName, "dx_gateway_owner_account_id"), - resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "1"), - resource.TestCheckTypeSetElemAttr(resourceName, "allowed_prefixes.*", "10.255.255.0/28"), ), }, }, @@ -285,15 +282,15 @@ func TestAccAwsDxGatewayAssociation_basicTransitGatewaySingleAccount(t *testing. Config: testAccDxGatewayAssociationConfig_basicTransitGatewaySingleAccount(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName, &ga, &gap), - resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), - resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameTgw, "id"), - resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), - resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "transitGateway"), - testAccCheckResourceAttrAccountID(resourceName, "associated_gateway_owner_account_id"), - testAccCheckResourceAttrAccountID(resourceName, "dx_gateway_owner_account_id"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "2"), resource.TestCheckTypeSetElemAttr(resourceName, "allowed_prefixes.*", "10.255.255.0/30"), resource.TestCheckTypeSetElemAttr(resourceName, "allowed_prefixes.*", "10.255.255.8/30"), + resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameTgw, "id"), + testAccCheckResourceAttrAccountID(resourceName, "associated_gateway_owner_account_id"), + resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "transitGateway"), + resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), + resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), + testAccCheckResourceAttrAccountID(resourceName, "dx_gateway_owner_account_id"), ), }, { @@ -317,10 +314,7 @@ func TestAccAwsDxGatewayAssociation_basicTransitGatewayCrossAccount(t *testing.T var gap directconnect.GatewayAssociationProposal resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - testAccAlternateAccountPreCheck(t) - }, + PreCheck: func() { testAccPreCheck(t); testAccAlternateAccountPreCheck(t) }, ErrorCheck: testAccErrorCheck(t, directconnect.EndpointsID), ProviderFactories: testAccProviderFactoriesAlternate(&providers), CheckDestroy: testAccCheckAwsDxGatewayAssociationDestroy, @@ -329,16 +323,16 @@ func TestAccAwsDxGatewayAssociation_basicTransitGatewayCrossAccount(t *testing.T Config: testAccDxGatewayAssociationConfig_basicTransitGatewayCrossAccount(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName, &ga, &gap), - resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "allowed_prefixes.*", "10.255.255.0/30"), + resource.TestCheckTypeSetElemAttr(resourceName, "allowed_prefixes.*", "10.255.255.8/30"), resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameTgw, "id"), - resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), - resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "transitGateway"), testAccCheckResourceAttrAccountID(resourceName, "associated_gateway_owner_account_id"), + resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "transitGateway"), + resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), + resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), // dx_gateway_owner_account_id is the "awsalternate" provider's account ID. // testAccCheckResourceAttrAccountID(resourceName, "dx_gateway_owner_account_id"), - resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "2"), - resource.TestCheckTypeSetElemAttr(resourceName, "allowed_prefixes.*", "10.255.255.0/30"), - resource.TestCheckTypeSetElemAttr(resourceName, "allowed_prefixes.*", "10.255.255.8/30"), ), }, }, @@ -364,12 +358,12 @@ func TestAccAwsDxGatewayAssociation_multiVpnGatewaysSingleAccount(t *testing.T) Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName1, &ga, &gap), testAccCheckAwsDxGatewayAssociationExists(resourceName2, &ga, &gap), - resource.TestCheckResourceAttrSet(resourceName1, "dx_gateway_association_id"), resource.TestCheckResourceAttr(resourceName1, "allowed_prefixes.#", "1"), resource.TestCheckTypeSetElemAttr(resourceName1, "allowed_prefixes.*", "10.255.255.0/28"), - resource.TestCheckResourceAttrSet(resourceName2, "dx_gateway_association_id"), + resource.TestCheckResourceAttrSet(resourceName1, "dx_gateway_association_id"), resource.TestCheckResourceAttr(resourceName2, "allowed_prefixes.#", "1"), resource.TestCheckTypeSetElemAttr(resourceName2, "allowed_prefixes.*", "10.255.255.16/28"), + resource.TestCheckResourceAttrSet(resourceName2, "dx_gateway_association_id"), ), }, }, @@ -395,12 +389,12 @@ func TestAccAwsDxGatewayAssociation_allowedPrefixesVpnGatewaySingleAccount(t *te Config: testAccDxGatewayAssociationConfig_allowedPrefixesVpnGatewaySingleAccount(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName, &ga, &gap), - resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), - resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameVgw, "id"), - resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "2"), resource.TestCheckTypeSetElemAttr(resourceName, "allowed_prefixes.*", "10.255.255.0/30"), resource.TestCheckTypeSetElemAttr(resourceName, "allowed_prefixes.*", "10.255.255.8/30"), + resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameVgw, "id"), + resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), + resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), ), }, { @@ -432,10 +426,7 @@ func TestAccAwsDxGatewayAssociation_allowedPrefixesVpnGatewayCrossAccount(t *tes var gap directconnect.GatewayAssociationProposal resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - testAccAlternateAccountPreCheck(t) - }, + PreCheck: func() { testAccPreCheck(t); testAccAlternateAccountPreCheck(t) }, ErrorCheck: testAccErrorCheck(t, directconnect.EndpointsID), ProviderFactories: testAccProviderFactoriesAlternate(&providers), CheckDestroy: testAccCheckAwsDxGatewayAssociationDestroy, @@ -444,11 +435,11 @@ func TestAccAwsDxGatewayAssociation_allowedPrefixesVpnGatewayCrossAccount(t *tes Config: testAccDxGatewayAssociationConfig_allowedPrefixesVpnGatewayCrossAccount(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName, &ga, &gap), - resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), - resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameVgw, "id"), - resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "1"), resource.TestCheckTypeSetElemAttr(resourceName, "allowed_prefixes.*", "10.255.255.8/29"), + resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameVgw, "id"), + resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), + resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), ), // Accepting the proposal with overridden prefixes changes the returned RequestedAllowedPrefixesToDirectConnectGateway value (allowed_prefixes attribute). ExpectNonEmptyPlan: true, @@ -457,12 +448,12 @@ func TestAccAwsDxGatewayAssociation_allowedPrefixesVpnGatewayCrossAccount(t *tes Config: testAccDxGatewayAssociationConfig_allowedPrefixesVpnGatewayCrossAccountUpdated(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName, &ga, &gap), - resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), - resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameVgw, "id"), - resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "2"), resource.TestCheckTypeSetElemAttr(resourceName, "allowed_prefixes.*", "10.255.255.0/30"), resource.TestCheckTypeSetElemAttr(resourceName, "allowed_prefixes.*", "10.255.255.8/30"), + resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameVgw, "id"), + resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), + resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), ), }, }, @@ -472,16 +463,14 @@ func TestAccAwsDxGatewayAssociation_allowedPrefixesVpnGatewayCrossAccount(t *tes func TestAccAwsDxGatewayAssociation_recreateProposal(t *testing.T) { var providers []*schema.Provider resourceName := "aws_dx_gateway_association.test" + resourceNameProposal := "aws_dx_gateway_association_proposal.test" rName := acctest.RandomWithPrefix("tf-acc-test") rBgpAsn := acctest.RandIntRange(64512, 65534) var ga1, ga2 directconnect.GatewayAssociation var gap1, gap2 directconnect.GatewayAssociationProposal resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - testAccAlternateAccountPreCheck(t) - }, + PreCheck: func() { testAccPreCheck(t); testAccAlternateAccountPreCheck(t) }, ErrorCheck: testAccErrorCheck(t, directconnect.EndpointsID), ProviderFactories: testAccProviderFactoriesAlternate(&providers), CheckDestroy: testAccCheckAwsDxGatewayAssociationDestroy, @@ -490,7 +479,7 @@ func TestAccAwsDxGatewayAssociation_recreateProposal(t *testing.T) { Config: testAccDxGatewayAssociationConfig_basicVpnGatewayCrossAccount(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName, &ga1, &gap1), - testAccCheckResourceDisappears(testAccProvider, resourceAwsDxGatewayAssociationProposal(), resourceName), + testAccCheckResourceDisappears(testAccProvider, resourceAwsDxGatewayAssociationProposal(), resourceNameProposal), ), ExpectNonEmptyPlan: true, }, @@ -592,6 +581,7 @@ func testAccCheckAwsDxGatewayAssociationStateUpgradeV0(name string) resource.Tes if !ok { return fmt.Errorf("Not found: %s", name) } + if rs.Primary.ID == "" { return fmt.Errorf("No ID is set") } @@ -602,6 +592,7 @@ func testAccCheckAwsDxGatewayAssociationStateUpgradeV0(name string) resource.Tes } updatedRawState, err := resourceAwsDxGatewayAssociationStateUpgradeV0(context.Background(), rawState, testAccProvider.Meta()) + if err != nil { return err } From 9a5d4db5492ab615d0cf4245908dcb1300fb4664 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 8 Jul 2021 15:03:06 -0400 Subject: [PATCH 23/27] r/aws_dx_gateway: Use internal finder and waiter packages. --- .../service/directconnect/finder/finder.go | 33 ++++ .../service/directconnect/waiter/status.go | 16 ++ .../service/directconnect/waiter/waiter.go | 38 ++++ aws/resource_aws_dx_gateway.go | 125 +++++-------- aws/resource_aws_dx_gateway_test.go | 166 +++++++++++------- 5 files changed, 232 insertions(+), 146 deletions(-) diff --git a/aws/internal/service/directconnect/finder/finder.go b/aws/internal/service/directconnect/finder/finder.go index a688455108f5..5bfca4c06d57 100644 --- a/aws/internal/service/directconnect/finder/finder.go +++ b/aws/internal/service/directconnect/finder/finder.go @@ -6,6 +6,39 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) +func GatewayByID(conn *directconnect.DirectConnect, id string) (*directconnect.Gateway, error) { + input := &directconnect.DescribeDirectConnectGatewaysInput{ + DirectConnectGatewayId: aws.String(id), + } + + output, err := conn.DescribeDirectConnectGateways(input) + + if err != nil { + return nil, err + } + + if output == nil || len(output.DirectConnectGateways) == 0 || output.DirectConnectGateways[0] == nil { + return nil, &resource.NotFoundError{ + Message: "Empty result", + LastRequest: input, + } + } + + // TODO Check for multiple results. + // TODO https://github.com/hashicorp/terraform-provider-aws/pull/17613. + + gateway := output.DirectConnectGateways[0] + + if state := aws.StringValue(gateway.DirectConnectGatewayState); state == directconnect.GatewayStateDeleted { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + return gateway, nil +} + func GatewayAssociationByID(conn *directconnect.DirectConnect, id string) (*directconnect.GatewayAssociation, error) { input := &directconnect.DescribeDirectConnectGatewayAssociationsInput{ AssociationId: aws.String(id), diff --git a/aws/internal/service/directconnect/waiter/status.go b/aws/internal/service/directconnect/waiter/status.go index 98e90d4097b4..2513c615b171 100644 --- a/aws/internal/service/directconnect/waiter/status.go +++ b/aws/internal/service/directconnect/waiter/status.go @@ -8,6 +8,22 @@ import ( "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) +func GatewayState(conn *directconnect.DirectConnect, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := finder.GatewayByID(conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.DirectConnectGatewayState), nil + } +} + func GatewayAssociationState(conn *directconnect.DirectConnect, id string) resource.StateRefreshFunc { return func() (interface{}, string, error) { output, err := finder.GatewayAssociationByID(conn, id) diff --git a/aws/internal/service/directconnect/waiter/waiter.go b/aws/internal/service/directconnect/waiter/waiter.go index ab54c49cb62a..01fe6ad79713 100644 --- a/aws/internal/service/directconnect/waiter/waiter.go +++ b/aws/internal/service/directconnect/waiter/waiter.go @@ -10,6 +10,44 @@ import ( "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) +func GatewayCreated(conn *directconnect.DirectConnect, id string, timeout time.Duration) (*directconnect.Gateway, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{directconnect.GatewayStatePending}, + Target: []string{directconnect.GatewayStateAvailable}, + Refresh: GatewayState(conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*directconnect.Gateway); ok { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.StateChangeError))) + + return output, err + } + + return nil, err +} + +func GatewayDeleted(conn *directconnect.DirectConnect, id string, timeout time.Duration) (*directconnect.Gateway, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{directconnect.GatewayStatePending, directconnect.GatewayStateAvailable, directconnect.GatewayStateDeleting}, + Target: []string{}, + Refresh: GatewayState(conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*directconnect.Gateway); ok { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.StateChangeError))) + + return output, err + } + + return nil, err +} + func GatewayAssociationCreated(conn *directconnect.DirectConnect, id string, timeout time.Duration) (*directconnect.GatewayAssociation, error) { stateConf := &resource.StateChangeConf{ Pending: []string{directconnect.GatewayAssociationStateAssociating}, diff --git a/aws/resource_aws_dx_gateway.go b/aws/resource_aws_dx_gateway.go index 8a2b70de3098..e27598f9cca9 100644 --- a/aws/resource_aws_dx_gateway.go +++ b/aws/resource_aws_dx_gateway.go @@ -8,8 +8,11 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/directconnect" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/directconnect/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/directconnect/waiter" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) func resourceAwsDxGateway() *schema.Resource { @@ -17,22 +20,25 @@ func resourceAwsDxGateway() *schema.Resource { Create: resourceAwsDxGatewayCreate, Read: resourceAwsDxGatewayRead, Delete: resourceAwsDxGatewayDelete, + Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, "amazon_side_asn": { Type: schema.TypeString, Required: true, ForceNew: true, ValidateFunc: validateAmazonSideAsn, }, + + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "owner_account_id": { Type: schema.TypeString, Computed: true, @@ -49,36 +55,28 @@ func resourceAwsDxGateway() *schema.Resource { func resourceAwsDxGatewayCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).dxconn - req := &directconnect.CreateDirectConnectGatewayInput{ - DirectConnectGatewayName: aws.String(d.Get("name").(string)), + name := d.Get("name").(string) + input := &directconnect.CreateDirectConnectGatewayInput{ + DirectConnectGatewayName: aws.String(name), } - if asn, ok := d.GetOk("amazon_side_asn"); ok { - i, err := strconv.ParseInt(asn.(string), 10, 64) - if err != nil { - return err + + if v, ok := d.Get("amazon_side_asn").(string); ok && v != "" { + if v, err := strconv.ParseInt(v, 10, 64); err == nil { + input.AmazonSideAsn = aws.Int64(v) } - req.AmazonSideAsn = aws.Int64(i) } - log.Printf("[DEBUG] Creating Direct Connect gateway: %#v", req) - resp, err := conn.CreateDirectConnectGateway(req) + log.Printf("[DEBUG] Creating Direct Connect Gateway: %s", input) + resp, err := conn.CreateDirectConnectGateway(input) + if err != nil { - return fmt.Errorf("Error creating Direct Connect gateway: %s", err) + return fmt.Errorf("error creating Direct Connect Gateway (%s): %w", name, err) } d.SetId(aws.StringValue(resp.DirectConnectGateway.DirectConnectGatewayId)) - stateConf := &resource.StateChangeConf{ - Pending: []string{directconnect.GatewayStatePending}, - Target: []string{directconnect.GatewayStateAvailable}, - Refresh: dxGatewayStateRefresh(conn, d.Id()), - Timeout: d.Timeout(schema.TimeoutCreate), - Delay: 10 * time.Second, - MinTimeout: 5 * time.Second, - } - _, err = stateConf.WaitForState() - if err != nil { - return fmt.Errorf("Error waiting for Direct Connect gateway (%s) to become available: %s", d.Id(), err) + if _, err := waiter.GatewayCreated(conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return fmt.Errorf("error waiting for Direct Connect Gateway (%s) to create: %w", d.Id(), err) } return resourceAwsDxGatewayRead(d, meta) @@ -87,20 +85,21 @@ func resourceAwsDxGatewayCreate(d *schema.ResourceData, meta interface{}) error func resourceAwsDxGatewayRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).dxconn - dxGwRaw, state, err := dxGatewayStateRefresh(conn, d.Id())() - if err != nil { - return fmt.Errorf("Error reading Direct Connect gateway: %s", err) - } - if state == directconnect.GatewayStateDeleted { - log.Printf("[WARN] Direct Connect gateway (%s) not found, removing from state", d.Id()) + output, err := finder.GatewayByID(conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Direct Connect Gateway (%s) not found, removing from state", d.Id()) d.SetId("") return nil } - dxGw := dxGwRaw.(*directconnect.Gateway) - d.Set("name", dxGw.DirectConnectGatewayName) - d.Set("amazon_side_asn", strconv.FormatInt(aws.Int64Value(dxGw.AmazonSideAsn), 10)) - d.Set("owner_account_id", dxGw.OwnerAccount) + if err != nil { + return fmt.Errorf("error reading Direct Connect Gateway (%s): %w", d.Id(), err) + } + + d.Set("amazon_side_asn", strconv.FormatInt(aws.Int64Value(output.AmazonSideAsn), 10)) + d.Set("name", output.DirectConnectGatewayName) + d.Set("owner_account_id", output.OwnerAccount) return nil } @@ -108,58 +107,22 @@ func resourceAwsDxGatewayRead(d *schema.ResourceData, meta interface{}) error { func resourceAwsDxGatewayDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).dxconn + log.Printf("[DEBUG] Deleting Direct Connect Gateway: %s", d.Id()) _, err := conn.DeleteDirectConnectGateway(&directconnect.DeleteDirectConnectGatewayInput{ DirectConnectGatewayId: aws.String(d.Id()), }) - if err != nil { - if isAWSErr(err, "DirectConnectClientException", "does not exist") { - return nil - } - return fmt.Errorf("Error deleting Direct Connect gateway: %s", err) - } - if err := waitForDirectConnectGatewayDeletion(conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { - return fmt.Errorf("Error waiting for Direct Connect gateway (%s) to be deleted: %s", d.Id(), err) + if tfawserr.ErrMessageContains(err, directconnect.ErrCodeClientException, "does not exist") { + return nil } - return nil -} - -func dxGatewayStateRefresh(conn *directconnect.DirectConnect, dxgwId string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - resp, err := conn.DescribeDirectConnectGateways(&directconnect.DescribeDirectConnectGatewaysInput{ - DirectConnectGatewayId: aws.String(dxgwId), - }) - if err != nil { - return nil, "", err - } - - n := len(resp.DirectConnectGateways) - switch n { - case 0: - return "", directconnect.GatewayStateDeleted, nil - - case 1: - dxgw := resp.DirectConnectGateways[0] - return dxgw, aws.StringValue(dxgw.DirectConnectGatewayState), nil - - default: - return nil, "", fmt.Errorf("Found %d Direct Connect gateways for %s, expected 1", n, dxgwId) - } + if err != nil { + return fmt.Errorf("error deleting Direct Connect Gateway (%s): %w", d.Id(), err) } -} -func waitForDirectConnectGatewayDeletion(conn *directconnect.DirectConnect, gatewayID string, timeout time.Duration) error { - stateConf := &resource.StateChangeConf{ - Pending: []string{directconnect.GatewayStatePending, directconnect.GatewayStateAvailable, directconnect.GatewayStateDeleting}, - Target: []string{directconnect.GatewayStateDeleted}, - Refresh: dxGatewayStateRefresh(conn, gatewayID), - Timeout: timeout, - Delay: 10 * time.Second, - MinTimeout: 5 * time.Second, + if _, err := waiter.GatewayDeleted(conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return fmt.Errorf("error waiting for Direct Connect Gateway (%s) to delete: %w", d.Id(), err) } - _, err := stateConf.WaitForState() - - return err + return nil } diff --git a/aws/resource_aws_dx_gateway_test.go b/aws/resource_aws_dx_gateway_test.go index 4809545c4420..00a66f3dbe8e 100644 --- a/aws/resource_aws_dx_gateway_test.go +++ b/aws/resource_aws_dx_gateway_test.go @@ -4,13 +4,16 @@ import ( "fmt" "log" "testing" - "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/directconnect" + multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/directconnect/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/directconnect/lister" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) func init() { @@ -26,95 +29,91 @@ func init() { func testSweepDirectConnectGateways(region string) error { client, err := sharedClientForRegion(region) if err != nil { - return fmt.Errorf("error getting client: %s", err) + return fmt.Errorf("error getting client: %w", err) } conn := client.(*AWSClient).dxconn input := &directconnect.DescribeDirectConnectGatewaysInput{} + var sweeperErrs *multierror.Error + sweepResources := make([]*testSweepResource, 0) - for { - output, err := conn.DescribeDirectConnectGateways(input) - - if testSweepSkipSweepError(err) { - log.Printf("[WARN] Skipping Direct Connect Gateway sweep for %s: %s", region, err) - return nil - } - - if err != nil { - return fmt.Errorf("error retrieving Direct Connect Gateways: %s", err) + err = lister.DescribeDirectConnectGatewaysPages(conn, input, func(page *directconnect.DescribeDirectConnectGatewaysOutput, lastPage bool) bool { + if page == nil { + return !lastPage } - for _, gateway := range output.DirectConnectGateways { - id := aws.StringValue(gateway.DirectConnectGatewayId) + for _, gateway := range page.DirectConnectGateways { + directConnectGatewayID := aws.StringValue(gateway.DirectConnectGatewayId) - if aws.StringValue(gateway.DirectConnectGatewayState) != directconnect.GatewayStateAvailable { - log.Printf("[INFO] Skipping Direct Connect Gateway in non-available (%s) state: %s", aws.StringValue(gateway.DirectConnectGatewayState), id) + if state := aws.StringValue(gateway.DirectConnectGatewayState); state != directconnect.GatewayStateAvailable { + log.Printf("[INFO] Skipping Direct Connect Gateway in non-available (%s) state: %s", state, directConnectGatewayID) continue } - var associations bool - associationInput := &directconnect.DescribeDirectConnectGatewayAssociationsInput{ - DirectConnectGatewayId: gateway.DirectConnectGatewayId, + input := &directconnect.DescribeDirectConnectGatewayAssociationsInput{ + DirectConnectGatewayId: aws.String(directConnectGatewayID), } - for { - associationOutput, err := conn.DescribeDirectConnectGatewayAssociations(associationInput) + var associations bool - if err != nil { - return fmt.Errorf("error retrieving Direct Connect Gateway (%s) Associations: %s", id, err) + err := lister.DescribeDirectConnectGatewayAssociationsPages(conn, input, func(page *directconnect.DescribeDirectConnectGatewayAssociationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage } // If associations still remain, its likely that our region is not the home // region of those associations and the previous sweepers skipped them. // When we hit this condition, we skip trying to delete the gateway as it // will go from deleting -> available after a few minutes and timeout. - if len(associationOutput.DirectConnectGatewayAssociations) > 0 { + if len(page.DirectConnectGatewayAssociations) > 0 { associations = true - break - } - if aws.StringValue(associationOutput.NextToken) == "" { - break + return false } - associationInput.NextToken = associationOutput.NextToken + return !lastPage + }) + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing Direct Connect Gateway Associations (%s): %w", region, err)) } if associations { - log.Printf("[INFO] Skipping Direct Connect Gateway with remaining associations: %s", id) + log.Printf("[INFO] Skipping Direct Connect Gateway with remaining associations: %s", directConnectGatewayID) continue } - input := &directconnect.DeleteDirectConnectGatewayInput{ - DirectConnectGatewayId: aws.String(id), - } + r := resourceAwsDxGateway() + d := r.Data(nil) + d.SetId(directConnectGatewayID) - log.Printf("[INFO] Deleting Direct Connect Gateway: %s", id) - _, err := conn.DeleteDirectConnectGateway(input) + sweepResources = append(sweepResources, NewTestSweepResource(r, d, client)) + } - if isAWSErr(err, directconnect.ErrCodeClientException, "does not exist") { - continue - } + return !lastPage + }) - if err != nil { - return fmt.Errorf("error deleting Direct Connect Gateway (%s): %s", id, err) - } + if testSweepSkipSweepError(err) { + log.Print(fmt.Errorf("[WARN] Skipping Direct Connect Gateway sweep for %s: %w", region, err)) + return sweeperErrs // In case we have completed some pages, but had errors + } - if err := waitForDirectConnectGatewayDeletion(conn, id, 20*time.Minute); err != nil { - return fmt.Errorf("error waiting for Direct Connect Gateway (%s) to be deleted: %s", id, err) - } - } + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing Direct Connect Gateways (%s): %w", region, err)) + } - if aws.StringValue(output.NextToken) == "" { - break - } + err = testSweepResourceOrchestrator(sweepResources) - input.NextToken = output.NextToken + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error sweeping Direct Connect Gateways (%s): %w", region, err)) } - return nil + return sweeperErrs.ErrorOrNil() } func TestAccAwsDxGateway_basic(t *testing.T) { + var v directconnect.Gateway + rName := acctest.RandomWithPrefix("tf-acc-test") + rBgpAsn := acctest.RandIntRange(64512, 65534) resourceName := "aws_dx_gateway.test" resource.ParallelTest(t, resource.TestCase{ @@ -124,9 +123,9 @@ func TestAccAwsDxGateway_basic(t *testing.T) { CheckDestroy: testAccCheckAwsDxGatewayDestroy, Steps: []resource.TestStep{ { - Config: testAccDxGatewayConfig(acctest.RandString(5), acctest.RandIntRange(64512, 65534)), + Config: testAccDxGatewayConfig(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsDxGatewayExists(resourceName), + testAccCheckAwsDxGatewayExists(resourceName, &v), testAccCheckResourceAttrAccountID(resourceName, "owner_account_id"), ), }, @@ -139,7 +138,32 @@ func TestAccAwsDxGateway_basic(t *testing.T) { }) } +func TestAccAwsDxGateway_disappears(t *testing.T) { + var v directconnect.Gateway + rName := acctest.RandomWithPrefix("tf-acc-test") + rBgpAsn := acctest.RandIntRange(64512, 65534) + resourceName := "aws_dx_gateway.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, directconnect.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsDxGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDxGatewayConfig(rName, rBgpAsn), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsDxGatewayExists(resourceName, &v), + testAccCheckResourceDisappears(testAccProvider, resourceAwsDxGateway(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + func TestAccAwsDxGateway_complex(t *testing.T) { + var v directconnect.Gateway rName := acctest.RandomWithPrefix("tf-acc-test") rBgpAsn := acctest.RandIntRange(64512, 65534) resourceName := "aws_dx_gateway.test" @@ -153,7 +177,7 @@ func TestAccAwsDxGateway_complex(t *testing.T) { { Config: testAccDxGatewayAssociationConfig_multiVpnGatewaysSingleAccount(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsDxGatewayExists(resourceName), + testAccCheckAwsDxGatewayExists(resourceName, &v), testAccCheckResourceAttrAccountID(resourceName, "owner_account_id"), ), }, @@ -174,30 +198,42 @@ func testAccCheckAwsDxGatewayDestroy(s *terraform.State) error { continue } - input := &directconnect.DescribeDirectConnectGatewaysInput{ - DirectConnectGatewayId: aws.String(rs.Primary.ID), + _, err := finder.GatewayByID(conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue } - resp, err := conn.DescribeDirectConnectGateways(input) if err != nil { return err } - for _, v := range resp.DirectConnectGateways { - if *v.DirectConnectGatewayId == rs.Primary.ID && !(*v.DirectConnectGatewayState == directconnect.GatewayStateDeleted) { - return fmt.Errorf("[DESTROY ERROR] DX Gateway (%s) not deleted", rs.Primary.ID) - } - } + + return fmt.Errorf("Direct Connect Gateway %s still exists", rs.Primary.ID) } return nil } -func testAccCheckAwsDxGatewayExists(name string) resource.TestCheckFunc { +func testAccCheckAwsDxGatewayExists(name string, v *directconnect.Gateway) resource.TestCheckFunc { return func(s *terraform.State) error { - _, ok := s.RootModule().Resources[name] + rs, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("Not found: %s", name) } + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).dxconn + + output, err := finder.GatewayByID(conn, rs.Primary.ID) + + if err != nil { + return err + } + + *v = *output + return nil } } @@ -205,8 +241,8 @@ func testAccCheckAwsDxGatewayExists(name string) resource.TestCheckFunc { func testAccDxGatewayConfig(rName string, rBgpAsn int) string { return fmt.Sprintf(` resource "aws_dx_gateway" "test" { - name = "terraform-testacc-dxgw-%s" - amazon_side_asn = "%d" + name = %[1]q + amazon_side_asn = "%[2]d" } `, rName, rBgpAsn) } From 7d84773269330e64c0f1c4dd158f8b458f8fab90 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 8 Jul 2021 15:27:04 -0400 Subject: [PATCH 24/27] Add CHANGELOG entry. --- .changelog/19741.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/19741.txt diff --git a/.changelog/19741.txt b/.changelog/19741.txt new file mode 100644 index 000000000000..83d9fc756e95 --- /dev/null +++ b/.changelog/19741.txt @@ -0,0 +1,3 @@ +```release-note:note +resource/aws_dx_gateway_association_proposal: If an accepted Proposal reaches end-of-life and is removed by AWS do not recreate the resource, instead refreshing Terraform state from the resource's Direct Connect Gateway ID and Associated Gateway ID. +``` \ No newline at end of file From 8cb22b4bf4187c2c40d9801455699a9eeed20f53 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 8 Jul 2021 17:16:08 -0400 Subject: [PATCH 25/27] Taint Proposal to test Gateway Association ForceNew. --- ...rce_aws_dx_gateway_association_proposal_test.go | 14 ++++++++++++-- aws/resource_aws_dx_gateway_association_test.go | 5 ++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/aws/resource_aws_dx_gateway_association_proposal_test.go b/aws/resource_aws_dx_gateway_association_proposal_test.go index a696eddccb37..700ae9ec673f 100644 --- a/aws/resource_aws_dx_gateway_association_proposal_test.go +++ b/aws/resource_aws_dx_gateway_association_proposal_test.go @@ -351,6 +351,16 @@ func testAccCheckAwsDxGatewayAssociationProposalRecreated(old, new *directconnec } } +// func testAccCheckAwsDxGatewayAssociationProposalNotRecreated(old, new *directconnect.GatewayAssociationProposal) resource.TestCheckFunc { +// return func(s *terraform.State) error { +// if old, new := aws.StringValue(old.ProposalId), aws.StringValue(new.ProposalId); old != new { +// return fmt.Errorf("Direct Connect Gateway Association Proposal (%s) recreated (%s)", old, new) +// } + +// return nil +// } +// } + func testAccCheckAwsDxGatewayAssociationProposalAccepted(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[resourceName] @@ -366,8 +376,8 @@ func testAccCheckAwsDxGatewayAssociationProposalAccepted(resourceName string) re return err } - if aws.StringValue(output.ProposalState) != directconnect.GatewayAssociationProposalStateAccepted { - return fmt.Errorf("Direct Connect Gateway Association Proposal (%s) not accepted", rs.Primary.ID) + if state := aws.StringValue(output.ProposalState); state != directconnect.GatewayAssociationProposalStateAccepted { + return fmt.Errorf("Direct Connect Gateway Association Proposal (%s) not accepted (%s)", rs.Primary.ID, state) } return nil diff --git a/aws/resource_aws_dx_gateway_association_test.go b/aws/resource_aws_dx_gateway_association_test.go index cbfe01d13035..6578f18265e0 100644 --- a/aws/resource_aws_dx_gateway_association_test.go +++ b/aws/resource_aws_dx_gateway_association_test.go @@ -479,9 +479,7 @@ func TestAccAwsDxGatewayAssociation_recreateProposal(t *testing.T) { Config: testAccDxGatewayAssociationConfig_basicVpnGatewayCrossAccount(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName, &ga1, &gap1), - testAccCheckResourceDisappears(testAccProvider, resourceAwsDxGatewayAssociationProposal(), resourceNameProposal), ), - ExpectNonEmptyPlan: true, }, { Config: testAccDxGatewayAssociationConfig_basicVpnGatewayCrossAccount(rName, rBgpAsn), @@ -490,6 +488,7 @@ func TestAccAwsDxGatewayAssociation_recreateProposal(t *testing.T) { testAccCheckAwsDxGatewayAssociationNotRecreated(&ga1, &ga2), testAccCheckAwsDxGatewayAssociationProposalRecreated(&gap1, &gap2), ), + Taint: []string{resourceNameProposal}, }, }, }) @@ -566,7 +565,7 @@ func testAccCheckAwsDxGatewayAssociationExists(name string, ga *directconnect.Ga func testAccCheckAwsDxGatewayAssociationNotRecreated(old, new *directconnect.GatewayAssociation) resource.TestCheckFunc { return func(s *terraform.State) error { - if old, new := aws.StringValue(old.AssociationId), aws.StringValue(new.AssociationId); old == new { + if old, new := aws.StringValue(old.AssociationId), aws.StringValue(new.AssociationId); old != new { return fmt.Errorf("Direct Connect Gateway Association (%s) recreated (%s)", old, new) } From e75ba1b21cc08e273c8734cb42e1f746064baff6 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 9 Jul 2021 11:46:58 -0400 Subject: [PATCH 26/27] Configuration changes for 'TestAccAwsDxGatewayAssociation_recreateProposal'. --- ...ws_dx_gateway_association_proposal_test.go | 10 ------ ...esource_aws_dx_gateway_association_test.go | 32 +++++++++++++++++-- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/aws/resource_aws_dx_gateway_association_proposal_test.go b/aws/resource_aws_dx_gateway_association_proposal_test.go index 700ae9ec673f..5cc620a05208 100644 --- a/aws/resource_aws_dx_gateway_association_proposal_test.go +++ b/aws/resource_aws_dx_gateway_association_proposal_test.go @@ -351,16 +351,6 @@ func testAccCheckAwsDxGatewayAssociationProposalRecreated(old, new *directconnec } } -// func testAccCheckAwsDxGatewayAssociationProposalNotRecreated(old, new *directconnect.GatewayAssociationProposal) resource.TestCheckFunc { -// return func(s *terraform.State) error { -// if old, new := aws.StringValue(old.ProposalId), aws.StringValue(new.ProposalId); old != new { -// return fmt.Errorf("Direct Connect Gateway Association Proposal (%s) recreated (%s)", old, new) -// } - -// return nil -// } -// } - func testAccCheckAwsDxGatewayAssociationProposalAccepted(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[resourceName] diff --git a/aws/resource_aws_dx_gateway_association_test.go b/aws/resource_aws_dx_gateway_association_test.go index 6578f18265e0..9a72d222fea8 100644 --- a/aws/resource_aws_dx_gateway_association_test.go +++ b/aws/resource_aws_dx_gateway_association_test.go @@ -463,7 +463,6 @@ func TestAccAwsDxGatewayAssociation_allowedPrefixesVpnGatewayCrossAccount(t *tes func TestAccAwsDxGatewayAssociation_recreateProposal(t *testing.T) { var providers []*schema.Provider resourceName := "aws_dx_gateway_association.test" - resourceNameProposal := "aws_dx_gateway_association_proposal.test" rName := acctest.RandomWithPrefix("tf-acc-test") rBgpAsn := acctest.RandIntRange(64512, 65534) var ga1, ga2 directconnect.GatewayAssociation @@ -482,13 +481,12 @@ func TestAccAwsDxGatewayAssociation_recreateProposal(t *testing.T) { ), }, { - Config: testAccDxGatewayAssociationConfig_basicVpnGatewayCrossAccount(rName, rBgpAsn), + Config: testAccDxGatewayAssociationConfig_basicVpnGatewayCrossAccountUpdatedProposal(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName, &ga2, &gap2), testAccCheckAwsDxGatewayAssociationNotRecreated(&ga1, &ga2), testAccCheckAwsDxGatewayAssociationProposalRecreated(&gap1, &gap2), ), - Taint: []string{resourceNameProposal}, }, }, }) @@ -701,6 +699,34 @@ resource "aws_dx_gateway_association" "test" { `) } +func testAccDxGatewayAssociationConfig_basicVpnGatewayCrossAccountUpdatedProposal(rName string, rBgpAsn int) string { + return composeConfig( + testAccDxGatewayAssociationConfigBase_vpnGatewayCrossAccount(rName, rBgpAsn), + ` +# Creator +resource "aws_dx_gateway_association_proposal" "test" { + dx_gateway_id = aws_dx_gateway.test.id + dx_gateway_owner_account_id = aws_dx_gateway.test.owner_account_id + associated_gateway_id = aws_vpn_gateway_attachment.test.vpn_gateway_id +} + +resource "aws_dx_gateway_association_proposal" "test2" { + dx_gateway_id = aws_dx_gateway.test.id + dx_gateway_owner_account_id = aws_dx_gateway.test.owner_account_id + associated_gateway_id = aws_vpn_gateway_attachment.test.vpn_gateway_id + } + +# Accepter +resource "aws_dx_gateway_association" "test" { + provider = "awsalternate" + + proposal_id = aws_dx_gateway_association_proposal.test2.id + dx_gateway_id = aws_dx_gateway.test.id + associated_gateway_owner_account_id = data.aws_caller_identity.creator.account_id +} +`) +} + func testAccDxGatewayAssociationConfig_basicTransitGatewaySingleAccount(rName string, rBgpAsn int) string { return fmt.Sprintf(` resource "aws_dx_gateway" "test" { From f97a52e4e2a55da63e8cd101d63f095266243c23 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 12 Jul 2021 09:28:04 -0400 Subject: [PATCH 27/27] Fix 'terrafmt' errors. --- ...e_aws_dx_gateway_association_proposal_test.go | 16 ++++++++-------- aws/resource_aws_dx_gateway_association_test.go | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/aws/resource_aws_dx_gateway_association_proposal_test.go b/aws/resource_aws_dx_gateway_association_proposal_test.go index 5cc620a05208..02c976285edf 100644 --- a/aws/resource_aws_dx_gateway_association_proposal_test.go +++ b/aws/resource_aws_dx_gateway_association_proposal_test.go @@ -416,11 +416,11 @@ func testAccDxGatewayAssociationProposalConfig_endOfLifeVpn(rName string, rBgpAs data "aws_caller_identity" "current" {} resource "aws_dx_gateway_association" "test" { -provider = "awsalternate" + provider = "awsalternate" -proposal_id = aws_dx_gateway_association_proposal.test.id -dx_gateway_id = aws_dx_gateway.test.id -associated_gateway_owner_account_id = data.aws_caller_identity.current.account_id + proposal_id = aws_dx_gateway_association_proposal.test.id + dx_gateway_id = aws_dx_gateway.test.id + associated_gateway_owner_account_id = data.aws_caller_identity.current.account_id } `) } @@ -430,11 +430,11 @@ func testAccDxGatewayAssociationProposalConfig_endOfLifeTgw(rName string, rBgpAs data "aws_caller_identity" "current" {} resource "aws_dx_gateway_association" "test" { -provider = "awsalternate" + provider = "awsalternate" -proposal_id = aws_dx_gateway_association_proposal.test.id -dx_gateway_id = aws_dx_gateway.test.id -associated_gateway_owner_account_id = data.aws_caller_identity.current.account_id + proposal_id = aws_dx_gateway_association_proposal.test.id + dx_gateway_id = aws_dx_gateway.test.id + associated_gateway_owner_account_id = data.aws_caller_identity.current.account_id } `) } diff --git a/aws/resource_aws_dx_gateway_association_test.go b/aws/resource_aws_dx_gateway_association_test.go index 9a72d222fea8..8d324e0c4828 100644 --- a/aws/resource_aws_dx_gateway_association_test.go +++ b/aws/resource_aws_dx_gateway_association_test.go @@ -711,10 +711,10 @@ resource "aws_dx_gateway_association_proposal" "test" { } resource "aws_dx_gateway_association_proposal" "test2" { - dx_gateway_id = aws_dx_gateway.test.id - dx_gateway_owner_account_id = aws_dx_gateway.test.owner_account_id - associated_gateway_id = aws_vpn_gateway_attachment.test.vpn_gateway_id - } + dx_gateway_id = aws_dx_gateway.test.id + dx_gateway_owner_account_id = aws_dx_gateway.test.owner_account_id + associated_gateway_id = aws_vpn_gateway_attachment.test.vpn_gateway_id +} # Accepter resource "aws_dx_gateway_association" "test" {