3 min read, 500 words

First real adventures with Terraform

I have been meaning to try terraform for months now, but have always been held back by other obligations on time, energy, and infrastructure needs. After letting the tabs languish for months, finally, a project came my way that was begging for a full terraform deploy. All the attributes made this a perfect test bed: Dedicated VPCs, completely untied to any existing infrastructure, generous time line, etc.

Things I encountered while implementing my project:

Most of my issues came from my desire to modularize as much as possible, to be reused across deployment tiers and products. This lead into me writing states that had different requirements for different modules, like some projects having EIPs that they have to use for white-listing, existing resources, etc. I wanted the module to be able support handling either having the string of the existing resource passed to it, or generating a new resource when the calling project didn’t particularly care.

  1. Empty strings do not evaluate to either a boolean nor an integer.
    variable create_subnet { default = "" }
    resource "aws_subnet" "conditional_test" {
        count = "${var.create_subnet}"
        ...
    }
    

This will just error out with a string to integer (strconv.ParseInt) conversion failure. variable create_subnet { default = "" } resource "aws_subnet" "conditional_test" { count = "${var.create_subnet} ? 0 : 1" ... } This will error out with a string to boolean (strconv.ParseBool) conversion error.

  1. When attempting to do a conditional reference on a block like the above, using a coalesce to get the first non-empty string, it will error when evaluating ${aws_subnet.conditional_test.id}, as it obviously doesn’t exist. You can get around this using a join() statement with an empty string to the resource covered by the conditional count like so.

    variable nat_eip { default = "" }
    variable subnet_id { }
    variable
    resource "aws_eip" "nat" {
      count = "${replace(replace(var.nat_eip,"/.+/","0"),"/^$/","1")}"
      vpc = true
    }
    
    resource "aws_nat_gateway" "nat" {
      allocation_id = "${coalesce(var.nat_eip, join("", aws_eip.nat.*.id))}"
      subnet_id = "${var.subnet_id}"
    }
    

This was an unexpected obstacle for me, I was expecting coalesce() to not even evaluate the second parameter if the first was not empty. In retrospect I can see how it attempting to resolve the reference to pass the object would cause issues. By using the loop form, it successfully detects that there are 0 elements and moves on with its life. Note that with coalesce() order is important, as the function uses the first non-empty string.

Overall, I have had a great time playing with my first real foray into all in infrastructure as code. In the past most of my projects have been working with orchestration and configuration management, but none of those have really touched the underlying infrastructure. Bringing an entire stack up from having an AWS account and credentials to serving requests in a single command was pretty fun, I have to admit.


DevOps Days NYC 2016 Once again fucked by the intricacies of Debian packaging


comments powered by Disqus