Uncover the power of conditional logic in Terraform with this comprehensive guide. Learn how to create dynamic, flexible, and efficient infrastructure using conditional expressions, built-in functions, and data sources. This post also includes best practices for maintaining readability and simplicity in your Terraform code.
Stephen A. Lizcano
10 min read
In the realm of infrastructure management with Terraform, conditionals have proven to be an invaluable tool for creating adaptable and flexible configurations. In this guide, we will delve into the nuances of Terraform and demonstrate how ternary expressions and functions can be used to tackle complex conditions with ease.
Terraform natively supports the classic IF-ELSE logic, a fundamental tool for handling basic scenarios. However, as our infrastructure grows in complexity, managing and maintaining advanced conditionals can become a challenging task.
Enter ternary expressions. These offer a concise and effective way to articulate conditionals within a single line of code. By using the syntax condition ? true_value : false_value
, we can dynamically adjust resource properties based on conditions.
Consider an instance where we have a variable labeled environment
, which can assume the values dev
or prod
. Depending on this variable, we aim to set the instance_type
for an EC2 instance resource.
variable "environment" {
description = "The environment for the infrastructure"
type = string
default = "dev"
}
resource "aws_instance" "example" {
instance_type = var.environment == "prod" ? "t2.large" : "t2.micro"
# Additional resource configurations...
}
In this scenario, the ternary expression evaluates the condition var.environment == "prod"
. If true, instance_type
is assigned as t2.large
; otherwise, it defaults to t2.micro
. This concise expression provides us with the power to dynamically adjust resource properties based on various conditions.
While ternary expressions present a resolution for simple conditions, Terraform extends a comprehensive set of built-in functions to manage intricate scenarios. These functions allow us to perform operations on variables, manipulate data, and craft sophisticated conditional logic.
A potent function, coalesce()
, accepts multiple arguments and returns the first non-null value encountered. This function is particularly useful when dealing with optional variables or defining default values.
variable "region" {
description = "The AWS region for the infrastructure"
type = string
default = null
}
resource "aws_instance" "example" {
ami = coalesce(var.custom_ami, data.aws_ami.latest.id, "ami-12345678")
instance_type = "t2.micro"
# Additional configurations...
}
In the above example, the coalesce()
function helps us to set the ami
property of the EC2 instance resource based on several possibilities. It first checks the value of var.custom_ami
and if null, falls back to data.aws_ami.latest.id
. If both values are null, it defaults to ami-12345678
. This ensures that the ami
property is always set, even if some values are optional or missing.
Functions serve as the secret ingredient that elevates conditional logic to a higher degree. They offer a wide spectrum of capabilities to manipulate data, perform computations, and construct sophisticated conditional statements.
Let's delve deeper into some of the essential functions and their practical applications.
contains()
The contains()
function checks if a specified element exists within a list or set. It is highly useful when dealing with dynamic configurations that depend on certain conditions.
variable "allowed_regions" {
description = "List of allowed AWS regions"
type =
=list(string)
default = ["us-east-1", "us-west-2"]
}
resource "aws_instance" "example" {
ami = contains(var.allowed_regions, var.region) ? "ami-12345678" : "ami-87654321"
instance_type = "t2.micro"
# Other resource configurations...
}
In this example, the ami
property is determined based on whether the var.region
exists within the var.allowed_regions
list. If it does, it uses ami-12345678
; otherwise, it defaults to ami-87654321
. This dynamic configuration enables us to adjust our infrastructure provisioning based on region-specific requirements.
file()
and fileexists()
These functions enable us to interact with files during provisioning. We can conditionally include or exclude files based on specific conditions, thereby enhancing the flexibility of our configuration.
variable "enable_ssh_key" {
description = "Flag to enable SSH key"
type = bool
default = true
}
resource "aws_instance" "example" {
# Other resource configurations...
provisioner "file" {
source = var.enable_ssh_key ? "ssh_key.pub" : null
destination = "/home/ubuntu/.ssh/authorized_keys"
}
}
Here, the file()
function is used to conditionally include the SSH key file based on the var.enable_ssh_key
flag. If the flag is set to true, the file is included during provisioning. Otherwise, it is excluded, ensuring a secure and controlled environment.
Data sources are powerful tools that allow us to fetch information from external systems and use it within our infrastructure configuration. By incorporating data sources into conditional expressions, we can devise dynamic conditionals based on real-time data.
For instance, let's consider a situation where we want to conditionally create a security group resource based on the existence of a specific VPC. We can use the aws_vpc
data source to fetch information about the VPC and then incorporate it into our conditional logic.
data "aws_vpc" "example" {
id = "vpc-12345678"
}
resource "aws_security_group" "example" {
count = data.aws_vpc.example != null ? 1 : 0
# Other resource configurations...
}
In this case, the count
argument of the aws_security_group
resource is set based on the existence of the VPC with the ID vpc-12345678
. If the VPC is found, one security group resource is created; otherwise, it is skipped.
As we unravel the capabilities of conditional logic in Terraform, it's crucial to adopt best practices to ensure efficient and maintainable code. Here are some key guidelines and techniques to maximize the benefits of conditional expressions.
While Terraform’s conditional expressions offer flexibility, it’s essential to strike a balance between readability and complexity. Aim to keep your conditionals concise and avoid overly complicated expressions that may hinder code comprehension.
variable "environment" {
description = "The environment for the infrastructure"
type = string
default = "dev"
}
resource "aws_instance" "example" {
ami = var.environment == "prod" ? "ami-12345678" : "ami-87654321"
# Other resource configurations...
}
In this example,
When orchestrating infrastructure with Terraform, one of the critical tools at our disposal is conditional logic. It provides us with the means to construct flexible and dynamic configurations. Let's begin by exploring the use of ternary expressions, a compact method of expressing conditionals in a single line of code.
Consider a scenario where we have a variable environment
that can be either dev
or prod
. We can utilize a ternary expression to dynamically assign the instance_type
for an AWS EC2 instance based on this variable.
variable "environment" {
description = "The environment for the infrastructure"
type = string
default = "dev"
}
resource "aws_instance" "example" {
instance_type = var.environment == "prod" ? "t2.large" : "t2.micro"
# Additional resource configurations...
}
In this instance, the ternary expression evaluates whether var.environment
equals prod
. If it does, instance_type
is set to t2.large
; otherwise, it reverts to t2.micro
. With this concise expression, we can dynamically tweak resource properties based on different conditions【6†source】.
Beyond basic ternaries, Terraform boasts a suite of built-in functions to handle more complex conditions. These functions empower us to manipulate data, carry out operations on variables, and establish intricate conditional logic.
Consider the function coalesce()
, which takes multiple arguments and returns the first non-null value. This function proves invaluable when working with optional variables or setting default values.
variable "region" {
description = "The AWS region for the infrastructure"
type = string
default = null
}
resource "aws_instance" "example" {
ami = coalesce(var.custom_ami, data.aws_ami.latest.id, "ami-12345678")
instance_type = "t2.micro"
# Other configurations...
}
In the example above, coalesce()
allows us to set the ami
property of the EC2 instance based on several possibilities. It first checks for the value of var.custom_ami
, and if null, falls back to data.aws_ami.latest.id
. If both are null, it defaults to ami-12345678
. This ensures the ami
property is always set, even if some values are optional or not available【7†source】.
Terraform's functions are the secret ingredients that elevate conditional logic. They offer a wide range of capabilities, from data manipulation to sophisticated conditional statements.
contains()
The contains()
function checks whether a given element is within a list or set. It's particularly useful when handling dynamic configurations dependent on specific conditions.
variable "allowed_regions" {
description = "List of allowed AWS regions"
type = list(string)
default = ["us-east-1", "us-west-2"]
}
resource "aws_instance" "example" {
ami = contains(var.allowed_regions, var.region) ? "ami-12345678" : "ami-87654321"
instance_type = "t2.micro"
# Other resource configurations...
}
In this example, the ami
property is set based on whether var.region
is present in the var.allowed_regions
list. If it exists, it uses ami-12345678
; otherwise, it defaults to ami-87654321
【8
†source】.
file()
and fileexists()
The file()
and fileexists()
functions offer the ability to interact with files during provisioning. We can conditionally include or exclude files based on certain conditions, which can enhance the flexibility of our configuration.
variable "enable_ssh_key" {
description = "Flag to enable SSH key"
type = bool
default = true
}
resource "aws_instance" "example" {
# Other resource configurations...
provisioner "file" {
source = var.enable_ssh_key ? "ssh_key.pub" : null
destination = "/home/ubuntu/.ssh/authorized_keys"
}
}
In this case, the file()
function conditionally includes the SSH key file based on the var.enable_ssh_key
flag. If the flag is set to true, the file is included during provisioning. Otherwise, it is excluded, ensuring a secure and controlled environment【8†source】.
Data sources serve as powerful tools that allow us to fetch information from external systems and use it within our infrastructure configuration. By using data sources within conditional expressions, we can create dynamic conditionals based on real-time data.
Consider a scenario where we want to conditionally create a security group resource based on the existence of a specific VPC. We can use the aws_vpc
data source to fetch information about the VPC and then incorporate it into our conditional logic.
data "aws_vpc" "example" {
id = "vpc-12345678"
}
resource "aws_security_group" "example" {
count = data.aws_vpc.example != null ? 1 : 0
# Other resource configurations...
}
In this example, the count
argument of the aws_security_group
resource is set based on the existence of the VPC with the ID vpc-12345678
. If the VPC is found, one security group resource is created; otherwise, it is skipped【9†source】.
As we navigate the capabilities of conditional logic in Terraform, it's crucial to adopt best practices to ensure efficient and maintainable code.
While Terraform's conditional expressions offer flexibility, it's vital to balance readability and complexity. Aim to keep your conditionals concise and avoid overly complicated expressions that may hinder code comprehension.
variable "environment" {
description = "The environment for the infrastructure"
type = string
default = "dev"
}
resource "aws_instance" "example" {
ami = var.environment == "prod" ? "ami-12345678" : "ami-87654321"
# Other resource configurations...
}
Here, the conditional expression checks if var.environment
is set to prod
and assigns the appropriate AMI accordingly. By keeping the conditional expression simple and straightforward, we improve code readability and maintainability【14†source】.
In conclusion, conditional logic is a potent tool in Terraform. It's not just about creating conditions; it's about creating dynamic, flexible, and efficient infrastructure as code. By understanding and effectively using conditional expressions and functions, we can take our infrastructure provisioning to the next level.
Share this article
Delivering the fastest path to security and compliance in the cloud.
© Copyright 2023 StarOps.
Proudly made in
Los Angeles, CA 🇺🇸
Lviv & Kyiv, Ukraine 🇺🇦