Skip to content

Commit

Permalink
provider/aws: Implement IPV6 Support for ec2 / VPC (hashicorp#10538)
Browse files Browse the repository at this point in the history
* provider/aws: Add support for IPV6 enabled VPC

```
% make testacc TEST=./builtin/providers/aws TESTARGS='-run=TestAccAWSVpc'
==> Checking that code complies with gofmt requirements...
go generate $(go list ./... | grep -v /terraform/vendor/)
2016/12/09 14:07:31 Generated command/internal_plugin_list.go
TF_ACC=1 go test ./builtin/providers/aws -v -run=TestAccAWSVpc -timeout 120m
=== RUN   TestAccAWSVpc_importBasic
--- PASS: TestAccAWSVpc_importBasic (43.03s)
=== RUN   TestAccAWSVpc_basic
--- PASS: TestAccAWSVpc_basic (36.32s)
=== RUN   TestAccAWSVpc_enableIpv6
--- PASS: TestAccAWSVpc_enableIpv6 (29.37s)
=== RUN   TestAccAWSVpc_dedicatedTenancy
--- PASS: TestAccAWSVpc_dedicatedTenancy (36.63s)
=== RUN   TestAccAWSVpc_tags
--- PASS: TestAccAWSVpc_tags (67.54s)
=== RUN   TestAccAWSVpc_update
--- PASS: TestAccAWSVpc_update (66.16s)
=== RUN   TestAccAWSVpc_bothDnsOptionsSet
--- PASS: TestAccAWSVpc_bothDnsOptionsSet (16.82s)
=== RUN   TestAccAWSVpc_DisabledDnsSupport
--- PASS: TestAccAWSVpc_DisabledDnsSupport (36.52s)
=== RUN   TestAccAWSVpc_classiclinkOptionSet
--- PASS: TestAccAWSVpc_classiclinkOptionSet (38.13s)
PASS
ok  	github.com/hashicorp/terraform/builtin/providers/aws	739.543s
```

* provider/aws: New Resource: aws_egress_only_internet_gateway

```
make testacc TEST=./builtin/providers/aws TESTARGS='-run=TestAccAWSEgressOnlyInternetGateway_'
==> Checking that code complies with gofmt requirements...
go generate $(go list ./... | grep -v /terraform/vendor/)
2016/12/09 14:22:16 Generated command/internal_plugin_list.go
TF_ACC=1 go test ./builtin/providers/aws -v -run=TestAccAWSEgressOnlyInternetGateway_ -timeout 120m
=== RUN   TestAccAWSEgressOnlyInternetGateway_basic
--- PASS: TestAccAWSEgressOnlyInternetGateway_basic (32.67s)
PASS
ok  	github.com/hashicorp/terraform/builtin/providers/aws	32.692s
```

* provider/aws: Add IPV6 support to aws_subnet

```
% make testacc TEST=./builtin/providers/aws
% TESTARGS='-run=TestAccAWSSubnet_'
% 1 ↵ ✹ ✭
==> Checking that code complies with gofmt requirements...
go generate $(go list ./... | grep -v /terraform/vendor/)
2017/02/27 19:08:34 Generated command/internal_plugin_list.go
TF_ACC=1 go test ./builtin/providers/aws -v -run=TestAccAWSSubnet_
-timeout 120m
=== RUN   TestAccAWSSubnet_importBasic
--- PASS: TestAccAWSSubnet_importBasic (69.88s)
=== RUN   TestAccAWSSubnet_basic
--- PASS: TestAccAWSSubnet_basic (51.28s)
=== RUN   TestAccAWSSubnet_ipv6
--- PASS: TestAccAWSSubnet_ipv6 (90.39s)
PASS
ok      github.com/hashicorp/terraform/builtin/providers/aws211.574s
```

* provider/aws: Add support for running aws_instances with ipv6 addresses
  • Loading branch information
stack72 authored and Yann DEGAT committed Mar 13, 2017
1 parent bc1432e commit 88aef77
Show file tree
Hide file tree
Showing 14 changed files with 568 additions and 9 deletions.
1 change: 1 addition & 0 deletions builtin/providers/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ func Provider() terraform.ResourceProvider {
"aws_ecs_task_definition": resourceAwsEcsTaskDefinition(),
"aws_efs_file_system": resourceAwsEfsFileSystem(),
"aws_efs_mount_target": resourceAwsEfsMountTarget(),
"aws_egress_only_internet_gateway": resourceAwsEgressOnlyInternetGateway(),
"aws_eip": resourceAwsEip(),
"aws_eip_association": resourceAwsEipAssociation(),
"aws_elasticache_cluster": resourceAwsElasticacheCluster(),
Expand Down
126 changes: 126 additions & 0 deletions builtin/providers/aws/resource_aws_egress_only_internet_gateway.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
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/errwrap"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsEgressOnlyInternetGateway() *schema.Resource {
return &schema.Resource{
Create: resourceAwsEgressOnlyInternetGatewayCreate,
Read: resourceAwsEgressOnlyInternetGatewayRead,
Delete: resourceAwsEgressOnlyInternetGatewayDelete,

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

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

resp, err := conn.CreateEgressOnlyInternetGateway(&ec2.CreateEgressOnlyInternetGatewayInput{
VpcId: aws.String(d.Get("vpc_id").(string)),
})
if err != nil {
return fmt.Errorf("Error creating egress internet gateway: %s", err)
}

d.SetId(*resp.EgressOnlyInternetGateway.EgressOnlyInternetGatewayId)

err = resource.Retry(5*time.Minute, func() *resource.RetryError {
igRaw, _, err := EIGWStateRefreshFunc(conn, d.Id())()
if igRaw != nil {
return nil
}
if err == nil {
return resource.RetryableError(err)
} else {
return resource.NonRetryableError(err)
}
})

if err != nil {
return errwrap.Wrapf("{{err}}", err)
}

return resourceAwsEgressOnlyInternetGatewayRead(d, meta)
}

func EIGWStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
resp, err := conn.DescribeEgressOnlyInternetGateways(&ec2.DescribeEgressOnlyInternetGatewaysInput{
EgressOnlyInternetGatewayIds: []*string{aws.String(id)},
})
if err != nil {
ec2err, ok := err.(awserr.Error)
if ok && ec2err.Code() == "InvalidEgressInternetGatewayID.NotFound" {
resp = nil
} else {
log.Printf("[ERROR] Error on EIGWStateRefreshFunc: %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
}

ig := resp.EgressOnlyInternetGateways[0]
return ig, "available", nil
}
}

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

resp, err := conn.DescribeEgressOnlyInternetGateways(&ec2.DescribeEgressOnlyInternetGatewaysInput{
EgressOnlyInternetGatewayIds: []*string{aws.String(d.Id())},
})
if err != nil {
return fmt.Errorf("Error describing egress internet gateway: %s", err)
}

found := false
for _, igw := range resp.EgressOnlyInternetGateways {
if *igw.EgressOnlyInternetGatewayId == d.Id() {
found = true
}
}

if !found {
log.Printf("[Error] Cannot find Egress Only Internet Gateway: %q", d.Id())
d.SetId("")
return nil
}

return nil
}

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

_, err := conn.DeleteEgressOnlyInternetGateway(&ec2.DeleteEgressOnlyInternetGatewayInput{
EgressOnlyInternetGatewayId: aws.String(d.Id()),
})
if err != nil {
return fmt.Errorf("Error deleting egress internet gateway: %s", err)
}

return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package aws

import (
"fmt"
"testing"

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

func TestAccAWSEgressOnlyInternetGateway_basic(t *testing.T) {
var igw ec2.EgressOnlyInternetGateway
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSEgressOnlyInternetGatewayDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSEgressOnlyInternetGatewayConfig_basic,
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSEgressOnlyInternetGatewayExists("aws_egress_only_internet_gateway.foo", &igw),
),
},
},
})
}

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

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

describe, err := conn.DescribeEgressOnlyInternetGateways(&ec2.DescribeEgressOnlyInternetGatewaysInput{
EgressOnlyInternetGatewayIds: []*string{aws.String(rs.Primary.ID)},
})

if err == nil {
if len(describe.EgressOnlyInternetGateways) != 0 &&
*describe.EgressOnlyInternetGateways[0].EgressOnlyInternetGatewayId == rs.Primary.ID {
return fmt.Errorf("Egress Only Internet Gateway %q still exists", rs.Primary.ID)
}
}

return nil
}

return nil
}

func testAccCheckAWSEgressOnlyInternetGatewayExists(n string, igw *ec2.EgressOnlyInternetGateway) 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 Egress Only IGW ID is set")
}

conn := testAccProvider.Meta().(*AWSClient).ec2conn
resp, err := conn.DescribeEgressOnlyInternetGateways(&ec2.DescribeEgressOnlyInternetGatewaysInput{
EgressOnlyInternetGatewayIds: []*string{aws.String(rs.Primary.ID)},
})
if err != nil {
return err
}
if len(resp.EgressOnlyInternetGateways) == 0 {
return fmt.Errorf("Egress Only IGW not found")
}

*igw = *resp.EgressOnlyInternetGateways[0]

return nil
}
}

const testAccAWSEgressOnlyInternetGatewayConfig_basic = `
resource "aws_vpc" "foo" {
cidr_block = "10.1.0.0/16"
assign_generated_ipv6_cidr_block = true
}
resource "aws_egress_only_internet_gateway" "foo" {
vpc_id = "${aws_vpc.foo.id}"
}
`
41 changes: 41 additions & 0 deletions builtin/providers/aws/resource_aws_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,23 @@ func resourceAwsInstance() *schema.Resource {
Optional: true,
},

"ipv6_address_count": {
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
},

"ipv6_addresses": {
Type: schema.TypeList,
Optional: true,
Computed: true,
ForceNew: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
ConflictsWith: []string{"ipv6_address_count"},
},

"tenancy": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -362,6 +379,23 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
UserData: instanceOpts.UserData64,
}

if v, ok := d.GetOk("ipv6_address_count"); ok {
runOpts.Ipv6AddressCount = aws.Int64(int64(v.(int)))
}

if v, ok := d.GetOk("ipv6_addresses"); ok {
ipv6Addresses := make([]*ec2.InstanceIpv6Address, len(v.([]interface{})))
for _, address := range v.([]interface{}) {
ipv6Address := &ec2.InstanceIpv6Address{
Ipv6Address: aws.String(address.(string)),
}

ipv6Addresses = append(ipv6Addresses, ipv6Address)
}

runOpts.Ipv6Addresses = ipv6Addresses
}

// Create the instance
log.Printf("[DEBUG] Run configuration: %s", runOpts)

Expand Down Expand Up @@ -500,6 +534,13 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
d.Set("subnet_id", ni.SubnetId)
d.Set("network_interface_id", ni.NetworkInterfaceId)
d.Set("associate_public_ip_address", ni.Association != nil)
d.Set("ipv6_address_count", len(ni.Ipv6Addresses))

var ipv6Addresses []string
for _, address := range ni.Ipv6Addresses {
ipv6Addresses = append(ipv6Addresses, *address.Ipv6Address)
}
d.Set("ipv6_addresses", ipv6Addresses)
}
}
} else {
Expand Down
54 changes: 54 additions & 0 deletions builtin/providers/aws/resource_aws_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,29 @@ func TestAccAWSInstance_vpc(t *testing.T) {
})
}

func TestAccAWSInstance_ipv6_supportAddressCount(t *testing.T) {
var v ec2.Instance

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckInstanceDestroy,
Steps: []resource.TestStep{
{
Config: testAccInstanceConfigIpv6Support,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists(
"aws_instance.foo", &v),
resource.TestCheckResourceAttr(
"aws_instance.foo",
"ipv6_address_count",
"1"),
),
},
},
})
}

func TestAccAWSInstance_multipleRegions(t *testing.T) {
var v ec2.Instance

Expand Down Expand Up @@ -1123,6 +1146,37 @@ resource "aws_instance" "foo" {
}
`

const testAccInstanceConfigIpv6Support = `
resource "aws_vpc" "foo" {
cidr_block = "10.1.0.0/16"
assign_generated_ipv6_cidr_block = true
tags {
Name = "tf-ipv6-instance-acc-test"
}
}
resource "aws_subnet" "foo" {
cidr_block = "10.1.1.0/24"
vpc_id = "${aws_vpc.foo.id}"
ipv6_cidr_block = "${cidrsubnet(aws_vpc.foo.ipv6_cidr_block, 8, 1)}"
tags {
Name = "tf-ipv6-instance-acc-test"
}
}
resource "aws_instance" "foo" {
# us-west-2
ami = "ami-c5eabbf5"
instance_type = "t2.micro"
subnet_id = "${aws_subnet.foo.id}"
ipv6_address_count = 1
tags {
Name = "tf-ipv6-instance-acc-test"
}
}
`

const testAccInstanceConfigMultipleRegions = `
provider "aws" {
alias = "west"
Expand Down
Loading

0 comments on commit 88aef77

Please sign in to comment.