Sorting numerically in Terraform

A simple enough question

I woke up this morning to read the following question in the Terraform IRC channel.

Is there a way to sort a list of numbers in Terraform? Using the sort function, the numbers are first cast to strings and sorted as strings resulting in incorrect ordering.

Unfortunately the user didn’t hang around long enough for an answer so I thought I’ll make a quick blog post with my answer as it’s similar in nature to some of the hacks I’ve been doing in my attempt to solve Advent of Code in Terraform (whole blog series about that on the way).

The problem

First lets take a quick look at an example that demonstrates the problem;

  
  locals {
    unsorted_numbers = [3, 10, 1, 5, 99, 100]
    sorted_numbers   = sort(local.unsorted_numbers)
  }

  output "sorted_numbers" {
    value = local.sorted_numbers
  }
  

If we run terraform refresh on the above we get the following result;


  $ terraform refresh
  ╷
  │ Warning: Empty or non-existent state
  │ 
  │ There are currently no resources tracked in the state, so there is nothing to refresh.
  ╵
  
  Outputs:
  
  sorted_numbers = tolist([
    "1",
    "10",
    "100",
    "3",
    "5",
    "99",
  ])
  

Clearly not the result we intended.

Give it what it wants

So, how can we get the sort function to give us what we want? Simple, we need to give the sort function what it wants, and it want strings. In order to get the strings to sort correctly for us we need to left-pad the strings with zeros so that, for example 1 becomes "001" and 99 becomes "099". In Terraform we can achieve this with a combination of a for expression and a call to format which accepts printf style syntax.

  
  unsorted_strings = [for n in local.unsorted_numbers : format("%03d", n)]

Now that you’ve got strings you can pass these to sort to give you the result you want

  
  sorted_strings   = sort(local.unsorted_strings)

Then if you really need numbers at the end, you need to convert back to numbers using a similar for expression as before.

  
  sorted_numbers   = [for s in local.sorted_strings : tonumber(s)]

Which should give you the desired result.

A small optimisation

While writing this post I found the the formatlist function in the Terraform documentation that means we can simplify the conversion from number to string to the following;

  
  unsorted_strings = formatlist("%03d", local.unsorted_numbers)

Here’s one I made earlier

Putting all together we end up with the following Terraform code;

  
  locals {
    unsorted_numbers = [3, 10, 1, 5, 99, 100]
    unsorted_strings = formatlist("%03d", local.unsorted_numbers)
    sorted_strings   = sort(local.unsorted_strings)
    sorted_numbers   = [for s in local.sorted_strings : tonumber(s)]
  }
  
  output "sorted_numbers" {
    value = local.sorted_numbers
  }

Which results in the following output;

  
  terraform refresh
  ╷
  │ Warning: Empty or non-existent state
  │ 
  │ There are currently no resources tracked in the state, so there is nothing to refresh.
  ╵
  
  Outputs:
  
  sorted_numbers = [
    1,
    3,
    5,
    10,
    99,
    100,
  ]

Finally a numerically sorted list!