Terraform Conditionals
Brendan Thompson • 10 December 2021 • 6 min read
What is a conditional?#
Terraform, like many programming languages or Domain-Specific Languages (DSL), has the concept of Conditional Expressions, this uses the value of a boolean expression to select two values.
The syntax for a conditional is:
condition ? true : false
Some expression that must return a boolean result is used as the condition; if that expression
returns true
, then we will use the value provided on the left-hand side of the colon (:
) and the
right-hand side if false
.
When to use a conditional?#
Conditionals are a pretty valuable part of the Terraform DSL. It allows our code to be selective when it runs. Here are some common scenarios when conditionals should be used:
- As a feature flag to turn on or off a Resource or Data Source
- Enable/Disable a feature inside of a Resource with a Dynamic Block
- Enable/Disable a field or property on a Resource or Data Source
How to use a conditional?#
As a feature flag#
In this section, we will go through a few ways that conditionals can be used as feature flags, there
are two ways to achieve this; count
and for_each
.
In the below count
example, we will set the instance count of the resource to be 1
if
our variable enabled
is true
and set it to 0
if it is false
. This gives you a straightforward way
to create one or more resource instances if your condition is true
. The thing to remember
here is that you will have to deal with your resource as an array from here on out.
terraform {
required_providers {
scratch = {
source = "BrendanThompson/scratch"
version = "0.1.0"
}
}
}
variable "enabled" {
type = bool
description = <<EOT
(Optional) Enable feature X.
Default: true
EOT
default = true
}
resource "scratch_string" "this" {
count = (var.enabled ? 1 : 0)
in = "Enabled"
}
We will use the same variable as above (enabled
) for our following scenario. We are going to create
one or more instances of an object where we will be setting some properties, we will use a locals
block.
When the condition is true
, our code will iterate over a map defined in our locals
, although this
could be defined anywhere, including the output of another resource or data source. This option
allows us a lot more flexibility as we can change properties on each instance of our resource and
we could even include conditionals inside the resource to further let us define if a property
is required or not!
variable "enabled" {
...
}
locals {
networks = {
primary = {
name = "primary-network"
address_space = "10.0.0.0/24"
}
secondary = {
name = "secondary-network"
address_space = "10.0.1.0/24"
}
}
}
resource "scratch_string" "complex" {
for_each = (var.enabled ? local.networks : {})
in = format("Name: %s, Address Space: %s", each.value.name, each.value.address_space)
}
Dynamic Blocks#
In this instance, we will be dynamically set the properties for the SSH Key on an Azure Virtual Machine instance. We will use some of the above for conditionally enabling or disabling properties on the resources as well!
provider "azurerm" {
features {}
}
variable "ssh_enabled" {
type = bool
description = <<EOT
(Optional) Enable feature SSH key authentication.
Default: true
EOT
default = true
}
locals {
ssh_keys = {
bltadmin = file("/Users/brendanthompson/.ssh/bltadmin.pub")
}
}
resource "azurerm_resource_group" "this" {
name = "rg-aus-blt"
location = "Australia Southeast"
}
resource "random_password" "this" {
count = (var.ssh_enabled ? 0 : 1)
length = 16
}
resource "azurerm_virtual_network" "this" {
name = "vn-aus-blt"
address_space = ["10.0.0.0/24"]
location = azurerm_resource_group.this.location
resource_group_name = azurerm_resource_group.this.name
}
resource "azurerm_subnet" "this" {
name = "sn-iaas"
resource_group_name = azurerm_resource_group.this.name
virtual_network_name = azurerm_virtual_network.this.name
address_prefixes = ["10.0.0.0/24"]
}
resource "azurerm_network_interface" "this" {
name = "nic-blt"
location = azurerm_resource_group.this.location
resource_group_name = azurerm_resource_group.this.name
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.this.id
private_ip_address_allocation = "Dynamic"
}
}
resource "azurerm_linux_virtual_machine" "this" {
name = "vmblt"
resource_group_name = azurerm_resource_group.this.name
location = azurerm_resource_group.this.location
size = "Standard_F2"
admin_username = "bltadmin"
admin_password = (var.ssh_enabled ? null : random_password.this[0].result)
disable_password_authentication = (var.ssh_enabled ? true : false)
network_interface_ids = [
azurerm_network_interface.this.id,
]
dynamic "admin_ssh_key" {
for_each = (var.ssh_enabled ? local.ssh_keys : {})
content {
username = admin_ssh_key.key
public_key = admin_ssh_key.value
}
}
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "16.04-LTS"
version = "latest"
}
}
The following excerpt demonstrates how we use dynamic
blocks:
resource "azurerm_linux_virtual_machine" "this" {
...
dynamic "admin_ssh_key" {
for_each = (var.ssh_enabled ? local.ssh_keys : {})
content {
username = admin_ssh_key.key
public_key = admin_ssh_key.value
}
}
...
}
If the variable ssh_enabled
is set to true
, we are going to create an instance of the admin_ssh_key
block and consume some properties from the locals
block we have defined. If the condition is false
then we will pass in an empty map; this will mean that the entire block will not get set! Doing this
is even more helpful when creating multiple instances of a block. You want to pass in the
parameters via a Terraform variable or ingest out of config.
Resource Fields#
The last scenario I would like to go through in this post is how to enable or disable fields on a resource using conditionals. There are situations where you might do this either on its own or in conjunction with one of the other conditional scenarios described above. I will use the above Virtual Machine example to talk through this; however, I will pull out the relevant pieces for the sake of clarity.
In the below excerpt, it can be seen that we are applying some conditional logic on the admin_password
and disable_password_authentication
properties. When var.ssh_enabled
is false
, we still need
to authenticate to our instance; the only other way Azure allows you to do this is via
password authentication. Thus, when it is disabled, we are enabling the creation of the random_password
resource (see the above), and setting its result to the admin_password
property, we must
ensure that disable_password_authentication
is set to true
.
resource "azurerm_linux_virtual_machine" "this" {
...
admin_password = (var.ssh_enabled ? null : random_password.this[0].result)
disable_password_authentication = (var.ssh_enabled ? true : false)
...
}
Closing Out#
Hopefully, this post has given you some insight into how to use conditionals in Terraform and some typical scenarios where they could be used.