diff --git a/aws/data_source_aws_ec2_transit_gateway_peering_attachment.go b/aws/data_source_aws_ec2_transit_gateway_peering_attachment.go new file mode 100644 index 00000000000..b7d147b1231 --- /dev/null +++ b/aws/data_source_aws_ec2_transit_gateway_peering_attachment.go @@ -0,0 +1,104 @@ +package aws + +import ( + "errors" + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" +) + +func dataSourceAwsEc2TransitGatewayPeeringAttachment() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsEc2TransitGatewayPeeringAttachmentRead, + + Schema: map[string]*schema.Schema{ + "filter": ec2CustomFiltersSchema(), + "id": { + Type: schema.TypeString, + Optional: true, + }, + "peer_account_id": { + Type: schema.TypeString, + Computed: true, + }, + "peer_region": { + Type: schema.TypeString, + Computed: true, + }, + "peer_transit_gateway_id": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tagsSchemaComputed(), + "transit_gateway_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceAwsEc2TransitGatewayPeeringAttachmentRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + input := &ec2.DescribeTransitGatewayPeeringAttachmentsInput{} + + if v, ok := d.GetOk("id"); ok { + input.TransitGatewayAttachmentIds = aws.StringSlice([]string{v.(string)}) + } + + input.Filters = buildEC2CustomFilterList(d.Get("filter").(*schema.Set)) + if v := d.Get("tags").(map[string]interface{}); len(v) > 0 { + input.Filters = append(input.Filters, ec2TagFiltersFromMap(v)...) + } + if len(input.Filters) == 0 { + // Don't send an empty filters list; the EC2 API won't accept it. + input.Filters = nil + } + + log.Printf("[DEBUG] Reading EC2 Transit Gateway Peering Attachments: %s", input) + output, err := conn.DescribeTransitGatewayPeeringAttachments(input) + + if err != nil { + return fmt.Errorf("error reading EC2 Transit Gateway Peering Attachments: %s", err) + } + + if output == nil || len(output.TransitGatewayPeeringAttachments) == 0 { + return errors.New("error reading EC2 Transit Gateway Peering Attachment: no results found") + } + + if len(output.TransitGatewayPeeringAttachments) > 1 { + return errors.New("error reading EC2 Transit Gateway Peering Attachment: multiple results found, try adjusting search criteria") + } + + transitGatewayPeeringAttachment := output.TransitGatewayPeeringAttachments[0] + + if transitGatewayPeeringAttachment == nil { + return errors.New("error reading EC2 Transit Gateway Peering Attachment: empty result") + } + + local := transitGatewayPeeringAttachment.RequesterTgwInfo + peer := transitGatewayPeeringAttachment.AccepterTgwInfo + + if aws.StringValue(transitGatewayPeeringAttachment.AccepterTgwInfo.OwnerId) == meta.(*AWSClient).accountid && aws.StringValue(transitGatewayPeeringAttachment.AccepterTgwInfo.Region) == meta.(*AWSClient).region { + local = transitGatewayPeeringAttachment.AccepterTgwInfo + peer = transitGatewayPeeringAttachment.RequesterTgwInfo + } + + d.Set("peer_account_id", peer.OwnerId) + d.Set("peer_region", peer.Region) + d.Set("peer_transit_gateway_id", peer.TransitGatewayId) + d.Set("transit_gateway_id", local.TransitGatewayId) + + if err := d.Set("tags", keyvaluetags.Ec2KeyValueTags(transitGatewayPeeringAttachment.Tags).IgnoreAws().Map()); err != nil { + return fmt.Errorf("error setting tags: %s", err) + } + + d.SetId(aws.StringValue(transitGatewayPeeringAttachment.TransitGatewayAttachmentId)) + + return nil +} diff --git a/aws/data_source_aws_ec2_transit_gateway_peering_attachment_test.go b/aws/data_source_aws_ec2_transit_gateway_peering_attachment_test.go new file mode 100644 index 00000000000..3665bcf5de8 --- /dev/null +++ b/aws/data_source_aws_ec2_transit_gateway_peering_attachment_test.go @@ -0,0 +1,221 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func TestAccAWSEc2TransitGatewayPeeringAttachmentDataSource_Filter_sameAccount(t *testing.T) { + var providers []*schema.Provider + rName := acctest.RandomWithPrefix("tf-acc-test") + dataSourceName := "data.aws_ec2_transit_gateway_peering_attachment.test" + resourceName := "aws_ec2_transit_gateway_peering_attachment.test" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAWSEc2TransitGateway(t) + testAccMultipleRegionsPreCheck(t) + testAccAlternateRegionPreCheck(t) + }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckAWSEc2TransitGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2TransitGatewayPeeringAttachmentDataSourceConfigFilter_sameAccount(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceName, "peer_account_id", dataSourceName, "peer_account_id"), + resource.TestCheckResourceAttrPair(resourceName, "peer_region", dataSourceName, "peer_region"), + resource.TestCheckResourceAttrPair(resourceName, "peer_transit_gateway_id", dataSourceName, "peer_transit_gateway_id"), + resource.TestCheckResourceAttrPair(resourceName, "tags.%", dataSourceName, "tags.%"), + resource.TestCheckResourceAttrPair(resourceName, "transit_gateway_id", dataSourceName, "transit_gateway_id"), + ), + }, + }, + }) +} +func TestAccAWSEc2TransitGatewayPeeringAttachmentDataSource_Filter_differentAccount(t *testing.T) { + var providers []*schema.Provider + rName := acctest.RandomWithPrefix("tf-acc-test") + dataSourceName := "data.aws_ec2_transit_gateway_peering_attachment.test" + resourceName := "aws_ec2_transit_gateway_peering_attachment.test" + transitGatewayResourceName := "aws_ec2_transit_gateway.test" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAWSEc2TransitGateway(t) + testAccMultipleRegionsPreCheck(t) + testAccAlternateRegionPreCheck(t) + }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckAWSEc2TransitGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2TransitGatewayPeeringAttachmentDataSourceConfigFilter_differentAccount(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, "peer_region", testAccGetRegion()), + resource.TestCheckResourceAttrPair(transitGatewayResourceName, "owner_id", dataSourceName, "peer_account_id"), + resource.TestCheckResourceAttrPair(resourceName, "transit_gateway_id", dataSourceName, "peer_transit_gateway_id"), + resource.TestCheckResourceAttrPair(resourceName, "peer_transit_gateway_id", dataSourceName, "transit_gateway_id"), + ), + }, + }, + }) +} +func TestAccAWSEc2TransitGatewayPeeringAttachmentDataSource_ID_sameAccount(t *testing.T) { + var providers []*schema.Provider + rName := acctest.RandomWithPrefix("tf-acc-test") + dataSourceName := "data.aws_ec2_transit_gateway_peering_attachment.test" + resourceName := "aws_ec2_transit_gateway_peering_attachment.test" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAWSEc2TransitGateway(t) + testAccMultipleRegionsPreCheck(t) + testAccAlternateRegionPreCheck(t) + }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckAWSEc2TransitGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2TransitGatewayPeeringAttachmentDataSourceConfigID_sameAccount(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceName, "peer_account_id", dataSourceName, "peer_account_id"), + resource.TestCheckResourceAttrPair(resourceName, "peer_region", dataSourceName, "peer_region"), + resource.TestCheckResourceAttrPair(resourceName, "peer_transit_gateway_id", dataSourceName, "peer_transit_gateway_id"), + resource.TestCheckResourceAttrPair(resourceName, "tags.%", dataSourceName, "tags.%"), + resource.TestCheckResourceAttrPair(resourceName, "transit_gateway_id", dataSourceName, "transit_gateway_id"), + ), + }, + }, + }) +} +func TestAccAWSEc2TransitGatewayPeeringAttachmentDataSource_ID_differentAccount(t *testing.T) { + var providers []*schema.Provider + rName := acctest.RandomWithPrefix("tf-acc-test") + dataSourceName := "data.aws_ec2_transit_gateway_peering_attachment.test" + resourceName := "aws_ec2_transit_gateway_peering_attachment.test" + transitGatewayResourceName := "aws_ec2_transit_gateway.test" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAWSEc2TransitGateway(t) + testAccMultipleRegionsPreCheck(t) + testAccAlternateRegionPreCheck(t) + }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckAWSEc2TransitGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2TransitGatewayPeeringAttachmentDataSourceConfigID_differentAccount(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, "peer_region", testAccGetRegion()), + resource.TestCheckResourceAttrPair(transitGatewayResourceName, "owner_id", dataSourceName, "peer_account_id"), + resource.TestCheckResourceAttrPair(resourceName, "transit_gateway_id", dataSourceName, "peer_transit_gateway_id"), + resource.TestCheckResourceAttrPair(resourceName, "peer_transit_gateway_id", dataSourceName, "transit_gateway_id"), + ), + }, + }, + }) +} +func TestAccAWSEc2TransitGatewayPeeringAttachmentDataSource_Tags(t *testing.T) { + var providers []*schema.Provider + rName := acctest.RandomWithPrefix("tf-acc-test") + dataSourceName := "data.aws_ec2_transit_gateway_peering_attachment.test" + resourceName := "aws_ec2_transit_gateway_peering_attachment.test" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAWSEc2TransitGateway(t) + testAccMultipleRegionsPreCheck(t) + testAccAlternateRegionPreCheck(t) + }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckAWSEc2TransitGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2TransitGatewayPeeringAttachmentDataSourceConfigTags_sameAccount(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceName, "peer_account_id", dataSourceName, "peer_account_id"), + resource.TestCheckResourceAttrPair(resourceName, "peer_region", dataSourceName, "peer_region"), + resource.TestCheckResourceAttrPair(resourceName, "peer_transit_gateway_id", dataSourceName, "peer_transit_gateway_id"), + resource.TestCheckResourceAttrPair(resourceName, "tags.%", dataSourceName, "tags.%"), + resource.TestCheckResourceAttrPair(resourceName, "transit_gateway_id", dataSourceName, "transit_gateway_id"), + ), + }, + }, + }) +} + +func testAccAWSEc2TransitGatewayPeeringAttachmentDataSourceConfig_sameAccount_base(rName string) string { + return testAccAWSEc2TransitGatewayPeeringAttachmentConfig_sameAccount_base(rName) + fmt.Sprintf(` +resource "aws_ec2_transit_gateway_peering_attachment" "test" { + peer_region = %[1]q + peer_transit_gateway_id = "${aws_ec2_transit_gateway.peer.id}" + transit_gateway_id = "${aws_ec2_transit_gateway.test.id}" + tags = { + Name = %[2]q + } +} +`, testAccGetAlternateRegion(), rName) +} +func testAccAWSEc2TransitGatewayPeeringAttachmentDataSourceConfig_differentAccount_base(rName string) string { + return testAccAWSEc2TransitGatewayPeeringAttachmentConfig_differentAccount_base(rName) + fmt.Sprintf(` +resource "aws_ec2_transit_gateway_peering_attachment" "test" { + peer_region = %[1]q + peer_transit_gateway_id = "${aws_ec2_transit_gateway.peer.id}" + transit_gateway_id = "${aws_ec2_transit_gateway.test.id}" + tags = { + Name = %[2]q + } +} +`, testAccGetAlternateRegion(), rName) +} +func testAccAWSEc2TransitGatewayPeeringAttachmentDataSourceConfigFilter_sameAccount(rName string) string { + return testAccAWSEc2TransitGatewayPeeringAttachmentDataSourceConfig_sameAccount_base(rName) + fmt.Sprintf(` +data "aws_ec2_transit_gateway_peering_attachment" "test" { + filter { + name = "transit-gateway-attachment-id" + values = ["${aws_ec2_transit_gateway_peering_attachment.test.id}"] + } +} +`) +} +func testAccAWSEc2TransitGatewayPeeringAttachmentDataSourceConfigID_sameAccount(rName string) string { + return testAccAWSEc2TransitGatewayPeeringAttachmentDataSourceConfig_sameAccount_base(rName) + fmt.Sprintf(` +data "aws_ec2_transit_gateway_peering_attachment" "test" { + id = "${aws_ec2_transit_gateway_peering_attachment.test.id}" +} +`) +} +func testAccAWSEc2TransitGatewayPeeringAttachmentDataSourceConfigTags_sameAccount(rName string) string { + return testAccAWSEc2TransitGatewayPeeringAttachmentDataSourceConfig_sameAccount_base(rName) + fmt.Sprintf(` +data "aws_ec2_transit_gateway_peering_attachment" "test" { + tags = { + Name = "${aws_ec2_transit_gateway_peering_attachment.test.tags["Name"]}" + } +} +`) +} +func testAccAWSEc2TransitGatewayPeeringAttachmentDataSourceConfigFilter_differentAccount(rName string) string { + return testAccAWSEc2TransitGatewayPeeringAttachmentDataSourceConfig_differentAccount_base(rName) + fmt.Sprintf(` +data "aws_ec2_transit_gateway_peering_attachment" "test" { + provider = "aws.alternate" + filter { + name = "transit-gateway-attachment-id" + values = ["${aws_ec2_transit_gateway_peering_attachment.test.id}"] + } +} +`) +} +func testAccAWSEc2TransitGatewayPeeringAttachmentDataSourceConfigID_differentAccount(rName string) string { + return testAccAWSEc2TransitGatewayPeeringAttachmentDataSourceConfig_differentAccount_base(rName) + fmt.Sprintf(` +data "aws_ec2_transit_gateway_peering_attachment" "test" { + provider = "aws.alternate" + id = "${aws_ec2_transit_gateway_peering_attachment.test.id}" +} +`) +} diff --git a/aws/ec2_transit_gateway.go b/aws/ec2_transit_gateway.go index 236ea3e2830..ccbadd99e64 100644 --- a/aws/ec2_transit_gateway.go +++ b/aws/ec2_transit_gateway.go @@ -211,6 +211,43 @@ func ec2DescribeTransitGatewayRouteTablePropagation(conn *ec2.EC2, transitGatewa return output.TransitGatewayRouteTablePropagations[0], nil } +func ec2DescribeTransitGatewayPeeringAttachment(conn *ec2.EC2, transitGatewayAttachmentID string) (*ec2.TransitGatewayPeeringAttachment, error) { + input := &ec2.DescribeTransitGatewayPeeringAttachmentsInput{ + TransitGatewayAttachmentIds: []*string{aws.String(transitGatewayAttachmentID)}, + } + + log.Printf("[DEBUG] Reading EC2 Transit Gateway Peering Attachment (%s): %s", transitGatewayAttachmentID, input) + for { + output, err := conn.DescribeTransitGatewayPeeringAttachments(input) + + if err != nil { + return nil, err + } + + if output == nil || len(output.TransitGatewayPeeringAttachments) == 0 { + return nil, nil + } + + for _, transitGatewayPeeringAttachment := range output.TransitGatewayPeeringAttachments { + if transitGatewayPeeringAttachment == nil { + continue + } + + if aws.StringValue(transitGatewayPeeringAttachment.TransitGatewayAttachmentId) == transitGatewayAttachmentID { + return transitGatewayPeeringAttachment, nil + } + } + + if aws.StringValue(output.NextToken) == "" { + break + } + + input.NextToken = output.NextToken + } + + return nil, nil +} + func ec2DescribeTransitGatewayVpcAttachment(conn *ec2.EC2, transitGatewayAttachmentID string) (*ec2.TransitGatewayVpcAttachment, error) { input := &ec2.DescribeTransitGatewayVpcAttachmentsInput{ TransitGatewayAttachmentIds: []*string{aws.String(transitGatewayAttachmentID)}, @@ -374,6 +411,26 @@ func ec2TransitGatewayRouteTableAssociationRefreshFunc(conn *ec2.EC2, transitGat } } +func ec2TransitGatewayPeeringAttachmentRefreshFunc(conn *ec2.EC2, transitGatewayAttachmentID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + transitGatewayPeeringAttachment, err := ec2DescribeTransitGatewayPeeringAttachment(conn, transitGatewayAttachmentID) + + if isAWSErr(err, "InvalidTransitGatewayAttachmentID.NotFound", "") { + return nil, ec2.TransitGatewayAttachmentStateDeleted, nil + } + + if err != nil { + return nil, "", fmt.Errorf("error reading EC2 Transit Gateway Peering Attachment (%s): %s", transitGatewayAttachmentID, err) + } + + if transitGatewayPeeringAttachment == nil { + return nil, ec2.TransitGatewayAttachmentStateDeleted, nil + } + + return transitGatewayPeeringAttachment, aws.StringValue(transitGatewayPeeringAttachment.State), nil + } +} + func ec2TransitGatewayVpcAttachmentRefreshFunc(conn *ec2.EC2, transitGatewayAttachmentID string) resource.StateRefreshFunc { return func() (interface{}, string, error) { transitGatewayVpcAttachment, err := ec2DescribeTransitGatewayVpcAttachment(conn, transitGatewayAttachmentID) @@ -502,6 +559,49 @@ func waitForEc2TransitGatewayRouteTableAssociationDeletion(conn *ec2.EC2, transi return err } +func waitForEc2TransitGatewayPeeringAttachmentCreation(conn *ec2.EC2, transitGatewayAttachmentID string) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{ + ec2.TransitGatewayAttachmentStatePending, + "initiatingRequest", // No ENUM currently exists in the SDK for the state given by AWS + }, + Target: []string{ + ec2.TransitGatewayAttachmentStatePendingAcceptance, + ec2.TransitGatewayAttachmentStateAvailable, + }, + Refresh: ec2TransitGatewayPeeringAttachmentRefreshFunc(conn, transitGatewayAttachmentID), + Timeout: 10 * time.Minute, + } + + log.Printf("[DEBUG] Waiting for EC2 Transit Gateway Peering Attachment (%s) availability", transitGatewayAttachmentID) + _, err := stateConf.WaitForState() + + return err +} + +func waitForEc2TransitGatewayPeeringAttachmentDeletion(conn *ec2.EC2, transitGatewayAttachmentID string) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{ + ec2.TransitGatewayAttachmentStateAvailable, + ec2.TransitGatewayAttachmentStateDeleting, + ec2.TransitGatewayAttachmentStatePendingAcceptance, + ec2.TransitGatewayAttachmentStateRejected, + }, + Target: []string{ec2.TransitGatewayAttachmentStateDeleted}, + Refresh: ec2TransitGatewayPeeringAttachmentRefreshFunc(conn, transitGatewayAttachmentID), + Timeout: 10 * time.Minute, + } + + log.Printf("[DEBUG] Waiting for EC2 Transit Gateway Peering Attachment (%s) deletion", transitGatewayAttachmentID) + _, err := stateConf.WaitForState() + + if isResourceNotFoundError(err) { + return nil + } + + return err +} + func waitForEc2TransitGatewayVpcAttachmentAcceptance(conn *ec2.EC2, transitGatewayAttachmentID string) error { stateConf := &resource.StateChangeConf{ Pending: []string{ diff --git a/aws/provider.go b/aws/provider.go index 7918530ce83..b0f5d3b72ad 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -213,6 +213,7 @@ func Provider() terraform.ResourceProvider { "aws_ec2_instance_type_offerings": dataSourceAwsEc2InstanceTypeOfferings(), "aws_ec2_transit_gateway": dataSourceAwsEc2TransitGateway(), "aws_ec2_transit_gateway_dx_gateway_attachment": dataSourceAwsEc2TransitGatewayDxGatewayAttachment(), + "aws_ec2_transit_gateway_peering_attachment": dataSourceAwsEc2TransitGatewayPeeringAttachment(), "aws_ec2_transit_gateway_route_table": dataSourceAwsEc2TransitGatewayRouteTable(), "aws_ec2_transit_gateway_vpc_attachment": dataSourceAwsEc2TransitGatewayVpcAttachment(), "aws_ec2_transit_gateway_vpn_attachment": dataSourceAwsEc2TransitGatewayVpnAttachment(), @@ -528,6 +529,7 @@ func Provider() terraform.ResourceProvider { "aws_ec2_traffic_mirror_target": resourceAwsEc2TrafficMirrorTarget(), "aws_ec2_traffic_mirror_session": resourceAwsEc2TrafficMirrorSession(), "aws_ec2_transit_gateway": resourceAwsEc2TransitGateway(), + "aws_ec2_transit_gateway_peering_attachment": resourceAwsEc2TransitGatewayPeeringAttachment(), "aws_ec2_transit_gateway_route": resourceAwsEc2TransitGatewayRoute(), "aws_ec2_transit_gateway_route_table": resourceAwsEc2TransitGatewayRouteTable(), "aws_ec2_transit_gateway_route_table_association": resourceAwsEc2TransitGatewayRouteTableAssociation(), diff --git a/aws/resource_aws_ec2_transit_gateway.go b/aws/resource_aws_ec2_transit_gateway.go index 08381949bbe..a9ef4791175 100644 --- a/aws/resource_aws_ec2_transit_gateway.go +++ b/aws/resource_aws_ec2_transit_gateway.go @@ -232,6 +232,10 @@ func resourceAwsEc2TransitGatewayDelete(d *schema.ResourceData, meta interface{} return resource.RetryableError(err) } + if isAWSErr(err, "IncorrectState", "has non-deleted Transit Gateway Cross Region Peering Attachments") { + return resource.RetryableError(err) + } + if err != nil { return resource.NonRetryableError(err) } diff --git a/aws/resource_aws_ec2_transit_gateway_peering_attachment.go b/aws/resource_aws_ec2_transit_gateway_peering_attachment.go new file mode 100644 index 00000000000..0e1a8a9df37 --- /dev/null +++ b/aws/resource_aws_ec2_transit_gateway_peering_attachment.go @@ -0,0 +1,157 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" +) + +func resourceAwsEc2TransitGatewayPeeringAttachment() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsEc2TransitGatewayPeeringAttachmentCreate, + Read: resourceAwsEc2TransitGatewayPeeringAttachmentRead, + Update: resourceAwsEc2TransitGatewayPeeringAttachmentUpdate, + Delete: resourceAwsEc2TransitGatewayPeeringAttachmentDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "peer_account_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + ValidateFunc: validateAwsAccountId, + }, + "peer_region": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "peer_transit_gateway_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "tags": tagsSchema(), + "transit_gateway_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceAwsEc2TransitGatewayPeeringAttachmentCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + peerAccountId := meta.(*AWSClient).accountid + if v, ok := d.GetOk("peer_account_id"); ok { + peerAccountId = v.(string) + } + input := &ec2.CreateTransitGatewayPeeringAttachmentInput{ + PeerAccountId: aws.String(peerAccountId), + PeerRegion: aws.String(d.Get("peer_region").(string)), + PeerTransitGatewayId: aws.String(d.Get("peer_transit_gateway_id").(string)), + TagSpecifications: ec2TagSpecificationsFromMap(d.Get("tags").(map[string]interface{}), ec2.ResourceTypeTransitGatewayAttachment), + TransitGatewayId: aws.String(d.Get("transit_gateway_id").(string)), + } + + log.Printf("[DEBUG] Creating EC2 Transit Gateway Peering Attachment: %s", input) + output, err := conn.CreateTransitGatewayPeeringAttachment(input) + if err != nil { + return fmt.Errorf("error creating EC2 Transit Gateway Peering Attachment: %s", err) + } + + d.SetId(aws.StringValue(output.TransitGatewayPeeringAttachment.TransitGatewayAttachmentId)) + + if err := waitForEc2TransitGatewayPeeringAttachmentCreation(conn, d.Id()); err != nil { + return fmt.Errorf("error waiting for EC2 Transit Gateway Peering Attachment (%s) availability: %s", d.Id(), err) + } + + return resourceAwsEc2TransitGatewayPeeringAttachmentRead(d, meta) +} + +func resourceAwsEc2TransitGatewayPeeringAttachmentRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + transitGatewayPeeringAttachment, err := ec2DescribeTransitGatewayPeeringAttachment(conn, d.Id()) + + if isAWSErr(err, "InvalidTransitGatewayAttachmentID.NotFound", "") { + log.Printf("[WARN] EC2 Transit Gateway Peering Attachment (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading EC2 Transit Gateway Peering Attachment: %s", err) + } + + if transitGatewayPeeringAttachment == nil { + log.Printf("[WARN] EC2 Transit Gateway Peering Attachment (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if aws.StringValue(transitGatewayPeeringAttachment.State) == ec2.TransitGatewayAttachmentStateDeleting || aws.StringValue(transitGatewayPeeringAttachment.State) == ec2.TransitGatewayAttachmentStateDeleted { + log.Printf("[WARN] EC2 Transit Gateway Peering Attachment (%s) in deleted state (%s), removing from state", d.Id(), aws.StringValue(transitGatewayPeeringAttachment.State)) + d.SetId("") + return nil + } + + d.Set("peer_account_id", transitGatewayPeeringAttachment.AccepterTgwInfo.OwnerId) + d.Set("peer_region", transitGatewayPeeringAttachment.AccepterTgwInfo.Region) + d.Set("peer_transit_gateway_id", transitGatewayPeeringAttachment.AccepterTgwInfo.TransitGatewayId) + d.Set("transit_gateway_id", transitGatewayPeeringAttachment.RequesterTgwInfo.TransitGatewayId) + + if err := d.Set("tags", keyvaluetags.Ec2KeyValueTags(transitGatewayPeeringAttachment.Tags).IgnoreAws().Map()); err != nil { + return fmt.Errorf("error setting tags: %s", err) + } + + return nil +} + +func resourceAwsEc2TransitGatewayPeeringAttachmentUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + if d.HasChange("tags") { + o, n := d.GetChange("tags") + + if err := keyvaluetags.Ec2UpdateTags(conn, d.Id(), o, n); err != nil { + return fmt.Errorf("error updating EC2 Transit Gateway Peering Attachment (%s) tags: %s", d.Id(), err) + } + } + + return nil +} + +func resourceAwsEc2TransitGatewayPeeringAttachmentDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + input := &ec2.DeleteTransitGatewayPeeringAttachmentInput{ + TransitGatewayAttachmentId: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Deleting EC2 Transit Gateway Peering Attachment (%s): %s", d.Id(), input) + _, err := conn.DeleteTransitGatewayPeeringAttachment(input) + + if isAWSErr(err, "InvalidTransitGatewayAttachmentID.NotFound", "") { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting EC2 Transit Gateway Peering Attachment: %s", err) + } + + if err := waitForEc2TransitGatewayPeeringAttachmentDeletion(conn, d.Id()); err != nil { + return fmt.Errorf("error waiting for EC2 Transit Gateway Peering Attachment (%s) deletion: %s", d.Id(), err) + } + + return nil +} diff --git a/aws/resource_aws_ec2_transit_gateway_peering_attachment_test.go b/aws/resource_aws_ec2_transit_gateway_peering_attachment_test.go new file mode 100644 index 00000000000..51d73597036 --- /dev/null +++ b/aws/resource_aws_ec2_transit_gateway_peering_attachment_test.go @@ -0,0 +1,401 @@ +package aws + +import ( + "fmt" + "log" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/go-multierror" + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func init() { + resource.AddTestSweepers("aws_ec2_transit_gateway_peering_attachment", &resource.Sweeper{ + Name: "aws_ec2_transit_gateway_peering_attachment", + F: testSweepEc2TransitGatewayPeeringAttachments, + }) +} + +func testSweepEc2TransitGatewayPeeringAttachments(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*AWSClient).ec2conn + input := &ec2.DescribeTransitGatewayPeeringAttachmentsInput{} + var sweeperErrs *multierror.Error + + err = conn.DescribeTransitGatewayPeeringAttachmentsPages(input, + func(page *ec2.DescribeTransitGatewayPeeringAttachmentsOutput, lastPage bool) bool { + for _, transitGatewayPeeringAttachment := range page.TransitGatewayPeeringAttachments { + if aws.StringValue(transitGatewayPeeringAttachment.State) == ec2.TransitGatewayAttachmentStateDeleted { + continue + } + + id := aws.StringValue(transitGatewayPeeringAttachment.TransitGatewayAttachmentId) + + input := &ec2.DeleteTransitGatewayPeeringAttachmentInput{ + TransitGatewayAttachmentId: aws.String(id), + } + + log.Printf("[INFO] Deleting EC2 Transit Gateway Peering Attachment: %s", id) + _, err := conn.DeleteTransitGatewayPeeringAttachment(input) + + if isAWSErr(err, "InvalidTransitGatewayAttachmentID.NotFound", "") { + continue + } + + if err != nil { + sweeperErr := fmt.Errorf("error deleting EC2 Transit Gateway Peering Attachment (%s): %w", id, err) + log.Printf("[ERROR] %s", sweeperErr) + sweeperErrs = multierror.Append(sweeperErrs, sweeperErr) + continue + } + + if err := waitForEc2TransitGatewayPeeringAttachmentDeletion(conn, id); err != nil { + sweeperErr := fmt.Errorf("error waiting for EC2 Transit Gateway Peering Attachment (%s) deletion: %w", id, err) + log.Printf("[ERROR] %s", sweeperErr) + sweeperErrs = multierror.Append(sweeperErrs, sweeperErr) + continue + } + } + return !lastPage + }) + + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping EC2 Transit Gateway Peering Attachment sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error retrieving EC2 Transit Gateway Peering Attachments: %s", err) + } + + return sweeperErrs.ErrorOrNil() +} + +func TestAccAWSEc2TransitGatewayPeeringAttachment_basic(t *testing.T) { + var transitGatewayPeeringAttachment ec2.TransitGatewayPeeringAttachment + var providers []*schema.Provider + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_ec2_transit_gateway_peering_attachment.test" + transitGatewayResourceName := "aws_ec2_transit_gateway.test" + transitGatewayResourceNamePeer := "aws_ec2_transit_gateway.peer" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAWSEc2TransitGateway(t) + testAccMultipleRegionsPreCheck(t) + testAccAlternateRegionPreCheck(t) + }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckAWSEc2TransitGatewayPeeringAttachmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2TransitGatewayPeeringAttachmentConfigBasic_sameAccount(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TransitGatewayPeeringAttachmentExists(resourceName, &transitGatewayPeeringAttachment), + testAccCheckResourceAttrAccountID(resourceName, "peer_account_id"), + resource.TestCheckResourceAttr(resourceName, "peer_region", testAccGetAlternateRegion()), + resource.TestCheckResourceAttrPair(resourceName, "peer_transit_gateway_id", transitGatewayResourceNamePeer, "id"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttrPair(resourceName, "transit_gateway_id", transitGatewayResourceName, "id"), + ), + }, + { + Config: testAccAWSEc2TransitGatewayPeeringAttachmentConfigBasic_sameAccount(rName), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSEc2TransitGatewayPeeringAttachment_disappears(t *testing.T) { + var transitGatewayPeeringAttachment ec2.TransitGatewayPeeringAttachment + var providers []*schema.Provider + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_ec2_transit_gateway_peering_attachment.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAWSEc2TransitGateway(t) + testAccMultipleRegionsPreCheck(t) + testAccAlternateRegionPreCheck(t) + }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckAWSEc2TransitGatewayPeeringAttachmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2TransitGatewayPeeringAttachmentConfigBasic_sameAccount(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TransitGatewayPeeringAttachmentExists(resourceName, &transitGatewayPeeringAttachment), + testAccCheckAWSEc2TransitGatewayPeeringAttachmentDisappears(&transitGatewayPeeringAttachment), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSEc2TransitGatewayPeeringAttachment_Tags_sameAccount(t *testing.T) { + var transitGatewayPeeringAttachment ec2.TransitGatewayPeeringAttachment + var providers []*schema.Provider + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_ec2_transit_gateway_peering_attachment.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAWSEc2TransitGateway(t) + testAccMultipleRegionsPreCheck(t) + testAccAlternateRegionPreCheck(t) + }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckAWSEc2TransitGatewayPeeringAttachmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2TransitGatewayPeeringAttachmentConfigTags1_sameAccount(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TransitGatewayPeeringAttachmentExists(resourceName, &transitGatewayPeeringAttachment), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), + ), + }, + { + Config: testAccAWSEc2TransitGatewayPeeringAttachmentConfigTags1_sameAccount(rName, "key1", "value1"), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSEc2TransitGatewayPeeringAttachmentConfigTags2_sameAccount(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TransitGatewayPeeringAttachmentExists(resourceName, &transitGatewayPeeringAttachment), + resource.TestCheckResourceAttr(resourceName, "tags.%", "3"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), + ), + }, + { + Config: testAccAWSEc2TransitGatewayPeeringAttachmentConfigBasic_sameAccount(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TransitGatewayPeeringAttachmentExists(resourceName, &transitGatewayPeeringAttachment), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + }, + }) +} + +func TestAccAWSEc2TransitGatewayPeeringAttachment_differentAccount(t *testing.T) { + var transitGatewayPeeringAttachment ec2.TransitGatewayPeeringAttachment + var providers []*schema.Provider + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_ec2_transit_gateway_peering_attachment.test" + transitGatewayResourceName := "aws_ec2_transit_gateway.test" + transitGatewayResourceNamePeer := "aws_ec2_transit_gateway.peer" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAWSEc2TransitGateway(t) + testAccMultipleRegionsPreCheck(t) + testAccAlternateRegionPreCheck(t) + }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckAWSEc2TransitGatewayPeeringAttachmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2TransitGatewayPeeringAttachmentConfigBasic_differentAccount(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TransitGatewayPeeringAttachmentExists(resourceName, &transitGatewayPeeringAttachment), + // Test that the peer account ID != the primary (request) account ID + func(s *terraform.State) error { + if testAccCheckResourceAttrAccountID(resourceName, "peer_account_id") == nil { + return fmt.Errorf("peer_account_id attribute incorrectly to the requester's account ID") + } + return nil + }, + resource.TestCheckResourceAttr(resourceName, "peer_region", testAccGetAlternateRegion()), + resource.TestCheckResourceAttrPair(resourceName, "peer_transit_gateway_id", transitGatewayResourceNamePeer, "id"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttrPair(resourceName, "transit_gateway_id", transitGatewayResourceName, "id"), + ), + }, + { + Config: testAccAWSEc2TransitGatewayPeeringAttachmentConfigBasic_differentAccount(rName), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAWSEc2TransitGatewayPeeringAttachmentExists(resourceName string, transitGatewayPeeringAttachment *ec2.TransitGatewayPeeringAttachment) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No EC2 Transit Gateway Peering Attachment ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + attachment, err := ec2DescribeTransitGatewayPeeringAttachment(conn, rs.Primary.ID) + + if err != nil { + return err + } + + if attachment == nil { + return fmt.Errorf("EC2 Transit Gateway Peering Attachment not found") + } + + if aws.StringValue(attachment.State) != ec2.TransitGatewayAttachmentStateAvailable && aws.StringValue(attachment.State) != ec2.TransitGatewayAttachmentStatePendingAcceptance { + return fmt.Errorf("EC2 Transit Gateway Peering Attachment (%s) exists in non-available/pending acceptance (%s) state", rs.Primary.ID, aws.StringValue(attachment.State)) + } + + *transitGatewayPeeringAttachment = *attachment + + return nil + } +} + +func testAccCheckAWSEc2TransitGatewayPeeringAttachmentDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ec2_transit_gateway_peering_attachment" { + continue + } + + peeringAttachment, err := ec2DescribeTransitGatewayPeeringAttachment(conn, rs.Primary.ID) + + if isAWSErr(err, "InvalidTransitGatewayAttachmentID.NotFound", "") { + continue + } + + if err != nil { + return err + } + + if peeringAttachment == nil { + continue + } + + if aws.StringValue(peeringAttachment.State) != ec2.TransitGatewayAttachmentStateDeleted { + return fmt.Errorf("EC2 Transit Gateway Peering Attachment (%s) still exists in non-deleted (%s) state", rs.Primary.ID, aws.StringValue(peeringAttachment.State)) + } + } + + return nil +} + +func testAccCheckAWSEc2TransitGatewayPeeringAttachmentDisappears(transitGatewayPeeringAttachment *ec2.TransitGatewayPeeringAttachment) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + input := &ec2.DeleteTransitGatewayPeeringAttachmentInput{ + TransitGatewayAttachmentId: transitGatewayPeeringAttachment.TransitGatewayAttachmentId, + } + + if _, err := conn.DeleteTransitGatewayPeeringAttachment(input); err != nil { + return err + } + + return waitForEc2TransitGatewayPeeringAttachmentDeletion(conn, aws.StringValue(transitGatewayPeeringAttachment.TransitGatewayAttachmentId)) + } +} + +func testAccAWSEc2TransitGatewayPeeringAttachmentConfig_base(rName string) string { + return fmt.Sprintf(` +resource "aws_ec2_transit_gateway" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway" "peer" { + provider = "aws.alternate" + + tags = { + Name = %[1]q + } +} +`, rName) +} + +func testAccAWSEc2TransitGatewayPeeringAttachmentConfig_sameAccount_base(rName string) string { + return testAccAlternateRegionProviderConfig() + testAccAWSEc2TransitGatewayPeeringAttachmentConfig_base(rName) +} + +func testAccAWSEc2TransitGatewayPeeringAttachmentConfig_differentAccount_base(rName string) string { + return testAccAlternateAccountAlternateRegionProviderConfig() + testAccAWSEc2TransitGatewayPeeringAttachmentConfig_base(rName) +} + +func testAccAWSEc2TransitGatewayPeeringAttachmentConfigBasic_sameAccount(rName string) string { + return testAccAWSEc2TransitGatewayPeeringAttachmentConfig_sameAccount_base(rName) + fmt.Sprintf(` +resource "aws_ec2_transit_gateway_peering_attachment" "test" { + peer_region = %[1]q + peer_transit_gateway_id = "${aws_ec2_transit_gateway.peer.id}" + transit_gateway_id = "${aws_ec2_transit_gateway.test.id}" +} +`, testAccGetAlternateRegion()) +} + +func testAccAWSEc2TransitGatewayPeeringAttachmentConfigBasic_differentAccount(rName string) string { + return testAccAWSEc2TransitGatewayPeeringAttachmentConfig_differentAccount_base(rName) + fmt.Sprintf(` +resource "aws_ec2_transit_gateway_peering_attachment" "test" { + peer_account_id = "${aws_ec2_transit_gateway.peer.owner_id}" + peer_region = %[1]q + peer_transit_gateway_id = "${aws_ec2_transit_gateway.peer.id}" + transit_gateway_id = "${aws_ec2_transit_gateway.test.id}" +} +`, testAccGetAlternateRegion()) +} + +func testAccAWSEc2TransitGatewayPeeringAttachmentConfigTags1_sameAccount(rName, tagKey1, tagValue1 string) string { + return testAccAWSEc2TransitGatewayPeeringAttachmentConfig_sameAccount_base(rName) + fmt.Sprintf(` +resource "aws_ec2_transit_gateway_peering_attachment" "test" { + peer_region = %[1]q + peer_transit_gateway_id = "${aws_ec2_transit_gateway.peer.id}" + transit_gateway_id = "${aws_ec2_transit_gateway.test.id}" + + tags = { + Name = %[2]q + %[3]s = %[4]q + } +} +`, testAccGetAlternateRegion(), rName, tagKey1, tagValue1) +} + +func testAccAWSEc2TransitGatewayPeeringAttachmentConfigTags2_sameAccount(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return testAccAWSEc2TransitGatewayPeeringAttachmentConfig_sameAccount_base(rName) + fmt.Sprintf(` +resource "aws_ec2_transit_gateway_peering_attachment" "test" { + peer_region = %[1]q + peer_transit_gateway_id = "${aws_ec2_transit_gateway.peer.id}" + transit_gateway_id = "${aws_ec2_transit_gateway.test.id}" + + tags = { + Name = %[2]q + %[3]s = %[4]q + %[5]s = %[6]q + } +} +`, testAccGetAlternateRegion(), rName, tagKey1, tagValue1, tagKey2, tagValue2) +} diff --git a/aws/resource_aws_ec2_transit_gateway_test.go b/aws/resource_aws_ec2_transit_gateway_test.go index b824ec5d07b..1c0a8b4c684 100644 --- a/aws/resource_aws_ec2_transit_gateway_test.go +++ b/aws/resource_aws_ec2_transit_gateway_test.go @@ -20,6 +20,7 @@ func init() { Dependencies: []string{ "aws_dx_gateway_association", "aws_ec2_transit_gateway_vpc_attachment", + "aws_ec2_transit_gateway_peering_attachment", "aws_vpn_connection", }, }) diff --git a/examples/transit-gateway-cross-account-peering-attachment/README.md b/examples/transit-gateway-cross-account-peering-attachment/README.md new file mode 100644 index 00000000000..728bbe54d08 --- /dev/null +++ b/examples/transit-gateway-cross-account-peering-attachment/README.md @@ -0,0 +1,24 @@ +# EC2 Transit Gateway Cross-Account Peering Attachment + +This example demonstrates how to peer two Transit Gateways in different regions. The peer transit gateway can be in your account or a different AWS account. Refer to AWS documentation for supported regions. + +See [more in the Transit Gateway Peering Attachment documentation](https://docs.aws.amazon.com/vpc/latest/tgw/tgw-peering.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_first_region=us-east-2" \ + -var="aws_second_region=us-west-2" +``` + +## Prerequisites + +- This example requires two AWS accounts within the same AWS Organizations Organization +- Ensure Resource Access Manager is enabled in your organization. For more information, see the [Resource Access Manager User Guide](https://docs.aws.amazon.com/ram/latest/userguide/getting-started-sharing.html). diff --git a/examples/transit-gateway-cross-account-peering-attachment/main.tf b/examples/transit-gateway-cross-account-peering-attachment/main.tf new file mode 100644 index 00000000000..437c3642aa9 --- /dev/null +++ b/examples/transit-gateway-cross-account-peering-attachment/main.tf @@ -0,0 +1,86 @@ +// First accepts the Peering attachment. +provider "aws" { + alias = "first" + + region = "${var.aws_first_region}" + access_key = "${var.aws_first_access_key}" + secret_key = "${var.aws_first_secret_key}" +} + +// Second creates the Peering attachment. +provider "aws" { + alias = "second" + + region = "${var.aws_second_region}" + access_key = "${var.aws_second_access_key}" + secret_key = "${var.aws_second_secret_key}" +} + +data "aws_caller_identity" "first" { + provider = "aws.first" +} + +data "aws_caller_identity" "second" { + provider = "aws.second" +} + +resource "aws_ec2_transit_gateway" "first" { + provider = "aws.first" + + tags = { + Name = "terraform-example" + } +} + +resource "aws_ram_resource_share" "example" { + provider = "aws.first" + + name = "terraform-example" + + tags = { + Name = "terraform-example" + } +} + +// Share the transit gateway... +resource "aws_ram_resource_association" "example" { + provider = "aws.first" + + resource_arn = "${aws_ec2_transit_gateway.first.arn}" + resource_share_arn = "${aws_ram_resource_share.example.id}" +} + +// ...with the second account. +resource "aws_ram_principal_association" "example" { + provider = "aws.first" + + principal = "${data.aws_caller_identity.second.account_id}" + resource_share_arn = "${aws_ram_resource_share.example.id}" +} + +resource "aws_ec2_transit_gateway" "second" { + provider = "aws.second" + + tags = { + Name = "terraform-example" + } +} + +// Create the Peering attachment in the second account... +resource "aws_ec2_transit_gateway_peering_attachment" "example" { + provider = "aws.second" + peer_account_id = "${data.aws_caller_identity.first.account_id}" + peer_region = "${var.aws_first_region}" + peer_transit_gateway_id = "${aws_ec2_transit_gateway.first.id}" + transit_gateway_id = "${aws_ec2_transit_gateway.second.id}" + tags = { + Name = "terraform-example" + Side = "Creator" + } + depends_on = ["aws_ram_principal_association.example", "aws_ram_resource_association.example"] + +} + +// ...it then needs to accepted by the first account. + +// ...terraform currently doesnt have resource for Transit Gateway Peering Attachment Acceptance diff --git a/examples/transit-gateway-cross-account-peering-attachment/terraform.template.tfvars b/examples/transit-gateway-cross-account-peering-attachment/terraform.template.tfvars new file mode 100644 index 00000000000..813b24302fe --- /dev/null +++ b/examples/transit-gateway-cross-account-peering-attachment/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/transit-gateway-cross-account-peering-attachment/variables.tf b/examples/transit-gateway-cross-account-peering-attachment/variables.tf new file mode 100644 index 00000000000..88b369a456e --- /dev/null +++ b/examples/transit-gateway-cross-account-peering-attachment/variables.tf @@ -0,0 +1,11 @@ +variable "aws_first_access_key" {} + +variable "aws_first_secret_key" {} + +variable "aws_second_access_key" {} + +variable "aws_second_secret_key" {} + +variable "aws_first_region" {} + +variable "aws_second_region" {} diff --git a/website/aws.erb b/website/aws.erb index 794a0a396fd..63cd92981d0 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -1121,6 +1121,9 @@