Dave Konopka

Terraform conditionals. Sort of.

Terraform doesn’t have support for conditionals on resources. There’s nothing like Ansible’s when statement to conditionally create Terraform resources based on a boolean variable value. At least not yet.

I value the declarative simplicity that Terraform configuration enforces by design. However, the lack of conditionals makes it difficult to create reusable, DRY Terraform modules. Luckily, there is a workaround. Terraform resources offer a count parameter that can be used as an indirect conditional. Before I give a usage example, let me explain my use case.

Why do I want conditionals?

At ReactiveOps where I work we created a Terraform module to manage client Amazon VPCs. We have a standard pattern of VPC architecture that includes public and private subnets. Our TF VPC module sets up all the subnets, route tables, and NAT EC2 appliance instances needed for that pattern. This helps us setup clients rapidly on AWS with a best-practices VPC design.

Amazon recently released NAT Gateways as an alternative to NAT EC2 instances. NAT Gateways offer availability and network throughput benefits over EC2 instances. I want to add an option to our TF module to launch a VPC with either NAT Gateways or NAT EC2 instances. We have existing customers using NAT EC2 instances, so we need to support both paths.

Our VPC TF module creates a few resources specific to NAT EC2 instances:

  • An EC2 instance per availability zone.
  • A security group with rules for the EC2 instances.
  • Route table entries pointing egress traffic at the EC2 instances.

Using NAT Gateways we won’t need any of the above listed resources. We will still need the VPC, subnets, route tables, new route table entries, and the NAT Gateways.

Without conditionals one option would be to isolate the NAT portions of the VPC module to two separate modules. Then we could include the VPC module and the appropriate NAT module for a given project. In this case though the VPC module is not very usable on its own without one of the two NAT modules.

Another option would be to create two separate VPC modules duplicating all the shared resources with separate resources alongside two different NAT approaches. This duplication of VPC resources would be unwieldy to support.

Using count as a conditional

Enter the count parameter. This parameter is typically used to create more than one instance of a resource. If the value is set to 0 though none will be created.

With this in mind, we can add two variables to our module’s variables.tf file:


variable "nat_instance_enabled" {
    description = "set to 1 to create nat ec2 instances for private subnets"
    default = 0
}

variable "nat_gateway_enabled" {
    description = "set to 1 to create nat gateway instances for private subnets"
    default = 0
}

Resources that are specific to one approach can then set a count value using one of the flag variables. When either of these variables is set to 0 the related resources will not be created. When either is set to 1 the related resources will be created.

Below is a security group only needed by the EC2 instance approach:


resource "aws_security_group" "nat" {
 count = "${var.nat_instance_enabled}"
 ...
}

Some resources in our VPC module already set a count to create multiple instances. For these, we can use the flag variables as multipliers. Anything multiplied by zero is zero which means the resources will or will not be created depending on the multiplier.

Below are the actual instances required by the EC2 instance approach:


resource "aws_instance" "nat" {
  count = "${var.az_count * var.nat_instance_enabled}"
  ...
}

When the module is used we can flip either flag to 1 or 0 to pick between the two NAT approaches using a single Terraform module.


# variables.tf
...
variable "nat_instance_enabled" {
  default = 0
}

variable "nat_gateway_enabled" {
  default = 0
}
...

# terraform.tfvars
...
nat_instance_enabled = 0
nat_gateway_enabled = 1
...

# main.tf
module "vpc" {
  ...
  nat_instance_enabled = "${var.nat_instance_enabled}"
  nat_gateway_enabled = "${var.nat_gateway_enabled}"
  ...
}

Caveats

One caveat here is Terraform doesn’t offer variable validation. So you have to be careful not to set nat_instance_enabled to 100 or you would create 100’s of resources. A terraform plan run should make this kind of mistake obvious.

This is admittedly a simplistic example of a conditional. It doesn’t cover conditionally setting parameters or other more complex scenarios based on variable value ranges. The Support use cases with conditional logic thread up on the Terraform repo covers a few of the more complicated use cases. I’m hopeful that thread ends up forming the basis of a kick-ass Terraform conditional feature.