Variables, Resource Attributes, and Dependencies #
Overview #
Terraform uses variables to parameterize configurations, resource attributes to create dependencies between resources, and data sources to reference existing infrastructure.
Variables #
To assign variables, we need to make use of a variable block:
variable "filename" {
default = "/root/pets.txt"
}
variable "content" {
default = "We love pets!"
}
variable "prefix" {
default = "Mrs"
}
variable "separator" {
default = "."
}
variable "length" {
default = "1"
}resource "local_file" "pet" {
filename = "/root/pets.txt"
content = "We love pets!"
}
resource "random_pet" "my-pet" {
prefix = "Mrs"
separator = "."
length = "1"
}To make use of the variables, we need to reference them with var.:
resource "local_file" "pet" {
filename = var.filename
content = var.content
}
resource "random_pet" "my-pet" {
prefix = var.prefix
separator = var.separator
length = var.length
}Now when you run terraform apply, it will use the default values set in the variable block.
Another Example #
resource "aws_instance" "webserver" {
ami = var.ami
instance_type = var.instance_type
}variable "ami" {
default = "ami-0edab43b6fa892279"
}
variable "instance_type" {
default = "t2.micro"
}Ways to Pass Variables #
1. Interactive Prompt #
You can leave the variables empty and Terraform will prompt for values at runtime:
variable "ami" {
}
variable "instance_type" {
}This will prompt “Enter a value:” for each variable when you run terraform apply.
2. Command-Line Flags #
terraform apply -var="ami=ami-0edab43b6fa892279" -var="instance_type=t2.micro"3. Environment Variables #
export TF_VAR_ami=ami-0edab43b6fa892279
export TF_VAR_instance_type=t2.micro
terraform apply4. Variable Definition Files #
ami = "ami-0edab43b6fa892279"
instance_type = "t2.micro"terraform apply -var-file="variables.tfvars"Files are auto-loaded by Terraform if they are named:
terraform.tfvarsterraform.tfvars.json*.auto.tfvars*.auto.tfvars.json
Any other names require the -var-file flag.
Variable Definition Precedence #
| Order | Option | Example |
|---|---|---|
| 1 | Environment Variables | export TF_VAR_type=t2.micro |
| 2 | terraform.tfvars |
type = "t2.micro" |
| 3 | *.auto.tfvars (alphabetical order) |
type = "t2.micro" |
| 4 | -var or -var-file (CLI flags) |
terraform apply -var "type=t2.medium" |
Note: Command-line flags have the highest precedence, followed by
*.auto.tfvars, thenterraform.tfvars, and finally environment variables.
Variable Arguments #
variable "ami" {
default = "ami-0edab43b6fa892279"
description = "Type of AMI"
type = string
sensitive = true
}
variable "instance_type" {
default = "t2.micro"
description = "Size of instance"
type = string
sensitive = false
}Validation Rules #
variable "ami" {
type = string
description = "The id of the machine image (AMI) to use for the server."
validation {
condition = substr(var.ami, 0, 4) == "ami-"
error_message = "The AMI must start with 'ami-'."
}
}Error example:
$ terraform apply -var "ami=abc-11223"
Error: Invalid value for variable
on main.tf line 1:
1: variable "ami" {
The image_id value must be a valid AMI id, starting with "ami-".
This was checked by the validation rule at main.tf:5,3-13This will fail because the value doesn’t start with ami-.
Variable Types #
| Type | Example |
|---|---|
string |
"/root/pets.txt" |
number |
42 |
bool |
true / false |
any |
Default value |
list |
["t2.micro", "t2.small", "t2.medium"] |
map |
{ region1 = "us-east-1", region2 = "us-west-2" } |
object |
Complex data structure |
tuple |
Ordered data structure |
Note: Terraform will always try to convert the value to the type specified. If it cannot convert the value, it will throw an error.
Basic Types #
variable "count" {
default = 2
type = number
description = "Count of VM's"
}
variable "monitoring" {
default = true
type = bool
description = "Enable detailed monitoring"
}If you put the number 2 in quotes, Terraform will try to convert it to a number. However, type mismatches that can’t be converted will fail:
variable "monitoring" {
default = 1
type = bool
description = "Enable detailed monitoring"
}$ terraform init
Error: Invalid default value for variable
on variables.tf line 3, in variable "monitoring":
3: default = 1
This default value is not compatible with the variable's type constraint: bool required.List #
variable "servers" {
default = ["web1", "web2", "web3"]
type = list
}Lists are 0-indexed: servers[0] = "web1", servers[1] = "web2", servers[2] = "web3".
resource "aws_instance" "web" {
ami = var.ami
instance_type = var.instance_type
tags = {
name = var.servers[0]
}
}var.servers[0] will be "web1".
List of a Type #
variable "servers" {
default = ["web1", "web2", "web3"]
type = list(string)
}variable "prefix" {
default = [1, 2, 3]
type = list(number)
}If the type and default value are not compatible, it will throw an error:
variable "servers" {
default = ["web1", "web2", "web3"]
type = list(number)
}$ terraform plan
Error: Invalid default value for variable
on variables.tf line 3, in variable "prefix":
3: default = ["Mr", "Mrs", "Sir"]
This default value is not compatible with the variable's type constraint: a number is required.Map #
variable "instance_type" {
type = map
default = {
"production" = "m5.large"
"development" = "t2.micro"
}
}resource "aws_instance" "production" {
ami = var.ami
instance_type = var.instance_type["development"]
}Map of a Type #
variable "instance_type" {
default = {
"production" = "m5.large"
"development" = "t2.micro"
}
type = map(string)
}variable "server_count" {
default = {
"web" = 3
"db" = 1
"agent" = 2
}
type = map(number)
}Set #
The set type is a collection of unique values. It is similar to a list, but does not allow duplicate values.
variable "servers" {
default = ["web1", "web2", "web3"]
type = set(string)
}This will throw an error because "web2" is duplicated:
variable "prefix" {
default = ["web1", "web2", "web2"]
type = set(string)
}Object #
Objects can create complex data structures with multiple attributes:
variable "Jen" {
type = object({
name = string
color = string
age = number
food = list(string)
favorite_pet = bool
})
default = {
name = "Jen"
color = "blue"
age = 30
food = ["pizza", "sushi", "tacos"]
favorite_pet = true
}
}Tuple #
A tuple is an ordered list of values of different types:
variable "web" {
type = tuple([string, number, bool])
default = ["web1", 3, true]
}This will throw an error because the tuple has too many elements:
variable "db" {
type = tuple([string, number, bool])
default = ["db1", 1, true, "db2"]
}$ terraform plan
Error: Invalid default value for variable
on variables.tf line 3, in variable "db":
3: default = ["db1", 1, true, "db2"]
This default value is not compatible with the variable's type constraint: tuple requiredOutput Variables #
Terraform allows you to define output variables. These are values returned after a successful terraform apply. They can be used to display information about created resources or to pass values to other modules.
resource "aws_instance" "web" {
ami = var.ami
instance_type = var.instance_type
}
output "pub_ip" {
value = aws_instance.web.public_ip
description = "The public IP address of the web server"
}variable "ami" {
default = "ami-0edab43b6fa892279"
}
variable "instance_type" {
default = "t2.micro"
}When you run terraform apply, after the resources are created, it will display the output:
$ terraform apply
...
Output:
pub_ip = "123"You can also use terraform output to display all outputs, or terraform output pub_ip to display a specific output variable.
Note:
terraform planwill not render outputs — onlyterraform applyorterraform outputwill. This is because outputs are only available after the resources have been created.
Note: You can use any name for a variable except for the following reserved words:
source,version,providers,count,for_each,lifecycle,depends_on, andlocals.
Sensitive Variables #
To handle sensitive information like passwords, API keys, and other secrets, mark a variable as sensitive:
variable "db_password" {
default = "supersecretpassword"
type = string
sensitive = true
}When a variable is marked as sensitive, Terraform will not display its value in the output of terraform plan or terraform apply. However, they are stored as plain text in the state file.
Resource Attributes and Dependencies #
When you create a resource, it has attributes that can be referenced by other resources. This creates a dependency between the resources.
resource "aws_key_pair" "alpha" {
key_name = "alpha"
public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD3……alpha@a-server"
}This will create an AWS key pair resource with attributes that can be referenced by other resources. For example, you can reference aws_key_pair.alpha.id in another resource to create a dependency. (This can be found in the documentation for the AWS provider.)
Implicit Dependencies #
resource "aws_key_pair" "alpha" {
key_name = "alpha"
public_key = "ssh-rsa…"
}
resource "aws_instance" "cerberus" {
ami = var.ami
instance_type = var.instance_type
key_name = aws_key_pair.alpha.key_name
}Here the aws_instance.cerberus resource has a dependency on the aws_key_pair.alpha resource because it references the key_name attribute. This means that aws_key_pair.alpha must be created before aws_instance.cerberus.
Note: By default, Terraform creates all resources in parallel. If there are dependencies between resources, Terraform will create them in the correct order. If there are no dependencies, Terraform may create them in any order.
Explicit Dependencies #
resource "aws_instance" "db" {
ami = var.db_ami
instance_type = var.web_instance_type
}
resource "aws_instance" "web" {
ami = var.web_ami
instance_type = var.db_instance_type
depends_on = [
aws_instance.db
]
}In this example, the aws_instance.web resource has an explicit dependency on aws_instance.db because of the depends_on argument. This means aws_instance.db must be created before aws_instance.web, regardless of whether any attributes are being referenced.
Resource Targeting #
resource "random_string" "server-suffix" {
length = 6
upper = false
special = false
}
resource "aws_instance" "web" {
ami = "ami-06178cf087598769c"
instance_type = "m5.large"
tags = {
Name = "web-${random_string.server-suffix.id}"
}
}The expression ${random_string.server-suffix.id} is an interpolation sequence — a resource attribute reference used to reference the output of one resource as an input to another.
If the length gets updated to 5, the random string resource will be updated first, and then the AWS instance will be updated with the new random string because of the dependency.
To update only the random string resource without updating the AWS instance, use resource targeting:
terraform apply -target=random_string.server-suffixData Sources #
There are many cases where a resource already exists or was created outside of Terraform, and you want to reference it in your configuration. You can use a data source to reference existing infrastructure.
Say you have an existing AWS key pair:
| Attribute | Value |
|---|---|
| Project | cerberus |
| Name | alpha |
| Type | rsa |
| Fingerprint | 1a:2b:3c:4d:5e:6f:7g:8h:9i:0j:1k:2l:3m:4n:5o:6p |
| ID | key-1234567890abcdef0 |
data "aws_key_pair" "cerberus-key" {
key_name = "alpha"
}
resource "aws_instance" "cerberus" {
ami = var.ami
instance_type = var.instance_type
key_name = data.aws_key_pair.cerberus-key.key_name
}The aws_instance.cerberus resource references the key_name attribute of the data source, allowing us to use resources created outside of Terraform in our configuration.
Using Filters #
You can use filters to reference specific resources. For example, if there are multiple key pairs, use a filter to target the right one:
data "aws_key_pair" "cerberus-key" {
filter {
name = "tag:Project"
values = ["cerberus"]
}
}
resource "aws_instance" "cerberus" {
ami = var.ami
instance_type = var.instance_type
key_name = data.aws_key_pair.cerberus-key.key_name
}Resource vs. Data Source #
| Aspect | Resource | Data Source |
|---|---|---|
| Keyword | resource |
data |
| Purpose | Creates, updates, and destroys infrastructure | Reads and references existing infrastructure |
| Alias | Also called managed resources | Also called data resources |