hashicorp / terraform

Terraform enables you to safely and predictably create, change, and improve infrastructure. It is a source-available tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, edited, reviewed, and versioned.
https://www.terraform.io
Other
42.77k stars 9.56k forks source link

Function to convert boolean to 1 or 0 #33150

Open tmccombs opened 1 year ago

tmccombs commented 1 year ago

Terraform Version

Terraform v1.4.3
on linux_amd64

Use Cases

The main use case I have for such a function is to use as the value to the count meta argument, to simplify conditionally creating resources.

Attempted Solutions

The standard way to do this would be to use a ternary operator with something like:

resource "null_resource" "example" {
  count = var.should_create ? 1 : 0
}

And this works, but it is kind of awkward, and IMO not very readable.

Proposal

Add a new function that takes a boolean, and returns 1 if the boolean is true, or 0 if it is false. It would effectively be something like:

f(x) = x ? 1 : 0

This could potentially just be extending the tonumber function to accept a boolean value or it could be new function. If the latter, I'm not sure what the best name for it would be. Perhaps "condcount", or "boolocount", or possibly something like "enabled" to indicate the specific use case it is useful for.

References

This has a similar goal to #21953, but is a smaller change, and doesn't require any fundamental changes to the language or design of terraform, so could be landed in a more reasonable amount of time.

tmccombs commented 1 year ago

I would be happy to help with the implementation if adding this function is something the maintainers are willing to accept, and we decide on a name for it.

crw commented 1 year ago

Thanks for this request, I'll bring it to triage to discuss your proposal.

timothyclarke commented 1 year ago

I'd normally do the the following in a local

locals {
  my_var_true = var.should_create ? 1 : 0
}

resource "thing" "this" {
  count = local.my_var_true
tmccombs commented 1 year ago

Another function that has a somewhat related usecase is a function that is the inverse of one. meanting that it takes a scalar possibly null value , and if it is null creates an empty list, if it is non-null creates a singleton list containing just that item, so it woudl be equivalen to:

singleton(x) = x != null ? [x] : []

this would be useful for dynamic blocks, since you could do something like:


dynamic "block" {
  for_each = singleton(var.my_var)

  content {
     some_attr = block.x
  }
}
crw commented 1 year ago

@tmccombs I brought up the original use case in triage and the response was that we do not usually introduce new ways to solve a problem (as a built-in function, for example) if there is already a (relative concise) way to accomplish the same thing. In this case the ternary operator was deemed to be sufficiently uncomplicated for the use case. The subjective part here is "what is more or less readable code," but that is the reasoning of the maintainers in this case.

The second looks like it would follow similar logic, but is unique enough that I can re-raise the issue.

tmccombs commented 1 year ago

IMO, something like:

count = if(var.enabled)

or even

count = tonumber(var.enabled)

is a lot more readable than

count = var.enabled ? 1 : 0 

and even more so, if instead of just a variable reference we have something like var.something != null since you now have a lot of symbols , and either need to worry about order of operations, or add parenthesis, which adds even more symbols.

For my singleton function, it also has the benefit, that you no longer have to repeat the expression multiple times. That is more of an issue if the expression is more complicated than just a variable lookup. Sometimes you can use a local to factor that out, but not always, for example, if the dynamic block is part of a resource that itself uses for_each or count, and you need to reference the iteration variable.

crw commented 8 months ago

Thank you for your continued interest in this issue.

Terraform version 1.8 launches with support of provider-defined functions. It is now possible to implement your own functions! We would love to see this implemented as a provider-defined function.

Please see the provider-defined functions documentation to learn how to implement functions in your providers. If you are new to provider development, learn how to create a new provider with the Terraform Plugin Framework. If you have any questions, please visit the Terraform Plugin Development category in our official forum.

We hope this feature unblocks future function development and provides more flexibility for the Terraform community. Thank you for your continued support of Terraform!

pierreact commented 8 months ago

https://developer.hashicorp.com/terraform/language/functions/tonumber should simply accept booleans as arguments. You already can uglily to do: tonumber(tostring(true))

I have another use case for the request also.