From 55a59e84a5522c7f3027b2a2fa9fe674c6d2e584 Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Mon, 25 Mar 2019 14:00:39 -0400 Subject: [PATCH] resource/aws_vpn_connection: Add transit_gateway_attachment_id attribute Reference: https://github.com/terraform-providers/terraform-provider-aws/issues/6884 Only performs lookup when the VPN Connection has an associated Transit Gateway ID. I tried unsuccessfully to use a EC2 Transit Gateway shared via Resource Access Manager (RAM) in EC2 VPN Connection creation and it always returned the below error even after waiting for greater than 10 minutes and in the web console manually as well: ``` InvalidTransitGatewayID.NotFound: The transitGateway ID 'tgw-XXXXXXXXX' does not exist ``` This broken acceptance testing is omitted from this change request as it may not be functionality actually supported by EC2. If there is a working configuration with shared Transit Gateways, we may need to lean on additional feedback after this implementation to smooth over those issues. Output from acceptance testing: ``` --- PASS: TestAccAWSVpnConnection_basic (617.58s) --- PASS: TestAccAWSVpnConnection_disappears (426.58s) --- PASS: TestAccAWSVpnConnection_importBasic (238.20s) --- PASS: TestAccAWSVpnConnection_TransitGatewayID (439.63s) --- PASS: TestAccAWSVpnConnection_tunnelOptions (269.52s) --- PASS: TestAccAWSVpnConnection_withoutStaticRoutes (195.55s) ``` --- aws/resource_aws_vpn_connection.go | 71 +++++++++++++++++---- aws/resource_aws_vpn_connection_test.go | 9 ++- website/docs/r/vpn_connection.html.markdown | 1 + 3 files changed, 69 insertions(+), 12 deletions(-) diff --git a/aws/resource_aws_vpn_connection.go b/aws/resource_aws_vpn_connection.go index 64e3461e7cb..397236768e6 100644 --- a/aws/resource_aws_vpn_connection.go +++ b/aws/resource_aws_vpn_connection.go @@ -84,6 +84,11 @@ func resourceAwsVpnConnection() *schema.Resource { ForceNew: true, }, + "transit_gateway_attachment_id": { + Type: schema.TypeString, + Computed: true, + }, + "transit_gateway_id": { Type: schema.TypeString, Optional: true, @@ -375,27 +380,70 @@ func resourceAwsVpnConnectionRead(d *schema.ResourceData, meta interface{}) erro resp, err := conn.DescribeVpnConnections(&ec2.DescribeVpnConnectionsInput{ VpnConnectionIds: []*string{aws.String(d.Id())}, }) + + if isAWSErr(err, "InvalidVpnConnectionID.NotFound", "") { + log.Printf("[WARN] EC2 VPN Connection (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + if err != nil { - if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVpnConnectionID.NotFound" { - d.SetId("") - return nil - } else { - log.Printf("[ERROR] Error finding VPN connection: %s", err) - return err - } + return fmt.Errorf("error reading EC2 VPN Connection (%s): %s", d.Id(), err) + } + + if resp == nil || len(resp.VpnConnections) == 0 || resp.VpnConnections[0] == nil { + return fmt.Errorf("error reading EC2 VPN Connection (%s): empty response", d.Id()) } - if len(resp.VpnConnections) != 1 { - return fmt.Errorf("Error finding VPN connection: %s", d.Id()) + if len(resp.VpnConnections) > 1 { + return fmt.Errorf("error reading EC2 VPN Connection (%s): multiple responses", d.Id()) } vpnConnection := resp.VpnConnections[0] - if vpnConnection == nil || *vpnConnection.State == "deleted" { - // Seems we have lost our VPN Connection + + if aws.StringValue(vpnConnection.State) == ec2.VpnStateDeleted { + log.Printf("[WARN] EC2 VPN Connection (%s) already deleted, removing from state", d.Id()) d.SetId("") return nil } + var transitGatewayAttachmentID string + if vpnConnection.TransitGatewayId != nil { + input := &ec2.DescribeTransitGatewayAttachmentsInput{ + Filters: []*ec2.Filter{ + { + Name: aws.String("resource-id"), + Values: []*string{vpnConnection.VpnConnectionId}, + }, + { + Name: aws.String("resource-type"), + Values: []*string{aws.String(ec2.TransitGatewayAttachmentResourceTypeVpn)}, + }, + { + Name: aws.String("transit-gateway-id"), + Values: []*string{vpnConnection.TransitGatewayId}, + }, + }, + } + + log.Printf("[DEBUG] Finding EC2 VPN Connection Transit Gateway Attachment: %s", input) + output, err := conn.DescribeTransitGatewayAttachments(input) + + if err != nil { + return fmt.Errorf("error finding EC2 VPN Connection (%s) Transit Gateway Attachment: %s", d.Id(), err) + } + + if output == nil || len(output.TransitGatewayAttachments) == 0 || output.TransitGatewayAttachments[0] == nil { + return fmt.Errorf("error finding EC2 VPN Connection (%s) Transit Gateway Attachment: empty response", d.Id()) + } + + if len(output.TransitGatewayAttachments) > 1 { + return fmt.Errorf("error reading EC2 VPN Connection (%s) Transit Gateway Attachment: multiple responses", d.Id()) + } + + transitGatewayAttachmentID = aws.StringValue(output.TransitGatewayAttachments[0].TransitGatewayAttachmentId) + } + // Set attributes under the user's control. d.Set("vpn_gateway_id", vpnConnection.VpnGatewayId) d.Set("customer_gateway_id", vpnConnection.CustomerGatewayId) @@ -414,6 +462,7 @@ func resourceAwsVpnConnectionRead(d *schema.ResourceData, meta interface{}) erro // Set read only attributes. d.Set("customer_gateway_configuration", vpnConnection.CustomerGatewayConfiguration) + d.Set("transit_gateway_attachment_id", transitGatewayAttachmentID) if vpnConnection.CustomerGatewayConfiguration != nil { if tunnelInfo, err := xmlConfigToTunnelInfo(*vpnConnection.CustomerGatewayConfiguration); err != nil { diff --git a/aws/resource_aws_vpn_connection_test.go b/aws/resource_aws_vpn_connection_test.go index ba326488262..cb64059903f 100644 --- a/aws/resource_aws_vpn_connection_test.go +++ b/aws/resource_aws_vpn_connection_test.go @@ -51,6 +51,7 @@ func TestAccAWSVpnConnection_basic(t *testing.T) { Config: testAccAwsVpnConnectionConfig(rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccAwsVpnConnectionExists("aws_vpn_connection.foo", &vpn), + resource.TestCheckResourceAttr("aws_vpn_connection.foo", "transit_gateway_attachment_id", ""), ), }, { @@ -66,10 +67,14 @@ func TestAccAWSVpnConnection_basic(t *testing.T) { func TestAccAWSVpnConnection_TransitGatewayID(t *testing.T) { var vpn ec2.VpnConnection rBgpAsn := acctest.RandIntRange(64512, 65534) + transitGatewayResourceName := "aws_ec2_transit_gateway.test" resourceName := "aws_vpn_connection.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAWSEc2TransitGateway(t) + }, Providers: testAccProviders, CheckDestroy: testAccAwsVpnConnectionDestroy, Steps: []resource.TestStep{ @@ -77,6 +82,8 @@ func TestAccAWSVpnConnection_TransitGatewayID(t *testing.T) { Config: testAccAwsVpnConnectionConfigTransitGatewayID(rBgpAsn), Check: resource.ComposeTestCheckFunc( testAccAwsVpnConnectionExists(resourceName, &vpn), + resource.TestMatchResourceAttr(resourceName, "transit_gateway_attachment_id", regexp.MustCompile(`tgw-attach-.+`)), + resource.TestCheckResourceAttrPair(resourceName, "transit_gateway_id", transitGatewayResourceName, "id"), ), }, }, diff --git a/website/docs/r/vpn_connection.html.markdown b/website/docs/r/vpn_connection.html.markdown index 30d0f86b1be..2ce7d0ff7e8 100644 --- a/website/docs/r/vpn_connection.html.markdown +++ b/website/docs/r/vpn_connection.html.markdown @@ -93,6 +93,7 @@ In addition to all arguments above, the following attributes are exported: * `customer_gateway_id` - The ID of the customer gateway to which the connection is attached. * `static_routes_only` - Whether the VPN connection uses static routes exclusively. * `tags` - Tags applied to the connection. +* `transit_gateway_attachment_id` - When associated with an EC2 Transit Gateway (`transit_gateway_id` argument), the attachment ID. * `tunnel1_address` - The public IP address of the first VPN tunnel. * `tunnel1_cgw_inside_address` - The RFC 6890 link-local address of the first VPN tunnel (Customer Gateway Side). * `tunnel1_vgw_inside_address` - The RFC 6890 link-local address of the first VPN tunnel (VPN Gateway Side).