destroy locals referenced by root outputs

When planning a destroy operations, locals only referenced by root
outputs do not need to be kept in the graph, because the root output
does not get evaluated. Rather than try and prune the local based on
this condition, we can prevent the connection from being created by
ensuring that a root output destroy node has no references.

The separate plan+apply destroy fields used for outputs can be
simplified by combining, since they are only ever referenced together.
This commit is contained in:
James Bardin 2023-05-22 12:21:30 -04:00
parent 53755180fd
commit 0a921976cd
5 changed files with 62 additions and 26 deletions

View File

@ -4005,6 +4005,48 @@ output "out" {
assertNoErrors(t, diags)
}
func TestContext2Plan_destroyPartialStateLocalRef(t *testing.T) {
m := testModuleInline(t, map[string]string{
"main.tf": `
module "already_destroyed" {
count = 1
source = "./mod"
}
locals {
eval_error = module.already_destroyed[0].out
}
output "already_destroyed" {
value = local.eval_error
}
`,
"./mod/main.tf": `
resource "test_object" "a" {
}
output "out" {
value = test_object.a.test_string
}
`})
p := simpleMockProvider()
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)
}
// Make sure the data sources in the prior state are serializeable even if
// there were an error in the plan.
func TestContext2Plan_dataSourceReadPlanError(t *testing.T) {

View File

@ -99,8 +99,8 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
&ModuleVariableTransformer{Config: b.Config},
&LocalTransformer{Config: b.Config},
&OutputTransformer{
Config: b.Config,
ApplyDestroy: b.Operation == walkDestroy,
Config: b.Config,
Destroying: b.Operation == walkDestroy,
},
// Creates all the resource instances represented in the diff, along

View File

@ -127,7 +127,7 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
&OutputTransformer{
Config: b.Config,
RefreshOnly: b.skipPlanChanges || b.preDestroyRefresh,
PlanDestroy: b.Operation == walkPlanDestroy,
Destroying: b.Operation == walkPlanDestroy,
// NOTE: We currently treat anything built with the plan graph
// builder as "planning" for our purposes here, because we share

View File

@ -23,12 +23,11 @@ import (
// nodeExpandOutput is the placeholder for a non-root module output that has
// not yet had its module path expanded.
type nodeExpandOutput struct {
Addr addrs.OutputValue
Module addrs.Module
Config *configs.Output
PlanDestroy bool
ApplyDestroy bool
RefreshOnly bool
Addr addrs.OutputValue
Module addrs.Module
Config *configs.Output
Destroying bool
RefreshOnly bool
// Planning is set to true when this node is in a graph that was produced
// by the plan graph builder, as opposed to the apply graph builder.
@ -103,13 +102,13 @@ func (n *nodeExpandOutput) DynamicExpand(ctx EvalContext) (*Graph, error) {
var node dag.Vertex
switch {
case module.IsRoot() && (n.PlanDestroy || n.ApplyDestroy):
case module.IsRoot() && n.Destroying:
node = &NodeDestroyableOutput{
Addr: absAddr,
Planning: n.Planning,
}
case n.PlanDestroy:
case n.Destroying:
// nothing is done here for non-root outputs
continue
@ -119,7 +118,7 @@ func (n *nodeExpandOutput) DynamicExpand(ctx EvalContext) (*Graph, error) {
Config: n.Config,
Change: change,
RefreshOnly: n.RefreshOnly,
DestroyApply: n.ApplyDestroy,
DestroyApply: n.Destroying,
Planning: n.Planning,
}
}
@ -186,7 +185,7 @@ func (n *nodeExpandOutput) ReferenceOutside() (selfPath, referencePath addrs.Mod
// GraphNodeReferencer
func (n *nodeExpandOutput) References() []*addrs.Reference {
// DestroyNodes do not reference anything.
if n.Module.IsRoot() && n.ApplyDestroy {
if n.Module.IsRoot() && n.Destroying {
return nil
}

View File

@ -28,12 +28,8 @@ type OutputTransformer struct {
Planning bool
// If this is a planned destroy, root outputs are still in the configuration
// so we need to record that we wish to remove them
PlanDestroy bool
// ApplyDestroy indicates that this is being added to an apply graph, which
// is the result of a destroy plan.
ApplyDestroy bool
// so we need to record that we wish to remove them.
Destroying bool
}
func (t *OutputTransformer) Transform(g *Graph) error {
@ -59,13 +55,12 @@ func (t *OutputTransformer) transform(g *Graph, c *configs.Config) error {
addr := addrs.OutputValue{Name: o.Name}
node := &nodeExpandOutput{
Addr: addr,
Module: c.Path,
Config: o,
PlanDestroy: t.PlanDestroy,
ApplyDestroy: t.ApplyDestroy,
RefreshOnly: t.RefreshOnly,
Planning: t.Planning,
Addr: addr,
Module: c.Path,
Config: o,
Destroying: t.Destroying,
RefreshOnly: t.RefreshOnly,
Planning: t.Planning,
}
log.Printf("[TRACE] OutputTransformer: adding %s as %T", o.Name, node)