Terraform is an excellent tool for defining infrastructure as code to increase productivity and transparency. Terraform enables you to safely and predictably create, change, and improve production infrastructure.

As a declarative language, Terraform enforces simplicity by design giving you an accurate view of the infrastructure you’re deploying at the expense of allowing you to do more complicated task such as conditionals and loops which you’re accustomed to in a procedural language. The lack of conditionals makes it difficult to create reusable Terraform modules.

Luckily, there’re certain workarounds we can use to achieve our goal. We’ll achieve this using a few primitives namely - a meta-parameter called count plus a large number of interpolation functions provided by Terraform that allow you to do certain types of loops and if-statements.

Conditionals

An excellent example of where you might want to use conditionals is having the option of deploying AWS NAT Gateways or NAT Instances for the VPC created by TF VPC module. Here are the types of conditionals we’ll go over:

  • If-statements
  • If-else-statements

If-statements

We’ll startup with a basic scenario where our Terraform module creates an EC2 instance and an Elastic IP that’ll be attached to the created instance:

resource "aws_instance" "example" {
  ami = "${var.ami}"
  instance_type = "${var.instance_type}"
  tags {
    Name = "${var.service_name}"
  }
}
resource "aws_eip" "example" {
  instance = "${aws_instance.example.id}"
}

The module takes 3 variables as inputs:

variable "service_name" {
  description = "The name of the service"
}
variable "ami" {
  description = "The ID of an AMI for the service"
}
variable "instance_type" {
  description = "The type of EC2 Instance for the service"
}

We can then use this module to deploy two services, a Website and API:

module "website" {
  source = "/modules/services"
  service_name = "website"
  ami = "ami-abcd1234"
  instance_type = "t2.large"
}
module "api" {
  source = "/modules/services"
  service_name = "api"
  ami = "ami-efgh5678"
  instance_type = "m4.xlarge"
}

This works as expected except we only want the public IP attached to our Website instance and not the API as it won’t be public facing and will be consumed internally by the Website. To address this, let’s add another variable as an input for our module:

variable "create_eip" {
  description = "If set to true, create an EIP for the service"
}

We can accomplish our goal by making use of the count meta-parameter provided by Terraform. count controls the number of identical resources to create. By default, in Terraform a boolean true is converted to a 1 and a boolean false is converted to a 0. Let’s update the module to utilise count:

resource "aws_eip" "example" {
  count = "${var.create_eip}"
  instance = "${aws_instance.example.id}"
}

If var.create_ip is true, the module will create one EIP and if var.create_ip is false, the module will create zero EIPs:

module "website" {
  source = "/modules/services"
  service_name = "website"
  ami = "ami-abcd1234"
  instance_type = "t2.large"

  create_eip = true
}
module "api" {
  source = "/modules/services"
  service_name = "api"
  ami = "ami-efgh5678"
  instance_type = "m4.xlarge"

  create_eip = false
}

If-else-statements

Now that we are able to do an if-statement, let’s look at if-else-statements. Building on from our module, we will like to create an Elastic Load Balancer if no EIP is created as our API will be load balanced. Let’s update the module:

resource "aws_elb" "bar" {
  name               = "foobar-terraform-elb"
  availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"]

  count = "${1 - var.create_eip}"

  access_logs {
    bucket        = "foo"
    bucket_prefix = "bar"
    interval      = 60
  }

  listener {
    instance_port     = 8000
    instance_protocol = "http"
    lb_port           = 80
    lb_protocol       = "http"
  }

  listener {
    instance_port      = 8000
    instance_protocol  = "http"
    lb_port            = 443
    lb_protocol        = "https"
    ssl_certificate_id = "arn:aws:iam::123456789012:server-certificate/certName"
  }

  health_check {
    healthy_threshold   = 2
    unhealthy_threshold = 2
    timeout             = 3
    target              = "HTTP:8000/"
    interval            = 30
  }

  instances                   = ["${aws_instance.example.id}"]
  cross_zone_load_balancing   = true
  idle_timeout                = 400
  connection_draining         = true
  connection_draining_timeout = 400

  tags {
     Name = "foobar-terraform-elb"
  }
}

We added an ELB resource and are setting the count to 1 - var.create_eip which means if var.create_eip is true (1), count for the ELB resource will be false (1 - 1 = 0) meaning it won’t be created but if it is false (0), count for the ELB resource will be true (1 - 0 = 1) so our ELB get’s created.

Conclusion

Although Terraform is a declarative language, it includes a large number of tools that give the language a surprising amount of flexibility and expressive power. In a follow up post I’ll touch on more complicated if-statements and if-else-statements and also have a look at how to do loops with Terraform. Follow us on Twitter to be notified when it comes out.

New to Terraform? Our Terraform 101 post is the perfect starting point.

If you need help with Terraform, DevOps practices, or AWS at your company, feel free to reach out to us at AltoStack.

At AltoStack, our experts can maintain your DevOps platform and be responsible for day-to-day operational issues, allowing you to develop and ship your product without the need for internal DevOps hires.