Hi,
Is there a way to ‘refer’ to another variable within ‘vars.tf’?
variable "jar_version" {
default = "1.0"
}
variable "test_var" {
type = set(object({
s3_jar_version = string
}))
default = {
s3_jar_version = var.jar_version
}
}
Due to external requirements outside my control, I need to define ‘jar_version’ variable outside the ‘set/list’ of objects. And I use ‘test_var’ to create a set of resources using ‘for_each’ loop.
Terraform Version :- 1.0.0
Please let me know if there is a solution to this issue. Thanks in advance.
I don’t know if it is possible but it would be ‘OK’ for me if I can look up a ‘variable’(jar_version) by ‘for_each’ ‘key’ value.
Hi @coolbreeze,
The default value for a variable must always be a static value (no references) but you can use its value in combination with other values to derive a merged value. For example, you could declare a local value which represents the final “test var” after merging in all of the s3_jar_version
values:
locals {
test_thing = toset([
for o in var.test_var : {
s3_jar_version = coalesce(o.s3_jar_version, var.jar_version)
}
])
}
You can then use local.test_thing
elsewhere in your module to access the final set, with s3_jar_version
populated for all elements.
I do have a caution that isn’t directly related to your question: the behavior of a set is to remove any duplicate values that are equal, because with a set a particular value is either in the set or not. Since null
is always equal to itself, if the caller wrote out multiple elements with s3_jar_version
set to null
then they’d all be coalesced together into a single element here. Perhaps in your real configuration there are other attributes inside var.test_var
that make that not important, or perhaps it’s okay because you want to deduplicate the items by JAR version anyway, but I just wanted to point that out because I expect that if you did unexpectedly run into that it might not be easy to quickly understand what had happened.
Thanks @apparentlymart . ‘coalesce’ perfectly worked if there is single variable(jar_version). But what if there are multiple variables as below?
variable “jar_version” {
default = “1.0”
}
variable “jar2_version” {
default = “2.0”
}
variable “test_var” {
type = set(object({
s3_jar_version = string
s3_jar2_version = string
}))
default = {
s3_jar_version = var.jar_version
s3_jar2_version = var.jar2_version
}
}
The best approach would have been that each object in ‘test_var’ sets its own ‘jar_version’ and I can easily iterate the list of objects and set ‘jar_version’ per ‘resource’. But because of external requirements, I forced to define simple variables like ‘s3_jar_version’, ‘s3_jar2_version’ in ‘vars.tf’. I can’t even use a ‘map’ variable(because of external requirement) so that I do a lookup in the ‘for_each’ loop.
I can define multiple ‘resource’ blocks explicitly, but I want my terraform code to be ‘dynamic’ in such a way that the user can define any number of resources just by adding a new element to the ‘test_var’ list of objects.
Thanks for the advice on the ‘set’. The object is complex with 10-15 attributes. I had chosen ‘set’ for the below 2 reasons. Please let me know if ‘set’ is not the right choice, instead, I should go for ‘tuple’ or ‘list’.
- I am not worried about the ordering of elements.
- For 3-4 objects which I define in ‘test_var’, I don’t think performance matters. But since I thought I would be using 'find an element in a list of elements, I assumed(based on python exp) ‘set’ will be faster than ‘list’ in finding an element.
Thanks in advance.
If you will have a separate variable for each of the attributes then there would be no alternative but to write a separate coalesce
rule for each one inside the for
expression.
However, if you’re able to refine the problem to make all of the defaults be provided in a single object-typed variable that matches the object type in your set then you can generalize it:
variable "test_var_defaults" {
type = object({
s3_jar_version = string
})
}
variable "test_var" {
type = set(object({
s3_jar_version = string
}))
}
locals {
test_thing = toset([
for o in var.test_var : {
for k, v in o : k => coalesce(v, var.test_var_defaults[k])
}
])
}
This can work because then you can draw values out of the defaults object dynamically as part of the expression, rather than having to specify a separate variable for each of the attributes.
Thanks, @apparentlymart. Unfortunately I in ‘former’( If you will have a separate variable for each of the attributes then there would be no alternative but to write a separate coalesce
rule for each one inside the for
expression.) situation than a latter one.
Also, I did another change, inside of the ‘for_each’ loop in the ‘resource’ section in the module. I used ‘for_each’ while calling the module. This made code a little cleaner I guess. Module ‘resource’ section is exactly creating one resource(Instead of creating multiple resources earlier).
Current
module “iam” {
for_each = { for i in var.iam_config : i.iam_name => i }
source = “…/…/…/…/modules/iam”
…
…
}
===module iam/iam_role===
resource “aws_iam_role” “iam_role” {
…
…
}
Earlier
module “iam” {
source = “…/…/…/…/modules/iam”
…
…
}
resource “aws_iam_role” “iam_role” {
for_each = { for i in var.iam_config : i.iam_name => i }
…
…
}
Thanks.