Skip to content

Commit

Permalink
resource/aws_vpn_connection: Add transit_gateway_attachment_id attribute
Browse files Browse the repository at this point in the history
Reference: #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)
```
  • Loading branch information
bflad committed Mar 25, 2019
1 parent 61dbbbd commit 55a59e8
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 12 deletions.
71 changes: 60 additions & 11 deletions aws/resource_aws_vpn_connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -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 {
Expand Down
9 changes: 8 additions & 1 deletion aws/resource_aws_vpn_connection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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", ""),
),
},
{
Expand All @@ -66,17 +67,23 @@ 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{
{
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"),
),
},
},
Expand Down
1 change: 1 addition & 0 deletions website/docs/r/vpn_connection.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down

0 comments on commit 55a59e8

Please sign in to comment.