diff --git a/aws/ec2_transit_gateway.go b/aws/ec2_transit_gateway.go index 89f4fc0ca667..58db81e197fb 100644 --- a/aws/ec2_transit_gateway.go +++ b/aws/ec2_transit_gateway.go @@ -541,7 +541,24 @@ func waitForEc2TransitGatewayRouteTableAssociationDeletion(conn *ec2.EC2, transi return err } -func waitForEc2TransitGatewayRouteTableAttachmentCreation(conn *ec2.EC2, transitGatewayAttachmentID string) error { +func waitForEc2TransitGatewayVpcAttachmentAcceptance(conn *ec2.EC2, transitGatewayAttachmentID string) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{ + ec2.TransitGatewayAttachmentStatePending, + ec2.TransitGatewayAttachmentStatePendingAcceptance, + }, + Target: []string{ec2.TransitGatewayAttachmentStateAvailable}, + Refresh: ec2TransitGatewayVpcAttachmentRefreshFunc(conn, transitGatewayAttachmentID), + Timeout: 10 * time.Minute, + } + + log.Printf("[DEBUG] Waiting for EC2 Transit Gateway VPC Attachment (%s) availability", transitGatewayAttachmentID) + _, err := stateConf.WaitForState() + + return err +} + +func waitForEc2TransitGatewayVpcAttachmentCreation(conn *ec2.EC2, transitGatewayAttachmentID string) error { stateConf := &resource.StateChangeConf{ Pending: []string{ec2.TransitGatewayAttachmentStatePending}, Target: []string{ @@ -558,7 +575,7 @@ func waitForEc2TransitGatewayRouteTableAttachmentCreation(conn *ec2.EC2, transit return err } -func waitForEc2TransitGatewayRouteTableAttachmentDeletion(conn *ec2.EC2, transitGatewayAttachmentID string) error { +func waitForEc2TransitGatewayVpcAttachmentDeletion(conn *ec2.EC2, transitGatewayAttachmentID string) error { stateConf := &resource.StateChangeConf{ Pending: []string{ ec2.TransitGatewayAttachmentStateAvailable, @@ -580,7 +597,7 @@ func waitForEc2TransitGatewayRouteTableAttachmentDeletion(conn *ec2.EC2, transit return err } -func waitForEc2TransitGatewayRouteTableAttachmentUpdate(conn *ec2.EC2, transitGatewayAttachmentID string) error { +func waitForEc2TransitGatewayVpcAttachmentUpdate(conn *ec2.EC2, transitGatewayAttachmentID string) error { stateConf := &resource.StateChangeConf{ Pending: []string{ec2.TransitGatewayAttachmentStateModifying}, Target: []string{ec2.TransitGatewayAttachmentStateAvailable}, diff --git a/aws/provider.go b/aws/provider.go index dd39d480fadc..b972efc4a86d 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -438,6 +438,7 @@ func Provider() terraform.ResourceProvider { "aws_ec2_transit_gateway_route_table_association": resourceAwsEc2TransitGatewayRouteTableAssociation(), "aws_ec2_transit_gateway_route_table_propagation": resourceAwsEc2TransitGatewayRouteTablePropagation(), "aws_ec2_transit_gateway_vpc_attachment": resourceAwsEc2TransitGatewayVpcAttachment(), + "aws_ec2_transit_gateway_vpc_attachment_accepter": resourceAwsEc2TransitGatewayVpcAttachmentAccepter(), "aws_ecr_lifecycle_policy": resourceAwsEcrLifecyclePolicy(), "aws_ecr_repository": resourceAwsEcrRepository(), "aws_ecr_repository_policy": resourceAwsEcrRepositoryPolicy(), diff --git a/aws/resource_aws_ec2_transit_gateway_vpc_attachment.go b/aws/resource_aws_ec2_transit_gateway_vpc_attachment.go index 8faa70056259..5ff6016a5303 100644 --- a/aws/resource_aws_ec2_transit_gateway_vpc_attachment.go +++ b/aws/resource_aws_ec2_transit_gateway_vpc_attachment.go @@ -104,7 +104,7 @@ func resourceAwsEc2TransitGatewayVpcAttachmentCreate(d *schema.ResourceData, met d.SetId(aws.StringValue(output.TransitGatewayVpcAttachment.TransitGatewayAttachmentId)) - if err := waitForEc2TransitGatewayRouteTableAttachmentCreation(conn, d.Id()); err != nil { + if err := waitForEc2TransitGatewayVpcAttachmentCreation(conn, d.Id()); err != nil { return fmt.Errorf("error waiting for EC2 Transit Gateway VPC Attachment (%s) availability: %s", d.Id(), err) } @@ -238,7 +238,7 @@ func resourceAwsEc2TransitGatewayVpcAttachmentUpdate(d *schema.ResourceData, met return fmt.Errorf("error modifying EC2 Transit Gateway VPC Attachment (%s): %s", d.Id(), err) } - if err := waitForEc2TransitGatewayRouteTableAttachmentUpdate(conn, d.Id()); err != nil { + if err := waitForEc2TransitGatewayVpcAttachmentUpdate(conn, d.Id()); err != nil { return fmt.Errorf("error waiting for EC2 Transit Gateway VPC Attachment (%s) update: %s", d.Id(), err) } } @@ -295,7 +295,7 @@ func resourceAwsEc2TransitGatewayVpcAttachmentDelete(d *schema.ResourceData, met return fmt.Errorf("error deleting EC2 Transit Gateway VPC Attachment: %s", err) } - if err := waitForEc2TransitGatewayRouteTableAttachmentDeletion(conn, d.Id()); err != nil { + if err := waitForEc2TransitGatewayVpcAttachmentDeletion(conn, d.Id()); err != nil { return fmt.Errorf("error waiting for EC2 Transit Gateway VPC Attachment (%s) deletion: %s", d.Id(), err) } diff --git a/aws/resource_aws_ec2_transit_gateway_vpc_attachment_accepter.go b/aws/resource_aws_ec2_transit_gateway_vpc_attachment_accepter.go new file mode 100644 index 000000000000..e88de4c091e0 --- /dev/null +++ b/aws/resource_aws_ec2_transit_gateway_vpc_attachment_accepter.go @@ -0,0 +1,246 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsEc2TransitGatewayVpcAttachmentAccepter() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsEc2TransitGatewayVpcAttachmentAccepterCreate, + Read: resourceAwsEc2TransitGatewayVpcAttachmentAccepterRead, + Update: resourceAwsEc2TransitGatewayVpcAttachmentAccepterUpdate, + Delete: resourceAwsEc2TransitGatewayVpcAttachmentAccepterDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "dns_support": { + Type: schema.TypeString, + Computed: true, + }, + "ipv6_support": { + Type: schema.TypeString, + Computed: true, + }, + "subnet_ids": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "tags": tagsSchema(), + "transit_gateway_attachment_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "transit_gateway_default_route_table_association": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "transit_gateway_default_route_table_propagation": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "transit_gateway_id": { + Type: schema.TypeString, + Computed: true, + }, + "vpc_id": { + Type: schema.TypeString, + Computed: true, + }, + "vpc_owner_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAwsEc2TransitGatewayVpcAttachmentAccepterCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + input := &ec2.AcceptTransitGatewayVpcAttachmentInput{ + TransitGatewayAttachmentId: aws.String(d.Get("transit_gateway_attachment_id").(string)), + } + + log.Printf("[DEBUG] Accepting EC2 Transit Gateway VPC Attachment: %s", input) + output, err := conn.AcceptTransitGatewayVpcAttachment(input) + if err != nil { + return fmt.Errorf("error accepting EC2 Transit Gateway VPC Attachment: %s", err) + } + + d.SetId(aws.StringValue(output.TransitGatewayVpcAttachment.TransitGatewayAttachmentId)) + transitGatewayID := aws.StringValue(output.TransitGatewayVpcAttachment.TransitGatewayId) + + if err := waitForEc2TransitGatewayVpcAttachmentAcceptance(conn, d.Id()); err != nil { + return fmt.Errorf("error waiting for EC2 Transit Gateway VPC Attachment (%s) availability: %s", d.Id(), err) + } + + if err := setTags(conn, d); err != nil { + return fmt.Errorf("error updating EC2 Transit Gateway VPC Attachment (%s) tags: %s", d.Id(), err) + } + + transitGateway, err := ec2DescribeTransitGateway(conn, transitGatewayID) + if err != nil { + return fmt.Errorf("error describing EC2 Transit Gateway (%s): %s", transitGatewayID, err) + } + + if transitGateway.Options == nil { + return fmt.Errorf("error describing EC2 Transit Gateway (%s): missing options", transitGatewayID) + } + + if err := ec2TransitGatewayRouteTableAssociationUpdate(conn, aws.StringValue(transitGateway.Options.AssociationDefaultRouteTableId), d.Id(), d.Get("transit_gateway_default_route_table_association").(bool)); err != nil { + return fmt.Errorf("error updating EC2 Transit Gateway Attachment (%s) Route Table (%s) association: %s", d.Id(), aws.StringValue(transitGateway.Options.AssociationDefaultRouteTableId), err) + } + + if err := ec2TransitGatewayRouteTablePropagationUpdate(conn, aws.StringValue(transitGateway.Options.PropagationDefaultRouteTableId), d.Id(), d.Get("transit_gateway_default_route_table_propagation").(bool)); err != nil { + return fmt.Errorf("error updating EC2 Transit Gateway Attachment (%s) Route Table (%s) propagation: %s", d.Id(), aws.StringValue(transitGateway.Options.PropagationDefaultRouteTableId), err) + } + + return resourceAwsEc2TransitGatewayVpcAttachmentAccepterRead(d, meta) +} + +func resourceAwsEc2TransitGatewayVpcAttachmentAccepterRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + transitGatewayVpcAttachment, err := ec2DescribeTransitGatewayVpcAttachment(conn, d.Id()) + + if isAWSErr(err, "InvalidTransitGatewayAttachmentID.NotFound", "") { + log.Printf("[WARN] EC2 Transit Gateway VPC Attachment (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading EC2 Transit Gateway VPC Attachment: %s", err) + } + + if transitGatewayVpcAttachment == nil { + log.Printf("[WARN] EC2 Transit Gateway VPC Attachment (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if aws.StringValue(transitGatewayVpcAttachment.State) == ec2.TransitGatewayAttachmentStateDeleting || aws.StringValue(transitGatewayVpcAttachment.State) == ec2.TransitGatewayAttachmentStateDeleted { + log.Printf("[WARN] EC2 Transit Gateway VPC Attachment (%s) in deleted state (%s), removing from state", d.Id(), aws.StringValue(transitGatewayVpcAttachment.State)) + d.SetId("") + return nil + } + + transitGatewayID := aws.StringValue(transitGatewayVpcAttachment.TransitGatewayId) + transitGateway, err := ec2DescribeTransitGateway(conn, transitGatewayID) + if err != nil { + return fmt.Errorf("error describing EC2 Transit Gateway (%s): %s", transitGatewayID, err) + } + + if transitGateway.Options == nil { + return fmt.Errorf("error describing EC2 Transit Gateway (%s): missing options", transitGatewayID) + } + + transitGatewayAssociationDefaultRouteTableID := aws.StringValue(transitGateway.Options.AssociationDefaultRouteTableId) + transitGatewayDefaultRouteTableAssociation, err := ec2DescribeTransitGatewayRouteTableAssociation(conn, transitGatewayAssociationDefaultRouteTableID, d.Id()) + if err != nil { + return fmt.Errorf("error determining EC2 Transit Gateway Attachment (%s) association to Route Table (%s): %s", d.Id(), transitGatewayAssociationDefaultRouteTableID, err) + } + + transitGatewayPropagationDefaultRouteTableID := aws.StringValue(transitGateway.Options.PropagationDefaultRouteTableId) + transitGatewayDefaultRouteTablePropagation, err := ec2DescribeTransitGatewayRouteTablePropagation(conn, transitGatewayPropagationDefaultRouteTableID, d.Id()) + if err != nil { + return fmt.Errorf("error determining EC2 Transit Gateway Attachment (%s) propagation to Route Table (%s): %s", d.Id(), transitGatewayPropagationDefaultRouteTableID, err) + } + + if transitGatewayVpcAttachment.Options == nil { + return fmt.Errorf("error reading EC2 Transit Gateway VPC Attachment (%s): missing options", d.Id()) + } + + d.Set("dns_support", transitGatewayVpcAttachment.Options.DnsSupport) + d.Set("ipv6_support", transitGatewayVpcAttachment.Options.Ipv6Support) + + if err := d.Set("subnet_ids", aws.StringValueSlice(transitGatewayVpcAttachment.SubnetIds)); err != nil { + return fmt.Errorf("error setting subnet_ids: %s", err) + } + + if err := d.Set("tags", tagsToMap(transitGatewayVpcAttachment.Tags)); err != nil { + return fmt.Errorf("error setting tags: %s", err) + } + + d.Set("transit_gateway_attachment_id", aws.StringValue(transitGatewayVpcAttachment.TransitGatewayAttachmentId)) + d.Set("transit_gateway_default_route_table_association", (transitGatewayDefaultRouteTableAssociation != nil)) + d.Set("transit_gateway_default_route_table_propagation", (transitGatewayDefaultRouteTablePropagation != nil)) + d.Set("transit_gateway_id", aws.StringValue(transitGatewayVpcAttachment.TransitGatewayId)) + d.Set("vpc_id", aws.StringValue(transitGatewayVpcAttachment.VpcId)) + d.Set("vpc_owner_id", aws.StringValue(transitGatewayVpcAttachment.VpcOwnerId)) + + return nil +} + +func resourceAwsEc2TransitGatewayVpcAttachmentAccepterUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + if d.HasChange("transit_gateway_default_route_table_association") || d.HasChange("transit_gateway_default_route_table_propagation") { + transitGatewayID := d.Get("transit_gateway_id").(string) + + transitGateway, err := ec2DescribeTransitGateway(conn, transitGatewayID) + if err != nil { + return fmt.Errorf("error describing EC2 Transit Gateway (%s): %s", transitGatewayID, err) + } + + if transitGateway.Options == nil { + return fmt.Errorf("error describing EC2 Transit Gateway (%s): missing options", transitGatewayID) + } + + if d.HasChange("transit_gateway_default_route_table_association") { + if err := ec2TransitGatewayRouteTableAssociationUpdate(conn, aws.StringValue(transitGateway.Options.AssociationDefaultRouteTableId), d.Id(), d.Get("transit_gateway_default_route_table_association").(bool)); err != nil { + return fmt.Errorf("error updating EC2 Transit Gateway Attachment (%s) Route Table (%s) association: %s", d.Id(), aws.StringValue(transitGateway.Options.AssociationDefaultRouteTableId), err) + } + } + + if d.HasChange("transit_gateway_default_route_table_propagation") { + if err := ec2TransitGatewayRouteTablePropagationUpdate(conn, aws.StringValue(transitGateway.Options.PropagationDefaultRouteTableId), d.Id(), d.Get("transit_gateway_default_route_table_propagation").(bool)); err != nil { + return fmt.Errorf("error updating EC2 Transit Gateway Attachment (%s) Route Table (%s) propagation: %s", d.Id(), aws.StringValue(transitGateway.Options.PropagationDefaultRouteTableId), err) + } + } + } + + if d.HasChange("tags") { + if err := setTags(conn, d); err != nil { + return fmt.Errorf("error updating EC2 Transit Gateway VPC Attachment (%s) tags: %s", d.Id(), err) + } + } + + return nil +} + +func resourceAwsEc2TransitGatewayVpcAttachmentAccepterDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + input := &ec2.DeleteTransitGatewayVpcAttachmentInput{ + TransitGatewayAttachmentId: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Deleting EC2 Transit Gateway VPC Attachment (%s): %s", d.Id(), input) + _, err := conn.DeleteTransitGatewayVpcAttachment(input) + + if isAWSErr(err, "InvalidTransitGatewayAttachmentID.NotFound", "") { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting EC2 Transit Gateway VPC Attachment: %s", err) + } + + if err := waitForEc2TransitGatewayVpcAttachmentDeletion(conn, d.Id()); err != nil { + return fmt.Errorf("error waiting for EC2 Transit Gateway VPC Attachment (%s) deletion: %s", d.Id(), err) + } + + return nil +} diff --git a/aws/resource_aws_ec2_transit_gateway_vpc_attachment_accepter_test.go b/aws/resource_aws_ec2_transit_gateway_vpc_attachment_accepter_test.go new file mode 100644 index 000000000000..12f26ca042ef --- /dev/null +++ b/aws/resource_aws_ec2_transit_gateway_vpc_attachment_accepter_test.go @@ -0,0 +1,320 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func TestAccAWSEc2TransitGatewayVpcAttachmentAccepter_basic(t *testing.T) { + var providers []*schema.Provider + var transitGatewayVpcAttachment ec2.TransitGatewayVpcAttachment + resourceName := "aws_ec2_transit_gateway_vpc_attachment_accepter.test" + vpcAttachmentName := "aws_ec2_transit_gateway_vpc_attachment.test" + transitGatewayResourceName := "aws_ec2_transit_gateway.test" + vpcResourceName := "aws_vpc.test" + callerIdentityDatasourceName := "data.aws_caller_identity.creator" + rName := fmt.Sprintf("tf-testacc-tgwvpcattach-%s", acctest.RandString(8)) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccAlternateAccountPreCheck(t) + testAccPreCheckAWSEc2TransitGateway(t) + }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckAWSEc2TransitGatewayVpcAttachmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2TransitGatewayVpcAttachmentAccepterConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TransitGatewayVpcAttachmentExists(resourceName, &transitGatewayVpcAttachment), + resource.TestCheckResourceAttr(resourceName, "dns_support", ec2.DnsSupportValueEnable), + resource.TestCheckResourceAttr(resourceName, "ipv6_support", ec2.Ipv6SupportValueDisable), + resource.TestCheckResourceAttr(resourceName, "subnet_ids.#", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttrPair(resourceName, "transit_gateway_id", transitGatewayResourceName, "id"), + resource.TestCheckResourceAttrPair(resourceName, "transit_gateway_attachment_id", vpcAttachmentName, "id"), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_default_route_table_association", "true"), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_default_route_table_propagation", "true"), + resource.TestCheckResourceAttrPair(resourceName, "vpc_id", vpcResourceName, "id"), + resource.TestCheckResourceAttrPair(resourceName, "vpc_owner_id", callerIdentityDatasourceName, "account_id"), + ), + }, + { + Config: testAccAWSEc2TransitGatewayVpcAttachmentAccepterConfig_basic(rName), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSEc2TransitGatewayVpcAttachmentAccepter_Tags(t *testing.T) { + var providers []*schema.Provider + var transitGatewayVpcAttachment ec2.TransitGatewayVpcAttachment + resourceName := "aws_ec2_transit_gateway_vpc_attachment_accepter.test" + vpcAttachmentName := "aws_ec2_transit_gateway_vpc_attachment.test" + transitGatewayResourceName := "aws_ec2_transit_gateway.test" + vpcResourceName := "aws_vpc.test" + callerIdentityDatasourceName := "data.aws_caller_identity.creator" + rName := fmt.Sprintf("tf-testacc-tgwvpcattach-%s", acctest.RandString(8)) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccAlternateAccountPreCheck(t) + testAccPreCheckAWSEc2TransitGateway(t) + }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckAWSEc2TransitGatewayVpcAttachmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2TransitGatewayVpcAttachmentAccepterConfig_tags(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TransitGatewayVpcAttachmentExists(resourceName, &transitGatewayVpcAttachment), + resource.TestCheckResourceAttr(resourceName, "dns_support", ec2.DnsSupportValueEnable), + resource.TestCheckResourceAttr(resourceName, "ipv6_support", ec2.Ipv6SupportValueDisable), + resource.TestCheckResourceAttr(resourceName, "subnet_ids.#", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "4"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), + resource.TestCheckResourceAttr(resourceName, "tags.Side", "Accepter"), + resource.TestCheckResourceAttr(resourceName, "tags.Key1", "Value1"), + resource.TestCheckResourceAttr(resourceName, "tags.Key2", "Value2a"), + resource.TestCheckResourceAttrPair(resourceName, "transit_gateway_id", transitGatewayResourceName, "id"), + resource.TestCheckResourceAttrPair(resourceName, "transit_gateway_attachment_id", vpcAttachmentName, "id"), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_default_route_table_association", "true"), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_default_route_table_propagation", "true"), + resource.TestCheckResourceAttrPair(resourceName, "vpc_id", vpcResourceName, "id"), + resource.TestCheckResourceAttrPair(resourceName, "vpc_owner_id", callerIdentityDatasourceName, "account_id"), + ), + }, + { + Config: testAccAWSEc2TransitGatewayVpcAttachmentAccepterConfig_tagsUpdated(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TransitGatewayVpcAttachmentExists(resourceName, &transitGatewayVpcAttachment), + resource.TestCheckResourceAttr(resourceName, "dns_support", ec2.DnsSupportValueEnable), + resource.TestCheckResourceAttr(resourceName, "ipv6_support", ec2.Ipv6SupportValueDisable), + resource.TestCheckResourceAttr(resourceName, "subnet_ids.#", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "4"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), + resource.TestCheckResourceAttr(resourceName, "tags.Side", "Accepter"), + resource.TestCheckResourceAttr(resourceName, "tags.Key3", "Value3"), + resource.TestCheckResourceAttr(resourceName, "tags.Key2", "Value2b"), + resource.TestCheckResourceAttrPair(resourceName, "transit_gateway_id", transitGatewayResourceName, "id"), + resource.TestCheckResourceAttrPair(resourceName, "transit_gateway_attachment_id", vpcAttachmentName, "id"), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_default_route_table_association", "true"), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_default_route_table_propagation", "true"), + resource.TestCheckResourceAttrPair(resourceName, "vpc_id", vpcResourceName, "id"), + resource.TestCheckResourceAttrPair(resourceName, "vpc_owner_id", callerIdentityDatasourceName, "account_id"), + ), + }, + { + Config: testAccAWSEc2TransitGatewayVpcAttachmentAccepterConfig_tagsUpdated(rName), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSEc2TransitGatewayVpcAttachmentAccepter_TransitGatewayDefaultRouteTableAssociationAndPropagation(t *testing.T) { + var providers []*schema.Provider + var transitGateway ec2.TransitGateway + var transitGatewayVpcAttachment ec2.TransitGatewayVpcAttachment + resourceName := "aws_ec2_transit_gateway_vpc_attachment_accepter.test" + transitGatewayResourceName := "aws_ec2_transit_gateway.test" + rName := fmt.Sprintf("tf-testacc-tgwvpcattach-%s", acctest.RandString(8)) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccAlternateAccountPreCheck(t) + testAccPreCheckAWSEc2TransitGateway(t) + }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckAWSEc2TransitGatewayVpcAttachmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2TransitGatewayVpcAttachmentAccepterConfig_defaultRouteTableAssociationAndPropagation(rName, false, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TransitGatewayExists(transitGatewayResourceName, &transitGateway), + testAccCheckAWSEc2TransitGatewayVpcAttachmentExists(resourceName, &transitGatewayVpcAttachment), + testAccCheckAWSEc2TransitGatewayAssociationDefaultRouteTableVpcAttachmentNotAssociated(&transitGateway, &transitGatewayVpcAttachment), + testAccCheckAWSEc2TransitGatewayPropagationDefaultRouteTableVpcAttachmentNotPropagated(&transitGateway, &transitGatewayVpcAttachment), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_default_route_table_association", "false"), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_default_route_table_propagation", "false"), + ), + }, + { + Config: testAccAWSEc2TransitGatewayVpcAttachmentAccepterConfig_defaultRouteTableAssociationAndPropagation(rName, true, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TransitGatewayExists(transitGatewayResourceName, &transitGateway), + testAccCheckAWSEc2TransitGatewayVpcAttachmentExists(resourceName, &transitGatewayVpcAttachment), + testAccCheckAWSEc2TransitGatewayAssociationDefaultRouteTableVpcAttachmentAssociated(&transitGateway, &transitGatewayVpcAttachment), + testAccCheckAWSEc2TransitGatewayPropagationDefaultRouteTableVpcAttachmentNotPropagated(&transitGateway, &transitGatewayVpcAttachment), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_default_route_table_association", "true"), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_default_route_table_propagation", "false"), + ), + }, + { + Config: testAccAWSEc2TransitGatewayVpcAttachmentAccepterConfig_defaultRouteTableAssociationAndPropagation(rName, false, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TransitGatewayExists(transitGatewayResourceName, &transitGateway), + testAccCheckAWSEc2TransitGatewayVpcAttachmentExists(resourceName, &transitGatewayVpcAttachment), + testAccCheckAWSEc2TransitGatewayAssociationDefaultRouteTableVpcAttachmentNotAssociated(&transitGateway, &transitGatewayVpcAttachment), + testAccCheckAWSEc2TransitGatewayPropagationDefaultRouteTableVpcAttachmentPropagated(&transitGateway, &transitGatewayVpcAttachment), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_default_route_table_association", "false"), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_default_route_table_propagation", "true"), + ), + }, + { + Config: testAccAWSEc2TransitGatewayVpcAttachmentAccepterConfig_defaultRouteTableAssociationAndPropagation(rName, true, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TransitGatewayExists(transitGatewayResourceName, &transitGateway), + testAccCheckAWSEc2TransitGatewayVpcAttachmentExists(resourceName, &transitGatewayVpcAttachment), + testAccCheckAWSEc2TransitGatewayAssociationDefaultRouteTableVpcAttachmentAssociated(&transitGateway, &transitGatewayVpcAttachment), + testAccCheckAWSEc2TransitGatewayPropagationDefaultRouteTableVpcAttachmentPropagated(&transitGateway, &transitGatewayVpcAttachment), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_default_route_table_association", "true"), + resource.TestCheckResourceAttr(resourceName, "transit_gateway_default_route_table_propagation", "true"), + ), + }, + }, + }) +} + +func testAccAWSEc2TransitGatewayVpcAttachmentAccepterConfig_base(rName string) string { + return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` +data "aws_availability_zones" "available" { + # IncorrectState: Transit Gateway is not available in availability zone us-west-2d + blacklisted_zone_ids = ["usw2-az4"] + state = "available" +} + +resource "aws_ec2_transit_gateway" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_ram_resource_share" "test" { + name = %[1]q + + tags = { + Name = %[1]q + } +} + +resource "aws_ram_resource_association" "test" { + resource_arn = "${aws_ec2_transit_gateway.test.arn}" + resource_share_arn = "${aws_ram_resource_share.test.id}" +} + +resource "aws_ram_principal_association" "test" { + principal = "${data.aws_caller_identity.creator.account_id}" + resource_share_arn = "${aws_ram_resource_share.test.id}" +} + +# VPC attachment creator. +data "aws_caller_identity" "creator" { + provider = "aws.alternate" +} + +resource "aws_vpc" "test" { + provider = "aws.alternate" + + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + provider = "aws.alternate" + + availability_zone = "${data.aws_availability_zones.available.names[0]}" + cidr_block = "10.0.0.0/24" + vpc_id = "${aws_vpc.test.id}" + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_vpc_attachment" "test" { + provider = "aws.alternate" + + depends_on = ["aws_ram_principal_association.test", "aws_ram_resource_association.test"] + + subnet_ids = ["${aws_subnet.test.id}"] + transit_gateway_id = "${aws_ec2_transit_gateway.test.id}" + vpc_id = "${aws_vpc.test.id}" + + tags = { + Name = %[1]q + Side = "Creator" + } +} +`, rName) +} + +func testAccAWSEc2TransitGatewayVpcAttachmentAccepterConfig_basic(rName string) string { + return testAccAWSEc2TransitGatewayVpcAttachmentAccepterConfig_base(rName) + fmt.Sprintf(` +resource "aws_ec2_transit_gateway_vpc_attachment_accepter" "test" { + transit_gateway_attachment_id = "${aws_ec2_transit_gateway_vpc_attachment.test.id}" +} +`) +} + +func testAccAWSEc2TransitGatewayVpcAttachmentAccepterConfig_tags(rName string) string { + return testAccAWSEc2TransitGatewayVpcAttachmentAccepterConfig_base(rName) + fmt.Sprintf(` +resource "aws_ec2_transit_gateway_vpc_attachment_accepter" "test" { + transit_gateway_attachment_id = "${aws_ec2_transit_gateway_vpc_attachment.test.id}" + + tags = { + Name = %[1]q + Side = "Accepter" + Key1 = "Value1" + Key2 = "Value2a" + } +} +`, rName) +} + +func testAccAWSEc2TransitGatewayVpcAttachmentAccepterConfig_tagsUpdated(rName string) string { + return testAccAWSEc2TransitGatewayVpcAttachmentAccepterConfig_base(rName) + fmt.Sprintf(` +resource "aws_ec2_transit_gateway_vpc_attachment_accepter" "test" { + transit_gateway_attachment_id = "${aws_ec2_transit_gateway_vpc_attachment.test.id}" + + tags = { + Name = %[1]q + Side = "Accepter" + Key3 = "Value3" + Key2 = "Value2b" + } +} +`, rName) +} + +func testAccAWSEc2TransitGatewayVpcAttachmentAccepterConfig_defaultRouteTableAssociationAndPropagation(rName string, association, propagation bool) string { + return testAccAWSEc2TransitGatewayVpcAttachmentAccepterConfig_base(rName) + fmt.Sprintf(` +resource "aws_ec2_transit_gateway_vpc_attachment_accepter" "test" { + transit_gateway_attachment_id = "${aws_ec2_transit_gateway_vpc_attachment.test.id}" + + tags = { + Name = %[1]q + Side = "Accepter" + } + + transit_gateway_default_route_table_association = %[2]t + transit_gateway_default_route_table_propagation = %[3]t +} +`, rName, association, propagation) +} diff --git a/aws/resource_aws_ec2_transit_gateway_vpc_attachment_test.go b/aws/resource_aws_ec2_transit_gateway_vpc_attachment_test.go index eb0ca59b1cd9..2a43c38a803f 100644 --- a/aws/resource_aws_ec2_transit_gateway_vpc_attachment_test.go +++ b/aws/resource_aws_ec2_transit_gateway_vpc_attachment_test.go @@ -68,7 +68,7 @@ func testSweepEc2TransitGatewayVpcAttachments(region string) error { return fmt.Errorf("error deleting EC2 Transit Gateway VPC Attachment (%s): %s", id, err) } - if err := waitForEc2TransitGatewayRouteTableAttachmentDeletion(conn, id); err != nil { + if err := waitForEc2TransitGatewayVpcAttachmentDeletion(conn, id); err != nil { return fmt.Errorf("error waiting for EC2 Transit Gateway VPC Attachment (%s) deletion: %s", id, err) } } @@ -529,7 +529,7 @@ func testAccCheckAWSEc2TransitGatewayVpcAttachmentDisappears(transitGatewayVpcAt return err } - return waitForEc2TransitGatewayRouteTableAttachmentDeletion(conn, aws.StringValue(transitGatewayVpcAttachment.TransitGatewayAttachmentId)) + return waitForEc2TransitGatewayVpcAttachmentDeletion(conn, aws.StringValue(transitGatewayVpcAttachment.TransitGatewayAttachmentId)) } } diff --git a/examples/transit-gateway-cross-account-vpc-attachment/README.md b/examples/transit-gateway-cross-account-vpc-attachment/README.md new file mode 100644 index 000000000000..4216c743786e --- /dev/null +++ b/examples/transit-gateway-cross-account-vpc-attachment/README.md @@ -0,0 +1,18 @@ +# EC2 Transit Gateway Cross-Account VPC Attachment + +This example demonstrates how to create a Transit Gateway in one AWS account, share it with a second AWS account, and attach a VPC in the second account to the Transit Gateway. + +See [more in the Transit Gateway documentation](https://docs.aws.amazon.com/vpc/latest/tgw/tgw-transit-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/transit-gateway-cross-account-vpc-attachment/main.tf b/examples/transit-gateway-cross-account-vpc-attachment/main.tf new file mode 100644 index 000000000000..adb8f2b2e512 --- /dev/null +++ b/examples/transit-gateway-cross-account-vpc-attachment/main.tf @@ -0,0 +1,109 @@ +// First account owns the transit gateway and accepts the VPC attachment. +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 VPC and creates the VPC attachment. +provider "aws" { + alias = "second" + + region = "${var.aws_region}" + access_key = "${var.aws_second_access_key}" + secret_key = "${var.aws_second_secret_key}" +} + +data "aws_availability_zones" "available" { + provider = "aws.second" + + state = "available" +} + +data "aws_caller_identity" "second" { + provider = "aws.second" +} + +resource "aws_ec2_transit_gateway" "example" { + 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.example.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_vpc" "example" { + provider = "aws.second" + + cidr_block = "10.0.0.0/16" + + tags = { + Name = "terraform-example" + } +} + +resource "aws_subnet" "example" { + provider = "aws.second" + + availability_zone = "${data.aws_availability_zones.available.names[0]}" + cidr_block = "10.0.0.0/24" + vpc_id = "${aws_vpc.example.id}" + + tags = { + Name = "terraform-example" + } +} + +// Create the VPC attachment in the second account... +resource "aws_ec2_transit_gateway_vpc_attachment" "example" { + provider = "aws.second" + + depends_on = ["aws_ram_principal_association.example", "aws_ram_resource_association.example"] + + subnet_ids = ["${aws_subnet.example.id}"] + transit_gateway_id = "${aws_ec2_transit_gateway.example.id}" + vpc_id = "${aws_vpc.example.id}" + + tags = { + Name = "terraform-example" + Side = "Creator" + } +} + +// ...and accept it in the first account. +resource "aws_ec2_transit_gateway_vpc_attachment_accepter" "example" { + transit_gateway_attachment_id = "${aws_ec2_transit_gateway_vpc_attachment.example.id}" + + tags = { + Name = "terraform-example" + Side = "Accepter" + } +} diff --git a/examples/transit-gateway-cross-account-vpc-attachment/terraform.template.tfvars b/examples/transit-gateway-cross-account-vpc-attachment/terraform.template.tfvars new file mode 100644 index 000000000000..813b24302fe8 --- /dev/null +++ b/examples/transit-gateway-cross-account-vpc-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-vpc-attachment/variables.tf b/examples/transit-gateway-cross-account-vpc-attachment/variables.tf new file mode 100644 index 000000000000..ed1a71f53b1d --- /dev/null +++ b/examples/transit-gateway-cross-account-vpc-attachment/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/aws.erb b/website/aws.erb index 62ad39416e28..dab643ebe8c8 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -1184,6 +1184,10 @@ aws_ec2_transit_gateway_vpc_attachment +
  • + aws_ec2_transit_gateway_vpc_attachment_accepter +
  • +
  • aws_eip
  • @@ -2918,4 +2922,4 @@ <% end %> <%= yield %> -<% end %> \ No newline at end of file +<% end %> diff --git a/website/docs/r/ec2_transit_gateway_vpc_attachment.html.markdown b/website/docs/r/ec2_transit_gateway_vpc_attachment.html.markdown index 512125969b86..50275c3ef677 100644 --- a/website/docs/r/ec2_transit_gateway_vpc_attachment.html.markdown +++ b/website/docs/r/ec2_transit_gateway_vpc_attachment.html.markdown @@ -20,6 +20,8 @@ resource "aws_ec2_transit_gateway_vpc_attachment" "example" { } ``` +A full example of how to how to create a Transit Gateway in one AWS account, share it with a second AWS account, and attach a VPC in the second account to the Transit Gateway via the `aws_ec2_transit_gateway_vpc_attachment` and `aws_ec2_transit_gateway_vpc_attachment_accepter` resources can be found in [the `./examples/transit-gateway-cross-account-vpc-attachment` directory within the Github Repository](https://github.com/terraform-providers/terraform-provider-aws/tree/master/examples/transit-gateway-cross-account-vpc-attachment). + ## Argument Reference The following arguments are supported: diff --git a/website/docs/r/ec2_transit_gateway_vpc_attachment_accepter.html.markdown b/website/docs/r/ec2_transit_gateway_vpc_attachment_accepter.html.markdown new file mode 100644 index 000000000000..a8e4e2cc7d2d --- /dev/null +++ b/website/docs/r/ec2_transit_gateway_vpc_attachment_accepter.html.markdown @@ -0,0 +1,60 @@ +--- +layout: "aws" +page_title: "AWS: aws_ec2_transit_gateway_vpc_attachment_accepter" +sidebar_current: "docs-aws-resource-ec2-transit-gateway-vpc-attachment-accepter" +description: |- + Manages the accepter's side of an EC2 Transit Gateway VPC Attachment +--- + +# Resource: aws_ec2_transit_gateway_vpc_attachment_accepter + +Manages the accepter's side of an EC2 Transit Gateway VPC Attachment. + +When a cross-account (requester's AWS account differs from the accepter's AWS account) EC2 Transit Gateway VPC Attachment +is created, an EC2 Transit Gateway VPC Attachment resource is automatically created in the accepter's account. +The requester can use the `aws_ec2_transit_gateway_vpc_attachment` resource to manage its side of the connection +and the accepter can use the `aws_ec2_transit_gateway_vpc_attachment_accepter` resource to "adopt" its side of the +connection into management. + +## Example Usage + +```hcl +resource "aws_ec2_transit_gateway_vpc_attachment_accepter" "example" { + transit_gateway_attachment_id = "${aws_ec2_transit_gateway_vpc_attachment.example.id}" + + tags = { + Name = "Example cross-account attachment" + } +} +``` + +A full example of how to how to create a Transit Gateway in one AWS account, share it with a second AWS account, and attach a VPC in the second account to the Transit Gateway via the `aws_ec2_transit_gateway_vpc_attachment` and `aws_ec2_transit_gateway_vpc_attachment_accepter` resources can be found in [the `./examples/transit-gateway-cross-account-vpc-attachment` directory within the Github Repository](https://github.com/terraform-providers/terraform-provider-aws/tree/master/examples/transit-gateway-cross-account-vpc-attachment). + +## Argument Reference + +The following arguments are supported: + +* `transit_gateway_attachment_id` - (Required) The ID of the EC2 Transit Gateway Attachment to manage. +* `transit_gateway_default_route_table_association` - (Optional) Boolean whether the VPC Attachment should be associated with the EC2 Transit Gateway association default route table. Default value: `true`. +* `transit_gateway_default_route_table_propagation` - (Optional) Boolean whether the VPC Attachment should propagate routes with the EC2 Transit Gateway propagation default route table. Default value: `true`. +* `tags` - (Optional) Key-value tags for the EC2 Transit Gateway VPC Attachment. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - EC2 Transit Gateway Attachment identifier +* `dns_support` - Whether DNS support is enabled. Valid values: `disable`, `enable`. +* `ipv6_support` - Whether IPv6 support is enabled. Valid values: `disable`, `enable`. +* `subnet_ids` - Identifiers of EC2 Subnets. +* `transit_gateway_id` - Identifier of EC2 Transit Gateway. +* `vpc_id` - Identifier of EC2 VPC. +* `vpc_owner_id` - Identifier of the AWS account that owns the EC2 VPC. + +## Import + +`aws_ec2_transit_gateway_vpc_attachment_accepter` can be imported by using the EC2 Transit Gateway Attachment identifier, e.g. + +``` +$ terraform import aws_ec2_transit_gateway_vpc_attachment_accepter.example tgw-attach-12345678 +```