Good Practices: Terraform

Photo by Max Duzij on Unsplash

Terraform is one awesome way to write your infrastructure as a code. This blog is aimed for people starting out with terraform and are about to write a module. These tricks will be helpful for writing a good module.

You may look at terraform-gcp-openwisp & terraform-kubernetes-openwisp as two example modules that follow these guidelines and are published on terraform registry.

This blog has been written based on syntax from terraform 0.12.20. It contains three sections:
- Dynamic resources & blocks
- Useful Features
- Module Folder Structure

Dynamic resources & blocks:

Suppose we have these local variables, let’s see some loops to make our terraform code DRY.

locals {
example_var = [
{ "A" : "Value-1", "B" : 1 },
{ "A" : "Value-2", "B" : 2 }
]
example_var_check = 5
}

1. Dynamic Resources

The count option enables you to add a resource dynamically zero or more times.

resource "example_resource" "example_name" {
count = length(local.example_var)
A = local.example_var[count.index].A
B = local.example_var[count.index].B
}

2. Dynamic Blocks

When you want to dynamically add an option of a resource, you can use dynamic blocks to add zero or more of the blocks for the option, in the example below, two blocks of example_option_name are added.

resource "example_resource_block" "example_name" {
dynamic "example_option_name" {
for_each = [for exp_value in local.example_var : {
A = exp_value.A
B = exp_value.B
}]
content {
A = example_option_name.value.A
B = example_option_name.value.B
}
}
}

3. Resource's If-statement

Terraform doesn’t have an “if” block, but there is an easy work around using count & a ternary operator.

resource "example_resource" "example_name" {
count = local.example_var_check == 5 ? 1 : 0
example_item = local.example_var_check
}

4. Resource block’s If-statement

Similar to a resource, there is a work around for block inside a resource as well. In the example below, A is only set when it’s value is not equal to one and B is always set.

resource "example_resource_block" "example_name" {
count = length(local.example_var)
dynamic "example_option_name" {
for_each = local.example_var[count.index].A != 1 ? [1] : []
content {
A = local.example_var[count.index].A
}
}
B = local.example_var[count.index].B
}

Useful Features:

1. Objects

You can group multiple values together in an object to create complex datatypes, like struct in C++ . Example the variable below contains an object and a nested object inside a variable:

variable "variable_1" {
required_value = object({
nested_object = object({
example_nested_string = string
})
example_string = string
})
}

You can access the nested object as: variable_1.required_value.nested_object.example_nested_string

2. Variable Descriptions

Each variable and output can have a description value, this value is displayed on the registry and on the command line when value is not provided while running the terraform script, example:

variable "example_var" {
type = string
description = "This should explain the value that the user should input and might include examples."
}

Module Folder Structure

A good terraform repository would contain at least the following files with the structure:

Folder structure of terraform repository

1. Module Name

It’s recommended to name your module as terraform-<provider>-<module>. This format clearly defines your provider’s name and the infrastructure that would be deployed by this module. Example, if I were deploying OpenWISP on kubernetes provider using terraform, my module name would be “terraform-kubernetes-openwisp”.

2. Module Base

Each module should contain at least:

- main.tf: In many programming languages, main() is where everything starts. Same here, main.tf is the file where you define everything to get started.
- variables.tf: All the module input variables are defined in this file.
- outputs.tf: All the module output variables are defined in this file.

3. Examples

You should include the examples for usage of the module, a standalone example is a must but if the module can be used with another terraform module, examples for using with that module may be included as well. Example, in the dummy module in the image, the examples are included for using this module as standalone and with a module called google-cloud.

4. Documentation

Documentation for inputs and outputs is important and as mentioned before, the variables & outputs can have descriptions that are displayed when terraform is run or on the terraform registry, however, there is a problem with the registry. The descriptions shown on the website doesn’t support multi-lines, which means if you are using multi-line descriptions, they are all squished to one line which doesn’t look good, so instead I like to have a separate documentation folder to store the variable description markdown files and in the description of the variables, I add the link to this file.

Hope You Learned something useful, Good Luck with your project!

Stoic. Existentialist. Optimistically Nihilist. Snowdenist. Friendly. Confident. Perfectionist. Creative. Playful. Programmer. Philosopher.