Terraform: Conditional Outputs in Modules

There are several drastic HCL improvements forthcoming in version 0.12 of Terraform, but after an issue I encountered this week while creating some infrastructure with an 0.11.x version of the runtime, I wanted to cover the issue, how to remedy it in versions < 0.12, and talk about how (I believe) the issue will be remedied thanks to some of the 0.12 improvements.

Basically, this type of issue will manifest itself as an error during the plan phase with this form of error message: module.mymodule.output.myoutput: Resource 'foo.bar' not found for variable 'foo.bar.attribute'. Right off, the astute Terraform user will notice that this error looks like it’s coming from the use of a module – generally, you will encounter this kind of error with a module, but not always. The cause of the error stems from the use of outputs that are tied to conditional resources. Personally, I tend to see outputs more commonly used within modules than on root directory code (yes, technically, this is a module as well, but let’s put aside strict technicalities for a few minutes), hence my belief that these types of errors tend to manifest more generally with the use of modules. If you’re not using a module, but still see an error of this form where it doesn’t quite make sense to see it (e.g. resource foo.bar does indeed exist in your codebase and foo.bar.attribute is a documented, exported attribute in an output or that you’re feeding as a parameter to some other resource), then read on.

In my case, as mentioned previously, this error was discovered in a codebase that had already provisioned resources in an account. The module had a variable that created a condition to create a certain resource or not within the module. That resource had an output tied to it. The first time that variable was set such that the resource wasn’t going to be created, the error was thrown. The output basically looked like this:

output "aws_alb_arn" {
    value = "${aws_alb.foo.arn}"
}

Since aws_alb.foo had always been created before, there was no reason for the plan to fail. Now that we weren’t creating that ALB, the plan (quite obviously) would fail here. Let’s say for the sake of clarity that we had a true/false variable called create_alb that we were passing to the module invocation to toggle creation of the ALB. Then we could do something like this in the module itself (assuming we create an internal variable to pass that true/false value down to our output from the module caller’s interface):

output "aws_alb_arn" {
    value = "${var.create_alb ? aws_alb.foo.arn : ""}"
}

This should work, right? Well, yes, I’m of the opinion that it should work. But, it won’t (at least prior to version 0.12). If you want to fix this prior to 0.12, here’s how:

output "aws_alb_arn" {
    value = "${ join("", aws_alb.foo.*.arn) }"
}

That’s one ugly hack, but it does work. Why does the first ternary not work? Prior to 0.12, Terraform does not lazy-eval (or short-circuit) on ternary expressions – meaning, it evaluates the entire expression before determining what to do. This means it’s going to evaluate aws_alb.foo, which is a resource not in our graph, which Terraform doesn’t like. The splat syntax basically evaluates to an empty list for the non-existent resource, and so the workaround is considered valid (ref).

That being said, with the introduction of 0.12, we see this in the improvements list:

… The conditional operator … ? … : … now supports any value type and lazily evaluates results

That “lazily evaluates results” basically gives us the short-circuiting we expect from most general-purpose programming languages and should (theoretically) allow the expression with the ternary operator to function correctly from 0.12 on.

I’m very excited to see some of the HCL behavior improvements in 0.12 and am looking forward to cleaning up some codebases to more clearly reflect my intention as a developer, which should create better long-term outcomes for code sustainability! Thanks HashiCorp!

comments powered by Disqus