Interpolation of variables within 'vars.tf'

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’.

  1. I am not worried about the ordering of elements.
  2. 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.