Always evaluate resources in their entirety#22846
Conversation
In order to allow lazy evaluation of resource indexes, we can't index resources immediately via GetResourceInstance. Change the evaluation to always return whole Resources via GetResource, and index individual instances during expression evaluation. This will allow us to always check for invalid index errors rather than returning an unknown value and ignoring it during apply.
Always return the entire resource object from evaluationStateData.GetResource, rather than parsing the references for individual instances. This allows for later evaluation of resource indexes so we can return errors when they don't exist, and prevent errors when short-circuiting around invalid indexes in conditionals.
8284d38 to
95da635
Compare
Continue only evaluating resource at a whole and push the indexing of the resource down into the expression evaluation. The exception here is that `self` must be an instance which must be extracted from the resource. We now also add the entire resource to the context, which was previously only partially populated with the self referenced instance.
Now that we only evaluate whole resources, we can parse resource refs correct as the resource, rather than an unknown instance.
Now that the most common cause of unknowns (invalid resource indexes) is caught earlier, we can validate that the final apply config is wholly known before attempting to apply it. This ensures that we're applying the configuration we intend, and not silently dropping values.
95da635 to
fed4e82
Compare
apparentlymart
left a comment
There was a problem hiding this comment.
I left some questions/feedback inline, but this broadly makes sense to me and I expect my questions may be arising from not having the context fully loaded.
lang/eval.go
Outdated
| rawSubj = selfAddr | ||
| isSelf = true | ||
| // self can only be used within a resource instance | ||
| subj := selfAddr.(addrs.ResourceInstance) |
There was a problem hiding this comment.
This was originally designed to be generic so that we could in principle support self in other contexts in future, since there are several issues open around that sort of thing. Fully generalizing this isn't necessary right now but since this limitation is not obvious in the Scope API, could we handle this by panicking a more specific error instead?
Relatedly, it seems like the condition right below this can no longer be true because if selfAddr were addrs.Self this type assertion would've panicked already. That branch is also just trying to present a more helpful panic message when the caller uses this API improperly, so perhaps we should invert this to let that one "win" for the more specific case.
| panic("scope SelfAddr attempting to alias itself") | ||
| } | ||
|
|
||
| val, valDiags := normalizeRefValue(s.Data.GetResource(subj.ContainingResource(), rng)) |
There was a problem hiding this comment.
Rather than handling this as a totally special case up here, could we instead get the same result by setting rawSubj to subj.ContainingResource and then just letting this fall out into the switch statement below? If I'm reading this right, it seems like we'd then branch to the case addrs.Resource label and get the same result.
(Alternatively, maybe we could just skip populating managedResources altogether here; populating self should be sufficient I think, because if there were any references to the resource itself as well then we'll catch them on a different iteration of the loop. That would also remove the risk that some future enhancement allows self to refer to data resources and we end up incorrectly putting them in the managedResources map below, just thinking defensively about what ways outside code might violate these assumptions in future.)
There was a problem hiding this comment.
I think your latter suggestion is correct, and I just followed existing code+tests which ended up populating the managed resources.
We need to assign an instance to self in the final context, so I think this section is still required to extract the "self" value from the resource as a whole, whereas the switch below is adding resources.
lang/eval.go
Outdated
| } | ||
|
|
||
| val, valDiags := normalizeRefValue(s.Data.GetResourceInstance(subj, rng)) | ||
| val, valDiags := normalizeRefValue(s.Data.GetResource(subj.ContainingResource(), rng)) |
There was a problem hiding this comment.
Could we remove this case by putting something like the following fixup before the switch here:
if addr, ok := rawSubj.(addrs.ResourceInstance); ok {
rawSubj = addr.ContainingResource()
}I think that wouldn't have been possible with how self was handled before, where each of these cases was responsible for detecting if it was self and setting it, but now that self is being handled as special anyway it seems like that removes the remaining need for this clause. I'm probably missing something though, since there seem to be a bunch of different codepaths through there and I'm not sure I'm catching all of the interactions.
There was a problem hiding this comment.
Good call. I think I meant to try and coalesce these cases and forgot
self references do not need to be added to `managedResource`, and in fact that could cause issues later if self is allowed in contexts other than managed resources. Coalesce 2 cases in the Referenceable switch, be take the ContainingResource address of an instance beforehand.
|
Updated with the minor fixes from review. |
|
@jbardin It seems this PR is breaking the following use-case: module "mymodule" {
source = "something/something"
name = "myname"
create_resources = var.create_resources
security_groups = [
# ...
aws_security_group.mysg[0].id,
]
}
resource "aws_security_group" "mysg" {
count = var.create_resources ? 1 : 0
name = "myname"
# ...
}Because we can't have module "mymodule" {
source = "something/something"
name = "myname"
create_resources = var.create_resources
security_groups = [
# ...
var.create_resources? aws_security_group.mysg[0].id : null,
]
}
# ...We have a bunch of other places like that in our codebase, and thus I'm wondering if it should really be fixed that way or not. Let me know if that makes sense! |
|
Hi @Ninir, Yes, if there are no I would use which ever solution leads to the most readable result. Taking a guess from the snippet above, a splat expression should give you want you want Which is equivalent to a list comprehension, which can add additional filtering as an If you don't want to operate on the instances as a complete group however, guarding single references with a conditional would be required. |
|
@jbardin ok, so it is up to people to fix their cases got it. Thank you! |
This commit was generated from hashicorp/terraform#22846.
This cherry pick depends on hashicorp/terraform#22846 create_before_destroy across modules
Cherry pick hashicorp/terraform#22846: Always evaluate resources in their entirety
This cherry pick depends on hashicorp/terraform#22846 create_before_destroy across modules
This cherry pick depends on hashicorp/terraform#22846 create_before_destroy across modules
|
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. |
The primary goal of this PR is to always return entire resources to be evaluated, rather than individual instances. This allows us to push the evaluation of resource indexes down into expression evaluation.
Invalid indexed references to other resources were silently returning unknown values when the instance didn't exist. These were only caught by extra validation in data sources when the config still contained unknown values during
ReadDataSource. This now will catch invalid indexes, and adds anIsWhollyKnownvalidation to the config during apply, preventing config values from being silently dropped.Only data sources caught this previously, but returned a rather unhelpful error of
Now that indexes are handled during expression evaluation, we can get a full diagnostic output: