diff --git a/.changelog/27101.txt b/.changelog/27101.txt new file mode 100644 index 000000000000..f6b206250157 --- /dev/null +++ b/.changelog/27101.txt @@ -0,0 +1,3 @@ +```release-note:new-data-source +aws_vpc_ipam_pools +``` diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 951df0734dd4..3a7c395afb5b 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -598,6 +598,7 @@ func New(_ context.Context) (*schema.Provider, error) { "aws_vpc_endpoint_service": ec2.DataSourceVPCEndpointService(), "aws_vpc_endpoint": ec2.DataSourceVPCEndpoint(), "aws_vpc_ipam_pool": ec2.DataSourceIPAMPool(), + "aws_vpc_ipam_pools": ec2.DataSourceIPAMPools(), "aws_vpc_ipam_pool_cidrs": ec2.DataSourceIPAMPoolCIDRs(), "aws_vpc_ipam_preview_next_cidr": ec2.DataSourceIPAMPreviewNextCIDR(), "aws_vpc_peering_connection": ec2.DataSourceVPCPeeringConnection(), diff --git a/internal/service/ec2/find.go b/internal/service/ec2/find.go index 7f3cc74a3893..dcdafcd7c450 100644 --- a/internal/service/ec2/find.go +++ b/internal/service/ec2/find.go @@ -4970,6 +4970,30 @@ func FindIPAMPoolCIDRs(conn *ec2.EC2, input *ec2.GetIpamPoolCidrsInput) ([]*ec2. return output, nil } +func FindIPAMPools(conn *ec2.EC2, input *ec2.DescribeIpamPoolsInput) ([]*ec2.IpamPool, error) { + var output []*ec2.IpamPool + + err := conn.DescribeIpamPoolsPages(input, func(page *ec2.DescribeIpamPoolsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.IpamPools { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if err != nil { + return nil, err + } + + return output, nil +} + func FindKeyPair(conn *ec2.EC2, input *ec2.DescribeKeyPairsInput) (*ec2.KeyPairInfo, error) { output, err := FindKeyPairs(conn, input) diff --git a/internal/service/ec2/ipam_pools_data_source.go b/internal/service/ec2/ipam_pools_data_source.go new file mode 100644 index 000000000000..64b52668f778 --- /dev/null +++ b/internal/service/ec2/ipam_pools_data_source.go @@ -0,0 +1,176 @@ +package ec2 + +import ( + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" +) + +func DataSourceIPAMPools() *schema.Resource { + return &schema.Resource{ + Read: dataSourceIPAMPoolsRead, + + Schema: map[string]*schema.Schema{ + "filter": DataSourceFiltersSchema(), + + "ipam_pools": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + + "address_family": { + Type: schema.TypeString, + Computed: true, + }, + + "allocation_default_netmask_length": { + Type: schema.TypeInt, + Computed: true, + }, + + "allocation_max_netmask_length": { + Type: schema.TypeInt, + Computed: true, + }, + + "allocation_min_netmask_length": { + Type: schema.TypeInt, + Computed: true, + }, + + "allocation_resource_tags": tftags.TagsSchemaComputed(), + + "auto_import": { + Type: schema.TypeBool, + Computed: true, + }, + + "aws_service": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + + "id": { + Type: schema.TypeString, + Optional: true, + }, + + "ipam_scope_id": { + Type: schema.TypeString, + Computed: true, + }, + + "ipam_scope_type": { + Type: schema.TypeString, + Computed: true, + }, + + "ipam_pool_id": { + Type: schema.TypeString, + Computed: true, + }, + + "locale": { + Type: schema.TypeString, + Computed: true, + }, + + "publicly_advertisable": { + Type: schema.TypeBool, + Computed: true, + }, + + "pool_depth": { + Type: schema.TypeInt, + Computed: true, + }, + + "source_ipam_pool_id": { + Type: schema.TypeString, + Computed: true, + }, + + "state": { + Type: schema.TypeString, + Computed: true, + }, + + "tags": tftags.TagsSchemaComputed(), + }, + }, + }, + }, + } +} + +func dataSourceIPAMPoolsRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).EC2Conn + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + input := &ec2.DescribeIpamPoolsInput{} + + filters, filtersOk := d.GetOk("filter") + if filtersOk { + input.Filters = BuildFiltersDataSource(filters.(*schema.Set)) + } + + pools, err := FindIPAMPools(conn, input) + + if err != nil { + return err + } + + d.Set("ipam_pools", flattenIPAMPools(pools, ignoreTagsConfig)) + + d.SetId(meta.(*conns.AWSClient).Region) + + return nil +} + +func flattenIPAMPools(c []*ec2.IpamPool, ignoreTagsConfig *tftags.IgnoreConfig) []interface{} { + pools := []interface{}{} + for _, pool := range c { + pools = append(pools, flattenIPAMPool(pool, ignoreTagsConfig)) + } + return pools +} + +func flattenIPAMPool(p *ec2.IpamPool, ignoreTagsConfig *tftags.IgnoreConfig) map[string]interface{} { + pool := make(map[string]interface{}) + + pool["address_family"] = aws.StringValue(p.AddressFamily) + pool["allocation_default_netmask_length"] = aws.Int64Value(p.AllocationDefaultNetmaskLength) + pool["allocation_max_netmask_length"] = aws.Int64Value(p.AllocationMaxNetmaskLength) + pool["allocation_min_netmask_length"] = aws.Int64Value(p.AllocationMinNetmaskLength) + pool["allocation_resource_tags"] = KeyValueTags(tagsFromIPAMAllocationTags(p.AllocationResourceTags)).Map() + pool["arn"] = aws.StringValue(p.IpamPoolArn) + pool["auto_import"] = aws.BoolValue(p.AutoImport) + pool["aws_service"] = aws.StringValue(p.AwsService) + pool["description"] = aws.StringValue(p.Description) + pool["ipam_scope_id"] = strings.Split(aws.StringValue(p.IpamScopeArn), "/")[1] + pool["ipam_scope_type"] = aws.StringValue(p.IpamScopeType) + pool["locale"] = aws.StringValue(p.Locale) + pool["pool_depth"] = aws.Int64Value(p.PoolDepth) + pool["publicly_advertisable"] = aws.BoolValue(p.PubliclyAdvertisable) + pool["source_ipam_pool_id"] = aws.StringValue(p.SourceIpamPoolId) + pool["state"] = aws.StringValue(p.State) + + if v := p.Tags; v != nil { + pool["tags"] = KeyValueTags(v).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map() + } + + return pool +} diff --git a/internal/service/ec2/ipam_pools_data_source_test.go b/internal/service/ec2/ipam_pools_data_source_test.go new file mode 100644 index 000000000000..1867abaeb302 --- /dev/null +++ b/internal/service/ec2/ipam_pools_data_source_test.go @@ -0,0 +1,166 @@ +package ec2_test + +import ( + "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 TestAccIPAMPoolsDataSource_basic(t *testing.T) { + dataSourceName := "data.aws_vpc_ipam_pools.test" + dataSourceNameTwo := "data.aws_vpc_ipam_pools.testtwo" + resourceName := "aws_vpc_ipam_pool.testthree" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccIPAMPreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccIPAMPoolsDataSourceConfig_basic, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, "ipam_pools.#", "1"), + ), + }, + { + Config: testAccIPAMPoolsDataSourceConfig_basicTwoPools, + Check: resource.ComposeAggregateTestCheckFunc( + // DS 1 finds all 3 pools + resource.TestCheckResourceAttr(dataSourceName, "ipam_pools.#", "3"), + + // DS 2 filters on 1 specific pool to validate attributes + resource.TestCheckResourceAttr(dataSourceNameTwo, "ipam_pools.#", "1"), + + resource.TestCheckResourceAttrPair(dataSourceNameTwo, "ipam_pools.0.address_family", resourceName, "address_family"), + resource.TestCheckResourceAttrPair(dataSourceNameTwo, "ipam_pools.0.allocation_default_netmask_length", resourceName, "allocation_default_netmask_length"), + resource.TestCheckResourceAttrPair(dataSourceNameTwo, "ipam_pools.0.allocation_max_netmask_length", resourceName, "allocation_max_netmask_length"), + resource.TestCheckResourceAttrPair(dataSourceNameTwo, "ipam_pools.0.allocation_min_netmask_length", resourceName, "allocation_min_netmask_length"), + resource.TestCheckResourceAttrPair(dataSourceNameTwo, "ipam_pools.0.allocation_resource_tags.%", resourceName, "allocation_resource_tags.%"), + resource.TestCheckResourceAttrPair(dataSourceNameTwo, "ipam_pools.0.arn", resourceName, "arn"), + resource.TestCheckResourceAttrPair(dataSourceNameTwo, "ipam_pools.0.auto_import", resourceName, "auto_import"), + resource.TestCheckResourceAttrPair(dataSourceNameTwo, "ipam_pools.0.description", resourceName, "description"), + resource.TestCheckResourceAttrPair(dataSourceNameTwo, "ipam_pools.0.aws_service", resourceName, "aws_service"), + resource.TestCheckResourceAttrPair(dataSourceNameTwo, "ipam_pools.0.ipam_scope_id", resourceName, "ipam_scope_id"), + resource.TestCheckResourceAttrPair(dataSourceNameTwo, "ipam_pools.0.ipam_scope_type", resourceName, "ipam_scope_type"), + resource.TestCheckResourceAttrPair(dataSourceNameTwo, "ipam_pools.0.locale", resourceName, "locale"), + resource.TestCheckResourceAttrPair(dataSourceNameTwo, "ipam_pools.0.pool_depth", resourceName, "pool_depth"), + resource.TestCheckResourceAttrPair(dataSourceNameTwo, "ipam_pools.0.publicly_advertisable", resourceName, "publicly_advertisable"), + resource.TestCheckResourceAttrPair(dataSourceNameTwo, "ipam_pools.0.source_ipam_pool_id", resourceName, "source_ipam_pool_id"), + + resource.TestCheckResourceAttr(dataSourceNameTwo, "ipam_pools.0.tags.tagtest", "3"), + ), + }, + }, + }) +} + +func TestAccIPAMPoolsDataSource_empty(t *testing.T) { + dataSourceName := "data.aws_vpc_ipam_pools.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccIPAMPreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccIPAMPoolsDataSourceConfig_empty, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, "ipam_pools.#", "0"), + ), + }, + }, + }) +} + +var testAccIPAMPoolsDataSourceConfig_basic = acctest.ConfigCompose(testAccIPAMPoolConfig_base, ` +resource "aws_vpc_ipam_pool" "test" { + address_family = "ipv4" + ipam_scope_id = aws_vpc_ipam.test.private_default_scope_id + auto_import = true + allocation_default_netmask_length = 32 + allocation_max_netmask_length = 32 + allocation_min_netmask_length = 32 + allocation_resource_tags = { + test = "1" + } + description = "test" +} + +data "aws_vpc_ipam_pools" "test" { + depends_on = [ + aws_vpc_ipam_pool.test + ] +} +`) + +var testAccIPAMPoolsDataSourceConfig_basicTwoPools = acctest.ConfigCompose(testAccIPAMPoolConfig_base, ` +resource "aws_vpc_ipam_pool" "test" { + address_family = "ipv4" + ipam_scope_id = aws_vpc_ipam.test.private_default_scope_id + auto_import = true + allocation_default_netmask_length = 32 + allocation_max_netmask_length = 32 + allocation_min_netmask_length = 32 + allocation_resource_tags = { + test = "1" + } + description = "test" +} + +resource "aws_vpc_ipam_pool" "testtwo" { + address_family = "ipv4" + ipam_scope_id = aws_vpc_ipam.test.private_default_scope_id + allocation_resource_tags = { + test = "2" + } + description = "testtwo" +} + +resource "aws_vpc_ipam_pool" "testthree" { + address_family = "ipv4" + ipam_scope_id = aws_vpc_ipam.test.private_default_scope_id + allocation_default_netmask_length = 32 + allocation_max_netmask_length = 32 + allocation_min_netmask_length = 32 + auto_import = true + allocation_resource_tags = { + test = "3" + } + description = "testthree" + tags = { + tagtest = 3 + } +} + +data "aws_vpc_ipam_pools" "test" { + depends_on = [ + aws_vpc_ipam_pool.test, + aws_vpc_ipam_pool.testtwo, + aws_vpc_ipam_pool.testthree + ] +} + +data "aws_vpc_ipam_pools" "testtwo" { + filter { + name = "description" + values = ["*three*"] + } + + depends_on = [ + aws_vpc_ipam_pool.test, + aws_vpc_ipam_pool.testtwo, + aws_vpc_ipam_pool.testthree + ] +} +`) + +var testAccIPAMPoolsDataSourceConfig_empty = acctest.ConfigCompose(` +data "aws_vpc_ipam_pools" "test" { + filter { + name = "description" + values = ["*none*"] + } +} +`) diff --git a/website/docs/d/vpc_ipam_pools.html.markdown b/website/docs/d/vpc_ipam_pools.html.markdown new file mode 100644 index 000000000000..e5556fce16bf --- /dev/null +++ b/website/docs/d/vpc_ipam_pools.html.markdown @@ -0,0 +1,64 @@ +--- +subcategory: "VPC IPAM (IP Address Manager)" +layout: "aws" +page_title: "AWS: aws_vpc_ipam_pools" +description: |- + Returns details about IPAM pools that match the search parameters provided. +--- + +# Data Source: aws_vpc_ipam_pools + +`aws_vpc_ipam_pools` provides details about IPAM pools. + +This resource can prove useful when IPAM pools are created in another root +module and you need the pool ids as input variables. For example, pools +can be shared via RAM and used to create vpcs with CIDRs from that pool. + +## Example Usage + +```terraform +data "aws_vpc_ipam_pools" "test" { + filter { + name = "description" + values = ["*test*"] + } + + filter { + name = "address-family" + values = ["ipv4"] + } +} +``` + +## Argument Reference + +The arguments of this data source act as filters for querying the available +IPAM Pools in the current region. + +* `filter` - Custom filter block as described below. + +## Attributes Reference + +In addition to all of the arguments above, the following attributes are exported: + +* `ipam_pools` - List of IPAM pools and their attributes. See below for details + +### ipam_pools + +The following attributes are available on each pool entry found. + +* `address_family` - IP protocol assigned to this pool. +* `publicly_advertisable` - Defines whether or not IPv6 pool space is publicly ∂advertisable over the internet. +* `allocation_default_netmask_length` - A default netmask length for allocations added to this pool. If, for example, the CIDR assigned to this pool is 10.0.0.0/8 and you enter 16 here, new allocations will default to 10.0.0.0/16. +* `allocation_max_netmask_length` - The maximum netmask length that will be required for CIDR allocations in this pool. +* `allocation_min_netmask_length` - The minimum netmask length that will be required for CIDR allocations in this pool. +* `allocation_resource_tags` - Tags that are required to create resources in using this pool. +* `arn` - ARN of the pool +* `auto_import` - If enabled, IPAM will continuously look for resources within the CIDR range of this pool and automatically import them as allocations into your IPAM. +* `aws_service` - Limits which service in AWS that the pool can be used in. "ec2", for example, allows users to use space for Elastic IP addresses and VPCs. +* `description` - Description for the IPAM pool. +* `id` - ID of the IPAM pool. +* `ipam_scope_id` - ID of the scope the pool belongs to. +* `locale` - Locale is the Region where your pool is available for allocations. You can only create pools with locales that match the operating Regions of the IPAM. You can only create VPCs from a pool whose locale matches the VPC's Region. +* `source_ipam_pool_id` - ID of the source IPAM pool. +* `tags` - Map of tags to assigned to the resource.