prune unused nodes from a destroy plan graph

We may need to prune nodes from a full destroy plan graph which cannot
be evaluated if there is no current state.

Add missing method to nodeExpandPlannableResource to ensure planned
resource are handled correctly when pruning nodes.
This commit is contained in:
James Bardin 2022-09-22 10:23:45 -04:00
parent 4153685bfe
commit aedd95a1ee
4 changed files with 58 additions and 0 deletions

View File

@ -3518,3 +3518,44 @@ resource "test_object" "b" {
t.Fatalf("no cycle error found:\n got: %s\n", msg)
}
}
// plan a destroy with no state where configuration could fail to evaluate
// expansion indexes.
func TestContext2Plan_emptyDestroy(t *testing.T) {
m := testModuleInline(t, map[string]string{
"main.tf": `
locals {
enable = true
value = local.enable ? module.example[0].out : null
}
module "example" {
count = local.enable ? 1 : 0
source = "./example"
}
`,
"example/main.tf": `
resource "test_resource" "x" {
}
output "out" {
value = test_resource.x
}
`,
})
p := testProvider("test")
state := states.NewState()
ctx := testContext2(t, &ContextOpts{
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
})
_, diags := ctx.Plan(m, state, &PlanOpts{
Mode: plans.DestroyMode,
})
assertNoErrors(t, diags)
}

View File

@ -170,6 +170,10 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
// TargetsTransformer can determine which nodes to keep in the graph.
&DestroyEdgeTransformer{},
&pruneUnusedNodesTransformer{
skip: b.Operation != walkPlanDestroy,
},
// Target
&TargetsTransformer{Targets: b.Targets},

View File

@ -53,12 +53,16 @@ var (
_ GraphNodeAttachResourceConfig = (*nodeExpandPlannableResource)(nil)
_ GraphNodeAttachDependencies = (*nodeExpandPlannableResource)(nil)
_ GraphNodeTargetable = (*nodeExpandPlannableResource)(nil)
_ graphNodeExpandsInstances = (*nodeExpandPlannableResource)(nil)
)
func (n *nodeExpandPlannableResource) Name() string {
return n.NodeAbstractResource.Name() + " (expand)"
}
func (n *nodeExpandPlannableResource) expandsInstances() {
}
// GraphNodeAttachDependencies
func (n *nodeExpandPlannableResource) AttachDependencies(deps []addrs.ConfigResource) {
n.dependencies = deps

View File

@ -170,9 +170,18 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
// closers also need to disable their use of expansion if the module itself is
// no longer present.
type pruneUnusedNodesTransformer struct {
// The plan graph builder will skip this transformer except during a full
// destroy. Planing normally involves all nodes, but during a destroy plan
// we may need to prune things which are in the configuration but do not
// exist in state to evaluate.
skip bool
}
func (t *pruneUnusedNodesTransformer) Transform(g *Graph) error {
if t.skip {
return nil
}
// We need a reverse depth first walk of modules, processing them in order
// from the leaf modules to the root. This allows us to remove unneeded
// dependencies from child modules, freeing up nodes in the parent module