-
Notifications
You must be signed in to change notification settings - Fork 9.6k
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
Use plan graph builder for destroy #31163
Conversation
Destroy nodes should never participate in references. These edges didn't come up before, because we weren't building a complete graph including all temporary values.
We want to use the normal plan graph for destroy, so we need to flag off configuration for that process.
All instances in state are being removed for destroy, so we can skip checking for orphans. Because we want to use the normal plan graph, we need to be able to still call this during destroy, so flag it off.
Rather than maintain a separate graph builder for destroy, use the normal plan graph with some extra options. Utilize the same pattern as the validate graph for now, where we take the normal plan graph builder and inject a new concrete function for the destroy nodes.
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.
This makes sense to me!
Aside from the very nitty nits inline, my inline comments are areas where I stumbled a bit trying to follow what was going on, and so my motivation there is just to share some ideas that might make this code easier to follow in future without having all of the mental context already loaded. They're all subjective, so I'll leave it up to your judgement what (if anything) to do about them.
type = map(string) | ||
default = { | ||
"a" = "first" | ||
"b" = "second" |
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 nittiest nit: this line seems to have a tab, while the others have spaces.
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.
thanks, my vim setup doesn't like switching contexts like that
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.
FWIW, I've typically just used Go-style tab-based indentation for situations where we have configuration embedded in raw strings inside Go code, because it avoids this sort of editor fighting and isn't really important unless the test is actually testing something related to formatting.
So making this all be tabs would also have been an acceptable answer as far as I'm concerned; it was the inconsistency that bothered me. 😀
// TODO: extend this to ensure the otherProvider is always properly | ||
// configured during the destroy 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.
Heh... this comment represents exactly what I thought when I first read through this test and saw that it's only testing that the plan succeeds, not that it succeeded for the right reason.
Did you TODO
this because there's something blocking us from actually configuring the provider right now? My first instinct here was that it should be possible to check otherProvider.ConfigureProviderResponse
after the plan succeeds, but probably there's some extra complexity here I'm not understanding! If so, it would be nice to capture that in this comment too so we can see what needs to be true in order for us TODO
what this comment says. 😀
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.
yup, the provider still won't be configured yet, that will be enabled in a follow up PR. This was intended to be a drop-in replacement of the destroy plan graph with no change in behavior.
@@ -574,7 +574,7 @@ func (c *Context) planGraph(config *configs.Config, prevRunState *states.State, | |||
}).Build(addrs.RootModuleInstance) | |||
return graph, walkPlan, diags | |||
case plans.DestroyMode: | |||
graph, diags := (&DestroyPlanGraphBuilder{ | |||
graph, diags := DestroyPlanGraphBuilder(&PlanGraphBuilder{ |
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.
Would it be reasonable to make this be consistent with how we deal with RefreshOnlyMode
above and have this instead "just" set a flag in the PlanGraphBuilder
struct and let the Build
method worry about the details?
I think I ended up leaving it in this odd shape where NormalMode
and RefreshOnlyMode
are largely the same just because the DestroyMode
case was a drastically different shape, but if we normalize these to all just be conditional flags on the struct then we could potentially rework this to avoid duplicating the common fields for each case and only set skipPlanChanges
/ destroy
in the non-normal modes.
Of course fine to leave this for another day if it seems too risky, since it's just some further cleanup after all.
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.
This was following the pattern used by the validate graph, because I thought I would end up injecting more concrete functions that I ended using. I think as part of the follow up here we should get rid of both the validate and destroy builder entry points, and just make everything a plan ;)
// destroyPlan indicated this is being called from a destroy plan. | ||
destroyPlan bool |
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.
It feels a little weird to me that this flag essentially means to disable the behavior of this transformer entirely, but it's named as if it's going to modify the behavior in a more subtle way than that.
I guess being able to flag it off is probably easier than dynamically omitting it from the list of graph builders given that we currently just write those out as a slice constructor, but maybe it would read more clearly to just call this disable
, ignoreConfig
or similar instead, so that e.g. the caller will say ignoreConfig: b.destroy
which (to me at least) reads as "ignore configuration if we are in destroy mode".
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.
That makes sense to me. Adding conditional logic to the builder steps is quite cumbersome, and we managed to get all that out of the way a while back. The old flags were called destroy, but making them a bit more descriptive can definitely help.
@@ -4,9 +4,7 @@ import ( | |||
"log" | |||
|
|||
"github.com/hashicorp/terraform/internal/addrs" | |||
"github.com/hashicorp/terraform/internal/states" | |||
|
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.
Another nitty nit: it isn't clear to me why this blank line was here in the first place, but in any event maybe now we can remove it along with the two lines that were around it. 😀
@@ -26,9 +26,17 @@ type OrphanResourceInstanceTransformer struct { | |||
// Config is the root node in the configuration tree. We'll look up | |||
// the appropriate note in this tree using the path in each node. | |||
Config *configs.Config | |||
|
|||
// There are no orphans when doing a full destroy | |||
destroyPlan bool |
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.
Same thought here as about ConfigTransformer
above: can we make this flag have a name that describes more specifically what effect it has?
@@ -21,7 +21,7 @@ type OutputTransformer struct { | |||
|
|||
// If this is a planned destroy, root outputs are still in the configuration | |||
// so we need to record that we wish to remove them | |||
Destroy bool | |||
destroyPlan bool |
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.
Maybe forceDestroyAll bool
to represent that this field specifies to destroy all outputs regardless of what action we might otherwise have planned for them? (and then maybe nodeExpandOutput
itself has forceDestroy
instead of just Destroy
, for consistency?
Reminder for the merging maintainer: if this is a user-visible change, please update the changelog on the appropriate release branch. |
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 PR aims to allow the same components used in
PlanGraphBuilder
to create the full destroy plan, and eliminate the special case of theDestroyPlanGraph
.The destroy plan was originally envisioned to be completely "offline", where we could generate a simple
Delete
action for every resource in the state. It turns out that this is not possible, since in order to read each resource from the state, we must callUpgradeResourceState
to ensure the schema matches the encoded state, requiring interaction with the provider aside from fetching the schema. Provider developers have also expressed the desire to be able to plan or validate destroy actions, which would also require providers to be correctly configured.Because the simplistic
DestroyPlanGraphBuilder
was only intended to createDelete
actions, it was not sufficient to ensure we had all the data required to configure a provider before callingUpgradeResourceState
. The complete plan graph is where all the setup to properly evaluate the entire config is already done, and since a destroy plan is still a form of planning, we can extend the existingPlanGraphBuilder
to accommodate a complete destroy operation rather than duplicating all the logic in theDestroyPlanGraphBuilder
. A secondary reason for extending thePlanGraphBuilder
(rather than duplicating the logic) is that the destroy graph is often forgotten when changing how plans work, which has led to numerous bugs we can avoid with a single plan codepath.This does not yet fix the un-configured
UpgradeResourceState
calls reported in #30460, but does lay the foundation for being able to make that call. This also provides the necessary infrastructure to be able to address #30140, since that also requires a fully configured provider.Aside from the above mentioned issues, another possibility from this point is to combine the separate refresh step into the destroy plan, removing the special case where we need 2 individual plans to complete a destroy operation. That was purposely left out of consideration for this PR to prevent it from growing too large, and it's not yet clear whether there are any blockers to that being possible.