Passing empty arrays to resource arguments in Terraform

The interpolation syntax of terraform allows you to do many other things to make your modules reuseable, but one of the common problems we find is passing empty strings from variables in arrays.

Note that the strange syntax I show you are obtuse, and while someone who has done terraform for a while may well recognise the patterns, they’re non-obvious and come with an increased cost in complexity and maintenance. Make sure you make the right decision between all the costs associated with reuse.

So let’s say you have a module that registers an auto-scaling group, but you want to allow module consumers to pass an elastic load balancer they defined themselves. The naive approach would be to just pass it in the array attribute.

variable "elb" { default = "" }
resource "aws_autoscaling_group" "asg" {
  // stuff here
  load_balancers = ["${var.elb}"]
}

If you try to apply this, it will return an array of 1 with an empty string, which is not what the api expect.

The trick here is to force turning the string in an array.

resource "aws_autoscaling_group" "asg" {
  // stuff here
  load_balancers = ["${compact(split("",var.elb))}"]
}

And voila, an array of 0 or 1 element. Note that this problem will go away with terraform 0.7, as modules will be able to take lists as variables.

Ads

Creating Terraform resources conditionally

Terraform has modules, and they are both insanely great and absolutely infuriating at times.

When you split your logical resources in modules, you often want to make sure you make them configurable, by having some resources only generated when you set a flag in a variable.

variable "create_server" { default = true }
resource "aws_instance" "insanely_great_server" {
  count = "${var.create_server}"
}

This works through the magic of variables of type boolean being parsed as 0 or 1 when ints are involved, such as in count. So far so good. But what if you want to create a resource only when a variable is not set, say, if you wanted to pass an elastic IP address to a resource, or let it create one.

// let's create the variable, defaults to nothing
variable "eip" { default = "" }

// create an eip if none was passed
resource "aws_eip" "maybe_eip" {
  count="${replace(replace(var.nat_eip,"/.+/","0"),"/^$/","1")}"
}

// and finally, use the eip or the created one
resource "aws_nat_gateway" "nat" {
  allocation_id = "${coalesce(var.eip,aws_eip.maybe_eap.id)}"
  // and other stuff here
}

As any good developer knows, when you’re stuck in a corner, if you have regex, you now have +* problems but at least one solution.

Here, we use regex to turn any (.+) string into 0, so if an eip was passed we don’t create anything, and if that didn’t work out, we turn the empty string ^$ into a 1.

Only thing left to do is to coalesce, aka to take the first non empty string in the list, trying first the passed-in eip, and second the created eip.

Morale of this tale, when a resource has a count of 0 or 1, it’s just like an only_if. For more information, check out the github issue with the proposed future of conditionals.

Ads

Ignoring files in git, but only locally

I love git for one main reason: there are enough advanced commands to make nearly any problem find a solution. Ignoring some files, but only in your repository, is one of those.

For example, when doing terraform, I sometime set development-time variables in the terraform.tfvars file that exist for that exact purpose, say for my AWS credentials, and I really don’t want the file to be committed. All the same, I probably don’t want to be adding that file to the ignore list for everyone, which is the .gitignore file.

Instead, you can add your ignore to .git/info/exclude. I tend to do that from the command line.

$ echo 'myfolder/terraform.tfvars' >> .git/info/exclude

Ads