diff --git a/aws/resource_aws_dx_gateway_association.go b/aws/resource_aws_dx_gateway_association.go index 36fa5f4b3e6..88909978f0d 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" @@ -34,22 +35,58 @@ func resourceAwsDxGatewayAssociation() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, }, - "dx_gateway_id": { + "associated_gateway_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + 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": { 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": { + "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"}, + }, + + "vpn_gateway_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"associated_gateway_id", "associated_gateway_owner_account_id", "proposal_id"}, + Deprecated: "use 'associated_gateway_id' argument instead", + }, }, Timeouts: &schema.ResourceTimeout{ @@ -64,31 +101,65 @@ func resourceAwsDxGatewayAssociationCreate(d *schema.ResourceData, meta interfac conn := meta.(*AWSClient).dxconn dxgwId := d.Get("dx_gateway_id").(string) - vgwId := d.Get("vpn_gateway_id").(string) - req := &directconnect.CreateDirectConnectGatewayAssociationInput{ - AddAllowedPrefixesToDirectConnectGateway: expandDxRouteFilterPrefixes(d.Get("allowed_prefixes").(*schema.Set)), - DirectConnectGatewayId: aws.String(dxgwId), - VirtualGatewayId: aws.String(vgwId), + gwIdRaw, gwIdOk := d.GetOk("associated_gateway_id") + vgwIdRaw, vgwIdOk := d.GetOk("vpn_gateway_id") + 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 fmt.Errorf("associated_gateway_owner_account_id and proposal_id must be configured") + } + } else if !(gwIdOk || vgwIdOk) { + 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") } - log.Printf("[DEBUG] Creating Direct Connect gateway association: %#v", req) - _, err := conn.CreateDirectConnectGatewayAssociation(req) - if err != nil { - return fmt.Errorf("error creating Direct Connect gateway association: %s", err) - } + 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)), + } - d.SetId(dxGatewayAssociationId(dxgwId, vgwId)) + 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) + } - 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, + // 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 { + 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) + } + + // 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)) } - _, err = stateConf.WaitForState() - if err != nil { + d.Set("dx_gateway_association_id", associationId) + + if err := waitForDirectConnectGatewayAssociationAvailabilityOnCreate(conn, associationId, d.Timeout(schema.TimeoutCreate)); err != nil { return fmt.Errorf("error waiting for Direct Connect gateway association (%s) to become available: %s", d.Id(), err) } @@ -98,11 +169,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 +181,38 @@ 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_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) + 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 +221,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 +242,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,8 +255,8 @@ 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) } return nil @@ -192,25 +265,38 @@ func resourceAwsDxGatewayAssociationDelete(d *schema.ResourceData, meta interfac 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) - d.SetId(dxGatewayAssociationId(dxgwId, vgwId)) - d.Set("dx_gateway_id", dxgwId) - d.Set("vpn_gateway_id", vgwId) + 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(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 +309,62 @@ 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 dxGatewayAssociationId(dxgwId, vgwId string) string { - return fmt.Sprintf("ga-%s%s", dxgwId, vgwId) +// 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 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, diff --git a/aws/resource_aws_dx_gateway_association_proposal.go b/aws/resource_aws_dx_gateway_association_proposal.go index 6bd3d3d3642..49f2522a8c1 100644 --- a/aws/resource_aws_dx_gateway_association_proposal.go +++ b/aws/resource_aws_dx_gateway_association_proposal.go @@ -6,6 +6,7 @@ import ( "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 +19,43 @@ 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 + } + + return proposal != nil && aws.StringValue(proposal.ProposalState) == directconnect.GatewayAssociationProposalStateRequested + }), + ), + 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 +68,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,13 +81,25 @@ func resourceAwsDxGatewayAssociationProposal() *schema.Resource { func resourceAwsDxGatewayAssociationProposalCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).dxconn + allowedPrefixes := expandDirectConnectGatewayAssociationProposalAllowedPrefixes(d.Get("allowed_prefixes").(*schema.Set).List()) 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)), - GatewayId: aws.String(d.Get("vpn_gateway_id").(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) @@ -97,9 +141,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..6c77d874e56 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_VpnGatewayId(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,93 @@ func TestAccAwsDxGatewayAssociationProposal_basic(t *testing.T) { CheckDestroy: testAccCheckAwsDxGatewayAssociationProposalDestroy, Steps: []resource.TestStep{ { - Config: testAccDxGatewayAssociationProposalConfig(rName, rBgpAsn), + Config: testAccDxGatewayAssociationProposalConfig_vpnGatewayId(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, "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"), + ), + }, + { + Config: testAccDxGatewayAssociationProposalConfig_basicVpnGateway(rName, rBgpAsn), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +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(rName, rBgpAsn), + 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, @@ -60,7 +141,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), @@ -184,7 +265,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" @@ -197,7 +278,7 @@ resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" tags = { - Name = "tf-acc-test-dx-gateway-association-proposal" + Name = %[1]q } } @@ -205,14 +286,14 @@ 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) } -func testAccDxGatewayAssociationProposalConfig(rName string, rBgpAsn int) string { - return testAccDxGatewayAssociationProposalConfigBase(rName, rBgpAsn) + fmt.Sprintf(` +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}" dx_gateway_owner_account_id = "${aws_dx_gateway.test.owner_account_id}" @@ -221,24 +302,62 @@ 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 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(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..5705530f331 100644 --- a/aws/resource_aws_dx_gateway_association_test.go +++ b/aws/resource_aws_dx_gateway_association_test.go @@ -10,6 +10,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" ) @@ -55,19 +56,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 +75,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 +101,7 @@ func testSweepDirectConnectGatewayAssociations(region string) error { return nil } -func TestAccAwsDxGatewayAssociation_basic(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" @@ -114,12 +114,47 @@ func TestAccAwsDxGatewayAssociation_basic(t *testing.T) { CheckDestroy: testAccCheckAwsDxGatewayAssociationDestroy, Steps: []resource.TestStep{ { - Config: testAccDxGatewayAssociationConfig_basic(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.TestCheckResourceAttr(resourceName, "associated_gateway_type", "virtualPrivateGateway"), + 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"), + ), + }, + }, + }) +} + +func TestAccAwsDxGatewayAssociation_basicVpnGatewaySingleAccount(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_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.TestCheckResourceAttr(resourceName, "associated_gateway_type", "virtualPrivateGateway"), + 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"), ), @@ -132,7 +167,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, @@ -141,7 +176,125 @@ func TestAccAwsDxGatewayAssociation_basic(t *testing.T) { }) } -func TestAccAwsDxGatewayAssociation_multiVgws(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, "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"), + 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" + 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_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.TestCheckResourceAttr(resourceName, "associated_gateway_type", "transitGateway"), + testAccCheckResourceAttrAccountID(resourceName, "associated_gateway_owner_account_id"), + testAccCheckResourceAttrAccountID(resourceName, "dx_gateway_owner_account_id"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "2"), + resource.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) { + 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_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" + 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_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"), + 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()) @@ -154,7 +307,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), @@ -170,7 +323,7 @@ func TestAccAwsDxGatewayAssociation_multiVgws(t *testing.T) { }) } -func TestAccAwsDxGatewayAssociation_allowedPrefixes(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" @@ -183,11 +336,11 @@ func TestAccAwsDxGatewayAssociation_allowedPrefixes(t *testing.T) { CheckDestroy: testAccCheckAwsDxGatewayAssociationDestroy, Steps: []resource.TestStep{ { - Config: testAccDxGatewayAssociationConfig_allowedPrefixes(rName, rBgpAsn), + Config: testAccDxGatewayAssociationConfig_allowedPrefixesVpnGatewaySingleAccount(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,12 +348,57 @@ func TestAccAwsDxGatewayAssociation_allowedPrefixes(t *testing.T) { ), }, { - Config: testAccDxGatewayAssociationConfig_allowedPrefixesUpdated(rName, rBgpAsn), + Config: testAccDxGatewayAssociationConfig_allowedPrefixesVpnGatewaySingleAccountUpdated(rName, rBgpAsn), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsDxGatewayAssociationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.#", "1"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.1642241106", "10.255.255.8/29"), + ), + }, + }, + }) +} + +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, "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"), ), + // 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, "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"), + resource.TestCheckResourceAttr(resourceName, "allowed_prefixes.2984398124", "10.255.255.8/30"), + ), }, }, }) @@ -214,13 +412,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 +442,42 @@ func testAccCheckAwsDxGatewayAssociationExists(name string) resource.TestCheckFu } } -func testAccDxGatewayAssociationConfig_basic(rName string, rBgpAsn int) string { +func testAccDxGatewayAssociationConfigBase_vpnGatewaySingleAccount(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 + } +} + +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}" +} +`, rName, rBgpAsn) +} + +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" + tags = { Name = %[1]q } @@ -261,26 +490,135 @@ 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}" } +# 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}" + 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}" +} +`) +} + +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_basicTransitGatewaySingleAccount(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 { +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 + 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 +631,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,84 +655,94 @@ 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 { - return fmt.Sprintf(` -resource "aws_dx_gateway" "test" { - name = %[1]q - amazon_side_asn = "%[2]d" -} +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_vpc" "test" { - cidr_block = "10.255.255.0/28" - tags = { - Name = %[1]q - } + allowed_prefixes = [ + "10.255.255.0/30", + "10.255.255.8/30", + ] } - -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}" +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.8/29", + ] +} +`) } -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_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", ] } -`, rName, rBgpAsn) -} -func testAccDxGatewayAssociationConfig_allowedPrefixesUpdated(rName string, rBgpAsn int) string { - return fmt.Sprintf(` -resource "aws_dx_gateway" "test" { - name = %[1]q - amazon_side_asn = "%[2]d" -} +# Accepter +resource "aws_dx_gateway_association" "test" { + provider = "aws.alternate" -resource "aws_vpc" "test" { - cidr_block = "10.255.255.0/28" - 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}" - vpn_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/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 2cfe37ce2e9..4d84631f59a 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 +### VPN Gateway Association ```hcl resource "aws_dx_gateway" "example" { @@ -29,8 +33,29 @@ 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}" +} +``` + +### Transit Gateway Association + +```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", + ] } ``` @@ -51,8 +76,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 +86,23 @@ resource "aws_dx_gateway_association" "example" { } ``` +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 +~> **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. -* `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. +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 @@ -74,7 +110,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. +* `dx_gateway_owner_account_id` - The ID of the AWS account that owns the Direct Connect gateway. ## Timeouts @@ -87,7 +125,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..725d25d9205 100644 --- a/website/docs/r/dx_gateway_association_proposal.html.markdown +++ b/website/docs/r/dx_gateway_association_proposal.html.markdown @@ -13,40 +13,34 @@ 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" -} - -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}" - vpn_gateway_id = "${aws_vpn_gateway.example.id}" + associated_gateway_id = "${aws_vpn_gateway.example.id}" } ``` +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 +~> **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