When programming with Terraform, I often find myself writing the same code again and again. In order to maintain the DRY principal in Terraform configuration, modules are used to encapsulate logic and design a layer of abstraction for infrastructure code. This article explains how I created Terraform modules that are reused throughout my AWS Infrastructure as Code.
DRY stands for "Do not Repeat Yourself." It's the design philosophy that similar code shouldn't exist in multiple locations. Instead, repeated code segments should be combined into a single component or function.
As I write Terraform code, I notice there are certain AWS resource configurations repeated across my repositories. For example, my AWS infrastructure has multiple VPCs which are all configured in a similar manner. Eventually I got unhappy with all this duplicate code. I started creating reusable modules that are used across all my Terraform repositories.
My current AWS architecture is split across multiple git repositories. I have a single repository with global infrastructure such as VPCs and multiple other repositories with application specific infrastructure such as EC2 instances. Additionally, I have one final repository containing reusable modules.
All my repositories are able to use Terraform modules defined in terraform-modules. In fact, anyone can use these modules because the repository is public.
Now that I've explained my Terraform modules from the repository standpoint, let's look at the code.
Terraform defines a module as a series of
.tf files in a directory1. Therefore, anytime you write Terraform code you are creating one or more modules. A common approach is to have three Terraform files in a module. The first file contains resources and data blocks, the second file contains input variables, and the final file contains output variables2. For example, a VPC module would have the following folder structure:
This file structure isn't mandatory. Instead, it's a common standard used for grouping related Terraform code blocks. In practice Terraform simply loads all
.tf files in a directory at once for execution3.
With this structure in mind, let's explore one of the reusable modules I've built so far:
I currently have two reusable modules -
vpc. The rest of this article explores the
security-group module since it has fewer moving parts. You can check out the
vpc module on GitHub.
security-group module, the var.tf file contains input variables. These variables are like function arguments for the module.
enabled is used to enable or disable the creation of the security group. Usually the
count variable is used for this purpose in Terraform resources, however it isn't exposed to modules. Using the
enabled variable is a workaround for this limitation.
Variables under the “Naming Variables” header are used to name the AWS resources and their tags. Variables under the “aws_security_group Variables” header are used to configure the
aws_security_group_rule resources. Both are defined in main.tf. It's important to note that these resources use the new Terraform 0.12 and HCL 2 syntax.
This code creates an AWS security group and its corresponding rules. Rules are created for each item in the
var.sg_rules list. This module returns a single value - the ID of the security group.
This is the complete complete configuration of the
security-group module. To use this module, a
module code block must be created.
module exposes a variable
source which helps Terraform locate the module. Since my module lives in a GitHub repository, its accessible using a GitHub source type5.
The module is located in the security-group subdirectory. I tag my repository whenever I alter the modules. In the example above, I'm pulling the module from the v0.1.7 tag. This is helpful because I don't want a potentially breaking change to impact existing codebases. You can also omit the tag reference, in which case the latest commit is pulled.
The rest of the
module code block assigns values to the input variables defined in var.tf. The following code snippet is an example
Creating generic Terraform modules promotes code reuse and cuts down on bugs that commonly plague developers who copy and paste their code. I have a much better understanding of Terraform and HCL syntax because I spent time designing reusable modules. All the code from this article is available on GitHub.