Terraform | Beyond IF-ELSE

Exploring Terraform’s Power with Ternary Expressions and Functions

Ink Insight 🧘🏼
ILLUMINATION
6 min readJun 14, 2023

--

When it comes to managing infrastructure with Terraform, I’ve discovered that conditionals are an indispensable tool for creating dynamic and flexible configurations.

In this post, we will go through some of the techniques in Terraform using ternary expressions and functions to handle complex conditions effortlessly.

1. Understanding Terraform Conditionals - The Foundation of Dynamic Configurations

Although Terraform uses the classic IF-ELSE logic as a core tool for handling basic circumstances, managing and maintaining sophisticated conditionals can be difficult as our infrastructure becomes more complex.

Ternary expressions are useful in this situation. They provide a clear and effective method for expressing conditionals in a single line of code.

Let’s investigate how ternary expressions can improve our Terraform configurations.

By employing the syntax condition ? true_value : false_value, we can dynamically set resource properties based on conditions.

Let’s take an example. Imagine we have a variable called environment , which can take values of either dev or prod. Based on this variable, we want 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"
# Other resource configurations…
}

In the above example, the ternary expression evaluates the condition var.environment == “prod”. If true, the instance_type is set to “t2.large”; otherwise, it defaults to “t2.micro”.

This succinct expression empowers us to dynamically adjust resource properties based on varying conditions.

2. Expanding Possibilities with Functions: Going Beyond the Basics

While ternary expressions provide a solution for simple conditions, Terraform offers an arsenal of built-in functions to handle more complex scenarios.

These functions enable us to perform operations on variables, manipulate data, and create sophisticated conditional logic.

One such powerful function is coalesce(). It accepts multiple arguments and returns the first non-null value encountered. This function proves valuable 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"
# Other configurations…

}

In the above example, the coalesce() function allows us to set the ami property of the EC2 instance resource based on multiple possibilities. It checks for the value of var.custom_ami first and, if null, falls back to data.aws_ami.latest.id.

If both are null, it defaults to ami-12345678. This ensures that the ami property is always set, even if some values are optional or unavailable.

These are just a glimpse into the vast possibilities that ternary expressions and functions unlock in Terraform.

With these techniques, we can write cleaner, more concise code, paving the way for efficient and flexible infrastructure provisioning.

3. Leveraging Functions for Conditional Logic (few more)

Functions are the secret sauce that takes conditional logic to the next level. They provide us with a wide array of capabilities to manipulate data, perform calculations, and create sophisticated conditional statements.

Let’s explore a few more of the essential functions and their practical applications.

a. contains(): This function checks if a given element exists within a list or set. It’s beneficial when dealing with dynamic configurations that depend 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 the above example, the ami property is set based on whether the var.region exists in the var.allowed_regions list. If it does, it uses ami-12345678; otherwise, it defaults to ami-87654321.

This dynamic configuration enables us to adapt our infrastructure provisioning based on region-specific requirements.

b. file() and fileexists(): These functions allow us to interact with files during provisioning. We can conditionally include or exclude files based on specific conditions, 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"
}
}

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.

4. Dynamic Conditionals with Data Sources: Exploring the Power of External Data

We can also look into data sources as it’s powerful tools that enable us to fetch information from external systems and use it within our infrastructure configuration.

By leveraging data sources within conditional expressions, we can create dynamic conditionals based on real-time data.

a. `aws_vpc` Data Source: Let’s 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…
}

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

b. External Data Sources: Terraform supports a variety of external data sources, such as APIs, databases, and files. These sources can provide real-time information that can be used within conditional expressions to drive infrastructure configuration.

data "external" "my_api" {
program = ["python", "api_script.py"]
}

resource "aws_instance" "example" {
count = data.external.my_api.result == "success" ? 1 : 0
# Other resource configurations…
}

For sample example, here the external data source invokes an external Python script `api_script.py` that fetches data from an API. We use the result from the API call in our conditional logic to determine whether to create the aws_instance resource. If the result is “success”, one instance is created; otherwise, it is skipped.

5. Best Practices for Conditional Logic in Terraform

As we explore some of the capabilities of conditional logic in Terraform, it’s essential to adopt best practices to ensure efficient and maintainable code.

Let’s look into some of the key guidelines and techniques to maximize the benefits of conditional expressions.

a. Keep It Concise: While Terraform’s conditional expressions offer flexibility, it’s crucial to strike a balance between readability and complexity. Aim to keep your conditionals concise and avoid overly convoluted 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 the 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.

b. Modularize Conditionals: As your infrastructure grows, modularizing conditionals becomes crucial to maintain a structured and scalable codebase. Break down complex conditionals into smaller reusable components, making your code more modular and easier to maintain.

module "example_instance" {
source = "./modules/instance"
instance_type = var.environment == "prod" ? "t2.large" : "t2.micro"
# Other module configurations…
}

Here we leverage a separate module for creating instances, encapsulating the conditional logic within the module. By modularizing conditionals, we promote code reuse, enhance readability, and facilitate future updates and modifications.

c. Leverage Terraform Functions: Terraform provides a rich set of functions that can greatly enhance your conditional expressions. When facing complex conditions, leverage functions like `coalesce()`, `contains()`, and `fileexists()` to handle various scenarios effectively.

resource "aws_instance" "example" {
ami = coalesce(var.custom_ami, data.aws_ami.latest.id, "ami-12345678")
instance_type = var.environment == "prod" ? "t2.large" : "t2.micro"
key_name = fileexists(var.ssh_key_path) ? var.ssh_key_name : null
# Other configurations…
#...
}

We utilize the coalesce() function to set the AMI based on multiple possibilities, handle optional variables, and provide a default value. We also use the fileexists() function to conditionally set the key_name based on the existence of an SSH key file.

d. Documentation and Comments: Clearly document the purpose and conditions of your conditional expressions to aid future developers and yourself. Add comments where necessary to explain complex conditions or provide context for decision-making.

resource "aws_instance" "example" {
# Use t2.micro for non-production environments, t2.large for production
ami = var.environment == "prod" ? "ami-12345678" : "ami-87654321"

# configurations…
# ...
}

Thanks for reading! I’d appreciate your support 👏 and engagement 🚙 in my stories :)

Don’t miss a beat — subscribe to my Medium newsletter to get my latest articles and content before anyone else!

--

--

Ink Insight 🧘🏼
ILLUMINATION

Discover the intersection of DevOps, InfoSec, and mindfulness with Ink Insight. Follow for valuable insights! ✍︎ 👨‍💻 🧘🏼