Expression
Expressions in Terraform are used either to access data from other parts of your infrastructure configuration or to perform operations, such as math or string combinations, where the result will be a new value. For example, simple expressions include 5, "stringValue", True and complex expressions are data.aws_instance.id, var.example ? 12 : "hello".
Expressions in Terraform are used within the argument part of Terraform, where you assign values to identifiers.
Expression syntax
The syntax of a Expression is as follows:
<any_block> {
identifier = expression
}
Here:
<any_block>
: Any block in Terraform, eg. resource block, data block, module block, variable block, output block, locals block, provider block, etc.
identifier
: The name of the argument being set.
expression
: The expression being used to set the argument value.
Let's assume an EC2 instance created with the following configuration:
resource "aws_instance" "server" { name = "dev-server" instance_type = data.aws_instance.prod.instance_type }
In this example:
- name and instance_type are the identifiers, that is, argument names.
- The expressions are dev-server (simple expression) and data.aws_instance.prod.instance_type (complex expression), respectively, i.e., the values being assigned to the arguments.
Types of values
When an expression is evaluated in Terraform, it will return a value. Each value has a type that determines how it will be used and what transformations are able to be applied to it. They are seven types of values in the configuration:
- string
- number
- bool
- list
- set
- map
- null
1. string
String literals are the most complex and commonly used type in Terraform. Terraform supports two syntax options for string literals:
- Quoted syntax: Strings enclosed in double quotes, e.g. "example".
- Heredoc syntax: This is a more flexible syntax when complex strings are required.
Quoted Strings
Quoted strings are a type of string literal in Terraform. They are enclosed within straight double-quote characters (
""
) and can contain Characters and Escape sequences (special characters that change how the string behaves).
provider "aws" {
region = "us-east-1"
}
In the above example, string "us-east-1" is a quoted string. It is enclosed in double quotes and consists of the characters (the text is "us-east-1"). There are, in this case, no escape Sequences, but you can insert them if required.
Escape Sequences in Quoted Strings
In Terraform, when using quoted strings, you could use the escape sequences to introduce special characters or change the behavior of the string. The backslash character (
\
) is used to introduce these escape sequences.
Escape Sequence
Usage
\n
Newline
\r
Carriage Return
\t
Tab
\"
Literal quote (without ending the string)
\\
Literal backslash
\uNNNN
Unicode character from the basic multilingual plane (NNNN is 4 hex digits)
\UNNNNNNNN
Unicode character from supplementary planes (NNNNNNNN is 8 hex digits)
Besides the usual escape sequences that start with a backslash, there are two special escape sequences without a backslash:
Escape Sequence
Usage
$${
Literal ${, without beginning an interpolation sequence.
%%{
Literal %{, without beginning a template directive sequence.
Heredoc String
A heredoc string is a type of string literal in Terraform that allows you to create multi-line strings in a clear and readable way, inspired by Unix shell languages.
How to Write a Heredoc String
- Start with a heredoc marker
<<
(two less-than signs are termed as standard heredoc).
- Use a delimiter word of your choice, such as EOT or EOF, but without having a space after the delimiter word.
- Press Enter to create a new line.
- Write the string contents, which can span multiple lines.
- Finally, to complete the Heredoc string, on a new line, write the delimiter word used in step 2.
Here's an example:
variable "multi_line_var" { default = <<EOF touch terraform.txt echo "content" > terraform.txt EOF } resource "null_resource" "ssh_key" { provisioner "local-exec" { command = var.multi_line_var on_failure = fail } }
Terraform will perform the following actions:
# null_resource.default will be created
+ resource "null_resource" "default" {
+ id = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
Enter a value: yes
null_resource.default: Creating...
null_resource.default: Provisioning with 'local-exec'...
null_resource.default (local-exec): Executing: ["/bin/sh" "-c" " touch terraform.txt\n echo \"content\" > terraform.txt\n"]
null_resource.default: Creation complete after 0s [id=4425172414869377883]
Here, a multi-line variable, multi_line_var, is being set using the heredoc syntax. The contents of the string include two shell commands, namely
touch terraform.txt
and echo "content" > terraform.txt
. EOF here is the delimiter word to be used, and it stands for "end of file". Local-exec provisioner on the null_resource resource uses the variable multi_line_var as the command that will run the shell commands inside the heredoc.
When Terraform runs this configuration, you can see that the spacing from the Heredoc string was preserved. This is because of using the regular Heredoc syntax (
<<
). To avoid this and have better readability of your Terraform configuration file, Terraform provides another variant, the indented Heredoc (<<-
). It analyzes the lines within the string to find the line with the smallest number of leading spaces. It then removes the same quantity of leading spaces from all the lines, and thus gets a readable, well formatted multi-line string. Example:
variable "multi_line_var" { default = <<-EOF touch terraform.txt echo "content" > terraform.txt EOF } resource "null_resource" "ssh_key" { provisioner "local-exec" { command = var.multi_line_var on_failure = fail } }
Terraform will perform the following actions:
# null_resource.default will be created
+ resource "null_resource" "default" {
+ id = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
Enter a value: yes
null_resource.default: Creating...
null_resource.default: Provisioning with 'local-exec'...
null_resource.default (local-exec): Executing: ["/bin/sh" "-c" "touch terraform.txt\n echo \"content\" > terraform.txt\n"]
null_resource.default: Creation complete after 0s [id=4425172414869377883]
In this case among all lines, the first line has the minimum of 3 leading spaces. Then Terraform trims 3 spaces from the beginning of each line in the multi-line string. As a result, the first line that originally had 3 leading spaces now has no leading spaces, and the second line that had originally 6 leading spaces now has only 3 leading spaces.
Escape Sequences in Heredoc Strings
Backslash sequences are not interpreted as escapes in a Heredoc string expression. Instead, the backslash character is interpreted literally. Heredocs support two special escape sequences without using backslashes:
Escape Sequence
Usage
${
This sequence is replaced with a literal ${, without beginning an interpolation sequence.
%%{
This sequence is replaced with a literal %{, without beginning a template directive sequence.
String Templates
Terraform supports string templates which allow the dynamic insertion of expressions into string literals. There are two types of template sequences:
1. Interpolation
Interpolation is a Terraform feature that allows for embedding of expressions inside strings. The
${ ... }
sequence is used to evaluate the expression inside it and insert the result, as a string. If the expression value isn't a string, it is automatically converted to a string before inserting it into the final string. This will enable the use of variables, function calls, and other expressions in your Terraform configuration dynamically and flexibly.
For instance, you might have a variable called amiid whose value is 1234567890. Then you can use interpolation to set the ami attribute of an aws_instance resource in the following way:
variable "amiid" { default = 1234567890 type = number } resource "aws_instance" "server" { ami = "ami-${var.amiid}" instance_type = "t2.micro" }
In the above example, amiid variable is placed as a string into the ami attribute even though its original type is a number. Terraform will automatically convert the number to a string and append it to the string "ami-", the resulting value will be "ami-1234567890".
2. Directives
Directives in Terraform are represented by the
%{ ... }
sequence, and allow you to perform conditional logic, such as if statements, and to iterate over a number of items in a collection (like for loops). Two different directives are supported:
If-Else Directive
This directive chooses between two templates/string literal based on a boolean expression. The syntax is
%{if <Condition>}
${<Templates>}/<Stringliteral>
%{else}
${<Templates>}/<Stringliteral>
%{endif}
Here,
- The boolean expression is evaluated.
- If the expression is true, the template or string literal between %{if ... } and %{else} is used.
- If the expression is false, the template or string literal between %{else} and %{endif} is used, if present. If %{else} is omitted, an empty string is returned.
Suppose you have a web application that has to be deployed on AWS and wish to use different Amazon Machine Images depending on the environment, say production or stage. If-Else instruction can be used here:
variable "environment" { description = "The environment (production or staging)" type = string } variable "ami" { default = "ami-0abcd1234efgh5678" type = string } resource "aws_instance" "server" { ami = "%{if var.environment == "production"}ami-0cff7528ff7ef9b9e%{else}${var.ami}%{endif}" instance_type = "t2.micro" }
The environment variable in this example is one that makes a decision about what AMI to use for the EC2 instance. The If-Else directive %{if var.environment == "production"} performs a check on the value of the environment variable. If the environment variable is "production," then the AMI ID ami-0cff7528ff7ef9b9e (a string literal) is used. Otherwise, if the environment variable is not "production" (in other words, it is "staging" or some other value), then ${var.ami} (a template) is evaluated and used.
For Directive
%{for <item> in <collection>}
${<template>}
%{endfor}
Here,
- The <collection> can be of type complex datatype.
- The <item> is a variable representing each element of the <collection>.
- The ${<template>} is the template to be repeated for each element in the <collection>
resource "aws_s3_bucket" "buckets" { for_each = tomap ({ "my-bucket-1" = "us-east-1" "my-bucket-2" = "us-west-2" "my-bucket-3" = "eu-east-1" }) bucket = each.key region = each.value } output "bucket_ids" { default = <<-EOF %{for id in aws_s3_bucket.buckets[*].id} ${id}, %{endfor} EOF type = list(string) }
Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
Outputs:
name = [
<<-EOT
i-63738683648368,
i-47672672872873,
i-74874863763882,
EOT,
]
When using a for directive, the output can be messy due to unwanted whitespace and newlines as in the above example result. To overcome this problem, Terraform introduces a feature named "strip markers" (
~
) which is used to strip away unwanted whitespace and newlines from the output. This way you are able to format your template for readability without disturbance in final result.
How to Configure Strip Markers:
- Add ~ immediately following the opening characters or preceding the end of a template sequence.
- When ~ is present, the template sequence removes literal whitespace (spaces and newlines) either before or after the sequence.
resource "aws_s3_bucket" "buckets" { for_each = tomap ({ "my-bucket-1" = "us-east-1" "my-bucket-2" = "us-west-2" "my-bucket-3" = "eu-east-1" }) bucket = each.key region = each.value } output "bucket_ids" { default = <<-EOF %{for id in aws_s3_bucket.buckets[*].id ~} ${id}, %{endfor ~} EOF type = list(string) }
Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
Outputs:
name = [
<<-EOT
i-63738683648368,
i-47672672872873,
i-74874863763882,
EOT,
]
Above, the "~" character has been added directly after the opening %{for directive and before the closing %{endfor} directive. This will strip any unwanted whitespace and newlines from the final output.
2. Number
A number is a numeric value could be either a whole number, such as 82, or a decimal value, such as 8.276722. It is a value that can be used in mathematical operations.
variable "version" { default = 3.2.3 }
In the above example, the value 3.2.3 is a decimal number and is assigned to a default identifier.
3. Boolean (bool)
A boolean is a value that is either true or false. It is a simple "yes" or "no" value that can be used in conditional logic, like if-else statements.
variable "production-account" { default = True }
Here, True is a boolean type assigned to the default identifier.
4. List (or Tuple)
A list is a sequence of values, like a collection of items. The representation of the list is by putting square brackets around the values. The items of this list are indexed by a number, starting from 0, to access it.
variable "buckets" { default = ["bucket-1", "bucket-2", "bucket-3"] }
In the above example, ["bucket-1", "bucket-2", "bucket-3"] is of a list of strings type assigned to the default identifier.
5. Set
A set is a collection of unique values, like a group of distinct items. It's similar to a list, but the order of the values doesn't matter, and duplicates are not allowed.
variable "buckets_without_duplication" { default = ["bucket-1", "bucket-2", "bucket-2"] }
Here, ["bucket-1", "bucket-2", "bucket-2"] is a set of string type assigned to the default identifier but the duplicates are removed.
6. Map (or Object)
A map is a collection of key-value pairs much like a dictionary. It is represented by curly brackets (
{}
) around the key-value pairs. Keys are unique strings, whereas their values can be of any type (eg: string, number, etc.).
variable "tech_details" { default = { tech = "terraform" version = "1.3.7" } }
In this example, the map type {tech = "terraform", version = "1.3.7"} was assigned to a default identifier where tech and version were keys with string values.
7. null
In Terraform, null is a special value that represents the absence or omission of a value. It does not have any data type. If you set the argument of a resource to null in Terraform, it uses the default value when the argument has a default value, or it raises an error when the argument is mandatory (required).
The null value is most useful within conditional expressions. You can dynamically omit an argument during execution if some condition is not met, enabling more flexible and dynamic Terraform configurations.
variable "os" { default = "Linux" } resource "null_resource" "ssh_key" { provisioner "local-exec" { command = "echo 'Verfying null output'" interpreter = var.os != "Linux" ? null : ["/bin/bash", "-c"] } }
Terraform will perform the following actions:
# null_resource.default will be created
+ resource "null_resource" "default" {
+ id = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
Enter a value: yes
null_resource.default: Creating...
null_resource.default: Provisioning with 'local-exec'...
null_resource.default (local-exec): Executing: ["/bin/sh" "-c" "echo \"Verfying null output\"]
null_resource.default: Creation complete after 0s [id=4425172414869377883]
In the above example, the interpreter argument of the local-exec provisioner is set to null when the value of os variable is not "Linux". This will let Terraform to use the default value for the interpreter argument being /bin/sh. If os is "Linux", interprets argument is set to ["/bin/bash", "-c"].
Related Pages
- Terraform Blocks - resource block, data block, module block, variable block, output block, locals block, and provider block.
- Variables - Input value and Local value
Feedback
Was this page helpful?