From 88aef77da6189b8e9e8c9ec2a4a16aa0e991a882 Mon Sep 17 00:00:00 2001 From: Paul Stack Date: Wed, 1 Mar 2017 16:16:59 +0000 Subject: [PATCH] provider/aws: Implement IPV6 Support for ec2 / VPC (#10538) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- builtin/providers/aws/provider.go | 1 + ...source_aws_egress_only_internet_gateway.go | 126 ++++++++++++++++++ ...e_aws_egress_only_internet_gateway_test.go | 92 +++++++++++++ .../providers/aws/resource_aws_instance.go | 41 ++++++ .../aws/resource_aws_instance_test.go | 54 ++++++++ builtin/providers/aws/resource_aws_subnet.go | 53 +++++++- .../providers/aws/resource_aws_subnet_test.go | 87 +++++++++++- builtin/providers/aws/resource_aws_vpc.go | 32 ++++- .../providers/aws/resource_aws_vpc_test.go | 32 +++++ ...egress_only_internet_gateway.html.markdown | 39 ++++++ .../providers/aws/r/instance.html.markdown | 2 + .../docs/providers/aws/r/subnet.html.markdown | 7 +- .../docs/providers/aws/r/vpc.html.markdown | 5 + website/source/layouts/aws.erb | 6 +- 14 files changed, 568 insertions(+), 9 deletions(-) create mode 100644 builtin/providers/aws/resource_aws_egress_only_internet_gateway.go create mode 100644 builtin/providers/aws/resource_aws_egress_only_internet_gateway_test.go create mode 100644 website/source/docs/providers/aws/r/egress_only_internet_gateway.html.markdown diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index c9a77f6cca4f..d6b156bb0a61 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -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(), diff --git a/builtin/providers/aws/resource_aws_egress_only_internet_gateway.go b/builtin/providers/aws/resource_aws_egress_only_internet_gateway.go new file mode 100644 index 000000000000..14a01e72efa1 --- /dev/null +++ b/builtin/providers/aws/resource_aws_egress_only_internet_gateway.go @@ -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 +} diff --git a/builtin/providers/aws/resource_aws_egress_only_internet_gateway_test.go b/builtin/providers/aws/resource_aws_egress_only_internet_gateway_test.go new file mode 100644 index 000000000000..b0a4b653cdb7 --- /dev/null +++ b/builtin/providers/aws/resource_aws_egress_only_internet_gateway_test.go @@ -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}" +} +` diff --git a/builtin/providers/aws/resource_aws_instance.go b/builtin/providers/aws/resource_aws_instance.go index ce421fb3ddb7..061c619badd0 100644 --- a/builtin/providers/aws/resource_aws_instance.go +++ b/builtin/providers/aws/resource_aws_instance.go @@ -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, @@ -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) @@ -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 { diff --git a/builtin/providers/aws/resource_aws_instance_test.go b/builtin/providers/aws/resource_aws_instance_test.go index e7941eb170fd..e6e2d62efb07 100644 --- a/builtin/providers/aws/resource_aws_instance_test.go +++ b/builtin/providers/aws/resource_aws_instance_test.go @@ -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 @@ -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" diff --git a/builtin/providers/aws/resource_aws_subnet.go b/builtin/providers/aws/resource_aws_subnet.go index b9d6c42ed0e5..68bf1e0f4f11 100644 --- a/builtin/providers/aws/resource_aws_subnet.go +++ b/builtin/providers/aws/resource_aws_subnet.go @@ -23,31 +23,48 @@ func resourceAwsSubnet() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "vpc_id": &schema.Schema{ + "vpc_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "cidr_block": &schema.Schema{ + "cidr_block": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "availability_zone": &schema.Schema{ + "ipv6_cidr_block": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "availability_zone": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "map_public_ip_on_launch": &schema.Schema{ + "map_public_ip_on_launch": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "assign_ipv6_address_on_creation": { Type: schema.TypeBool, Optional: true, Default: false, }, + "ipv6_cidr_block_association_id": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tagsSchema(), }, } @@ -62,6 +79,10 @@ func resourceAwsSubnetCreate(d *schema.ResourceData, meta interface{}) error { VpcId: aws.String(d.Get("vpc_id").(string)), } + if v, ok := d.GetOk("ipv6_cidr_block"); ok { + createOpts.Ipv6CidrBlock = aws.String(v.(string)) + } + var err error resp, err := conn.CreateSubnet(createOpts) @@ -119,6 +140,11 @@ func resourceAwsSubnetRead(d *schema.ResourceData, meta interface{}) error { d.Set("availability_zone", subnet.AvailabilityZone) d.Set("cidr_block", subnet.CidrBlock) d.Set("map_public_ip_on_launch", subnet.MapPublicIpOnLaunch) + d.Set("assign_ipv6_address_on_creation", subnet.AssignIpv6AddressOnCreation) + if subnet.Ipv6CidrBlockAssociationSet != nil { + d.Set("ipv6_cidr_block", subnet.Ipv6CidrBlockAssociationSet[0].Ipv6CidrBlock) + d.Set("ipv6_cidr_block_association_id", subnet.Ipv6CidrBlockAssociationSet[0].AssociationId) + } d.Set("tags", tagsToMap(subnet.Tags)) return nil @@ -135,6 +161,25 @@ func resourceAwsSubnetUpdate(d *schema.ResourceData, meta interface{}) error { d.SetPartial("tags") } + if d.HasChange("assign_ipv6_address_on_creation") { + modifyOpts := &ec2.ModifySubnetAttributeInput{ + SubnetId: aws.String(d.Id()), + AssignIpv6AddressOnCreation: &ec2.AttributeBooleanValue{ + Value: aws.Bool(d.Get("assign_ipv6_address_on_creation").(bool)), + }, + } + + log.Printf("[DEBUG] Subnet modify attributes: %#v", modifyOpts) + + _, err := conn.ModifySubnetAttribute(modifyOpts) + + if err != nil { + return err + } else { + d.SetPartial("assign_ipv6_address_on_creation") + } + } + if d.HasChange("map_public_ip_on_launch") { modifyOpts := &ec2.ModifySubnetAttributeInput{ SubnetId: aws.String(d.Id()), diff --git a/builtin/providers/aws/resource_aws_subnet_test.go b/builtin/providers/aws/resource_aws_subnet_test.go index 2a4e3259dc95..b86284fdb557 100644 --- a/builtin/providers/aws/resource_aws_subnet_test.go +++ b/builtin/providers/aws/resource_aws_subnet_test.go @@ -32,7 +32,7 @@ func TestAccAWSSubnet_basic(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckSubnetDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccSubnetConfig, Check: resource.ComposeTestCheckFunc( testAccCheckSubnetExists( @@ -44,6 +44,55 @@ func TestAccAWSSubnet_basic(t *testing.T) { }) } +func TestAccAWSSubnet_ipv6(t *testing.T) { + var v ec2.Subnet + + testCheck := func(*terraform.State) error { + if v.Ipv6CidrBlockAssociationSet == nil { + return fmt.Errorf("Expected IPV6 CIDR Block Association") + } + + if *v.AssignIpv6AddressOnCreation != true { + return fmt.Errorf("bad AssignIpv6AddressOnCreation: %t", *v.AssignIpv6AddressOnCreation) + } + + return nil + } + + testCheckUpdated := func(*terraform.State) error { + if *v.AssignIpv6AddressOnCreation != false { + return fmt.Errorf("bad AssignIpv6AddressOnCreation: %t", *v.AssignIpv6AddressOnCreation) + } + + return nil + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "aws_subnet.foo", + Providers: testAccProviders, + CheckDestroy: testAccCheckSubnetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSubnetConfigIpv6, + Check: resource.ComposeTestCheckFunc( + testAccCheckSubnetExists( + "aws_subnet.foo", &v), + testCheck, + ), + }, + { + Config: testAccSubnetConfigIpv6Updated, + Check: resource.ComposeTestCheckFunc( + testAccCheckSubnetExists( + "aws_subnet.foo", &v), + testCheckUpdated, + ), + }, + }, + }) +} + func testAccCheckSubnetDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).ec2conn @@ -119,3 +168,39 @@ resource "aws_subnet" "foo" { } } ` + +const testAccSubnetConfigIpv6 = ` +resource "aws_vpc" "foo" { + cidr_block = "10.10.0.0/16" + assign_generated_ipv6_cidr_block = true +} + +resource "aws_subnet" "foo" { + cidr_block = "10.10.1.0/24" + vpc_id = "${aws_vpc.foo.id}" + ipv6_cidr_block = "${cidrsubnet(aws_vpc.foo.ipv6_cidr_block, 8, 1)}" + map_public_ip_on_launch = true + assign_ipv6_address_on_creation = true + tags { + Name = "tf-subnet-acc-test" + } +} +` + +const testAccSubnetConfigIpv6Updated = ` +resource "aws_vpc" "foo" { + cidr_block = "10.10.0.0/16" + assign_generated_ipv6_cidr_block = true +} + +resource "aws_subnet" "foo" { + cidr_block = "10.10.1.0/24" + vpc_id = "${aws_vpc.foo.id}" + ipv6_cidr_block = "${cidrsubnet(aws_vpc.foo.ipv6_cidr_block, 8, 3)}" + map_public_ip_on_launch = true + assign_ipv6_address_on_creation = false + tags { + Name = "tf-subnet-acc-test" + } +} +` diff --git a/builtin/providers/aws/resource_aws_vpc.go b/builtin/providers/aws/resource_aws_vpc.go index 89afe67113f3..e2e9f83a7ca3 100644 --- a/builtin/providers/aws/resource_aws_vpc.go +++ b/builtin/providers/aws/resource_aws_vpc.go @@ -55,6 +55,13 @@ func resourceAwsVpc() *schema.Resource { Computed: true, }, + "assign_generated_ipv6_cidr_block": { + Type: schema.TypeBool, + ForceNew: true, + Optional: true, + Default: false, + }, + "main_route_table_id": { Type: schema.TypeString, Computed: true, @@ -80,6 +87,16 @@ func resourceAwsVpc() *schema.Resource { Computed: true, }, + "ipv6_association_id": { + Type: schema.TypeString, + Computed: true, + }, + + "ipv6_cidr_block": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tagsSchema(), }, } @@ -91,11 +108,14 @@ func resourceAwsVpcCreate(d *schema.ResourceData, meta interface{}) error { if v, ok := d.GetOk("instance_tenancy"); ok { instance_tenancy = v.(string) } + // Create the VPC createOpts := &ec2.CreateVpcInput{ - CidrBlock: aws.String(d.Get("cidr_block").(string)), - InstanceTenancy: aws.String(instance_tenancy), + CidrBlock: aws.String(d.Get("cidr_block").(string)), + InstanceTenancy: aws.String(instance_tenancy), + AmazonProvidedIpv6CidrBlock: aws.Bool(d.Get("assign_generated_ipv6_cidr_block").(bool)), } + log.Printf("[DEBUG] VPC create config: %#v", *createOpts) vpcResp, err := conn.CreateVpc(createOpts) if err != nil { @@ -154,6 +174,14 @@ func resourceAwsVpcRead(d *schema.ResourceData, meta interface{}) error { // Tags d.Set("tags", tagsToMap(vpc.Tags)) + if vpc.Ipv6CidrBlockAssociationSet != nil { + d.Set("assign_generated_ipv6_cidr_block", true) + d.Set("ipv6_association_id", vpc.Ipv6CidrBlockAssociationSet[0].AssociationId) + d.Set("ipv6_cidr_block", vpc.Ipv6CidrBlockAssociationSet[0].Ipv6CidrBlock) + } else { + d.Set("assign_generated_ipv6_cidr_block", false) + } + // Attributes attribute := "enableDnsSupport" DescribeAttrOpts := &ec2.DescribeVpcAttributeInput{ diff --git a/builtin/providers/aws/resource_aws_vpc_test.go b/builtin/providers/aws/resource_aws_vpc_test.go index 29fda784b8dd..44f672268f27 100644 --- a/builtin/providers/aws/resource_aws_vpc_test.go +++ b/builtin/providers/aws/resource_aws_vpc_test.go @@ -36,6 +36,31 @@ func TestAccAWSVpc_basic(t *testing.T) { }) } +func TestAccAWSVpc_enableIpv6(t *testing.T) { + var vpc ec2.Vpc + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVpcDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVpcConfigIpv6Enabled, + Check: resource.ComposeTestCheckFunc( + testAccCheckVpcExists("aws_vpc.foo", &vpc), + testAccCheckVpcCidr(&vpc, "10.1.0.0/16"), + resource.TestCheckResourceAttr( + "aws_vpc.foo", "cidr_block", "10.1.0.0/16"), + resource.TestCheckResourceAttrSet( + "aws_vpc.foo", "ipv6_association_id"), + resource.TestCheckResourceAttrSet( + "aws_vpc.foo", "ipv6_cidr_block"), + ), + }, + }, + }) +} + func TestAccAWSVpc_dedicatedTenancy(t *testing.T) { var vpc ec2.Vpc @@ -251,6 +276,13 @@ resource "aws_vpc" "foo" { } ` +const testAccVpcConfigIpv6Enabled = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" + assign_generated_ipv6_cidr_block = true +} +` + const testAccVpcConfigUpdate = ` resource "aws_vpc" "foo" { cidr_block = "10.1.0.0/16" diff --git a/website/source/docs/providers/aws/r/egress_only_internet_gateway.html.markdown b/website/source/docs/providers/aws/r/egress_only_internet_gateway.html.markdown new file mode 100644 index 000000000000..613a40315ba6 --- /dev/null +++ b/website/source/docs/providers/aws/r/egress_only_internet_gateway.html.markdown @@ -0,0 +1,39 @@ +--- +layout: "aws" +page_title: "AWS: aws_egress_only_internet_gateway" +sidebar_current: "docs-aws-resource-egress-only-internet-gateway" +description: |- + Provides a resource to create a VPC Egress Only Internet Gateway. +--- + +# aws\_egress\_only\_internet\_gateway + +[IPv6 only] Creates an egress-only Internet gateway for your VPC. +An egress-only Internet gateway is used to enable outbound communication +over IPv6 from instances in your VPC to the Internet, and prevents hosts +outside of your VPC from initiating an IPv6 connection with your instance. + +## Example Usage + +``` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" + assign_amazon_ipv6_cidr_block = true +} + +resource "aws_egress_only_internet_gateway" "foo" { + vpc_id = "${aws_vpc.foo.id}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `vpc_id` - (Required) The VPC ID to create in. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the Egress Only Internet Gateway. \ No newline at end of file diff --git a/website/source/docs/providers/aws/r/instance.html.markdown b/website/source/docs/providers/aws/r/instance.html.markdown index 9939a7ff296e..fe3491d626ea 100644 --- a/website/source/docs/providers/aws/r/instance.html.markdown +++ b/website/source/docs/providers/aws/r/instance.html.markdown @@ -77,6 +77,8 @@ instances. See [Shutdown Behavior](https://docs.aws.amazon.com/AWSEC2/latest/Use * `user_data` - (Optional) The user data to provide when launching the instance. * `iam_instance_profile` - (Optional) The IAM Instance Profile to launch the instance with. +* `ipv6_address_count`- (Optional) A number of IPv6 addresses to associate with the primary network interface. Amazon EC2 chooses the IPv6 addresses from the range of your subnet. +* `ipv6_addresses` - (Optional) Specify one or more IPv6 addresses from the range of the subnet to associate with the primary network interface * `tags` - (Optional) A mapping of tags to assign to the resource. * `root_block_device` - (Optional) Customize details about the root block device of the instance. See [Block Devices](#block-devices) below for details. diff --git a/website/source/docs/providers/aws/r/subnet.html.markdown b/website/source/docs/providers/aws/r/subnet.html.markdown index 44c95de4011a..f7457d8ee5e0 100644 --- a/website/source/docs/providers/aws/r/subnet.html.markdown +++ b/website/source/docs/providers/aws/r/subnet.html.markdown @@ -29,9 +29,14 @@ The following arguments are supported: * `availability_zone`- (Optional) The AZ for the subnet. * `cidr_block` - (Required) The CIDR block for the subnet. +* `ipv6_cidr_block` - (Optional) The IPv6 network range for the subnet, + in CIDR notation. The subnet size must use a /64 prefix length. * `map_public_ip_on_launch` - (Optional) Specify true to indicate that instances launched into the subnet should be assigned - a public IP address. + a public IP address. Default is `false`. +* `assign_ipv6_address_on_creation` - (Optional) Specify true to indicate + that network interfaces created in the specified subnet should be + assigned an IPv6 address. Default is `false` * `vpc_id` - (Required) The VPC ID. * `tags` - (Optional) A mapping of tags to assign to the resource. diff --git a/website/source/docs/providers/aws/r/vpc.html.markdown b/website/source/docs/providers/aws/r/vpc.html.markdown index bf06aeaeb25b..a05b57d35a7a 100644 --- a/website/source/docs/providers/aws/r/vpc.html.markdown +++ b/website/source/docs/providers/aws/r/vpc.html.markdown @@ -44,6 +44,9 @@ The following arguments are supported: * `enable_classiclink` - (Optional) A boolean flag to enable/disable ClassicLink for the VPC. Only valid in regions and accounts that support EC2 Classic. See the [ClassicLink documentation][1] for more information. Defaults false. +* `assign_generated_ipv6_cidr_block` - (Optional) Requests an Amazon-provided IPv6 CIDR +block with a /56 prefix length for the VPC. You cannot specify the range of IP addresses, or +the size of the CIDR block. Default is `false`. * `tags` - (Optional) A mapping of tags to assign to the resource. ## Attributes Reference @@ -62,6 +65,8 @@ The following attributes are exported: * `default_network_acl_id` - The ID of the network ACL created by default on VPC creation * `default_security_group_id` - The ID of the security group created by default on VPC creation * `default_route_table_id` - The ID of the route table created by default on VPC creation +* `ipv6_association_id` - The association ID for the IPv6 CIDR block. +* `ipv6_cidr_block` - The IPv6 CIDR block. [1]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/vpc-classiclink.html diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 2fc57b173cb0..1b868743c3c8 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -1209,7 +1209,7 @@ - > + > VPC Resources