Explicit workspaces for Terraform

NOTE 2019-06-26: The original way this check worked with Terraform 11 no longer works with Terraform 12, as v12 checks that attribute names are valid. The code has been updated accordingly. Instead of a null resource, we now use a local_file data source with Terraform 12.

I’ve recently begun working with Terraform at work to move our infrastructure definitions to code. Like a lot of companies, we have some legacy applications that either have never been re-installed, or have been re-installed using a very long and usually outdated README or other document. Using a tool like Terraform and moving infrastructure into code makes re-installing a system much easier.

TL;DR: How do I make it work?


Terraform’s workspaces are designed to keep different environments (dev/production) separate. Mostly workspaces seem to work fine; For example, it’s quite easy to automatically select the correct variables based on the current workspace.

However, workspaces don’t keep the environments quite as separate as I’d like. Once you select a workspace, Terraform won’t go out of its way to remind you about which workspace you’re on. It’s only a matter of time before this happens:

Let's do a deployment into production:

$ git checkout production
$ terraform workspace select prod
$ terraform apply

Yay, it all went better than expected! Time to go home for the weekend.

Monday. Let’s start working on the new features.

$ git checkout master
$ hack hack hack
$ git add .
$ git commit -m "Bad developer, pushing to master"
$ git push
$ terraform apply

Oops. Terraform remembers the last workspace it was on, and now the new experimental features went to production.

As far as I can tell the only way to avoid this is to do “terraform workspace show” religiously before applying, but that’s not exactly reliable.

Explicit variable files

Another option would be to skip workspaces altogether, and use explicit variable files for each environment:

my_env         = "dev"
my_server_name = "Development server"

And then when applying:

$ terraform apply -var-file="dev.tfvars"

This works, but has the downside of having all the environments in the same state, which doesn’t feel right either.

Workspaces and explicit variable files

The next option is to use both workspaces and variable files:

$ terraform workspace select prod
$ terraform apply -var-file="dev.tfvars"

Oops. Here at least you might notice that the apply command is about to delete and re-create a whole bunch of resources. I’d rather not let it go that far.

Workspaces and explicit variable files, with a check to make sure they match

The obvious next step is to add a check that makes sure the current workspace and the given variable file match. Unfortunately, Terraform doesn’t allow us to explicitly raise an error. However, there is a workaround using null resources!

So let’s put all of this together:

provider "aws" {
  region     = "eu-west-1"

variable "my_env" {}
variable "my_server_name" {}

# Original version for Terraform 11
#resource "null_resource" "is_environment_name_correct" {
#  count = "${var.my_env == terraform.workspace ? 0 : 1}"
#  "ERROR: Workspace does not match given environment name!" = true

# New version for Terraform 12
data "local_file" "workspace_check" {
  count = "${var.env == terraform.workspace ? 0 : 1}"
  filename = "ERROR: Workspace does not match given environment name!"

resource "aws_instance" "my_test" {
  # Amazon Linux AMI
  ami           = "ami-9cbe9be5"
  instance_type = "t2.micro"

  tags {
    Name = "${var.my_server_name}"
my_env         = "dev"
my_server_name = "Development server"
my_env         = "prod"
my_server_name = "Production server"

Now we need to explicitly state our environment when applying, and the script automatically fails if the given environment doesn’t match the current workspace:

$ terraform workspace select prod
Switched to workspace "prod".
$ terraform plan -var-file="dev.tfvars"

Error: null_resource.is_environment_name_correct: : invalid or unknown key: ERROR: Workspace does not match given environment name!

$ terraform workspace select dev
Switched to workspace "dev".
$ terraform plan -var-file="dev.tfvars"
Refreshing Terraform state in-memory prior to plan...

Thanks to Borys Borysenko, Marco Molteni, Martin Atkins, Tim Gurney and Jamie Nelson for the Github comments and blog posts that I’ve used as the source material for this blog post.

Leave a comment

Your email address will not be published. Required fields are marked *