Elements Terraform Function
Lets start by taking a brief look at the element function, then explore why this simple function is so useful.
We won’t labour the point here as that’s what the hashicorp help page is for, although sometimes the examples could be better.
The element function looks like this:
element(list, index)
You’ll note it uses an index … like our count index in the previous post so we will expand upon the previous example to demonstrate a real would use case.
To re-cap, one of the limitations of using the count index to create resources is that they are all identical (as they use the same code block). Some things need to be unique, such as the name, other things could do with some variance as well. Lets consider Azure Zones and an example.
Briefly, Zones represent independent data centres in the same region and offer far greater resilience than the use of availability sets. For consumption purposes 3 zones are surfaced up for use in a single deployment. So how can we distribute our counted resources across zones?
At the most basic level we could just try using the count index and basic arithmetic like this:
resource "azurerm_windows_virtual_machine" "windows_vm" {
count = local.windows_vm_count
name = join("-", ["vm", (count.index + 1)])
zone = 1 + count.index
...
(Note: Zones count from 1 to 3 and count index from 0, so I wasn’t just trying to mess with your head in the previous blog with the arithmetic on the name argument!)
Here vm-1 would get zone (1 + 0 = 1), and vm-2 would get (1 + 1 = 2).
Great problem solved, or is it? … not really, as this doesn’t scale past a count of three, since there are only 3 addressable zones (vm-4 would error). Elements function to the rescue!
Element uses a wrap-around function, hence you always receive 1 element of the list, the list doesn’t have to be numbers either. Let look at a quick generic example:
element(["red", "green", "blue"], index)
the index counts from 0, so red=0, green=1, blue=2, (..wrap..) red=3, etc. Wrap-around is the key here so for a given value of 4, this resolves as:
> element(["red", "green", "blue"], 4)
green
Hopefully that’s clear and you got 4 = green too? … You did? Ok, lets apply that to our vm code example:
resource "azurerm_windows_virtual_machine" "windows_vm" {
count = local.windows_vm_count
name = join("-", ["vm", (count.index + 1)])
zone = element([1, 2, 3], count.index)
...
Perfect, the zones argument now rolls-around the 3 zones in a rond-robin style and our vm’s are evenly distributed via our count.index.
Lets consider another similar related example, this one is a classic lifecycle thing, suppose you deployed 4 identical vm’s via a count index, then later on, some of the vm’s need to be resized to dis-similar values!
resource "azurerm_windows_virtual_machine" "windows_vm" {
count = local.windows_vm_count
name = join("-", ["vm", (count.index + 1)])
zone = element([1, 2, 3], count.index)
size = index(["Standard_D4s_v4", "Standard_D8ds_v5", "Standard_D8ds_v5", "Standard_D8ds_v5"], count.index)
...
Note: here we do not need the elements special wrap-around function so as a best practice we instead use a basic index in preference to the element function.
This can obviously be applied to any argument in the counted resource block, and whilst arguably not as declarative as separate resources may help you out where count indexes are already in play. The alternative code re-factoring (and un-counting an existing counted resource, via numerous terraform state move operations) is possible, but it is a significanlty bigger piece of work.