Terraform is a powerful infrastructure as code (IaC) tool that enables you to define and manage your cloud infrastructure in a declarative manner. One of the key features of Terraform is the ability to use variables, which allow you to parameterize your configurations and make them more flexible and reusable.

Did you know that you could also order Domino's Pizza with Terraform? In this blog post, we will have fun with the Domino's Pizza Terraform provider while exploring the world of Terraform variables. We'll understand how to effectively use them in your infrastructure deployments.

Video Walkthrough

Requirements: A GitHub account (all the hands-on sections will utilize GitHub’s Codespaces so you won’t need to install anything on your machine)

TL;DR: You can find the repo here.

What are Terraform Variables?

Terraform variables are used to customize modules and resources without altering their source code. They allow you to define and manage reusable values in your Terraform configurations, making it easier to adapt to changes and maintain consistency across different environments.

Importance of Terraform Variables

Using Terraform variables is essential for managing large infrastructure configurations, as it helps avoid hardcoding values and makes it easier to update and maintain your configurations. Variables also enable you to reuse code and create modular, scalable, and maintainable infrastructure.

Terraform Variable Types

Terraform is pretty versatile with its variable types. Each type serves a specific purpose:

  • String: This is the most basic type. It's used for text and is pretty straightforward. For example, you might use a string for a server name or a tag.
  • Number: As you'd guess, this type is for numbers. Whether you're specifying a count of VMs or defining a size parameter, numbers come in handy.
  • Bool: Short for Boolean, this type is all about [.code]true[.code] or [.code]false[.code]. Super useful for toggles, like turning on/off a feature in your configuration.
  • List: Think of this as an array. Lists can hold a bunch of values of the same type. So, if you have a list of IP addresses or user names, this is your go-to.
  • Map: This is a key-value pair collection. Maps are great when you need to associate values with unique keys, like setting up environment-specific variables.
  • Object: A bit more complex, objects allow you to define a structure with different fields of different types. It’s like creating a custom data type tailored to your needs.

Default Values

Setting default values can make your Terraform scripts more user-friendly and less error-prone. Here are some things you should know:

  • Why use default values?
    They simplify your configurations. If there's a common value that's frequently used, setting it as a default can save time and avoid repetitive inputs.
  • How to set them?
    Just define the default within the variable definition. If a user doesn’t provide a value, Terraform will quietly use the default.
  • What about optional variables?
    With default values, some variables become optional. Users only need to specify them if they want to override the default.
  • Prompting for Input?
    If no default is set and a value is required, Terraform will ask for it. This ensures that your configuration doesn’t run with missing pieces.

In the example below you can see how to set defaults for an instance type, a logging flag, and a list of server ports:

variable "instance_type" {
  type    = string
  default = "t2.micro"  # A common default for AWS EC2 instances
}

variable "enable_logging" {
  type    = bool
  default = true  # Defaulting to having logging enabled
}

variable "server_ports" {
  type    = list(number)
  default = [80, 443]  # Defaulting to standard HTTP and HTTPS ports
}

Using a setup like this makes the configuration smoother and more intuitive.

Defining Variables and Assigning Values

To define variables in Terraform, you use the variable block within your configuration files. This block specifies the name, type, default value, and other attributes of the variable.

By defining variables, you inform Terraform about the inputs it expects during deployment. While variable blocks can be defined in any terraform configuration file, the convention is to define values in a separate variables.tf file in your Terraform project.

Syntax of Variable Blocks

The syntax for a variable block includes the [.code]variable[.code] keyword, followed by the [.code]variable name[.code], and then a set of attributes enclosed in curly braces. The attributes can include the variable type, default value, description, and validation rules.

variable "variable_name" {
  type        = "variable_type"
  default     = "default_value"
  description = "variable_description"
  validation {
    condition = var.variable_name > 0
    error_message = "Variable must be greater than zero."
  }
}

Variable Description

You can add a description to your variable to provide more context and information about its purpose. The description attribute is optional and can be added within the variable block. For example:

variable "order" {
  type        = bool
  description = "Whether to order a pizza or not"
  default     = false
}

Assigning Variable Values with a terraform.tfvars file

You use a file named terraform.tfvars to set up values for your variables in Terraform. When you run commands like [.code]apply[.code] or [.code]plan[.code], Terraform automatically picks up these values. Put this file in the main folder of your Terraform project. Inside, you list your variables with their values, like this:

variable_name = "variable_value"

For example:

first_name   = "John"
last_name    = "Smith"

In this example, you're just telling Terraform that first_name should be "John" and last_name should be "Smith". Terraform uses these details when it runs.

Furthermore, the convention is to add this terraform.tfvars file in your .gitignore file so that you don't check it into git. The reason for this is that you would typically define sensitive values in this file. You can also create a terraform.tfvars.example file that shows an example of the content of the terraform.tfvars file with no sensitive values.

This would serve as an example for users using your code.

Using Terraform Variables in Configuration

To use variables in your Terraform configurations, reference them using the syntax [.code]var.variable_name[.code]. For example, if you have a variable named street, you can reference it in your configuration with [.code]var.street[.code] like this:

data "dominos_address" "addr" {
  street      = var.street
}

Utilizing Built-in Functions

Terraform provides a variety of built-in functions that you can use to manipulate and transform variable values. These functions enable you to perform operations such as string manipulation, mathematical calculations, conditional logic, and more. Some common functions include lookup, merge, element, length, keys, and values.

Conditional Expressions

Conditional expressions in Terraform allow you to choose between two values based on a condition. The syntax for a conditional expression is

condition ? true_value : false_value

For example:

variable "order" {
  type        = bool
  description = "Whether to order a pizza or not"
  default     = false
}

locals {
  should_order = var.order ? 1 : 0
}

resource "dominos_order" "order" {
  count      = local.should_order
...
}

The Terraform code example above defines a variable named [.code]order[.code] of type [.code]bool[.code]. It represents whether to order a pizza or not. The variable has a description stating its purpose, and a default value set to [.code]false[.code] if no value is explicitly provided.

The code also declares a local variable named [.code]should_order[.code] which is based on the value of the [.code]order[.code] variable. If the [.code]order[.code] variable is true, the [.code]should_order[.code] variable will be set to '1'. Otherwise, it will be set to '0'. This local variable is used to conditionally control the creation of the [.code]dominos_order[.code] resource.

Working with Lists and Maps

Terraform variables support lists and maps, which are particularly useful when you need to handle multiple values or key-value pairs. Lists allow you to store and iterate over multiple elements, while maps provide a convenient way to associate values with specific keys. Let's take a look at an example:

# Example of a List
variable "menu_item" {
  type        = list(string)
  description = "A list of the items to order from the menu"
  default     = ["philly", "medium", "thin"]
}

# Example of a Map
variable "credit_card" {
  type        = map(string)
  description = "Credit Card Info"
  sensitive   = true
  default     = {
    number      = 123456789101112
    cvv         = 1314
    date        = "15/16"
    postal_code = "18192"
  }
}

You would then reference the list and map variables the same way with the [.code]var.variable[.code] syntax.

Terraform Environment Variables

Terraform environment variables are used to customize various aspects of Terraform's behavior, such as logging, input handling, and backend configuration. They can also be used to set values for input variables by using the [.code]TF_VAR_[.code] prefix followed by the variable name.

How to Set Terraform Environment Variable Values

To set a Terraform environment variable value, use the export command on Unix/Linux systems or the set command on Windows systems. For example in Linux you would run:

export TF_VAR_first_name="Sam"

Input Variables

Input variables are variables that are provided when running Terraform commands. They allow users to input values interactively or through command-line arguments. These are the terraform variables that we've been discussing thus far.

Local Variables

Terraform also supports local variables, which are variables calculated based on other variables or expressions. Local variables can be useful for complex calculations or for simplifying configuration files by reducing redundancy. Below is an example of using local variables:

locals {
  customer_info = {
    first_name    = var.first_name
    last_name     = var.last_name
    email_address = "${var.first_name}.${var.last_name}@company.com"
    phone_number  = var.phone_number
  }
}

provider "dominos" {
  first_name    = local.customer_info.first_name
  last_name     = local.customer_info.last_name
  email_address = local.customer_info.email_address
  phone_number  = local.customer_info.phone_number
  credit_card   = var.credit_card
}

In this example, a local variable called [.code]customer_info[.code] is declared. This variable is of type map and contains as string values several key-value pairs.

The [.code]customer_info[.code] map includes the following key-value pairs:

  • [.code]first_name[.code]: The value is set to the value of the input variable [.code]var.first_name[.code].
  • [.code]last_name[.code]: The value is set to the value of the input variable [.code]var.last_name[.code].
  • [.code]email_address[.code]: The value is set to an interpolated string [.code]${var.first_name}.${var.last_name}@company.com[.code]. This creates an email address by concatenating the values of the input variables
  • [.code]var.first_name[.code] and [.code]var.last_name[.code], separated by a dot and followed by [.code]@company.com[.code]. This is one of the benefits of using locals. You can't combine input variables and assign the result to another input variable.
  • [.code]phone_number[.code]: The value is set to the value of the input variable [.code]var.phone_number[.code].

We then call on the local values using the [.code]local.variable_name[.code] reference inside the provider block. When calling first_name for example using [.code]local.customer_info.first_name[.code].

Output Variables

Output variables are used to export values from your Terraform configuration, making them available for use in other configurations or for display in the CLI output. They are defined using the output block and are typically defined in an outputs.tf file. Below is an example of an output variable for our pizza order:

output "OrderOutput" {
  value = data.dominos_menu_item.item.matches[*]
  description = "My order"
}

Variable Precedence

Terraform uses a specific order of precedence when determining the value of a variable. If the same variable is assigned multiple values, Terraform will use the value of highest precedence, overriding any other values. Below is the precedence order starting from the highest priority to the lowest.

  1. Environment variables [.code]TF_VAR_variable_name[.code]
  2. The terraform.tfvars file
  3. The terraform.tfvars.json file
  4. Any .auto.tfvars or .auto.tfvars.json files, processed in lexical order of their filenames.
  5. Any [.code]-var[.code] and [.code]-var-file[.code] options on the command line, in the order they are provided.
  6. Variable defaults

Variable Validation

You can add validation rules to your variables using the validation block within the variable definition. This allows you to enforce constraints on the values that can be assigned to a particular variable name. Check the example below:

variable "menu_item" {
  type        = list(string)
  description = "A list of the items to order from the menu"
  validation {
    condition     = contains(var.menu_item, "thin")
    error_message = "You must order a 'thin' pizza crust since it's our team's favorite"
  }
}

The example code above includes a validation block to enforce a specific condition on the [.code]menu_item[.code] variable value.

The validation block includes the following elements:

  • [.code]condition[.code]: This element specifies the condition that needs to be satisfied for the variable value to be considered valid. In this case, the condition is defined as [.code]contains(var.menu_item, "thin")[.code]. It checks whether the list of menu items contains the string "thin". If this condition is true, the validation passes.
  • [.code]error_message[.code]: This element provides an error message to be displayed if the validation condition is not met. In this case, the error message is "You must order a 'thin' pizza crust since it's our team's favorite".

Below is a screenshot of the error message if we don't include the "thin" element in the [.code]menu_item[.code] list variable:

menu_item    = ["philly", "medium"]

Sensitive Variables and Security

Terraform allows you to mark variables as sensitive, which prevents their values from being displayed in the CLI output. To mark a variable as sensitive, add the sensitive attribute to the variable definition. Below is an example of our credit card to be used.

# variable definition contained in the variables.tf file
variable "credit_card" {
  type        = map(string)
  description = "Credit Card Info"
  sensitive   = true
}
# variable assignment contained in the terraform.tfvars file
credit_card = {
  number      = 123456789101112
  cvv         = 1314
  date        = "15/16"
  postal_code = "18192"
}

Typically you would not check the terraform.tfvars file into git since it usually contains sensitive values as mentioned before.

If you try to output the credit card variable, using this output block:

output "credit_card" {
  value = var.credit_card
  description = "Credit Card Info"
}

you'll get the error message below:

You would need to update the output variable file to look like this:

output "credit_card" {
  value = var.credit_card
  description = "Credit Card Info"
  sensitive = true
}

Now when you run [.code]terraform apply[.code], you won't get an error and the output variable [.code]credit_card[.code] will show as [.code]<sensitive>[.code]:

State File Shows Sensitive Data

Although the output of the credit card is not displayed, the sensitive data is still stored in the Terraform state file and therefore needs to be properly secured. You can use a remote backend that is encrypted at rest such as an S3 bucket or make use of a fully-fledged Terraform Automation & Collaboration Software (TACoS) solution such as env0. Below is the credit card data in the state file:

Our Full Example

Now let's take a look at the full example of ordering a Domino's Pizza. Below are all the necessary files to get this going. You will need to run the following Terraform commands to order a pizza. However, be careful putting in your actual credit card because the system will order a pizza for you!

Run these two commands:

terraform init
terraform apply

The variable [.code]order[.code] is set to [.code]false[.code] by default. This will prevent the resource [.code]dominos_order[.code] from actually ordering a pizza. If you are serious about ordering a pizza with Terraform, set this variable to true.

Our main.tf File

terraform {
  required_providers {
    dominos = {
      source  = "MNThomson/dominos"
      version = "0.2.1"
    }
  }
}

provider "dominos" {
  first_name    = local.customer_info.first_name
  last_name     = local.customer_info.last_name
  email_address = local.customer_info.email_address
  phone_number  = local.customer_info.phone_number
  credit_card   = var.credit_card
}

data "dominos_address" "addr" {
  street      = var.street
  city        = var.city
  region      = var.region
  postal_code = var.postal_code
}

data "dominos_store" "store" {
  address_url_object = data.dominos_address.addr.url_object
}

data "dominos_menu_item" "item" {
  store_id     = data.dominos_store.store.store_id
  query_string = var.menu_item
}

resource "dominos_order" "order" {
  count      = local.should_order
  api_object = data.dominos_address.addr.api_object
  store_id   = data.dominos_store.store.store_id
  item_codes = data.dominos_menu_item.item.matches[*].code
}

Our variables.tf File

variable "first_name" {
  type        = string
  description = "Customer's first name"
}

variable "last_name" {
  type        = string
  description = "Customer's last name"
}

variable "phone_number" {
  type        = string
  description = "Customer's phone number"
}

variable "street" {
  type        = string
  description = "Street address of the Store"
}

variable "city" {
  type        = string
  description = "City where the store is located"
}

variable "region" {
  type        = string
  description = "State or Province where the store is located"
}

variable "postal_code" {
  type        = string
  description = "Postal code of the Store"
}

variable "order" {
  type        = bool
  description = "Whether to order a pizza or not"
  default     = false
}

variable "menu_item" {
  type        = list(string)
  description = "A list of the items to order from the menu"
  validation {
    condition     = contains(var.menu_item, "thin")
    error_message = "You must order a 'thin' pizza crust since it's our team's favorite"
  }
}

variable "credit_card" {
  type        = map(string)
  description = "Credit Card Info"
  sensitive   = true
}

locals {
  customer_info = {
    first_name    = var.first_name
    last_name     = var.last_name
    email_address = "${var.first_name}.${var.last_name}@company.com"
    phone_number  = var.phone_number
  }
  should_order = var.order ? 1 : 0
}

Our terraform.tfvars File

credit_card = {
  number      = 123456789101112
  cvv         = 1314
  date        = "15/16"
  postal_code = "18192"
}

Our more_variables.auto.tfvars File

first_name   = "Sam"
last_name    = "Gabrail"
phone_number = "15555555555"
street       = "123 Main St"
city         = "Anytown"
region       = "WA"
postal_code  = "02122"
menu_item    = ["philly", "medium", "thin"]

Our outputs.tf File

output "OrderOutput" {
  value = data.dominos_menu_item.item.matches[*]
  description = "My order"
}

output "credit_card" {
  value = var.credit_card
  description = "Credit Card Info"
  sensitive = true
}

Tips and Best Practices

  • Use a consistent naming convention for variables and provide descriptive variable descriptions to improve code readability.
  • Store variable declarations in a separate variables.tf file.
  • Use reasonable default values for optional variables.
  • Consider using local variables and built-in functions to simplify your configurations and perform complex operations.
  • Use validation rules to enforce constraints on variable values.
  • Mark sensitive variables as such to prevent accidental exposure.
  • Consider using a TACoS (Terraform Automation and Collaboration Software) such as env0 where you can securely store your sensitive variables and manage those in hierarchy.

Advanced Terraform Variable Scenarios

Terraform supports complex data types, such as objects and tuples, as variables. These can be used to store and manipulate more complex data structures in your configurations.

Key Takeaways for Working with Terraform Variables

  • Terraform variables are essential for managing large infrastructure configurations and maintaining consistency across environments.
  • Variables can be assigned in various ways, including command-line flags, environment variables, and .tfvars files.
  • Use built-in functions, locals, conditional expressions, and complex data types to create more flexible and powerful configurations.
  • Follow best practices for naming, organizing, and securing your variables to ensure maintainable and secure infrastructure.

To learn more about Terraform, check out this Terraform tutorial.

Terraform is a powerful infrastructure as code (IaC) tool that enables you to define and manage your cloud infrastructure in a declarative manner. One of the key features of Terraform is the ability to use variables, which allow you to parameterize your configurations and make them more flexible and reusable.

Did you know that you could also order Domino's Pizza with Terraform? In this blog post, we will have fun with the Domino's Pizza Terraform provider while exploring the world of Terraform variables. We'll understand how to effectively use them in your infrastructure deployments.

Video Walkthrough

Requirements: A GitHub account (all the hands-on sections will utilize GitHub’s Codespaces so you won’t need to install anything on your machine)

TL;DR: You can find the repo here.

What are Terraform Variables?

Terraform variables are used to customize modules and resources without altering their source code. They allow you to define and manage reusable values in your Terraform configurations, making it easier to adapt to changes and maintain consistency across different environments.

Importance of Terraform Variables

Using Terraform variables is essential for managing large infrastructure configurations, as it helps avoid hardcoding values and makes it easier to update and maintain your configurations. Variables also enable you to reuse code and create modular, scalable, and maintainable infrastructure.

Terraform Variable Types

Terraform is pretty versatile with its variable types. Each type serves a specific purpose:

  • String: This is the most basic type. It's used for text and is pretty straightforward. For example, you might use a string for a server name or a tag.
  • Number: As you'd guess, this type is for numbers. Whether you're specifying a count of VMs or defining a size parameter, numbers come in handy.
  • Bool: Short for Boolean, this type is all about [.code]true[.code] or [.code]false[.code]. Super useful for toggles, like turning on/off a feature in your configuration.
  • List: Think of this as an array. Lists can hold a bunch of values of the same type. So, if you have a list of IP addresses or user names, this is your go-to.
  • Map: This is a key-value pair collection. Maps are great when you need to associate values with unique keys, like setting up environment-specific variables.
  • Object: A bit more complex, objects allow you to define a structure with different fields of different types. It’s like creating a custom data type tailored to your needs.

Default Values

Setting default values can make your Terraform scripts more user-friendly and less error-prone. Here are some things you should know:

  • Why use default values?
    They simplify your configurations. If there's a common value that's frequently used, setting it as a default can save time and avoid repetitive inputs.
  • How to set them?
    Just define the default within the variable definition. If a user doesn’t provide a value, Terraform will quietly use the default.
  • What about optional variables?
    With default values, some variables become optional. Users only need to specify them if they want to override the default.
  • Prompting for Input?
    If no default is set and a value is required, Terraform will ask for it. This ensures that your configuration doesn’t run with missing pieces.

In the example below you can see how to set defaults for an instance type, a logging flag, and a list of server ports:

variable "instance_type" {
  type    = string
  default = "t2.micro"  # A common default for AWS EC2 instances
}

variable "enable_logging" {
  type    = bool
  default = true  # Defaulting to having logging enabled
}

variable "server_ports" {
  type    = list(number)
  default = [80, 443]  # Defaulting to standard HTTP and HTTPS ports
}

Using a setup like this makes the configuration smoother and more intuitive.

Defining Variables and Assigning Values

To define variables in Terraform, you use the variable block within your configuration files. This block specifies the name, type, default value, and other attributes of the variable.

By defining variables, you inform Terraform about the inputs it expects during deployment. While variable blocks can be defined in any terraform configuration file, the convention is to define values in a separate variables.tf file in your Terraform project.

Syntax of Variable Blocks

The syntax for a variable block includes the [.code]variable[.code] keyword, followed by the [.code]variable name[.code], and then a set of attributes enclosed in curly braces. The attributes can include the variable type, default value, description, and validation rules.

variable "variable_name" {
  type        = "variable_type"
  default     = "default_value"
  description = "variable_description"
  validation {
    condition = var.variable_name > 0
    error_message = "Variable must be greater than zero."
  }
}

Variable Description

You can add a description to your variable to provide more context and information about its purpose. The description attribute is optional and can be added within the variable block. For example:

variable "order" {
  type        = bool
  description = "Whether to order a pizza or not"
  default     = false
}

Assigning Variable Values with a terraform.tfvars file

You use a file named terraform.tfvars to set up values for your variables in Terraform. When you run commands like [.code]apply[.code] or [.code]plan[.code], Terraform automatically picks up these values. Put this file in the main folder of your Terraform project. Inside, you list your variables with their values, like this:

variable_name = "variable_value"

For example:

first_name   = "John"
last_name    = "Smith"

In this example, you're just telling Terraform that first_name should be "John" and last_name should be "Smith". Terraform uses these details when it runs.

Furthermore, the convention is to add this terraform.tfvars file in your .gitignore file so that you don't check it into git. The reason for this is that you would typically define sensitive values in this file. You can also create a terraform.tfvars.example file that shows an example of the content of the terraform.tfvars file with no sensitive values.

This would serve as an example for users using your code.

Using Terraform Variables in Configuration

To use variables in your Terraform configurations, reference them using the syntax [.code]var.variable_name[.code]. For example, if you have a variable named street, you can reference it in your configuration with [.code]var.street[.code] like this:

data "dominos_address" "addr" {
  street      = var.street
}

Utilizing Built-in Functions

Terraform provides a variety of built-in functions that you can use to manipulate and transform variable values. These functions enable you to perform operations such as string manipulation, mathematical calculations, conditional logic, and more. Some common functions include lookup, merge, element, length, keys, and values.

Conditional Expressions

Conditional expressions in Terraform allow you to choose between two values based on a condition. The syntax for a conditional expression is

condition ? true_value : false_value

For example:

variable "order" {
  type        = bool
  description = "Whether to order a pizza or not"
  default     = false
}

locals {
  should_order = var.order ? 1 : 0
}

resource "dominos_order" "order" {
  count      = local.should_order
...
}

The Terraform code example above defines a variable named [.code]order[.code] of type [.code]bool[.code]. It represents whether to order a pizza or not. The variable has a description stating its purpose, and a default value set to [.code]false[.code] if no value is explicitly provided.

The code also declares a local variable named [.code]should_order[.code] which is based on the value of the [.code]order[.code] variable. If the [.code]order[.code] variable is true, the [.code]should_order[.code] variable will be set to '1'. Otherwise, it will be set to '0'. This local variable is used to conditionally control the creation of the [.code]dominos_order[.code] resource.

Working with Lists and Maps

Terraform variables support lists and maps, which are particularly useful when you need to handle multiple values or key-value pairs. Lists allow you to store and iterate over multiple elements, while maps provide a convenient way to associate values with specific keys. Let's take a look at an example:

# Example of a List
variable "menu_item" {
  type        = list(string)
  description = "A list of the items to order from the menu"
  default     = ["philly", "medium", "thin"]
}

# Example of a Map
variable "credit_card" {
  type        = map(string)
  description = "Credit Card Info"
  sensitive   = true
  default     = {
    number      = 123456789101112
    cvv         = 1314
    date        = "15/16"
    postal_code = "18192"
  }
}

You would then reference the list and map variables the same way with the [.code]var.variable[.code] syntax.

Terraform Environment Variables

Terraform environment variables are used to customize various aspects of Terraform's behavior, such as logging, input handling, and backend configuration. They can also be used to set values for input variables by using the [.code]TF_VAR_[.code] prefix followed by the variable name.

How to Set Terraform Environment Variable Values

To set a Terraform environment variable value, use the export command on Unix/Linux systems or the set command on Windows systems. For example in Linux you would run:

export TF_VAR_first_name="Sam"

Input Variables

Input variables are variables that are provided when running Terraform commands. They allow users to input values interactively or through command-line arguments. These are the terraform variables that we've been discussing thus far.

Local Variables

Terraform also supports local variables, which are variables calculated based on other variables or expressions. Local variables can be useful for complex calculations or for simplifying configuration files by reducing redundancy. Below is an example of using local variables:

locals {
  customer_info = {
    first_name    = var.first_name
    last_name     = var.last_name
    email_address = "${var.first_name}.${var.last_name}@company.com"
    phone_number  = var.phone_number
  }
}

provider "dominos" {
  first_name    = local.customer_info.first_name
  last_name     = local.customer_info.last_name
  email_address = local.customer_info.email_address
  phone_number  = local.customer_info.phone_number
  credit_card   = var.credit_card
}

In this example, a local variable called [.code]customer_info[.code] is declared. This variable is of type map and contains as string values several key-value pairs.

The [.code]customer_info[.code] map includes the following key-value pairs:

  • [.code]first_name[.code]: The value is set to the value of the input variable [.code]var.first_name[.code].
  • [.code]last_name[.code]: The value is set to the value of the input variable [.code]var.last_name[.code].
  • [.code]email_address[.code]: The value is set to an interpolated string [.code]${var.first_name}.${var.last_name}@company.com[.code]. This creates an email address by concatenating the values of the input variables
  • [.code]var.first_name[.code] and [.code]var.last_name[.code], separated by a dot and followed by [.code]@company.com[.code]. This is one of the benefits of using locals. You can't combine input variables and assign the result to another input variable.
  • [.code]phone_number[.code]: The value is set to the value of the input variable [.code]var.phone_number[.code].

We then call on the local values using the [.code]local.variable_name[.code] reference inside the provider block. When calling first_name for example using [.code]local.customer_info.first_name[.code].

Output Variables

Output variables are used to export values from your Terraform configuration, making them available for use in other configurations or for display in the CLI output. They are defined using the output block and are typically defined in an outputs.tf file. Below is an example of an output variable for our pizza order:

output "OrderOutput" {
  value = data.dominos_menu_item.item.matches[*]
  description = "My order"
}

Variable Precedence

Terraform uses a specific order of precedence when determining the value of a variable. If the same variable is assigned multiple values, Terraform will use the value of highest precedence, overriding any other values. Below is the precedence order starting from the highest priority to the lowest.

  1. Environment variables [.code]TF_VAR_variable_name[.code]
  2. The terraform.tfvars file
  3. The terraform.tfvars.json file
  4. Any .auto.tfvars or .auto.tfvars.json files, processed in lexical order of their filenames.
  5. Any [.code]-var[.code] and [.code]-var-file[.code] options on the command line, in the order they are provided.
  6. Variable defaults

Variable Validation

You can add validation rules to your variables using the validation block within the variable definition. This allows you to enforce constraints on the values that can be assigned to a particular variable name. Check the example below:

variable "menu_item" {
  type        = list(string)
  description = "A list of the items to order from the menu"
  validation {
    condition     = contains(var.menu_item, "thin")
    error_message = "You must order a 'thin' pizza crust since it's our team's favorite"
  }
}

The example code above includes a validation block to enforce a specific condition on the [.code]menu_item[.code] variable value.

The validation block includes the following elements:

  • [.code]condition[.code]: This element specifies the condition that needs to be satisfied for the variable value to be considered valid. In this case, the condition is defined as [.code]contains(var.menu_item, "thin")[.code]. It checks whether the list of menu items contains the string "thin". If this condition is true, the validation passes.
  • [.code]error_message[.code]: This element provides an error message to be displayed if the validation condition is not met. In this case, the error message is "You must order a 'thin' pizza crust since it's our team's favorite".

Below is a screenshot of the error message if we don't include the "thin" element in the [.code]menu_item[.code] list variable:

menu_item    = ["philly", "medium"]

Sensitive Variables and Security

Terraform allows you to mark variables as sensitive, which prevents their values from being displayed in the CLI output. To mark a variable as sensitive, add the sensitive attribute to the variable definition. Below is an example of our credit card to be used.

# variable definition contained in the variables.tf file
variable "credit_card" {
  type        = map(string)
  description = "Credit Card Info"
  sensitive   = true
}
# variable assignment contained in the terraform.tfvars file
credit_card = {
  number      = 123456789101112
  cvv         = 1314
  date        = "15/16"
  postal_code = "18192"
}

Typically you would not check the terraform.tfvars file into git since it usually contains sensitive values as mentioned before.

If you try to output the credit card variable, using this output block:

output "credit_card" {
  value = var.credit_card
  description = "Credit Card Info"
}

you'll get the error message below:

You would need to update the output variable file to look like this:

output "credit_card" {
  value = var.credit_card
  description = "Credit Card Info"
  sensitive = true
}

Now when you run [.code]terraform apply[.code], you won't get an error and the output variable [.code]credit_card[.code] will show as [.code]<sensitive>[.code]:

State File Shows Sensitive Data

Although the output of the credit card is not displayed, the sensitive data is still stored in the Terraform state file and therefore needs to be properly secured. You can use a remote backend that is encrypted at rest such as an S3 bucket or make use of a fully-fledged Terraform Automation & Collaboration Software (TACoS) solution such as env0. Below is the credit card data in the state file:

Our Full Example

Now let's take a look at the full example of ordering a Domino's Pizza. Below are all the necessary files to get this going. You will need to run the following Terraform commands to order a pizza. However, be careful putting in your actual credit card because the system will order a pizza for you!

Run these two commands:

terraform init
terraform apply

The variable [.code]order[.code] is set to [.code]false[.code] by default. This will prevent the resource [.code]dominos_order[.code] from actually ordering a pizza. If you are serious about ordering a pizza with Terraform, set this variable to true.

Our main.tf File

terraform {
  required_providers {
    dominos = {
      source  = "MNThomson/dominos"
      version = "0.2.1"
    }
  }
}

provider "dominos" {
  first_name    = local.customer_info.first_name
  last_name     = local.customer_info.last_name
  email_address = local.customer_info.email_address
  phone_number  = local.customer_info.phone_number
  credit_card   = var.credit_card
}

data "dominos_address" "addr" {
  street      = var.street
  city        = var.city
  region      = var.region
  postal_code = var.postal_code
}

data "dominos_store" "store" {
  address_url_object = data.dominos_address.addr.url_object
}

data "dominos_menu_item" "item" {
  store_id     = data.dominos_store.store.store_id
  query_string = var.menu_item
}

resource "dominos_order" "order" {
  count      = local.should_order
  api_object = data.dominos_address.addr.api_object
  store_id   = data.dominos_store.store.store_id
  item_codes = data.dominos_menu_item.item.matches[*].code
}

Our variables.tf File

variable "first_name" {
  type        = string
  description = "Customer's first name"
}

variable "last_name" {
  type        = string
  description = "Customer's last name"
}

variable "phone_number" {
  type        = string
  description = "Customer's phone number"
}

variable "street" {
  type        = string
  description = "Street address of the Store"
}

variable "city" {
  type        = string
  description = "City where the store is located"
}

variable "region" {
  type        = string
  description = "State or Province where the store is located"
}

variable "postal_code" {
  type        = string
  description = "Postal code of the Store"
}

variable "order" {
  type        = bool
  description = "Whether to order a pizza or not"
  default     = false
}

variable "menu_item" {
  type        = list(string)
  description = "A list of the items to order from the menu"
  validation {
    condition     = contains(var.menu_item, "thin")
    error_message = "You must order a 'thin' pizza crust since it's our team's favorite"
  }
}

variable "credit_card" {
  type        = map(string)
  description = "Credit Card Info"
  sensitive   = true
}

locals {
  customer_info = {
    first_name    = var.first_name
    last_name     = var.last_name
    email_address = "${var.first_name}.${var.last_name}@company.com"
    phone_number  = var.phone_number
  }
  should_order = var.order ? 1 : 0
}

Our terraform.tfvars File

credit_card = {
  number      = 123456789101112
  cvv         = 1314
  date        = "15/16"
  postal_code = "18192"
}

Our more_variables.auto.tfvars File

first_name   = "Sam"
last_name    = "Gabrail"
phone_number = "15555555555"
street       = "123 Main St"
city         = "Anytown"
region       = "WA"
postal_code  = "02122"
menu_item    = ["philly", "medium", "thin"]

Our outputs.tf File

output "OrderOutput" {
  value = data.dominos_menu_item.item.matches[*]
  description = "My order"
}

output "credit_card" {
  value = var.credit_card
  description = "Credit Card Info"
  sensitive = true
}

Tips and Best Practices

  • Use a consistent naming convention for variables and provide descriptive variable descriptions to improve code readability.
  • Store variable declarations in a separate variables.tf file.
  • Use reasonable default values for optional variables.
  • Consider using local variables and built-in functions to simplify your configurations and perform complex operations.
  • Use validation rules to enforce constraints on variable values.
  • Mark sensitive variables as such to prevent accidental exposure.
  • Consider using a TACoS (Terraform Automation and Collaboration Software) such as env0 where you can securely store your sensitive variables and manage those in hierarchy.

Advanced Terraform Variable Scenarios

Terraform supports complex data types, such as objects and tuples, as variables. These can be used to store and manipulate more complex data structures in your configurations.

Key Takeaways for Working with Terraform Variables

  • Terraform variables are essential for managing large infrastructure configurations and maintaining consistency across environments.
  • Variables can be assigned in various ways, including command-line flags, environment variables, and .tfvars files.
  • Use built-in functions, locals, conditional expressions, and complex data types to create more flexible and powerful configurations.
  • Follow best practices for naming, organizing, and securing your variables to ensure maintainable and secure infrastructure.

To learn more about Terraform, check out this Terraform tutorial.

Logo Podcast
With special guest
Andrew Brown

Schedule a technical demo. See env0 in action.

Footer Illustration