-
Notifications
You must be signed in to change notification settings - Fork 10.1k
query: propagate graph node removal to descendants #37664
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
Conversation
4f22ec4 to
46d5c2f
Compare
46d5c2f to
6b39ec2
Compare
|
The equivalence tests failed. Please investigate here. |
| // If the node is to be removed, we need to remove it and its descendants from the graph. | ||
| if v.ResourceAddr().Resource.Mode != addrs.ListResourceMode { | ||
| deps := g.Descendants(v) | ||
| g.Remove(v) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you want to reverse the logic here and find what you need to keep rather than remove -- you can see the current TargetsTransformer for an example. While it's unlikely to happen between resources right now with the structure of query files, this can technically remove things like named values or providers you depend on, because those can still reference non-list resources.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 I intentionally let them be removed, because they are probably not able to evaluate correctly if their dependencies are missing. Since those nodes depend on a node that has been removed, their evaluation would likely result in an error. Is that what we want rather than removing them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The correct way to evaluate dependencies is to follow the dependencies from the source (usually via Ancestors). If you walk in the opposite direction, you don't know where those nodes in turn have their own dependencies. This means that a node which is a shared dependency of what you want to keep and what you want to remove could be removed. Take this minimal example
# .tf
ephemeral test_secret key {
}
provider "bufo" {
key = ephemeral.test_secret.key
}
# .tfquery.hcl
list bufo_resource all {
}
When you reach ephemeral.test_secret_key you would walk backwards and remove the bufo provider which the list resource depends on, and actually remove the list resource too! What instead needs to happen is that you start from the list resource and keep everything that it depends on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you. Yeah the list resource itself would get removed here 🥲 . However, unlike -target, which would try to keep as may things in the graph that the target may need (here the target is the list resource), we want to still remove ephemeral.test_secret_key because we only want to keep list resource nodes, and potentially other non-resource node.
When we implement this with Ancestors like you suggested, we also would keep provider.bufo, but not ephemeral.test_secret_key. When we try to evaluate the provider node, we would probably fail because it depends on a non-list resource. That would be okay, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In order for list resources to work, you need to be able to configure their provider. The list.bufo_resource in my example would require that the bufo provider be correctly configured to operate, and that configuration requires the ephemeral.test_secret resource. As far as I understood it, the primary reason for including the main configuration in query evaluation is because we need the providers which are configured there.
Since providers can depend on literally anything, you must take all dependencies into account. I think the only alternative would be to require static provider configuration directly in the tfquery file, then there would be no reason to evaluate the rest of the config at all.
Your graph at this point is the source of truth for the sum of all dependencies between nodes, so you have to trust that everything upstream from list resources is required for the list resources. Trying to individually pick out nodes without following all the dependencies is only going to lead to eventual errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think then this brings us back to the point where terraform query may then also execute managed resources. I thought we wanted to avoid that in the first place.
Also If the attribute that the provider depends on is known after apply, the provider cannot be configured, and the list cannot be evaluated.
If we are mixing configuration like this, we either have to accept that
- a query plan does not only operate on query files. At best, it's like a target for list blocks, and may still include resources needed for those blocks to execute.
- we take a hard stance on preventing cross-usage, and (mis)use would likely lead to confusing errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went with the second option in 6a8f3d2
in the new example test, the list resource depends on a provider which depends on a managed resource. We will turn this into plan errors like
"Reference to uninitialized resource"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, technically the provider can reference managed resources too, although of course it's not recommended. If the query is being executed when that resource already exists in the state, then it will probably still work; but if the value is unknown during the plan, then there's not much we can do other than error out.
| keep.Add(v) | ||
| deps := g.Ancestors(v) | ||
| for node := range deps { | ||
| if _, ok := node.(GraphNodeConfigResource); !ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There can be other types of resource nodes in the graph here, just skipping these is not a complete guarantee that you can't pickup something unusual. Note that GraphNodeConfigResource encompasses all resource modes, so you could be removing things you need as well.
In the rare case of a managed resource is captured as a dependency of a list resource (which would probably just be through a provider for now), I would say it's probably OK to just continue and see what happens. You should be at least a -refresh-only type mode, so there shouldn't be any changes to plan.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that GraphNodeConfigResource encompasses all resource modes, so you could be removing things you need as well.
Because the outer loop is going over the entire graph, and keeping all list resource nodes (on line 42), we would have kept it, even if it wasn't added in this internal loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say it's probably OK to just continue and see what happens. You should be at least a -refresh-only type mode, so there shouldn't be any changes to plan.
We could also do that. But if it isn't a refresh mode, e.g. the resource is not yet in the state, the operation cannot succeed. But it would not succeed anyway if the resource is unknown
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly! It can't work either way, so we probably don't need to go out of our way to avoid it, while also risking other inconsistencies in the graph.
4f1b8f3 to
12c4ce6
Compare
12c4ce6 to
d1e0ee1
Compare
Co-authored-by: Samsondeen <[email protected]>
|
I'm going to lock this pull request because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active contributions. |
This replaces the
resourceMatcherfunction with a transformer called towards the end of the graph transformation process.At that stage, all resources and blocks should have been added into the graph. The transformer targets the list resource nodes in the graph, keeping all vertices along the path of each list resource nodes. This ensures that the final graph is complete in evaluating list resources.
Target Release
1.15.x
Rollback Plan
Changes to Security Controls
Are there any changes to security controls (access controls, encryption, logging) in this pull request? If so, explain.
CHANGELOG entry