Skip to main content

Variables, Resource Attributes, and Dependencies

·1818 words·9 mins
Jack Warner
Author
Jack Warner
A little blog by me

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:

variables.tf
variable "filename" {
    default = "/root/pets.txt"
}
variable "content" {
    default = "We love pets!"
}
variable "prefix" {
    default = "Mrs"
}
variable "separator" {
    default = "."
}
variable "length" {
    default = "1"
}
main.tf
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.:

main.tf
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
#

main.tf
resource "aws_instance" "webserver" {
    ami           = var.ami
    instance_type = var.instance_type
}
variables.tf
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:

variables.tf
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 apply

4. Variable Definition Files
#

variables.tfvars
ami           = "ami-0edab43b6fa892279"
instance_type = "t2.micro"
terraform apply -var-file="variables.tfvars"

Files are auto-loaded by Terraform if they are named:

  • terraform.tfvars
  • terraform.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, then terraform.tfvars, and finally environment variables.


Variable Arguments
#

variables.tf
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
#

variables.tf
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-13

This 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
#

variables.tf
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:

variables.tf
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
#

variables.tf
variable "servers" {
    default = ["web1", "web2", "web3"]
    type    = list
}

Lists are 0-indexed: servers[0] = "web1", servers[1] = "web2", servers[2] = "web3".

main.tf
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
#

variables.tf
variable "servers" {
    default = ["web1", "web2", "web3"]
    type    = list(string)
}
variables.tf
variable "prefix" {
    default = [1, 2, 3]
    type    = list(number)
}

If the type and default value are not compatible, it will throw an error:

variables.tf
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
#

variables.tf
variable "instance_type" {
    type = map
    default = {
        "production"  = "m5.large"
        "development" = "t2.micro"
    }
}
main.tf
resource "aws_instance" "production" {
    ami           = var.ami
    instance_type = var.instance_type["development"]
}

Map of a Type
#

variables.tf
variable "instance_type" {
    default = {
        "production"  = "m5.large"
        "development" = "t2.micro"
    }
    type = map(string)
}
variables.tf
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.

variables.tf
variable "servers" {
    default = ["web1", "web2", "web3"]
    type    = set(string)
}

This will throw an error because "web2" is duplicated:

variables.tf
variable "prefix" {
    default = ["web1", "web2", "web2"]
    type    = set(string)
}

Object
#

Objects can create complex data structures with multiple attributes:

variables.tf
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:

variables.tf
variable "web" {
    type    = tuple([string, number, bool])
    default = ["web1", 3, true]
}

This will throw an error because the tuple has too many elements:

variables.tf
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 required

Output 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.

main.tf
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"
}
variables.tf
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 plan will not render outputs — only terraform apply or terraform output will. 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, and locals.


Sensitive Variables
#

To handle sensitive information like passwords, API keys, and other secrets, mark a variable as sensitive:

variables.tf
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.

main.tf
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
#

main.tf
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
#

main.tf
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
#

main.tf
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-suffix

Data 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
main.tf
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:

main.tf
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

Related