loading...
Cover image for What I've Learned Learning Terraform: Part 7

What I've Learned Learning Terraform: Part 7

gzg profile image Ekin Öcalan ・5 min read

We continue to learn more about the basic Terraform features.

Conditionals

We've already used some sort of conditionals in the previous parts of this series. However, it makes sense to categorize the usage of conditionals in Terraform. Just like with the loops, we use a different approach to use conditionals since Terraform is a declarative language.

Conditional expressions

Although we don't have a procedural way of implementing if-else statements, Terraform supports the ternary syntax. Let's say you have two environments: Production and staging. You'd like to have a production environment with more computing power, but you also want to cut costs on staging and have less configuration there. How do you declare your condition?

Let's say you create a variable to determine whether the environment is production or not:

variable "is_production" {
  description = "If the environment is production or not"
  type        = bool
  default     = false
}

output "is_staging" {
  value = ! var.is_production
}

The code above is only a demonstration of a pair of helpers. One of them is a variable, and the other one is an output. To keep the example simple, we are not going to populate the variable programmatically, but make it is_production=false.

Now, let's say you are going to create one server for each environment. The production server will have a more powerful configuration.

resource "digitalocean_droplet" "server" {
  image  = "ubuntu-20-04-x64"
  name   = "example-droplet"
  region = "ams3"
  size   = var.is_production ? "s-8vcpu-32gb" : "s-1vcpu-1gb"
}

Notice how we used the ternary syntax to declare the size of the droplet. Now if you run terraform plan, you'll see that Terraform plans to create a droplet with the size of s-1vcpu-1gb because, by default, it's not the production environment:

Terraform will perform the following actions:

  # digitalocean_droplet.server will be created
  + resource "digitalocean_droplet" "server" {
      + backups              = false
      + created_at           = (known after apply)
      + disk                 = (known after apply)
      + id                   = (known after apply)
      + image                = "ubuntu-20-04-x64"
      + ipv4_address         = (known after apply)
      + ipv4_address_private = (known after apply)
      + ipv6                 = false
      + ipv6_address         = (known after apply)
      + ipv6_address_private = (known after apply)
      + locked               = (known after apply)
      + memory               = (known after apply)
      + monitoring           = false
      + name                 = "example-droplet"
      + price_hourly         = (known after apply)
      + price_monthly        = (known after apply)
      + private_networking   = (known after apply)
      + region               = "ams3"
      + resize_disk          = true
      + size                 = "s-1vcpu-1gb"
      + status               = (known after apply)
      + urn                  = (known after apply)
      + vcpus                = (known after apply)
      + volume_ids           = (known after apply)
      + vpc_uuid             = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Creating resources conditionally

Resources are the backbone of IaC. We declare resources, and Terraform finds a way to create the defined resources. It's easy to make a resource when you know that it's necessary. But what if you don't know if you're going to need that resource?

Moving on from the previous example of production and staging environments, let's move one step up and create a resource conditionally. Add this resource declaration to the file:

resource "digitalocean_domain" "domain" {
  count      = var.is_production ? 1 : 0
  name       = "example-domain.com"
  ip_address = digitalocean_droplet.server.ipv4_address
}

Domain declaration requires name and the IP address attributes. count parameter is the one that allows us to create this resource conditionally. I should mention that this is a hack to simulate a conditional statement on a resource-level. If the environment is production, Terraform will create one domain with this configuration. If it's staging, then it's not going to create at all.

If you run terraform plan, you'll see that nothing changes in the plan because our environment is staging by default:

Terraform will perform the following actions:

  # digitalocean_droplet.server will be created
  + resource "digitalocean_droplet" "server" {
      + backups              = false
      + created_at           = (known after apply)
      + disk                 = (known after apply)
      + id                   = (known after apply)
      + image                = "ubuntu-20-04-x64"
      + ipv4_address         = (known after apply)
      + ipv4_address_private = (known after apply)
      + ipv6                 = false
      + ipv6_address         = (known after apply)
      + ipv6_address_private = (known after apply)
      + locked               = (known after apply)
      + memory               = (known after apply)
      + monitoring           = false
      + name                 = "example-droplet"
      + price_hourly         = (known after apply)
      + price_monthly        = (known after apply)
      + private_networking   = (known after apply)
      + region               = "ams3"
      + resize_disk          = true
      + size                 = "s-1vcpu-1gb"
      + status               = (known after apply)
      + urn                  = (known after apply)
      + vcpus                = (known after apply)
      + volume_ids           = (known after apply)
      + vpc_uuid             = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Now let's go ahead and change the default value for is_production variable to true:

variable "is_production" {
  description = "If the environment is production or not"
  type        = bool
  default     = true
}

If you run the terraform plan command now, you'll see two additional changes on the plan. One for the variable change, and the other for the domain resource creation:

Terraform will perform the following actions:

  # digitalocean_domain.domain[0] will be created
  + resource "digitalocean_domain" "domain" {
      + id         = (known after apply)
      + ip_address = (known after apply)
      + name       = "example-domain.com"
      + urn        = (known after apply)
    }

  # digitalocean_droplet.server will be created
  + resource "digitalocean_droplet" "server" {
      + backups              = false
      + created_at           = (known after apply)
      + disk                 = (known after apply)
      + id                   = (known after apply)
      + image                = "ubuntu-20-04-x64"
      + ipv4_address         = (known after apply)
      + ipv4_address_private = (known after apply)
      + ipv6                 = false
      + ipv6_address         = (known after apply)
      + ipv6_address_private = (known after apply)
      + locked               = (known after apply)
      + memory               = (known after apply)
      + monitoring           = false
      + name                 = "example-droplet"
      + price_hourly         = (known after apply)
      + price_monthly        = (known after apply)
      + private_networking   = (known after apply)
      + region               = "ams3"
      + resize_disk          = true
      + size                 = "s-8vcpu-32gb"
      + status               = (known after apply)
      + urn                  = (known after apply)
      + vcpus                = (known after apply)
      + volume_ids           = (known after apply)
      + vpc_uuid             = (known after apply)
    }

Plan: 2 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  ~ is_staging = true -> false

There is a drawback with this hack, though. As you can see from the plan, the domain resource will be created as part of a list: digitalocean_domain.domain[0]. That's because we use the count parameter within the resource.

By the way, starting from Terraform 0.13, you can do this hack with modules as well.

Setting values conditionally (Interpolation)

There is one more feature for conditionals on Terraform, which is the interpolation.

In computer programming, string interpolation (or variable interpolation, variable substitution, or variable expansion) is the process of evaluating a string literal containing one or more placeholders, yielding a result in which the placeholders are replaced with their corresponding values. 1

Interpolation in Terraform is perhaps the closest thing to a conditional in other programming languages due to its direct if-else statement usage.

Let's demonstrate this by setting the droplet name based on the environment:

resource "digitalocean_droplet" "server" {
  image  = "ubuntu-20-04-x64"
  name   = "%{if var.is_production}${"production-droplet"}%{else}${"staging-droplet"}%{endif}"
  region = "ams3"
  size   = var.is_production ? "s-8vcpu-32gb" : "s-1vcpu-1gb"
}

Notice how we used if-else statements inside the name attribute. You can even use variables here to define your interpolation with placeholders. Running the terraform plan command will give us the plan below:

  # digitalocean_droplet.server will be created
  + resource "digitalocean_droplet" "server" {
      + backups              = false
      + created_at           = (known after apply)
      + disk                 = (known after apply)
      + id                   = (known after apply)
      + image                = "ubuntu-20-04-x64"
      + ipv4_address         = (known after apply)
      + ipv4_address_private = (known after apply)
      + ipv6                 = false
      + ipv6_address         = (known after apply)
      + ipv6_address_private = (known after apply)
      + locked               = (known after apply)
      + memory               = (known after apply)
      + monitoring           = false
      + name                 = "production-droplet"
      + price_hourly         = (known after apply)
      + price_monthly        = (known after apply)
      + private_networking   = (known after apply)
      + region               = "ams3"
      + resize_disk          = true
      + size                 = "s-8vcpu-32gb"
      + status               = (known after apply)
      + urn                  = (known after apply)
      + vcpus                = (known after apply)
      + volume_ids           = (known after apply)
      + vpc_uuid             = (known after apply)
    }

You see that the name is production-droplet since we set our environment to production in the last step. Now you have a conditional value setting.

Cover photo by Mario Dobelmann


Part 6...................................................................................................................

Posted on by:

gzg profile

Ekin Öcalan

@gzg

I'm writing on the path to becoming a full-stack.

Discussion

pic
Editor guide
 

Thank you for sharing. I'm starting with terraform as well and I'm learning it along the way. One question: do you know how to reuse the remote exec among resources? I have many resources sharing some part of the remote exec, would love to know a better way to reuse instead of copy and paste. Also, when you setup certain things you may have to out password in the exec command which is unsafe as I understand it. Do you find any solution for that?