Terraform Certification (Part 6): Variables
Published: 2023-03-29 10:00:00

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.

![terraform variables](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hiontv2643a0o3sxi25v.png)

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` block[^block]. The general format of a `variable` block is this:

```hcl
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 module[^module]. 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 are `string`, `number`, and `bool`. It is also possible to specify more advanced types such as `list(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` or `false`, default is `false`) that specifies if Terraform should consider this value to be sensitive, like a password. If `true`, Terraform will mask this value in any output it produces.
- `nullable` is a boolean (`true` or `false`, default is `true`) that specifies if this variable is allowed to be `null`.

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:

```hcl
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:

```hcl
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:

```hcl
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:

```hcl
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:

```hcl
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 blocks[^backend]

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:

```hcl
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:

```hcl
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:

```console
$ terraform plan -var "my_string=hello" -var "my_number=42" -var "my_bool=true"
```

Likewise I can provide them to `terraform apply`: 

```console
$ 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:

```hcl
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`:

```console
$ 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:

```hcl
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:

```console
$ 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:

1. Environment variables are read first (i.e. all `TF_VAR_*` environment variables)
1. The file named `terraform.tfvars` is read next, if it exists (followed by `terraform.tfvars.json` if it exists)
1. All files named `*.auto.tfvars` sorted by names (`*.auto.tfvars.json` are also included)
1. 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:
  1. environment variables
  2. `terraform.tfvars`
  3. `*.auto.tfvars`
  4. `-var` and `-var-file` flags

[^module]: 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.
[^block]: Remember that in HCL there are two main concepts: _blocks_ and _arguments_. To refresh your memory of these concepts see the lesson on [providers](./../providers/) where we first encountered these concepts.
[^backend]: We will cover backends in a future lesson.