Handling invalid index on empty tuple from list in Terraform

It is common to use count = 0 to archive feature toggle in Terraform. However, it could result empty tuple error when the when it is implement in a resource and the feature is disable. A tuple is a type of list that can contain any number of elements of different types. Index zero is used to access the enabled resource, eg, module.feature[0].id. The tuple is empty when resource is disabled and module.feature[0] does not exist and result an error.

For example, below code runs well,

1
2
3
4
5
6
7
8
locals {
my_tuple = [ { name: "a" }, { name: "b" } ]
result = local.my_tuple[0].name
}

output "result" {
value = local.result
}

Output:

1
2
3
4
$ terraform plan

Changes to Outputs:
+ result = "a"

However, it will throw an error when my_tuple is empty.

1
2
3
4
5
6
7
8
locals {
my_tuple = []
result = local.my_tuple[0].name
}

output "result" {
value = local.result
}

Output:

1
2
3
4
5
6
7
8
9
10
$ terraform plan

│ Error: Invalid index

│ on main.tf line 5, in locals:
│ 5: value = local.my_tuple[0].name
│ ├────────────────
│ │ local.my_tuple is empty tuple

│ The given key does not identify an element in this collection value: the collection has no elements.

Feature toggling is a common use case for empty tuples. However, there are other use cases for empty tuples. For example, if you are using a module that returns a tuple, you may want to handle the case where the tuple is empty.

Here are techniques that can be used to handle empty tuples in Terraform,

Conditional Expressions

A straight forward approach to handling empty tuples is to use a conditional expression to check whether the tuple is empty before trying to access an element. Here’s an example of how to use a conditional expression:

1
2
3
4
5
locals {
my_tuple = []

result = length(local.my_tuple) > 0 ? local.my_tuple[0].name : "default-value"
}

In this example, the length() function is used to check whether my_tuple is empty. If my_tuple is not empty, name in the first element ([0]) is assigned to the result variable. If my_tuple is empty, “default-value” is assigned to the result variable. No empty tuple error is thrown becasue local.my_tuple[0].name is not evaluated when my_tuple is empty.

This can be difficult to read when the variable name is long, as the variable name is repeated twice in the conditional expression.

try Function

Another approach to handling empty tuples is to use the try() function. The try() function is used to attempt to access a value and provide a default value if the value is undefined. Here’s an example of how to use the try() function:

1
2
3
4
5
locals {
my_tuple = []

result = try(local.my_tuple[0].name, "default-value")
}

In this example, the try() function is used to attempt to access name in the first element ([0]) of my_tuple. Since my_tuple is empty, the string “default-value” is used as a default value for value.

for Expression

In Terraform 0.13.0 you can use for expressions to handle empty tuples,

1
2
3
4
5
locals {
my_tuple = []

result = [for i in local.my_tuple: i.name]
}

This is the least readable approach, but bring us to splat expressions below,

Splat Expressions

1
2
3
4
5
locals {
my_tuple = []

result = local.my_tuple[*].name
}

Both for expressions and splat expressions are not supported in Terraform 0.12.29, but they are supported in Terraform 0.13.0 and later. Also, both return an empty tuple if the my_tuple is empty, unlike the conditional expression and try() function which a default value can be specified.

A legacy splat expression local.my_tuple.*.name is also supported in Terraform 0.12.29 and later (v1.4 as of date of this post). However, this is not recommended as it could be removed in a future release.

More information about splat expression, https://developer.hashicorp.com/terraform/language/expressions/splat

It is worth noting that result from the expressions is a tuple. You may need to convert it to a list with tolist() function, or toset() function if you want to remove duplicates and/or order the items. compact() function can be used to remove empty string and null values from a list. You can also use join() function to convert a list to a string.

feature toggling with meta-argument for_each with dynamic blocks

Use of for_each meta-argument with dynamic blocks can be an approch for feature toggling but beyound scope of this post.

References

https://support.hashicorp.com/hc/en-us/articles/9471971461651-ERROR-Invalid-index-on-empty-tuple

Share