From c006eb226c56c9978ee4898ecb7021f28b50b29a Mon Sep 17 00:00:00 2001 From: stack72 Date: Thu, 31 Aug 2017 16:35:36 +0100 Subject: [PATCH 1/7] New Resource: aws_vpc_associate_cidr_block Adds the ability to associate extra IPv4 or IPv6 CIDR Blocks with a VPC. In order to avoid getting into the same issue as security_group and security_group_rule, we added a diffSuppressFunc that stops people enabling `ipv6` for an existant AWS VPC. We added a note to the documentation to talk about this ``` % make testacc TEST=./aws TESTARGS='-run=TestAccAWSVpc_' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -run=TestAccAWSVpc_ -timeout 120m === RUN TestAccAWSVpc_importBasic --- PASS: TestAccAWSVpc_importBasic (54.16s) === RUN TestAccAWSVpc_basic --- PASS: TestAccAWSVpc_basic (45.26s) === RUN TestAccAWSVpc_enableIpv6 --- PASS: TestAccAWSVpc_enableIpv6 (87.40s) === RUN TestAccAWSVpc_dedicatedTenancy --- PASS: TestAccAWSVpc_dedicatedTenancy (45.51s) === RUN TestAccAWSVpc_tags --- PASS: TestAccAWSVpc_tags (84.80s) === RUN TestAccAWSVpc_update --- PASS: TestAccAWSVpc_update (97.12s) === RUN TestAccAWSVpc_bothDnsOptionsSet --- PASS: TestAccAWSVpc_bothDnsOptionsSet (19.82s) === RUN TestAccAWSVpc_DisabledDnsSupport --- PASS: TestAccAWSVpc_DisabledDnsSupport (44.93s) === RUN TestAccAWSVpc_classiclinkOptionSet --- PASS: TestAccAWSVpc_classiclinkOptionSet (46.18s) === RUN TestAccAWSVpc_classiclinkDnsSupportOptionSet --- PASS: TestAccAWSVpc_classiclinkDnsSupportOptionSet (47.51s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 572.738s ``` ``` % make testacc TEST=./aws TESTARGS='-run=TestAccAWSVpcAssociat' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -run=TestAccAWSVpcAssociat -timeout 120m === RUN TestAccAWSVpcAssociateIpv4CidrBlock --- PASS: TestAccAWSVpcAssociateIpv4CidrBlock (51.48s) === RUN TestAccAWSVpcAssociateIpv6CidrBlock --- PASS: TestAccAWSVpcAssociateIpv6CidrBlock (50.16s) === RUN TestAccAWSVpcAssociateIpv4AndIpv6CidrBlock --- PASS: TestAccAWSVpcAssociateIpv4AndIpv6CidrBlock (2.13s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 103.802s ``` --- aws/resource_aws_vpc.go | 100 +++----- aws/resource_aws_vpc_associate_cidr_block.go | 128 +++++++++ ...ource_aws_vpc_associate_cidr_block_test.go | 242 ++++++++++++++++++ aws/resource_aws_vpc_test.go | 20 +- website/aws.erb | 4 + website/docs/r/vpc.html.markdown | 4 + .../r/vpc_associate_cidr_block.html.markdown | 58 +++++ 7 files changed, 472 insertions(+), 84 deletions(-) create mode 100644 aws/resource_aws_vpc_associate_cidr_block.go create mode 100644 aws/resource_aws_vpc_associate_cidr_block_test.go create mode 100644 website/docs/r/vpc_associate_cidr_block.html.markdown diff --git a/aws/resource_aws_vpc.go b/aws/resource_aws_vpc.go index e1687477d0a..6b5d2dea88a 100644 --- a/aws/resource_aws_vpc.go +++ b/aws/resource_aws_vpc.go @@ -20,7 +20,7 @@ func resourceAwsVpc() *schema.Resource { Update: resourceAwsVpcUpdate, Delete: resourceAwsVpcDelete, Importer: &schema.ResourceImporter{ - State: resourceAwsVpcInstanceImport, + State: schema.ImportStatePassthrough, }, CustomizeDiff: resourceAwsVpcCustomizeDiff, @@ -69,7 +69,13 @@ func resourceAwsVpc() *schema.Resource { "assign_generated_ipv6_cidr_block": { Type: schema.TypeBool, Optional: true, - Default: false, + Computed: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if !d.IsNewResource() && old == "false" { + return true + } + return false + }, }, "main_route_table_id": { @@ -180,6 +186,10 @@ func resourceAwsVpcRead(d *schema.ResourceData, meta interface{}) error { // Tags d.Set("tags", tagsToMap(vpc.Tags)) + if len(vpc.Ipv6CidrBlockAssociationSet) == 0 { + d.Set("assign_generated_ipv6_cidr_block", false) + } + for _, a := range vpc.Ipv6CidrBlockAssociationSet { if *a.Ipv6CidrBlockState.State == "associated" { //we can only ever have 1 IPv6 block associated at once d.Set("assign_generated_ipv6_cidr_block", true) @@ -389,65 +399,31 @@ func resourceAwsVpcUpdate(d *schema.ResourceData, meta interface{}) error { } if d.HasChange("assign_generated_ipv6_cidr_block") && !d.IsNewResource() { - toAssign := d.Get("assign_generated_ipv6_cidr_block").(bool) - - log.Printf("[INFO] Modifying assign_generated_ipv6_cidr_block to %#v", toAssign) - - if toAssign { - modifyOpts := &ec2.AssociateVpcCidrBlockInput{ - VpcId: &vpcid, - AmazonProvidedIpv6CidrBlock: aws.Bool(toAssign), - } - log.Printf("[INFO] Enabling assign_generated_ipv6_cidr_block vpc attribute for %s: %#v", - d.Id(), modifyOpts) - resp, err := conn.AssociateVpcCidrBlock(modifyOpts) - if err != nil { - return err - } - - // Wait for the CIDR to become available - log.Printf( - "[DEBUG] Waiting for IPv6 CIDR (%s) to become associated", - d.Id()) - stateConf := &resource.StateChangeConf{ - Pending: []string{"associating", "disassociated"}, - Target: []string{"associated"}, - Refresh: Ipv6CidrStateRefreshFunc(conn, d.Id(), *resp.Ipv6CidrBlockAssociation.AssociationId), - Timeout: 1 * time.Minute, - } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf( - "Error waiting for IPv6 CIDR (%s) to become associated: %s", - d.Id(), err) - } - } else { - modifyOpts := &ec2.DisassociateVpcCidrBlockInput{ - AssociationId: aws.String(d.Get("ipv6_association_id").(string)), - } - log.Printf("[INFO] Disabling assign_generated_ipv6_cidr_block vpc attribute for %s: %#v", - d.Id(), modifyOpts) - if _, err := conn.DisassociateVpcCidrBlock(modifyOpts); err != nil { - return err - } - - // Wait for the CIDR to become available - log.Printf( - "[DEBUG] Waiting for IPv6 CIDR (%s) to become disassociated", - d.Id()) - stateConf := &resource.StateChangeConf{ - Pending: []string{"disassociating", "associated"}, - Target: []string{"disassociated"}, - Refresh: Ipv6CidrStateRefreshFunc(conn, d.Id(), d.Get("ipv6_association_id").(string)), - Timeout: 1 * time.Minute, - } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf( - "Error waiting for IPv6 CIDR (%s) to become disassociated: %s", - d.Id(), err) - } + //we know that the change is only going to be to disable IPv6 as we suppress the func for enabling + modifyOpts := &ec2.DisassociateVpcCidrBlockInput{ + AssociationId: aws.String(d.Get("ipv6_association_id").(string)), + } + log.Printf("[INFO] Disabling assign_generated_ipv6_cidr_block vpc attribute for %s: %#v", + d.Id(), modifyOpts) + if _, err := conn.DisassociateVpcCidrBlock(modifyOpts); err != nil { + return err } - d.SetPartial("assign_generated_ipv6_cidr_block") + // Wait for the CIDR to become available + log.Printf( + "[DEBUG] Waiting for IPv6 CIDR (%s) to become disassociated", + d.Id()) + stateConf := &resource.StateChangeConf{ + Pending: []string{"disassociating", "associated"}, + Target: []string{"disassociated"}, + Refresh: Ipv6CidrStateRefreshFunc(conn, d.Id(), d.Get("ipv6_association_id").(string)), + Timeout: 1 * time.Minute, + } + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf( + "Error waiting for IPv6 CIDR (%s) to become disassociated: %s", + d.Id(), err) + } } if d.HasChange("instance_tenancy") && !d.IsNewResource() { @@ -656,12 +632,6 @@ func resourceAwsVpcSetDefaultRouteTable(conn *ec2.EC2, d *schema.ResourceData) e return nil } -func resourceAwsVpcInstanceImport( - d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - d.Set("assign_generated_ipv6_cidr_block", false) - return []*schema.ResourceData{d}, nil -} - func awsVpcDescribeVpcAttribute(attribute string, vpcId string, conn *ec2.EC2) (*ec2.DescribeVpcAttributeOutput, error) { describeAttrOpts := &ec2.DescribeVpcAttributeInput{ Attribute: aws.String(attribute), diff --git a/aws/resource_aws_vpc_associate_cidr_block.go b/aws/resource_aws_vpc_associate_cidr_block.go new file mode 100644 index 00000000000..9c19a90f2e5 --- /dev/null +++ b/aws/resource_aws_vpc_associate_cidr_block.go @@ -0,0 +1,128 @@ +package aws + +import ( + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsVpcAssociateCidrBlock() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsVpcAssociateCidrBlockCreate, + Read: resourceAwsVpcAssociateCidrBlockRead, + Delete: resourceAwsVpcAssociateCidrBlockDelete, + + Schema: map[string]*schema.Schema{ + + "vpc_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "ipv4_cidr_block": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validateCIDRNetworkAddress, + }, + + "assign_generated_ipv6_cidr_block": { + Type: schema.TypeBool, + Optional: true, + Default: false, + ForceNew: true, + ConflictsWith: []string{"ipv4_cidr_block"}, + }, + + "ipv6_cidr_block": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAwsVpcAssociateCidrBlockCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + params := &ec2.AssociateVpcCidrBlockInput{ + VpcId: aws.String(d.Get("vpc_id").(string)), + AmazonProvidedIpv6CidrBlock: aws.Bool(d.Get("assign_generated_ipv6_cidr_block").(bool)), + } + + if v, ok := d.GetOk("ipv4_cidr_block"); ok { + params.CidrBlock = aws.String(v.(string)) + } + + resp, err := conn.AssociateVpcCidrBlock(params) + if err != nil { + return err + } + + if d.Get("assign_generated_ipv6_cidr_block").(bool) == true { + d.SetId(*resp.Ipv6CidrBlockAssociation.AssociationId) + } else { + d.SetId(*resp.CidrBlockAssociation.AssociationId) + } + + return resourceAwsVpcAssociateCidrBlockRead(d, meta) +} + +func resourceAwsVpcAssociateCidrBlockRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + vpcRaw, _, err := VPCStateRefreshFunc(conn, d.Get("vpc_id").(string))() + if err != nil { + return err + } + + if vpcRaw == nil { + log.Printf("[INFO] No VPC Found for id %q", d.Get("vpc_id").(string)) + d.SetId("") + return nil + } + + vpc := vpcRaw.(*ec2.Vpc) + found := false + + if d.Get("assign_generated_ipv6_cidr_block").(bool) == true { + for _, ipv6Association := range vpc.Ipv6CidrBlockAssociationSet { + if *ipv6Association.AssociationId == d.Id() { + found = true + d.Set("ipv6_cidr_block", ipv6Association.Ipv6CidrBlock) + break + } + } + } else { + for _, cidrAssociation := range vpc.CidrBlockAssociationSet { + if *cidrAssociation.AssociationId == d.Id() { + found = true + d.Set("ipv4_cidr_block", cidrAssociation.CidrBlock) + } + } + } + + if !found { + log.Printf("[INFO] No VPC CIDR Association found for ID: %q", d.Id()) + } + + return nil +} + +func resourceAwsVpcAssociateCidrBlockDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + params := &ec2.DisassociateVpcCidrBlockInput{ + AssociationId: aws.String(d.Id()), + } + + _, err := conn.DisassociateVpcCidrBlock(params) + if err != nil { + return err + } + + return nil +} diff --git a/aws/resource_aws_vpc_associate_cidr_block_test.go b/aws/resource_aws_vpc_associate_cidr_block_test.go new file mode 100644 index 00000000000..f8a00942520 --- /dev/null +++ b/aws/resource_aws_vpc_associate_cidr_block_test.go @@ -0,0 +1,242 @@ +package aws + +import ( + "fmt" + "testing" + + "regexp" + + "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 TestAccAWSVpcAssociateIpv4CidrBlock(t *testing.T) { + var association ec2.VpcCidrBlockAssociation + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVpcAssociateCidrBlockDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVpcIpv4CidrAssociationConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckVpcIpv4CidrAssociationExists("aws_vpc_associate_cidr_block.secondary_cidr", &association), + testAccCheckVpcIpv4AssociationCidr(&association, "172.2.0.0/16"), + ), + }, + }, + }) +} + +func TestAccAWSVpcAssociateIpv6CidrBlock(t *testing.T) { + var association ec2.VpcIpv6CidrBlockAssociation + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVpcAssociateCidrBlockDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVpcIpv6CidrAssociationConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckVpcIpv6CidrAssociationExists("aws_vpc_associate_cidr_block.secondary_ipv6_cidr", &association), + resource.TestCheckResourceAttrSet("aws_vpc_associate_cidr_block.secondary_ipv6_cidr", "ipv6_cidr_block"), + ), + }, + }, + }) +} + +func TestAccAWSVpcAssociateIpv4AndIpv6CidrBlock(t *testing.T) { + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVpcAssociateCidrBlockDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVpcIpv4AndIpv6CidrAssociationConfig, + ExpectError: regexp.MustCompile(`: conflicts with`), + }, + }, + }) +} + +func testAccCheckVpcIpv4AssociationCidr(association *ec2.VpcCidrBlockAssociation, expected string) resource.TestCheckFunc { + return func(s *terraform.State) error { + CIDRBlock := association.CidrBlock + if *CIDRBlock != expected { + return fmt.Errorf("Bad cidr: %s", *association.CidrBlock) + } + + return nil + } +} + +func testAccCheckVpcAssociateCidrBlockDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_vpc_associate_cidr_block" { + continue + } + + // Try to find the VPC + DescribeVpcOpts := &ec2.DescribeVpcsInput{ + VpcIds: []*string{aws.String(rs.Primary.Attributes["vpc_id"])}, + } + resp, err := conn.DescribeVpcs(DescribeVpcOpts) + if err == nil { + vpc := resp.Vpcs[0] + + for _, ipv4Association := range vpc.CidrBlockAssociationSet { + if *ipv4Association.AssociationId == rs.Primary.ID { + return fmt.Errorf("VPC CIDR Association still exists.") + } + } + + for _, ipv6Association := range vpc.Ipv6CidrBlockAssociationSet { + if *ipv6Association.AssociationId == rs.Primary.ID { + return fmt.Errorf("VPC CIDR Association still exists.") + } + } + + return nil + } + + // Verify the error is what we want + ec2err, ok := err.(awserr.Error) + if !ok { + return err + } + if ec2err.Code() != "InvalidVpcID.NotFound" { + return err + } + } + + return nil +} + +func testAccCheckVpcIpv4CidrAssociationExists(n string, association *ec2.VpcCidrBlockAssociation) 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 VPC ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).ec2conn + DescribeVpcOpts := &ec2.DescribeVpcsInput{ + VpcIds: []*string{aws.String(rs.Primary.Attributes["vpc_id"])}, + } + resp, err := conn.DescribeVpcs(DescribeVpcOpts) + if err != nil { + return err + } + if len(resp.Vpcs) == 0 { + return fmt.Errorf("VPC not found") + } + + vpc := resp.Vpcs[0] + found := false + for _, cidrAssociation := range vpc.CidrBlockAssociationSet { + if *cidrAssociation.AssociationId == rs.Primary.ID { + *association = *cidrAssociation + found = true + } + } + + if !found { + return fmt.Errorf("VPC CIDR Association not found") + } + + return nil + } +} + +func testAccCheckVpcIpv6CidrAssociationExists(n string, association *ec2.VpcIpv6CidrBlockAssociation) 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 VPC ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).ec2conn + DescribeVpcOpts := &ec2.DescribeVpcsInput{ + VpcIds: []*string{aws.String(rs.Primary.Attributes["vpc_id"])}, + } + resp, err := conn.DescribeVpcs(DescribeVpcOpts) + if err != nil { + return err + } + if len(resp.Vpcs) == 0 { + return fmt.Errorf("VPC not found") + } + + vpc := resp.Vpcs[0] + found := false + for _, cidrAssociation := range vpc.Ipv6CidrBlockAssociationSet { + if *cidrAssociation.AssociationId == rs.Primary.ID { + *association = *cidrAssociation + found = true + } + } + + if !found { + return fmt.Errorf("VPC CIDR Association not found") + } + + return nil + } +} + +const testAccVpcIpv4CidrAssociationConfig = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_vpc_associate_cidr_block" "secondary_cidr" { + vpc_id = "${aws_vpc.foo.id}" + ipv4_cidr_block = "172.2.0.0/16" +} + +resource "aws_vpc_associate_cidr_block" "tertiary_cidr" { + vpc_id = "${aws_vpc.foo.id}" + ipv4_cidr_block = "170.2.0.0/16" +} +` + +const testAccVpcIpv6CidrAssociationConfig = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_vpc_associate_cidr_block" "secondary_ipv6_cidr" { + vpc_id = "${aws_vpc.foo.id}" + assign_generated_ipv6_cidr_block = true +} +` + +const testAccVpcIpv4AndIpv6CidrAssociationConfig = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" + assign_generated_ipv6_cidr_block = true +} + +resource "aws_vpc_associate_cidr_block" "secondary_ipv6_cidr" { + vpc_id = "${aws_vpc.foo.id}" + ipv4_cidr_block = "172.2.0.0/16" + assign_generated_ipv6_cidr_block = true +} +` diff --git a/aws/resource_aws_vpc_test.go b/aws/resource_aws_vpc_test.go index 1059b255975..d13f0055b15 100644 --- a/aws/resource_aws_vpc_test.go +++ b/aws/resource_aws_vpc_test.go @@ -113,7 +113,6 @@ func TestAccAWSVpc_enableIpv6(t *testing.T) { Config: testAccVpcConfigIpv6Enabled, Check: resource.ComposeAggregateTestCheckFunc( 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( @@ -128,28 +127,10 @@ func TestAccAWSVpc_enableIpv6(t *testing.T) { Config: testAccVpcConfigIpv6Disabled, Check: resource.ComposeAggregateTestCheckFunc( 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.TestCheckResourceAttr( "aws_vpc.foo", "assign_generated_ipv6_cidr_block", "false"), ), }, - { - Config: testAccVpcConfigIpv6Enabled, - Check: resource.ComposeAggregateTestCheckFunc( - 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"), - resource.TestCheckResourceAttr( - "aws_vpc.foo", "assign_generated_ipv6_cidr_block", "true"), - ), - }, }, }) } @@ -461,6 +442,7 @@ resource "aws_vpc" "foo" { const testAccVpcConfigIpv6Disabled = ` resource "aws_vpc" "foo" { cidr_block = "10.1.0.0/16" + assign_generated_ipv6_cidr_block = false tags { Name = "terraform-testacc-vpc-ipv6" } diff --git a/website/aws.erb b/website/aws.erb index 9fdefaafb52..7c2e6635788 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -2198,6 +2198,10 @@ aws_vpc + > + aws_vpc_associate_cidr_block + + > aws_vpc_dhcp_options diff --git a/website/docs/r/vpc.html.markdown b/website/docs/r/vpc.html.markdown index 16e7bfe851f..8bc99e93f51 100644 --- a/website/docs/r/vpc.html.markdown +++ b/website/docs/r/vpc.html.markdown @@ -51,6 +51,10 @@ block with a /56 prefix length for the VPC. You cannot specify the range of IP a the size of the CIDR block. Default is `false`. * `tags` - (Optional) A mapping of tags to assign to the resource. +~> **Note:** If you create a VPC with `assign_generated_ipv6_cidr_block` set to `false` and want to +update that to `true`, Terraform will no longer support this operation and we suggest to use `aws_vpc_associate_cidr_block` resource instead. +The VPC won't reflect the new IPv6 values from the new resource until `terraform refresh` happens. + ## Attributes Reference In addition to all arguments above, the following attributes are exported: diff --git a/website/docs/r/vpc_associate_cidr_block.html.markdown b/website/docs/r/vpc_associate_cidr_block.html.markdown new file mode 100644 index 00000000000..276f5beec6d --- /dev/null +++ b/website/docs/r/vpc_associate_cidr_block.html.markdown @@ -0,0 +1,58 @@ +--- +layout: "aws" +page_title: "AWS: aws_vpc_associate_cidr_block" +sidebar_current: "docs-aws-resource-vpc-associate-cidr-block" +description: |- + Associates a CIDR block to a VPC +--- + +# aws_vpc_associate_cidr_block + +Associates a CIDR block to a VPC + +## Example Usage + +IPv4 CIDR Block Association: + +```hcl +resource "aws_vpc" "main" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_vpc_associate_cidr_block" "secondary_cidr" { + vpc_id = "${aws_vpc.main.id}" + ipv4_cidr_block = "172.2.0.0/16" +} +``` + +IPv6 CIDR Block Association: + +```hcl +resource "aws_vpc" "main" { + cidr_block = "10.0.0.0/16" + assign_generated_ipv6_cidr_block = true +} + +resource "aws_vpc_associate_cidr_block" "secondary_ipv6_cidr" { + vpc_id = "${aws_vpc.main.id}" + assign_generated_ipv6_cidr_block = true +} +``` + +## Argument Reference + +The following arguments are supported: + +* `vpc_id` - (Required) The ID of the VPC to make the association with +* `cidr_block` - (Optional) The IPv4 CIDR block to associate to the VPC. +* `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`. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the VPC CIDR Association +* `ipv6_cidr_block` - The IPv6 CIDR block. + From 3e43f34c3c4a0db8c419ceef7d4ebe2384eebacf Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 9 Mar 2018 14:50:17 -0500 Subject: [PATCH 2/7] Restore original 'aws_vpc.assign_generated_ipv6_cidr_block' functionality. --- aws/resource_aws_vpc.go | 100 ++++++++++++++++++++----------- aws/resource_aws_vpc_test.go | 20 ++++++- website/docs/r/vpc.html.markdown | 4 -- 3 files changed, 84 insertions(+), 40 deletions(-) diff --git a/aws/resource_aws_vpc.go b/aws/resource_aws_vpc.go index 6b5d2dea88a..e1687477d0a 100644 --- a/aws/resource_aws_vpc.go +++ b/aws/resource_aws_vpc.go @@ -20,7 +20,7 @@ func resourceAwsVpc() *schema.Resource { Update: resourceAwsVpcUpdate, Delete: resourceAwsVpcDelete, Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, + State: resourceAwsVpcInstanceImport, }, CustomizeDiff: resourceAwsVpcCustomizeDiff, @@ -69,13 +69,7 @@ func resourceAwsVpc() *schema.Resource { "assign_generated_ipv6_cidr_block": { Type: schema.TypeBool, Optional: true, - Computed: true, - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - if !d.IsNewResource() && old == "false" { - return true - } - return false - }, + Default: false, }, "main_route_table_id": { @@ -186,10 +180,6 @@ func resourceAwsVpcRead(d *schema.ResourceData, meta interface{}) error { // Tags d.Set("tags", tagsToMap(vpc.Tags)) - if len(vpc.Ipv6CidrBlockAssociationSet) == 0 { - d.Set("assign_generated_ipv6_cidr_block", false) - } - for _, a := range vpc.Ipv6CidrBlockAssociationSet { if *a.Ipv6CidrBlockState.State == "associated" { //we can only ever have 1 IPv6 block associated at once d.Set("assign_generated_ipv6_cidr_block", true) @@ -399,31 +389,65 @@ func resourceAwsVpcUpdate(d *schema.ResourceData, meta interface{}) error { } if d.HasChange("assign_generated_ipv6_cidr_block") && !d.IsNewResource() { - //we know that the change is only going to be to disable IPv6 as we suppress the func for enabling - modifyOpts := &ec2.DisassociateVpcCidrBlockInput{ - AssociationId: aws.String(d.Get("ipv6_association_id").(string)), - } - log.Printf("[INFO] Disabling assign_generated_ipv6_cidr_block vpc attribute for %s: %#v", - d.Id(), modifyOpts) - if _, err := conn.DisassociateVpcCidrBlock(modifyOpts); err != nil { - return err - } + toAssign := d.Get("assign_generated_ipv6_cidr_block").(bool) - // Wait for the CIDR to become available - log.Printf( - "[DEBUG] Waiting for IPv6 CIDR (%s) to become disassociated", - d.Id()) - stateConf := &resource.StateChangeConf{ - Pending: []string{"disassociating", "associated"}, - Target: []string{"disassociated"}, - Refresh: Ipv6CidrStateRefreshFunc(conn, d.Id(), d.Get("ipv6_association_id").(string)), - Timeout: 1 * time.Minute, - } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf( - "Error waiting for IPv6 CIDR (%s) to become disassociated: %s", - d.Id(), err) + log.Printf("[INFO] Modifying assign_generated_ipv6_cidr_block to %#v", toAssign) + + if toAssign { + modifyOpts := &ec2.AssociateVpcCidrBlockInput{ + VpcId: &vpcid, + AmazonProvidedIpv6CidrBlock: aws.Bool(toAssign), + } + log.Printf("[INFO] Enabling assign_generated_ipv6_cidr_block vpc attribute for %s: %#v", + d.Id(), modifyOpts) + resp, err := conn.AssociateVpcCidrBlock(modifyOpts) + if err != nil { + return err + } + + // Wait for the CIDR to become available + log.Printf( + "[DEBUG] Waiting for IPv6 CIDR (%s) to become associated", + d.Id()) + stateConf := &resource.StateChangeConf{ + Pending: []string{"associating", "disassociated"}, + Target: []string{"associated"}, + Refresh: Ipv6CidrStateRefreshFunc(conn, d.Id(), *resp.Ipv6CidrBlockAssociation.AssociationId), + Timeout: 1 * time.Minute, + } + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf( + "Error waiting for IPv6 CIDR (%s) to become associated: %s", + d.Id(), err) + } + } else { + modifyOpts := &ec2.DisassociateVpcCidrBlockInput{ + AssociationId: aws.String(d.Get("ipv6_association_id").(string)), + } + log.Printf("[INFO] Disabling assign_generated_ipv6_cidr_block vpc attribute for %s: %#v", + d.Id(), modifyOpts) + if _, err := conn.DisassociateVpcCidrBlock(modifyOpts); err != nil { + return err + } + + // Wait for the CIDR to become available + log.Printf( + "[DEBUG] Waiting for IPv6 CIDR (%s) to become disassociated", + d.Id()) + stateConf := &resource.StateChangeConf{ + Pending: []string{"disassociating", "associated"}, + Target: []string{"disassociated"}, + Refresh: Ipv6CidrStateRefreshFunc(conn, d.Id(), d.Get("ipv6_association_id").(string)), + Timeout: 1 * time.Minute, + } + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf( + "Error waiting for IPv6 CIDR (%s) to become disassociated: %s", + d.Id(), err) + } } + + d.SetPartial("assign_generated_ipv6_cidr_block") } if d.HasChange("instance_tenancy") && !d.IsNewResource() { @@ -632,6 +656,12 @@ func resourceAwsVpcSetDefaultRouteTable(conn *ec2.EC2, d *schema.ResourceData) e return nil } +func resourceAwsVpcInstanceImport( + d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + d.Set("assign_generated_ipv6_cidr_block", false) + return []*schema.ResourceData{d}, nil +} + func awsVpcDescribeVpcAttribute(attribute string, vpcId string, conn *ec2.EC2) (*ec2.DescribeVpcAttributeOutput, error) { describeAttrOpts := &ec2.DescribeVpcAttributeInput{ Attribute: aws.String(attribute), diff --git a/aws/resource_aws_vpc_test.go b/aws/resource_aws_vpc_test.go index d13f0055b15..1059b255975 100644 --- a/aws/resource_aws_vpc_test.go +++ b/aws/resource_aws_vpc_test.go @@ -113,6 +113,7 @@ func TestAccAWSVpc_enableIpv6(t *testing.T) { Config: testAccVpcConfigIpv6Enabled, Check: resource.ComposeAggregateTestCheckFunc( 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( @@ -127,10 +128,28 @@ func TestAccAWSVpc_enableIpv6(t *testing.T) { Config: testAccVpcConfigIpv6Disabled, Check: resource.ComposeAggregateTestCheckFunc( 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.TestCheckResourceAttr( "aws_vpc.foo", "assign_generated_ipv6_cidr_block", "false"), ), }, + { + Config: testAccVpcConfigIpv6Enabled, + Check: resource.ComposeAggregateTestCheckFunc( + 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"), + resource.TestCheckResourceAttr( + "aws_vpc.foo", "assign_generated_ipv6_cidr_block", "true"), + ), + }, }, }) } @@ -442,7 +461,6 @@ resource "aws_vpc" "foo" { const testAccVpcConfigIpv6Disabled = ` resource "aws_vpc" "foo" { cidr_block = "10.1.0.0/16" - assign_generated_ipv6_cidr_block = false tags { Name = "terraform-testacc-vpc-ipv6" } diff --git a/website/docs/r/vpc.html.markdown b/website/docs/r/vpc.html.markdown index 8bc99e93f51..16e7bfe851f 100644 --- a/website/docs/r/vpc.html.markdown +++ b/website/docs/r/vpc.html.markdown @@ -51,10 +51,6 @@ block with a /56 prefix length for the VPC. You cannot specify the range of IP a the size of the CIDR block. Default is `false`. * `tags` - (Optional) A mapping of tags to assign to the resource. -~> **Note:** If you create a VPC with `assign_generated_ipv6_cidr_block` set to `false` and want to -update that to `true`, Terraform will no longer support this operation and we suggest to use `aws_vpc_associate_cidr_block` resource instead. -The VPC won't reflect the new IPv6 values from the new resource until `terraform refresh` happens. - ## Attributes Reference In addition to all arguments above, the following attributes are exported: From 839d50badc5a0fcf53ccbfd284176d8a9a6e9203 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 9 Mar 2018 15:52:27 -0500 Subject: [PATCH 3/7] 'aws_vpc_associate_cidr_block' -> 'aws_vpc_secondary_ipv4_cidr_block'. --- aws/aws_vpc_secondary_ipv4_cidr_block.go | 96 +++++++ aws/aws_vpc_secondary_ipv4_cidr_block_test.go | 138 ++++++++++ aws/provider.go | 1 + aws/resource_aws_vpc_associate_cidr_block.go | 128 --------- ...ource_aws_vpc_associate_cidr_block_test.go | 242 ------------------ website/aws.erb | 8 +- .../r/vpc_associate_cidr_block.html.markdown | 58 ----- ...pc_secondary_ipv4_cidr_block.html.markdown | 37 +++ 8 files changed, 276 insertions(+), 432 deletions(-) create mode 100644 aws/aws_vpc_secondary_ipv4_cidr_block.go create mode 100644 aws/aws_vpc_secondary_ipv4_cidr_block_test.go delete mode 100644 aws/resource_aws_vpc_associate_cidr_block.go delete mode 100644 aws/resource_aws_vpc_associate_cidr_block_test.go delete mode 100644 website/docs/r/vpc_associate_cidr_block.html.markdown create mode 100644 website/docs/r/vpc_secondary_ipv4_cidr_block.html.markdown diff --git a/aws/aws_vpc_secondary_ipv4_cidr_block.go b/aws/aws_vpc_secondary_ipv4_cidr_block.go new file mode 100644 index 00000000000..ca6f81ae714 --- /dev/null +++ b/aws/aws_vpc_secondary_ipv4_cidr_block.go @@ -0,0 +1,96 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" +) + +func resourceAwsVpcSecondaryIpv4CidrBlock() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsVpcSecondaryIpv4CidrBlockCreate, + Read: resourceAwsVpcSecondaryIpv4CidrBlockRead, + Delete: resourceAwsVpcSecondaryIpv4CidrBlockDelete, + + Schema: map[string]*schema.Schema{ + "vpc_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "ipv4_cidr_block": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.CIDRNetwork(16, 28), // The allowed block size is between a /28 netmask and /16 netmask. + }, + }, + } +} + +func resourceAwsVpcSecondaryIpv4CidrBlockCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + req := &ec2.AssociateVpcCidrBlockInput{ + VpcId: aws.String(d.Get("vpc_id").(string)), + CidrBlock: aws.String(d.Get("ipv4_cidr_block").(string)), + } + log.Printf("[DEBUG] Creating VPC secondary IPv4 CIDR block: %#v", req) + resp, err := conn.AssociateVpcCidrBlock(req) + if err != nil { + return fmt.Errorf("Error creating VPC secondary IPv4 CIDR block: %s", err.Error()) + } + + d.SetId(aws.StringValue(resp.CidrBlockAssociation.AssociationId)) + + return resourceAwsVpcSecondaryIpv4CidrBlockRead(d, meta) +} + +func resourceAwsVpcSecondaryIpv4CidrBlockRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + vpcId := d.Get("vpc_id").(string) + vpcRaw, _, err := VPCStateRefreshFunc(conn, vpcId)() + if err != nil { + return fmt.Errorf("Error reading VPC: %s", err.Error()) + } + if vpcRaw == nil { + log.Printf("[WARN] VPC (%s) not found, removing secondary IPv4 CIDR block from state", vpcId) + d.SetId("") + return nil + } + + vpc := vpcRaw.(*ec2.Vpc) + found := false + for _, cidrAssociation := range vpc.CidrBlockAssociationSet { + if aws.StringValue(cidrAssociation.AssociationId) == d.Id() { + found = true + d.Set("ipv4_cidr_block", cidrAssociation.CidrBlock) + } + } + if !found { + log.Printf("[WARN] VPC secondary IPv4 CIDR block (%s) not found, removing from state", d.Id()) + d.SetId("") + } + + return nil +} + +func resourceAwsVpcSecondaryIpv4CidrBlockDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + log.Printf("[DEBUG] Deleting VPC secondary IPv4 CIDR block: %s", d.Id()) + _, err := conn.DisassociateVpcCidrBlock(&ec2.DisassociateVpcCidrBlockInput{ + AssociationId: aws.String(d.Id()), + }) + if err != nil { + return fmt.Errorf("Error deleting VPC secondary IPv4 CIDR block: %s", err.Error()) + } + + return nil +} diff --git a/aws/aws_vpc_secondary_ipv4_cidr_block_test.go b/aws/aws_vpc_secondary_ipv4_cidr_block_test.go new file mode 100644 index 00000000000..c603368e356 --- /dev/null +++ b/aws/aws_vpc_secondary_ipv4_cidr_block_test.go @@ -0,0 +1,138 @@ +package aws + +import ( + "fmt" + "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 TestAccAwsVpcSecondaryIpv4CidrBlock_basic(t *testing.T) { + var associationSecondary, associationTertiary ec2.VpcCidrBlockAssociation + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsVpcSecondaryIpv4CidrBlockDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsVpcSecondaryIpv4CidrBlockConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsVpcSecondaryIpv4CidrBlockExists("aws_vpc_secondary_ipv4_cidr_block.secondary_cidr", &associationSecondary), + testAccCheckAwsVpcSecondaryIpv4CidrBlock(&associationSecondary, "172.2.0.0/16"), + testAccCheckAwsVpcSecondaryIpv4CidrBlockExists("aws_vpc_secondary_ipv4_cidr_block.tertiary_cidr", &associationTertiary), + testAccCheckAwsVpcSecondaryIpv4CidrBlock(&associationTertiary, "170.2.0.0/16"), + ), + }, + }, + }) +} + +func testAccCheckAwsVpcSecondaryIpv4CidrBlock(association *ec2.VpcCidrBlockAssociation, expected string) resource.TestCheckFunc { + return func(s *terraform.State) error { + CIDRBlock := association.CidrBlock + if *CIDRBlock != expected { + return fmt.Errorf("Bad CIDR: %s", *association.CidrBlock) + } + + return nil + } +} + +func testAccCheckAwsVpcSecondaryIpv4CidrBlockDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_vpc_secondary_ipv4_cidr_block" { + continue + } + + // Try to find the VPC + DescribeVpcOpts := &ec2.DescribeVpcsInput{ + VpcIds: []*string{aws.String(rs.Primary.Attributes["vpc_id"])}, + } + resp, err := conn.DescribeVpcs(DescribeVpcOpts) + if err == nil { + vpc := resp.Vpcs[0] + + for _, ipv4Association := range vpc.CidrBlockAssociationSet { + if *ipv4Association.AssociationId == rs.Primary.ID { + return fmt.Errorf("VPC secondary CIDR block still exists") + } + } + + return nil + } + + // Verify the error is what we want + ec2err, ok := err.(awserr.Error) + if !ok { + return err + } + if ec2err.Code() != "InvalidVpcID.NotFound" { + return err + } + } + + return nil +} + +func testAccCheckAwsVpcSecondaryIpv4CidrBlockExists(n string, association *ec2.VpcCidrBlockAssociation) 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 VPC ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).ec2conn + DescribeVpcOpts := &ec2.DescribeVpcsInput{ + VpcIds: []*string{aws.String(rs.Primary.Attributes["vpc_id"])}, + } + resp, err := conn.DescribeVpcs(DescribeVpcOpts) + if err != nil { + return err + } + if len(resp.Vpcs) == 0 { + return fmt.Errorf("VPC not found") + } + + vpc := resp.Vpcs[0] + found := false + for _, cidrAssociation := range vpc.CidrBlockAssociationSet { + if *cidrAssociation.AssociationId == rs.Primary.ID { + *association = *cidrAssociation + found = true + } + } + + if !found { + return fmt.Errorf("VPC secondary CIDR block not found") + } + + return nil + } +} + +const testAccAwsVpcSecondaryIpv4CidrBlockConfig = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_vpc_secondary_ipv4_cidr_block" "secondary_cidr" { + vpc_id = "${aws_vpc.foo.id}" + ipv4_cidr_block = "172.2.0.0/16" +} + +resource "aws_vpc_secondary_ipv4_cidr_block" "tertiary_cidr" { + vpc_id = "${aws_vpc.foo.id}" + ipv4_cidr_block = "170.2.0.0/16" +} +` diff --git a/aws/provider.go b/aws/provider.go index 6a513c7249f..a5c7e9e1b4e 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -609,6 +609,7 @@ func Provider() terraform.ResourceProvider { "aws_vpc_endpoint_subnet_association": resourceAwsVpcEndpointSubnetAssociation(), "aws_vpc_endpoint_service": resourceAwsVpcEndpointService(), "aws_vpc_endpoint_service_allowed_principal": resourceAwsVpcEndpointServiceAllowedPrincipal(), + "aws_vpc_ipv4_cidr_block_association": resourceAwsVpcIpv4CidrBlockAssociation(), "aws_vpn_connection": resourceAwsVpnConnection(), "aws_vpn_connection_route": resourceAwsVpnConnectionRoute(), "aws_vpn_gateway": resourceAwsVpnGateway(), diff --git a/aws/resource_aws_vpc_associate_cidr_block.go b/aws/resource_aws_vpc_associate_cidr_block.go deleted file mode 100644 index 9c19a90f2e5..00000000000 --- a/aws/resource_aws_vpc_associate_cidr_block.go +++ /dev/null @@ -1,128 +0,0 @@ -package aws - -import ( - "log" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ec2" - "github.com/hashicorp/terraform/helper/schema" -) - -func resourceAwsVpcAssociateCidrBlock() *schema.Resource { - return &schema.Resource{ - Create: resourceAwsVpcAssociateCidrBlockCreate, - Read: resourceAwsVpcAssociateCidrBlockRead, - Delete: resourceAwsVpcAssociateCidrBlockDelete, - - Schema: map[string]*schema.Schema{ - - "vpc_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "ipv4_cidr_block": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: validateCIDRNetworkAddress, - }, - - "assign_generated_ipv6_cidr_block": { - Type: schema.TypeBool, - Optional: true, - Default: false, - ForceNew: true, - ConflictsWith: []string{"ipv4_cidr_block"}, - }, - - "ipv6_cidr_block": { - Type: schema.TypeString, - Computed: true, - }, - }, - } -} - -func resourceAwsVpcAssociateCidrBlockCreate(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).ec2conn - - params := &ec2.AssociateVpcCidrBlockInput{ - VpcId: aws.String(d.Get("vpc_id").(string)), - AmazonProvidedIpv6CidrBlock: aws.Bool(d.Get("assign_generated_ipv6_cidr_block").(bool)), - } - - if v, ok := d.GetOk("ipv4_cidr_block"); ok { - params.CidrBlock = aws.String(v.(string)) - } - - resp, err := conn.AssociateVpcCidrBlock(params) - if err != nil { - return err - } - - if d.Get("assign_generated_ipv6_cidr_block").(bool) == true { - d.SetId(*resp.Ipv6CidrBlockAssociation.AssociationId) - } else { - d.SetId(*resp.CidrBlockAssociation.AssociationId) - } - - return resourceAwsVpcAssociateCidrBlockRead(d, meta) -} - -func resourceAwsVpcAssociateCidrBlockRead(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).ec2conn - - vpcRaw, _, err := VPCStateRefreshFunc(conn, d.Get("vpc_id").(string))() - if err != nil { - return err - } - - if vpcRaw == nil { - log.Printf("[INFO] No VPC Found for id %q", d.Get("vpc_id").(string)) - d.SetId("") - return nil - } - - vpc := vpcRaw.(*ec2.Vpc) - found := false - - if d.Get("assign_generated_ipv6_cidr_block").(bool) == true { - for _, ipv6Association := range vpc.Ipv6CidrBlockAssociationSet { - if *ipv6Association.AssociationId == d.Id() { - found = true - d.Set("ipv6_cidr_block", ipv6Association.Ipv6CidrBlock) - break - } - } - } else { - for _, cidrAssociation := range vpc.CidrBlockAssociationSet { - if *cidrAssociation.AssociationId == d.Id() { - found = true - d.Set("ipv4_cidr_block", cidrAssociation.CidrBlock) - } - } - } - - if !found { - log.Printf("[INFO] No VPC CIDR Association found for ID: %q", d.Id()) - } - - return nil -} - -func resourceAwsVpcAssociateCidrBlockDelete(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).ec2conn - - params := &ec2.DisassociateVpcCidrBlockInput{ - AssociationId: aws.String(d.Id()), - } - - _, err := conn.DisassociateVpcCidrBlock(params) - if err != nil { - return err - } - - return nil -} diff --git a/aws/resource_aws_vpc_associate_cidr_block_test.go b/aws/resource_aws_vpc_associate_cidr_block_test.go deleted file mode 100644 index f8a00942520..00000000000 --- a/aws/resource_aws_vpc_associate_cidr_block_test.go +++ /dev/null @@ -1,242 +0,0 @@ -package aws - -import ( - "fmt" - "testing" - - "regexp" - - "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 TestAccAWSVpcAssociateIpv4CidrBlock(t *testing.T) { - var association ec2.VpcCidrBlockAssociation - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckVpcAssociateCidrBlockDestroy, - Steps: []resource.TestStep{ - { - Config: testAccVpcIpv4CidrAssociationConfig, - Check: resource.ComposeTestCheckFunc( - testAccCheckVpcIpv4CidrAssociationExists("aws_vpc_associate_cidr_block.secondary_cidr", &association), - testAccCheckVpcIpv4AssociationCidr(&association, "172.2.0.0/16"), - ), - }, - }, - }) -} - -func TestAccAWSVpcAssociateIpv6CidrBlock(t *testing.T) { - var association ec2.VpcIpv6CidrBlockAssociation - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckVpcAssociateCidrBlockDestroy, - Steps: []resource.TestStep{ - { - Config: testAccVpcIpv6CidrAssociationConfig, - Check: resource.ComposeTestCheckFunc( - testAccCheckVpcIpv6CidrAssociationExists("aws_vpc_associate_cidr_block.secondary_ipv6_cidr", &association), - resource.TestCheckResourceAttrSet("aws_vpc_associate_cidr_block.secondary_ipv6_cidr", "ipv6_cidr_block"), - ), - }, - }, - }) -} - -func TestAccAWSVpcAssociateIpv4AndIpv6CidrBlock(t *testing.T) { - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckVpcAssociateCidrBlockDestroy, - Steps: []resource.TestStep{ - { - Config: testAccVpcIpv4AndIpv6CidrAssociationConfig, - ExpectError: regexp.MustCompile(`: conflicts with`), - }, - }, - }) -} - -func testAccCheckVpcIpv4AssociationCidr(association *ec2.VpcCidrBlockAssociation, expected string) resource.TestCheckFunc { - return func(s *terraform.State) error { - CIDRBlock := association.CidrBlock - if *CIDRBlock != expected { - return fmt.Errorf("Bad cidr: %s", *association.CidrBlock) - } - - return nil - } -} - -func testAccCheckVpcAssociateCidrBlockDestroy(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).ec2conn - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_vpc_associate_cidr_block" { - continue - } - - // Try to find the VPC - DescribeVpcOpts := &ec2.DescribeVpcsInput{ - VpcIds: []*string{aws.String(rs.Primary.Attributes["vpc_id"])}, - } - resp, err := conn.DescribeVpcs(DescribeVpcOpts) - if err == nil { - vpc := resp.Vpcs[0] - - for _, ipv4Association := range vpc.CidrBlockAssociationSet { - if *ipv4Association.AssociationId == rs.Primary.ID { - return fmt.Errorf("VPC CIDR Association still exists.") - } - } - - for _, ipv6Association := range vpc.Ipv6CidrBlockAssociationSet { - if *ipv6Association.AssociationId == rs.Primary.ID { - return fmt.Errorf("VPC CIDR Association still exists.") - } - } - - return nil - } - - // Verify the error is what we want - ec2err, ok := err.(awserr.Error) - if !ok { - return err - } - if ec2err.Code() != "InvalidVpcID.NotFound" { - return err - } - } - - return nil -} - -func testAccCheckVpcIpv4CidrAssociationExists(n string, association *ec2.VpcCidrBlockAssociation) 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 VPC ID is set") - } - - conn := testAccProvider.Meta().(*AWSClient).ec2conn - DescribeVpcOpts := &ec2.DescribeVpcsInput{ - VpcIds: []*string{aws.String(rs.Primary.Attributes["vpc_id"])}, - } - resp, err := conn.DescribeVpcs(DescribeVpcOpts) - if err != nil { - return err - } - if len(resp.Vpcs) == 0 { - return fmt.Errorf("VPC not found") - } - - vpc := resp.Vpcs[0] - found := false - for _, cidrAssociation := range vpc.CidrBlockAssociationSet { - if *cidrAssociation.AssociationId == rs.Primary.ID { - *association = *cidrAssociation - found = true - } - } - - if !found { - return fmt.Errorf("VPC CIDR Association not found") - } - - return nil - } -} - -func testAccCheckVpcIpv6CidrAssociationExists(n string, association *ec2.VpcIpv6CidrBlockAssociation) 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 VPC ID is set") - } - - conn := testAccProvider.Meta().(*AWSClient).ec2conn - DescribeVpcOpts := &ec2.DescribeVpcsInput{ - VpcIds: []*string{aws.String(rs.Primary.Attributes["vpc_id"])}, - } - resp, err := conn.DescribeVpcs(DescribeVpcOpts) - if err != nil { - return err - } - if len(resp.Vpcs) == 0 { - return fmt.Errorf("VPC not found") - } - - vpc := resp.Vpcs[0] - found := false - for _, cidrAssociation := range vpc.Ipv6CidrBlockAssociationSet { - if *cidrAssociation.AssociationId == rs.Primary.ID { - *association = *cidrAssociation - found = true - } - } - - if !found { - return fmt.Errorf("VPC CIDR Association not found") - } - - return nil - } -} - -const testAccVpcIpv4CidrAssociationConfig = ` -resource "aws_vpc" "foo" { - cidr_block = "10.1.0.0/16" -} - -resource "aws_vpc_associate_cidr_block" "secondary_cidr" { - vpc_id = "${aws_vpc.foo.id}" - ipv4_cidr_block = "172.2.0.0/16" -} - -resource "aws_vpc_associate_cidr_block" "tertiary_cidr" { - vpc_id = "${aws_vpc.foo.id}" - ipv4_cidr_block = "170.2.0.0/16" -} -` - -const testAccVpcIpv6CidrAssociationConfig = ` -resource "aws_vpc" "foo" { - cidr_block = "10.1.0.0/16" -} - -resource "aws_vpc_associate_cidr_block" "secondary_ipv6_cidr" { - vpc_id = "${aws_vpc.foo.id}" - assign_generated_ipv6_cidr_block = true -} -` - -const testAccVpcIpv4AndIpv6CidrAssociationConfig = ` -resource "aws_vpc" "foo" { - cidr_block = "10.1.0.0/16" - assign_generated_ipv6_cidr_block = true -} - -resource "aws_vpc_associate_cidr_block" "secondary_ipv6_cidr" { - vpc_id = "${aws_vpc.foo.id}" - ipv4_cidr_block = "172.2.0.0/16" - assign_generated_ipv6_cidr_block = true -} -` diff --git a/website/aws.erb b/website/aws.erb index 7c2e6635788..e65e202886a 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -2198,10 +2198,6 @@ aws_vpc - > - aws_vpc_associate_cidr_block - - > aws_vpc_dhcp_options @@ -2246,6 +2242,10 @@ aws_vpc_peering_connection_options + > + aws_vpc_secondary_ipv4_cidr_block + + > aws_vpn_connection diff --git a/website/docs/r/vpc_associate_cidr_block.html.markdown b/website/docs/r/vpc_associate_cidr_block.html.markdown deleted file mode 100644 index 276f5beec6d..00000000000 --- a/website/docs/r/vpc_associate_cidr_block.html.markdown +++ /dev/null @@ -1,58 +0,0 @@ ---- -layout: "aws" -page_title: "AWS: aws_vpc_associate_cidr_block" -sidebar_current: "docs-aws-resource-vpc-associate-cidr-block" -description: |- - Associates a CIDR block to a VPC ---- - -# aws_vpc_associate_cidr_block - -Associates a CIDR block to a VPC - -## Example Usage - -IPv4 CIDR Block Association: - -```hcl -resource "aws_vpc" "main" { - cidr_block = "10.0.0.0/16" -} - -resource "aws_vpc_associate_cidr_block" "secondary_cidr" { - vpc_id = "${aws_vpc.main.id}" - ipv4_cidr_block = "172.2.0.0/16" -} -``` - -IPv6 CIDR Block Association: - -```hcl -resource "aws_vpc" "main" { - cidr_block = "10.0.0.0/16" - assign_generated_ipv6_cidr_block = true -} - -resource "aws_vpc_associate_cidr_block" "secondary_ipv6_cidr" { - vpc_id = "${aws_vpc.main.id}" - assign_generated_ipv6_cidr_block = true -} -``` - -## Argument Reference - -The following arguments are supported: - -* `vpc_id` - (Required) The ID of the VPC to make the association with -* `cidr_block` - (Optional) The IPv4 CIDR block to associate to the VPC. -* `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`. - -## Attributes Reference - -The following attributes are exported: - -* `id` - The ID of the VPC CIDR Association -* `ipv6_cidr_block` - The IPv6 CIDR block. - diff --git a/website/docs/r/vpc_secondary_ipv4_cidr_block.html.markdown b/website/docs/r/vpc_secondary_ipv4_cidr_block.html.markdown new file mode 100644 index 00000000000..b8d8dfd8e46 --- /dev/null +++ b/website/docs/r/vpc_secondary_ipv4_cidr_block.html.markdown @@ -0,0 +1,37 @@ +--- +layout: "aws" +page_title: "AWS: aws_vpc_secondary_ipv4_cidr_block" +sidebar_current: "docs-aws-resource-vpc-secondary-ipv4-cidr-block" +description: |- + Associate a secondary IPv4 CIDR blocks with a VPC +--- + +# aws_vpc_secondary_ipv4_cidr_block + +Provides a resource to associate a secondary IPv4 CIDR blocks with a VPC. + +## Example Usage + +```hcl +resource "aws_vpc" "main" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_vpc_secondary_ipv4_cidr_block" "secondary_cidr" { + vpc_id = "${aws_vpc.main.id}" + ipv4_cidr_block = "172.2.0.0/16" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `vpc_id` - (Required) The ID of the VPC to make the association with. +* `ipv4_cidr_block` - (Required) The secondary IPv4 CIDR block to associate with the VPC. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the VPC CIDR association From ecad19d21bc83a51acb0e41dc2e7080c1aa47196 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 9 Mar 2018 16:38:27 -0500 Subject: [PATCH 4/7] 'ipv4_cidr_block' -> 'cidr_block'. --- aws/aws_vpc_secondary_ipv4_cidr_block.go | 6 +++--- aws/aws_vpc_secondary_ipv4_cidr_block_test.go | 4 ++-- website/docs/r/vpc_secondary_ipv4_cidr_block.html.markdown | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/aws/aws_vpc_secondary_ipv4_cidr_block.go b/aws/aws_vpc_secondary_ipv4_cidr_block.go index ca6f81ae714..49ad422bbb4 100644 --- a/aws/aws_vpc_secondary_ipv4_cidr_block.go +++ b/aws/aws_vpc_secondary_ipv4_cidr_block.go @@ -23,7 +23,7 @@ func resourceAwsVpcSecondaryIpv4CidrBlock() *schema.Resource { ForceNew: true, }, - "ipv4_cidr_block": { + "cidr_block": { Type: schema.TypeString, Required: true, ForceNew: true, @@ -38,7 +38,7 @@ func resourceAwsVpcSecondaryIpv4CidrBlockCreate(d *schema.ResourceData, meta int req := &ec2.AssociateVpcCidrBlockInput{ VpcId: aws.String(d.Get("vpc_id").(string)), - CidrBlock: aws.String(d.Get("ipv4_cidr_block").(string)), + CidrBlock: aws.String(d.Get("cidr_block").(string)), } log.Printf("[DEBUG] Creating VPC secondary IPv4 CIDR block: %#v", req) resp, err := conn.AssociateVpcCidrBlock(req) @@ -70,7 +70,7 @@ func resourceAwsVpcSecondaryIpv4CidrBlockRead(d *schema.ResourceData, meta inter for _, cidrAssociation := range vpc.CidrBlockAssociationSet { if aws.StringValue(cidrAssociation.AssociationId) == d.Id() { found = true - d.Set("ipv4_cidr_block", cidrAssociation.CidrBlock) + d.Set("cidr_block", cidrAssociation.CidrBlock) } } if !found { diff --git a/aws/aws_vpc_secondary_ipv4_cidr_block_test.go b/aws/aws_vpc_secondary_ipv4_cidr_block_test.go index c603368e356..42582d29234 100644 --- a/aws/aws_vpc_secondary_ipv4_cidr_block_test.go +++ b/aws/aws_vpc_secondary_ipv4_cidr_block_test.go @@ -128,11 +128,11 @@ resource "aws_vpc" "foo" { resource "aws_vpc_secondary_ipv4_cidr_block" "secondary_cidr" { vpc_id = "${aws_vpc.foo.id}" - ipv4_cidr_block = "172.2.0.0/16" + cidr_block = "172.2.0.0/16" } resource "aws_vpc_secondary_ipv4_cidr_block" "tertiary_cidr" { vpc_id = "${aws_vpc.foo.id}" - ipv4_cidr_block = "170.2.0.0/16" + cidr_block = "170.2.0.0/16" } ` diff --git a/website/docs/r/vpc_secondary_ipv4_cidr_block.html.markdown b/website/docs/r/vpc_secondary_ipv4_cidr_block.html.markdown index b8d8dfd8e46..74c4b1d5561 100644 --- a/website/docs/r/vpc_secondary_ipv4_cidr_block.html.markdown +++ b/website/docs/r/vpc_secondary_ipv4_cidr_block.html.markdown @@ -19,7 +19,7 @@ resource "aws_vpc" "main" { resource "aws_vpc_secondary_ipv4_cidr_block" "secondary_cidr" { vpc_id = "${aws_vpc.main.id}" - ipv4_cidr_block = "172.2.0.0/16" + cidr_block = "172.2.0.0/16" } ``` @@ -28,7 +28,7 @@ resource "aws_vpc_secondary_ipv4_cidr_block" "secondary_cidr" { The following arguments are supported: * `vpc_id` - (Required) The ID of the VPC to make the association with. -* `ipv4_cidr_block` - (Required) The secondary IPv4 CIDR block to associate with the VPC. +* `cidr_block` - (Required) The secondary IPv4 CIDR block to associate with the VPC. ## Attributes Reference From 043935747383a40418239e4489f3de868801bb37 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 29 Jun 2018 10:44:58 -0400 Subject: [PATCH 5/7] 'aws_vpc_secondary_ipv4_cidr_block' -> 'aws_vpc_ipv4_cidr_block_association'. --- ...=> aws_vpc_ipv4_cidr_block_association.go} | 30 +++++++------- ...s_vpc_ipv4_cidr_block_association_test.go} | 35 ++++++++-------- website/aws.erb | 8 ++-- ..._ipv4_cidr_block_association.html.markdown | 40 +++++++++++++++++++ ...pc_secondary_ipv4_cidr_block.html.markdown | 37 ----------------- 5 files changed, 78 insertions(+), 72 deletions(-) rename aws/{aws_vpc_secondary_ipv4_cidr_block.go => aws_vpc_ipv4_cidr_block_association.go} (59%) rename aws/{aws_vpc_secondary_ipv4_cidr_block_test.go => aws_vpc_ipv4_cidr_block_association_test.go} (63%) create mode 100644 website/docs/r/vpc_ipv4_cidr_block_association.html.markdown delete mode 100644 website/docs/r/vpc_secondary_ipv4_cidr_block.html.markdown diff --git a/aws/aws_vpc_secondary_ipv4_cidr_block.go b/aws/aws_vpc_ipv4_cidr_block_association.go similarity index 59% rename from aws/aws_vpc_secondary_ipv4_cidr_block.go rename to aws/aws_vpc_ipv4_cidr_block_association.go index 49ad422bbb4..a07a4f731ca 100644 --- a/aws/aws_vpc_secondary_ipv4_cidr_block.go +++ b/aws/aws_vpc_ipv4_cidr_block_association.go @@ -10,11 +10,11 @@ import ( "github.com/hashicorp/terraform/helper/validation" ) -func resourceAwsVpcSecondaryIpv4CidrBlock() *schema.Resource { +func resourceAwsVpcIpv4CidrBlockAssociation() *schema.Resource { return &schema.Resource{ - Create: resourceAwsVpcSecondaryIpv4CidrBlockCreate, - Read: resourceAwsVpcSecondaryIpv4CidrBlockRead, - Delete: resourceAwsVpcSecondaryIpv4CidrBlockDelete, + Create: resourceAwsVpcIpv4CidrBlockAssociationCreate, + Read: resourceAwsVpcIpv4CidrBlockAssociationRead, + Delete: resourceAwsVpcIpv4CidrBlockAssociationDelete, Schema: map[string]*schema.Schema{ "vpc_id": { @@ -33,34 +33,34 @@ func resourceAwsVpcSecondaryIpv4CidrBlock() *schema.Resource { } } -func resourceAwsVpcSecondaryIpv4CidrBlockCreate(d *schema.ResourceData, meta interface{}) error { +func resourceAwsVpcIpv4CidrBlockAssociationCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn req := &ec2.AssociateVpcCidrBlockInput{ VpcId: aws.String(d.Get("vpc_id").(string)), CidrBlock: aws.String(d.Get("cidr_block").(string)), } - log.Printf("[DEBUG] Creating VPC secondary IPv4 CIDR block: %#v", req) + log.Printf("[DEBUG] Creating VPC IPv4 CIDR block association: %#v", req) resp, err := conn.AssociateVpcCidrBlock(req) if err != nil { - return fmt.Errorf("Error creating VPC secondary IPv4 CIDR block: %s", err.Error()) + return fmt.Errorf("Error creating VPC IPv4 CIDR block association: %s", err) } d.SetId(aws.StringValue(resp.CidrBlockAssociation.AssociationId)) - return resourceAwsVpcSecondaryIpv4CidrBlockRead(d, meta) + return resourceAwsVpcIpv4CidrBlockAssociationRead(d, meta) } -func resourceAwsVpcSecondaryIpv4CidrBlockRead(d *schema.ResourceData, meta interface{}) error { +func resourceAwsVpcIpv4CidrBlockAssociationRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn vpcId := d.Get("vpc_id").(string) vpcRaw, _, err := VPCStateRefreshFunc(conn, vpcId)() if err != nil { - return fmt.Errorf("Error reading VPC: %s", err.Error()) + return fmt.Errorf("Error reading VPC: %s", err) } if vpcRaw == nil { - log.Printf("[WARN] VPC (%s) not found, removing secondary IPv4 CIDR block from state", vpcId) + log.Printf("[WARN] VPC (%s) not found, removing IPv4 CIDR block association from state", vpcId) d.SetId("") return nil } @@ -74,22 +74,22 @@ func resourceAwsVpcSecondaryIpv4CidrBlockRead(d *schema.ResourceData, meta inter } } if !found { - log.Printf("[WARN] VPC secondary IPv4 CIDR block (%s) not found, removing from state", d.Id()) + log.Printf("[WARN] VPC IPv4 CIDR block association (%s) not found, removing from state", d.Id()) d.SetId("") } return nil } -func resourceAwsVpcSecondaryIpv4CidrBlockDelete(d *schema.ResourceData, meta interface{}) error { +func resourceAwsVpcIpv4CidrBlockAssociationDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn - log.Printf("[DEBUG] Deleting VPC secondary IPv4 CIDR block: %s", d.Id()) + log.Printf("[DEBUG] Deleting VPC IPv4 CIDR block association: %s", d.Id()) _, err := conn.DisassociateVpcCidrBlock(&ec2.DisassociateVpcCidrBlockInput{ AssociationId: aws.String(d.Id()), }) if err != nil { - return fmt.Errorf("Error deleting VPC secondary IPv4 CIDR block: %s", err.Error()) + return fmt.Errorf("Error deleting VPC IPv4 CIDR block association: %s", err) } return nil diff --git a/aws/aws_vpc_secondary_ipv4_cidr_block_test.go b/aws/aws_vpc_ipv4_cidr_block_association_test.go similarity index 63% rename from aws/aws_vpc_secondary_ipv4_cidr_block_test.go rename to aws/aws_vpc_ipv4_cidr_block_association_test.go index 42582d29234..7a60173a612 100644 --- a/aws/aws_vpc_secondary_ipv4_cidr_block_test.go +++ b/aws/aws_vpc_ipv4_cidr_block_association_test.go @@ -11,28 +11,28 @@ import ( "github.com/hashicorp/terraform/terraform" ) -func TestAccAwsVpcSecondaryIpv4CidrBlock_basic(t *testing.T) { +func TestAccAwsVpcIpv4CidrBlockAssociation_basic(t *testing.T) { var associationSecondary, associationTertiary ec2.VpcCidrBlockAssociation resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, - CheckDestroy: testAccCheckAwsVpcSecondaryIpv4CidrBlockDestroy, + CheckDestroy: testAccCheckAwsVpcIpv4CidrBlockAssociationDestroy, Steps: []resource.TestStep{ { - Config: testAccAwsVpcSecondaryIpv4CidrBlockConfig, + Config: testAccAwsVpcIpv4CidrBlockAssociationConfig, Check: resource.ComposeTestCheckFunc( - testAccCheckAwsVpcSecondaryIpv4CidrBlockExists("aws_vpc_secondary_ipv4_cidr_block.secondary_cidr", &associationSecondary), - testAccCheckAwsVpcSecondaryIpv4CidrBlock(&associationSecondary, "172.2.0.0/16"), - testAccCheckAwsVpcSecondaryIpv4CidrBlockExists("aws_vpc_secondary_ipv4_cidr_block.tertiary_cidr", &associationTertiary), - testAccCheckAwsVpcSecondaryIpv4CidrBlock(&associationTertiary, "170.2.0.0/16"), + testAccCheckAwsVpcIpv4CidrBlockAssociationExists("aws_vpc_ipv4_cidr_block_association.secondary_cidr", &associationSecondary), + testAccCheckAdditionalAwsVpcIpv4CidrBlock(&associationSecondary, "172.2.0.0/16"), + testAccCheckAwsVpcIpv4CidrBlockAssociationExists("aws_vpc_ipv4_cidr_block_association.tertiary_cidr", &associationTertiary), + testAccCheckAdditionalAwsVpcIpv4CidrBlock(&associationTertiary, "170.2.0.0/16"), ), }, }, }) } -func testAccCheckAwsVpcSecondaryIpv4CidrBlock(association *ec2.VpcCidrBlockAssociation, expected string) resource.TestCheckFunc { +func testAccCheckAdditionalAwsVpcIpv4CidrBlock(association *ec2.VpcCidrBlockAssociation, expected string) resource.TestCheckFunc { return func(s *terraform.State) error { CIDRBlock := association.CidrBlock if *CIDRBlock != expected { @@ -43,11 +43,11 @@ func testAccCheckAwsVpcSecondaryIpv4CidrBlock(association *ec2.VpcCidrBlockAssoc } } -func testAccCheckAwsVpcSecondaryIpv4CidrBlockDestroy(s *terraform.State) error { +func testAccCheckAwsVpcIpv4CidrBlockAssociationDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).ec2conn for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_vpc_secondary_ipv4_cidr_block" { + if rs.Type != "aws_vpc_ipv4_cidr_block_association" { continue } @@ -61,7 +61,7 @@ func testAccCheckAwsVpcSecondaryIpv4CidrBlockDestroy(s *terraform.State) error { for _, ipv4Association := range vpc.CidrBlockAssociationSet { if *ipv4Association.AssociationId == rs.Primary.ID { - return fmt.Errorf("VPC secondary CIDR block still exists") + return fmt.Errorf("VPC CIDR block association still exists") } } @@ -81,7 +81,7 @@ func testAccCheckAwsVpcSecondaryIpv4CidrBlockDestroy(s *terraform.State) error { return nil } -func testAccCheckAwsVpcSecondaryIpv4CidrBlockExists(n string, association *ec2.VpcCidrBlockAssociation) resource.TestCheckFunc { +func testAccCheckAwsVpcIpv4CidrBlockAssociationExists(n string, association *ec2.VpcCidrBlockAssociation) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -114,24 +114,27 @@ func testAccCheckAwsVpcSecondaryIpv4CidrBlockExists(n string, association *ec2.V } if !found { - return fmt.Errorf("VPC secondary CIDR block not found") + return fmt.Errorf("VPC CIDR block association not found") } return nil } } -const testAccAwsVpcSecondaryIpv4CidrBlockConfig = ` +const testAccAwsVpcIpv4CidrBlockAssociationConfig = ` resource "aws_vpc" "foo" { cidr_block = "10.1.0.0/16" + tags { + Name = "terraform-testacc-vpc-ipv4-cidr-block-association" + } } -resource "aws_vpc_secondary_ipv4_cidr_block" "secondary_cidr" { +resource "aws_vpc_ipv4_cidr_block_association" "secondary_cidr" { vpc_id = "${aws_vpc.foo.id}" cidr_block = "172.2.0.0/16" } -resource "aws_vpc_secondary_ipv4_cidr_block" "tertiary_cidr" { +resource "aws_vpc_ipv4_cidr_block_association" "tertiary_cidr" { vpc_id = "${aws_vpc.foo.id}" cidr_block = "170.2.0.0/16" } diff --git a/website/aws.erb b/website/aws.erb index e65e202886a..f4d36921980 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -2230,6 +2230,10 @@ aws_vpc_endpoint_subnet_association + > + aws_vpc_ipv4_cidr_block_association + + > aws_vpc_peering_connection @@ -2242,10 +2246,6 @@ aws_vpc_peering_connection_options - > - aws_vpc_secondary_ipv4_cidr_block - - > aws_vpn_connection diff --git a/website/docs/r/vpc_ipv4_cidr_block_association.html.markdown b/website/docs/r/vpc_ipv4_cidr_block_association.html.markdown new file mode 100644 index 00000000000..14a82e76fbb --- /dev/null +++ b/website/docs/r/vpc_ipv4_cidr_block_association.html.markdown @@ -0,0 +1,40 @@ +--- +layout: "aws" +page_title: "AWS: aws_vpc_ipv4_cidr_block_association" +sidebar_current: "docs-aws-resource-vpc-ipv4-cidr-block-association" +description: |- + Associate additional IPv4 CIDR blocks with a VPC +--- + +# aws_vpc_ipv4_cidr_block_association + +Provides a resource to associate additional IPv4 CIDR blocks with a VPC. + +When a VPC is created, a primary IPv4 CIDR block for the VPC must be specified. +The `aws_vpc_ipv4_cidr_block_association` resource allows further IPv4 CIDR blocks to be added to the VPC. + +## Example Usage + +```hcl +resource "aws_vpc" "main" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_vpc_ipv4_cidr_block_association" "secondary_cidr" { + vpc_id = "${aws_vpc.main.id}" + cidr_block = "172.2.0.0/16" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `cidr_block` - (Required) The additional IPv4 CIDR block to associate with the VPC. +* `vpc_id` - (Required) The ID of the VPC to make the association with. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the VPC CIDR association diff --git a/website/docs/r/vpc_secondary_ipv4_cidr_block.html.markdown b/website/docs/r/vpc_secondary_ipv4_cidr_block.html.markdown deleted file mode 100644 index 74c4b1d5561..00000000000 --- a/website/docs/r/vpc_secondary_ipv4_cidr_block.html.markdown +++ /dev/null @@ -1,37 +0,0 @@ ---- -layout: "aws" -page_title: "AWS: aws_vpc_secondary_ipv4_cidr_block" -sidebar_current: "docs-aws-resource-vpc-secondary-ipv4-cidr-block" -description: |- - Associate a secondary IPv4 CIDR blocks with a VPC ---- - -# aws_vpc_secondary_ipv4_cidr_block - -Provides a resource to associate a secondary IPv4 CIDR blocks with a VPC. - -## Example Usage - -```hcl -resource "aws_vpc" "main" { - cidr_block = "10.0.0.0/16" -} - -resource "aws_vpc_secondary_ipv4_cidr_block" "secondary_cidr" { - vpc_id = "${aws_vpc.main.id}" - cidr_block = "172.2.0.0/16" -} -``` - -## Argument Reference - -The following arguments are supported: - -* `vpc_id` - (Required) The ID of the VPC to make the association with. -* `cidr_block` - (Required) The secondary IPv4 CIDR block to associate with the VPC. - -## Attributes Reference - -The following attributes are exported: - -* `id` - The ID of the VPC CIDR association From 506cc150f7f25b12aec689807e0d7e3323cd8718 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 29 Jun 2018 11:29:08 -0400 Subject: [PATCH 6/7] Minor changes after code review. --- aws/aws_vpc_ipv4_cidr_block_association.go | 4 ++++ website/docs/r/subnet.html.markdown | 21 ++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/aws/aws_vpc_ipv4_cidr_block_association.go b/aws/aws_vpc_ipv4_cidr_block_association.go index a07a4f731ca..1aa5deee43a 100644 --- a/aws/aws_vpc_ipv4_cidr_block_association.go +++ b/aws/aws_vpc_ipv4_cidr_block_association.go @@ -71,6 +71,7 @@ func resourceAwsVpcIpv4CidrBlockAssociationRead(d *schema.ResourceData, meta int if aws.StringValue(cidrAssociation.AssociationId) == d.Id() { found = true d.Set("cidr_block", cidrAssociation.CidrBlock) + break } } if !found { @@ -89,6 +90,9 @@ func resourceAwsVpcIpv4CidrBlockAssociationDelete(d *schema.ResourceData, meta i AssociationId: aws.String(d.Id()), }) if err != nil { + if isAWSErr(err, "InvalidVpcID.NotFound", "") { + return nil + } return fmt.Errorf("Error deleting VPC IPv4 CIDR block association: %s", err) } diff --git a/website/docs/r/subnet.html.markdown b/website/docs/r/subnet.html.markdown index cda826371de..32a6a0e41cb 100644 --- a/website/docs/r/subnet.html.markdown +++ b/website/docs/r/subnet.html.markdown @@ -12,6 +12,8 @@ Provides an VPC subnet resource. ## Example Usage +### Basic Usage + ```hcl resource "aws_subnet" "main" { vpc_id = "${aws_vpc.main.id}" @@ -23,6 +25,23 @@ resource "aws_subnet" "main" { } ``` +### Subnets In Secondary VPC CIDR Blocks + +When managing subnets in one of a VPC's secondary CIDR blocks created using a [`aws_vpc_ipv4_cidr_block_association`](vpc_ipv4_cidr_block_association.html) +resource, it is recommended to reference that resource's `vpc_id` attribute to ensure correct dependency ordering. + +```hcl +resource "aws_vpc_ipv4_cidr_block_association" "secondary_cidr" { + vpc_id = "${aws_vpc.main.id}" + cidr_block = "172.2.0.0/16" +} + +resource "aws_subnet" "in_secondary_cidr" { + vpc_id = "${aws_vpc_ipv4_cidr_block_association.secondary_cidr.vpc_id}" + cidr_block = "172.2.0.0/24" +} +``` + ## Argument Reference The following arguments are supported: @@ -57,4 +76,4 @@ Subnets can be imported using the `subnet id`, e.g. ``` $ terraform import aws_subnet.public_subnet subnet-9d4a7b6c -``` \ No newline at end of file +``` From ff10478925671c53f5d3e91af0b0604d96245a0b Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 29 Jun 2018 13:51:07 -0400 Subject: [PATCH 7/7] Correctly handle VPC IPv4 CIDR block association state changes. --- aws/aws_vpc_ipv4_cidr_block_association.go | 80 ++++++++++++++----- aws/resource_aws_vpc.go | 30 +++++++ ..._ipv4_cidr_block_association.html.markdown | 8 ++ 3 files changed, 100 insertions(+), 18 deletions(-) diff --git a/aws/aws_vpc_ipv4_cidr_block_association.go b/aws/aws_vpc_ipv4_cidr_block_association.go index 1aa5deee43a..54cf6c01e12 100644 --- a/aws/aws_vpc_ipv4_cidr_block_association.go +++ b/aws/aws_vpc_ipv4_cidr_block_association.go @@ -3,13 +3,19 @@ package aws import ( "fmt" "log" + "time" "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/helper/schema" "github.com/hashicorp/terraform/helper/validation" ) +const ( + VpcCidrBlockStateCodeDeleted = "deleted" +) + func resourceAwsVpcIpv4CidrBlockAssociation() *schema.Resource { return &schema.Resource{ Create: resourceAwsVpcIpv4CidrBlockAssociationCreate, @@ -30,6 +36,11 @@ func resourceAwsVpcIpv4CidrBlockAssociation() *schema.Resource { ValidateFunc: validation.CIDRNetwork(16, 28), // The allowed block size is between a /28 netmask and /16 netmask. }, }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, } } @@ -48,36 +59,37 @@ func resourceAwsVpcIpv4CidrBlockAssociationCreate(d *schema.ResourceData, meta i d.SetId(aws.StringValue(resp.CidrBlockAssociation.AssociationId)) + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.VpcCidrBlockStateCodeAssociating}, + Target: []string{ec2.VpcCidrBlockStateCodeAssociated}, + Refresh: vpcIpv4CidrBlockAssociationStateRefresh(conn, d.Get("vpc_id").(string), d.Id()), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 10 * time.Second, + MinTimeout: 5 * time.Second, + } + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for IPv4 CIDR block association (%s) to become available: %s", d.Id(), err) + } + return resourceAwsVpcIpv4CidrBlockAssociationRead(d, meta) } func resourceAwsVpcIpv4CidrBlockAssociationRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn - vpcId := d.Get("vpc_id").(string) - vpcRaw, _, err := VPCStateRefreshFunc(conn, vpcId)() + assocRaw, state, err := vpcIpv4CidrBlockAssociationStateRefresh(conn, d.Get("vpc_id").(string), d.Id())() if err != nil { - return fmt.Errorf("Error reading VPC: %s", err) + return fmt.Errorf("Error reading IPv4 CIDR block association: %s", err) } - if vpcRaw == nil { - log.Printf("[WARN] VPC (%s) not found, removing IPv4 CIDR block association from state", vpcId) + if state == VpcCidrBlockStateCodeDeleted { + log.Printf("[WARN] IPv4 CIDR block association (%s) not found, removing from state", d.Id()) d.SetId("") return nil } - vpc := vpcRaw.(*ec2.Vpc) - found := false - for _, cidrAssociation := range vpc.CidrBlockAssociationSet { - if aws.StringValue(cidrAssociation.AssociationId) == d.Id() { - found = true - d.Set("cidr_block", cidrAssociation.CidrBlock) - break - } - } - if !found { - log.Printf("[WARN] VPC IPv4 CIDR block association (%s) not found, removing from state", d.Id()) - d.SetId("") - } + assoc := assocRaw.(*ec2.VpcCidrBlockAssociation) + d.Set("cidr_block", assoc.CidrBlock) return nil } @@ -96,5 +108,37 @@ func resourceAwsVpcIpv4CidrBlockAssociationDelete(d *schema.ResourceData, meta i return fmt.Errorf("Error deleting VPC IPv4 CIDR block association: %s", err) } + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.VpcCidrBlockStateCodeDisassociating}, + Target: []string{ec2.VpcCidrBlockStateCodeDisassociated, VpcCidrBlockStateCodeDeleted}, + Refresh: vpcIpv4CidrBlockAssociationStateRefresh(conn, d.Get("vpc_id").(string), d.Id()), + Timeout: d.Timeout(schema.TimeoutDelete), + Delay: 10 * time.Second, + MinTimeout: 5 * time.Second, + } + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for VPC IPv4 CIDR block association (%s) to be deleted: %s", d.Id(), err.Error()) + } + return nil } + +func vpcIpv4CidrBlockAssociationStateRefresh(conn *ec2.EC2, vpcId, assocId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + vpc, err := vpcDescribe(conn, vpcId) + if err != nil { + return nil, "", err + } + + if vpc != nil { + for _, cidrAssociation := range vpc.CidrBlockAssociationSet { + if aws.StringValue(cidrAssociation.AssociationId) == assocId { + return cidrAssociation, aws.StringValue(cidrAssociation.CidrBlockState.State), nil + } + } + } + + return "", VpcCidrBlockStateCodeDeleted, nil + } +} diff --git a/aws/resource_aws_vpc.go b/aws/resource_aws_vpc.go index e1687477d0a..1006d9d29ca 100644 --- a/aws/resource_aws_vpc.go +++ b/aws/resource_aws_vpc.go @@ -674,3 +674,33 @@ func awsVpcDescribeVpcAttribute(attribute string, vpcId string, conn *ec2.EC2) ( return resp, nil } + +// vpcDescribe returns EC2 API information about the specified VPC. +// If the VPC doesn't exist, return nil. +func vpcDescribe(conn *ec2.EC2, vpcId string) (*ec2.Vpc, error) { + resp, err := conn.DescribeVpcs(&ec2.DescribeVpcsInput{ + VpcIds: aws.StringSlice([]string{vpcId}), + }) + if err != nil { + if !isAWSErr(err, "InvalidVpcID.NotFound", "") { + return nil, err + } + resp = nil + } + + if resp == nil { + return nil, nil + } + + n := len(resp.Vpcs) + switch n { + case 0: + return nil, nil + + case 1: + return resp.Vpcs[0], nil + + default: + return nil, fmt.Errorf("Found %d VPCs for %s, expected 1", n, vpcId) + } +} diff --git a/website/docs/r/vpc_ipv4_cidr_block_association.html.markdown b/website/docs/r/vpc_ipv4_cidr_block_association.html.markdown index 14a82e76fbb..e33cc898faa 100644 --- a/website/docs/r/vpc_ipv4_cidr_block_association.html.markdown +++ b/website/docs/r/vpc_ipv4_cidr_block_association.html.markdown @@ -33,6 +33,14 @@ The following arguments are supported: * `cidr_block` - (Required) The additional IPv4 CIDR block to associate with the VPC. * `vpc_id` - (Required) The ID of the VPC to make the association with. +## Timeouts + +`aws_vpc_ipv4_cidr_block_association` provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - (Default `10 minutes`) Used for creating the association +- `delete` - (Default `10 minutes`) Used for destroying the association + ## Attributes Reference The following attributes are exported: