- Welcome my DevOps blog./
- 🔰Posts/
- 🗂️My Trainings/
- Terraform Trainings and Certifications/
- Terraform on AWS with SRE & IaC DevOps/
- Terraform Expressions 🔥/
Terraform Expressions 🔥
Table of Contents
Terraform expressions are used to compute values within Terraform configurations, allowing for dynamic and flexible infrastructure management. They can include simple literals, complex references, and various built-in functions to manipulate data types and structures.
More info: Terraform Expressions
Types and Values #
Data types that Terraform expressions can resolve to, and the literal syntaxes for values of those types.
Types #
Strings, numbers, and bools are sometimes called primitive types. Lists/tuples and maps/objects are sometimes called complex types, structural types, or collection types.Primitive types #
string- characters representing some text, like"hello world!".number- a numeric value. Thenumbertype can represent both whole numbers like15and fractional values like6.283185.bool- a boolean value, eithertrueorfalse.boolvalues can be used in conditional logic.
Complex types #
list(ortuple) - a sequence of values, like["us-west-1a", "us-west-1c"]. Identify elements in a list with consecutive whole numbers, starting with zero.set- a collection of unique values that do not have any secondary identifiers or ordering.
Define a set. The following example specifies a set name
example_set:variable "example_set" { type = set(string) default = ["foo", "bar"] }Use the
tolistfunction to convert the set to a list. The following example stores the converted list as a local variable calledexample_list:locals { example_list = tolist(var.example_set) }You can then reference an element in the list:
output "first_element" { value = local.example_list[0] } output "second_element" { value = local.example_list[1] }
map(orobject) - a group of values identified by named labels, like{name = "Mabel", age = 52}. Maps/objects are represented by a pair of curly braces containing a series of<KEY> = <VALUE>pairs:
{
name = "John"
age = 52
}
Key/value pairs can be separated by either a comma or a line break. The keys in a map must be strings.null- a value that represents absence or omission.nullis most useful in conditional expressions, so you can dynamically omit an argument if a condition isn’t met.
More info: Types and Values
Strings and Templates #
Syntaxes for string literals, including interpolation sequences and template directives.
Quoted Strings #
A quoted string is a series of characters delimited by straight double-quote characters (").
"hello"
Escape Sequences #
In quoted strings, the backslash character serves as an escape sequence, with the following characters selecting the escape behaviour:
| Sequence | Replacement |
|---|---|
\n | Newline |
\r | Carriage Return |
\t | Tab |
\" | Literal quote (without terminating the string) |
\\ | Literal backslash |
\uNNNN | Unicode character from the basic multilingual plane (NNNN is four hex digits) |
\UNNNNNNNN | Unicode character from supplementary planes (NNNNNNNN is eight hex digits) |
| There are also two special escape sequences that do not use backslashes: |
| Sequence | Replacement |
|---|---|
$${ | Literal ${, without beginning an interpolation sequence. |
%%{ | Literal %{, without beginning a template directive sequence. |
Heredoc Strings #
Terraform supports a “heredoc” style of string literal inspired by Unix shell languages, which allows multi-line strings to be expressed more clearly.
<<EOT
hello
world
EOT
Terraform also accepts an indented heredoc string variant that is introduced by the <<- sequence:
block {
value = <<-EOT
hello
world
EOT
}
Don’t use “heredoc” strings to generate JSON or YAML. Instead, use the jsonencode function or the yamlencode function so that Terraform can be responsible for guaranteeing valid JSON or YAML syntax.
example = jsonencode({
a = 1
b = "hello"
})
Escape Sequences #
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 that do not use backslashes:
| Sequence | Replacement |
|---|---|
$${ | Literal ${, without beginning an interpolation sequence. |
%%{ | Literal %{, without beginning a template directive sequence. |
Interpolation #
A ${ ... } sequence is an interpolation, which evaluates the expression given between the markers, converts the result to a string if necessary, and then inserts it into the final string:
"Hello, ${var.name}!"
In the above example, the named object var.name is accessed and its value inserted into the string, producing a result like “Hello, Juan!”.
More info: Strings and Templates
References to Values #
How to refer to named values like variables and resource attributes.
The main kinds of named values available in Terraform are:
- Resources
- Input variables
- Local values
- Child module outputs
- Data sources
- Filesystem and workspace info
- Block-local values
More info: References to Values
Operators #
Arithmetic, comparison, and logical operators.
When multiple operators are used together in an expression, they are evaluated in the following order of operations:
!,-(multiplication by-1)*,/,%+,-(subtraction)>,>=,<,<===,!=&&||
More info: Operators
Function Calls #
Syntax for calling Terraform’s built-in functions.
The Terraform language has a number of built-in functions that can be used in expressions to transform and combine values. These are similar to the operators but all follow a common syntax:
<FUNCTION NAME>(<ARGUMENT 1>, <ARGUMENT 2>)
The function name specifies which function to call. Each defined function expects a specific number of arguments with specific value types, and returns a specific value type as a result.
More info: Function Calls
Conditional Expressions #
<CONDITION> ? <TRUE VAL> : <FALSE VAL> expression, which chooses between two values based on a bool condition.
The syntax of a conditional expression is as follows:
condition ? true_val : false_val
If condition is true then the result is true_val. If condition is false then the result is false_val.
A common use of conditional expressions is to define defaults to replace invalid values:
var.a == "" ? "default-a" : var.a
If var.a is an empty string then the result is "default-a", but otherwise it is the actual value of var.a.
Use the logical operators && (AND), || (OR), and ! (NOT) to combine multiple conditions together.
condition = var.name != "" && lower(var.name) == var.name
contains Function #
Use the contains function to test whether a given value is one of a set of predefined valid values.
condition = contains(["STAGE", "PROD"], var.environment)
length Function #
Use the length function to test a collection’s length and require a non-empty list or map.
condition = length(var.items) != 0
for Expressions #
Use for expressions in conjunction with the functions alltrue and anytrue to test whether a condition holds for all or for any elements of a collection.
condition = alltrue([
for v in var.instances : contains(["t2.micro", "m3.medium"], v.type)
])
can Function #
Use the can function to concisely use the validity of an expression as a condition. It returns true if its given expression evaluates successfully and false if it returns any error.
condition = can(regex("^[a-z]+$", var.name))
self Object #
Use the self object in postcondition blocks to refer to attributes of the instance under evaluation.
resource "aws_instance" "example" {
instance_type = "t2.micro"
ami = "ami-abc123"
lifecycle {
postcondition {
condition = self.instance_state == "running"
error_message = "EC2 instance must be running."
}
}
}
each and count Objects #
In blocks where for_each or count are set, use each and count objects to refer to other resources that are expanded in a chain.
variable "vpc_cidrs" {
type = set(string)
}
data "aws_vpc" "example" {
for_each = var.vpc_cidrs
filter {
name = "cidr"
values = [each.key]
}
}
resource "aws_internet_gateway" "example" {
for_each = data.aws_vpc.example
vpc_id = each.value.id
lifecycle {
precondition {
condition = data.aws_vpc.example[each.key].state == "available"
error_message = "VPC ${each.key} must be available."
}
}
}
More info: Conditional Expressions
For Expressions #
Expressions like [for s in var.list : upper(s)], which can transform a complex type value into another complex type value.
More info: For Expressions
Splat Expressions #
Expressions like var.list[*].id, which can extract simpler collections from more complicated expressions.
A splat expression provides a more concise way to express a common operation that could otherwise be performed with a for expression.
If var.list is a list of objects that all have an attribute id, then a list of the ids could be produced with the following for expression:
[for o in var.list : o.id]
This is equivalent to the following splat expression:
var.list[*].id
The special [*] symbol iterates over all of the elements of the list given to its left and accesses from each one the attribute name given on its right. A splat expression can also be used to access attributes and indexes from lists of complex types by extending the sequence of operations to the right of the symbol:
var.list[*].interfaces[0].name
More info: Splat Expressions
Dynamic Blocks #
A way to create multiple repeatable nested blocks within a resource or other construct.
Within top-level block constructs like resources, expressions can usually be used only when assigning a value to an argument using the name = expression form. This covers many uses, but some resource types include repeatable nested blocks in their arguments, which typically represent separate objects that are related to (or embedded within) the containing object:
resource "aws_elastic_beanstalk_environment" "tfenvtest" {
name = "tf-test-name" # can use expressions here
setting {
# but the "setting" block is always a literal block
}
}
You can dynamically construct repeatable nested blocks like setting using a special dynamic block type, which is supported inside resource, data, provider, and provisioner blocks:
resource "aws_elastic_beanstalk_environment" "tfenvtest" {
name = "tf-test-name"
application = aws_elastic_beanstalk_application.tftest.name
solution_stack_name = "64bit Amazon Linux 2018.03 v2.11.4 running Go 1.12.6"
dynamic "setting" {
for_each = var.settings
content {
namespace = setting.value["namespace"]
name = setting.value["name"]
value = setting.value["value"]
}
}
}
A dynamic block acts much like a for expression, but produces nested blocks instead of a complex typed value. It iterates over a given complex value, and generates a nested block for each element of that complex value.
Overuse of dynamic blocks can make configuration hard to read and maintain, so we recommend using them only when you need to hide details in order to build a clean user interface for a re-usable module.
Always write nested blocks out literally where possible.
More info: Dynamic Blocks
Validate your configuration #
To verify variable conditions, check blocks, and resource preconditions and postconditions.
Input variable validation #
Use input variable validation to perform the following tasks:
- Verify input variables meet specific format requirements.
- Verify input values fall within acceptable ranges.
- Prevent Terraform operations if a variable is misconfigured.
For example, you can validate whether a variable value has valid AMI ID syntax.
variable "image_id" {
type = string
description = "The id of the machine image (AMI) to use for the server."
validation {
condition = length(var.image_id) > 4 && substr(var.image_id, 0, 4) == "ami-"
error_message = "The image_id value must be a valid AMI id, starting with \"ami-\"."
}
}
If you set the value of the image_id variable to a string without AMI ID syntax, the condition evaluates to false. When a variable validation fails, Terraform errors, displays the configured error_message, and stops the operation from proceeding.
More info: Validate your configuration
Type Constraints #
Syntax for referring to a type, rather than a value of that type. Input variables expect this syntax in their type argument.
More info: Type Constraints
Version Constraints #
Syntax of special strings that define a set of allowed software versions. Terraform uses version constraints in several places.
Use the following syntax to specify version constraints:
version = "<operator> <version>"
In the following example, Terraform installs a versions 1.2.0 and newer, as well as version older than 2.0.0:
version = ">= 1.2.0, < 2.0.0"
The following table describes the operators you can use to configure version constraints:
| Operator | Description |
|---|---|
=,no operator | Allows only one exact version number. Cannot be combined with other conditions. |
!= | Excludes an exact version number. |
>,>=,<,<= | Compares to a specified version. Terraform allows versions that resolve to true. The > and >= operators request newer versions. The < and <= operators request older versions. |
~> | Allows only the right-most version component to increment. Examples: - ~> 1.0.4: Allows Terraform to install 1.0.5 and 1.0.10 but not 1.1.0.- ~> 1.1: Allows Terraform to install 1.2 and 1.10 but not 2.0. |
More info: Version Constraints