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

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:

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.

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,
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:
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 {
    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:
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:
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:

Related Pages

Feedback

Was this page helpful?