Leveling up Your Terraform Deploys with Github Codespaces

Recently Github made a beta publicly available for Codespaces. This will provide a fully functional cloud development environment which developers can access via a browser or using an extension on Visual Studio Code. It can be configured to provide all of a project's dependencies as soon as a developer starts up the environment. No more "this only works on my machine" because now everyone's machine is the same.

There are a lot of obvious benefits to using this sort of configuration when developing with a team of people. As a devops person, one possibility that got me excited was how this could be used to streamline deploys with Terraform.

A Quick Preface

Before we get into specifics, let me just preface by saying that I don't think you should manage everything Terraform through a Codespace. Most of the time you actually shouldn't need it. Generally you want your deployments to be completely automated. That's best handled with a pipeline tool such as Github Actions, Jenkins, Circle CI, etc. Occasionally, you'll run into situations where things go a little less smoothly, and that's where Codespaces will fit in. Mainly, we can avoid some unwanted errors for those times when we have to use Terraform outside of our pipeline.

Our Main Fix

I really like Terraform and I think it's a fantastic infrastructure-as-code (IaC) tool, however like most things it's not perfect. Sometimes after you run terraform plan things don't go according to plan 😬. Excusing the horrible dad joke, here's an error I ran into the other day when updating an nlb target group in my AWS environment:

Error: Error creating LB Target Group: DuplicateTargetGroupName: A target group with the same name 'example_tg' exists, but with different settings
	status code: 400, request id: <request-id>

So what exactly happened here? I had hoped to update the health check endpoint that the target group was using. This required the target group be completely replaced. Before deleting the old target group, Terraform attempted to create the new one. Terraform threw this error because target group names must be unique to the AWS account, and it tried to create a second with the name example-tg. Here's an issue on Github with some more details. The easiest solution is to delete the old target group and run our deployment again.

So why would you want to leverage Codespaces for something simple like deleting an AWS resource? There's actually a couple of reasons. Let me first mention that when using Infrastructure as Code, you should live and die by it. By that I mean since Terraform was used to provision all of the AWS resources on our project, it should be the only thing that makes changes. This is important because it avoids confusion about what is currently up in your environment. If you need to know something, you can refer to your codebase as a source of truth.

So to delete the target group, we should run the command:

terraform destroy -target aws_lb_target_group.example_tg

When running Terraform commands, versioning really matters. You already know that if you've ever seen this error message or something close:

Error: Error loading state: state snapshot was created by Terraform v0.12.26, which is newer than current v0.12.20; upgrade to Terraform v0.12.26 or greater to work with this state

Basically, once you run an apply using a specific version of the Terraform cli, from that point on you can only use that version or something newer.

So let's see how we could run into this scenario when someone innocently tries to delete their target group. Say that our devops team has agreed to use v0.12.20 and this is what's set up in the deployment pipelines. Meanwhile our developer unknowingly has v0.12.26 installed locally. He or she runs the targeted destroy command, and just like that the entire project needs to upgraded to v0.12.26. You don't need all that much expertise to know that when you upgrade something it's better to know about it 🙂.

Now let's say that our team is using Github Codespaces. With this, we should never have to worry about accidental forced updates. When the developer needs to delete a target group, he or she will first log into his Codespace. Our Codespace environment has v0.12.20 pre-installed (I show how to do that below). When he/she, or any other developer needs to run a targeted command, we've guaranteed the proper Terraform version is installed.  When we're ready to intentionally upgrade our terraform version, we just need to update our Codespace container and everyone has the correct version.

A Few Added Benefits

Avoiding unwanted upgrades is probably the biggest benefit here, however there's a few other's worth mentioning.

Always On Master

When running Terraform commands locally, you sometimes run into puzzling behaviors only to realize 10 minutes later that you forgot to pull down the latest code. With Codespace, we can always track the HEAD of our master branch.

Modules Always Correct

Codespaces allow you to specify a parameter postStartCommand. With this you can have your container run a shell command right after it starts up. If we set the value of postStartCommand to terraform init this will pull down the latest versions of your terraform modules. Since it runs every time you start up, you never have to wonder if you need to re-init.

Automatic Provider Updates

Most supported Terraform providers regularly receive updates. For example, here's the change log of the AWS provider. Ideally, you want to stick to the latest provider version so you're always getting the best. Sometimes you may not see an error caused by a provider update until it's time to deploy. Your pipeline will most likely be configured to pull the latest provider version each time it runs. It's easy to forget to do that locally. Once again, with our  postStartCommand set to terraform init we don't have to worry about this. Every time we re-initialize the environment we get the latest provider version.

Setup

Codespaces has a number of pre-built environments you can use. You can see them on this repo. Unfortunately, there's not one for Terraform and AWS yet. Since AWS is Rocket's specialty, I created my own. If you want one similar, I figured I'd share mine and save you some time:


FROM mcr.microsoft.com/vscode/devcontainers/base:buster

RUN apt-get update && \
        apt-get install -y python

RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \
        unzip awscliv2.zip && \
        sudo ./aws/install

RUN wget "https://releases.hashicorp.com/terraform/0.12.20/terraform_0.12.20_linux_amd64.zip" && \
        unzip terraform_0.12.20_linux_amd64.zip && \
        mv terraform /usr/local/bin/
        

Nothing fancy here. I just start with the Codespace base container, then install the AWS cli and my desired terraform version. If you want a different version of Terraform you can just change the url. Also, for the .devcontainer.json this is all you need.


{
    "build": {"dockerfile": "./docker/Dockerfile"},
    "postStartCommand": ["terraform", "init"]
}

Conclusion

Although most of the time you won't need your Github Codespace with Terraform, there are a number of cases where it can save you from getting yourself into trouble. When it's fully out of beta, I do intend to use it in my regular development workflow. In the meantime, you can put yourself on the list for early access here. Also, if you need to know more about how it, here's the documentation. If you're looking to level up your development workflow's with more methods like this, please reach out and we'd be more than happy to help!