Terraform: Conditional doesn't support boolean comparison

Created on 5 Apr 2017  ยท  16Comments  ยท  Source: hashicorp/terraform

Terraform Version

Terraform v0.9.1

If this issue appears to affect multiple resources, it may be an issue with Terraform's core, so please mention this.

Terraform Configuration Files

variable "launch_box" {
  description = "Should a box be launched?"
  default = true
}

resource "aws_instance" "box" {
    count = "${var.launch_box == true ? 1 : 0}"
    ....
}

Expected Behavior

Expected that launch_box == true would evaluate to true.

Actual Behavior

It only works as launch_box ? 1: 0. When adding the comparison operator it no longer evaluates the boolean comparison.

bug config

Most helpful comment

I believe what's going on here is that when you compare a variable value to true you're comparing a string to a boolean, and so this always returns false because values can only be equal if they are the same type.

In the next major release of Terraform we're planning to allow variables of types other than string, map of string, and list of string. That will at least allow the variable to actually _be_ boolean and thus satisfy this condition, including attempting automatic conversion of what the caller passes in to the variable:

# Not yet implemented, and details may change before release
variable "launch_box" {
  type    = bool
  default = true
}

For now I'd suggest just using the variable value directly as your predicate, since the ? : operator knows that it needs to convert the predicate to bool before evaluating it, while the == operator doesn't have enough context to assume that's what's desired.

Once we've completed the work to allow variables to be explicitly typed as boolean we can see if this mitigates the problem or if some further changes are required.

All 16 comments

Hi @adamgotterer! Sorry things didn't work as expected here.

Comparing the variable's value to true is somewhat redundant since it's already a bool anyway, but agreed that what you wrote here should work. Just to clarify: when you wrote it as shown in the example here, did it return some sort of error or did it just give you the wrong result? If the former, it'd help to see the actual error message returned, and if the latter it'd be good to hear more about what value actually ended up being assigned to count here.

count = "${var.launch_box ? 1 : 0}" works as expected. It didn't return an error, it just evaluated to false.

I'm seeing the same behavior, I've previously used var.variable == true ? 1 : 0 in the past and that syntax has worked fine (and still appears to be working for the old places in our code where we still use it).

aws_route53_record was the resource where I noticed the issue, but I don't know if this is specific to that resource type or not. I wanted to conditionally set up some DNS records based on a count flag, but the var.variable == true syntax does not function correctly when there is a nested call to get a list length inside the conditional:

This definition produces a count of 0 regardless of the state of the flag variable:

resource "aws_route53_record" "local-applications" {
  count = "${var.manage-local-dns-entries == true ? length(var.plumbing-local-application-names) :   
  ... 
  <other parameters>
}

Without the equivalence operator, however, we get the expected count of the size of the list when the flag variable is true.

resource "aws_route53_record" "local-applications" {
  count = "${var.manage-local-dns-entries ? length(var.plumbing-local-application-names) : 0 }"
  ... 
  <other parameters>
}

I just tested the more verbose variable == true ? 0 : 1syntax on a simple new resource, and it also is not correctly added when I create a new plan (as @adamgotterer also observed). My existing resources that use this syntax, however, are unaffected (Terraform is not actively trying to delete them), so at least the logic for handling refresh of existing state with these resources seems to be functioning correctly.

edit All these behaviors were observed on Terraform v0.9.4

This is still the case in 0.10.8. count = "${var.variable == true ? 1 : 0}" wants to remove my resource when variable is set to true during plan. Removing == true comparison fixes it.

Yeah. I've hit the same thing. I spent almost an entire day, until I added an extra variable to my module and resource, and VOILA, it worked as expected.

Went back, excised the extra var, and removed the == true comparison on the original bool, and bango, it works again.

Kinda weird. Really unintuitive. BUT, to be fair, their documentation DOES (in a very ambiguous way) hint at this as being the proper usage. See here

```A common use case for conditionals is to enable/disable a resource by conditionally setting the count:

resource "aws_instance" "vpn" {
count = "${var.something ? 1 : 0}"
}
In the example above, the "vpn" resource will only be included if "var.something" evaluates to true. Otherwise, the VPN resource will not be created at all.

```

Maybe we can leave the interpolation logic buried deep in the heart of the beast alone, and instead just ask for a NOTE: Do not do equality comparisons on bools warning/clarification in the documentation instead?

I think I've found a less annoying/confusing pattern, to be honest. Does anyone have advice on why I shouldn't do things this way?

Rather than using a ternary at all, why not just multiply the boolean variable by total count of whatever it is you're creating?

Example:

At the project I'm currently with, My use case is that I always want a DNS record to be created with each new public EC2 (or EC2 cluster) I instantiate, but if I create a private instance (non-publicly addressable), I don't want a DNS record to be created.

Since EC2_instance resources carry the hard-coded "associate_public_ip_address" property as a boolean (and it is set to true/1 when enabled), why do any ternary magic to come up with a count variable, when the value of that boolean is always going to be 1, or 0?

Instead, I simply multiply the boolean (associate_public_ip_address) by the # of instances being created to get a count.

I'm sure there are many cases when the value you're hoping to set for count is different from something like a simple "number_of_instances" variable, but it's getting the job done for many of my cases.

Here's the route53 snippet from my ec2-module.

locals {
  public_ip_enabled_instances_count = "${var.associate_public_ip_address * var.total_instances }"
}

# TODO: This thing forces a new DNS entry when you increase count from 1 to anything > 1
# Best way to fix this is with `signum()`
# This is problematic, but create_before_destroy sidesteps that (hacky)

resource "aws_route53_record" "public_record" {
  count   = "${local.public_ip_enabled_instances_count}"
  zone_id = "${var.aws_route53_zone_id}"
  name    = "${var.total_instances > 1 ? format("%s-%02d.%s", var.instance_name, (count.index + 1), var.dns_zone) : format("%s.%s", var.instance_name, var.dns_zone)}"
  type    = "A"
  ttl     = "${var.dns_ttl}"
  records = ["${aws_instance.ec2_instance.*.public_ip[count.index]}"]

  lifecycle {
    create_before_destroy = true
  }
}

Thoughts?

how do you miss-match multiple conditions?

when i am trying to do

variable "Project_instEIPFlag" {
  description  = "Whether if EIP is required or not for the project instance."
  default      = false
}

count = "${
    var.Project_instEIPFlag*(local.Project_spotInst_noEBS == 2) ?
    var.Project_projInstNo :
    0
  }"

i get the error
Error: module.newProj.aws_eip.spot_eip: At column 5, line 2: operand 2 should be TypeInt, got TypeBool in:

${
    var.Project_instEIPFlag*(local.Project_spotInst_noEBS == 2) ?
    var.Project_projInstNo :
    0
  }

This just cost me one hour of debugging. I looked at that conditional again and again to check if i somehow got this super simple logic wrong... turns our terraform cannot do conditionals with booleans. There was absolutely no error message, it just used the else branch.

I believe what's going on here is that when you compare a variable value to true you're comparing a string to a boolean, and so this always returns false because values can only be equal if they are the same type.

In the next major release of Terraform we're planning to allow variables of types other than string, map of string, and list of string. That will at least allow the variable to actually _be_ boolean and thus satisfy this condition, including attempting automatic conversion of what the caller passes in to the variable:

# Not yet implemented, and details may change before release
variable "launch_box" {
  type    = bool
  default = true
}

For now I'd suggest just using the variable value directly as your predicate, since the ? : operator knows that it needs to convert the predicate to bool before evaluating it, while the == operator doesn't have enough context to assume that's what's desired.

Once we've completed the work to allow variables to be explicitly typed as boolean we can see if this mitigates the problem or if some further changes are required.

Any thoughts on why this fails with the Terraform version v0.11.8 ?

  lifecycle {
    prevent_destroy = "${terraform.workspace == "prod" ? true : false}"
  }

also tried ? 1 : 0

* cannot parse 'prevent_destroy' as bool: strconv.ParseBool: parsing "${terraform.workspace == \"prod\" ? true : false}": invalid syntax

Same issue as @jeffsteinmetz posted. Any answer?

Hi all,

In v0.12.0-alpha1 I tried the following configuration which I derived from the one in the initial comment of this issue:

variable "launch_box" {
  description = "Should a box be launched?"
  default     = true
}

resource "null_resource" "box" {
  count = var.launch_box == true ? 1 : 0
}

This now works as expected:

$ terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # null_resource.box[0] will be created
  + resource "null_resource" "box" {
      + id = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

null_resource.box[0]: Creating...
null_resource.box[0]: Creation complete after 0s [id=3509795697543999897]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
$ terraform apply -var="launch_box=false"
null_resource.box[0]: Refreshing state... [id=3509795697543999897]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # null_resource.box[0] will be destroyed
  - resource "null_resource" "box" {
      - id = "3509795697543999897" -> null
    }

Plan: 0 to add, 0 to change, 1 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

null_resource.box[0]: Destroying... [id=3509795697543999897]
null_resource.box[0]: Destruction complete after 0s

Apply complete! Resources: 0 added, 1 changed, 0 destroyed.

This works now because Terraform v0.12 supports variables of type bool rather than forcing all primitive-typed variables to be strings. Although I didn't specify type = bool explicitly in this configuration, Terraform inferred it by looking at the type of the default expression.

Thanks for reporting this, and thanks for your patience while we laid the groundwork to fix it!


(Regarding the question about prevent_destroy: that and the other values in the lifecycle block do not support interpolation expression at all because they are resolved early on while Terraform is building its graph. prevent_destroy in particular is due for some redesign in a later major release to deal with the fact that it is currently difficult to use with child modules or workspaces for this reason, but that is outside of the scope of this particular issue. At the moment, prevent_destroy must always have a literal value.)

I do not believe this completely fixed. The boolean comparison still returns true when used with maps and arbitrary expressions.

For example:

locals {
  accounts = {
    "abc123" = {
      "flag" = true
    }
  }
}

locals {
  value_1 = "${lookup(local.accounts["abc123"], "flag", false) ? "correct" : "not correct"}"
  value_2 = "${lookup(local.accounts["abc123"], "flag", false) == true ? "correct" : "not correct"}"
}

output "debug_var" {
  value = {
    value_1 = "${local.value_1}"
    value_2 = "${local.value_2}"
  }
}

Outputs:

debug_var = {
  value_1 = correct
  value_2 = not correct
}

From the language perspective, the value should be the same. But somehow either type information gets lost or something else happens and the resulting logic is incorrect.

As others noted above this problem is very difficult to pinpoint when debugging, because of how familiar the pattern is and how unexpected the behavior is.

Hi @vasili-zolotov!

Would you mind opening a new issue for that more specific example? It's likely that the root cause of it is quite different than the original bug reported here, so we'll need to start a fresh investigation starting with the information requested in the New Issue template.

Hi @vasili-zolotov!

Would you mind opening a new issue for that more specific example? It's likely that the root cause of it is quite different than the original bug reported here, so we'll need to start a fresh investigation starting with the information requested in the New Issue template.

21574

I'm going to lock this issue because it has been closed for _30 days_ โณ. This helps our maintainers find and focus on the active issues.

If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

Was this page helpful?
0 / 5 - 0 ratings