Variables in Terraform represent containers for values coming from the outside world. The outside world in this case is usually the command line, or a deployment pipeline. Variables could also be part of a module (which we will cover later) and values for the variables could be provided from another module.
A schematic view of what variables are is shown in the figure below.
In this lesson I begin to go through part 8 of the Certified Terraform Associate exam curriculum. This part of the curriculum is outlined below:
Part | Content |
---|---|
8 | Read, generate, and modify configuration |
(a) | Demonstrate use of variables and outputs |
(b) | Describe secure secret injection best practice |
(c) | Understand the use of collection and structural types |
(d) | Create and differentiate resource and data configuration |
(e) | Use resource addressing and resource parameters to connect resources together |
(f) | Use HCL and Terraform functions to write configuration |
(g) | Describe built-in dependency management (order of execution based) |
To be specific: I will cover parts of 8 (a) in this lesson, i.e. Demonstrate use of variables. We also briefly touch on the subjects in 8 (b) and (c).
Declaring a variable#
In Terraform a variable is declared using a variable
block1. The general format of a variable
block is this:
variable "label" {
type = <type>
default = <default value>
description = <description>
sensitive = boolean
nullable = boolean
validation { ... }
}
The variable
block has one label. The label is the symbolic name of the variable, it must be unique within a given module2. We can use the symbolic name of the variable to refer to it from elsewhere in our Terraform configuration.
The arguments in the variable
block are explained next:
type
restricts the possible values for this variable to a given type. The basic types available arestring
,number
, andbool
. It is also possible to specify more advanced types such aslist(string)
,set(number)
, etc. If no type is specified then any type will be accepted.default
is a provided default value for this variable if no other value is provided at the time that Terraform runs this configuration.description
is a friendly string explaining what this variable is used for.sensitive
is a boolean (true
orfalse
, default isfalse
) that specifies if Terraform should consider this value to be sensitive, like a password. Iftrue
, Terraform will mask this value in any output it produces.nullable
is a boolean (true
orfalse
, default istrue
) that specifies if this variable is allowed to benull
.
Apart from these arguments you can also specify validation
blocks nested inside of variable
blocks. A validation
block is used to validate the provided value. A simple example of a validation block that checks that the length of a string variable is less than 10 characters looks like this:
variable "my_string" {
type = string
validation {
condition = length(var.my_string) < 10
error_message = "my_string should be less than 10 characters long"
}
}
Let us see a few more examples of how we can declare a variable. In the next example I declare a variable of type number
that should be an even number:
variable "even_number" {
type = number
description = "Provide an even number"
default = 2
nullable = false
validation {
condition = var.even_number % 2 == 0
error_message = "even_number should be an even number!"
}
}
The next example shows a variable that should be a list of strings and the number of elements should be at least three:
variable "names" {
type = list(string)
description = "Provide a list of three or more names"
default = ["agnetha", "anni-frid", "björn", "benny"]
nullable = false
validation {
condition = length(var.names) >= 3
error_message = "You should provide at least three names"
}
}
Using a variable in Terraform#
Once we have declared a variable we can refer to it by var.<NAME>
, where <NAME>
is replaced by the symbolic name of the variable (the label on the variable
block). An example of where I define a simple string variable and use it in a resource looks like this:
variable "name" {
type = string
}
resource "local_file" "my_file" {
filename = var.name
// ...the rest of the resource omitted
}
We can also use variables in string interpolation. String interpolation means we include one or more variables inside of a string. We do this using ${}
inside of a string, like so:
variable "name" {
type = string
}
resource "local_file" "my_file" {
filename = "prefix-${var.name}-postfix"
// ...the rest of the resource omitted
}
Variables may be used almost anywhere. There are some exceptions:
- you can’t use a variable in the label of a Terraform block
- you can’t use variables inside of other variable definitions
- you can’t use variables inside of backend blocks3
These are the exceptions I could think of right now, but there might be more. If you try to use a variable where it is not supported you will get an error message stating this.
Providing a value for a variable#
We have declared our variables and we have used them in our Terraform configuration. How do we provide values to them? There are a few different ways.
Default values#
We already saw that in a variable
block we can specify the default
argument with a value that should be the default value if no other value is provided. It makes sense to put a good default value here that is sufficient most of the time, then you only need to provide a different value to the variable if you have a specific need to do so.
Imagine I create a Kubernetes cluster using Terraform and I want the number of worker nodes in my cluster (the number of virtual machines) to be 10, except in rare cases where I want something else. In this case it would make sense to define a variable like this:
variable "number_of_worker_nodes" {
type = number
default = 10
}
Command line#
In the previous lesson I went through the core Terraform workflow, and we saw the terraform plan
and terraform apply
commands. We can provide values for our variables in these commands. Imagine that we have the following variable declared in our Terraform configuration:
variable "my_string" {
type = string
}
variable "my_number" {
type = number
}
variable "my_bool" {
type = bool
}
I can run terraform plan
and provide values to my variables using the -var
flag:
$ terraform plan -var "my_string=hello" -var "my_number=42" -var "my_bool=true"
Likewise I can provide them to terraform apply
:
$ terraform plan -var "my_string=hello" -var "my_number=42" -var "my_bool=true"
Variable definition files#
If you have many variables it is tedious to set them using -var
flags for terraform plan
and terraform apply
. Instead you can use variable definition files. This is a file with a file-ending of .tfvars
or .tfvars.json
(in my experience the JSON-kind is rarely used, but it is good to know that it is possible to use it).
A basic variable definition file looks like this:
variable_1 = "value 1"
variable_2 = 10000
variable_3 = [
"list_item_1",
"list_item_2"
]
It is basically a list of variable assignments.
To use a variable definition file you can explicitly provide it to terraform plan
and terraform apply
:
$ terraform plan -var-file="variables.tfvars"
$ terraform apply -var-file="variables.tfvars"
You could include several variable definition files by using the -var-file
flag more than once.
You can let Terraform automatically include your variable definition files by naming your file exactly terraform.tfvars
or any name ending with .auto.tfvars
, e.g. dev.auto.tfvars
.
Environment variables#
In automation scenarios I have found it to be beneficial to be able to set values for variables using environment variables.
If I have the following variable declared in Terraform:
variable "my_variable" {
type = string
}
I can define an environment variable to match this variable. The name of the environment variable must start with TF_VAR_
and be followed by the name of the variable, like this:
$ export TF_VAR_my_variable=my_value
The following terraform plan
and terraform apply
commands will automatically use this environment variable.
Variable definition precedence#
With so many ways of providing values for variables keep this precedence order in mind:
- Environment variables are read first (i.e. all
TF_VAR_*
environment variables) - The file named
terraform.tfvars
is read next, if it exists (followed byterraform.tfvars.json
if it exists) - All files named
*.auto.tfvars
sorted by names (*.auto.tfvars.json
are also included) - All explicitly provided variable values with
-var
or variable definition files with-var-file
, in the order that they are provided
This means that if I have a variable named my_variable
I could define a value for it as an environment variable TF_VAR_my_variable
, I could include it in terraform.tfvars
, I could include it in myvars.auto.tfvars
, and I could provide it with -var "my_variable=my_value"
. What would happen then? In this case the -var "my_variable=my_value"
would take precedence because it is the last method to be evaluated.
Summary#
We now know all we need to know about variables to be able to handle variable-related questions in the certification exam! This lesson covered everything I have ever needed to know about variables when using Terraform in production scenarios as well. A quick summary of what we did:
- We saw how to declare a variable in our Terraform configuration and went through all the arguments we can specify
- We went through how we refer to a variable from other parts of our Terraform configuration with
var.<NAME>
, e.g.var.my_variable
- We saw how to provide values for variables using the Terraform CLI
terraform plan -var "variable_1=value" -var "variable_2=value"
terraform apply -var "variable_1=value" -var "variable_2=value"
- We saw how we can easily provide values for several variables at once using variable definition files, i.e. files with names ending in
.tfvars
terraform plan -var-file "dev.tfvars"
terraform apply -var-file "dev.tfvars"
- We saw how we can provide values for variables using environment variables like
TF_VAR_my_variable
- We learned about the precedence order of variable definitions:
- environment variables
terraform.tfvars
*.auto.tfvars
-var
and-var-file
flags
Remember that in HCL there are two main concepts: blocks and arguments. To refresh your memory of these concepts see the lesson on providers where we first encountered these concepts. ↩︎
Again, we cover modules in the future but for now think of a module as a given scope where names of things must be unique. ↩︎
We will cover backends in a future lesson. ↩︎