From e5a2504acf7e8022805585b08cfa53c23ac5702c Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Thu, 5 Mar 2015 14:40:45 +1000 Subject: [PATCH 01/12] First pass at aws_vpn_gateway resource Uses the aws-sdk-go module and is based on the way the existing aws_internet_gateway resource works. --- .../providers/aws/resource_aws_vpn_gateway.go | 331 ++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 builtin/providers/aws/resource_aws_vpn_gateway.go diff --git a/builtin/providers/aws/resource_aws_vpn_gateway.go b/builtin/providers/aws/resource_aws_vpn_gateway.go new file mode 100644 index 0000000000..4bf73b9752 --- /dev/null +++ b/builtin/providers/aws/resource_aws_vpn_gateway.go @@ -0,0 +1,331 @@ +package aws + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/aws-sdk-go/aws" + "github.com/hashicorp/aws-sdk-go/gen/ec2" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsVpnGateway() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsVpnGatewayCreate, + Read: resourceAwsVpnGatewayRead, + Update: resourceAwsVpnGatewayUpdate, + Delete: resourceAwsVpnGatewayDelete, + + Schema: map[string]*schema.Schema{ + "availability_zone": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "vpc_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + }, + } +} + +func resourceAwsVpnGatewayCreate(d *schema.ResourceData, meta interface{}) error { + ec2conn := meta.(*AWSClient).awsEC2conn + + createOpts := &ec2.CreateVPNGatewayRequest{ + AvailabilityZone: aws.String(d.Get("availability_zone").(string)), + Type: aws.String(d.Get("type").(string)), + } + + // Create the VPN gateway + log.Printf("[DEBUG] Creating VPN gateway") + resp, err := ec2conn.CreateVPNGateway(createOpts) + if err != nil { + return fmt.Errorf("Error creating VPN gateway: %s", err) + } + + // Get the ID and store it + vpnGateway := resp.VPNGateway + d.SetId(*vpnGateway.VPNGatewayID) + log.Printf("[INFO] VPN Gateway ID: %s", vpnGateway.VPNGatewayID) + + // Attach the VPN gateway to the correct VPC + return resourceAwsVpnGatewayUpdate(d, meta) +} + +func resourceAwsVpnGatewayRead(d *schema.ResourceData, meta interface{}) error { + ec2conn := meta.(*AWSClient).awsEC2conn + + resp, err := ec2conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ + VPNGatewayIDs: []string{d.Id()}, + }) + + if err != nil { + if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnGatewayID.NotFound " { + // Update state to indicate the subnet no longer exists. + d.SetId("") + return nil + } + return err + } + if resp == nil { + return nil + } + + vpnGateway := &resp.VPNGateways[0] + if len(vpnGateway.VPCAttachments) == 0 { + // VPN gateway exists but not attached to the VPC + d.Set("vpc_id", "") + } else { + d.Set("vpc_id", vpnGateway.VPCAttachments[0].VPCID) + } + d.Set("availability_zone", vpnGateway.AvailabilityZone) + d.Set("type", vpnGateway.Type) + d.Set("tags", tagsToMapSDK(vpnGateway.Tags)) + + return nil +} + +func resourceAwsVpnGatewayUpdate(d *schema.ResourceData, meta interface{}) error { + if d.HasChange("vpc_id") { + // If we're already attached, detach it first + if err := resourceAwsVpnGatewayDetach(d, meta); err != nil { + return err + } + + // Attach the VPN gateway to the new vpc + if err := resourceAwsVpnGatewayAttach(d, meta); err != nil { + return err + } + } + + ec2conn := meta.(*AWSClient).awsEC2conn + + d.Partial(true) + + if err := setTagsSDK(ec2conn, d); err != nil { + return err + } else { + d.SetPartial("tags") + } + + d.Partial(false) + + return resourceAwsVpnGatewayRead(d, meta) +} + +func resourceAwsVpnGatewayDelete(d *schema.ResourceData, meta interface{}) error { + ec2conn := meta.(*AWSClient).awsEC2conn + + // Detach if it is attached + if err := resourceAwsVpnGatewayDetach(d, meta); err != nil { + return err + } + + log.Printf("[INFO] Deleting VPN gateway: %s", d.Id()) + + return resource.Retry(5*time.Minute, func() error { + err := ec2conn.DeleteVPNGateway(&ec2.DeleteVPNGatewayRequest{ + VPNGatewayID: aws.String(d.Id()), + }) + if err == nil { + return nil + } + + ec2err, ok := err.(*aws.APIError) + if !ok { + return err + } + + switch ec2err.Code { + case "InvalidVpnGatewayID.NotFound": + return nil + case "DependencyViolation": + return err // retry + } + + return resource.RetryError{Err: err} + }) + + return nil +} + +func resourceAwsVpnGatewayAttach(d *schema.ResourceData, meta interface{}) error { + ec2conn := meta.(*AWSClient).awsEC2conn + + if d.Get("vpc_id").(string) == "" { + log.Printf( + "[DEBUG] Not attaching VPN Gateway '%s' as no VPC ID is set", + d.Id()) + return nil + } + + log.Printf( + "[INFO] Attaching VPN Gateway '%s' to VPC '%s'", + d.Id(), + d.Get("vpc_id").(string)) + + _, err := ec2conn.AttachVPNGateway(&ec2.AttachVPNGatewayRequest{ + VPNGatewayID: aws.String(d.Id()), + VPCID: aws.String(d.Get("vpc_id").(string)), + }) + if err != nil { + return err + } + + log.Printf("[DEBUG] Waiting for VPN gateway (%s) to attach", d.Id()) + stateConf := &resource.StateChangeConf{ + Pending: []string{"detached", "attaching"}, + Target: "available", + Refresh: VpnGatewayAttachStateRefreshFunc(ec2conn, d.Id(), "available"), + Timeout: 1 * time.Minute, + } + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf( + "Error waiting for VPN gateway (%s) to attach: %s", + d.Id(), err) + } + + return nil +} + +func resourceAwsVpnGatewayDetach(d *schema.ResourceData, meta interface{}) error { + ec2conn := meta.(*AWSClient).awsEC2conn + + // Get the old VPC ID to detach from + vpcID, _ := d.GetChange("vpc_id") + + if vpcID.(string) == "" { + log.Printf( + "[DEBUG] Not detaching VPN Gateway '%s' as no VPC ID is set", + d.Id()) + return nil + } + + log.Printf( + "[INFO] Detaching VPN Gateway '%s' from VPC '%s'", + d.Id(), + vpcID.(string)) + + wait := true + err := ec2conn.DetachVPNGateway(&ec2.DetachVPNGatewayRequest{ + VPNGatewayID: aws.String(d.Id()), + VPCID: aws.String(d.Get("vpc_id").(string)), + }) + if err != nil { + ec2err, ok := err.(*aws.APIError) + if ok { + if ec2err.Code == "InvalidVpnGatewayID.NotFound" { + err = nil + wait = false + } else if ec2err.Code == "InvalidVpnGatewayAttachment.NotFound" { + err = nil + wait = false + } + } + + if err != nil { + return err + } + } + + if !wait { + return nil + } + + // Wait for it to be fully detached before continuing + log.Printf("[DEBUG] Waiting for VPN gateway (%s) to detach", d.Id()) + stateConf := &resource.StateChangeConf{ + Pending: []string{"attached", "detaching", "available"}, + Target: "detached", + Refresh: VpnGatewayAttachStateRefreshFunc(ec2conn, d.Id(), "detached"), + Timeout: 1 * time.Minute, + } + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf( + "Error waiting for vpn gateway (%s) to detach: %s", + d.Id(), err) + } + + return nil +} + + +// VpnGatewayStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch a VPNGateway. +func VpnGatewayStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ + VPNGatewayIDs: []string{id}, + }) + if err != nil { + if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnGatewayID.NotFound" { + resp = nil + } else { + log.Printf("[ERROR] Error on VpnGatewayStateRefresh: %s", err) + return nil, "", err + } + } + + if resp == nil { + // Sometimes AWS just has consistency issues and doesn't see + // our instance yet. Return an empty state. + return nil, "", nil + } + + vpnGateway := &resp.VPNGateways[0] + return vpnGateway, *vpnGateway.State, nil + } +} + +// VpnGatewayAttachStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch +// the state of a VPN gateway's attachment +func VpnGatewayAttachStateRefreshFunc(conn *ec2.EC2, id string, expected string) resource.StateRefreshFunc { + var start time.Time + return func() (interface{}, string, error) { + if start.IsZero() { + start = time.Now() + } + + resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ + VPNGatewayIDs: []string{id}, + }) + if err != nil { + if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnGatewayID.NotFound" { + resp = nil + } else { + log.Printf("[ERROR] Error on VpnGatewayStateRefresh: %s", err) + return nil, "", err + } + } + + if resp == nil { + // Sometimes AWS just has consistency issues and doesn't see + // our instance yet. Return an empty state. + return nil, "", nil + } + + vpnGateway := &resp.VPNGateways[0] + + if time.Now().Sub(start) > 10*time.Second { + return vpnGateway, expected, nil + } + + if len(vpnGateway.VPCAttachments) == 0 { + // No attachments, we're detached + return vpnGateway, "detached", nil + } + + return vpnGateway, *vpnGateway.VPCAttachments[0].State, nil + } +} From 4706ee7ffc763eee1be05a76735a4c79c32773d4 Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Thu, 5 Mar 2015 16:24:14 +1000 Subject: [PATCH 02/12] Add acceptance test for aws_vpn_gateway resource. --- builtin/providers/aws/provider.go | 1 + .../providers/aws/resource_aws_vpn_gateway.go | 5 +- .../aws/resource_aws_vpn_gateway_test.go | 231 ++++++++++++++++++ 3 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 builtin/providers/aws/resource_aws_vpn_gateway_test.go diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 0ab2919fd8..8c9cbd5d3b 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -67,6 +67,7 @@ func Provider() terraform.ResourceProvider { "aws_subnet": resourceAwsSubnet(), "aws_vpc": resourceAwsVpc(), "aws_vpc_peering_connection": resourceAwsVpcPeeringConnection(), + "aws_vpn_gateway": resourceAwsVpnGateway(), }, ConfigureFunc: providerConfigure, diff --git a/builtin/providers/aws/resource_aws_vpn_gateway.go b/builtin/providers/aws/resource_aws_vpn_gateway.go index 4bf73b9752..9e85540efe 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway.go @@ -27,7 +27,8 @@ func resourceAwsVpnGateway() *schema.Resource { "type": &schema.Schema{ Type: schema.TypeString, - Required: true, + Default: "ipsec.1", + Optional: true, ForceNew: true, }, @@ -36,6 +37,8 @@ func resourceAwsVpnGateway() *schema.Resource { Optional: true, }, + "tags": tagsSchema(), + }, } } diff --git a/builtin/providers/aws/resource_aws_vpn_gateway_test.go b/builtin/providers/aws/resource_aws_vpn_gateway_test.go new file mode 100644 index 0000000000..25ab24ebca --- /dev/null +++ b/builtin/providers/aws/resource_aws_vpn_gateway_test.go @@ -0,0 +1,231 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/hashicorp/aws-sdk-go/aws" + "github.com/hashicorp/aws-sdk-go/gen/ec2" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSVpnGateway(t *testing.T) { + var v, v2 ec2.VPNGateway + + testNotEqual := func(*terraform.State) error { + if len(v.VPCAttachments) == 0 { + return fmt.Errorf("VPN gateway A is not attached") + } + if len(v2.VPCAttachments) == 0 { + return fmt.Errorf("VPN gateway B is not attached") + } + + id1 := v.VPCAttachments[0].VPCID + id2 := v2.VPCAttachments[0].VPCID + if id1 == id2 { + return fmt.Errorf("Both attachment IDs are the same") + } + + return nil + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVpnGatewayDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccVpnGatewayConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckVpnGatewayExists( + "aws_vpn_gateway.foo", &v), + ), + }, + + resource.TestStep{ + Config: testAccVpnGatewayConfigChangeVPC, + Check: resource.ComposeTestCheckFunc( + testAccCheckVpnGatewayExists( + "aws_vpn_gateway.foo", &v2), + testNotEqual, + ), + }, + }, + }) +} + +func TestAccAWSVpnGateway_delete(t *testing.T) { + var vpnGateway ec2.VPNGateway + + testDeleted := func(r string) resource.TestCheckFunc { + return func(s *terraform.State) error { + _, ok := s.RootModule().Resources[r] + if ok { + return fmt.Errorf("VPN Gateway %q should have been deleted", r) + } + return nil + } + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVpnGatewayDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccVpnGatewayConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckVpnGatewayExists("aws_vpn_gateway.foo", &vpnGateway)), + }, + resource.TestStep{ + Config: testAccNoVpnGatewayConfig, + Check: resource.ComposeTestCheckFunc(testDeleted("aws_vpn_gateway.foo")), + }, + }, + }) +} + +func TestAccVpnGateway_tags(t *testing.T) { + var v ec2.VPNGateway + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVpnGatewayDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckVpnGatewayConfigTags, + Check: resource.ComposeTestCheckFunc( + testAccCheckVpnGatewayExists("aws_vpn_gateway.foo", &v), + ), + }, + + resource.TestStep{ + Config: testAccCheckVpnGatewayConfigTagsUpdate, + Check: resource.ComposeTestCheckFunc( + testAccCheckVpnGatewayExists("aws_vpn_gateway.foo", &v), + testAccCheckTagsSDK(&v.Tags, "foo", ""), + testAccCheckTagsSDK(&v.Tags, "bar", "baz"), + ), + }, + }, + }) +} + +func testAccCheckVpnGatewayDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).awsEC2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_vpn_gateway" { + continue + } + + // Try to find the resource + resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ + VPNGatewayIDs: []string{rs.Primary.ID}, + }) + if err == nil { + if len(resp.VPNGateways) > 0 { + return fmt.Errorf("still exists") + } + + return nil + } + + // Verify the error is what we want + ec2err, ok := err.(*aws.APIError) + if !ok { + return err + } + if ec2err.Code != "InvalidVpnGatewayID.NotFound" { + return err + } + } + + return nil +} + +func testAccCheckVpnGatewayExists(n string, ig *ec2.VPNGateway) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).awsEC2conn + resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ + VPNGatewayIDs: []string{rs.Primary.ID}, + }) + if err != nil { + return err + } + if len(resp.VPNGateways) == 0 { + return fmt.Errorf("VPNGateway not found") + } + + *ig = resp.VPNGateways[0] + + return nil + } +} + +const testAccNoVpnGatewayConfig = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} +` + +const testAccVpnGatewayConfig = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_vpn_gateway" "foo" { + vpc_id = "${aws_vpc.foo.id}" +} +` + +const testAccVpnGatewayConfigChangeVPC = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_vpc" "bar" { + cidr_block = "10.2.0.0/16" +} + +resource "aws_vpn_gateway" "foo" { + vpc_id = "${aws_vpc.bar.id}" +} +` + +const testAccCheckVpnGatewayConfigTags = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_vpn_gateway" "foo" { + vpc_id = "${aws_vpc.foo.id}" + tags { + foo = "bar" + } +} +` + +const testAccCheckVpnGatewayConfigTagsUpdate = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_vpn_gateway" "foo" { + vpc_id = "${aws_vpc.foo.id}" + tags { + bar = "baz" + } +} +` From b741e0c9a30c1b6dc602b616a0b3219a167f1869 Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Thu, 5 Mar 2015 16:27:57 +1000 Subject: [PATCH 03/12] Add documentation --- .../providers/aws/r/vpn_gateway.html.markdown | 39 +++++++++++++++++++ website/source/layouts/aws.erb | 4 ++ 2 files changed, 43 insertions(+) create mode 100644 website/source/docs/providers/aws/r/vpn_gateway.html.markdown diff --git a/website/source/docs/providers/aws/r/vpn_gateway.html.markdown b/website/source/docs/providers/aws/r/vpn_gateway.html.markdown new file mode 100644 index 0000000000..7f72d6ebac --- /dev/null +++ b/website/source/docs/providers/aws/r/vpn_gateway.html.markdown @@ -0,0 +1,39 @@ +--- +layout: "aws" +page_title: "AWS: aws_vpn_gateway" +sidebar_current: "docs-aws-resource-vpn-gateway" +description: |- + Provides a resource to create a VPC VPN Gateway. +--- + +# aws\_vpn\_gateway + +Provides a resource to create a VPC VPN Gateway. + +## Example Usage + +``` +resource "aws_vpn_gateway" "vpn_gw" { + vpc_id = "${aws_vpc.main.id}" + + tags { + Name = "main" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `vpc_id` - (Required) The VPC ID to create in. +* `type` - (Optional) The type of VPN connection this virtual private gateway supports. +* `availability_zone` - (Optional) The Availability Zone for the virtual private gateway. +* `tags` - (Optional) A mapping of tags to assign to the resource. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the VPN Gateway. + diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 030192dfde..f73403d6cb 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -95,6 +95,10 @@ > aws_vpc + + > + aws_vpn_gateway + From 98d827b6f501f880f7e148df14817adc44e50fc6 Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Fri, 6 Mar 2015 08:47:29 +1000 Subject: [PATCH 04/12] Match the internet gateway code better. --- .../providers/aws/resource_aws_vpn_gateway.go | 58 +++++++++---------- .../aws/resource_aws_vpn_gateway_test.go | 10 ++-- 2 files changed, 31 insertions(+), 37 deletions(-) diff --git a/builtin/providers/aws/resource_aws_vpn_gateway.go b/builtin/providers/aws/resource_aws_vpn_gateway.go index 9e85540efe..34049e538a 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway.go @@ -48,7 +48,7 @@ func resourceAwsVpnGatewayCreate(d *schema.ResourceData, meta interface{}) error createOpts := &ec2.CreateVPNGatewayRequest{ AvailabilityZone: aws.String(d.Get("availability_zone").(string)), - Type: aws.String(d.Get("type").(string)), + Type: aws.String(d.Get("type").(string)), } // Create the VPN gateway @@ -61,7 +61,7 @@ func resourceAwsVpnGatewayCreate(d *schema.ResourceData, meta interface{}) error // Get the ID and store it vpnGateway := resp.VPNGateway d.SetId(*vpnGateway.VPNGatewayID) - log.Printf("[INFO] VPN Gateway ID: %s", vpnGateway.VPNGatewayID) + log.Printf("[INFO] VPN Gateway ID: %s", *vpnGateway.VPNGatewayID) // Attach the VPN gateway to the correct VPC return resourceAwsVpnGatewayUpdate(d, meta) @@ -70,29 +70,23 @@ func resourceAwsVpnGatewayCreate(d *schema.ResourceData, meta interface{}) error func resourceAwsVpnGatewayRead(d *schema.ResourceData, meta interface{}) error { ec2conn := meta.(*AWSClient).awsEC2conn - resp, err := ec2conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ - VPNGatewayIDs: []string{d.Id()}, - }) - - if err != nil { - if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnGatewayID.NotFound " { - // Update state to indicate the subnet no longer exists. - d.SetId("") - return nil - } - return err - } - if resp == nil { - return nil - } + vpnGatewayRaw, _, err := VpnGatewayStateRefreshFunc(ec2conn, d.Id())() + if err != nil { + return err + } + if vpnGatewayRaw == nil { + // Seems we have lost our VPN gateway + d.SetId("") + return nil + } - vpnGateway := &resp.VPNGateways[0] - if len(vpnGateway.VPCAttachments) == 0 { - // VPN gateway exists but not attached to the VPC - d.Set("vpc_id", "") - } else { - d.Set("vpc_id", vpnGateway.VPCAttachments[0].VPCID) - } + vpnGateway := vpnGatewayRaw.(*ec2.VPNGateway) + if len(vpnGateway.VPCAttachments) == 0 { + // Gateway exists but not attached to the VPC + d.Set("vpc_id", "") + } else { + d.Set("vpc_id", vpnGateway.VPCAttachments[0].VPCID) + } d.Set("availability_zone", vpnGateway.AvailabilityZone) d.Set("type", vpnGateway.Type) d.Set("tags", tagsToMapSDK(vpnGateway.Tags)) @@ -115,17 +109,13 @@ func resourceAwsVpnGatewayUpdate(d *schema.ResourceData, meta interface{}) error ec2conn := meta.(*AWSClient).awsEC2conn - d.Partial(true) - if err := setTagsSDK(ec2conn, d); err != nil { return err - } else { - d.SetPartial("tags") } - d.Partial(false) + d.SetPartial("tags") - return resourceAwsVpnGatewayRead(d, meta) + return nil } func resourceAwsVpnGatewayDelete(d *schema.ResourceData, meta interface{}) error { @@ -160,8 +150,6 @@ func resourceAwsVpnGatewayDelete(d *schema.ResourceData, meta interface{}) error return resource.RetryError{Err: err} }) - - return nil } func resourceAwsVpnGatewayAttach(d *schema.ResourceData, meta interface{}) error { @@ -187,6 +175,12 @@ func resourceAwsVpnGatewayAttach(d *schema.ResourceData, meta interface{}) error return err } + // A note on the states below: the AWS docs (as of July, 2014) say + // that the states would be: attached, attaching, detached, detaching, + // but when running, I noticed that the state is usually "available" when + // it is attached. + + // Wait for it to be fully attached before continuing log.Printf("[DEBUG] Waiting for VPN gateway (%s) to attach", d.Id()) stateConf := &resource.StateChangeConf{ Pending: []string{"detached", "attaching"}, diff --git a/builtin/providers/aws/resource_aws_vpn_gateway_test.go b/builtin/providers/aws/resource_aws_vpn_gateway_test.go index 25ab24ebca..7b2d98157a 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway_test.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway_test.go @@ -114,7 +114,7 @@ func TestAccVpnGateway_tags(t *testing.T) { } func testAccCheckVpnGatewayDestroy(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).awsEC2conn + ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn for _, rs := range s.RootModule().Resources { if rs.Type != "aws_vpn_gateway" { @@ -122,7 +122,7 @@ func testAccCheckVpnGatewayDestroy(s *terraform.State) error { } // Try to find the resource - resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ + resp, err := ec2conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ VPNGatewayIDs: []string{rs.Primary.ID}, }) if err == nil { @@ -134,7 +134,7 @@ func testAccCheckVpnGatewayDestroy(s *terraform.State) error { } // Verify the error is what we want - ec2err, ok := err.(*aws.APIError) + ec2err, ok := err.(aws.APIError) if !ok { return err } @@ -157,8 +157,8 @@ func testAccCheckVpnGatewayExists(n string, ig *ec2.VPNGateway) resource.TestChe return fmt.Errorf("No ID is set") } - conn := testAccProvider.Meta().(*AWSClient).awsEC2conn - resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ + ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn + resp, err := ec2conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ VPNGatewayIDs: []string{rs.Primary.ID}, }) if err != nil { From 7240af439ca5e391d4086b95a2f44445f70bd435 Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Fri, 6 Mar 2015 15:48:30 +1000 Subject: [PATCH 05/12] Minor test fixes. --- builtin/providers/aws/resource_aws_vpn_gateway.go | 2 +- builtin/providers/aws/resource_aws_vpn_gateway_test.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_vpn_gateway.go b/builtin/providers/aws/resource_aws_vpn_gateway.go index 34049e538a..3a9ad85636 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway.go @@ -115,7 +115,7 @@ func resourceAwsVpnGatewayUpdate(d *schema.ResourceData, meta interface{}) error d.SetPartial("tags") - return nil + return resourceAwsVpnGatewayRead(d, meta) } func resourceAwsVpnGatewayDelete(d *schema.ResourceData, meta interface{}) error { diff --git a/builtin/providers/aws/resource_aws_vpn_gateway_test.go b/builtin/providers/aws/resource_aws_vpn_gateway_test.go index 7b2d98157a..33d54d0ae1 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway_test.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway_test.go @@ -98,6 +98,7 @@ func TestAccVpnGateway_tags(t *testing.T) { Config: testAccCheckVpnGatewayConfigTags, Check: resource.ComposeTestCheckFunc( testAccCheckVpnGatewayExists("aws_vpn_gateway.foo", &v), + testAccCheckTagsSDK(&v.Tags, "foo", "bar"), ), }, From d253fff4e51cf291515d10050c06b3496c630509 Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Tue, 10 Mar 2015 09:49:46 +1000 Subject: [PATCH 06/12] Hardcode type parameter value. Current AWS documentation says there's only one type of VPN gateway for now. --- builtin/providers/aws/resource_aws_vpn_gateway.go | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/builtin/providers/aws/resource_aws_vpn_gateway.go b/builtin/providers/aws/resource_aws_vpn_gateway.go index 3a9ad85636..4b047af320 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway.go @@ -25,13 +25,6 @@ func resourceAwsVpnGateway() *schema.Resource { ForceNew: true, }, - "type": &schema.Schema{ - Type: schema.TypeString, - Default: "ipsec.1", - Optional: true, - ForceNew: true, - }, - "vpc_id": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -48,7 +41,7 @@ func resourceAwsVpnGatewayCreate(d *schema.ResourceData, meta interface{}) error createOpts := &ec2.CreateVPNGatewayRequest{ AvailabilityZone: aws.String(d.Get("availability_zone").(string)), - Type: aws.String(d.Get("type").(string)), + Type: aws.String("ipsec.1"), } // Create the VPN gateway @@ -88,7 +81,6 @@ func resourceAwsVpnGatewayRead(d *schema.ResourceData, meta interface{}) error { d.Set("vpc_id", vpnGateway.VPCAttachments[0].VPCID) } d.Set("availability_zone", vpnGateway.AvailabilityZone) - d.Set("type", vpnGateway.Type) d.Set("tags", tagsToMapSDK(vpnGateway.Tags)) return nil From c172fd373669014a0857b0d4efe943532b18aef7 Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Tue, 10 Mar 2015 10:28:44 +1000 Subject: [PATCH 07/12] Fix error handling. AWS returns IncorrectState not DependencyViolation when a VPN gateway is still attached to a VPC. --- builtin/providers/aws/resource_aws_vpn_gateway.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/builtin/providers/aws/resource_aws_vpn_gateway.go b/builtin/providers/aws/resource_aws_vpn_gateway.go index 4b047af320..2f06a29a73 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway.go @@ -128,7 +128,7 @@ func resourceAwsVpnGatewayDelete(d *schema.ResourceData, meta interface{}) error return nil } - ec2err, ok := err.(*aws.APIError) + ec2err, ok := err.(aws.APIError) if !ok { return err } @@ -136,7 +136,7 @@ func resourceAwsVpnGatewayDelete(d *schema.ResourceData, meta interface{}) error switch ec2err.Code { case "InvalidVpnGatewayID.NotFound": return nil - case "DependencyViolation": + case "IncorrectState": return err // retry } @@ -213,7 +213,7 @@ func resourceAwsVpnGatewayDetach(d *schema.ResourceData, meta interface{}) error VPCID: aws.String(d.Get("vpc_id").(string)), }) if err != nil { - ec2err, ok := err.(*aws.APIError) + ec2err, ok := err.(aws.APIError) if ok { if ec2err.Code == "InvalidVpnGatewayID.NotFound" { err = nil From d6a731040c8d4eb308e621b69c0f997c40b53043 Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Tue, 10 Mar 2015 10:30:29 +1000 Subject: [PATCH 08/12] Format the resource_vpn_gateway*.go files. --- .../providers/aws/resource_aws_vpn_gateway.go | 464 +++++++++--------- .../aws/resource_aws_vpn_gateway_test.go | 16 +- 2 files changed, 239 insertions(+), 241 deletions(-) diff --git a/builtin/providers/aws/resource_aws_vpn_gateway.go b/builtin/providers/aws/resource_aws_vpn_gateway.go index 2f06a29a73..e00472576d 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway.go @@ -25,43 +25,42 @@ func resourceAwsVpnGateway() *schema.Resource { ForceNew: true, }, - "vpc_id": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - - "tags": tagsSchema(), + "vpc_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "tags": tagsSchema(), }, } } func resourceAwsVpnGatewayCreate(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn - - createOpts := &ec2.CreateVPNGatewayRequest{ - AvailabilityZone: aws.String(d.Get("availability_zone").(string)), - Type: aws.String("ipsec.1"), - } - - // Create the VPN gateway - log.Printf("[DEBUG] Creating VPN gateway") - resp, err := ec2conn.CreateVPNGateway(createOpts) - if err != nil { - return fmt.Errorf("Error creating VPN gateway: %s", err) - } - - // Get the ID and store it - vpnGateway := resp.VPNGateway - d.SetId(*vpnGateway.VPNGatewayID) - log.Printf("[INFO] VPN Gateway ID: %s", *vpnGateway.VPNGatewayID) - - // Attach the VPN gateway to the correct VPC - return resourceAwsVpnGatewayUpdate(d, meta) + ec2conn := meta.(*AWSClient).awsEC2conn + + createOpts := &ec2.CreateVPNGatewayRequest{ + AvailabilityZone: aws.String(d.Get("availability_zone").(string)), + Type: aws.String("ipsec.1"), + } + + // Create the VPN gateway + log.Printf("[DEBUG] Creating VPN gateway") + resp, err := ec2conn.CreateVPNGateway(createOpts) + if err != nil { + return fmt.Errorf("Error creating VPN gateway: %s", err) + } + + // Get the ID and store it + vpnGateway := resp.VPNGateway + d.SetId(*vpnGateway.VPNGatewayID) + log.Printf("[INFO] VPN Gateway ID: %s", *vpnGateway.VPNGatewayID) + + // Attach the VPN gateway to the correct VPC + return resourceAwsVpnGatewayUpdate(d, meta) } func resourceAwsVpnGatewayRead(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn + ec2conn := meta.(*AWSClient).awsEC2conn vpnGatewayRaw, _, err := VpnGatewayStateRefreshFunc(ec2conn, d.Id())() if err != nil { @@ -80,92 +79,92 @@ func resourceAwsVpnGatewayRead(d *schema.ResourceData, meta interface{}) error { } else { d.Set("vpc_id", vpnGateway.VPCAttachments[0].VPCID) } - d.Set("availability_zone", vpnGateway.AvailabilityZone) - d.Set("tags", tagsToMapSDK(vpnGateway.Tags)) + d.Set("availability_zone", vpnGateway.AvailabilityZone) + d.Set("tags", tagsToMapSDK(vpnGateway.Tags)) - return nil + return nil } func resourceAwsVpnGatewayUpdate(d *schema.ResourceData, meta interface{}) error { - if d.HasChange("vpc_id") { - // If we're already attached, detach it first - if err := resourceAwsVpnGatewayDetach(d, meta); err != nil { - return err - } - - // Attach the VPN gateway to the new vpc - if err := resourceAwsVpnGatewayAttach(d, meta); err != nil { - return err - } - } + if d.HasChange("vpc_id") { + // If we're already attached, detach it first + if err := resourceAwsVpnGatewayDetach(d, meta); err != nil { + return err + } + + // Attach the VPN gateway to the new vpc + if err := resourceAwsVpnGatewayAttach(d, meta); err != nil { + return err + } + } - ec2conn := meta.(*AWSClient).awsEC2conn + ec2conn := meta.(*AWSClient).awsEC2conn - if err := setTagsSDK(ec2conn, d); err != nil { - return err - } + if err := setTagsSDK(ec2conn, d); err != nil { + return err + } - d.SetPartial("tags") + d.SetPartial("tags") - return resourceAwsVpnGatewayRead(d, meta) + return resourceAwsVpnGatewayRead(d, meta) } func resourceAwsVpnGatewayDelete(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn - - // Detach if it is attached - if err := resourceAwsVpnGatewayDetach(d, meta); err != nil { - return err - } - - log.Printf("[INFO] Deleting VPN gateway: %s", d.Id()) - - return resource.Retry(5*time.Minute, func() error { - err := ec2conn.DeleteVPNGateway(&ec2.DeleteVPNGatewayRequest{ - VPNGatewayID: aws.String(d.Id()), - }) - if err == nil { - return nil - } - - ec2err, ok := err.(aws.APIError) - if !ok { - return err - } - - switch ec2err.Code { - case "InvalidVpnGatewayID.NotFound": - return nil - case "IncorrectState": - return err // retry - } - - return resource.RetryError{Err: err} - }) + ec2conn := meta.(*AWSClient).awsEC2conn + + // Detach if it is attached + if err := resourceAwsVpnGatewayDetach(d, meta); err != nil { + return err + } + + log.Printf("[INFO] Deleting VPN gateway: %s", d.Id()) + + return resource.Retry(5*time.Minute, func() error { + err := ec2conn.DeleteVPNGateway(&ec2.DeleteVPNGatewayRequest{ + VPNGatewayID: aws.String(d.Id()), + }) + if err == nil { + return nil + } + + ec2err, ok := err.(aws.APIError) + if !ok { + return err + } + + switch ec2err.Code { + case "InvalidVpnGatewayID.NotFound": + return nil + case "IncorrectState": + return err // retry + } + + return resource.RetryError{Err: err} + }) } func resourceAwsVpnGatewayAttach(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn - - if d.Get("vpc_id").(string) == "" { - log.Printf( - "[DEBUG] Not attaching VPN Gateway '%s' as no VPC ID is set", - d.Id()) - return nil - } - - log.Printf( - "[INFO] Attaching VPN Gateway '%s' to VPC '%s'", - d.Id(), - d.Get("vpc_id").(string)) - - _, err := ec2conn.AttachVPNGateway(&ec2.AttachVPNGatewayRequest{ - VPNGatewayID: aws.String(d.Id()), - VPCID: aws.String(d.Get("vpc_id").(string)), - }) - if err != nil { - return err - } + ec2conn := meta.(*AWSClient).awsEC2conn + + if d.Get("vpc_id").(string) == "" { + log.Printf( + "[DEBUG] Not attaching VPN Gateway '%s' as no VPC ID is set", + d.Id()) + return nil + } + + log.Printf( + "[INFO] Attaching VPN Gateway '%s' to VPC '%s'", + d.Id(), + d.Get("vpc_id").(string)) + + _, err := ec2conn.AttachVPNGateway(&ec2.AttachVPNGatewayRequest{ + VPNGatewayID: aws.String(d.Id()), + VPCID: aws.String(d.Get("vpc_id").(string)), + }) + if err != nil { + return err + } // A note on the states below: the AWS docs (as of July, 2014) say // that the states would be: attached, attaching, detached, detaching, @@ -173,148 +172,147 @@ func resourceAwsVpnGatewayAttach(d *schema.ResourceData, meta interface{}) error // it is attached. // Wait for it to be fully attached before continuing - log.Printf("[DEBUG] Waiting for VPN gateway (%s) to attach", d.Id()) - stateConf := &resource.StateChangeConf{ - Pending: []string{"detached", "attaching"}, - Target: "available", - Refresh: VpnGatewayAttachStateRefreshFunc(ec2conn, d.Id(), "available"), - Timeout: 1 * time.Minute, - } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf( - "Error waiting for VPN gateway (%s) to attach: %s", - d.Id(), err) - } - - return nil + log.Printf("[DEBUG] Waiting for VPN gateway (%s) to attach", d.Id()) + stateConf := &resource.StateChangeConf{ + Pending: []string{"detached", "attaching"}, + Target: "available", + Refresh: VpnGatewayAttachStateRefreshFunc(ec2conn, d.Id(), "available"), + Timeout: 1 * time.Minute, + } + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf( + "Error waiting for VPN gateway (%s) to attach: %s", + d.Id(), err) + } + + return nil } func resourceAwsVpnGatewayDetach(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn - - // Get the old VPC ID to detach from - vpcID, _ := d.GetChange("vpc_id") - - if vpcID.(string) == "" { - log.Printf( - "[DEBUG] Not detaching VPN Gateway '%s' as no VPC ID is set", - d.Id()) - return nil - } - - log.Printf( - "[INFO] Detaching VPN Gateway '%s' from VPC '%s'", - d.Id(), - vpcID.(string)) - - wait := true - err := ec2conn.DetachVPNGateway(&ec2.DetachVPNGatewayRequest{ - VPNGatewayID: aws.String(d.Id()), - VPCID: aws.String(d.Get("vpc_id").(string)), - }) - if err != nil { - ec2err, ok := err.(aws.APIError) - if ok { - if ec2err.Code == "InvalidVpnGatewayID.NotFound" { - err = nil - wait = false - } else if ec2err.Code == "InvalidVpnGatewayAttachment.NotFound" { - err = nil - wait = false - } - } - - if err != nil { - return err - } - } - - if !wait { - return nil - } - - // Wait for it to be fully detached before continuing - log.Printf("[DEBUG] Waiting for VPN gateway (%s) to detach", d.Id()) - stateConf := &resource.StateChangeConf{ - Pending: []string{"attached", "detaching", "available"}, - Target: "detached", - Refresh: VpnGatewayAttachStateRefreshFunc(ec2conn, d.Id(), "detached"), - Timeout: 1 * time.Minute, - } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf( - "Error waiting for vpn gateway (%s) to detach: %s", - d.Id(), err) - } - - return nil -} + ec2conn := meta.(*AWSClient).awsEC2conn + + // Get the old VPC ID to detach from + vpcID, _ := d.GetChange("vpc_id") + + if vpcID.(string) == "" { + log.Printf( + "[DEBUG] Not detaching VPN Gateway '%s' as no VPC ID is set", + d.Id()) + return nil + } + + log.Printf( + "[INFO] Detaching VPN Gateway '%s' from VPC '%s'", + d.Id(), + vpcID.(string)) + wait := true + err := ec2conn.DetachVPNGateway(&ec2.DetachVPNGatewayRequest{ + VPNGatewayID: aws.String(d.Id()), + VPCID: aws.String(d.Get("vpc_id").(string)), + }) + if err != nil { + ec2err, ok := err.(aws.APIError) + if ok { + if ec2err.Code == "InvalidVpnGatewayID.NotFound" { + err = nil + wait = false + } else if ec2err.Code == "InvalidVpnGatewayAttachment.NotFound" { + err = nil + wait = false + } + } + + if err != nil { + return err + } + } + + if !wait { + return nil + } + + // Wait for it to be fully detached before continuing + log.Printf("[DEBUG] Waiting for VPN gateway (%s) to detach", d.Id()) + stateConf := &resource.StateChangeConf{ + Pending: []string{"attached", "detaching", "available"}, + Target: "detached", + Refresh: VpnGatewayAttachStateRefreshFunc(ec2conn, d.Id(), "detached"), + Timeout: 1 * time.Minute, + } + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf( + "Error waiting for vpn gateway (%s) to detach: %s", + d.Id(), err) + } + + return nil +} // VpnGatewayStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch a VPNGateway. func VpnGatewayStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ - VPNGatewayIDs: []string{id}, - }) - if err != nil { - if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnGatewayID.NotFound" { - resp = nil - } else { - log.Printf("[ERROR] Error on VpnGatewayStateRefresh: %s", err) - return nil, "", err - } - } - - if resp == nil { - // Sometimes AWS just has consistency issues and doesn't see - // our instance yet. Return an empty state. - return nil, "", nil - } - - vpnGateway := &resp.VPNGateways[0] - return vpnGateway, *vpnGateway.State, nil - } + return func() (interface{}, string, error) { + resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ + VPNGatewayIDs: []string{id}, + }) + if err != nil { + if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnGatewayID.NotFound" { + resp = nil + } else { + log.Printf("[ERROR] Error on VpnGatewayStateRefresh: %s", err) + return nil, "", err + } + } + + if resp == nil { + // Sometimes AWS just has consistency issues and doesn't see + // our instance yet. Return an empty state. + return nil, "", nil + } + + vpnGateway := &resp.VPNGateways[0] + return vpnGateway, *vpnGateway.State, nil + } } -// VpnGatewayAttachStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch +// VpnGatewayAttachStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch // the state of a VPN gateway's attachment func VpnGatewayAttachStateRefreshFunc(conn *ec2.EC2, id string, expected string) resource.StateRefreshFunc { - var start time.Time - return func() (interface{}, string, error) { - if start.IsZero() { - start = time.Now() - } - - resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ - VPNGatewayIDs: []string{id}, - }) - if err != nil { - if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnGatewayID.NotFound" { - resp = nil - } else { - log.Printf("[ERROR] Error on VpnGatewayStateRefresh: %s", err) - return nil, "", err - } - } - - if resp == nil { - // Sometimes AWS just has consistency issues and doesn't see - // our instance yet. Return an empty state. - return nil, "", nil - } - - vpnGateway := &resp.VPNGateways[0] - - if time.Now().Sub(start) > 10*time.Second { - return vpnGateway, expected, nil - } - - if len(vpnGateway.VPCAttachments) == 0 { - // No attachments, we're detached - return vpnGateway, "detached", nil - } - - return vpnGateway, *vpnGateway.VPCAttachments[0].State, nil - } + var start time.Time + return func() (interface{}, string, error) { + if start.IsZero() { + start = time.Now() + } + + resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ + VPNGatewayIDs: []string{id}, + }) + if err != nil { + if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnGatewayID.NotFound" { + resp = nil + } else { + log.Printf("[ERROR] Error on VpnGatewayStateRefresh: %s", err) + return nil, "", err + } + } + + if resp == nil { + // Sometimes AWS just has consistency issues and doesn't see + // our instance yet. Return an empty state. + return nil, "", nil + } + + vpnGateway := &resp.VPNGateways[0] + + if time.Now().Sub(start) > 10*time.Second { + return vpnGateway, expected, nil + } + + if len(vpnGateway.VPCAttachments) == 0 { + // No attachments, we're detached + return vpnGateway, "detached", nil + } + + return vpnGateway, *vpnGateway.VPCAttachments[0].State, nil + } } diff --git a/builtin/providers/aws/resource_aws_vpn_gateway_test.go b/builtin/providers/aws/resource_aws_vpn_gateway_test.go index 33d54d0ae1..2d3edbe3bf 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway_test.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/aws-sdk-go/aws" - "github.com/hashicorp/aws-sdk-go/gen/ec2" + "github.com/hashicorp/aws-sdk-go/aws" + "github.com/hashicorp/aws-sdk-go/gen/ec2" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" ) @@ -115,7 +115,7 @@ func TestAccVpnGateway_tags(t *testing.T) { } func testAccCheckVpnGatewayDestroy(s *terraform.State) error { - ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn + ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn for _, rs := range s.RootModule().Resources { if rs.Type != "aws_vpn_gateway" { @@ -124,8 +124,8 @@ func testAccCheckVpnGatewayDestroy(s *terraform.State) error { // Try to find the resource resp, err := ec2conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ - VPNGatewayIDs: []string{rs.Primary.ID}, - }) + VPNGatewayIDs: []string{rs.Primary.ID}, + }) if err == nil { if len(resp.VPNGateways) > 0 { return fmt.Errorf("still exists") @@ -158,10 +158,10 @@ func testAccCheckVpnGatewayExists(n string, ig *ec2.VPNGateway) resource.TestChe return fmt.Errorf("No ID is set") } - ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn + ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn resp, err := ec2conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ - VPNGatewayIDs: []string{rs.Primary.ID}, - }) + VPNGatewayIDs: []string{rs.Primary.ID}, + }) if err != nil { return err } From 0900452113a24bb607a2c532c2aeb2104e908960 Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Tue, 10 Mar 2015 10:32:49 +1000 Subject: [PATCH 09/12] Remove type parameter from vpn_gateway docs --- website/source/docs/providers/aws/r/vpn_gateway.html.markdown | 1 - 1 file changed, 1 deletion(-) diff --git a/website/source/docs/providers/aws/r/vpn_gateway.html.markdown b/website/source/docs/providers/aws/r/vpn_gateway.html.markdown index 7f72d6ebac..b64000ce55 100644 --- a/website/source/docs/providers/aws/r/vpn_gateway.html.markdown +++ b/website/source/docs/providers/aws/r/vpn_gateway.html.markdown @@ -27,7 +27,6 @@ resource "aws_vpn_gateway" "vpn_gw" { The following arguments are supported: * `vpc_id` - (Required) The VPC ID to create in. -* `type` - (Optional) The type of VPN connection this virtual private gateway supports. * `availability_zone` - (Optional) The Availability Zone for the virtual private gateway. * `tags` - (Optional) A mapping of tags to assign to the resource. From cfd8d913bdffde7ea7536e53f474303227d3498e Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Thu, 12 Mar 2015 08:13:39 +1000 Subject: [PATCH 10/12] Make vpnGatewayStateRefreshFunc private --- builtin/providers/aws/resource_aws_vpn_gateway.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/builtin/providers/aws/resource_aws_vpn_gateway.go b/builtin/providers/aws/resource_aws_vpn_gateway.go index e00472576d..d6ffcef97f 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway.go @@ -62,7 +62,7 @@ func resourceAwsVpnGatewayCreate(d *schema.ResourceData, meta interface{}) error func resourceAwsVpnGatewayRead(d *schema.ResourceData, meta interface{}) error { ec2conn := meta.(*AWSClient).awsEC2conn - vpnGatewayRaw, _, err := VpnGatewayStateRefreshFunc(ec2conn, d.Id())() + vpnGatewayRaw, _, err := vpnGatewayStateRefreshFunc(ec2conn, d.Id())() if err != nil { return err } @@ -249,8 +249,8 @@ func resourceAwsVpnGatewayDetach(d *schema.ResourceData, meta interface{}) error return nil } -// VpnGatewayStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch a VPNGateway. -func VpnGatewayStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { +// vpnGatewayStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch a VPNGateway. +func vpnGatewayStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { return func() (interface{}, string, error) { resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ VPNGatewayIDs: []string{id}, From 7d5f7cbf2022a63901862a689586d45acfbf67c6 Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Thu, 12 Mar 2015 08:16:22 +1000 Subject: [PATCH 11/12] Update changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8470f90b69..c9134c82b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ FEATURES: * **New provider: `dme` (DNSMadeEasy)** [GH-855] * **New command: `taint`** - Manually mark a resource as tainted, causing a destroy and recreate on the next plan/apply. + * **New resource: `aws_vpn_gateway`** [GH-1137] * **Self-variables** can be used to reference the current resource's attributes within a provisioner. Ex. `${self.private_ip_address}` [GH-1033] * **Continous state** saving during `terraform apply`. The state file is From 8ebbaf550cd4033ce575c331026400d0c1e7decf Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Thu, 19 Mar 2015 11:14:41 +1000 Subject: [PATCH 12/12] Fixes for goamz removal. --- .../providers/aws/resource_aws_vpn_gateway.go | 16 ++++++++-------- .../aws/resource_aws_vpn_gateway_test.go | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/builtin/providers/aws/resource_aws_vpn_gateway.go b/builtin/providers/aws/resource_aws_vpn_gateway.go index d6ffcef97f..b6ecba5813 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway.go @@ -36,7 +36,7 @@ func resourceAwsVpnGateway() *schema.Resource { } func resourceAwsVpnGatewayCreate(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn + ec2conn := meta.(*AWSClient).ec2conn createOpts := &ec2.CreateVPNGatewayRequest{ AvailabilityZone: aws.String(d.Get("availability_zone").(string)), @@ -60,7 +60,7 @@ func resourceAwsVpnGatewayCreate(d *schema.ResourceData, meta interface{}) error } func resourceAwsVpnGatewayRead(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn + ec2conn := meta.(*AWSClient).ec2conn vpnGatewayRaw, _, err := vpnGatewayStateRefreshFunc(ec2conn, d.Id())() if err != nil { @@ -80,7 +80,7 @@ func resourceAwsVpnGatewayRead(d *schema.ResourceData, meta interface{}) error { d.Set("vpc_id", vpnGateway.VPCAttachments[0].VPCID) } d.Set("availability_zone", vpnGateway.AvailabilityZone) - d.Set("tags", tagsToMapSDK(vpnGateway.Tags)) + d.Set("tags", tagsToMap(vpnGateway.Tags)) return nil } @@ -98,9 +98,9 @@ func resourceAwsVpnGatewayUpdate(d *schema.ResourceData, meta interface{}) error } } - ec2conn := meta.(*AWSClient).awsEC2conn + ec2conn := meta.(*AWSClient).ec2conn - if err := setTagsSDK(ec2conn, d); err != nil { + if err := setTags(ec2conn, d); err != nil { return err } @@ -110,7 +110,7 @@ func resourceAwsVpnGatewayUpdate(d *schema.ResourceData, meta interface{}) error } func resourceAwsVpnGatewayDelete(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn + ec2conn := meta.(*AWSClient).ec2conn // Detach if it is attached if err := resourceAwsVpnGatewayDetach(d, meta); err != nil { @@ -144,7 +144,7 @@ func resourceAwsVpnGatewayDelete(d *schema.ResourceData, meta interface{}) error } func resourceAwsVpnGatewayAttach(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn + ec2conn := meta.(*AWSClient).ec2conn if d.Get("vpc_id").(string) == "" { log.Printf( @@ -189,7 +189,7 @@ func resourceAwsVpnGatewayAttach(d *schema.ResourceData, meta interface{}) error } func resourceAwsVpnGatewayDetach(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn + ec2conn := meta.(*AWSClient).ec2conn // Get the old VPC ID to detach from vpcID, _ := d.GetChange("vpc_id") diff --git a/builtin/providers/aws/resource_aws_vpn_gateway_test.go b/builtin/providers/aws/resource_aws_vpn_gateway_test.go index 2d3edbe3bf..21ccb980c4 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway_test.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway_test.go @@ -98,7 +98,7 @@ func TestAccVpnGateway_tags(t *testing.T) { Config: testAccCheckVpnGatewayConfigTags, Check: resource.ComposeTestCheckFunc( testAccCheckVpnGatewayExists("aws_vpn_gateway.foo", &v), - testAccCheckTagsSDK(&v.Tags, "foo", "bar"), + testAccCheckTags(&v.Tags, "foo", "bar"), ), }, @@ -106,8 +106,8 @@ func TestAccVpnGateway_tags(t *testing.T) { Config: testAccCheckVpnGatewayConfigTagsUpdate, Check: resource.ComposeTestCheckFunc( testAccCheckVpnGatewayExists("aws_vpn_gateway.foo", &v), - testAccCheckTagsSDK(&v.Tags, "foo", ""), - testAccCheckTagsSDK(&v.Tags, "bar", "baz"), + testAccCheckTags(&v.Tags, "foo", ""), + testAccCheckTags(&v.Tags, "bar", "baz"), ), }, }, @@ -115,7 +115,7 @@ func TestAccVpnGateway_tags(t *testing.T) { } func testAccCheckVpnGatewayDestroy(s *terraform.State) error { - ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn + ec2conn := testAccProvider.Meta().(*AWSClient).ec2conn for _, rs := range s.RootModule().Resources { if rs.Type != "aws_vpn_gateway" { @@ -158,7 +158,7 @@ func testAccCheckVpnGatewayExists(n string, ig *ec2.VPNGateway) resource.TestChe return fmt.Errorf("No ID is set") } - ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn + ec2conn := testAccProvider.Meta().(*AWSClient).ec2conn resp, err := ec2conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ VPNGatewayIDs: []string{rs.Primary.ID}, })