Clock
7
min read

Dynamic Expressions in Terraform: Some Real-World Examples

Heading

This is some text inside of a div block.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.

As defined by HashiCorp, when it comes to Terraform an expression refers to or computes values within configuration. An expression would be some as simple as a literal value like "hello, world!" or as complex as conditionally returning exported attributes from a resource or data source.

Today we are going to be looking at dynamic expressions, these are expressions where the result returned is dynamic and can be changed by other factors within the environment or inputs.

Let’s have a look at a simple example, we will use a conditional expression to determine if one or two instances of a resource should be used.

In our examples we will be using the scratch provider to demonstrate the concepts.
variable "is_highly_available" {
  type        = bool
  description = <<DESC
    (Optional) if the solution is highly available
    [Default: false]
  DESC
  default     = false
}

resource "scratch_string" "primary" {
  in = format("Primary Instance - var.is_highly_available = '%s'", var.is_highly_available)
}

resource "scratch_string" "secondary" {
  for_each = var.is_highly_available ? { enabled = true } : {}

  in = format("Secondary Instance - var.is_highly_available = '%s'", var.is_highly_available)
}

In the above example we have a variable defined is_highly_available this is something that we would set when running our code to allow for our resources to be configured in a highly available fashion. The first (or primary ) instance does not do any conditional checks as we can safely assume we always want the primary instance to be provisioned. The secondary instance as you can see has a conditional as the value for its for_each.

var.is_highly_available ? { enabled = true } : {}

You can see that on the left-hand side we are checking to see if var.is_highly_available is true, if it is we will create a single secondary instance, if it is false however we will not create one at all. What happens if we need to do something with the IDs produced by our resources and we don’t want to concern ourselves if there are one, two or one hundred resource instances? In the below example we want to do something on a list of our resources ID property. However, we don’t care if we have one or two instances. The use of the try and compact functions gives us a dynamic expression as if there is only the primary instance provisioned we will have a list returning a single value, if the secondary instance is provisioned as well we will have both IDs returned to us.

resource "scratch_list" "dependent_resource" {
  in = compact([
    scratch_string.primary.id,
    try(scratch_string.secondary.*.id, "")
  ])
}

Let's look at another example, where we have the following local block:

locals {
  instances = {
    primary = {
      enabled = true
      region  = "australiaeast"
    }
    secondary = {
      enabled = true
      region  = "australiasoutheast"
    }
    tertiary = {
      enabled = true
      region  = "australiacentral1"
    }
  }
}

This describes our instances, now we might be passing this in via an input variable or perhaps it is static in local variables like we have above. We are going to create our instances and use a dynamic expression to work out if the instance should be created or not. First off we are just going to check to see if the instance should be enabled.

resource "scratch_string" "this" {
  for_each = {
    for k, v in local.instances :
    k => v
    if v.enabled
  }

  in = each.key
}

This will produce us three instances, as they are all marked as enabled the portion that is dynamic for us here is the if check on our for_each as it is running a conditional (or set of conditionals as we will see in a moment) check on our input. What about if we had a restriction on where resources could be deployed?

locals {
  allowed_regions = ["australiaeast", "australiasoutheast"]
}

We will use the above to further dynamically influence Terraforms decision on when resources should or should not be created, let's see that in action!

resource "scratch_string" "this" {
  for_each = {
    for k, v in local.instances :
    k => v
    if v.enabled && contains(local.allowed_regions, v.region)
  }

  in = each.key
}

As you can see we now have the && operator in place and are checking to see if our region is on our allowed list! This is where the power of dynamic expressions starts to come to life for me.

Closing Out

Dynamic expressions allow us to have our Terraform code make some decisions for us, instead of defining a static number of instances of a particular resource we can turn them on or off using conditional expressions, we can merge together the results without concerning ourselves to how many instances of a thing exists.