New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
enabled
parameter to avoid logical statements in count
#21953
Comments
This is in my opinion such a huge problem with what appears a "simple" solution. Relying on count to disable a module has a lot of bad effects from how you retrieve the output to having warnings about unsupported "count" from submodules that define providers themselves. What is the technical/functional obstacle of having a simple boolean switch on modules and/or resources? One can neatly separate module calls by using terraform workspace name and much more, avoiding tons of nasty hacks that are filling up the entire web when searching for "terraform re-usable modules" or any similar dry concepts. |
@PCatinean The argument was that there are no clear use-cases. I fully support yours and @romachalm and here's mine: I deploy Landing Zone for 200 Products from my However, when using What do you think of this use-case? |
Adding additional context @schollii provided in #27936 [As recommended by @apparentlymart in https://github.com/hashicorp/hcl/issues/450, I'm moving this improvement request here.] The notion of "nullness" or "existence" must be first class in a language, and the language should minimize how much "how" the user needs to express. Currently in order to make resources and modules optional, we have to resort to using count = 0 or 1. Examples:
The problem is that in current HCL the resource
Now make
The Similarly if I define an item as a list, it really is a list: a list of no items, 1 item, or N>1 items, all are lists and the code should reflect that. So if I write
clearly the semantics of Since enabling/disabling resources and modules is fundamentally different from creating and manipulating lists of items, it should have its own meta variable. I should be able to write
In addition, the language should make it easy to handle the situation where code refers to a resource that has been disabled. It is probably reasonable for the above expression
The third value (the 4th argument of the quaternary operator) would be used if the expression on the LHS (the 1st argument of quaternay operator) cannot be evaluated because some objects are nil. Syntax errors and calling an non-existent method on a non-nil object should still fail. An expression on LHS of ? like The notion of "nullness" or "existence" should be first class in a language, and the language should minimize how much "how" the user needs to express. |
Thank you @jbardin for taking the time to write that up. The sheer length of the post forced me to also take some time to read it and understand it, and you summarized it very well. How does it look now? Did the chances of this going through increase? It would help many Terraform users build better, nicer, cleaner and more flexible code, and adopt On the point of chained resources, I would say it doesn't require explicit handling. If parent resource is How does that sound? |
Hi @Tbohunek, don't thank me, thank @schollii, who originally did that nice writeup as feature request in the hcl repository ;) I agree it is a compelling feature, but there are many competing compelling features to consider for terraform, and we cannot prioritize them all. I cannot say when this might be considered, but it is not a minor feature and will take considerable planning to handle in a future version of terraform. |
@jbardin This looks like a minor and backward-compatible effort though. While waiting for the big features to be prioritized, we keep struggling daily and produce hard-to-maintain unreadable code! |
Consider Meta-Argument When choosing a Meta-Argument consider classes with existing resources.
|
@damon-atkins both condition and include_if are fine by me, but I'm not sure I agree with content. Can you provide an example? I think count and enabled / condition / include_if would have to be mutually exclusive. Count means you want an array, possibly of 0 size, and the language should make it simple to handle the case where count is 0, a bit like Whereas enabled / condition is never an array, the resource is either there or null, and the DSL should make it easy to deal with null / disabled resource. Eg in an output that refers to resource that has enabled false, the output should be omitted without error instead of having to jump through hoops as we do now. |
I just ran into this when trying to create OpenStack resources, optionally using a keypair or creating one depending on a value. Unfortunately it is extremely ugly to make everything consider the keypair to be a list. |
content as show allow you to pass a data structure representing the HCL
vs
|
@jbardin what is the next step in the process, for this idea to be approved in one shape or form. |
Hi all! I think there are at least four remaining design questions to be solved before something like this could be implemented:
I'd ask you all to recognize that the We are still open to discussing this direction but in order to move the discussion forward we need to work together to answer the remaining design challenges. There's still plenty of design work left to do here before it would be time to move on to implementation, so I'd love to hear ideas for how to address these, and indeed any other questions we might identify along the way in the process of answering the ones I've identified above. |
@apparentlymart I think |
The best sugar of it all would be to not have to add unpredictable |
@Tbohunek I wish |
count has order issues, for_each does not, hence people have change code to use for_each.
Sometime people create two tf files which are the same with parts of one deleted, because a flag like this does not exist.
|
To actually react on @apparentlymart's post
How about For |
The following could be the same, i.e. if
|
We cannot add any new synthetic attributes to an expression like |
Wouldn't the chosen attribute like |
This comes back to my comment about defining what exactly (Note that this is also in the vicinity of the other point I made about distinguishing the resource from the resource instance. For I also want to clarify that my intent with the previous comment wasn't to suggest that we can puzzle out all of those details individually over the course of many comments. Instead, I was imagining somebody writing a design proposal document that covers the whole problem space and proposed solutions all together, so that we can see how the different parts interact and make sure it all adds up to something that actually works as part of the broader language. |
In some languages (eg Python), null is an object ie it has a type and attributes just like any other (it is called None in Python but same thing). Could something like that work? So there would be a built-in resource that represents null and has enabled = false. Every other object would have enabled = true. Also, let's not forget that the real goal is to not have to litter the code with |
@Tbohunek Ah sorry I missed that, so here it is, looks like the answer to the following question is still no:
No because this: With resource random_pet "counted" {
count = 0
} a output "counted_res_3" {
value = random_pet.counted[0]
} whereas wrapping it in output "counted_res_4" {
value = try(random_pet.counted[0], "something")
} Here So the "magic" is already there, and for optional resource we would have resource random_pet "optional" {
enabled = false
} and the following would fail: output "optional" {
value = random_pet.optional
} but wrapping it in output "optional" {
value = try(random_pet.optional, {})
} because terraform knows that Some things regarding the
|
Summary so far(terminology clarification: Resources are specified via arguments and each resource instance has a type with specific attributes; the type is always available to terraform even if there are no instances of a resource) Singleton resource:(note: For lack of a better term I have named resources that do not have
Behavior in expressions:
Counted resource:
Behavior in expressions:
The backward compatible change would be to make every singleton resource optional, via a new meta argument similar to Optional singleton resource:
Behavior in expressions:
There doesn't seem to be a need to talk about resource nullness at this level. It remains so far an implementation detail. |
@schollii I am afraid the elephant in the room is being overlooked. In both examples, namely If the languages is to be uplifted to allow this, then the notion of using |
I think insisting on resource being null when it is disabled is the elephant. Let's say that that is not what we do. Eg in Python I can define a class with a data member flag Maybe that could be done in HCL2, but I'm sure there are other approaches if that one does not work for HCL2. Whatever the approach, it must not use null for the resource. |
@schollii That is a fair way to put it. But introducing Exception Handling and a more complex notion of a resource availability beyond exists or null is too much of a tall order IMO. I am not convinced that the notion of "nullable" resource should be solved otherwise, but what it ought to be: nullable resources with syntax-sugar to make them ergonomic. This is a solvable problem in HCL in a backwards compatible way. Anything else that would solve this by proxy demands unwanted complexity. |
@schollii Nice summary! 🚀 For a try(resource_type.name, null) but this does? try(resource_type.name.id, null)
I get how it work technically, but to me it's inconsistent and I would make it abort in both cases. try(resource_type.name[0], null) This however explains why these two approaches that you propose: try(resource_type.name, null) # access disabled resource
try(resource_type.name?.id, null) # access disabled resource's attribute And now my question would be, if it would hurt to have only this instead: try(resource_type.name?, null) # access disabled resource
try(resource_type.name?.id, null) # access disabled resource's attribute IMO it would be a touch more predictable not to have to differ between accessing a disabled resource vs accessing its attributes. Nevertheless, the latter (accessing attributes of |
However @schollii I have to come back to notion of
If to make resource value = resource_type.name
value = resource_type.name.id into bunch of this value = try(resource_type.name, null)
value = try(resource_type.name?.id, null) ...It probably makes static analysts happier, however it makes all terraform users unhappier. I'm not a huge fan of |
At the risk of adding more noise, I'd like to reiterate the problem trying to be solved here and showcase how @schollii solution does a good job of addressing that. Take this example: resource "null_resource" "this" {
triggers = {
pet_one = random_pet.one.id
pet_two = random_pet.two.id
}
}
resource "random_pet" "one" {
}
resource "random_pet" "two" {
}
output "triggers" {
value = null_resource.this.triggers
} If we were to need to "disable" one of or all of these resources, those resources would have to add a Example diff: resource "null_resource" "this" {
+ count = var.enabled ? 1 : 0
+
triggers = {
- pet_one = random_pet.one.id
- pet_two = random_pet.two.id
+ pet_one = random_pet.one[0].id
+ pet_two = random_pet.two[0].id
}
}
resource "random_pet" "one" {
+ count = var.enabled ? 1 : 0
}
resource "random_pet" "two" {
+ count = var.enabled ? 1 : 0
}
output "triggers" {
- value = null_resource.this.triggers
+ value = try(null_resource.this[0].triggers, "none")
} The other way to avoid this is to always create resources with an explicit If however, there were a way to "disable" a resource and that disabled resource would ignore any also disabled resources referenced in its block, much like the way the above doesn't care that Example: resource "null_resource" "this" {
+ enabled = var.enabled
+
triggers = {
pet_one = random_pet.one.id
pet_two = random_pet.two.id
@@ -6,11 +8,13 @@ resource "null_resource" "this" {
}
resource "random_pet" "one" {
+ enabled = var.enabled
}
resource "random_pet" "two" {
+ enabled = var.enabled
}
output "triggers" {
- value = null_resource.this.triggers
+ value = try(null_resource.this.triggers, "none")
} Notice that the use of To reiterate, the problem trying to be solved here is the code churn and error prone diffs that result from Terraform's currently lack of a easier and less verbose way of disabling resources conditionally. |
@zachwhaley you refer to @schollii solution, however you didn't follow it as you couldn't do: pet_one = random_pet.one.id You'd have to do: pet_one = random_pet.one?.id This is important detail, and makes the churn grow again: resource "null_resource" "this" {
+ enabled = var.enabled
+
triggers = {
- pet_one = random_pet.one.id
- pet_two = random_pet.two.id
+ pet_one = random_pet.one?.id
+ pet_two = random_pet.two?.id
#+ pet_one = random_pet.one[0].id
#+ pet_one = random_pet.two[0].id (How did you do that awesome highlight?) |
I don't think you would have to, because as @zachwhaley indicated, once a resource is disabled (like the |
It is totally consistent:
Same for OTOH I think that's another good reason to introduce enabled / disabled: thinking in terms of lists can be rather tricky, and leads to nasty code like |
That would definitely not be the case:
So given the following use case:
That sounds pretty sweet to me. This is not the only interesting use case, another one is selecting between different implementations that a module can provide, such as allowing postgres vs mysql, or aurora vs postgres, or allowing the user to select whether they want their security group to have fixed name or just a prefixed-name (and AWS generates the portion after prefix). In that latter case, with enabled it looks something like this: resource_type prefixed {
enabled = var.prefixed && var.user_specifiied_id == null
}
resource_type not_prefixed {
enabled = ! var.prefixed && var.user_specifiied_id == null
}
locals {
id = try(resource_type.prefixed.id, resource_type.not_prefixed.id, var.user_specifiied_id)
} because the following will happen:
|
No intention. It is difficult to keep track of the many ideas, questions, and technical details due to typos or just forgetting or just misunderstanding someone else's statement. My apologies if something I wrote comes across as intentionally dropping something you suggested. So I'm happy to add it, but I'm not sure what I should be adding! Can you clarify, like write out the table row or cell as you think it should be. |
Those damn lists! Thanks for your explanations @schollii, I hope these will be useful to more than just me. 👋 I originally didn't notice that you haven't included the It also makes clear the IMO most important part: So now I think would be the time for Hashicorp to again look at your proposal and see if that's achievable or not. |
It would have been just to explicitly point out |
For a single resource , i would also love to see |
|
count is a hack. The behavior should not be like this. A great example of the struggles here is when creating an aws load balancer + target group/target group attachments in a module. If you want to target an interface endpoint for example, you need to use the 'data "aws_network_interface" "data-name" {}' block to pull in the endpoints interface parameters (the private ips etc). You can then pass this to the load balancer module and use them as attachments. The problem here is is you want to conditionally create the load balancer it wont work... there is no way to avoid the "value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created." error message (none I have found). depends_on should fix this problem but it does not work. Makes me want to move away from terraform all together |
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
Well, the question about official triage feedback was not off-topic. |
Enabled seems like a similar solution to Ansible's when statement. Similar solution: |
Whatever the solution is, can it just be added to the next major TF version ( |
I have never been able to understand why Terraform doesn't include a simple conditional, as suggested. Be it "if var.value = true", "enabled = var.value", or someting -- anything -- other than having to rely on hacking the looping parameters to create conditional resources. This is a normal feature we find in most other languages. Specifically for Terraform, it has been requested in many places multiple times. People have blogged about how terrible Terraform is because a simple construct like this is not included. Yet for some reason a deaf ear is turned toward including this feature. Please don't make excuses anymore about why the "if" construct is unnecessary. Instead, please listen to the voices of the people working in Terraform day in and day out. This seems like a simple feature to implement. |
Terraform v0.12.2
Use-cases
We often use
count
to disable a resource (above all in modules) from a boolean var, with statements likecount = var.enabled ? 1 : 0
orcount = var.enabled ? 1 : length([some list of resources or datasources])
Here are among others some code snippets from
terraform-aws-vpc
module :I have also witnessed some
int
variable used ascount = var.enabled * length([some list of resources or datasources])
.It makes the triggers more complex to understand, as
count
actually embeds both a boolean expression to enable the provisionning, together with the number of instances of the resource. It sounds like a twist of the initial expectedcount
usage.Attempted Solutions
I have thought initially about having
count
accepting booleans by automatically convertingtrue->1
andfalse->0
.I had a quick discussion with
go-cty
developer that explained to me the intentional decision not to convertbool
tonumber
to avoid confusions, and indeed, I am aligned that the code should stay readable by segregating the scopes.Therefore, I attempt this proposal below.
Proposal
Introducing a new parameter
enabled
in resources acceptingbool
values or logical statements to provision the resource (default) or not, independently fromcount
value (which can be 0 by the way).It will increase the readibility of the code by avoiding sometines complex conditional operators
?:
The previous examples would become :
Another use case I often see is to to provision some resources only for prod configurations (audit logs buckets, special routes or firewall rules):
enabled
shall preced the interpretation ofcount
interpretation.IMO, it will make the code more readable by explicitly describing what may prevent the provisionning of
count
resources. It will also allow to clearly distinguishbool
fromint
variables.References
I haven't found any ticket on that subject. Sorry if I have missed one.
The text was updated successfully, but these errors were encountered: