Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add aws_vpn_gateway_attachment resource. #7870

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions builtin/providers/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ func Provider() terraform.ResourceProvider {
"aws_vpn_connection": resourceAwsVpnConnection(),
"aws_vpn_connection_route": resourceAwsVpnConnectionRoute(),
"aws_vpn_gateway": resourceAwsVpnGateway(),
"aws_vpn_gateway_attachment": resourceAwsVpnGatewayAttachment(),
},
ConfigureFunc: providerConfigure,
}
Expand Down
21 changes: 16 additions & 5 deletions builtin/providers/aws/resource_aws_vpn_gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func resourceAwsVpnGateway() *schema.Resource {
"vpc_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},

"tags": tagsSchema(),
Expand Down Expand Up @@ -80,17 +81,18 @@ func resourceAwsVpnGatewayRead(d *schema.ResourceData, meta interface{}) error {
}

vpnGateway := resp.VpnGateways[0]
if vpnGateway == nil {
if vpnGateway == nil || *vpnGateway.State == "deleted" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we are adding state == "deleted" here, then we should remove it from line 89 :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stack72 corrected!

// Seems we have lost our VPN gateway
d.SetId("")
return nil
}

if len(vpnGateway.VpcAttachments) == 0 || *vpnGateway.VpcAttachments[0].State == "detached" || *vpnGateway.VpcAttachments[0].State == "deleted" {
vpnAttachment := vpnGatewayGetAttachment(vpnGateway)
if len(vpnGateway.VpcAttachments) == 0 || *vpnAttachment.State == "detached" {
// Gateway exists but not attached to the VPC
d.Set("vpc_id", "")
} else {
d.Set("vpc_id", vpnGateway.VpcAttachments[0].VpcId)
d.Set("vpc_id", *vpnAttachment.VpcId)
}
d.Set("availability_zone", vpnGateway.AvailabilityZone)
d.Set("tags", tagsToMap(vpnGateway.Tags))
Expand Down Expand Up @@ -301,12 +303,21 @@ func vpnGatewayAttachStateRefreshFunc(conn *ec2.EC2, id string, expected string)
}

vpnGateway := resp.VpnGateways[0]

if len(vpnGateway.VpcAttachments) == 0 {
// No attachments, we're detached
return vpnGateway, "detached", nil
}

return vpnGateway, *vpnGateway.VpcAttachments[0].State, nil
vpnAttachment := vpnGatewayGetAttachment(vpnGateway)
return vpnGateway, *vpnAttachment.State, nil
}
}

func vpnGatewayGetAttachment(vgw *ec2.VpnGateway) *ec2.VpcAttachment {
for _, v := range vgw.VpcAttachments {
if *v.State == "attached" {
return v
}
}
return &ec2.VpcAttachment{State: aws.String("detached")}
}
210 changes: 210 additions & 0 deletions builtin/providers/aws/resource_aws_vpn_gateway_attachment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package aws

import (
"fmt"
"log"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsVpnGatewayAttachment() *schema.Resource {
return &schema.Resource{
Create: resourceAwsVpnGatewayAttachmentCreate,
Read: resourceAwsVpnGatewayAttachmentRead,
Delete: resourceAwsVpnGatewayAttachmentDelete,

Schema: map[string]*schema.Schema{
"vpc_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"vpn_gateway_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}

func resourceAwsVpnGatewayAttachmentCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn

vpcId := d.Get("vpc_id").(string)
vgwId := d.Get("vpn_gateway_id").(string)

createOpts := &ec2.AttachVpnGatewayInput{
VpcId: aws.String(vpcId),
VpnGatewayId: aws.String(vgwId),
}
log.Printf("[DEBUG] VPN Gateway attachment options: %#v", *createOpts)

_, err := conn.AttachVpnGateway(createOpts)
if err != nil {
return fmt.Errorf("Error attaching VPN Gateway %q to VPC %q: %s",
vgwId, vpcId, err)
}

d.SetId(vpnGatewayAttachmentId(vpcId, vgwId))
log.Printf("[INFO] VPN Gateway %q attachment ID: %s", vgwId, d.Id())

stateConf := &resource.StateChangeConf{
Pending: []string{"detached", "attaching"},
Target: []string{"attached"},
Refresh: vpnGatewayAttachmentStateRefresh(conn, vpcId, vgwId),
Timeout: 5 * time.Minute,
Delay: 10 * time.Second,
MinTimeout: 3 * time.Second,
}

_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("Error waiting for VPN Gateway %q to attach to VPC %q: %s",
vgwId, vpcId, err)
}
log.Printf("[DEBUG] VPN Gateway %q attached to VPC %q.", vgwId, vpcId)

return resourceAwsVpnGatewayAttachmentRead(d, meta)
}

func resourceAwsVpnGatewayAttachmentRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn

vgwId := d.Get("vpn_gateway_id").(string)

resp, err := conn.DescribeVpnGateways(&ec2.DescribeVpnGatewaysInput{
VpnGatewayIds: []*string{aws.String(vgwId)},
})

if err != nil {
awsErr, ok := err.(awserr.Error)
if ok && awsErr.Code() == "InvalidVPNGatewayID.NotFound" {
log.Printf("[WARN] VPN Gateway %q not found.", vgwId)
d.SetId("")
return nil
}
return err
}

vgw := resp.VpnGateways[0]
if *vgw.State == "deleted" {
log.Printf("[INFO] VPN Gateway %q appears to have been deleted.", vgwId)
d.SetId("")
return nil
}

vga := vpnGatewayGetAttachment(vgw)
if len(vgw.VpcAttachments) == 0 || *vga.State == "detached" {
d.Set("vpc_id", "")
return nil
}

d.Set("vpc_id", *vga.VpcId)
return nil
}

func resourceAwsVpnGatewayAttachmentDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn

vpcId := d.Get("vpc_id").(string)
vgwId := d.Get("vpn_gateway_id").(string)

if vpcId == "" {
log.Printf("[DEBUG] Not detaching VPN Gateway %q as no VPC ID is set.", vgwId)
return nil
}

_, err := conn.DetachVpnGateway(&ec2.DetachVpnGatewayInput{
VpcId: aws.String(vpcId),
VpnGatewayId: aws.String(vgwId),
})

if err != nil {
awsErr, ok := err.(awserr.Error)
if ok {
switch awsErr.Code() {
case "InvalidVPNGatewayID.NotFound":
log.Printf("[WARN] VPN Gateway %q not found.", vgwId)
d.SetId("")
return nil
case "InvalidVpnGatewayAttachment.NotFound":
log.Printf(
"[WARN] VPN Gateway %q attachment to VPC %q not found.",
vgwId, vpcId)
d.SetId("")
return nil
}
}

return fmt.Errorf("Error detaching VPN Gateway %q from VPC %q: %s",
vgwId, vpcId, err)
}

stateConf := &resource.StateChangeConf{
Pending: []string{"attached", "detaching"},
Target: []string{"detached"},
Refresh: vpnGatewayAttachmentStateRefresh(conn, vpcId, vgwId),
Timeout: 5 * time.Minute,
Delay: 10 * time.Second,
MinTimeout: 3 * time.Second,
}

_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("Error waiting for VPN Gateway %q to detach from VPC %q: %s",
vgwId, vpcId, err)
}
log.Printf("[DEBUG] VPN Gateway %q detached from VPC %q.", vgwId, vpcId)

d.SetId("")
return nil
}

func vpnGatewayAttachmentStateRefresh(conn *ec2.EC2, vpcId, vgwId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
resp, err := conn.DescribeVpnGateways(&ec2.DescribeVpnGatewaysInput{
Filters: []*ec2.Filter{
&ec2.Filter{
Name: aws.String("attachment.vpc-id"),
Values: []*string{aws.String(vpcId)},
},
},
VpnGatewayIds: []*string{aws.String(vgwId)},
})

if err != nil {
awsErr, ok := err.(awserr.Error)
if ok {
switch awsErr.Code() {
case "InvalidVPNGatewayID.NotFound":
fallthrough
case "InvalidVpnGatewayAttachment.NotFound":
return nil, "", nil
}
}

return nil, "", err
}

vgw := resp.VpnGateways[0]
if len(vgw.VpcAttachments) == 0 {
return vgw, "detached", nil
}

vga := vpnGatewayGetAttachment(vgw)

log.Printf("[DEBUG] VPN Gateway %q attachment status: %s", vgwId, *vga.State)
return vgw, *vga.State, nil
}
}

func vpnGatewayAttachmentId(vpcId, vgwId string) string {
return fmt.Sprintf("vpn-attachment-%x", hashcode.String(fmt.Sprintf("%s-%s", vpcId, vgwId)))
}
Loading