Making a Terraform Module

November 03, 2018

I was chatting with an Actual DevOps Guy at a recent security meetup and we got to chatting about modules. Previously a typical module for me would be “gophish-server”, which is not a module at all, really. It’s a completed build. It felt wrong when I was doing it, but it worked so I left it alone.

Anyways, the way he was talking about his modules made a lot more sense. His modules were “compute” and “network”, which even sound like modules. I had a bit of downtime between gigs and tore apart my entire Terraform repo and rebuilt it with real modules. Some day I’ll release it, but for now I’m going to share the process I used to create a module.

We’ll use a simple HTTP redirector hosted in AWS as an example. All we need is a box with 22/tcp, 80/tcp, and 443/tcp open inbound.

Break It Up

Our first steap is to break up our completed build into logical groupings of functionality.

For VMs that need to have connectivity with the outside world, our groupings should look something like:

Inside => Out

Next we need to figure out which Terraform resources we need to provision a functioning module, and which options we’ll need in each. This is easily the hardest part and will require some trial an error until you get things right.

Start with the core component of your module and identify which options are required for your core component to function. For a compute resource, this will be the VM itself. For network, it’ll likely be a firewall rule.

With the exception of DigitalOcean, creating a firewall rule is going to be an undertaking. If you’re familiar with the platform, you’ll likely know where to start looking. Otherwise, you can review existing code to get an idea how everything fits together.

To create our redirector, we’ll need the following resources:

Like with Like

Our next task is to split up our resources into standalong units of functionality. We want each unit to be self-contained and not require us to pass a large number of variables between modules.

We’ll just put that in the oven for 90 minutes and continue with the module I baked just before the show started. Let’s see how they turned out!

Network

Our network module incorporates everything except the VM. Many of the above resources depend on each other to function, so it makes sense to keep them together.

Here’s what provisioning our network module looks like:

# Set provider
provider "aws" {}

# Provision Network
module "aws_network" {
  source = "../../modules/aws/network/inbound-80-443"
}

# Gather outputs
output "security_group_id" {
  description = "Security group id"
  value       = "${aws_security_group.default.id}"
}

output "subnet_id" {
  description = "ID of created subnet"
  value       = "${aws_subnet.default.id}"
}

I hardcoded my CIDR ranges into the module config, so don’t actually need to provide any information to my module during provisioning. I do, however, need the security group ID and the subnet ID as outputs.

Compute

Our AWS compute module requires just one resource: aws_instance. As you might expect, we don’t need to configure much here. Let’s take a look:

provider "aws" {}

# Provision compute
module "aws_compute" {
  source            = "../../modules/aws/compute/t2.micro-ubuntu-16.04"
  key_name          = "liam"
  security_group_id = ["${module.aws_network.security_group_id}"]
  subnet_id         = "${module.aws_network.subnet_id}"
}

# Gather outputs
output "public_ip" {
  value = "${module.aws_compute.public_ip}"
}

All we need to tell our VM module is which SSH key, subnet, and security group to associate with it. As an output, we’ll get the public IP of the virtual machine.

Wrap Up

This post was more focused on the how than the what. As such, I haven’t included any sample code. Each provider is different so you’ll need to work your way through their quirks to get things working the way you want. My hope is that this post has given you a bit of a leg up in that journey.

It’s also worth noting that there are pre-built modules available that do most of what I’ve just described here, although I’d suggest working through it on your own to get a feel for how it all fits together.

References