diff --git a/.changelog/7537.txt b/.changelog/7537.txt new file mode 100644 index 000000000000..1cd879dd11e5 --- /dev/null +++ b/.changelog/7537.txt @@ -0,0 +1,3 @@ +```release-note:new-data-source +aws_eips +``` \ No newline at end of file diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 4952b0669d18..0380f599051f 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -471,6 +471,7 @@ func Provider() *schema.Provider { "aws_ec2_transit_gateway_vpc_attachment": ec2.DataSourceTransitGatewayVPCAttachment(), "aws_ec2_transit_gateway_vpn_attachment": ec2.DataSourceTransitGatewayVPNAttachment(), "aws_eip": ec2.DataSourceEIP(), + "aws_eips": ec2.DataSourceEIPs(), "aws_instance": ec2.DataSourceInstance(), "aws_instances": ec2.DataSourceInstances(), "aws_internet_gateway": ec2.DataSourceInternetGateway(), diff --git a/internal/service/ec2/eips_data_source.go b/internal/service/ec2/eips_data_source.go new file mode 100644 index 000000000000..9bfedc10694a --- /dev/null +++ b/internal/service/ec2/eips_data_source.go @@ -0,0 +1,76 @@ +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/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" +) + +func DataSourceEIPs() *schema.Resource { + return &schema.Resource{ + Read: dataSourceEIPsRead, + + Schema: map[string]*schema.Schema{ + "allocation_ids": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "filter": DataSourceFiltersSchema(), + "public_ips": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "tags": tftags.TagsSchemaComputed(), + }, + } +} + +func dataSourceEIPsRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).EC2Conn + + input := &ec2.DescribeAddressesInput{} + + if tags, tagsOk := d.GetOk("tags"); tagsOk { + input.Filters = append(input.Filters, BuildTagFilterList( + Tags(tftags.New(tags.(map[string]interface{}))), + )...) + } + + if filters, filtersOk := d.GetOk("filter"); filtersOk { + input.Filters = append(input.Filters, + BuildFiltersDataSource(filters.(*schema.Set))...) + } + + if len(input.Filters) == 0 { + input.Filters = nil + } + + output, err := FindEIPs(conn, input) + + if err != nil { + return fmt.Errorf("error reading EC2 EIPs: %w", err) + } + + var allocationIDs []string + var publicIPs []string + + for _, v := range output { + if aws.StringValue(v.Domain) == ec2.DomainTypeVpc { + allocationIDs = append(allocationIDs, aws.StringValue(v.AllocationId)) + } else { + publicIPs = append(publicIPs, aws.StringValue(v.PublicIp)) + } + } + + d.SetId(meta.(*conns.AWSClient).Region) + d.Set("allocation_ids", allocationIDs) + d.Set("public_ips", publicIPs) + + return nil +} diff --git a/internal/service/ec2/eips_data_source_test.go b/internal/service/ec2/eips_data_source_test.go new file mode 100644 index 000000000000..108ae41d3c0b --- /dev/null +++ b/internal/service/ec2/eips_data_source_test.go @@ -0,0 +1,100 @@ +package ec2_test + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/ec2" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +func TestAccEC2EIPsDataSource_vpcDomain(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + Steps: []resource.TestStep{ + { + Config: testAccEIPsVPCDomainDataSourceConfig(rName), + Check: resource.ComposeTestCheckFunc( + acctest.CheckResourceAttrGreaterThanValue("data.aws_eips.all", "allocation_ids.#", "1"), + resource.TestCheckResourceAttr("data.aws_eips.by_tags", "allocation_ids.#", "1"), + resource.TestCheckResourceAttr("data.aws_eips.by_tags", "public_ips.#", "0"), + resource.TestCheckResourceAttr("data.aws_eips.none", "allocation_ids.#", "0"), + resource.TestCheckResourceAttr("data.aws_eips.none", "public_ips.#", "0"), + ), + }, + }, + }) +} + +func TestAccEC2EIPsDataSource_standardDomain(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckEC2Classic(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccEIPsStandardDomainDataSourceConfig(), + Check: resource.ComposeTestCheckFunc( + acctest.CheckResourceAttrGreaterThanValue("data.aws_eips.all", "public_ips.#", "0"), + ), + }, + }, + }) +} + +func testAccEIPsVPCDomainDataSourceConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_eip" "test1" { + vpc = true + + tags = { + Name = "%[1]s-1" + } +} + +resource "aws_eip" "test2" { + vpc = true + + tags = { + Name = "%[1]s-2" + } +} + +data "aws_eips" "all" { + depends_on = [aws_eip.test1, aws_eip.test2] +} + +data "aws_eips" "by_tags" { + tags = { + Name = "%[1]s-1" + } + + depends_on = [aws_eip.test1, aws_eip.test2] +} + +data "aws_eips" "none" { + filter { + name = "tag-key" + values = ["%[1]s-3"] + } + + depends_on = [aws_eip.test1, aws_eip.test2] +} +`, rName) +} + +func testAccEIPsStandardDomainDataSourceConfig() string { + return acctest.ConfigCompose(acctest.ConfigEC2ClassicRegionProvider(), ` +resource "aws_eip" "test" {} + +data "aws_eips" "all" { + depends_on = [aws_eip.test] +} +`) +} diff --git a/internal/service/ec2/errors.go b/internal/service/ec2/errors.go index 60bf57ed6560..ccc0a84e2186 100644 --- a/internal/service/ec2/errors.go +++ b/internal/service/ec2/errors.go @@ -20,6 +20,8 @@ const ( ErrCodeDependencyViolation = "DependencyViolation" ErrCodeGatewayNotAttached = "Gateway.NotAttached" ErrCodeIncorrectState = "IncorrectState" + ErrCodeInvalidAddressNotFound = "InvalidAddress.NotFound" + ErrCodeInvalidAllocationIDNotFound = "InvalidAllocationID.NotFound" ErrCodeInvalidAssociationIDNotFound = "InvalidAssociationID.NotFound" ErrCodeInvalidAttachmentIDNotFound = "InvalidAttachmentID.NotFound" ErrCodeInvalidCarrierGatewayIDNotFound = "InvalidCarrierGatewayID.NotFound" diff --git a/internal/service/ec2/find.go b/internal/service/ec2/find.go index 36d22abab64b..9b746ea7f050 100644 --- a/internal/service/ec2/find.go +++ b/internal/service/ec2/find.go @@ -79,6 +79,31 @@ func FindClientVPNRouteByID(conn *ec2.EC2, routeID string) (*ec2.DescribeClientV return FindClientVPNRoute(conn, endpointID, targetSubnetID, destinationCidr) } +func FindEIPs(conn *ec2.EC2, input *ec2.DescribeAddressesInput) ([]*ec2.Address, error) { + var addresses []*ec2.Address + + output, err := conn.DescribeAddresses(input) + + if tfawserr.ErrCodeEquals(err, ErrCodeInvalidAddressNotFound, ErrCodeInvalidAllocationIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + for _, v := range output.Addresses { + if v != nil { + addresses = append(addresses, v) + } + } + + return addresses, nil +} + func FindHostByID(conn *ec2.EC2, id string) (*ec2.Host, error) { input := &ec2.DescribeHostsInput{ HostIds: aws.StringSlice([]string{id}), diff --git a/website/docs/d/eips.html.markdown b/website/docs/d/eips.html.markdown new file mode 100644 index 000000000000..e974cbf7e847 --- /dev/null +++ b/website/docs/d/eips.html.markdown @@ -0,0 +1,50 @@ +--- +subcategory: "VPC" +layout: "aws" +page_title: "AWS: aws_eips" +description: |- + Provides a list of Elastic IPs in a region +--- + +# Data Source: aws_eips + +Provides a list of Elastic IPs in a region. + +## Example Usage + +The following shows outputing all Elastic IPs with the a specific tag value. + +```terraform +data "aws_eips" "example" { + tags = { + Env = "dev" + } +} + +# VPC EIPs. +output "allocation_ids" { + value = data.aws_eips.example.allocation_ids +} + +# EC2-Classic EIPs. +output "public_ips" { + value = data.aws_eips.example.public_ips +} +``` + +## Argument Reference + +* `filter` - (Optional) Custom filter block as described below. +* `tags` - (Optional) A map of tags, each pair of which must exactly match a pair on the desired Elastic IPs. + +More complex filters can be expressed using one or more `filter` sub-blocks, which take the following arguments: + +* `name` - (Required) The name of the field to filter by, as defined by + [the underlying AWS API](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeAddresses.html). +* `values` - (Required) Set of values that are accepted for the given field. An Elastic IP will be selected if any one of the given values matches. + +## Attributes Reference + +* `id` - AWS Region. +* `allocation_ids` - A list of all the allocation IDs for address for use with EC2-VPC. +* `public_ips` - A list of all the Elastic IP addresses for use with EC2-Classic.