Expression Types
In Terraform, Expression are used to define and manipulate the infrastructure configurations. While writing the Terraform code, you would be working with two major types of expressions that can help you manage your infrastructure resources. These include the following two major types of expressions:
- Literal Expression
- Complex Expression
Literal Expression
In Terraform, a literal expression is a simple expression that should directly represent constant value. It is a way to write a value in a direct and explicit manner without any calculation or operation. Terraform has types of literal expressions for each its value types that offer you the facility to write values straightforwardly. Examples of Literal Expressions include:
- String: "hello", 'hello'
- Number: 5, 3.14
- Boolean: true, false
- List: ["us-east-1", "ap-southeast-1", "ap-southeast-2" ]
- Set: ['us-east-1', 'ap-southeast-1', 'ap-southeast-2', 'us-east-1']
- Map: { service: "Ec2-instance", Costcenter: "Production" }
- Null: null
Complex Expression
A complex expression is an expression that allows you to Reference output data from resources you have defined in your configuration (References to Named Values), Perform arithmetic functions (addition, subtraction, multiplication and division), and use conditional expressions to evaluate and select values based on certain conditions.
References to Named Values
In Terraform, you can reference various types of named values to use in your configurations. These named values are expressions that point to specific values, and you can use them as standalone expressions or combine them with others to compute new values.
- Resources
- Input variables
- Local values
- Child module outputs
- Data sources
- Filesystem and workspace info
- Block-local values
1. Resources
In Terraform, a resource reference is an expression that refers to the attributes of a managed resource. Syntax is simple:
<RESOURCE TYPE>.<LOCAL_NAME>
Here,
RESOURCE_TYPE
: The type of resource - eg: aws_instance, azure_virtual_machine
LOCAL_NAME
: The name to be given to the resource (e.g., my_ec2_instance, my_vm)
The value of a resource reference depends on how the resource is defined:
resource "aws_iam_role" "s3" {
assume_role_policy = data.aws_iam_policy_document.s3_policy.json
name = "s2-access-role"
}
resource "aws_iam_role_policy_attachment" "s3policy" {
role = aws_iam_role.s3.name
policy_arn = var.policy_arn
}
resource "aws_instance" "example_ec2_instance" {
ami = "ami-0123456789"
instance_type = "t2.micro"
}
resource "aws_eip" "my_eip" {
vpc = true
instance = aws_instance.example_ec2_instance.id
}
As shown in the above example, the aws_iam_role.s3.name expression accesses the name attribute, which is a direct attribute of the aws_iam_role resource named "s3". The value is used to set the role argument of the aws_iam_role_policy_attachment resource and the aws_instance.example_ec2_instance.id expression accesses the id attribute, which is a indirect attribute of the aws_instance resource named "example_ec2_instance". The value is used to set the instance argument of the aws_eip resource.
resource "aws_instance" "server" {
count = 3
ami = "ami-0123456789"
instance_type = "t2.micro"
tags = {
Name = "Ec2-${count.index}"
}
}
resource "aws_eip" "my_eip" {
vpc = true
instance = aws_instance.server[0].id
}
In this example, the expression aws_instance.server[0].id accesses the id attribute of the first instance of the aws_instance resource named "server". This value sets the instance argument of the aws_eip resource.
resource "aws_s3_bucket" "buckets" {
for_each = tomap ({
"my-bucket-1" = "us-east-1"
"my-bucket-2" = "us-west-2"
"my-bucket-3" = "eu-east-1"
})
bucket = each.key
region = each.value
}
resource "aws_s3_bucket_versioning" "versioning_bucket" {
bucket = aws_s3_bucket.buckets["my-bucket-1"].id
versioning_configuration {
status = "Enabled"
}
}
In this example, the aws_s3_bucket.buckets["my-bucket-1"].id expression accesses the id attribute of the first instance of the aws_s3_bucket resource named "buckets." This value is then used to set the bucket argument of the aws_s3_bucket_versioning resource.
2. Input variables
In Terraform, input variables are declared using the variable keyword. To access the value of an input variable, you would use the syntax
var.<local_name>
Here is the breakdown of the syntax:
var
is a keyword to access an input block.
local_name
is the name given to the input variable, when declared. This is what you will use in your Terraform module to refer to the variable.
variable "instance_type" {
type = string
default = "t2.micro"
description = "The type of EC2 instance to provision"
}
resource "aws_instance" "example_ec2_instance" {
ami = "ami-0123456789"
instance_type = var.instance_type
}
Here, the expression var.instance_type refers to the value of the input variable instance_type and assigns that to the instance_type attribute of the aws_instance resource.
3. Local values
In Terraform, a local value is a temporary value that exists only within the scope of a module. A local value can be referenced using the following syntax:
local.<name>
Here is the breakdown of the syntax:
local
is the keyword to access a local block.
name
is an identifier that you defined inside of the local block
local {
region = "us-west-2"
}
resource "aws_s3_bucket" "my_bucket" {
bucket = "my-terraform-bucket"
region = local.region
acl = "private"
}
In the above example, local.region is an expression that is utilized to access the value of the region local value to assign to the region attribute of the aws_s3_bucket resource.
Local values can reference other local values, even if they are defined in the same locals block. This enables you to construct complex expressions by reusing multiple local values. Example
local {
region = "us-west-2"
bucket_prefix = "my-terraform-bucket-"
bucket_name = "${local.bucket_prefix}-${local.region}"
}
resource "aws_s3_bucket" "my_bucket" {
bucket = local.bucket_name
region = local.region
acl = "private"
}
This example shows how local values can refer to each other to build complex expressions. We define three local values: region, bucket_prefix, and bucket_name. The bucket_name local value refers to the bucket_prefix and region local values using string interpolation (
${}
). Then we apply the bucket_name local value to the bucket attribute of the aws_s3_bucket resource.
However, beware of introducing a
circular dependency
, a local value that depends on another local value, which in turn depends on the first local value. This can cause errors and infinite loops. For instance
local {
region = "${local.bucket_name}"
bucket_prefix = "my-terraform-bucket-"
bucket_name = "${local.bucket_prefix}-${local.region}"
}
resource "aws_s3_bucket" "my_bucket" {
bucket = local.bucket_name
region = local.region
acl = "private"
}
Error: Cycle: local.bucket_name (expand), local.region (expand)
This would create a circular dependency, where region references bucket_name, and bucket_name in turn would reference region, hence leading to an error.
4. Child Module Outputs
In Terraform, a module is a self-contained block of Terraform configuration that can be reused across projects and environments. A module, in a nutshell, is a good set of related resources, data sources, and variables that work together to accomplish a certain task. To access the value of an output from a module, use the following format:
module.<local_name>
Here is the breakdown of the syntax:
module
is the keyword to access a module block.
local_name
is the name you used when declaring the module.
The value of a child module reference depends on how the child module is defined:
// File: policy/main.tf (Child Module)
resource "aws_iam_policy" "policy" {
name = "s3-access-policy"
path = "/"
}
output "s3_policy_arn" {
value = aws_iam_policy.s3.arn
description = "The password used for db_instance creation"
}
// File: main.tf (Root Module)
module "policy-module" {
source = "./policy"
}
resource "aws_iam_role_policy_attachment" "role-attachment" {
policy_arn = module.policy-module.s3_policy_arn
role = "IAM-role"
}
Here, we use the expression module.policy.s3_policy_arn to access the value of the s3_policy_arn output from our policy child module and assign it to the policy_arn attribute of the aws_iam_role_policy_attachment resource.
// File: password/main.tf (Child Module)
resource "random_password" "password" {
length = 40
special = false
}
output "db_password" {
value = random_password.password
description = "The password used for db_instance creation"
sensitive = true
}
// File: main.tf (Root Module)
module "password-module" {
count = 2
source = "./password"
}
resource "aws_db_instance" "db_instance" {
count = 2
engine = "mysql"
db_name = "database-1"
username = "admin"
password = module.password[each.count].db_password
}
Here the module.password-module[each.count].db_password accesses the db_password output value from the corresponding instance of the module password-module to set the password attribute of the aws_db_instance resource. The syntax each.count will iterate instances of the module.
// File: s3/main.tf (Child Module)
resource "aws_s3_bucket" "buckets" {
bucket = var.bucket-name
region = var.bucket-region
}
variable "bucket-name" {
value = aws_s3_bucket.buckets.id
}
variable "bucket-region" {
value = aws_s3_bucket.buckets.region
}
output "id" {
value = aws_s3_bucket.buckets.id
}
// File: main.tf (Root Module)
local {
buckets = {
"my-bucket-1" = "us-east-1"
"my-bucket-2" = "us-west-2"
"my-bucket-3" = "eu-east-1"
}
}
module "s3-module" {
for_each = tomap(local.buckets)
source = "./s3"
bucket-name = each.key
bucket-region = each.value
}
resource "aws_s3_bucket_versioning" "versioning_bucket" {
for_each = tomap(local.buckets)
bucket = "mysql"
versioning_configuration {
status = "Enabled"
}
}
The expression module.s3-module[each.key].id in this example accesses the value of the id output from the respective instance of the s3-module module and assigns it as a value to the bucket attribute of the aws_s3_bucket_versioning resource.
5. Data Sources
In Terraform, a data source is a read-only resource that provides the necessary input data to your Terraform configurations. The general syntax to access a data source is as shown below:
data.<DATA_TYPE>.<LOCAL_NAME>
Here is the breakdown of the syntax:
data
is the keyword to access a module block.
DATA_TYPE
is the type of data source you are accessing. For example: aws_ami, aws_vpc.
LOCAL_NAME
is the local name you gave to the data source when you declared it.
The value of a data source reference depends upon how the data source is said to be defined:
data "template_file" "example" {
template = "ls -al $${location}"
vars = {
location = "/var/logs"
}
}
resource "terraform_data" "provisioner_wrapper" {
provisioner "local-exec" {
command = data.template_file.example.rendered
}
}
Here, we access the output value of the rendered output through the data source using the expression data.template_file.example.rendered, and then we will use this value to set the command attribute of the terraform_data resource.
data "aws_instances" "test" {
count = 2
instance_tags = {
Env = "Production-instance-{count.index}"
}
}
resource "aws_eip" "eips" {
count = 2
instance = data.aws_instances.test.id[count.index]
}
In this example, to access the rendered list output value from the aws_instances data source, we use the expression data.aws_instances.test.id[count.index]. The value then would be used to set the instance attribute of the aws_eip resource.
data "aws_instances" "data_instance" {
for_each = tomap ({
"stageinstance" = {
"ami" = "ami-abc123"
"instance_type" = "t2.micro"
},
"prodinstance" = {
"ami" = "ami-def456"
"instance_type" = "t2.small"
}
})
instance_tags = {
ami = each.value.ami
Instance_type = each.value.instance_type
}
}
resource "aws_eip" "example" {
for_each = data.aws_instance.data_instance
instance = each.value.id
}
Here, we access the entire map output value from the data source by using the expression data.aws_instance.data_instance. We use this output in setting the for_each meta-argument of the aws_eip resource.
6. Filesystem and Workspace Info
Terraform provides three values to access filesystem paths:
path.module
The built-in expression path.module represents the filesystem path of the module where this expression is used. Example
// File: executor/main.tf (Child Module)
resource "null_resource" "module_executor" {
provisioner "local-exec" {
command = "echo 'Child Module is in ${path.module}'"
}
}
// File: main.tf (Root Module)
module "executor" {
source = "./executor"
}
resource "null_resource" "root_executor" {
provisioner "local-exec" {
command = "echo 'Root Module is in ${path.module}'"
}
}
Terraform will perform the following actions: # module.executor.null_resource.module_executor will be created + resource "null_resource" "module_executor" { + id = (known after apply) } # null_resource.root_executor will be created + resource "null_resource" "root_executor" { + id = (known after apply) } Plan: 2 to add, 0 to change, 0 to destroy. Enter a value: yes module.executor.null_resource.module_executor: Creating... null_resource.root_executor: Creating... module.executor.null_resource.module_executor: Provisioning with 'local-exec'... module.executor.null_resource.module_executor: Executing: ["/bin/sh" "-c" "echo \"Child Module is in executor/\"] module.executor.null_resource.module_executor: Child Module is in executor/ null_resource.root_executor: Provisioning with 'local-exec'... null_resource.root_executor (local-exec): Executing: ["/bin/sh" "-c" "echo \"Root Module is in .\"] null_resource.root_executor: Root Module is in . null_resource.root_executor: Creation complete after 0s [id=4425172414869377883]
In the above example, The child module is in the executor directory and the path.module expression in the child module resources will return the path "executor/" and The root module is using the child module and the path.module expression in the root module resources will return the path of the root module that is the directory containing the main.tf file.
path.root
The built-in expression path.root is the filesystem path of the root module of the Terraform configuration. When one uses the terraform plan or terraform apply command, Terraform automatically sets the value of path.root to the current working directory (CWD) from which you ran the command. It does this because Terraform assumes you ran that command from the directory of the root module, rather than from a subdirectory. For example
// File: File: executor/main.tf (Child Module)
resource "null_resource" "module_executor" {
provisioner "local-exec" {
command = "echo 'Root Module is in ${path.root}'"
}
}
// File: main.tf (Root Module)
module "executor" {
source = "./executor"
}
Terraform will perform the following actions: # module.executor.null_resource.module_executor will be created + resource "null_resource" "module_executor" { + id = (known after apply) } Plan: 1 to add, 0 to change, 0 to destroy. Enter a value: yes module.executor.null_resource.module_executor: Creating... module.executor.null_resource.module_executor: Provisioning with 'local-exec'... module.executor.null_resource.module_executor: Executing: ["/bin/sh" "-c" "echo \"Root Module is in .\"] module.executor.null_resource.module_executor: Root Module is in . null_resource.root_executor: Creation complete after 0s [id=4425172414869377883]
In this example, the expression path.root in the child module's resources would yield the path ".", representing the directory from which the Terraform command had been executed and where the main Terraform configuration file main.tf was located.
NOTE:
path.module
and path.root
return absolute path.
path.cwd
path.cwd is a built-in Terraform expression that returns the filesystem path of the original working directory from where you ran Terraform. The path is absolute, meaning including the full filesystem structure. Example
// File: executor/main.tf (Child Module)
resource "null_resource" "module_executor" {
provisioner "local-exec" {
command = "echo 'Terraform Executing location ${path.cwd}'"
}
}
// File: main.tf (Root Module)
module "executor" {
source = "./executor"
}
Terraform will perform the following actions: # module.executor.null_resource.module_executor will be created + resource "null_resource" "module_executor" { + id = (known after apply) } Plan: 1 to add, 0 to change, 0 to destroy. Enter a value: yes module.executor.null_resource.module_executor: Creating... module.executor.null_resource.module_executor: Provisioning with 'local-exec'... module.executor.null_resource.module_executor: Executing: ["/bin/sh" "-c" "echo \"Terraform Executing location /home/ubuntu/terraform\"] module.executor.null_resource.module_executor: Terraform Executing location /home/ubuntu/terraform null_resource.root_executor: Creation complete after 0s [id=4425172414869377883]
In this example the path.cwd expression in the child module resources will return the path "/home/ubuntu/terraform", that is the directory from which you have executed the Terraform command.
terraform.workspace
terraform.workspace is a built-in Terraform input variable that holds the name of the currently selected workspace. A Terraform workspace represents a way of managing multiple, isolated environments within one Terraform configuration. Each workspace maintains its own statefile that allows you to manage various, independent environments. Such as
resource "null_resource" "module_executor" {
provisioner "local-exec" {
command = "echo 'Current workspace is ${terraform.workspace}'"
}
}
Terraform will perform the following actions: # null_resource.module_executor will be created + resource "null_resource" "module_executor" { + id = (known after apply) } Plan: 1 to add, 0 to change, 0 to destroy. Enter a value: yes null_resource.module_executor: Creating... null_resource.module_executor: Provisioning with 'local-exec'... null_resource.module_executor: Executing: ["/bin/sh" "-c" "echo \"Current workspace is default\"] null_resource.module_executor: Current workspace is default null_resource.root_executor: Creation complete after 0s [id=4425172414869377883]
In the above example, the expression terraform.workspace within resources will return the name of the selected workspace. If you don't explicitly specify the selected workspace, Terraform uses the default workspace.
7. Block-Local Values
Besides the global values listed above, Terraform also has local values that exist locally in certain blocks of configurations or contexts. The local values are:
- Count-related values:
count.index
is available in resources that use the count metaargument.
- For-each values:
each.key
andeach.value
are available in resources that use the for_each meta argument.
- Provisioner and connection values:
self
is avaliable in provisioner and connection blocks.
Related Pages
- Variables - Input value and Local value
- Expressions - Expression Overview, Conditional Expression and Operator Expression
Feedback
Was this page helpful?