List free AWS Elastic IPs through terraform

I want to use the aws_eip terraform data source to retrieve a list of the Elastic IPs already allocated in my account; I want those that are free, i.e. they have not been associated with any resource.

Here is what I did:

data "aws_eip" "free_elastic_ips" {
  filter {
    name   = "association-id"
    values = ["null"]
  }
}

The problem is that this errors out:

│ Error: no matching EC2 EIP found
│
│   with data.aws_eip.free_elastic_ips,
│   on main.tf line 38, in data "aws_eip" "free_elastic_ips":
│   38: data "aws_eip" "free_elastic_ips" {
│

despite the fact that I have free Elastic IPs in my org, e.g.

Here is the output of aws ec2 describe-addresses for one such IP (values scrambled intentionally)

        {
            "PublicIp": "13.24.111.20",
            "AllocationId": "eipalloc-0d74fe3iktk47aafkc",
            "Domain": "vpc",
            "PublicIpv4Pool": "amazon",
            "NetworkBorderGroup": "us-east-1"
        },

(and this is just one of them)

Hi @pantelis-karamolegko,

I don’t know exactly how this data source expects you to represent the idea of not having an association-id at all, but the filter block is just a thin wrapper around the filters in the underlying EC2 API, so the ec2:DescribeAddresses documentation is what would specify how to query that, if it’s possible. I don’t see it mention the keyword null as having any meaning here, so I don’t think that’s the right way to specify that, though I also don’t know if it’s possible at all.

With all of that said, what you are trying to do sounds suspiciously like you are intending to write a contradictory Terraform configuration that can therefore never converge. If your intent is to find an IP address to use and pass it in to an EC2 instance then that won’t be possible this way, because as soon as you pass a free elastic IP address to an EC2 instance it won’t be free anymore, and so the next time you run Terraform it’ll select another different elastic IP address, and continue flip-flopping between different IP addresses.

Since Terraform is a desired-state system, you’ll need to either explicitly specify which IP address to use (either hard-coded, or via input variables, or similar) or rely on some external service to keep track of which IP addresses are allocated to what as a stateful object, represented as a resource block in Terraform so that the allocations are tracked between Terraform runs.

Unfortunately I don’t think EC2 offers any sort of managed “elastic IP address pool”, and so I don’t think the latter is an option with the EC2 API as currently designed. To implement that you’d need to build your own system for managing elastic IP address pools and then write a Terraform provider to interact with it using a new resource type.

Therefore I think probably the most straightforward option would be to use your own Terraform configuration as the source of record for the allocations, so that the “memory” of which IP address belongs to which other system is in your version control system.

For example:

locals {
  # local.elastic_ip_users has one element for each
  # allocated Elastic IP address. Its values are either
  # the symbolic name for a service that the address
  # is currently allocated to, or null to represent that
  # the address is currently not allocated to any service.
  elastic_ip_users = tomap({
    addr1 = "service1"
    addr2 = "service2"
    addr3 = null
    addr4 = "service3"
  })
}

resource "aws_eip" "allocations" {
  for_each = local.elastic_ip_users
}

locals {
  # local.service_eips lets you look up the allocated IP addresses
  # using the symbolic service keys instead of the IP address
  # keys, ignoring any of the addresses that are not currently
  # allocated to anything.
  service_eips = tomap({
    for addr_key, service_key in local.elastic_ip_users :
    service_key => aws_eip.allocations[addr_key]
    if service_key != null
  })
}

Then when you declare the infrastructure for a particular service, you can look up its IP address allocation as part of the definition, using the symbolic service name:

resource "aws_eip_association" "service2" {
  instance_id   = aws_instance.service2.id
  allocation_id = local.service_eips.service2.association_id
}

Each time you need an elastic IP for a new service, you’d “reserve” it for that service by updating local.elastic_ip_users – either using an existing address that’s null or adding a new element – so that local.elastic_ip_users is the source of record about which elastic IP addresses ought to exist and what each one is currently reserved for.