Expressions in Terraform

Wikipedia defines an expression in a programming language as a combination of one or more constants, variables, operators, and functions that the programming language interprets (according to its particular rules of precedence and of association) and computes to produce (“to return”, in a stateful environment) another value.

What are Expressions

Expressions, at their base level, consist of single values often referred to as primitives. Multiple values can combine to make more complex values based off a root set of values.

  • Strings: a sequence of characters that represent text, for example “fubar”, “snafu” or “Tarfu”
  • Numbers: numerical values for example 1, 10, and fractional values like “3.142”

A conditional expression is response based on one of two values;

  • Booleans: a true for false statement, example, “Yes/No”, “true/false”, these correspond to “0/1” in conditional logic.

Then we have more complex values which you may see referred to as complex, structural or collections:

  • Lists: also known as a “tuple” as set of elements presented in a sequence, surrounded by square brackets [ ]. For example, the regions to deploy a project to [“eu-west-1”, “eu-west-2”, “us-east-1”, “us-west-1”], are represented as consecutive numbers starting with a zero. In the example above it would mean “eu-west-1” would equate to [0] and “us-west-1” would equate to [3]
  • Maps: Maps are a group of values that are often used as inputs. These are different from a list as they have named labels for the values presented, enclosed by braces { } (also known as curly brackets). For example, {first_name = “tom”, Surname = “Howarth”, house_number = “311”}

There is also a special value that is effectually of no type – this means it is not actually any of the above but it does actually have a value:

  • Null: this value represents nothing, or to be more precise an absence of a value or an omission of a value. This is very useful of you want to clear a value or raise an error if a particular value is missing or present.

These are not the only Expressions

Although this is well known to developers and scripters of other languages, it is actually new to terraform in version 0.12: the for expression. It creates a complex value type by transforming another complex value.

for <variable1> in <variable2>: <action>

Arithmetic and logical operators

An operator is a type of expression that transforms or combines one of more other expressions:

Addition: +
Subtraction: -
Multiplication: *
Division: /
Modulus: %
Equality: == and !=
Numerical comparison: >, <, >=, <=
Boolean logic: &&, ||, unary !

How are expressions used?

OK, so now that we have all the building blocks understood, let’s look into explaining Terraform expressions by way of some examples. Firstly we will create four variables and assign default values to them

variable "ENV" {default = "PROD"}
variable "A" {default = 20}
variable "B" {default = 10}
variable "C" {default = 3}

After we have created the variables we create a main.tf file, this is using a special provider call Null.

Terraform-Init

The first stanza sets up three Null resources. It is the outputs that we care about:

resource "null_resource" "myresource" { #creates three null resources
count = "${var.C}"
#for(i=0;i<3;i++){return i}
}

The first output stanza outputs the values of the ENV variable as production or non-production based on the input. As we have not specifically set any value, the default of “PROD” is used:

output "myoutput1" {
value = "${var.ENV == "PROD" ? "PRODUCTION" : "NONPRODUCTION"}"
}

As we can see the out put of this is:

myoutput1 = PRODUCTION

We can verify this by changing the variable to any other value, we input this with the -var command line variable:

Variable_output

This is an example where we have used the equality expression “==.” This results in any value other than “prod” resulting in the return of “nonproduction”

In our second example, we are using some basic math to calculate some new values based on the default entries for the variables “A”, “B” and “C”. Here you will also note the backslash character followed by a lowercase “n” (\n), this signifies the input of a newline, you can see the result of this is the relevant output.

output "myoutput2" {
value = "A + B = ${var.A + var.B}\n A - B = ${var.A - var.B}\n A * B = ${var.A * var.B}\n A / B = ${var.A / var.B}\n A % C = ${var.A % var.C}"
}

At first look this appears to be a complicated construct but it is little more the five different math problems. Review back to the default settings of each variable.

myoutput2 =  A + B = 30
 A - B = 10
 A * B = 200
 A / B = 2
 A % C = 2

Output three works though a couple logic gates, to find out which variable has the greatest value.

output "myoutput3" {
value = "${((var.A > var.B && var.A > var.C) ? "A is greatest" : (var.B > var.C ? "B is greatest" : "C is greatest"))}"
#if(A>B && A>C){return A} elseif(B>C){return B} else{return C}
}

Section one checks if variable A is greater than variable B or C. As it is the response is show as below, if this was not the case it would work though the second and third expressions and return which ever is the greater.

myoutput3 = A is greatest

The final stanza is an example of using the “Splat” character.

output "myoutput4" {value = ["${null_resource.myresource.*.id}"]

}

With the use of the “*” we return the id of each of the three null outputs.

myoutput4 = [
  [
    "1283079804520585203",
    "7462044327476704949",
    "6335317867099027730",
  ],
]

How about a real world example of an Expression?

Say we are creating a couple of named instances for our LAMP stack and we have a known naming convention. This convention is “availability Zone + service Type + numerical value”.

We will have already declared these as variables, with the exception of the numerical value, so we need to set this. We have set this to create two instances.

variable "instance_count" {  default = "2"}

Within the resource that is creating the EC2 instance you would add the count parameter.

  count         = "${var.instance_count}"

You would then in the tags section add the following code.

tags = {
    Name  = "${module.var.AVZ}_${var.Type}-${count.index + 1}"
  }

We set the count index to +1 as count actually starts from 0.

Summary

As we can see Expression are a powerful tool in building up your infrastructure in a way that is understandable to humans without repeating or hard-coding information.