In this article, we will take a look at Terraform variables and the optional arguments they use. We will explain what they are, how to reference them, why they are useful, and how to use type constraints to enhance the quality of your code.
We will cover:
Optional arguments in Terraform can be used when specifying variables to define their behavior and characteristics. These arguments allow you to control how the variables are used and provide defaults, constraints, and metadata or omit certain settings. They enhance the quality of your code and make your infrastructure code more adaptable to different environments without requiring changes to the core logic.
How to define an optional variable in Terraform?
When you define a variable in Terraform, it can simply be defined as:
variable "example_var" {
}
The most commonly used optional arguments for variable declarations in Terraform include:
- type
- default
- description
- validation
- sensitive
- nullable
An example variable declaration could look like this with all the optional parameters included:
variable "example_var" {
type = string
default = "chewbacca"
description = "8 foot tall wookie"
validation = {
condition = var.example_var = "chewbacca"
error_message = "Value must be a wookie"
}
sensitive = false
nullable = false
}
Why are the optional parameters useful?
Depending on your use case, the main benefits of using optional parameters or combinations of the optional parameters in your variables are adaptability and code quality. They are used to further define variables’ behavior and characteristics, making your variables more structured and controlled and your Terraform configurations more robust and user-friendly.
Read more about Terraform variables.
Let’s look at each type of Terraform optional variable values.
1. type
The type
argument specifies the expected data type of the variable to help Terraform validate that the value provided for the variable matches the expected type.
variable "example_var" {
type = string
}
2. default
The default
argument provides the default value for the variable. If no value is explicitly passed when applying the configuration, Terraform will use the default value.
variable "example_var" {
type = string
default = "default_value"
}
3. description
The description
argument allows you to add a human-readable description to the variable. This description can help other users understand the purpose of the variable. As a suggested good practice, you can use the same description as the Terraform docs.
For example, the name variable description from the Azure Virtual Network page:
variable "name" {
type = string
description = "(Required) The name of the virtual network. Changing this forces a new resource to be created.."
}
4. validation
The validation
optional argument enables you to specify a validation rule using a function that the value of the variable must pass. If the validation fails, Terraform will raise an error.
variable "example_var" {
type = number
validation {
condition = var.example_var > 0
error_message = "Value must be greater than 0."
}
}
5. sensitive
When sentsitive
optional argument is set to true
, the value of the variable is treated as sensitive information. Terraform will take care to prevent the value from being shown in logs and outputs. Note that Terraform will still record sensitive values in the state, so anyone who can access the state data will have access to the sensitive values in cleartext.
variable "password" {
type = string
sensitive = true
}
6. nullable
The nullable
argument in a variable block controls whether the module caller may assign the value null
to the variable.
A null
value is one that represents absence or omission. If you set an argument of a resource to null
, Terraform behaves as though you had completely omitted it — it will use the argument’s default value if it has one or raises an error if the argument is mandatory.
variable "example" {
type = string
nullable = false
}
Before Terraform 1.3.0, if you were using objects, you simply couldn’t provide default values to the parameters of those objects.
resource "aws_instance" "this" {
for_each = var.instances
instance_type = lookup(each.value, "instance_type", "t2.micro")
ami = lookup(each.value, "ami_id", "ami324324")
tags = merge({
Name = each.key
}, lookup(each.value, "tags", {}))
}
variable "instances" {
type = map(any)
description = <<-EOT
instance_type = string
ami_id = string
tags = map(string)
EOT
}
The only way to do that was to use a lookup function. This function would check for a key in your object map, and if that key didn’t exist, it would use the default value provided.
Note: lookup takes three arguments, the first is the map/object you provide, the second is the key you are searching for, and the third is the default value it will add if the key is missing.
This meant that in order to take advantage of this, what you had to do is, declare your variable as a map(any), so that it supports anything inside of it. To still make your code more readable, you could’ve added the parameters of your object in the description, but still, this was just a hacky way of doing things.
After Terraform 1.3.0, the optional argument for objects was added so your code could easily transform to:
resource "aws_instance" "this" {
for_each = var.instances
instance_type = each.value.instance_type
ami = each.value.ami_id
tags = merge({
Name = each.key
}, each.value.tags)
}
variable "instances" {
type = map(object({
instance_type = optional(string, "t2.micro")
ami_id = optional(string, "ami324324")
tags = optional(map(string), {})
}))
}
This makes your code not only more readable, but your variable very easy to understand now because it has all the parameters defined in the type.
Note: Optional takes two arguments (the parameter type and the default value of the parameter)
default = {
inst1 = {}
inst2 = {}
inst3 = {
instance_type = "t3.micro"
}
}
If you add a default value, or if you use a tfvars file to give a value like in the above example, we will create two instances with the default values specified in the optionals, and one that has the instance_type changed to “t3.micro”.
Using the type constraint when defining an input variable specifies its expected data type. If no type constraint is set, then a value of any type is accepted. If both the type
and default
arguments are specified, the given default value must be convertible to the specified type.
The Terraform language will automatically convert number
and bool
values to string
values when needed, and vice-versa as long as the string contains a valid representation of a number or boolean value.
variable "example_var" {
type = number
}
Acceptable ‘primitive’ type values in Terraform are:
- string
- bool
- number
- any (indicates a value of any type is accepted).
The type constructors also allow you to specify complex types such as collections:
list(<TYPE>)
set(<TYPE>)
map(<TYPE>)
object({<ATTR NAME> = <TYPE>, ... })
tuple([<TYPE>, ...])
Terraform is really powerful, but to achieve an end-to-end secure GitOps approach, you need to use a product that can run your Terraform workflows. Spacelift takes managing Terraform to the next level by giving you access to a powerful CI/CD workflow and unlocking features such as:
- Policies (based on Open Policy Agent) — You can control how many approvals you need for runs, the kind of resources you can create, and the kind of parameters these resources can have, and you can also control the behavior when a pull request is open or merged.
- Multi-IaC workflows — Combine Terraform with Kubernetes, Ansible, and other IaC tools such as OpenTofu, Pulumi, and CloudFormation, create dependencies among them, and share outputs
- Build self-service infrastructure — You can use Blueprints to build self-service infrastructure; simply complete a form to provision infrastructure based on Terraform and other supported tools.
- Integrations with any third-party tools — You can integrate with your favorite third-party tools and even build policies for them. For example, you can Integrate security tools in your workflows using Custom Inputs.
Spacelift enables you to create private workers inside your infrastructure, which helps you execute Spacelift-related workflows on your end. The documentation provides more information on configuring private workers.
To learn more about Spacelift, create a free account today or book a demo with one of our engineers.
Variables can be specified with optional parameters to enhance the quality of your code. Optional variables in Terraform modules allow you to customize module behavior without requiring all parameters to be explicitly defined. Including appropriate optional parameters in variables depending on your use case can be useful. As a best practice, you should consider at least including the description to help with the readability of your code, where you may consider adding other parameters unnecessary.
Note: New versions of Terraform are placed under the BUSL license, but everything created before version 1.5.x stays open-source. OpenTofu is an open-source version of Terraform that expands on Terraform’s existing concepts and offerings. It is a viable alternative to HashiCorp’s Terraform, being forked from Terraform version 1.5.6.
Automate Terraform Deployments with Spacelift
Automate your infrastructure provisioning, build more complex workflows based on Terraform using policy as code, programmatic configuration, context sharing, drift detection, resource visualization, and many more.