This is a short tutorial on how to sort a list of Terraform objects by a specific attribute, which isn’t built-in to Terraform. This example will sort AWS VPC subnets based on their amount of available IP addresses.

Unfortunately (as of this writing), Terraform has no way of sorting a list of objects by a given attribute of the object, which can be problematic at times. Say that you wanted to sort some AWS subnets in descending order based on how many IP addresses were available in each subnet. This could be because you’d want to always place EC2 VMs in the subnet with the highest amount of IPs left, so that no subnet is overloaded with VMs. This is currently impossible in Terraform, so below is a workaround to this problem.

The Workaround#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# generate a list of object data, which are subnets from a VPC in this example
data "aws_subnets" "example" {
  filter {
    name   = "vpc-id"
    values = [var.vpc_id]
  }
}
data "aws_subnet" "example" {
  for_each = toset(data.aws_subnets.example.ids)
  id       = each.value
}

# WORKAROUND: order objects (aws_subnet) in descending order of an attribute (available_ip_address_count)
locals {
  sorted_subnet_ips = reverse(distinct(sort([
    for subnet in data.aws_subnet.example: format("%05d", subnet.available_ip_address_count)
  ])))

  sorted_subnets = compact(flatten([
    for ip_count in local.sorted_subnet_ips: [
      for subnet in data.aws_subnet.example: 
        subnet.id if subnet.available_ip_address_count == tonumber(ip_count)
    ]
  ]))
}

# use the sorted subnets however needed
resource "aws_instance" "app" {
  ami           = var.ami
  instance_type = "t2.micro"

  # use the subnet with the most amount of available IPs
  subnet_id   = local.sorted_subnets[0]
}

How it Works#

Objects aren’t sortable, but usually their attributes are (strings, numbers, etc.). This workaround creates a list containing the values of a given attribute, each value coming from each object in the object list. The attribute list is sorted, and from here, we can place the objects in the same order as their values appear in the attribute list. A few notes on the above code:

  • reverse is used to sort in descending order, as sort goes in ascending order by default.1
  • distinct is used to remove any duplicate attribute values.2
  • format is used to left-pad the strings with zero, so that numbers can be sorted. Terraform casts numbers to strings first before sorting, which results in incorrect ordering.3
  • tonumber is used to convert the attribute value back into a number, since sort type casts its contents into strings.4
  • compact is used to remove any empty string elements from the list.5
  • flatten is used to ensure the resulting list is 1-dimensional.6

General Workaround#

Below is a generalization of the above, to work with generally any object/attribute combo. Just substitute in the names of the actual resource types being used.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
locals {
  sorted_attribute_values = reverse(distinct(sort([
    for object in object_type.example: object.attribute
  ])))

  sorted_object_ids = compact(flatten([
    for value in local.sorted_attribute_values: [
      for object in object_type.example: 
        object.id if object.attribute == value
    ]
  ]))
}

  1. HashiCorp Developer | Terraform: sort Function. https://developer.hashicorp.com/terraform/language/functions/sort ↩︎

  2. HashiCorp Developer | Terraform: distinct Function. https://developer.hashicorp.com/terraform/language/functions/distinct ↩︎

  3. Jonathan Share’s Blog: Sorting numerically in Terraform. https://blog.sharebear.co.uk/2022/01/sorting-numerically-in-terraform/ ↩︎

  4. HashiCorp Developer | Terraform: tonumber Function. https://developer.hashicorp.com/terraform/language/functions/tonumber ↩︎

  5. HashiCorp Developer | Terraform: compact Function. https://developer.hashicorp.com/terraform/language/functions/compact ↩︎

  6. HashiCorp Developer | Terraform: flatten Function. https://developer.hashicorp.com/terraform/language/functions/flatten ↩︎