on
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!