diff --git a/.changelog/27274.txt b/.changelog/27274.txt new file mode 100644 index 000000000000..1facac6ae861 --- /dev/null +++ b/.changelog/27274.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_vpc_ipv6_cidr_block_association: Add `assign_generated_ipv6_cidr_block` and `ipv6_pool` arguments +``` \ No newline at end of file diff --git a/internal/service/ec2/exports_test.go b/internal/service/ec2/exports_test.go index 776ebab657ca..a8c5021245bf 100644 --- a/internal/service/ec2/exports_test.go +++ b/internal/service/ec2/exports_test.go @@ -105,6 +105,7 @@ var ( ResourceVPCEndpointService = resourceVPCEndpointService ResourceVPCEndpointSubnetAssociation = resourceVPCEndpointSubnetAssociation ResourceVPCIPv4CIDRBlockAssociation = resourceVPCIPv4CIDRBlockAssociation + ResourceVPCIPv6CIDRBlockAssociation = resourceVPCIPv6CIDRBlockAssociation ResourceVPCPeeringConnection = resourceVPCPeeringConnection ResourceVPNConnection = resourceVPNConnection ResourceVPNConnectionRoute = resourceVPNConnectionRoute diff --git a/internal/service/ec2/vpc_ipv6_cidr_block_association.go b/internal/service/ec2/vpc_ipv6_cidr_block_association.go index e79c4dd0847a..916684e69a22 100644 --- a/internal/service/ec2/vpc_ipv6_cidr_block_association.go +++ b/internal/service/ec2/vpc_ipv6_cidr_block_association.go @@ -42,6 +42,13 @@ func resourceVPCIPv6CIDRBlockAssociation() *schema.Resource { return nil }, Schema: map[string]*schema.Schema{ + "assign_generated_ipv6_cidr_block": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"ipv6_pool", "ipv6_ipam_pool_id", "ipv6_cidr_block", "ipv6_netmask_length"}, + }, "ipv6_cidr_block": { Type: schema.TypeString, Optional: true, @@ -49,13 +56,11 @@ func resourceVPCIPv6CIDRBlockAssociation() *schema.Resource { ForceNew: true, ValidateFunc: validVPCIPv6CIDRBlock, }, - // ipam parameters are not required by the API but other usage mechanisms are not implemented yet. TODO ipv6 options: - // --amazon-provided-ipv6-cidr-block - // --ipv6-pool "ipv6_ipam_pool_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"assign_generated_ipv6_cidr_block", "ipv6_pool"}, }, "ipv6_netmask_length": { Type: schema.TypeInt, @@ -63,8 +68,13 @@ func resourceVPCIPv6CIDRBlockAssociation() *schema.Resource { ForceNew: true, ValidateFunc: validation.IntInSlice(vpcCIDRValidIPv6Netmasks), ConflictsWith: []string{"ipv6_cidr_block"}, - // This RequiredWith setting should be applied once L57 is completed - // RequiredWith: []string{"ipv6_ipam_pool_id"}, + }, + "ipv6_pool": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"assign_generated_ipv6_cidr_block", "ipv6_ipam_pool_id"}, }, names.AttrVPCID: { Type: schema.TypeString, @@ -89,6 +99,10 @@ func resourceVPCIPv6CIDRBlockAssociationCreate(ctx context.Context, d *schema.Re VpcId: aws.String(vpcID), } + if v, ok := d.GetOk("assign_generated_ipv6_cidr_block"); ok { + input.AmazonProvidedIpv6CidrBlock = aws.Bool(v.(bool)) + } + if v, ok := d.GetOk("ipv6_cidr_block"); ok { input.Ipv6CidrBlock = aws.String(v.(string)) } @@ -101,7 +115,10 @@ func resourceVPCIPv6CIDRBlockAssociationCreate(ctx context.Context, d *schema.Re input.Ipv6NetmaskLength = aws.Int32(int32(v.(int))) } - log.Printf("[DEBUG] Creating EC2 VPC IPv6 CIDR Block Association: %#v", input) + if v, ok := d.GetOk("ipv6_pool"); ok { + input.Ipv6Pool = aws.String(v.(string)) + } + output, err := conn.AssociateVpcCidrBlock(ctx, input) if err != nil { @@ -110,9 +127,7 @@ func resourceVPCIPv6CIDRBlockAssociationCreate(ctx context.Context, d *schema.Re d.SetId(aws.ToString(output.Ipv6CidrBlockAssociation.AssociationId)) - _, err = waitVPCIPv6CIDRBlockAssociationCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)) - - if err != nil { + if _, err := waitVPCIPv6CIDRBlockAssociationCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { return sdkdiag.AppendErrorf(diags, "waiting for EC2 VPC (%s) IPv6 CIDR block (%s) to become associated: %s", vpcID, d.Id(), err) } @@ -135,7 +150,12 @@ func resourceVPCIPv6CIDRBlockAssociationRead(ctx context.Context, d *schema.Reso return sdkdiag.AppendErrorf(diags, "reading EC2 VPC IPv6 CIDR Block Association (%s): %s", d.Id(), err) } + ipv6PoolID := aws.ToString(vpcIpv6CidrBlockAssociation.Ipv6Pool) + isAmazonIPv6Pool := ipv6PoolID == amazonIPv6PoolID + + d.Set("assign_generated_ipv6_cidr_block", isAmazonIPv6Pool) d.Set("ipv6_cidr_block", vpcIpv6CidrBlockAssociation.Ipv6CidrBlock) + d.Set("ipv6_pool", ipv6PoolID) d.Set(names.AttrVPCID, vpc.VpcId) return diags @@ -158,9 +178,7 @@ func resourceVPCIPv6CIDRBlockAssociationDelete(ctx context.Context, d *schema.Re return sdkdiag.AppendErrorf(diags, "deleting EC2 VPC IPv6 CIDR Block Association (%s): %s", d.Id(), err) } - err = waitVPCIPv6CIDRBlockAssociationDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)) - - if err != nil { + if err := waitVPCIPv6CIDRBlockAssociationDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { return sdkdiag.AppendErrorf(diags, "waiting for EC2 VPC IPv6 CIDR block (%s) to become disassociated: %s", d.Id(), err) } diff --git a/internal/service/ec2/vpc_ipv6_cidr_block_association_test.go b/internal/service/ec2/vpc_ipv6_cidr_block_association_test.go index 6cd2e6f7af3e..be53c000c7dc 100644 --- a/internal/service/ec2/vpc_ipv6_cidr_block_association_test.go +++ b/internal/service/ec2/vpc_ipv6_cidr_block_association_test.go @@ -7,17 +7,82 @@ import ( "context" "fmt" "strings" + "testing" "github.com/aws/aws-sdk-go-v2/aws" awstypes "github.com/aws/aws-sdk-go-v2/service/ec2/types" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" ) +func TestAccVPCIPv6CIDRBlockAssociation_basic(t *testing.T) { + ctx := acctest.Context(t) + var associationSecondary, associationTertiary awstypes.VpcIpv6CidrBlockAssociation + resource1Name := "aws_vpc_ipv6_cidr_block_association.secondary_cidr" + resource2Name := "aws_vpc_ipv6_cidr_block_association.tertiary_cidr" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckVPCIPv6CIDRBlockAssociationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccVPCIPv6CIDRBlockAssociationConfig_amazonProvided(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckVPCIPv6CIDRBlockAssociationExists(ctx, resource1Name, &associationSecondary), + testAccCheckVPCIPv6CIDRBlockAssociationExists(ctx, resource2Name, &associationTertiary), + resource.TestCheckResourceAttr(resource1Name, "ipv6_pool", "Amazon"), + resource.TestCheckResourceAttr(resource2Name, "ipv6_pool", "Amazon"), + ), + }, + { + ResourceName: resource1Name, + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: resource2Name, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccVPCIPv6CIDRBlockAssociation_disappears(t *testing.T) { + ctx := acctest.Context(t) + var associationSecondary, associationTertiary awstypes.VpcIpv6CidrBlockAssociation + resource1Name := "aws_vpc_ipv6_cidr_block_association.secondary_cidr" + resource2Name := "aws_vpc_ipv6_cidr_block_association.tertiary_cidr" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckVPCIPv6CIDRBlockAssociationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccVPCIPv6CIDRBlockAssociationConfig_amazonProvided(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckVPCIPv6CIDRBlockAssociationExists(ctx, resource1Name, &associationSecondary), + testAccCheckVPCIPv6CIDRBlockAssociationExists(ctx, resource2Name, &associationTertiary), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfec2.ResourceVPCIPv6CIDRBlockAssociation(), resource1Name), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + func testAccCheckVPCIPv6CIDRBlockAssociationDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Client(ctx) @@ -78,3 +143,26 @@ func testAccCheckVPCAssociationIPv6CIDRPrefix(association *awstypes.VpcIpv6CidrB return nil } } + +func testAccVPCIPv6CIDRBlockAssociationConfig_amazonProvided(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + assign_generated_ipv6_cidr_block = true + + tags = { + Name = %[1]q + } +} + +resource "aws_vpc_ipv6_cidr_block_association" "secondary_cidr" { + vpc_id = aws_vpc.test.id + assign_generated_ipv6_cidr_block = true +} + +resource "aws_vpc_ipv6_cidr_block_association" "tertiary_cidr" { + vpc_id = aws_vpc.test.id + assign_generated_ipv6_cidr_block = true +} +`, rName) +} diff --git a/website/docs/r/vpc_ipv6_cidr_block_association.html.markdown b/website/docs/r/vpc_ipv6_cidr_block_association.html.markdown index e11ebc8da372..b4a4275de594 100644 --- a/website/docs/r/vpc_ipv6_cidr_block_association.html.markdown +++ b/website/docs/r/vpc_ipv6_cidr_block_association.html.markdown @@ -29,9 +29,11 @@ resource "aws_vpc_ipv6_cidr_block_association" "test" { This resource supports the following arguments: -* `ipv6_cidr_block` - (Optional) The IPv6 CIDR block for the VPC. CIDR can be explicitly set or it can be derived from IPAM using `ipv6_netmask_length`. This parameter is required if `ipv6_netmask_length` is not set and the IPAM pool does not have `allocation_default_netmask` set. -* `ipv6_ipam_pool_id` - (Required) The ID of an IPv6 IPAM pool you want to use for allocating this VPC's CIDR. IPAM is a VPC feature that you can use to automate your IP address management workflows including assigning, tracking, troubleshooting, and auditing IP addresses across AWS Regions and accounts. -* `ipv6_netmask_length` - (Optional) The netmask length of the IPv6 CIDR you want to allocate to this VPC. Requires specifying a `ipv6_ipam_pool_id`. This parameter is optional if the IPAM pool has `allocation_default_netmask` set, otherwise it or `cidr_block` are required +* `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 IPv6 addresses, or the size of the CIDR block. Default is `false`. Conflicts with `ipv6_pam_pool_id`, `ipv6_pool`, `ipv6_cidr_block` and `ipv6_netmask_length`. +* `ipv6_cidr_block` - (Optional) The IPv6 CIDR block for the VPC. CIDR can be explicitly set or it can be derived from IPAM using `ipv6_netmask_length`. This parameter is required if `ipv6_netmask_length` is not set and the IPAM pool does not have `allocation_default_netmask` set. Conflicts with `assign_generated_ipv6_cidr_block`. +* `ipv6_ipam_pool_id` - - (Optional) The ID of an IPv6 IPAM pool you want to use for allocating this VPC's CIDR. IPAM is a VPC feature that you can use to automate your IP address management workflows including assigning, tracking, troubleshooting, and auditing IP addresses across AWS Regions and accounts. Conflict with `assign_generated_ipv6_cidr_block` and `ipv6_ipam_pool_id`. +* `ipv6_netmask_length` - (Optional) The netmask length of the IPv6 CIDR you want to allocate to this VPC. Requires specifying a `ipv6_ipam_pool_id`. This parameter is optional if the IPAM pool has `allocation_default_netmask` set, otherwise it or `ipv6_cidr_block` are required. Conflicts with `assign_generated_ipv6_cidr_block` and `ipv6_ipam_pool_id`. +* `ipv6_pool` - (Optional) The ID of an IPv6 address pool from which to allocate the IPv6 CIDR block. Conflicts with `ipv6_pam_pool_id`, `ipv6_pool`. * `vpc_id` - (Required) The ID of the VPC to make the association with. ## Timeouts