From b5992bcb685677c79a51322fb26a584a9802e322 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Sat, 4 May 2019 18:36:41 -0400 Subject: [PATCH 01/13] Update aws_dx_gateway_association and aws_dx_gateway_association_proposal resources for transit gateways. --- aws/resource_aws_dx_gateway_association.go | 273 ++++++++++++++---- ...rce_aws_dx_gateway_association_proposal.go | 65 ++++- ...ws_dx_gateway_association_proposal_test.go | 73 ++++- ...esource_aws_dx_gateway_association_test.go | 225 ++++++++++++--- .../r/dx_gateway_association.html.markdown | 50 +++- ...gateway_association_proposal.html.markdown | 33 ++- 6 files changed, 584 insertions(+), 135 deletions(-) diff --git a/aws/resource_aws_dx_gateway_association.go b/aws/resource_aws_dx_gateway_association.go index 36fa5f4b3e6..94891367d21 100644 --- a/aws/resource_aws_dx_gateway_association.go +++ b/aws/resource_aws_dx_gateway_association.go @@ -1,6 +1,7 @@ package aws import ( + "errors" "fmt" "log" "strings" @@ -8,12 +9,15 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/directconnect" + "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" ) const ( gatewayAssociationStateDeleted = "deleted" + + transitGatewayAttachmentResourceTypeDirectConnectGateway = "direct-connect-gateway" ) func resourceAwsDxGatewayAssociation() *schema.Resource { @@ -34,22 +38,41 @@ func resourceAwsDxGatewayAssociation() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, }, - "dx_gateway_id": { + "associated_gateway_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"vpn_gateway_id"}, + }, + + "associated_gateway_type": { Type: schema.TypeString, - Required: true, - ForceNew: true, + Computed: true, }, - "vpn_gateway_id": { + "dx_gateway_association_id": { + Type: schema.TypeString, + Computed: true, + }, + + "dx_gateway_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "dx_gateway_association_id": { + "transit_gateway_attachment_id": { Type: schema.TypeString, Computed: true, }, + + "vpn_gateway_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"associated_gateway_id"}, + Deprecated: "use 'associated_gateway_id' argument instead", + }, }, Timeouts: &schema.ResourceTimeout{ @@ -63,32 +86,39 @@ func resourceAwsDxGatewayAssociation() *schema.Resource { func resourceAwsDxGatewayAssociationCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).dxconn + gwIdRaw, gwIdOk := d.GetOk("associated_gateway_id") + vgwIdRaw, vgwIdOk := d.GetOk("vpn_gateway_id") + if !gwIdOk && !vgwIdOk { + return errors.New("one of associated_gateway_id or vpn_gateway_id must be configured") + } + dxgwId := d.Get("dx_gateway_id").(string) - vgwId := d.Get("vpn_gateway_id").(string) + gwId := "" + req := &directconnect.CreateDirectConnectGatewayAssociationInput{ AddAllowedPrefixesToDirectConnectGateway: expandDxRouteFilterPrefixes(d.Get("allowed_prefixes").(*schema.Set)), DirectConnectGatewayId: aws.String(dxgwId), - VirtualGatewayId: aws.String(vgwId), + } + if gwIdOk { + gwId = gwIdRaw.(string) + req.GatewayId = aws.String(gwId) + } else { + gwId = vgwIdRaw.(string) + req.VirtualGatewayId = aws.String(gwId) } log.Printf("[DEBUG] Creating Direct Connect gateway association: %#v", req) - _, err := conn.CreateDirectConnectGatewayAssociation(req) + resp, err := conn.CreateDirectConnectGatewayAssociation(req) if err != nil { return fmt.Errorf("error creating Direct Connect gateway association: %s", err) } - d.SetId(dxGatewayAssociationId(dxgwId, vgwId)) + // 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)) + d.Set("dx_gateway_association_id", associationId) - stateConf := &resource.StateChangeConf{ - Pending: []string{directconnect.GatewayAssociationStateAssociating}, - Target: []string{directconnect.GatewayAssociationStateAssociated}, - Refresh: dxGatewayAssociationStateRefresh(conn, dxgwId, vgwId), - Timeout: d.Timeout(schema.TimeoutCreate), - Delay: 10 * time.Second, - MinTimeout: 5 * time.Second, - } - _, err = stateConf.WaitForState() - if err != nil { + 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) } @@ -98,11 +128,10 @@ func resourceAwsDxGatewayAssociationCreate(d *schema.ResourceData, meta interfac func resourceAwsDxGatewayAssociationRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).dxconn - dxgwId := d.Get("dx_gateway_id").(string) - vgwId := d.Get("vpn_gateway_id").(string) - assocRaw, state, err := dxGatewayAssociationStateRefresh(conn, dxgwId, vgwId)() + 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", err) + 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()) @@ -111,24 +140,62 @@ func resourceAwsDxGatewayAssociationRead(d *schema.ResourceData, meta interface{ } assoc := assocRaw.(*directconnect.GatewayAssociation) - d.Set("dx_gateway_id", assoc.DirectConnectGatewayId) - d.Set("vpn_gateway_id", assoc.VirtualGatewayId) - d.Set("dx_gateway_association_id", assoc.AssociationId) + err = d.Set("allowed_prefixes", flattenDxRouteFilterPrefixes(assoc.AllowedPrefixesToDirectConnectGateway)) if err != nil { return fmt.Errorf("error setting allowed_prefixes: %s", err) } + if _, ok := d.GetOk("vpn_gateway_id"); ok { + d.Set("vpn_gateway_id", assoc.VirtualGatewayId) + } else { + d.Set("associated_gateway_id", assoc.AssociatedGateway.Id) + } + d.Set("associated_gateway_type", assoc.AssociatedGateway.Type) + d.Set("dx_gateway_association_id", assoc.AssociationId) + d.Set("dx_gateway_id", assoc.DirectConnectGatewayId) + + if aws.StringValue(assoc.AssociatedGateway.Type) == directconnect.GatewayTypeTransitGateway { + ec2conn := meta.(*AWSClient).ec2conn + + req := &ec2.DescribeTransitGatewayAttachmentsInput{ + Filters: buildEC2AttributeFilterList(map[string]string{ + "resource-id": aws.StringValue(assoc.DirectConnectGatewayId), + "resource-type": transitGatewayAttachmentResourceTypeDirectConnectGateway, + "transit-gateway-id": aws.StringValue(assoc.AssociatedGateway.Id), + }), + } + + log.Printf("[DEBUG] Finding Direct Connect gateway association transit gateway attachment: %#v", req) + resp, err := ec2conn.DescribeTransitGatewayAttachments(req) + if err != nil { + return fmt.Errorf("error finding Direct Connect gateway association (%s) transit gateway attachment: %s", d.Id(), err) + } + if resp == nil || len(resp.TransitGatewayAttachments) == 0 || resp.TransitGatewayAttachments[0] == nil { + return fmt.Errorf("error finding Direct Connect gateway association (%s) transit gateway attachment: empty response", d.Id()) + } + if len(resp.TransitGatewayAttachments) > 1 { + return fmt.Errorf("error reading Direct Connect gateway association (%s) transit gateway attachment: multiple responses", d.Id()) + } + + d.Set("transit_gateway_attachment_id", resp.TransitGatewayAttachments[0].TransitGatewayAttachmentId) + } + return nil } func resourceAwsDxGatewayAssociationUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).dxconn - dxgwId := d.Get("dx_gateway_id").(string) - vgwId := d.Get("vpn_gateway_id").(string) + _, gwIdOk := d.GetOk("associated_gateway_id") + _, vgwIdOk := d.GetOk("vpn_gateway_id") + if !gwIdOk && !vgwIdOk { + return errors.New("one of associated_gateway_id or vpn_gateway_id must be configured") + } if d.HasChange("allowed_prefixes") { + associationId := d.Get("dx_gateway_association_id").(string) + oraw, nraw := d.GetChange("allowed_prefixes") o := oraw.(*schema.Set) n := nraw.(*schema.Set) @@ -137,26 +204,17 @@ func resourceAwsDxGatewayAssociationUpdate(d *schema.ResourceData, meta interfac req := &directconnect.UpdateDirectConnectGatewayAssociationInput{ AddAllowedPrefixesToDirectConnectGateway: expandDxRouteFilterPrefixes(add), - AssociationId: aws.String(d.Get("dx_gateway_association_id").(string)), + AssociationId: aws.String(associationId), RemoveAllowedPrefixesToDirectConnectGateway: expandDxRouteFilterPrefixes(del), } - log.Printf("[DEBUG] Direct Connect gateway association: %#v", req) + 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) } - stateConf := &resource.StateChangeConf{ - Pending: []string{directconnect.GatewayAssociationStateUpdating}, - Target: []string{directconnect.GatewayAssociationStateAssociated}, - Refresh: dxGatewayAssociationStateRefresh(conn, dxgwId, vgwId), - Timeout: d.Timeout(schema.TimeoutUpdate), - Delay: 10 * time.Second, - MinTimeout: 5 * time.Second, - } - _, err = stateConf.WaitForState() - if err != nil { + 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) } } @@ -167,13 +225,11 @@ func resourceAwsDxGatewayAssociationUpdate(d *schema.ResourceData, meta interfac func resourceAwsDxGatewayAssociationDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).dxconn - dxgwId := d.Get("dx_gateway_id").(string) - vgwId := d.Get("vpn_gateway_id").(string) + associationId := d.Get("dx_gateway_association_id").(string) log.Printf("[DEBUG] Deleting Direct Connect gateway association: %s", d.Id()) _, err := conn.DeleteDirectConnectGatewayAssociation(&directconnect.DeleteDirectConnectGatewayAssociationInput{ - DirectConnectGatewayId: aws.String(dxgwId), - VirtualGatewayId: aws.String(vgwId), + AssociationId: aws.String(associationId), }) if isAWSErr(err, directconnect.ErrCodeClientException, "No association exists") { return nil @@ -182,35 +238,55 @@ func resourceAwsDxGatewayAssociationDelete(d *schema.ResourceData, meta interfac return fmt.Errorf("error deleting Direct Connect gateway association: %s", err) } - if err := waitForDirectConnectGatewayAssociationDeletion(conn, dxgwId, vgwId, d.Timeout(schema.TimeoutDelete)); err != nil { - return fmt.Errorf("error waiting for Direct Connect gateway association (%s) to be deleted: %s", d.Id(), err.Error()) + 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 tgwAttachmentId := d.Get("transit_gateway_attachment_id").(string); tgwAttachmentId != "" { + ec2conn := meta.(*AWSClient).ec2conn + + if err := waitForEc2TransitGatewayAttachmentDeletion(ec2conn, tgwAttachmentId, d.Timeout(schema.TimeoutDelete)); err != nil { + return fmt.Errorf("error waiting for Direct Connect gateway association (%s) transit gateway attachment (%s) to be deleted: %s", d.Id(), tgwAttachmentId, err) + } + } return nil } func resourceAwsDxGatewayAssociationImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { parts := strings.Split(d.Id(), "/") if len(parts) != 2 { - return []*schema.ResourceData{}, fmt.Errorf("Wrong format of resource: %s. Please follow 'dx-gw-id/vgw-id'", d.Id()) + return []*schema.ResourceData{}, fmt.Errorf("Wrong format of resource: %s. Please follow 'dx-gw-id/gw-id'", d.Id()) } dxgwId := parts[0] - vgwId := parts[1] - log.Printf("[DEBUG] Importing Direct Connect gateway association %s/%s", dxgwId, vgwId) + gwId := parts[1] + id := dxGatewayAssociationId(dxgwId, gwId) + log.Printf("[DEBUG] Importing Direct Connect gateway association %s/%s", dxgwId, gwId) + + conn := meta.(*AWSClient).dxconn + + 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(dxGatewayAssociationId(dxgwId, vgwId)) - d.Set("dx_gateway_id", dxgwId) - d.Set("vpn_gateway_id", vgwId) + d.SetId(id) + d.Set("dx_gateway_id", resp.DirectConnectGatewayAssociations[0].DirectConnectGatewayId) + d.Set("dx_gateway_association_id", resp.DirectConnectGatewayAssociations[0].AssociationId) return []*schema.ResourceData{d}, nil } -func dxGatewayAssociationStateRefresh(conn *directconnect.DirectConnect, dxgwId, vgwId string) resource.StateRefreshFunc { +func dxGatewayAssociationStateRefresh(conn *directconnect.DirectConnect, associationId string) resource.StateRefreshFunc { return func() (interface{}, string, error) { resp, err := conn.DescribeDirectConnectGatewayAssociations(&directconnect.DescribeDirectConnectGatewayAssociationsInput{ - DirectConnectGatewayId: aws.String(dxgwId), - VirtualGatewayId: aws.String(vgwId), + AssociationId: aws.String(associationId), }) if err != nil { return nil, "", err @@ -223,23 +299,100 @@ func dxGatewayAssociationStateRefresh(conn *directconnect.DirectConnect, dxgwId, 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, dxGatewayAssociationId(dxgwId, vgwId)) + return nil, "", fmt.Errorf("Found %d Direct Connect gateway associations for %s, expected 1", n, associationId) + } + } +} + +func ec2TransitGatewayAttachmentStateRefresh(conn *ec2.EC2, attachmentId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + resp, err := conn.DescribeTransitGatewayAttachments(&ec2.DescribeTransitGatewayAttachmentsInput{ + TransitGatewayAttachmentIds: []*string{aws.String(attachmentId)}, + }) + if isAWSErr(err, "InvalidTransitGatewayAttachmentID.NotFound", "") { + return nil, ec2.TransitGatewayAttachmentStateDeleted, nil } + if err != nil { + return nil, "", err + } + + if resp == nil || len(resp.TransitGatewayAttachments) == 0 || resp.TransitGatewayAttachments[0] == nil { + return nil, ec2.TransitGatewayAttachmentStateDeleted, nil + } + if len(resp.TransitGatewayAttachments) > 1 { + return nil, "", errors.New("error reading EC2 Transit Gateway Attachment: multiple results found, try adjusting search criteria") + } + + return resp.TransitGatewayAttachments[0], aws.StringValue(resp.TransitGatewayAttachments[0].State), nil + } +} + +// Terraform resource ID. +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 dxGatewayAssociationId(dxgwId, vgwId string) string { - return fmt.Sprintf("ga-%s%s", dxgwId, vgwId) +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, directConnectGatewayID, virtualGatewayID string, timeout time.Duration) error { +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, directConnectGatewayID, virtualGatewayID), + Refresh: dxGatewayAssociationStateRefresh(conn, associationId), + Timeout: timeout, + Delay: 10 * time.Second, + MinTimeout: 5 * time.Second, + } + + _, err := stateConf.WaitForState() + + return err +} + +func waitForEc2TransitGatewayAttachmentDeletion(conn *ec2.EC2, attachmentId string, timeout time.Duration) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.TransitGatewayAttachmentStateAvailable, ec2.TransitGatewayAttachmentStateDeleting}, + Target: []string{ec2.TransitGatewayAttachmentStateDeleted}, + Refresh: ec2TransitGatewayAttachmentStateRefresh(conn, attachmentId), Timeout: timeout, Delay: 10 * time.Second, MinTimeout: 5 * time.Second, diff --git a/aws/resource_aws_dx_gateway_association_proposal.go b/aws/resource_aws_dx_gateway_association_proposal.go index 6bd3d3d3642..b2a32d9d71b 100644 --- a/aws/resource_aws_dx_gateway_association_proposal.go +++ b/aws/resource_aws_dx_gateway_association_proposal.go @@ -1,11 +1,13 @@ package aws import ( + "errors" "fmt" "log" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/directconnect" + "github.com/hashicorp/terraform/helper/customdiff" "github.com/hashicorp/terraform/helper/schema" ) @@ -18,14 +20,47 @@ func resourceAwsDxGatewayAssociationProposal() *schema.Resource { State: schema.ImportStatePassthrough, }, + CustomizeDiff: customdiff.Sequence( + // 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(d *schema.ResourceDiff, meta interface{}) bool { + 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 && aws.StringValue(proposal.ProposalState) == directconnect.GatewayAssociationProposalStateRequested { + return true + } + + return false + }), + ), + Schema: map[string]*schema.Schema{ "allowed_prefixes": { Type: schema.TypeSet, Optional: true, Computed: true, - ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, }, + "associated_gateway_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"vpn_gateway_id"}, + }, + "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, @@ -38,9 +73,11 @@ func resourceAwsDxGatewayAssociationProposal() *schema.Resource { ValidateFunc: validateAwsAccountId, }, "vpn_gateway_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"associated_gateway_id"}, + Deprecated: "use 'associated_gateway_id' argument instead", }, }, } @@ -49,11 +86,21 @@ func resourceAwsDxGatewayAssociationProposal() *schema.Resource { func resourceAwsDxGatewayAssociationProposalCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).dxconn + gwIdRaw, gwIdOk := d.GetOk("associated_gateway_id") + vgwIdRaw, vgwIdOk := d.GetOk("vpn_gateway_id") + if !gwIdOk && !vgwIdOk { + return errors.New("one of associated_gateway_id or vpn_gateway_id must be configured") + } + input := &directconnect.CreateDirectConnectGatewayAssociationProposalInput{ AddAllowedPrefixesToDirectConnectGateway: expandDirectConnectGatewayAssociationProposalAllowedPrefixes(d.Get("allowed_prefixes").(*schema.Set).List()), 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("vpn_gateway_id").(string)), + } + if gwIdOk { + input.GatewayId = aws.String(gwIdRaw.(string)) + } else { + input.GatewayId = aws.String(vgwIdRaw.(string)) } log.Printf("[DEBUG] Creating Direct Connect Gateway Association Proposal: %s", input) @@ -97,9 +144,15 @@ func resourceAwsDxGatewayAssociationProposalRead(d *schema.ResourceData, meta in return fmt.Errorf("error setting allowed_prefixes: %s", err) } + if _, ok := d.GetOk("vpn_gateway_id"); ok { + d.Set("vpn_gateway_id", aws.StringValue(proposal.AssociatedGateway.Id)) + } else { + d.Set("associated_gateway_id", aws.StringValue(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", aws.StringValue(proposal.DirectConnectGatewayId)) d.Set("dx_gateway_owner_account_id", aws.StringValue(proposal.DirectConnectGatewayOwnerAccount)) - d.Set("vpn_gateway_id", aws.StringValue(proposal.AssociatedGateway.Id)) 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 4cb2ffa6bfa..038f49f2a54 100644 --- a/aws/resource_aws_dx_gateway_association_proposal_test.go +++ b/aws/resource_aws_dx_gateway_association_proposal_test.go @@ -12,12 +12,14 @@ import ( "github.com/hashicorp/terraform/terraform" ) -func TestAccAwsDxGatewayAssociationProposal_basic(t *testing.T) { +func TestAccAwsDxGatewayAssociationProposal_deprecated(t *testing.T) { var proposal1 directconnect.GatewayAssociationProposal var providers []*schema.Provider rBgpAsn := randIntRange(64512, 65534) rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_dx_gateway_association_proposal.test" + resourceNameDxGw := "aws_dx_gateway.test" + resourceNameVgw := "aws_vpn_gateway.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { @@ -28,14 +30,51 @@ func TestAccAwsDxGatewayAssociationProposal_basic(t *testing.T) { CheckDestroy: testAccCheckAwsDxGatewayAssociationProposalDestroy, Steps: []resource.TestStep{ { - Config: testAccDxGatewayAssociationProposalConfig(rName, rBgpAsn), + Config: testAccDxGatewayAssociationProposalConfig_deprecated(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationProposalExists(resourceName, &proposal1), + resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), + resource.TestCheckResourceAttrPair(resourceName, "vpn_gateway_id", resourceNameVgw, "id"), + resource.TestCheckNoResourceAttr(resourceName, "associated_gateway_id"), + testAccCheckResourceAttrAccountID(resourceName, "associated_gateway_owner_account_id"), + resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "virtualPrivateGateway"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "1"), + ), + }, + }, + }) +} + +func TestAccAwsDxGatewayAssociationProposal_basicVpnGateway(t *testing.T) { + var proposal1 directconnect.GatewayAssociationProposal + var providers []*schema.Provider + rBgpAsn := randIntRange(64512, 65534) + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_dx_gateway_association_proposal.test" + resourceNameDxGw := "aws_dx_gateway.test" + resourceNameVgw := "aws_vpn_gateway.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccAlternateAccountPreCheck(t) + }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckAwsDxGatewayAssociationProposalDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDxGatewayAssociationProposalConfig_basicVpnGateway(rName, rBgpAsn), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsDxGatewayAssociationProposalExists(resourceName, &proposal1), + resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), + resource.TestCheckResourceAttrPair(resourceName, "vpn_gateway_id", resourceNameVgw, "id"), + testAccCheckResourceAttrAccountID(resourceName, "associated_gateway_owner_account_id"), + resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "virtualPrivateGateway"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "1"), ), }, { - Config: testAccDxGatewayAssociationProposalConfig(rName, rBgpAsn), + Config: testAccDxGatewayAssociationProposalConfig_basicVpnGateway(rName, rBgpAsn), ResourceName: resourceName, ImportState: true, ImportStateVerify: true, @@ -60,7 +99,7 @@ func TestAccAwsDxGatewayAssociationProposal_disappears(t *testing.T) { CheckDestroy: testAccCheckAwsDxGatewayAssociationProposalDestroy, Steps: []resource.TestStep{ { - Config: testAccDxGatewayAssociationProposalConfig(rName, rBgpAsn), + Config: testAccDxGatewayAssociationProposalConfig_basicVpnGateway(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationProposalExists(resourceName, &proposal1), testAccCheckAwsDxGatewayAssociationProposalDisappears(&proposal1), @@ -71,7 +110,7 @@ func TestAccAwsDxGatewayAssociationProposal_disappears(t *testing.T) { }) } -func TestAccAwsDxGatewayAssociationProposal_AllowedPrefixes(t *testing.T) { +func TestAccAwsDxGatewayAssociationProposal_allowedPrefixes(t *testing.T) { var proposal1, proposal2 directconnect.GatewayAssociationProposal var providers []*schema.Provider rBgpAsn := randIntRange(64512, 65534) @@ -184,7 +223,7 @@ func testAccCheckAwsDxGatewayAssociationProposalRecreated(i, j *directconnect.Ga } } -func testAccDxGatewayAssociationProposalConfigBase(rName string, rBgpAsn int) string { +func testAccDxGatewayAssociationProposalConfigBase_vpnGateway(rName string, rBgpAsn int) string { return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` resource "aws_dx_gateway" "test" { provider = "aws.alternate" @@ -211,8 +250,8 @@ resource "aws_vpn_gateway" "test" { `, rName, rBgpAsn) } -func testAccDxGatewayAssociationProposalConfig(rName string, rBgpAsn int) string { - return testAccDxGatewayAssociationProposalConfigBase(rName, rBgpAsn) + fmt.Sprintf(` +func testAccDxGatewayAssociationProposalConfig_deprecated(rName string, rBgpAsn int) string { + return testAccDxGatewayAssociationProposalConfigBase_vpnGateway(rName, rBgpAsn) + fmt.Sprintf(` 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}" @@ -221,24 +260,34 @@ resource "aws_dx_gateway_association_proposal" "test" { `) } +func testAccDxGatewayAssociationProposalConfig_basicVpnGateway(rName string, rBgpAsn int) string { + return testAccDxGatewayAssociationProposalConfigBase_vpnGateway(rName, rBgpAsn) + fmt.Sprintf(` +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 testAccDxGatewayAssociationProposalConfigAllowedPrefixes1(rName string, rBgpAsn int) string { - return testAccDxGatewayAssociationProposalConfigBase(rName, rBgpAsn) + fmt.Sprintf(` + return testAccDxGatewayAssociationProposalConfigBase_vpnGateway(rName, rBgpAsn) + fmt.Sprintf(` 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}" - vpn_gateway_id = "${aws_vpn_gateway.test.id}" + associated_gateway_id = "${aws_vpn_gateway.test.id}" } `) } func testAccDxGatewayAssociationProposalConfigAllowedPrefixes2(rName string, rBgpAsn int) string { - return testAccDxGatewayAssociationProposalConfigBase(rName, rBgpAsn) + fmt.Sprintf(` + return testAccDxGatewayAssociationProposalConfigBase_vpnGateway(rName, rBgpAsn) + fmt.Sprintf(` 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}" - vpn_gateway_id = "${aws_vpn_gateway.test.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 9d9af3e6b81..6582e321ac7 100644 --- a/aws/resource_aws_dx_gateway_association_test.go +++ b/aws/resource_aws_dx_gateway_association_test.go @@ -55,19 +55,18 @@ func testSweepDirectConnectGatewayAssociations(region string) error { } for _, association := range associationOutput.DirectConnectGatewayAssociations { - virtualGatewayID := aws.StringValue(association.VirtualGatewayId) + gatewayID := aws.StringValue(association.AssociatedGateway.Id) 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), virtualGatewayID) + log.Printf("[INFO] Skipping Direct Connect Gateway (%s) Association in non-available (%s) state: %s", directConnectGatewayID, aws.StringValue(association.AssociationState), gatewayID) continue } input := &directconnect.DeleteDirectConnectGatewayAssociationInput{ - DirectConnectGatewayId: gateway.DirectConnectGatewayId, - VirtualGatewayId: association.VirtualGatewayId, + AssociationId: association.AssociationId, } - log.Printf("[INFO] Deleting Direct Connect Gateway (%s) Association: %s", directConnectGatewayID, virtualGatewayID) + log.Printf("[INFO] Deleting Direct Connect Gateway (%s) Association: %s", directConnectGatewayID, gatewayID) _, err := conn.DeleteDirectConnectGatewayAssociation(input) if isAWSErr(err, directconnect.ErrCodeClientException, "No association exists") { @@ -75,11 +74,11 @@ func testSweepDirectConnectGatewayAssociations(region string) error { } if err != nil { - return fmt.Errorf("error deleting Direct Connect Gateway (%s) Association (%s): %s", directConnectGatewayID, virtualGatewayID, err) + return fmt.Errorf("error deleting Direct Connect Gateway (%s) Association (%s): %s", directConnectGatewayID, gatewayID, err) } - if err := waitForDirectConnectGatewayAssociationDeletion(conn, directConnectGatewayID, virtualGatewayID, 20*time.Minute); err != nil { - return fmt.Errorf("error waiting for Direct Connect Gateway (%s) Association (%s) to be deleted: %s", directConnectGatewayID, virtualGatewayID, 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) } } @@ -101,7 +100,7 @@ func testSweepDirectConnectGatewayAssociations(region string) error { return nil } -func TestAccAwsDxGatewayAssociation_basic(t *testing.T) { +func TestAccAwsDxGatewayAssociation_deprecated(t *testing.T) { resourceName := "aws_dx_gateway_association.test" resourceNameDxGw := "aws_dx_gateway.test" resourceNameVgw := "aws_vpn_gateway.test" @@ -114,16 +113,93 @@ func TestAccAwsDxGatewayAssociation_basic(t *testing.T) { CheckDestroy: testAccCheckAwsDxGatewayAssociationDestroy, Steps: []resource.TestStep{ { - Config: testAccDxGatewayAssociationConfig_basic(rName, rBgpAsn), + Config: testAccDxGatewayAssociationConfig_deprecated(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName), resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), resource.TestCheckResourceAttrPair(resourceName, "vpn_gateway_id", resourceNameVgw, "id"), resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), + resource.TestCheckNoResourceAttr(resourceName, "associated_gateway_id"), + resource.TestCheckNoResourceAttr(resourceName, "transit_gateway_attachment_id"), + resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "virtualPrivateGateway"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "1"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.1216997074", "10.255.255.0/28"), ), }, + }, + }) +} + +func TestAccAwsDxGatewayAssociation_basicVpnGateway(t *testing.T) { + resourceName := "aws_dx_gateway_association.test" + resourceNameDxGw := "aws_dx_gateway.test" + resourceNameVgw := "aws_vpn_gateway.test" + rName := fmt.Sprintf("terraform-testacc-dxgwassoc-%d", acctest.RandInt()) + rBgpAsn := randIntRange(64512, 65534) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsDxGatewayAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDxGatewayAssociationConfig_basicVpnGateway(rName, rBgpAsn), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsDxGatewayAssociationExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), + resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameVgw, "id"), + resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), + resource.TestCheckNoResourceAttr(resourceName, "vpn_gateway_id"), + resource.TestCheckNoResourceAttr(resourceName, "transit_gateway_attachment_id"), + resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "virtualPrivateGateway"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "1"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.1216997074", "10.255.255.0/28"), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("Not Found: %s", resourceName) + } + + return fmt.Sprintf("%s/%s", rs.Primary.Attributes["dx_gateway_id"], rs.Primary.Attributes["associated_gateway_id"]), nil + }, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAwsDxGatewayAssociation_basicTransitGateway(t *testing.T) { + resourceName := "aws_dx_gateway_association.test" + resourceNameDxGw := "aws_dx_gateway.test" + resourceNameTgw := "aws_ec2_transit_gateway.test" + rName := fmt.Sprintf("terraform-testacc-dxgwassoc-%d", acctest.RandInt()) + rBgpAsn := randIntRange(64512, 65534) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsDxGatewayAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDxGatewayAssociationConfig_basicTransitGateway(rName, rBgpAsn), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsDxGatewayAssociationExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), + resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameTgw, "id"), + resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), + resource.TestCheckNoResourceAttr(resourceName, "vpn_gateway_id"), + resource.TestCheckResourceAttrSet(resourceName, "transit_gateway_attachment_id"), + resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "transitGateway"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "2"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.2173830893", "10.255.255.0/30"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.2984398124", "10.255.255.8/30"), + ), + }, { ResourceName: resourceName, ImportStateIdFunc: func(s *terraform.State) (string, error) { @@ -132,7 +208,7 @@ func TestAccAwsDxGatewayAssociation_basic(t *testing.T) { return "", fmt.Errorf("Not Found: %s", resourceName) } - return fmt.Sprintf("%s/%s", rs.Primary.Attributes["dx_gateway_id"], rs.Primary.Attributes["vpn_gateway_id"]), nil + return fmt.Sprintf("%s/%s", rs.Primary.Attributes["dx_gateway_id"], rs.Primary.Attributes["associated_gateway_id"]), nil }, ImportState: true, ImportStateVerify: true, @@ -170,7 +246,7 @@ func TestAccAwsDxGatewayAssociation_multiVgws(t *testing.T) { }) } -func TestAccAwsDxGatewayAssociation_allowedPrefixes(t *testing.T) { +func TestAccAwsDxGatewayAssociation_allowedPrefixesVpnGateway(t *testing.T) { resourceName := "aws_dx_gateway_association.test" resourceNameDxGw := "aws_dx_gateway.test" resourceNameVgw := "aws_vpn_gateway.test" @@ -183,11 +259,11 @@ func TestAccAwsDxGatewayAssociation_allowedPrefixes(t *testing.T) { CheckDestroy: testAccCheckAwsDxGatewayAssociationDestroy, Steps: []resource.TestStep{ { - Config: testAccDxGatewayAssociationConfig_allowedPrefixes(rName, rBgpAsn), + Config: testAccDxGatewayAssociationConfig_allowedPrefixesVpnGateway(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName), resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), - resource.TestCheckResourceAttrPair(resourceName, "vpn_gateway_id", resourceNameVgw, "id"), + resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameVgw, "id"), resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "2"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.2173830893", "10.255.255.0/30"), @@ -195,7 +271,7 @@ func TestAccAwsDxGatewayAssociation_allowedPrefixes(t *testing.T) { ), }, { - Config: testAccDxGatewayAssociationConfig_allowedPrefixesUpdated(rName, rBgpAsn), + Config: testAccDxGatewayAssociationConfig_allowedPrefixesVpnGatewayUpdated(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "1"), @@ -214,13 +290,17 @@ func testAccCheckAwsDxGatewayAssociationDestroy(s *terraform.State) error { continue } - resp, _ := conn.DescribeDirectConnectGatewayAssociations(&directconnect.DescribeDirectConnectGatewayAssociationsInput{ - DirectConnectGatewayId: aws.String(rs.Primary.Attributes["dx_gateway_id"]), - VirtualGatewayId: aws.String(rs.Primary.Attributes["vpn_gateway_id"]), + resp, err := conn.DescribeDirectConnectGatewayAssociations(&directconnect.DescribeDirectConnectGatewayAssociationsInput{ + AssociationId: aws.String(rs.Primary.Attributes["dx_gateway_association_id"]), }) + if err != nil { + return err + } if len(resp.DirectConnectGatewayAssociations) > 0 { - return fmt.Errorf("Direct Connect Gateway (%s) is not dissociated from VGW %s", rs.Primary.Attributes["dx_gateway_id"], rs.Primary.Attributes["vpn_gateway_id"]) + 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 nil @@ -240,15 +320,16 @@ func testAccCheckAwsDxGatewayAssociationExists(name string) resource.TestCheckFu } } -func testAccDxGatewayAssociationConfig_basic(rName string, rBgpAsn int) string { +func testAccDxGatewayAssociationConfig_deprecated(rName string, rBgpAsn int) string { return fmt.Sprintf(` resource "aws_dx_gateway" "test" { - name = %[1]q + name = %[1]q amazon_side_asn = "%[2]d" } resource "aws_vpc" "test" { cidr_block = "10.255.255.0/28" + tags = { Name = %[1]q } @@ -261,26 +342,85 @@ resource "aws_vpn_gateway" "test" { } resource "aws_vpn_gateway_attachment" "test" { - vpc_id = "${aws_vpc.test.id}" + vpc_id = "${aws_vpc.test.id}" vpn_gateway_id = "${aws_vpn_gateway.test.id}" } resource "aws_dx_gateway_association" "test" { - dx_gateway_id = "${aws_dx_gateway.test.id}" + dx_gateway_id = "${aws_dx_gateway.test.id}" vpn_gateway_id = "${aws_vpn_gateway_attachment.test.vpn_gateway_id}" } `, rName, rBgpAsn) } +func testAccDxGatewayAssociationConfig_basicVpnGateway(rName string, rBgpAsn int) string { + return fmt.Sprintf(` +resource "aws_dx_gateway" "test" { + name = %[1]q + amazon_side_asn = "%[2]d" +} + +resource "aws_vpc" "test" { + cidr_block = "10.255.255.0/28" + + tags = { + Name = %[1]q + } +} + +resource "aws_vpn_gateway" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_vpn_gateway_attachment" "test" { + vpc_id = "${aws_vpc.test.id}" + vpn_gateway_id = "${aws_vpn_gateway.test.id}" +} + +resource "aws_dx_gateway_association" "test" { + dx_gateway_id = "${aws_dx_gateway.test.id}" + associated_gateway_id = "${aws_vpn_gateway_attachment.test.vpn_gateway_id}" +} +`, rName, rBgpAsn) +} + +func testAccDxGatewayAssociationConfig_basicTransitGateway(rName string, rBgpAsn int) string { + return fmt.Sprintf(` +resource "aws_dx_gateway" "test" { + name = %[1]q + amazon_side_asn = "%[2]d" +} + +resource "aws_ec2_transit_gateway" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_dx_gateway_association" "test" { + dx_gateway_id = "${aws_dx_gateway.test.id}" + associated_gateway_id = "${aws_ec2_transit_gateway.test.id}" + + allowed_prefixes = [ + "10.255.255.0/30", + "10.255.255.8/30", + ] +} +`, rName, rBgpAsn) +} + func testAccDxGatewayAssociationConfig_multiVgws(rName1, rName2 string, rBgpAsn int) string { return fmt.Sprintf(` resource "aws_dx_gateway" "test" { - name = %[1]q + name = %[1]q amazon_side_asn = "%[3]d" } resource "aws_vpc" "test1" { cidr_block = "10.255.255.16/28" + tags = { Name = %[1]q } @@ -293,17 +433,18 @@ resource "aws_vpn_gateway" "test1" { } resource "aws_vpn_gateway_attachment" "test1" { - vpc_id = "${aws_vpc.test1.id}" + vpc_id = "${aws_vpc.test1.id}" vpn_gateway_id = "${aws_vpn_gateway.test1.id}" } resource "aws_dx_gateway_association" "test1" { - dx_gateway_id = "${aws_dx_gateway.test.id}" - vpn_gateway_id = "${aws_vpn_gateway_attachment.test1.vpn_gateway_id}" + dx_gateway_id = "${aws_dx_gateway.test.id}" + associated_gateway_id = "${aws_vpn_gateway_attachment.test1.vpn_gateway_id}" } resource "aws_vpc" "test2" { cidr_block = "10.255.255.32/28" + tags = { Name = %[2]q } @@ -316,26 +457,27 @@ resource "aws_vpn_gateway" "test2" { } resource "aws_vpn_gateway_attachment" "test2" { - vpc_id = "${aws_vpc.test2.id}" + vpc_id = "${aws_vpc.test2.id}" vpn_gateway_id = "${aws_vpn_gateway.test2.id}" } resource "aws_dx_gateway_association" "test2" { - dx_gateway_id = "${aws_dx_gateway.test.id}" - vpn_gateway_id = "${aws_vpn_gateway_attachment.test2.vpn_gateway_id}" + dx_gateway_id = "${aws_dx_gateway.test.id}" + associated_gateway_id = "${aws_vpn_gateway_attachment.test2.vpn_gateway_id}" } `, rName1, rName2, rBgpAsn) } -func testAccDxGatewayAssociationConfig_allowedPrefixes(rName string, rBgpAsn int) string { +func testAccDxGatewayAssociationConfig_allowedPrefixesVpnGateway(rName string, rBgpAsn int) string { return fmt.Sprintf(` resource "aws_dx_gateway" "test" { - name = %[1]q + name = %[1]q amazon_side_asn = "%[2]d" } resource "aws_vpc" "test" { cidr_block = "10.255.255.0/28" + tags = { Name = %[1]q } @@ -348,13 +490,14 @@ resource "aws_vpn_gateway" "test" { } resource "aws_vpn_gateway_attachment" "test" { - vpc_id = "${aws_vpc.test.id}" + vpc_id = "${aws_vpc.test.id}" vpn_gateway_id = "${aws_vpn_gateway.test.id}" } resource "aws_dx_gateway_association" "test" { - dx_gateway_id = "${aws_dx_gateway.test.id}" - vpn_gateway_id = "${aws_vpn_gateway_attachment.test.vpn_gateway_id}" + dx_gateway_id = "${aws_dx_gateway.test.id}" + associated_gateway_id = "${aws_vpn_gateway_attachment.test.vpn_gateway_id}" + allowed_prefixes = [ "10.255.255.0/30", "10.255.255.8/30", @@ -363,15 +506,16 @@ resource "aws_dx_gateway_association" "test" { `, rName, rBgpAsn) } -func testAccDxGatewayAssociationConfig_allowedPrefixesUpdated(rName string, rBgpAsn int) string { +func testAccDxGatewayAssociationConfig_allowedPrefixesVpnGatewayUpdated(rName string, rBgpAsn int) string { return fmt.Sprintf(` resource "aws_dx_gateway" "test" { - name = %[1]q + name = %[1]q amazon_side_asn = "%[2]d" } resource "aws_vpc" "test" { cidr_block = "10.255.255.0/28" + tags = { Name = %[1]q } @@ -384,13 +528,14 @@ resource "aws_vpn_gateway" "test" { } resource "aws_vpn_gateway_attachment" "test" { - vpc_id = "${aws_vpc.test.id}" + vpc_id = "${aws_vpc.test.id}" vpn_gateway_id = "${aws_vpn_gateway.test.id}" } resource "aws_dx_gateway_association" "test" { - dx_gateway_id = "${aws_dx_gateway.test.id}" - vpn_gateway_id = "${aws_vpn_gateway_attachment.test.vpn_gateway_id}" + dx_gateway_id = "${aws_dx_gateway.test.id}" + associated_gateway_id = "${aws_vpn_gateway_attachment.test.vpn_gateway_id}" + allowed_prefixes = [ "10.255.255.8/29", ] diff --git a/website/docs/r/dx_gateway_association.html.markdown b/website/docs/r/dx_gateway_association.html.markdown index 2cfe37ce2e9..e36a52c9a62 100644 --- a/website/docs/r/dx_gateway_association.html.markdown +++ b/website/docs/r/dx_gateway_association.html.markdown @@ -3,16 +3,20 @@ layout: "aws" page_title: "AWS: aws_dx_gateway_association" sidebar_current: "docs-aws-resource-dx-gateway-association" description: |- - Associates a Direct Connect Gateway with a VGW. + Associates a Direct Connect Gateway with a VGW or transit gateway. --- # Resource: aws_dx_gateway_association -Associates a Direct Connect Gateway with a VGW. For creating cross-account association proposals, see the [`aws_dx_gateway_association_proposal` resource](/docs/providers/aws/r/dx_gateway_association_proposal.html). +Associates a Direct Connect Gateway with a VGW or transit gateway. + +To create a cross-account association, create an [`aws_dx_gateway_association_proposal` resource](/docs/providers/aws/r/dx_gateway_association_proposal.html) +in the AWS account that owns the VGW or transit gateway and then accept the proposal in the AWS account that owns the Direct Connect Gateway +by creating an `aws_dx_gateway_association` resource with the `proposal_id` and `associated_gateway_owner_account_id` attributes set. ## Example Usage -### Basic +### VGW Single Account ```hcl resource "aws_dx_gateway" "example" { @@ -29,12 +33,12 @@ resource "aws_vpn_gateway" "example" { } resource "aws_dx_gateway_association" "example" { - dx_gateway_id = "${aws_dx_gateway.example.id}" - vpn_gateway_id = "${aws_vpn_gateway.example.id}" + dx_gateway_id = "${aws_dx_gateway.example.id}" + associated_gateway_id = "${aws_vpn_gateway.example.id}" } ``` -### Allowed Prefixes +### Single Account VGW With Allowed Prefixes ```hcl resource "aws_dx_gateway" "example" { @@ -51,8 +55,8 @@ resource "aws_vpn_gateway" "example" { } resource "aws_dx_gateway_association" "example" { - dx_gateway_id = "${aws_dx_gateway.example.id}" - vpn_gateway_id = "${aws_vpn_gateway.example.id}" + dx_gateway_id = "${aws_dx_gateway.example.id}" + associated_gateway_id = "${aws_vpn_gateway.example.id}" allowed_prefixes = [ "210.52.109.0/24", @@ -61,12 +65,36 @@ resource "aws_dx_gateway_association" "example" { } ``` +### Transit Gateway Single Account + +```hcl +resource "aws_dx_gateway" "example" { + name = "example" + amazon_side_asn = "64512" +} + +resource "aws_ec2_transit_gateway" "example" {} + +resource "aws_dx_gateway_association" "example" { + dx_gateway_id = "${aws_dx_gateway.example.id}" + associated_gateway_id = "${aws_ec2_transit_gateway.example.id}" + + allowed_prefixes = [ + "10.255.255.0/30", + "10.255.255.8/30", + ] +} +``` + ## Argument Reference +~> **NOTE:** One of `associated_gateway_id`, or `vpn_gateway_id` must be specified. + The following arguments are supported: * `dx_gateway_id` - (Required) The ID of the Direct Connect gateway. -* `vpn_gateway_id` - (Required) The ID of the VGW with which to associate the gateway. +* `associated_gateway_id` - (Optional) The ID of the VGW or transit gateway with which to associate the Direct Connect gateway. +* `vpn_gateway_id` - (Optional) *Deprecated:* Use `associated_gateway_id` instead. The ID of the VGW with which to associate the gateway. * `allowed_prefixes` - (Optional) VPC prefixes (CIDRs) to advertise to the Direct Connect gateway. Defaults to the CIDR block of the VPC associated with the Virtual Gateway. To enable drift detection, must be configured. ## Attributes Reference @@ -74,7 +102,9 @@ The following arguments are supported: In addition to all arguments above, the following attributes are exported: * `id` - The ID of the Direct Connect gateway association resource. +* `associated_gateway_type` - The type of the associated gateway, `transitGateway` or `virtualPrivateGateway`. * `dx_gateway_association_id` - The ID of the Direct Connect gateway association. +* `transit_gateway_attachment_id` - The ID of the transit gateway attachment. ## Timeouts @@ -87,7 +117,7 @@ In addition to all arguments above, the following attributes are exported: ## Import -Direct Connect gateway associations can be imported using `dx_gateway_id` together with `vpn_gateway_id`, +Direct Connect gateway associations can be imported using `dx_gateway_id` together with `associated_gateway_id`, e.g. ``` diff --git a/website/docs/r/dx_gateway_association_proposal.html.markdown b/website/docs/r/dx_gateway_association_proposal.html.markdown index 8ee72a14a2d..e401a076ad7 100644 --- a/website/docs/r/dx_gateway_association_proposal.html.markdown +++ b/website/docs/r/dx_gateway_association_proposal.html.markdown @@ -13,11 +13,17 @@ Manages a Direct Connect Gateway Association Proposal, typically for enabling cr ## Example Usage ```hcl -resource "aws_dx_gateway" "example" { - name = "example" - amazon_side_asn = "64512" +provider "aws" { + # Creator's credentials. } +provider "aws" { + alias = "accepter" + + # Accepter's credentials. +} + +# Creator's side of the proposal. resource "aws_vpc" "example" { cidr_block = "10.255.255.0/28" } @@ -29,24 +35,37 @@ resource "aws_vpn_gateway" "example" { resource "aws_dx_gateway_association_proposal" "example" { dx_gateway_id = "${aws_dx_gateway.example.id}" dx_gateway_owner_account_id = "${aws_dx_gateway.example.owner_account_id}" - vpn_gateway_id = "${aws_vpn_gateway.example.id}" + associated_gateway_id = "${aws_vpn_gateway.example.id}" +} + +# Accepter's side of the proposal. +resource "aws_dx_gateway" "example" { + provider = "aws.accepter" + + name = "example" + amazon_side_asn = "64512" } ``` ## Argument Reference +~> **NOTE:** One of `associated_gateway_id`, or `vpn_gateway_id` must be specified. + The following arguments are supported: * `dx_gateway_id` - (Required) Direct Connect Gateway identifier. -* `dx_gateway_owner_account_id` - (Required) AWS Account identifier of the Direct Connect Gateway. -* `vpn_gateway_id` - (Required) Virtual Gateway identifier to associate with the Direct Connect Gateway. +* `dx_gateway_owner_account_id` - (Required) AWS Account identifier of the Direct Connect Gateway's owner. +* `associated_gateway_id` - (Optional) The ID of the VGW or transit gateway with which to associate the Direct Connect gateway. +* `vpn_gateway_id` - (Optional) *Deprecated:* Use `associated_gateway_id` instead. Virtual Gateway identifier to associate with the Direct Connect Gateway. * `allowed_prefixes` - (Optional) VPC prefixes (CIDRs) to advertise to the Direct Connect gateway. Defaults to the CIDR block of the VPC associated with the Virtual Gateway. To enable drift detection, must be configured. ## Attributes Reference In addition to all arguments above, the following attributes are exported: -* `id` - Direct Connect Gateway Association Proposal identifier +* `id` - Direct Connect Gateway Association Proposal identifier. +* `associated_gateway_owner_account_id` - The ID of the AWS account that owns the VGW or transit gateway with which to associate the Direct Connect gateway. +* `associated_gateway_type` - The type of the associated gateway, `transitGateway` or `virtualPrivateGateway`. ## Import From 02afd3b9f93ada75857a90700572dc400879768a Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 10 May 2019 13:36:32 -0400 Subject: [PATCH 02/13] Add association proposal acceptance (cross-account) to aws_dx_gateway_association. --- aws/resource_aws_dx_gateway_association.go | 103 +++-- ...ws_dx_gateway_association_proposal_test.go | 4 +- ...esource_aws_dx_gateway_association_test.go | 358 ++++++++++++++---- aws/resource_aws_dx_gateway_test.go | 2 +- .../r/dx_gateway_association.html.markdown | 56 ++- 5 files changed, 418 insertions(+), 105 deletions(-) diff --git a/aws/resource_aws_dx_gateway_association.go b/aws/resource_aws_dx_gateway_association.go index 94891367d21..049389545b1 100644 --- a/aws/resource_aws_dx_gateway_association.go +++ b/aws/resource_aws_dx_gateway_association.go @@ -41,8 +41,18 @@ func resourceAwsDxGatewayAssociation() *schema.Resource { "associated_gateway_id": { Type: schema.TypeString, Optional: true, + Computed: true, ForceNew: true, - ConflictsWith: []string{"vpn_gateway_id"}, + ConflictsWith: []string{"associated_gateway_owner_account_id", "proposal_id", "vpn_gateway_id"}, + }, + + "associated_gateway_owner_account_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validateAwsAccountId, + ConflictsWith: []string{"associated_gateway_id", "vpn_gateway_id"}, }, "associated_gateway_type": { @@ -61,6 +71,18 @@ func resourceAwsDxGatewayAssociation() *schema.Resource { ForceNew: true, }, + "dx_gateway_owner_account_id": { + Type: schema.TypeString, + Computed: true, + }, + + "proposal_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"associated_gateway_id", "vpn_gateway_id"}, + }, + "transit_gateway_attachment_id": { Type: schema.TypeString, Computed: true, @@ -70,7 +92,7 @@ func resourceAwsDxGatewayAssociation() *schema.Resource { Type: schema.TypeString, Optional: true, ForceNew: true, - ConflictsWith: []string{"associated_gateway_id"}, + ConflictsWith: []string{"associated_gateway_id", "associated_gateway_owner_account_id", "proposal_id"}, Deprecated: "use 'associated_gateway_id' argument instead", }, }, @@ -86,36 +108,63 @@ 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") vgwIdRaw, vgwIdOk := d.GetOk("vpn_gateway_id") - if !gwIdOk && !vgwIdOk { - return errors.New("one of associated_gateway_id or vpn_gateway_id must be configured") + gwAcctIdRaw, gwAcctIdOk := d.GetOk("associated_gateway_owner_account_id") + proposalIdRaw, proposalIdOk := d.GetOk("proposal_id") + + if gwAcctIdOk || proposalIdOk { + // Cross-account association. + if !(gwAcctIdOk && proposalIdOk) { + return errors.New("associated_gateway_owner_account_id and proposal_id must be configured") + } + } else if !(gwIdOk || vgwIdOk) { + return errors.New("either associated_gateway_owner_account_id and proposal_id or one of associated_gateway_id or vpn_gateway_id must be configured") } - dxgwId := d.Get("dx_gateway_id").(string) - gwId := "" + 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)), + } - req := &directconnect.CreateDirectConnectGatewayAssociationInput{ - AddAllowedPrefixesToDirectConnectGateway: expandDxRouteFilterPrefixes(d.Get("allowed_prefixes").(*schema.Set)), - DirectConnectGatewayId: aws.String(dxgwId), - } - if gwIdOk { - gwId = gwIdRaw.(string) - req.GatewayId = aws.String(gwId) + log.Printf("[DEBUG] Accepting Direct Connect gateway association proposal: %#v", req) + resp, err := conn.AcceptDirectConnectGatewayAssociationProposal(req) + if err != nil { + return fmt.Errorf("error accepting Direct Connect gateway association proposal: %s", 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))) } else { - gwId = vgwIdRaw.(string) - req.VirtualGatewayId = aws.String(gwId) - } + req := &directconnect.CreateDirectConnectGatewayAssociationInput{ + AddAllowedPrefixesToDirectConnectGateway: expandDxRouteFilterPrefixes(d.Get("allowed_prefixes").(*schema.Set)), + DirectConnectGatewayId: aws.String(dxgwId), + } + gwId := "" + if gwIdOk { + gwId = gwIdRaw.(string) + req.GatewayId = aws.String(gwId) + } else { + gwId = vgwIdRaw.(string) + req.VirtualGatewayId = aws.String(gwId) + } - log.Printf("[DEBUG] Creating Direct Connect gateway association: %#v", req) - resp, err := conn.CreateDirectConnectGatewayAssociation(req) - if err != nil { - return fmt.Errorf("error creating Direct Connect gateway association: %s", err) - } + log.Printf("[DEBUG] Creating Direct Connect gateway association: %#v", req) + resp, err := conn.CreateDirectConnectGatewayAssociation(req) + if err != nil { + return fmt.Errorf("error creating Direct Connect gateway association: %s", 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)) + // 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)) + } d.Set("dx_gateway_association_id", associationId) if err := waitForDirectConnectGatewayAssociationAvailabilityOnCreate(conn, associationId, d.Timeout(schema.TimeoutCreate)); err != nil { @@ -151,10 +200,13 @@ func resourceAwsDxGatewayAssociationRead(d *schema.ResourceData, meta interface{ } else { 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) + tgwAttachmentId := "" if aws.StringValue(assoc.AssociatedGateway.Type) == directconnect.GatewayTypeTransitGateway { ec2conn := meta.(*AWSClient).ec2conn @@ -178,8 +230,9 @@ func resourceAwsDxGatewayAssociationRead(d *schema.ResourceData, meta interface{ return fmt.Errorf("error reading Direct Connect gateway association (%s) transit gateway attachment: multiple responses", d.Id()) } - d.Set("transit_gateway_attachment_id", resp.TransitGatewayAttachments[0].TransitGatewayAttachmentId) + tgwAttachmentId = aws.StringValue(resp.TransitGatewayAttachments[0].TransitGatewayAttachmentId) } + d.Set("transit_gateway_attachment_id", tgwAttachmentId) 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 038f49f2a54..730ffbd59e1 100644 --- a/aws/resource_aws_dx_gateway_association_proposal_test.go +++ b/aws/resource_aws_dx_gateway_association_proposal_test.go @@ -236,7 +236,7 @@ resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" tags = { - Name = "tf-acc-test-dx-gateway-association-proposal" + Name = %[1]q } } @@ -244,7 +244,7 @@ resource "aws_vpn_gateway" "test" { vpc_id = "${aws_vpc.test.id}" tags = { - Name = "tf-acc-test-dx-gateway-association-proposal" + Name = %[1]q } } `, rName, rBgpAsn) diff --git a/aws/resource_aws_dx_gateway_association_test.go b/aws/resource_aws_dx_gateway_association_test.go index 6582e321ac7..4a1667d119c 100644 --- a/aws/resource_aws_dx_gateway_association_test.go +++ b/aws/resource_aws_dx_gateway_association_test.go @@ -3,6 +3,7 @@ package aws import ( "fmt" "log" + "regexp" "testing" "time" @@ -10,6 +11,7 @@ import ( "github.com/aws/aws-sdk-go/service/directconnect" "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" ) @@ -100,7 +102,7 @@ func testSweepDirectConnectGatewayAssociations(region string) error { return nil } -func TestAccAwsDxGatewayAssociation_deprecated(t *testing.T) { +func TestAccAwsDxGatewayAssociation_deprecatedSingleAccount(t *testing.T) { resourceName := "aws_dx_gateway_association.test" resourceNameDxGw := "aws_dx_gateway.test" resourceNameVgw := "aws_vpn_gateway.test" @@ -113,15 +115,17 @@ func TestAccAwsDxGatewayAssociation_deprecated(t *testing.T) { CheckDestroy: testAccCheckAwsDxGatewayAssociationDestroy, Steps: []resource.TestStep{ { - Config: testAccDxGatewayAssociationConfig_deprecated(rName, rBgpAsn), + Config: testAccDxGatewayAssociationConfig_deprecatedSingleAccount(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName), resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), resource.TestCheckResourceAttrPair(resourceName, "vpn_gateway_id", resourceNameVgw, "id"), resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), resource.TestCheckNoResourceAttr(resourceName, "associated_gateway_id"), - resource.TestCheckNoResourceAttr(resourceName, "transit_gateway_attachment_id"), resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "virtualPrivateGateway"), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_attachment_id", ""), + testAccCheckResourceAttrAccountID(resourceName, "associated_gateway_owner_account_id"), + testAccCheckResourceAttrAccountID(resourceName, "dx_gateway_owner_account_id"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "1"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.1216997074", "10.255.255.0/28"), ), @@ -130,7 +134,7 @@ func TestAccAwsDxGatewayAssociation_deprecated(t *testing.T) { }) } -func TestAccAwsDxGatewayAssociation_basicVpnGateway(t *testing.T) { +func TestAccAwsDxGatewayAssociation_basicVpnGatewaySingleAccount(t *testing.T) { resourceName := "aws_dx_gateway_association.test" resourceNameDxGw := "aws_dx_gateway.test" resourceNameVgw := "aws_vpn_gateway.test" @@ -143,15 +147,17 @@ func TestAccAwsDxGatewayAssociation_basicVpnGateway(t *testing.T) { CheckDestroy: testAccCheckAwsDxGatewayAssociationDestroy, Steps: []resource.TestStep{ { - Config: testAccDxGatewayAssociationConfig_basicVpnGateway(rName, rBgpAsn), + Config: testAccDxGatewayAssociationConfig_basicVpnGatewaySingleAccount(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName), resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameVgw, "id"), resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), resource.TestCheckNoResourceAttr(resourceName, "vpn_gateway_id"), - resource.TestCheckNoResourceAttr(resourceName, "transit_gateway_attachment_id"), resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "virtualPrivateGateway"), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_attachment_id", ""), + testAccCheckResourceAttrAccountID(resourceName, "associated_gateway_owner_account_id"), + testAccCheckResourceAttrAccountID(resourceName, "dx_gateway_owner_account_id"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "1"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.1216997074", "10.255.255.0/28"), ), @@ -173,7 +179,44 @@ func TestAccAwsDxGatewayAssociation_basicVpnGateway(t *testing.T) { }) } -func TestAccAwsDxGatewayAssociation_basicTransitGateway(t *testing.T) { +func TestAccAwsDxGatewayAssociation_basicVpnGatewayCrossAccount(t *testing.T) { + var providers []*schema.Provider + resourceName := "aws_dx_gateway_association.test" + resourceNameDxGw := "aws_dx_gateway.test" + resourceNameVgw := "aws_vpn_gateway.test" + rName := fmt.Sprintf("terraform-testacc-dxgwassoc-%d", acctest.RandInt()) + rBgpAsn := randIntRange(64512, 65534) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccAlternateAccountPreCheck(t) + }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckAwsDxGatewayAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDxGatewayAssociationConfig_basicVpnGatewayCrossAccount(rName, rBgpAsn), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsDxGatewayAssociationExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), + resource.TestCheckResourceAttrPair(resourceName, "vpn_gateway_id", resourceNameVgw, "id"), + resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), + resource.TestCheckNoResourceAttr(resourceName, "vpn_gateway_id"), + resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "virtualPrivateGateway"), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_attachment_id", ""), + testAccCheckResourceAttrAccountID(resourceName, "associated_gateway_owner_account_id"), + // dx_gateway_owner_account_id is the "aws.alternate" provider's account ID. + // testAccCheckResourceAttrAccountID(resourceName, "dx_gateway_owner_account_id"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "1"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.1216997074", "10.255.255.0/28"), + ), + }, + }, + }) +} + +func TestAccAwsDxGatewayAssociation_basicTransitGatewaySingleAccount(t *testing.T) { resourceName := "aws_dx_gateway_association.test" resourceNameDxGw := "aws_dx_gateway.test" resourceNameTgw := "aws_ec2_transit_gateway.test" @@ -186,15 +229,17 @@ func TestAccAwsDxGatewayAssociation_basicTransitGateway(t *testing.T) { CheckDestroy: testAccCheckAwsDxGatewayAssociationDestroy, Steps: []resource.TestStep{ { - Config: testAccDxGatewayAssociationConfig_basicTransitGateway(rName, rBgpAsn), + Config: testAccDxGatewayAssociationConfig_basicTransitGatewaySingleAccount(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName), resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameTgw, "id"), resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), resource.TestCheckNoResourceAttr(resourceName, "vpn_gateway_id"), - resource.TestCheckResourceAttrSet(resourceName, "transit_gateway_attachment_id"), resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "transitGateway"), + resource.TestMatchResourceAttr(resourceName, "transit_gateway_attachment_id", regexp.MustCompile(`^tgw-attach-.+`)), + testAccCheckResourceAttrAccountID(resourceName, "associated_gateway_owner_account_id"), + testAccCheckResourceAttrAccountID(resourceName, "dx_gateway_owner_account_id"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "2"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.2173830893", "10.255.255.0/30"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.2984398124", "10.255.255.8/30"), @@ -217,7 +262,41 @@ func TestAccAwsDxGatewayAssociation_basicTransitGateway(t *testing.T) { }) } -func TestAccAwsDxGatewayAssociation_multiVgws(t *testing.T) { +func TestAccAwsDxGatewayAssociation_basicTransitGatewayCrossAccount(t *testing.T) { + resourceName := "aws_dx_gateway_association.test" + resourceNameDxGw := "aws_dx_gateway.test" + resourceNameTgw := "aws_ec2_transit_gateway.test" + rName := fmt.Sprintf("terraform-testacc-dxgwassoc-%d", acctest.RandInt()) + rBgpAsn := randIntRange(64512, 65534) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsDxGatewayAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDxGatewayAssociationConfig_basicTransitGatewayCrossAccount(rName, rBgpAsn), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsDxGatewayAssociationExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), + resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameTgw, "id"), + resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), + resource.TestCheckNoResourceAttr(resourceName, "vpn_gateway_id"), + resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "transitGateway"), + resource.TestMatchResourceAttr(resourceName, "transit_gateway_attachment_id", regexp.MustCompile(`^tgw-attach-.+`)), + testAccCheckResourceAttrAccountID(resourceName, "associated_gateway_owner_account_id"), + // dx_gateway_owner_account_id is the "aws.alternate" provider's account ID. + // testAccCheckResourceAttrAccountID(resourceName, "dx_gateway_owner_account_id"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "2"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.2173830893", "10.255.255.0/30"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.2984398124", "10.255.255.8/30"), + ), + }, + }, + }) +} + +func TestAccAwsDxGatewayAssociation_multiVpnGatewaysSingleAccount(t *testing.T) { resourceName1 := "aws_dx_gateway_association.test1" resourceName2 := "aws_dx_gateway_association.test2" rName1 := fmt.Sprintf("terraform-testacc-dxgwassoc-%d", acctest.RandInt()) @@ -230,7 +309,7 @@ func TestAccAwsDxGatewayAssociation_multiVgws(t *testing.T) { CheckDestroy: testAccCheckAwsDxGatewayAssociationDestroy, Steps: []resource.TestStep{ { - Config: testAccDxGatewayAssociationConfig_multiVgws(rName1, rName2, rBgpAsn), + Config: testAccDxGatewayAssociationConfig_multiVpnGatewaysSingleAccount(rName1, rName2, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName1), testAccCheckAwsDxGatewayAssociationExists(resourceName2), @@ -246,7 +325,7 @@ func TestAccAwsDxGatewayAssociation_multiVgws(t *testing.T) { }) } -func TestAccAwsDxGatewayAssociation_allowedPrefixesVpnGateway(t *testing.T) { +func TestAccAwsDxGatewayAssociation_allowedPrefixesVpnGatewaySingleAccount(t *testing.T) { resourceName := "aws_dx_gateway_association.test" resourceNameDxGw := "aws_dx_gateway.test" resourceNameVgw := "aws_vpn_gateway.test" @@ -259,7 +338,7 @@ func TestAccAwsDxGatewayAssociation_allowedPrefixesVpnGateway(t *testing.T) { CheckDestroy: testAccCheckAwsDxGatewayAssociationDestroy, Steps: []resource.TestStep{ { - Config: testAccDxGatewayAssociationConfig_allowedPrefixesVpnGateway(rName, rBgpAsn), + Config: testAccDxGatewayAssociationConfig_allowedPrefixesVpnGatewaySingleAccount(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName), resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), @@ -271,7 +350,7 @@ func TestAccAwsDxGatewayAssociation_allowedPrefixesVpnGateway(t *testing.T) { ), }, { - Config: testAccDxGatewayAssociationConfig_allowedPrefixesVpnGatewayUpdated(rName, rBgpAsn), + Config: testAccDxGatewayAssociationConfig_allowedPrefixesVpnGatewaySingleAccountUpdated(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "1"), @@ -282,6 +361,51 @@ func TestAccAwsDxGatewayAssociation_allowedPrefixesVpnGateway(t *testing.T) { }) } +func TestAccAwsDxGatewayAssociation_allowedPrefixesVpnGatewayCrossAccount(t *testing.T) { + var providers []*schema.Provider + resourceName := "aws_dx_gateway_association.test" + resourceNameDxGw := "aws_dx_gateway.test" + resourceNameVgw := "aws_vpn_gateway.test" + rName := fmt.Sprintf("terraform-testacc-dxgwassoc-%d", acctest.RandInt()) + rBgpAsn := randIntRange(64512, 65534) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccAlternateAccountPreCheck(t) + }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckAwsDxGatewayAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDxGatewayAssociationConfig_allowedPrefixesVpnGatewayCrossAccount(rName, rBgpAsn), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsDxGatewayAssociationExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), + resource.TestCheckResourceAttrPair(resourceName, "vpn_gateway_id", resourceNameVgw, "id"), + resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "1"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.1642241106", "10.255.255.8/29"), + ), + // Accepting the proposal with overridden prefixes changes the returned RequestedAllowedPrefixesToDirectConnectGateway value (allowed_prefixes attribute). + ExpectNonEmptyPlan: true, + }, + { + Config: testAccDxGatewayAssociationConfig_allowedPrefixesVpnGatewayCrossAccountUpdated(rName, rBgpAsn), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsDxGatewayAssociationExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), + resource.TestCheckResourceAttrPair(resourceName, "vpn_gateway_id", resourceNameVgw, "id"), + resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "2"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.2173830893", "10.255.255.0/30"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.2984398124", "10.255.255.8/30"), + ), + }, + }, + }) +} + func testAccCheckAwsDxGatewayAssociationDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).dxconn @@ -320,7 +444,7 @@ func testAccCheckAwsDxGatewayAssociationExists(name string) resource.TestCheckFu } } -func testAccDxGatewayAssociationConfig_deprecated(rName string, rBgpAsn int) string { +func testAccDxGatewayAssociationConfigBase_vpnGatewaySingleAccount(rName string, rBgpAsn int) string { return fmt.Sprintf(` resource "aws_dx_gateway" "test" { name = %[1]q @@ -345,20 +469,13 @@ resource "aws_vpn_gateway_attachment" "test" { vpc_id = "${aws_vpc.test.id}" vpn_gateway_id = "${aws_vpn_gateway.test.id}" } - -resource "aws_dx_gateway_association" "test" { - dx_gateway_id = "${aws_dx_gateway.test.id}" - vpn_gateway_id = "${aws_vpn_gateway_attachment.test.vpn_gateway_id}" -} `, rName, rBgpAsn) } -func testAccDxGatewayAssociationConfig_basicVpnGateway(rName string, rBgpAsn int) string { - return fmt.Sprintf(` -resource "aws_dx_gateway" "test" { - name = %[1]q - amazon_side_asn = "%[2]d" -} +func testAccDxGatewayAssociationConfigBase_vpnGatewayCrossAccount(rName string, rBgpAsn int) string { + return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` +# Creator +data "aws_caller_identity" "creator" {} resource "aws_vpc" "test" { cidr_block = "10.255.255.0/28" @@ -379,14 +496,55 @@ resource "aws_vpn_gateway_attachment" "test" { vpn_gateway_id = "${aws_vpn_gateway.test.id}" } +# Accepter +resource "aws_dx_gateway" "test" { + provider = "aws.alternate" + + amazon_side_asn = %[2]d + name = %[1]q +} +`, rName, rBgpAsn) +} + +func testAccDxGatewayAssociationConfig_deprecatedSingleAccount(rName string, rBgpAsn int) string { + return testAccDxGatewayAssociationConfigBase_vpnGatewaySingleAccount(rName, rBgpAsn) + fmt.Sprintf(` +resource "aws_dx_gateway_association" "test" { + dx_gateway_id = "${aws_dx_gateway.test.id}" + vpn_gateway_id = "${aws_vpn_gateway_attachment.test.vpn_gateway_id}" +} +`) +} + +func testAccDxGatewayAssociationConfig_basicVpnGatewaySingleAccount(rName string, rBgpAsn int) string { + return testAccDxGatewayAssociationConfigBase_vpnGatewaySingleAccount(rName, rBgpAsn) + fmt.Sprintf(` resource "aws_dx_gateway_association" "test" { dx_gateway_id = "${aws_dx_gateway.test.id}" associated_gateway_id = "${aws_vpn_gateway_attachment.test.vpn_gateway_id}" } -`, rName, rBgpAsn) +`) +} + +func testAccDxGatewayAssociationConfig_basicVpnGatewayCrossAccount(rName string, rBgpAsn int) string { + return testAccDxGatewayAssociationConfigBase_vpnGatewayCrossAccount(rName, rBgpAsn) + fmt.Sprintf(` +# 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}" +} + +# Accepter +resource "aws_dx_gateway_association" "test" { + provider = "aws.alternate" + + 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.creator.account_id}" +} +`) } -func testAccDxGatewayAssociationConfig_basicTransitGateway(rName string, rBgpAsn int) string { +func testAccDxGatewayAssociationConfig_basicTransitGatewaySingleAccount(rName string, rBgpAsn int) string { return fmt.Sprintf(` resource "aws_dx_gateway" "test" { name = %[1]q @@ -411,7 +569,49 @@ resource "aws_dx_gateway_association" "test" { `, rName, rBgpAsn) } -func testAccDxGatewayAssociationConfig_multiVgws(rName1, rName2 string, rBgpAsn int) string { +func testAccDxGatewayAssociationConfig_basicTransitGatewayCrossAccount(rName string, rBgpAsn int) string { + return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` +# Creator +data "aws_caller_identity" "creator" {} + +# Accepter +resource "aws_dx_gateway" "test" { + provider = "aws.alternate" + + amazon_side_asn = %[2]d + name = %[1]q +} + +resource "aws_ec2_transit_gateway" "test" { + tags = { + Name = %[1]q + } +} + +# 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_ec2_transit_gateway.test.id}" + + allowed_prefixes = [ + "10.255.255.0/30", + "10.255.255.8/30", + ] +} + +# Accepter +resource "aws_dx_gateway_association" "test" { + provider = "aws.alternate" + + 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.creator.account_id}" +} +`, rName, rBgpAsn) +} + +func testAccDxGatewayAssociationConfig_multiVpnGatewaysSingleAccount(rName1, rName2 string, rBgpAsn int) string { return fmt.Sprintf(` resource "aws_dx_gateway" "test" { name = %[1]q @@ -468,77 +668,83 @@ resource "aws_dx_gateway_association" "test2" { `, rName1, rName2, rBgpAsn) } -func testAccDxGatewayAssociationConfig_allowedPrefixesVpnGateway(rName string, rBgpAsn int) string { - return fmt.Sprintf(` -resource "aws_dx_gateway" "test" { - name = %[1]q - amazon_side_asn = "%[2]d" -} - -resource "aws_vpc" "test" { - cidr_block = "10.255.255.0/28" - - tags = { - Name = %[1]q - } -} +func testAccDxGatewayAssociationConfig_allowedPrefixesVpnGatewaySingleAccount(rName string, rBgpAsn int) string { + return testAccDxGatewayAssociationConfigBase_vpnGatewaySingleAccount(rName, rBgpAsn) + fmt.Sprintf(` +resource "aws_dx_gateway_association" "test" { + dx_gateway_id = "${aws_dx_gateway.test.id}" + associated_gateway_id = "${aws_vpn_gateway_attachment.test.vpn_gateway_id}" -resource "aws_vpn_gateway" "test" { - tags = { - Name = %[1]q - } + allowed_prefixes = [ + "10.255.255.0/30", + "10.255.255.8/30", + ] } - -resource "aws_vpn_gateway_attachment" "test" { - vpc_id = "${aws_vpc.test.id}" - vpn_gateway_id = "${aws_vpn_gateway.test.id}" +`) } +func testAccDxGatewayAssociationConfig_allowedPrefixesVpnGatewaySingleAccountUpdated(rName string, rBgpAsn int) string { + return testAccDxGatewayAssociationConfigBase_vpnGatewaySingleAccount(rName, rBgpAsn) + fmt.Sprintf(` resource "aws_dx_gateway_association" "test" { dx_gateway_id = "${aws_dx_gateway.test.id}" associated_gateway_id = "${aws_vpn_gateway_attachment.test.vpn_gateway_id}" allowed_prefixes = [ - "10.255.255.0/30", - "10.255.255.8/30", + "10.255.255.8/29", ] } -`, rName, rBgpAsn) +`) } -func testAccDxGatewayAssociationConfig_allowedPrefixesVpnGatewayUpdated(rName string, rBgpAsn int) string { - return fmt.Sprintf(` -resource "aws_dx_gateway" "test" { - name = %[1]q - amazon_side_asn = "%[2]d" +func testAccDxGatewayAssociationConfig_allowedPrefixesVpnGatewayCrossAccount(rName string, rBgpAsn int) string { + return testAccDxGatewayAssociationConfigBase_vpnGatewayCrossAccount(rName, rBgpAsn) + fmt.Sprintf(` +# 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}" + + allowed_prefixes = [ + "10.255.255.0/30", + "10.255.255.8/30", + ] } -resource "aws_vpc" "test" { - cidr_block = "10.255.255.0/28" +# Accepter +resource "aws_dx_gateway_association" "test" { + provider = "aws.alternate" - tags = { - Name = %[1]q - } -} + 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.creator.account_id}" -resource "aws_vpn_gateway" "test" { - tags = { - Name = %[1]q - } + allowed_prefixes = [ + "10.255.255.8/29", + ] +} +`) } -resource "aws_vpn_gateway_attachment" "test" { - vpc_id = "${aws_vpc.test.id}" - vpn_gateway_id = "${aws_vpn_gateway.test.id}" +func testAccDxGatewayAssociationConfig_allowedPrefixesVpnGatewayCrossAccountUpdated(rName string, rBgpAsn int) string { + return testAccDxGatewayAssociationConfigBase_vpnGatewayCrossAccount(rName, rBgpAsn) + fmt.Sprintf(` +# 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}" } +# Accepter resource "aws_dx_gateway_association" "test" { - dx_gateway_id = "${aws_dx_gateway.test.id}" - associated_gateway_id = "${aws_vpn_gateway_attachment.test.vpn_gateway_id}" + provider = "aws.alternate" + + 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.creator.account_id}" allowed_prefixes = [ - "10.255.255.8/29", + "10.255.255.0/30", + "10.255.255.8/30", ] } -`, rName, rBgpAsn) +`) } diff --git a/aws/resource_aws_dx_gateway_test.go b/aws/resource_aws_dx_gateway_test.go index 788b74327d2..bf78dd7568c 100644 --- a/aws/resource_aws_dx_gateway_test.go +++ b/aws/resource_aws_dx_gateway_test.go @@ -121,7 +121,7 @@ func TestAccAwsDxGateway_importComplex(t *testing.T) { CheckDestroy: testAccCheckAwsDxGatewayDestroy, Steps: []resource.TestStep{ { - Config: testAccDxGatewayAssociationConfig_multiVgws(rName1, rName2, rBgpAsn), + Config: testAccDxGatewayAssociationConfig_multiVpnGatewaysSingleAccount(rName1, rName2, rBgpAsn), }, { diff --git a/website/docs/r/dx_gateway_association.html.markdown b/website/docs/r/dx_gateway_association.html.markdown index e36a52c9a62..7905f37b662 100644 --- a/website/docs/r/dx_gateway_association.html.markdown +++ b/website/docs/r/dx_gateway_association.html.markdown @@ -86,15 +86,68 @@ resource "aws_dx_gateway_association" "example" { } ``` +### VGW Cross-Account + +```hcl +provider "aws" { + # Creator's credentials. +} + +provider "aws" { + alias = "accepter" + + # Accepter's credentials. +} + +# Creator's side of the proposal. +data "aws_caller_identity" "creator" {} + +resource "aws_vpc" "example" { + cidr_block = "10.255.255.0/28" +} + +resource "aws_vpn_gateway" "example" { + vpc_id = "${aws_vpc.example.id}" +} + +resource "aws_dx_gateway_association_proposal" "example" { + dx_gateway_id = "${aws_dx_gateway.example.id}" + dx_gateway_owner_account_id = "${aws_dx_gateway.example.owner_account_id}" + associated_gateway_id = "${aws_vpn_gateway.example.id}" +} + +# Accepter's side of the proposal. +resource "aws_dx_gateway" "example" { + provider = "aws.accepter" + + name = "example" + amazon_side_asn = "64512" +} + +resource "aws_dx_gateway_association" "example" { + provider = "aws.accepter" + + proposal_id = "${aws_dx_gateway_association_proposal.example.id}" + dx_gateway_id = "${aws_dx_gateway.example.id}" + associated_gateway_owner_account_id = "${data.aws_caller_identity.creator.account_id}" +} +``` + ## Argument Reference -~> **NOTE:** One of `associated_gateway_id`, or `vpn_gateway_id` must be specified. +~> **NOTE:** `dx_gateway_id` plus one of `associated_gateway_id`, or `vpn_gateway_id` must be specified for single account Direct Connect gateway associations. The following arguments are supported: * `dx_gateway_id` - (Required) The ID of the Direct Connect gateway. * `associated_gateway_id` - (Optional) The ID of the VGW or transit gateway with which to associate the Direct Connect gateway. +Used for single account Direct Connect gateway associations. * `vpn_gateway_id` - (Optional) *Deprecated:* Use `associated_gateway_id` instead. The ID of the VGW with which to associate the gateway. +Used for single account Direct Connect gateway associations. +* `associated_gateway_owner_account_id` - (Optional) The ID of the AWS account that owns the VGW or transit gateway with which to associate the Direct Connect gateway. +Used for cross-account Direct Connect gateway associations. +* `proposal_id` - (Optional) The ID of the Direct Connect gateway association proposal. +Used for cross-account Direct Connect gateway associations. * `allowed_prefixes` - (Optional) VPC prefixes (CIDRs) to advertise to the Direct Connect gateway. Defaults to the CIDR block of the VPC associated with the Virtual Gateway. To enable drift detection, must be configured. ## Attributes Reference @@ -104,6 +157,7 @@ In addition to all arguments above, the following attributes are exported: * `id` - The ID of the Direct Connect gateway association resource. * `associated_gateway_type` - The type of the associated gateway, `transitGateway` or `virtualPrivateGateway`. * `dx_gateway_association_id` - The ID of the Direct Connect gateway association. +* `dx_gateway_owner_account_id` - The ID of the AWS account that owns the Direct Connect gateway. * `transit_gateway_attachment_id` - The ID of the transit gateway attachment. ## Timeouts From 392969da9dd907874b77dfb8e119ec3f3e44ceb8 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 10 May 2019 14:22:45 -0400 Subject: [PATCH 03/13] Get all aws_dx_gateway_association_proposal acceptance tests passing. --- ...ws_dx_gateway_association_proposal_test.go | 72 ++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/aws/resource_aws_dx_gateway_association_proposal_test.go b/aws/resource_aws_dx_gateway_association_proposal_test.go index 730ffbd59e1..a6de072f86b 100644 --- a/aws/resource_aws_dx_gateway_association_proposal_test.go +++ b/aws/resource_aws_dx_gateway_association_proposal_test.go @@ -67,7 +67,8 @@ func TestAccAwsDxGatewayAssociationProposal_basicVpnGateway(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationProposalExists(resourceName, &proposal1), resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), - resource.TestCheckResourceAttrPair(resourceName, "vpn_gateway_id", resourceNameVgw, "id"), + resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameVgw, "id"), + resource.TestCheckNoResourceAttr(resourceName, "vpn_gateway_id"), testAccCheckResourceAttrAccountID(resourceName, "associated_gateway_owner_account_id"), resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "virtualPrivateGateway"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "1"), @@ -83,6 +84,47 @@ func TestAccAwsDxGatewayAssociationProposal_basicVpnGateway(t *testing.T) { }) } +func TestAccAwsDxGatewayAssociationProposal_basicTransitGateway(t *testing.T) { + var proposal1 directconnect.GatewayAssociationProposal + var providers []*schema.Provider + rBgpAsn := randIntRange(64512, 65534) + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_dx_gateway_association_proposal.test" + resourceNameDxGw := "aws_dx_gateway.test" + resourceNameTgw := "aws_ec2_transit_gateway.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccAlternateAccountPreCheck(t) + }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckAwsDxGatewayAssociationProposalDestroy, + Steps: []resource.TestStep{ + { + 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"), + resource.TestCheckNoResourceAttr(resourceName, "vpn_gateway_id"), + testAccCheckResourceAttrAccountID(resourceName, "associated_gateway_owner_account_id"), + resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "transitGateway"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "2"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.2173830893", "10.255.255.0/30"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.2984398124", "10.255.255.8/30"), + ), + }, + { + Config: testAccDxGatewayAssociationProposalConfig_basicVpnGateway(rName, rBgpAsn), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccAwsDxGatewayAssociationProposal_disappears(t *testing.T) { var proposal1 directconnect.GatewayAssociationProposal var providers []*schema.Provider @@ -270,6 +312,34 @@ resource "aws_dx_gateway_association_proposal" "test" { `) } +func testAccDxGatewayAssociationProposalConfig_basicTransitGateway(rName string, rBgpAsn int) string { + return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` +resource "aws_dx_gateway" "test" { + provider = "aws.alternate" + + amazon_side_asn = %[2]d + name = %[1]q +} + +resource "aws_ec2_transit_gateway" "test" { + tags = { + Name = %[1]q + } +} + +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_ec2_transit_gateway.test.id}" + + allowed_prefixes = [ + "10.255.255.0/30", + "10.255.255.8/30", + ] +} +`, rName, rBgpAsn) +} + func testAccDxGatewayAssociationProposalConfigAllowedPrefixes1(rName string, rBgpAsn int) string { return testAccDxGatewayAssociationProposalConfigBase_vpnGateway(rName, rBgpAsn) + fmt.Sprintf(` resource "aws_dx_gateway_association_proposal" "test" { From c7b11031ef557969c71da657cda87262cc1b03b5 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 10 May 2019 18:59:16 -0400 Subject: [PATCH 04/13] Get all aws_dx_gateway_association acceptance tests passing. --- aws/resource_aws_dx_gateway_association_test.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/aws/resource_aws_dx_gateway_association_test.go b/aws/resource_aws_dx_gateway_association_test.go index 4a1667d119c..6eeb74054ef 100644 --- a/aws/resource_aws_dx_gateway_association_test.go +++ b/aws/resource_aws_dx_gateway_association_test.go @@ -200,7 +200,7 @@ func TestAccAwsDxGatewayAssociation_basicVpnGatewayCrossAccount(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName), resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), - resource.TestCheckResourceAttrPair(resourceName, "vpn_gateway_id", resourceNameVgw, "id"), + resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameVgw, "id"), resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), resource.TestCheckNoResourceAttr(resourceName, "vpn_gateway_id"), resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "virtualPrivateGateway"), @@ -263,6 +263,7 @@ func TestAccAwsDxGatewayAssociation_basicTransitGatewaySingleAccount(t *testing. } func TestAccAwsDxGatewayAssociation_basicTransitGatewayCrossAccount(t *testing.T) { + var providers []*schema.Provider resourceName := "aws_dx_gateway_association.test" resourceNameDxGw := "aws_dx_gateway.test" resourceNameTgw := "aws_ec2_transit_gateway.test" @@ -270,9 +271,12 @@ func TestAccAwsDxGatewayAssociation_basicTransitGatewayCrossAccount(t *testing.T rBgpAsn := randIntRange(64512, 65534) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAwsDxGatewayAssociationDestroy, + PreCheck: func() { + testAccPreCheck(t) + testAccAlternateAccountPreCheck(t) + }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckAwsDxGatewayAssociationDestroy, Steps: []resource.TestStep{ { Config: testAccDxGatewayAssociationConfig_basicTransitGatewayCrossAccount(rName, rBgpAsn), @@ -382,7 +386,7 @@ func TestAccAwsDxGatewayAssociation_allowedPrefixesVpnGatewayCrossAccount(t *tes Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName), resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), - resource.TestCheckResourceAttrPair(resourceName, "vpn_gateway_id", resourceNameVgw, "id"), + resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameVgw, "id"), resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "1"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.1642241106", "10.255.255.8/29"), @@ -395,7 +399,7 @@ func TestAccAwsDxGatewayAssociation_allowedPrefixesVpnGatewayCrossAccount(t *tes Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationExists(resourceName), resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), - resource.TestCheckResourceAttrPair(resourceName, "vpn_gateway_id", resourceNameVgw, "id"), + resource.TestCheckResourceAttrPair(resourceName, "associated_gateway_id", resourceNameVgw, "id"), resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "2"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.2173830893", "10.255.255.0/30"), From 2605482f6ec72bca21e5be922827fbcf8e6149f2 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 17 May 2019 10:18:48 -0400 Subject: [PATCH 05/13] Address review comments. --- ...rce_aws_dx_gateway_association_proposal.go | 31 +++++++++---------- ...ws_dx_gateway_association_proposal_test.go | 8 ++--- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/aws/resource_aws_dx_gateway_association_proposal.go b/aws/resource_aws_dx_gateway_association_proposal.go index b2a32d9d71b..49f2522a8c1 100644 --- a/aws/resource_aws_dx_gateway_association_proposal.go +++ b/aws/resource_aws_dx_gateway_association_proposal.go @@ -1,7 +1,6 @@ package aws import ( - "errors" "fmt" "log" @@ -32,11 +31,7 @@ func resourceAwsDxGatewayAssociationProposal() *schema.Resource { return false } - if proposal != nil && aws.StringValue(proposal.ProposalState) == directconnect.GatewayAssociationProposalStateRequested { - return true - } - - return false + return proposal != nil && aws.StringValue(proposal.ProposalState) == directconnect.GatewayAssociationProposalStateRequested }), ), @@ -86,23 +81,25 @@ func resourceAwsDxGatewayAssociationProposal() *schema.Resource { func resourceAwsDxGatewayAssociationProposalCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).dxconn - gwIdRaw, gwIdOk := d.GetOk("associated_gateway_id") - vgwIdRaw, vgwIdOk := d.GetOk("vpn_gateway_id") - if !gwIdOk && !vgwIdOk { - return errors.New("one of associated_gateway_id or vpn_gateway_id must be configured") - } - + allowedPrefixes := expandDirectConnectGatewayAssociationProposalAllowedPrefixes(d.Get("allowed_prefixes").(*schema.Set).List()) input := &directconnect.CreateDirectConnectGatewayAssociationProposalInput{ - AddAllowedPrefixesToDirectConnectGateway: expandDirectConnectGatewayAssociationProposalAllowedPrefixes(d.Get("allowed_prefixes").(*schema.Set).List()), + AddAllowedPrefixesToDirectConnectGateway: allowedPrefixes, DirectConnectGatewayId: aws.String(d.Get("dx_gateway_id").(string)), DirectConnectGatewayOwnerAccount: aws.String(d.Get("dx_gateway_owner_account_id").(string)), } - if gwIdOk { - input.GatewayId = aws.String(gwIdRaw.(string)) - } else { - input.GatewayId = aws.String(vgwIdRaw.(string)) + var gwID string + if v, ok := d.GetOk("vpn_gateway_id"); ok { + gwID = v.(string) + } else if v, ok := d.GetOk("associated_gateway_id"); ok { + gwID = v.(string) + } + + if gwID == "" { + return fmt.Errorf("gateway id not provided, one of associated_gateway_id or vpn_gateway_id must be configured") } + input.GatewayId = aws.String(gwID) + log.Printf("[DEBUG] Creating Direct Connect Gateway Association Proposal: %s", input) output, err := conn.CreateDirectConnectGatewayAssociationProposal(input) diff --git a/aws/resource_aws_dx_gateway_association_proposal_test.go b/aws/resource_aws_dx_gateway_association_proposal_test.go index a6de072f86b..6c77d874e56 100644 --- a/aws/resource_aws_dx_gateway_association_proposal_test.go +++ b/aws/resource_aws_dx_gateway_association_proposal_test.go @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/terraform/terraform" ) -func TestAccAwsDxGatewayAssociationProposal_deprecated(t *testing.T) { +func TestAccAwsDxGatewayAssociationProposal_VpnGatewayId(t *testing.T) { var proposal1 directconnect.GatewayAssociationProposal var providers []*schema.Provider rBgpAsn := randIntRange(64512, 65534) @@ -30,7 +30,7 @@ func TestAccAwsDxGatewayAssociationProposal_deprecated(t *testing.T) { CheckDestroy: testAccCheckAwsDxGatewayAssociationProposalDestroy, Steps: []resource.TestStep{ { - Config: testAccDxGatewayAssociationProposalConfig_deprecated(rName, rBgpAsn), + Config: testAccDxGatewayAssociationProposalConfig_vpnGatewayId(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxGatewayAssociationProposalExists(resourceName, &proposal1), resource.TestCheckResourceAttrPair(resourceName, "dx_gateway_id", resourceNameDxGw, "id"), @@ -152,7 +152,7 @@ func TestAccAwsDxGatewayAssociationProposal_disappears(t *testing.T) { }) } -func TestAccAwsDxGatewayAssociationProposal_allowedPrefixes(t *testing.T) { +func TestAccAwsDxGatewayAssociationProposal_AllowedPrefixes(t *testing.T) { var proposal1, proposal2 directconnect.GatewayAssociationProposal var providers []*schema.Provider rBgpAsn := randIntRange(64512, 65534) @@ -292,7 +292,7 @@ resource "aws_vpn_gateway" "test" { `, rName, rBgpAsn) } -func testAccDxGatewayAssociationProposalConfig_deprecated(rName string, rBgpAsn int) string { +func testAccDxGatewayAssociationProposalConfig_vpnGatewayId(rName string, rBgpAsn int) string { return testAccDxGatewayAssociationProposalConfigBase_vpnGateway(rName, rBgpAsn) + fmt.Sprintf(` resource "aws_dx_gateway_association_proposal" "test" { dx_gateway_id = "${aws_dx_gateway.test.id}" From a587d7407882fef34da8f969f52a4b4136b05b80 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 23 May 2019 13:33:37 -0400 Subject: [PATCH 06/13] Update aws/resource_aws_dx_gateway_association.go Review comment. Co-Authored-By: Wilken Rivera --- aws/resource_aws_dx_gateway_association.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_dx_gateway_association.go b/aws/resource_aws_dx_gateway_association.go index 049389545b1..d4a032e4e69 100644 --- a/aws/resource_aws_dx_gateway_association.go +++ b/aws/resource_aws_dx_gateway_association.go @@ -117,7 +117,7 @@ func resourceAwsDxGatewayAssociationCreate(d *schema.ResourceData, meta interfac if gwAcctIdOk || proposalIdOk { // Cross-account association. if !(gwAcctIdOk && proposalIdOk) { - return errors.New("associated_gateway_owner_account_id and proposal_id must be configured") + return fmt.Errorf("associated_gateway_owner_account_id and proposal_id must be configured") } } else if !(gwIdOk || vgwIdOk) { return errors.New("either associated_gateway_owner_account_id and proposal_id or one of associated_gateway_id or vpn_gateway_id must be configured") From f7b19f8a8c077f30456f9a4b558e02a7b0a61bf0 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 23 May 2019 13:33:57 -0400 Subject: [PATCH 07/13] Update aws/resource_aws_dx_gateway_association.go Review comment. Co-Authored-By: Wilken Rivera --- aws/resource_aws_dx_gateway_association.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_dx_gateway_association.go b/aws/resource_aws_dx_gateway_association.go index d4a032e4e69..ba14823d0d3 100644 --- a/aws/resource_aws_dx_gateway_association.go +++ b/aws/resource_aws_dx_gateway_association.go @@ -120,7 +120,7 @@ func resourceAwsDxGatewayAssociationCreate(d *schema.ResourceData, meta interfac return fmt.Errorf("associated_gateway_owner_account_id and proposal_id must be configured") } } else if !(gwIdOk || vgwIdOk) { - return errors.New("either associated_gateway_owner_account_id and proposal_id or one of associated_gateway_id or vpn_gateway_id must be configured") + return fmt.Errorf("either associated_gateway_owner_account_id and proposal_id or one of associated_gateway_id or vpn_gateway_id must be configured") } associationId := "" From 708a9e23d98d528fa171f571a8f00bd5c188294b Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 23 May 2019 13:44:03 -0400 Subject: [PATCH 08/13] Remove EC2 Transit Gateway attachment logic. --- aws/resource_aws_dx_gateway_association.go | 81 ------------------- ...esource_aws_dx_gateway_association_test.go | 6 -- .../r/dx_gateway_association.html.markdown | 1 - 3 files changed, 88 deletions(-) diff --git a/aws/resource_aws_dx_gateway_association.go b/aws/resource_aws_dx_gateway_association.go index ba14823d0d3..88909978f0d 100644 --- a/aws/resource_aws_dx_gateway_association.go +++ b/aws/resource_aws_dx_gateway_association.go @@ -9,15 +9,12 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/directconnect" - "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" ) const ( gatewayAssociationStateDeleted = "deleted" - - transitGatewayAttachmentResourceTypeDirectConnectGateway = "direct-connect-gateway" ) func resourceAwsDxGatewayAssociation() *schema.Resource { @@ -83,11 +80,6 @@ func resourceAwsDxGatewayAssociation() *schema.Resource { ConflictsWith: []string{"associated_gateway_id", "vpn_gateway_id"}, }, - "transit_gateway_attachment_id": { - Type: schema.TypeString, - Computed: true, - }, - "vpn_gateway_id": { Type: schema.TypeString, Optional: true, @@ -206,34 +198,6 @@ func resourceAwsDxGatewayAssociationRead(d *schema.ResourceData, meta interface{ d.Set("dx_gateway_id", assoc.DirectConnectGatewayId) d.Set("dx_gateway_owner_account_id", assoc.DirectConnectGatewayOwnerAccount) - tgwAttachmentId := "" - if aws.StringValue(assoc.AssociatedGateway.Type) == directconnect.GatewayTypeTransitGateway { - ec2conn := meta.(*AWSClient).ec2conn - - req := &ec2.DescribeTransitGatewayAttachmentsInput{ - Filters: buildEC2AttributeFilterList(map[string]string{ - "resource-id": aws.StringValue(assoc.DirectConnectGatewayId), - "resource-type": transitGatewayAttachmentResourceTypeDirectConnectGateway, - "transit-gateway-id": aws.StringValue(assoc.AssociatedGateway.Id), - }), - } - - log.Printf("[DEBUG] Finding Direct Connect gateway association transit gateway attachment: %#v", req) - resp, err := ec2conn.DescribeTransitGatewayAttachments(req) - if err != nil { - return fmt.Errorf("error finding Direct Connect gateway association (%s) transit gateway attachment: %s", d.Id(), err) - } - if resp == nil || len(resp.TransitGatewayAttachments) == 0 || resp.TransitGatewayAttachments[0] == nil { - return fmt.Errorf("error finding Direct Connect gateway association (%s) transit gateway attachment: empty response", d.Id()) - } - if len(resp.TransitGatewayAttachments) > 1 { - return fmt.Errorf("error reading Direct Connect gateway association (%s) transit gateway attachment: multiple responses", d.Id()) - } - - tgwAttachmentId = aws.StringValue(resp.TransitGatewayAttachments[0].TransitGatewayAttachmentId) - } - d.Set("transit_gateway_attachment_id", tgwAttachmentId) - return nil } @@ -295,13 +259,6 @@ func resourceAwsDxGatewayAssociationDelete(d *schema.ResourceData, meta interfac return fmt.Errorf("error waiting for Direct Connect gateway association (%s) to be deleted: %s", d.Id(), err) } - if tgwAttachmentId := d.Get("transit_gateway_attachment_id").(string); tgwAttachmentId != "" { - ec2conn := meta.(*AWSClient).ec2conn - - if err := waitForEc2TransitGatewayAttachmentDeletion(ec2conn, tgwAttachmentId, d.Timeout(schema.TimeoutDelete)); err != nil { - return fmt.Errorf("error waiting for Direct Connect gateway association (%s) transit gateway attachment (%s) to be deleted: %s", d.Id(), tgwAttachmentId, err) - } - } return nil } @@ -368,29 +325,6 @@ func dxGatewayAssociationStateRefresh(conn *directconnect.DirectConnect, associa } } -func ec2TransitGatewayAttachmentStateRefresh(conn *ec2.EC2, attachmentId string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - resp, err := conn.DescribeTransitGatewayAttachments(&ec2.DescribeTransitGatewayAttachmentsInput{ - TransitGatewayAttachmentIds: []*string{aws.String(attachmentId)}, - }) - if isAWSErr(err, "InvalidTransitGatewayAttachmentID.NotFound", "") { - return nil, ec2.TransitGatewayAttachmentStateDeleted, nil - } - if err != nil { - return nil, "", err - } - - if resp == nil || len(resp.TransitGatewayAttachments) == 0 || resp.TransitGatewayAttachments[0] == nil { - return nil, ec2.TransitGatewayAttachmentStateDeleted, nil - } - if len(resp.TransitGatewayAttachments) > 1 { - return nil, "", errors.New("error reading EC2 Transit Gateway Attachment: multiple results found, try adjusting search criteria") - } - - return resp.TransitGatewayAttachments[0], aws.StringValue(resp.TransitGatewayAttachments[0].State), nil - } -} - // Terraform resource ID. func dxGatewayAssociationId(dxgwId, gwId string) string { return fmt.Sprintf("ga-%s%s", dxgwId, gwId) @@ -440,18 +374,3 @@ func waitForDirectConnectGatewayAssociationDeletion(conn *directconnect.DirectCo return err } - -func waitForEc2TransitGatewayAttachmentDeletion(conn *ec2.EC2, attachmentId string, timeout time.Duration) error { - stateConf := &resource.StateChangeConf{ - Pending: []string{ec2.TransitGatewayAttachmentStateAvailable, ec2.TransitGatewayAttachmentStateDeleting}, - Target: []string{ec2.TransitGatewayAttachmentStateDeleted}, - Refresh: ec2TransitGatewayAttachmentStateRefresh(conn, attachmentId), - Timeout: timeout, - Delay: 10 * time.Second, - MinTimeout: 5 * time.Second, - } - - _, err := stateConf.WaitForState() - - return err -} diff --git a/aws/resource_aws_dx_gateway_association_test.go b/aws/resource_aws_dx_gateway_association_test.go index 6eeb74054ef..5705530f331 100644 --- a/aws/resource_aws_dx_gateway_association_test.go +++ b/aws/resource_aws_dx_gateway_association_test.go @@ -3,7 +3,6 @@ package aws import ( "fmt" "log" - "regexp" "testing" "time" @@ -123,7 +122,6 @@ func TestAccAwsDxGatewayAssociation_deprecatedSingleAccount(t *testing.T) { resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), resource.TestCheckNoResourceAttr(resourceName, "associated_gateway_id"), resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "virtualPrivateGateway"), - resource.TestCheckResourceAttr(resourceName, "transit_gateway_attachment_id", ""), testAccCheckResourceAttrAccountID(resourceName, "associated_gateway_owner_account_id"), testAccCheckResourceAttrAccountID(resourceName, "dx_gateway_owner_account_id"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "1"), @@ -155,7 +153,6 @@ func TestAccAwsDxGatewayAssociation_basicVpnGatewaySingleAccount(t *testing.T) { resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), resource.TestCheckNoResourceAttr(resourceName, "vpn_gateway_id"), resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "virtualPrivateGateway"), - resource.TestCheckResourceAttr(resourceName, "transit_gateway_attachment_id", ""), testAccCheckResourceAttrAccountID(resourceName, "associated_gateway_owner_account_id"), testAccCheckResourceAttrAccountID(resourceName, "dx_gateway_owner_account_id"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "1"), @@ -204,7 +201,6 @@ func TestAccAwsDxGatewayAssociation_basicVpnGatewayCrossAccount(t *testing.T) { resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), resource.TestCheckNoResourceAttr(resourceName, "vpn_gateway_id"), resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "virtualPrivateGateway"), - resource.TestCheckResourceAttr(resourceName, "transit_gateway_attachment_id", ""), testAccCheckResourceAttrAccountID(resourceName, "associated_gateway_owner_account_id"), // dx_gateway_owner_account_id is the "aws.alternate" provider's account ID. // testAccCheckResourceAttrAccountID(resourceName, "dx_gateway_owner_account_id"), @@ -237,7 +233,6 @@ func TestAccAwsDxGatewayAssociation_basicTransitGatewaySingleAccount(t *testing. resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), resource.TestCheckNoResourceAttr(resourceName, "vpn_gateway_id"), resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "transitGateway"), - resource.TestMatchResourceAttr(resourceName, "transit_gateway_attachment_id", regexp.MustCompile(`^tgw-attach-.+`)), testAccCheckResourceAttrAccountID(resourceName, "associated_gateway_owner_account_id"), testAccCheckResourceAttrAccountID(resourceName, "dx_gateway_owner_account_id"), resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "2"), @@ -287,7 +282,6 @@ func TestAccAwsDxGatewayAssociation_basicTransitGatewayCrossAccount(t *testing.T resource.TestCheckResourceAttrSet(resourceName, "dx_gateway_association_id"), resource.TestCheckNoResourceAttr(resourceName, "vpn_gateway_id"), resource.TestCheckResourceAttr(resourceName, "associated_gateway_type", "transitGateway"), - resource.TestMatchResourceAttr(resourceName, "transit_gateway_attachment_id", regexp.MustCompile(`^tgw-attach-.+`)), testAccCheckResourceAttrAccountID(resourceName, "associated_gateway_owner_account_id"), // dx_gateway_owner_account_id is the "aws.alternate" provider's account ID. // testAccCheckResourceAttrAccountID(resourceName, "dx_gateway_owner_account_id"), diff --git a/website/docs/r/dx_gateway_association.html.markdown b/website/docs/r/dx_gateway_association.html.markdown index 7905f37b662..533c213d664 100644 --- a/website/docs/r/dx_gateway_association.html.markdown +++ b/website/docs/r/dx_gateway_association.html.markdown @@ -158,7 +158,6 @@ In addition to all arguments above, the following attributes are exported: * `associated_gateway_type` - The type of the associated gateway, `transitGateway` or `virtualPrivateGateway`. * `dx_gateway_association_id` - The ID of the Direct Connect gateway association. * `dx_gateway_owner_account_id` - The ID of the AWS account that owns the Direct Connect gateway. -* `transit_gateway_attachment_id` - The ID of the transit gateway attachment. ## Timeouts From 23529da33c1a620855c93b2a4a913f5ff06c6cdc Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 23 May 2019 13:45:23 -0400 Subject: [PATCH 09/13] Update website/docs/r/dx_gateway_association.html.markdown Review comment. Co-Authored-By: Wilken Rivera --- website/docs/r/dx_gateway_association.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/dx_gateway_association.html.markdown b/website/docs/r/dx_gateway_association.html.markdown index 533c213d664..90f597dca47 100644 --- a/website/docs/r/dx_gateway_association.html.markdown +++ b/website/docs/r/dx_gateway_association.html.markdown @@ -16,7 +16,7 @@ by creating an `aws_dx_gateway_association` resource with the `proposal_id` and ## Example Usage -### VGW Single Account +### VPN Gateway Association ```hcl resource "aws_dx_gateway" "example" { From bf2bb30f602209aac66b9a039767e091b577319f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 23 May 2019 13:45:37 -0400 Subject: [PATCH 10/13] Update website/docs/r/dx_gateway_association.html.markdown Review comment. Co-Authored-By: Wilken Rivera --- website/docs/r/dx_gateway_association.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/dx_gateway_association.html.markdown b/website/docs/r/dx_gateway_association.html.markdown index 90f597dca47..891f41e8e1a 100644 --- a/website/docs/r/dx_gateway_association.html.markdown +++ b/website/docs/r/dx_gateway_association.html.markdown @@ -65,7 +65,7 @@ resource "aws_dx_gateway_association" "example" { } ``` -### Transit Gateway Single Account +### Transit Gateway Association ```hcl resource "aws_dx_gateway" "example" { From 97c9900baa1f205788f7554f2ecbd9428020c5f1 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 23 May 2019 13:48:26 -0400 Subject: [PATCH 11/13] Documentation review changes. --- .../r/dx_gateway_association.html.markdown | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/website/docs/r/dx_gateway_association.html.markdown b/website/docs/r/dx_gateway_association.html.markdown index 891f41e8e1a..acea7d39394 100644 --- a/website/docs/r/dx_gateway_association.html.markdown +++ b/website/docs/r/dx_gateway_association.html.markdown @@ -38,7 +38,7 @@ resource "aws_dx_gateway_association" "example" { } ``` -### Single Account VGW With Allowed Prefixes +### Transit Gateway Association ```hcl resource "aws_dx_gateway" "example" { @@ -46,26 +46,20 @@ resource "aws_dx_gateway" "example" { amazon_side_asn = "64512" } -resource "aws_vpc" "example" { - cidr_block = "10.255.255.0/28" -} - -resource "aws_vpn_gateway" "example" { - vpc_id = "${aws_vpc.example.id}" -} +resource "aws_ec2_transit_gateway" "example" {} resource "aws_dx_gateway_association" "example" { dx_gateway_id = "${aws_dx_gateway.example.id}" - associated_gateway_id = "${aws_vpn_gateway.example.id}" + associated_gateway_id = "${aws_ec2_transit_gateway.example.id}" allowed_prefixes = [ - "210.52.109.0/24", - "175.45.176.0/22", + "10.255.255.0/30", + "10.255.255.8/30", ] } ``` -### Transit Gateway Association +### Allowed Prefixes ```hcl resource "aws_dx_gateway" "example" { @@ -73,15 +67,21 @@ resource "aws_dx_gateway" "example" { amazon_side_asn = "64512" } -resource "aws_ec2_transit_gateway" "example" {} +resource "aws_vpc" "example" { + cidr_block = "10.255.255.0/28" +} + +resource "aws_vpn_gateway" "example" { + vpc_id = "${aws_vpc.example.id}" +} resource "aws_dx_gateway_association" "example" { dx_gateway_id = "${aws_dx_gateway.example.id}" - associated_gateway_id = "${aws_ec2_transit_gateway.example.id}" + associated_gateway_id = "${aws_vpn_gateway.example.id}" allowed_prefixes = [ - "10.255.255.0/30", - "10.255.255.8/30", + "210.52.109.0/24", + "175.45.176.0/22", ] } ``` From 473e2060357edacb95cfcc9776e26f6623db4952 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 23 May 2019 14:01:55 -0400 Subject: [PATCH 12/13] New cross-account Direct Connect gateway association example. --- .../README.md | 18 ++++++ .../main.tf | 64 +++++++++++++++++++ .../terraform.template.tfvars | 9 +++ .../variables.tf | 9 +++ .../r/dx_gateway_association.html.markdown | 47 +------------- ...gateway_association_proposal.html.markdown | 29 +-------- 6 files changed, 103 insertions(+), 73 deletions(-) create mode 100644 examples/dx-gateway-cross-account-vgw-association/README.md create mode 100644 examples/dx-gateway-cross-account-vgw-association/main.tf create mode 100644 examples/dx-gateway-cross-account-vgw-association/terraform.template.tfvars create mode 100644 examples/dx-gateway-cross-account-vgw-association/variables.tf diff --git a/examples/dx-gateway-cross-account-vgw-association/README.md b/examples/dx-gateway-cross-account-vgw-association/README.md new file mode 100644 index 00000000000..25693883e58 --- /dev/null +++ b/examples/dx-gateway-cross-account-vgw-association/README.md @@ -0,0 +1,18 @@ +# Direct Connect Gateway Cross-Account VGW Association + +This example demonstrates how to create a VPN Gateway in one AWS account, create a Direct Connect Gateway in a second AWS account, and associate the VPN Gateway with the Direct Connect Gateway via the `aws_dx_gateway_association_proposal` and `aws_dx_gateway_association` resources. + +See [more in the Direct Connect Gateway documentation](https://docs.aws.amazon.com/directconnect/latest/UserGuide/direct-connect-gateways.html). + +## Running this example + +Either `cp terraform.template.tfvars terraform.tfvars` and modify that new file accordingly or provide variables via CLI: + +``` +terraform apply \ + -var="aws_first_access_key=AAAAAAAAAAAAAAAAAAA" \ + -var="aws_first_secret_key=SuperSecretKeyForAccount1" \ + -var="aws_second_access_key=BBBBBBBBBBBBBBBBBBB" \ + -var="aws_second_secret_key=SuperSecretKeyForAccount2" \ + -var="aws_region=us-east-1" +``` diff --git a/examples/dx-gateway-cross-account-vgw-association/main.tf b/examples/dx-gateway-cross-account-vgw-association/main.tf new file mode 100644 index 00000000000..7aa0e306f28 --- /dev/null +++ b/examples/dx-gateway-cross-account-vgw-association/main.tf @@ -0,0 +1,64 @@ +// First account owns the VGW. +provider "aws" { + alias = "first" + + region = "${var.aws_region}" + access_key = "${var.aws_first_access_key}" + secret_key = "${var.aws_first_secret_key}" +} + +// Second account owns the DXGW. +provider "aws" { + alias = "second" + + region = "${var.aws_region}" + access_key = "${var.aws_second_access_key}" + secret_key = "${var.aws_second_secret_key}" +} + +data "aws_caller_identity" "first" {} + +resource "aws_vpc" "example" { + provider = "aws.first" + + cidr_block = "10.255.255.0/28" + + tags = { + Name = "terraform-example" + } +} + +resource "aws_vpn_gateway" "example" { + provider = "aws.first" + + vpc_id = "${aws_vpc.example.id}" + + tags = { + Name = "terraform-example" + } +} + +// Create the association proposal in the first account... +resource "aws_dx_gateway_association_proposal" "example" { + provider = "aws.first" + + dx_gateway_id = "${aws_dx_gateway.example.id}" + dx_gateway_owner_account_id = "${aws_dx_gateway.example.owner_account_id}" + associated_gateway_id = "${aws_vpn_gateway.example.id}" +} + +// ...and accept it in the second account, creating the association. +resource "aws_dx_gateway_association" "example" { + provider = "aws.second" + + proposal_id = "${aws_dx_gateway_association_proposal.example.id}" + dx_gateway_id = "${aws_dx_gateway.example.id}" + associated_gateway_owner_account_id = "${data.aws_caller_identity.first.account_id}" +} + +resource "aws_dx_gateway" "example" { + provider = "aws.second" + + name = "terraform-example" + amazon_side_asn = "64512" +} diff --git a/examples/dx-gateway-cross-account-vgw-association/terraform.template.tfvars b/examples/dx-gateway-cross-account-vgw-association/terraform.template.tfvars new file mode 100644 index 00000000000..813b24302fe --- /dev/null +++ b/examples/dx-gateway-cross-account-vgw-association/terraform.template.tfvars @@ -0,0 +1,9 @@ +# First account +aws_first_access_key = "AAAAAAAAAAAAAAAAAAA" +aws_first_secret_key = "SuperSecretKeyForAccount1" + +# Second account +aws_second_access_key = "BBBBBBBBBBBBBBBBBBB" +aws_second_secret_key = "SuperSecretKeyForAccount2" + +aws_region = "us-east-1" diff --git a/examples/dx-gateway-cross-account-vgw-association/variables.tf b/examples/dx-gateway-cross-account-vgw-association/variables.tf new file mode 100644 index 00000000000..ed1a71f53b1 --- /dev/null +++ b/examples/dx-gateway-cross-account-vgw-association/variables.tf @@ -0,0 +1,9 @@ +variable "aws_first_access_key" {} + +variable "aws_first_secret_key" {} + +variable "aws_second_access_key" {} + +variable "aws_second_secret_key" {} + +variable "aws_region" {} diff --git a/website/docs/r/dx_gateway_association.html.markdown b/website/docs/r/dx_gateway_association.html.markdown index acea7d39394..7a94894a56b 100644 --- a/website/docs/r/dx_gateway_association.html.markdown +++ b/website/docs/r/dx_gateway_association.html.markdown @@ -86,52 +86,7 @@ resource "aws_dx_gateway_association" "example" { } ``` -### VGW Cross-Account - -```hcl -provider "aws" { - # Creator's credentials. -} - -provider "aws" { - alias = "accepter" - - # Accepter's credentials. -} - -# Creator's side of the proposal. -data "aws_caller_identity" "creator" {} - -resource "aws_vpc" "example" { - cidr_block = "10.255.255.0/28" -} - -resource "aws_vpn_gateway" "example" { - vpc_id = "${aws_vpc.example.id}" -} - -resource "aws_dx_gateway_association_proposal" "example" { - dx_gateway_id = "${aws_dx_gateway.example.id}" - dx_gateway_owner_account_id = "${aws_dx_gateway.example.owner_account_id}" - associated_gateway_id = "${aws_vpn_gateway.example.id}" -} - -# Accepter's side of the proposal. -resource "aws_dx_gateway" "example" { - provider = "aws.accepter" - - name = "example" - amazon_side_asn = "64512" -} - -resource "aws_dx_gateway_association" "example" { - provider = "aws.accepter" - - proposal_id = "${aws_dx_gateway_association_proposal.example.id}" - dx_gateway_id = "${aws_dx_gateway.example.id}" - associated_gateway_owner_account_id = "${data.aws_caller_identity.creator.account_id}" -} -``` +A full example of how to to create a VPN Gateway in one AWS account, create a Direct Connect Gateway in a second AWS account, and associate the VPN Gateway with the Direct Connect Gateway via the `aws_dx_gateway_association_proposal` and `aws_dx_gateway_association` resources can be found in [the `./examples/dx-gateway-cross-account-vgw-association` directory within the Github Repository](https://github.com/terraform-providers/terraform-provider-aws/tree/master/examples/dx-gateway-cross-account-vgw-association). ## Argument Reference diff --git a/website/docs/r/dx_gateway_association_proposal.html.markdown b/website/docs/r/dx_gateway_association_proposal.html.markdown index e401a076ad7..7366898f347 100644 --- a/website/docs/r/dx_gateway_association_proposal.html.markdown +++ b/website/docs/r/dx_gateway_association_proposal.html.markdown @@ -13,40 +13,15 @@ Manages a Direct Connect Gateway Association Proposal, typically for enabling cr ## Example Usage ```hcl -provider "aws" { - # Creator's credentials. -} - -provider "aws" { - alias = "accepter" - - # Accepter's credentials. -} - -# Creator's side of the proposal. -resource "aws_vpc" "example" { - cidr_block = "10.255.255.0/28" -} - -resource "aws_vpn_gateway" "example" { - vpc_id = "${aws_vpc.example.id}" -} - resource "aws_dx_gateway_association_proposal" "example" { dx_gateway_id = "${aws_dx_gateway.example.id}" dx_gateway_owner_account_id = "${aws_dx_gateway.example.owner_account_id}" associated_gateway_id = "${aws_vpn_gateway.example.id}" } - -# Accepter's side of the proposal. -resource "aws_dx_gateway" "example" { - provider = "aws.accepter" - - name = "example" - amazon_side_asn = "64512" -} ``` +A full example of how to to create a VPN Gateway in one AWS account, create a Direct Connect Gateway in a second AWS account, and associate the VPN Gateway with the Direct Connect Gateway via the `aws_dx_gateway_association_proposal` and `aws_dx_gateway_association` resources can be found in [the `./examples/dx-gateway-cross-account-vgw-association` directory within the Github Repository](https://github.com/terraform-providers/terraform-provider-aws/tree/master/examples/dx-gateway-cross-account-vgw-association). + ## Argument Reference ~> **NOTE:** One of `associated_gateway_id`, or `vpn_gateway_id` must be specified. From 93e2df20a3ca7df137fc0a4f66c33a29c80f23e5 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 23 May 2019 15:36:41 -0400 Subject: [PATCH 13/13] Fix minor documentation typo. --- website/docs/r/dx_gateway_association.html.markdown | 2 +- website/docs/r/dx_gateway_association_proposal.html.markdown | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/r/dx_gateway_association.html.markdown b/website/docs/r/dx_gateway_association.html.markdown index 7a94894a56b..4d84631f59a 100644 --- a/website/docs/r/dx_gateway_association.html.markdown +++ b/website/docs/r/dx_gateway_association.html.markdown @@ -86,7 +86,7 @@ resource "aws_dx_gateway_association" "example" { } ``` -A full example of how to to create a VPN Gateway in one AWS account, create a Direct Connect Gateway in a second AWS account, and associate the VPN Gateway with the Direct Connect Gateway via the `aws_dx_gateway_association_proposal` and `aws_dx_gateway_association` resources can be found in [the `./examples/dx-gateway-cross-account-vgw-association` directory within the Github Repository](https://github.com/terraform-providers/terraform-provider-aws/tree/master/examples/dx-gateway-cross-account-vgw-association). +A full example of how to create a VPN Gateway in one AWS account, create a Direct Connect Gateway in a second AWS account, and associate the VPN Gateway with the Direct Connect Gateway via the `aws_dx_gateway_association_proposal` and `aws_dx_gateway_association` resources can be found in [the `./examples/dx-gateway-cross-account-vgw-association` directory within the Github Repository](https://github.com/terraform-providers/terraform-provider-aws/tree/master/examples/dx-gateway-cross-account-vgw-association). ## Argument Reference diff --git a/website/docs/r/dx_gateway_association_proposal.html.markdown b/website/docs/r/dx_gateway_association_proposal.html.markdown index 7366898f347..725d25d9205 100644 --- a/website/docs/r/dx_gateway_association_proposal.html.markdown +++ b/website/docs/r/dx_gateway_association_proposal.html.markdown @@ -20,7 +20,7 @@ resource "aws_dx_gateway_association_proposal" "example" { } ``` -A full example of how to to create a VPN Gateway in one AWS account, create a Direct Connect Gateway in a second AWS account, and associate the VPN Gateway with the Direct Connect Gateway via the `aws_dx_gateway_association_proposal` and `aws_dx_gateway_association` resources can be found in [the `./examples/dx-gateway-cross-account-vgw-association` directory within the Github Repository](https://github.com/terraform-providers/terraform-provider-aws/tree/master/examples/dx-gateway-cross-account-vgw-association). +A full example of how to create a VPN Gateway in one AWS account, create a Direct Connect Gateway in a second AWS account, and associate the VPN Gateway with the Direct Connect Gateway via the `aws_dx_gateway_association_proposal` and `aws_dx_gateway_association` resources can be found in [the `./examples/dx-gateway-cross-account-vgw-association` directory within the Github Repository](https://github.com/terraform-providers/terraform-provider-aws/tree/master/examples/dx-gateway-cross-account-vgw-association). ## Argument Reference