Skip to content

Commit

Permalink
provider/aws: Add aws_nat_gateway Resource
Browse files Browse the repository at this point in the history
  • Loading branch information
jen20 committed Dec 18, 2015
1 parent 5d91069 commit 61acbee
Show file tree
Hide file tree
Showing 9 changed files with 436 additions and 8 deletions.
1 change: 1 addition & 0 deletions builtin/providers/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ func Provider() terraform.ResourceProvider {
"aws_launch_configuration": resourceAwsLaunchConfiguration(),
"aws_lb_cookie_stickiness_policy": resourceAwsLBCookieStickinessPolicy(),
"aws_main_route_table_association": resourceAwsMainRouteTableAssociation(),
"aws_nat_gateway": resourceAwsNatGateway(),
"aws_network_acl": resourceAwsNetworkAcl(),
"aws_network_interface": resourceAwsNetworkInterface(),
"aws_opsworks_stack": resourceAwsOpsworksStack(),
Expand Down
179 changes: 179 additions & 0 deletions builtin/providers/aws/resource_aws_nat_gateway.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
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/resource"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsNatGateway() *schema.Resource {
return &schema.Resource{
Create: resourceAwsNatGatewayCreate,
Read: resourceAwsNatGatewayRead,
Delete: resourceAwsNatGatewayDelete,

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

"subnet_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"network_interface_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},

"private_ip": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},

"public_ip": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
}
}

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

// Create the NAT Gateway
createOpts := &ec2.CreateNatGatewayInput{
AllocationId: aws.String(d.Get("allocation_id").(string)),
SubnetId: aws.String(d.Get("subnet_id").(string)),
}

log.Printf("[DEBUG] Create NAT Gateway: %s", *createOpts)
natResp, err := conn.CreateNatGateway(createOpts)
if err != nil {
return fmt.Errorf("Error creating NAT Gateway: %s", err)
}

// Get the ID and store it
ng := natResp.NatGateway
d.SetId(*ng.NatGatewayId)
log.Printf("[INFO] NAT Gateway ID: %s", d.Id())

// Wait for the NAT Gateway to become available
log.Printf("[DEBUG] Waiting for NAT Gateway (%s) to become available", d.Id())
stateConf := &resource.StateChangeConf{
Pending: []string{"pending"},
Target: "available",
Refresh: NGStateRefreshFunc(conn, d.Id()),
Timeout: 10 * time.Minute,
}

if _, err := stateConf.WaitForState(); err != nil {
return fmt.Errorf("Error waiting for NAT Gateway (%s) to become available: %s", d.Id(), err)
}

// Update our attributes and return
return resourceAwsNatGatewayRead(d, meta)
}

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

// Refresh the NAT Gateway state
ngRaw, _, err := NGStateRefreshFunc(conn, d.Id())()
if err != nil {
return err
}
if ngRaw == nil {
d.SetId("")
return nil
}

// Set NAT Gateway attributes
ng := ngRaw.(*ec2.NatGateway)
address := ng.NatGatewayAddresses[0]
d.Set("network_interface_id", address.NetworkInterfaceId)
d.Set("private_ip", address.PrivateIp)
d.Set("public_ip", address.PublicIp)

return nil
}

func resourceAwsNatGatewayDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn
deleteOpts := &ec2.DeleteNatGatewayInput{
NatGatewayId: aws.String(d.Id()),
}
log.Printf("[INFO] Deleting NAT Gateway: %s", d.Id())

_, err := conn.DeleteNatGateway(deleteOpts)
if err != nil {
ec2err, ok := err.(awserr.Error)
if !ok {
return err
}

if ec2err.Code() == "NatGatewayNotFound" {
return nil
}

return err
}

stateConf := &resource.StateChangeConf{
Pending: []string{"deleting"},
Target: "deleted",
Refresh: NGStateRefreshFunc(conn, d.Id()),
Timeout: 30 * time.Minute,
Delay: 10 * time.Second,
MinTimeout: 10 * time.Second,
}

_, stateErr := stateConf.WaitForState()
if stateErr != nil {
return fmt.Errorf("Error waiting for NAT Gateway (%s) to delete: %s", d.Id(), err)
}

return nil
}

// NGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
// a NAT Gateway.
func NGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
opts := &ec2.DescribeNatGatewaysInput{
NatGatewayIds: []*string{aws.String(id)},
}
resp, err := conn.DescribeNatGateways(opts)
if err != nil {
if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "NatGatewayNotFound" {
resp = nil
} else {
log.Printf("Error on NGStateRefresh: %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
}

ng := resp.NatGateways[0]
return ng, *ng.State, nil
}
}
154 changes: 154 additions & 0 deletions builtin/providers/aws/resource_aws_nat_gateway_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package aws

import (
"fmt"
"strings"
"testing"

"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/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSNatGateway_basic(t *testing.T) {
var natGateway ec2.NatGateway

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckNatGatewayDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccNatGatewayConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckNatGatewayExists("aws_nat_gateway.gateway", &natGateway),
),
},
},
})
}

func testAccCheckNatGatewayDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn

for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_nat_gateway" {
continue
}

// Try to find the resource
resp, err := conn.DescribeNatGateways(&ec2.DescribeNatGatewaysInput{
NatGatewayIds: []*string{aws.String(rs.Primary.ID)},
})
if err == nil {
if len(resp.NatGateways) > 0 && strings.ToLower(*resp.NatGateways[0].State) != "deleted" {
return fmt.Errorf("still exists")
}

return nil
}

// Verify the error is what we want
ec2err, ok := err.(awserr.Error)
if !ok {
return err
}
if ec2err.Code() != "NatGatewayNotFound" {
return err
}
}

return nil
}

func testAccCheckNatGatewayExists(n string, ng *ec2.NatGateway) 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).ec2conn
resp, err := conn.DescribeNatGateways(&ec2.DescribeNatGatewaysInput{
NatGatewayIds: []*string{aws.String(rs.Primary.ID)},
})
if err != nil {
return err
}
if len(resp.NatGateways) == 0 {
return fmt.Errorf("NatGateway not found")
}

*ng = *resp.NatGateways[0]

return nil
}
}

const testAccNatGatewayConfig = `
resource "aws_vpc" "vpc" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "private" {
vpc_id = "${aws_vpc.vpc.id}"
cidr_block = "10.0.1.0/24"
map_public_ip_on_launch = false
}
resource "aws_subnet" "public" {
vpc_id = "${aws_vpc.vpc.id}"
cidr_block = "10.0.2.0/24"
map_public_ip_on_launch = true
}
resource "aws_internet_gateway" "gw" {
vpc_id = "${aws_vpc.vpc.id}"
}
resource "aws_eip" "nat_gateway" {
vpc = true
}
// Actual SUT
resource "aws_nat_gateway" "gateway" {
allocation_id = "${aws_eip.nat_gateway.id}"
subnet_id = "${aws_subnet.public.id}"
depends_on = ["aws_internet_gateway.gw"]
}
resource "aws_route_table" "private" {
vpc_id = "${aws_vpc.vpc.id}"
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = "${aws_nat_gateway.gateway.id}"
}
}
resource "aws_route_table_association" "private" {
subnet_id = "${aws_subnet.private.id}"
route_table_id = "${aws_route_table.private.id}"
}
resource "aws_route_table" "public" {
vpc_id = "${aws_vpc.vpc.id}"
route {
cidr_block = "0.0.0.0/0"
gateway_id = "${aws_internet_gateway.gw.id}"
}
}
resource "aws_route_table_association" "public" {
subnet_id = "${aws_subnet.public.id}"
route_table_id = "${aws_route_table.public.id}"
}
`
Loading

0 comments on commit 61acbee

Please sign in to comment.