diff --git a/.changelog/22643.txt b/.changelog/22643.txt new file mode 100644 index 00000000000..fa2f940e219 --- /dev/null +++ b/.changelog/22643.txt @@ -0,0 +1,3 @@ +```release-note:new-data-source +aws_vpc_ipam_preview_next_cidr +``` diff --git a/internal/provider/provider.go b/internal/provider/provider.go index b05196f8a35..cbc877adc5d 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -484,6 +484,7 @@ func Provider() *schema.Provider { "aws_vpc_endpoint_service": ec2.DataSourceVPCEndpointService(), "aws_vpc_endpoint": ec2.DataSourceVPCEndpoint(), "aws_vpc_ipam_pool": ec2.DataSourceVPCIpamPool(), + "aws_vpc_ipam_preview_next_cidr": ec2.DataSourceVPCIpamPreviewNextCidr(), "aws_vpc_peering_connection": ec2.DataSourceVPCPeeringConnection(), "aws_vpc_peering_connections": ec2.DataSourceVPCPeeringConnections(), "aws_vpc": ec2.DataSourceVPC(), diff --git a/internal/service/ec2/vpc_ipam_preview_next_cidr_data_source.go b/internal/service/ec2/vpc_ipam_preview_next_cidr_data_source.go new file mode 100644 index 00000000000..3fa0ffd3469 --- /dev/null +++ b/internal/service/ec2/vpc_ipam_preview_next_cidr_data_source.go @@ -0,0 +1,91 @@ +package ec2 + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/flex" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +func DataSourceVPCIpamPreviewNextCidr() *schema.Resource { + return &schema.Resource{ + Read: dataSourceVPCIpamPreviewNextCidrRead, + + Schema: map[string]*schema.Schema{ + "cidr": { + Type: schema.TypeString, + Computed: true, + }, + "disallowed_cidrs": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.Any( + verify.ValidIPv4CIDRNetworkAddress, + // Follow the numbers used for netmask_length + validation.IsCIDRNetwork(0, 32), + ), + }, + }, + "ipam_pool_id": { + Type: schema.TypeString, + Required: true, + }, + "netmask_length": { + // Possible netmask lengths for IPv4 addresses are 0 - 32. + // AllocateIpamPoolCidr API + // - If there is no DefaultNetmaskLength allocation rule set on the pool, + // you must specify either the NetmaskLength or the CIDR. + // - If the DefaultNetmaskLength allocation rule is set on the pool, + // you can specify either the NetmaskLength or the CIDR and the + // DefaultNetmaskLength allocation rule will be ignored. + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(0, 32), + }, + }, + } +} + +func dataSourceVPCIpamPreviewNextCidrRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).EC2Conn + poolId := d.Get("ipam_pool_id").(string) + + input := &ec2.AllocateIpamPoolCidrInput{ + ClientToken: aws.String(resource.UniqueId()), + IpamPoolId: aws.String(poolId), + PreviewNextCidr: aws.Bool(true), + } + + if v, ok := d.GetOk("disallowed_cidrs"); ok && v.(*schema.Set).Len() > 0 { + input.DisallowedCidrs = flex.ExpandStringSet(v.(*schema.Set)) + } + + if v, ok := d.GetOk("netmask_length"); ok { + input.NetmaskLength = aws.Int64(int64(v.(int))) + } + + output, err := conn.AllocateIpamPoolCidr(input) + + if err != nil { + return fmt.Errorf("Error previewing next cidr from IPAM pool (%s): %w", d.Get("ipam_pool_id").(string), err) + } + + if output == nil || output.IpamPoolAllocation == nil { + return fmt.Errorf("error previewing next cidr from ipam pool (%s): empty response", poolId) + } + + cidr := output.IpamPoolAllocation.Cidr + + d.Set("cidr", cidr) + d.SetId(encodeVPCIpamPreviewNextCidrID(aws.StringValue(cidr), poolId)) + + return nil +} diff --git a/internal/service/ec2/vpc_ipam_preview_next_cidr_data_source_test.go b/internal/service/ec2/vpc_ipam_preview_next_cidr_data_source_test.go new file mode 100644 index 00000000000..fdf1cadc329 --- /dev/null +++ b/internal/service/ec2/vpc_ipam_preview_next_cidr_data_source_test.go @@ -0,0 +1,168 @@ +package ec2_test + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +func TestAccDataSourceVPCIpamPreviewNextCidr_ipv4Basic(t *testing.T) { + datasourceName := "data.aws_vpc_ipam_preview_next_cidr.test" + netmaskLength := "28" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccIPAMPreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceVPCIpamPreviewNextCidrIpv4Basic(netmaskLength), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(datasourceName, "cidr"), + resource.TestCheckResourceAttrPair(datasourceName, "ipam_pool_id", "aws_vpc_ipam_pool.test", "id"), + resource.TestCheckResourceAttr(datasourceName, "netmask_length", netmaskLength), + ), + }, + }, + }) +} + +func TestAccDataSourceVPCIpamPreviewNextCidr_ipv4Allocated(t *testing.T) { + datasourceName := "data.aws_vpc_ipam_preview_next_cidr.test" + netmaskLength := "28" + allocatedCidr := "172.2.0.0/28" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccIPAMPreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceVPCIpamPreviewNextCidrIpv4Basic(netmaskLength), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(datasourceName, "cidr", allocatedCidr), + resource.TestCheckResourceAttrPair(datasourceName, "ipam_pool_id", "aws_vpc_ipam_pool.test", "id"), + resource.TestCheckResourceAttr(datasourceName, "netmask_length", netmaskLength), + ), + }, + { + Config: testAccDataSourceVPCIpamPreviewNextCidrIpv4Allocated(netmaskLength), + Check: resource.ComposeTestCheckFunc( + // cidr should not change even after allocation + resource.TestCheckResourceAttr(datasourceName, "cidr", allocatedCidr), + resource.TestCheckResourceAttrPair(datasourceName, "ipam_pool_id", "aws_vpc_ipam_pool.test", "id"), + resource.TestCheckResourceAttr(datasourceName, "netmask_length", netmaskLength), + ), + }, + }, + }) +} + +func TestAccDataSourceVPCIpamPreviewNextCidr_ipv4DisallowedCidr(t *testing.T) { + datasourceName := "data.aws_vpc_ipam_preview_next_cidr.test" + disallowedCidr := "172.2.0.0/28" + netmaskLength := "28" + expectedCidr := "172.2.0.16/28" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccIPAMPreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceVPCIpamPreviewNextCidrIpv4DisallowedCidr(netmaskLength, disallowedCidr), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(datasourceName, "cidr", expectedCidr), + resource.TestCheckResourceAttr(datasourceName, "disallowed_cidrs.#", "1"), + resource.TestCheckResourceAttr(datasourceName, "disallowed_cidrs.0", disallowedCidr), + resource.TestCheckResourceAttrPair(datasourceName, "ipam_pool_id", "aws_vpc_ipam_pool.test", "id"), + resource.TestCheckResourceAttr(datasourceName, "netmask_length", netmaskLength), + ), + }, + }, + }) +} + +const testAccDataSourceVPCIpamPreviewNextCidrIpv4Base = ` +data "aws_region" "current" {} + +resource "aws_vpc_ipam" "test" { + description = "test" + operating_regions { + region_name = data.aws_region.current.name + } +} + +resource "aws_vpc_ipam_pool" "test" { + address_family = "ipv4" + ipam_scope_id = aws_vpc_ipam.test.private_default_scope_id + locale = data.aws_region.current.name +} + +resource "aws_vpc_ipam_pool_cidr" "test" { + ipam_pool_id = aws_vpc_ipam_pool.test.id + cidr = "172.2.0.0/24" +} +` + +func testAccDataSourceVPCIpamPreviewNextCidrIpv4Basic(netmaskLength string) string { + return acctest.ConfigCompose( + testAccDataSourceVPCIpamPreviewNextCidrIpv4Base, + fmt.Sprintf(` +data "aws_vpc_ipam_preview_next_cidr" "test" { + ipam_pool_id = aws_vpc_ipam_pool.test.id + netmask_length = %[1]q + + depends_on = [ + aws_vpc_ipam_pool_cidr.test + ] +} +`, netmaskLength)) +} + +func testAccDataSourceVPCIpamPreviewNextCidrIpv4Allocated(netmaskLength string) string { + return acctest.ConfigCompose( + testAccDataSourceVPCIpamPreviewNextCidrIpv4Base, + fmt.Sprintf(` +data "aws_vpc_ipam_preview_next_cidr" "test" { + ipam_pool_id = aws_vpc_ipam_pool.test.id + netmask_length = %[1]q + + depends_on = [ + aws_vpc_ipam_pool_cidr.test + ] +} + +resource "aws_vpc_ipam_pool_cidr_allocation" "test" { + ipam_pool_id = aws_vpc_ipam_pool.test.id + cidr = data.aws_vpc_ipam_preview_next_cidr.test.cidr + + lifecycle { + ignore_changes = [cidr] + } +} +`, netmaskLength)) +} + +func testAccDataSourceVPCIpamPreviewNextCidrIpv4DisallowedCidr(netmaskLength, disallowedCidr string) string { + return testAccDataSourceVPCIpamPreviewNextCidrIpv4Base + fmt.Sprintf(` +data "aws_vpc_ipam_preview_next_cidr" "test" { + ipam_pool_id = aws_vpc_ipam_pool.test.id + netmask_length = %[1]q + + disallowed_cidrs = [ + %[2]q + ] + + depends_on = [ + aws_vpc_ipam_pool_cidr.test + ] +} +`, netmaskLength, disallowedCidr) +} diff --git a/website/docs/d/vpc_ipam_preview_next_cidr.html.markdown b/website/docs/d/vpc_ipam_preview_next_cidr.html.markdown new file mode 100644 index 00000000000..0b6902aac94 --- /dev/null +++ b/website/docs/d/vpc_ipam_preview_next_cidr.html.markdown @@ -0,0 +1,52 @@ +--- +subcategory: "VPC" +layout: "aws" +page_title: "AWS: aws_vpc_ipam_preview_next_cidr" +description: |- + Previews a CIDR from an IPAM address pool. +--- + +# Data Source: aws_vpc_ipam_preview_next_cidr + +Previews a CIDR from an IPAM address pool. Only works for private IPv4. + +~> **NOTE:** This functionality is also encapsulated in a resource sharing the same name. The data source can be used when you need to use the cidr in a calculation of the same Root module, `count` for example. However, once a cidr range has been allocated that was previewed, the next refresh will find a **new** cidr and may force new resources downstream. Make sure to use Terraform's lifecycle `ignore_changes` policy if this is undesirable. + +## Example Usage + +Basic usage: + +```terraform +data "aws_vpc_ipam_preview_next_cidr" "test" { + ipam_pool_id = aws_vpc_ipam_pool.test.id + netmask_length = 28 + + depends_on = [ + aws_vpc_ipam_pool_cidr.test + ] +} + +resource "aws_vpc_ipam_pool_cidr_allocation" "test" { + ipam_pool_id = aws_vpc_ipam_pool.test.id + cidr = data.aws_vpc_ipam_preview_next_cidr.test.cidr + + lifecycle { + ignore_changes = [cidr] + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `disallowed_cidrs` - (Optional) Exclude a particular CIDR range from being returned by the pool. +* `ipam_pool_id` - (Required) The ID of the pool to which you want to assign a CIDR. +* `netmask_length` - (Optional) The netmask length of the CIDR you would like to preview from the IPAM pool. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `cidr` - The previewed CIDR from the pool. +* `id` - The ID of the preview.