Now that we’ve gone through an introduction to Terraform, let’s take a look at parameterizing our deployments using Terraform Input Variables. Input Variables enable you to pass in configuration values at the time of deployment. This is a great way to build a Terraform project that can be reused to deploy multiple different environments or similar deployments that have slightly different configuration. These configurations are passed into the deployment as variables passed to the Terraform CLI when the plan or apply commands are executed.

Let’s dive in!

Note: Using Terraform Input Variables is one way of deploying development, staging, or production environments using the same Terraform resource declarations, with slightly different configuration.

Define Input Variables

Terraform Input Variables are defined using a variable block that defines the variable name and other option parameters for the variable. These variable blocks can be place in any .tf file within your Terraform project, but a common location used is within a file named variables.tf.

Input Variable Definition

The variable block is defined with the variable keyword followed by the name of the Input Variable. Also, the required type property must be added to define the data type of the variable; such as string, number, object, or other supported data types.

Here’s a couple examples of Input Variables defined a Terraform project:

variable "environment" {
  type = string
}

variable "location" {
  type        = string
  default     = "eastus"
  description = "The Azure Region to deploy resources."
}

There are a few basic properties that can be defined within the variable block. The type is required, but others are optional.

  • type – The data type of the Input Variable.
  • default – The default value for the Input Variable to use if the Input Variable value is not specified to the Terraform command.
  • description – Used to add some text that provides a description of what the Input Variable is for. This is something added for adding documentations about the Input Variable.

Note: Remember, all the .tf files within the same directory are loaded together when running Terraform deployments. As a result, you could place all Input Variables in the variables.tf file or any other .tf file within the Terraform project.

Input Variable Conditions

When defining Input Variables for a Terraform project, the Input Variable can have a custom validation rules defined. The custom validation rules for an Input Variable are defined by adding a validation block within the variable block for the Input Variable.

Note: The Input Variable validation rule feature is supported by Terraform v0.13.0 and later.

Here’s an example of an Input Variable with a custom validation rule defined:

variable "location" {
  type        = string
  description = "The Azure Region to deploy resources."

  validation {
    condition     = contains(["eastus", "westus"], lower(var.location))
    error_message = "Unsupported Azure Region specified. Supported regions include: eastus, westus"
  }
}

Within the validation block for the Input Variable, there are a couple properties to set for configuring custom validation rules:

  • condition – Defines the expression used to evaluate the Input Variable value. Must return either true for valid, or false for invalid value.
  • error_message – Defines the error message displayed by Terraform when the condition expression returns false for an invalid value.

It’s good practice to wrap the condition expression in the can() function for expressions that would otherwise return an error upon failure. Such as with a regex() function expression that would error if no matches are found. This is used to convert an error condition of the expression into a boolean value.

Here’s the above example modified to implement the can() function along with a regex() function to ensure that only United States Azure Regions are specified for the location Input Variable:

variable "location" {
  type        = string
  description = "The Azure Region to deploy resources."

  validation {
    condition     = can(regex("us$", var.location))
    error_message = "Unsupported Azure Region specified. Only United States Azure Regions are supported."
  }
}

Input Variable Types

Terraform supports several variable types for using with Input Variables. The most common is likely the string type but Terraform also supports a few more type options to choose from.

Primitive Types

Terraform supports three primitive types for Input Variables. These are the primary primitive types supported by all programming languages for representing variables of strings, numbers, and boolean values.

Here’s the list of primitive types supported by Terraform along with descriptions of the type of data they can represent:

  • string – Text content made up of Unicode characters. Such as 'eastus'
  • number – A numeric value that can be a whole number (such as 42) or a decimal value (such as 3.141592)
  • bool – A boolean value. Either true or false.

Here are simple examples of defining Input Variables using each of the primitive types:

variable "location" {
  type = string
}

variable "instance_count" {
  type = number
}

variable "enforce_ssl" {
  type = bool
}

Complex Types

In addition to primitive types, Terraform also supports a couple complex types. Complex types allow you to group multiple values together into a single variable. Complex types in Terraform are represented using type constructors; with shorthand keyword versions supported as well.

Here’s the list of complex types supported by Terraform along with descriptions of the types:

  • Collection types – A collection of multiple values grouped together as a single value.
  • Structural types – A collection of multiple values of several distinct types grouped together as a single value.

Collection Types

The Collection types in Terraform as similar to the programming construct called an Array. They allow you to group multiple values of the same type into single variable value.

There are three kinds of Collection types in Terraform:

  • list(...) – A sequence of values identified by an index starting with zero.
  • map(...) – A collection of values; each with a string label identifier.
  • set(...) – A collection of unique values without any secondary identifiers or ordering.

Here are simple examples of defining Input Variables using each of the Collection types:

# List of type String
variable "vm_ip_allow_list" {
  type = list(string)
}

# Map of Strings
variable "vm_ip_map" {
  type = map(string)
}

# Set of Numbers
variable "my_set" {
  type = set(number)
}

Here are sample values for each of the above Input Variable examples of Collection types:

# List of type String
vm_ip_allow_list  = [
  '10.50.0.1/32'
  '10.83.0.5/32'
]

# Map of Strings (using colons ':')
vm_ip_map = {
  "vm1": "10.50.0.1/32"
  "vm2": "10.83.0.5/32"
}

# Map of Strings (using equals '=')
vm_ip_map = {
  "vm1" = "10.50.0.1/32"
  "vm2" = "10.83.0.5/32"
}

# Set of Numbers
my_set = [1, 2, 3, 4, 5]

Note: The Terraform map complex type can be compared to a Key / Value Dictionary type in other programming languages. You define a set of values, then each individual value can be referenced using the string label identifier as the key to retrieve just that single value.

Structural Types

Structural types in Terraform allow multiple values of different types to be grouped together as a single value. Using structural types requires a data schema to be defined for the Input Variables type so that Terraform knows what a valid value is.

Here’s are the two structural types supported in Terraform:

  • object(...) – A collection of values each with their own type.
  • tuple(...) – A sequence of values each with their own type.

Here are simple examples of defining Input Variables using each of the Structural types:

# Object with defined schema
variable "vm_configs" {
  type = object({
    location       = string
    size           = string
    instance_count = number
  })
}

# Tuple with defined schema
variable "tuple_values" {
  type = tuple([
    string,
    number,
    bool
  ])
}

Here are sample values for each of the above Input Variable examples of Structural types:

# Input Variable of type Object
vm_configs = {
  location       = "eastus"
  size           = "Standard_DS2"
  instance_count = 4
}

# Input Variable of type Tuple
tuple_values = [
  "Chris",
  42,
  true
]

Configure Resources using Input Variables

Input Variables can be referenced within Terraform code using the var keyword that exposes a sort of object that makes properties accessible for each of the Input Variables defined. Input Variables are referenced in a similar fashion to local variables this way by using the var keyword instead.

Here’s an example of referencing an Input Variable named location:

var.location

Referencing Input Variables is useless unless you use the value of the variable to configure a resource. You can assign the Input Variable value directly to resources properties, like the following:

# Create an Azure resource group using the passed in location
resource "azurerm_resource_group" "b59" {
  name     = "b59-rg"
  location = var.location
}

You can also assign Input Variable values to local variables, or even use them as inputs to other Terraform functions.

locals {
  location_to_use = var.location

  location_lower = lower(var.location)
}

Pass Input Variables to Terraform Deployments

Input Variables in Terraform are used to pass in configuration values to the Terraform project at time of deployment. There are two methods supported for doing this using the Terraform CLI when running Terraform commands.

  • -var flag enables a single input variable value to be passed in at the command-line.
  • -var-file flag enables multiple input variable values to be passed in by referencing a file that contains the values.

Using the -var Command-line Flag

The -var flag is used to pass values for Input Variables to Terraform commands. This flag can be used with both of the Terraform plan and apply commands.

The argument passed to the -var flag is a string surrounded by either single or double quotes. The value within the quotes is made up of the name of the Input Variable followed by a equal (=) sign then followed by the value for the Input Variable to pass in.

Input Variable values are passed to the -var flag in the below format:

# Using single quotes
-var '<input-variable-name>=<value>'

# Using double quotes
-var "<input-variable-name>=<value>"

Here’s are some examples showing how to pass Input Variable values to Terraform commands:

# Pass Input Variable to 'plan' command
terraform plan \
-var 'location=eastus' \
-var 'vm_size=Standard_DS2'

# Pass Input Variable to 'apply' command
terraform apply \
-var 'location=eastus' \
-var 'vm_size=Standard_DS2'

Related: If you’re interested in taking the code reuse in the direction of implementing conditional deployment logic, then we recommend you also check out the “Terraform Feature Flags & Environment Toggle Design Patterns” written by Chris Pietschmann.

The above examples of the -var flag for Terraform commands pass in string type Input Variables. The -var flag can also be used to pass in Input Variables with list and map data types. Here’s a couple examples showing how to pass the values for these types of Input Variables to Terraform commands:

# List Input Variable
terraform apply \
  -var='locations=["eastus","westus"]'

# Map Input Variable
terraform apply \
  -var='location_resource_group_name={"eastus": "App-EastUS-RG", "westus": "App-WestUS-RG"}'

Remember, the -var argument can be used multiple times on a single Terraform command to add all the necessary Input Parameters required for the Terraform project.

Using .tfvars Files and -var-file Command-line Flag

The -var-file flag is used to pass Input Variable values into Terraform plan and apply commands using a file that contains the values. This allows you to save the Input Variable values in a file with a .tfvars extension that can be checked into source control for you variable environments you need to deploy to / manage.

The .tfvars file contents will contain the definition for the Input Variable values to pass into the Terraform commands. These are defined in a similar way to how local Variables are defined in Terraform code.

Here’s an example .tfvars file defining a few Input Variable values:

environment = 'production'
location    = 'eastus'

vm_instance_count = 4
vm_ip_allow_list  = [
  '10.50.0.1/32'
  '10.83.0.5/32'
]

Once you have one or more .tfvars files created, you can then use the -var-file flag to pass in the name of the file for Terraform to pull in for passing Input Variables to the Terraform command. Multiple -var-file flags can be used on the same Terraform command to pass in multiple .tfplans files as necessary.

Here’s a couple examples of using the -var-file flag to pass in .tfvars files to Terraform commands:

# Pass .tfvar files to 'plan' command
terraform plan \
-var-file 'production.tfvars' \
-var-file 'secrets.tfvars'

# Pass .tfvar files to 'apply ' command
terraform apply \
-var-file 'production.tfvars' \
-var-file 'secrets.tfvars'

It’s worth noting that you can pass in both -var and -var-file flags into either the plan or apply commands at the same time. This is useful when you have multiple Input Variable values defined within .tfvars files that are checked into source control, while other Input Variables must be passed in at the command-line as part of a CI/CD release pipeline.

Here’s an example of passing in both to the terraform apply command:

terraform plan \
-var 'secret=$(secretValue)'
-var-file 'production.tfvars'

Notice, in the above example, the secret Input Variable is being passed in with the value from the $(secretValue) bash variable. When running a Terraform command inside a CI/CD pipeline, the previous step of the pipeline or even the pipeline variables will define what the value is for this Input Variable. This way you can have the CI/CD pipeline pass in the specific value for a Terraform Input Variable without the value being checked into source control, as the .tfvars files will be contained within the source repository with the rest of the Terraform project files. This is a common method of passing in passwords or other secrets that are environment specific, but also need to be hidden from accessibility by developers or other team members with access to the source code repository.

Load Input Variables Automatically from .tfvars Files

In addition to using the -var-file flag to explicitly specify the -tfvars files to use for declaring the Input Variable values, you can also use certain special file names for Terraform to automatically pull in .tfvars files. There are a couple different file names and naming pattern you can use for .tfvars files in your Terraform project, and Terraform will automatically load them when you run a Terraform plan or apply command.

The automatic loading of .tfvars files is based on the following file names and pattern:

  • Files named with these exact file names will automatically be loaded to populate Input Variables: terraform.tfvars or terraform.tfvars.json
  • Any files with the following suffixes will automatically be loaded to populate Input Variables: .auto.tfvars or .auto.tfvars.json

Files with names that end in .json are parsed in as JSON objects. The root object properties correspond to Input Variable names.

Here’s a simple JSON example of the contents that could be used within a .json file defining Input Variables:

{
  "location": "eastus",
  "vm_ip_allow_list": [
    '10.50.0.1/32'
    '10.83.0.5/32'
  ]
}

Using Environment Variables

When running Terraform commands, you can also use Environment Variables to define the values for Input Variables on your Terraform project. Terraform automatically will pull in any Environment Variables that are named using the prefix of TF_VAR_ followed by the name of the Input Variable.

When using Environment Variables to define Input Variables for Terraform deployment, be sure to keep in mind that if the Operating System is case-sensitive, then Terraform will match variable names exactly as given during configuration. As a result, the upper and lower case of the Environment Variable will need to exactly match the Input Variable name.

Set Environment Variable using Bash

Here’s an example using Bash to specify the value of an Input Variable named location by setting the Environment Variable TF_VAR_location:

$ export TF_VAR_location="eastus"

Set Environment Variable using PowerShell

Here’s an example of using PowerShell to specify the value of an Input Variable named location by setting the Environment Variable TF_VAR_location:

PS C:\> $env:TF_VAR_location = 'eastus'

Once the Environment Variable is set with the TF_VAR_ prefix, then Terraform will automatically pull in it’s value for the Input Variable with the same name when running Terraform commands.

Complex Type Environment Variables

The map and list complex type values can also be defined using Environment Variables when passing Input Variable values to Terraform plan and apply commands.

Here’s an example of defining a list data type Input Variable value using an Environment Variable using Bash:

$ export TF_VAR_locations='["eastus","westus"]'

When using this method to define complex typed Input Variable values, take care with string escaping rules of the Bash shell. Be sure to mind readability and ease of maintenance with scripts using this method. It’s generally recommended to use .tfvars files where possible to avoid mistakes with passing complex types Input Variable values to Terraform commands.

Input Variable Definition Precedence

With multiple methods of being able to pass Input Variable values to Terraform plan and apply commands there is a specific order in which variables values are assigned. This is important if the same Input Variable is specified multiple times so you can be sure the expected value is used to override other duplicate value definitions for the same Input Variable.

When Terraform loads Input Variable values from the various definition methods, they are loaded in the following order with later sources taking precedence over earlier sources:

  1. Environment variables
  2. terraform.tfvars file, if present
  3. terraform.tfvars.json file, if present
  4. Any .auto.tfvars or .auto.tfvars.json files, processed in lexical order of their filenames.
  5. Any -var and -var-file flags on the command-line, in the order they are provided.

Note: With Terraform 0.12 and later, variables for map and object values behave as other types with the later source taking precedence over earlier sources. Previous versions of Terraform will merge map values together rather than override them.

Wrap Up

You can use Input Variables to pass in variable values to Terraform deployments. These can be used to define feature flags and other varying configurations that need to be different for different environments you are deploying to. This is also a helpful way to build reusable Terraform projects that can be used to deploy different environments using the same code. There are multiple methods of passing in Input Variables (command-line flags, .tfvars files and Environment Variables) giving you flexibility in defining and passing the values in to the deployment.

Happy Terraforming!

Chris Pietschmann is a Microsoft MVP, HashiCorp Ambassador, and Microsoft Certified Trainer (MCT) with 20+ years of experience designing and building Cloud & Enterprise systems. He has worked with companies of all sizes from startups to large enterprises. He has a passion for technology and sharing what he learns with others to help enable them to learn faster and be more productive.
Microsoft MVP HashiCorp Ambassador

Discover more from Build5Nines

Subscribe now to keep reading and get access to the full archive.

Continue reading