engine/planning: Find "orphan" resource instances

The eval system gradually reports the "desired" declarations at various
different levels of granularity, and so the planning engine should compare
that with the instances in the previous run state to notice when any
existing resource instance is no longer in the desired state.

This doesn't yet include a real implementation of planning the deletion of
such resource instances. Real planning behaviors depend on us having some
sort of provider instance and ephemeral resource instance manager to be
able to make requests to configured providers, so that will follow in
subsequent commits before we can implement the actual planning behaviors.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
This commit is contained in:
Martin Atkins
2025-09-15 10:24:27 -07:00
parent dee59fb8ac
commit 6b9cebaf8b
6 changed files with 255 additions and 32 deletions

View File

@@ -61,7 +61,7 @@ type PlanGlue interface {
// different levels of granularity and so the implementation must also
// keep track of all of the orphan resource instances it has already
// detected and handled to avoid generating duplicate planned actions.
PlanResourceInstanceOrphans(ctx context.Context, resourceAddr addrs.AbsResource, desiredInstances iter.Seq[addrs.InstanceKey]) tfdiags.Diagnostics
PlanResourceInstanceOrphans(ctx context.Context, resourceAddr addrs.AbsResource, desiredInstances iter.Seq[addrs.InstanceKey], oracle *PlanningOracle) tfdiags.Diagnostics
// PlanResourceOrphans creates planned actions for any instances of
// resources in the given module instance that that existed in the prior
@@ -71,7 +71,7 @@ type PlanGlue interface {
// with entirely-removed resources instead of removed instances of a
// resource that is still configured. The same caveat about wildcard
// instances applies here too.
PlanResourceOrphans(ctx context.Context, moduleInstAddr addrs.ModuleInstance, desiredResources iter.Seq[addrs.Resource]) tfdiags.Diagnostics
PlanResourceOrphans(ctx context.Context, moduleInstAddr addrs.ModuleInstance, desiredResources iter.Seq[addrs.Resource], oracle *PlanningOracle) tfdiags.Diagnostics
// PlanModuleCallInstanceOrphans creates planned actions for any prior
// state resource instances that belong to instances of the given module
@@ -81,7 +81,7 @@ type PlanGlue interface {
// the removal of an entire module instance containing resource instances
// instead of removal of the resources themselves. The same caveat about
// wildcard instances applies here too.
PlanModuleCallInstanceOrphans(ctx context.Context, moduleCallAddr addrs.AbsModuleCall, desiredInstances iter.Seq[addrs.InstanceKey]) tfdiags.Diagnostics
PlanModuleCallInstanceOrphans(ctx context.Context, moduleCallAddr addrs.AbsModuleCall, desiredInstances iter.Seq[addrs.InstanceKey], oracle *PlanningOracle) tfdiags.Diagnostics
// PlanModuleCallOrphans creates planned actions for any prior state
// resource instances that belong to any module calls within
@@ -91,7 +91,7 @@ type PlanGlue interface {
// with the removal of an entire module call containing resource instances,
// instead of removal of just one dynamic instance of a module call that's
// still declared.
PlanModuleCallOrphans(ctx context.Context, callerModuleInstAddr addrs.ModuleInstance, desiredCalls iter.Seq[addrs.ModuleCall]) tfdiags.Diagnostics
PlanModuleCallOrphans(ctx context.Context, callerModuleInstAddr addrs.ModuleInstance, desiredCalls iter.Seq[addrs.ModuleCall], oracle *PlanningOracle) tfdiags.Diagnostics
}
// DrivePlanning uses this configuration instance to drive forward a planning
@@ -145,7 +145,7 @@ func (c *ConfigInstance) DrivePlanning(ctx context.Context, glue PlanGlue) (*Pla
})
wg.Go(func() {
ctx := grapheval.ContextWithNewWorker(ctx)
orphanDiags = announcePlanOrphans(ctx, glue, rootModuleInstance)
orphanDiags = announcePlanOrphans(ctx, glue, evalGlue.oracle, rootModuleInstance)
})
wg.Wait()
diags = diags.Append(checkDiags)
@@ -212,17 +212,17 @@ func (p *planningEvalGlue) ResourceInstanceValue(ctx context.Context, ri *config
return p.planEngineGlue.PlanDesiredResourceInstance(ctx, desired, p.oracle)
}
func announcePlanOrphans(ctx context.Context, glue PlanGlue, rootModuleInstance evalglue.CompiledModuleInstance) tfdiags.Diagnostics {
func announcePlanOrphans(ctx context.Context, glue PlanGlue, oracle *PlanningOracle, rootModuleInstance evalglue.CompiledModuleInstance) tfdiags.Diagnostics {
var diags collectedDiagnostics
announcePlanOrphansRecursive(ctx, glue, &diags, addrs.RootModuleInstance, rootModuleInstance)
announcePlanOrphansRecursive(ctx, glue, oracle, &diags, addrs.RootModuleInstance, rootModuleInstance)
return diags.diags
}
func announcePlanOrphansRecursive(ctx context.Context, glue PlanGlue, diags *collectedDiagnostics, currentModuleInstAddr addrs.ModuleInstance, currentModuleInstance evalglue.CompiledModuleInstance) {
func announcePlanOrphansRecursive(ctx context.Context, glue PlanGlue, oracle *PlanningOracle, diags *collectedDiagnostics, currentModuleInstAddr addrs.ModuleInstance, currentModuleInstance evalglue.CompiledModuleInstance) {
var wg sync.WaitGroup
// Announce the module calls themselves
diags.Append(
glue.PlanModuleCallOrphans(ctx, currentModuleInstAddr, currentModuleInstance.ChildModuleCalls(ctx)),
glue.PlanModuleCallOrphans(ctx, currentModuleInstAddr, currentModuleInstance.ChildModuleCalls(ctx), oracle),
)
// Announce the instances of each module call and recurse into each one
// to deal with the declarations within it.
@@ -237,17 +237,17 @@ func announcePlanOrphansRecursive(ctx context.Context, glue PlanGlue, diags *col
return
}
}
}),
}, oracle),
)
for callInstAddr, childInst := range currentModuleInstance.ChildModuleInstancesForCall(ctx, callAddr) {
childInstAddr := currentModuleInstAddr.Child(callInstAddr.Call.Name, callInstAddr.Key)
announcePlanOrphansRecursive(ctx, glue, diags, childInstAddr, childInst)
announcePlanOrphansRecursive(ctx, glue, oracle, diags, childInstAddr, childInst)
}
}
})
// Announce the resource declarations themselves
diags.Append(
glue.PlanResourceOrphans(ctx, currentModuleInstAddr, currentModuleInstance.Resources(ctx)),
glue.PlanResourceOrphans(ctx, currentModuleInstAddr, currentModuleInstance.Resources(ctx), oracle),
)
// Announce the instances of each resource
wg.Go(func() {
@@ -261,7 +261,7 @@ func announcePlanOrphansRecursive(ctx context.Context, glue PlanGlue, diags *col
return
}
}
}),
}, oracle),
)
}
})

View File

@@ -346,28 +346,28 @@ func (p *planGlueCallLog) PlanDesiredResourceInstance(ctx context.Context, inst
}
// PlanModuleCallInstanceOrphans implements eval.PlanGlue.
func (p *planGlueCallLog) PlanModuleCallInstanceOrphans(ctx context.Context, moduleCallAddr addrs.AbsModuleCall, desiredInstances iter.Seq[addrs.InstanceKey]) tfdiags.Diagnostics {
func (p *planGlueCallLog) PlanModuleCallInstanceOrphans(ctx context.Context, moduleCallAddr addrs.AbsModuleCall, desiredInstances iter.Seq[addrs.InstanceKey], oracle *eval.PlanningOracle) tfdiags.Diagnostics {
// We don't currently do anything with calls to this method, because
// no tests we've written so far rely on it.
return nil
}
// PlanModuleCallOrphans implements eval.PlanGlue.
func (p *planGlueCallLog) PlanModuleCallOrphans(ctx context.Context, callerModuleInstAddr addrs.ModuleInstance, desiredCalls iter.Seq[addrs.ModuleCall]) tfdiags.Diagnostics {
func (p *planGlueCallLog) PlanModuleCallOrphans(ctx context.Context, callerModuleInstAddr addrs.ModuleInstance, desiredCalls iter.Seq[addrs.ModuleCall], oracle *eval.PlanningOracle) tfdiags.Diagnostics {
// We don't currently do anything with calls to this method, because
// no tests we've written so far rely on it.
return nil
}
// PlanResourceInstanceOrphans implements eval.PlanGlue.
func (p *planGlueCallLog) PlanResourceInstanceOrphans(ctx context.Context, resourceAddr addrs.AbsResource, desiredInstances iter.Seq[addrs.InstanceKey]) tfdiags.Diagnostics {
func (p *planGlueCallLog) PlanResourceInstanceOrphans(ctx context.Context, resourceAddr addrs.AbsResource, desiredInstances iter.Seq[addrs.InstanceKey], oracle *eval.PlanningOracle) tfdiags.Diagnostics {
// We don't currently do anything with calls to this method, because
// no tests we've written so far rely on it.
return nil
}
// PlanResourceOrphans implements eval.PlanGlue.
func (p *planGlueCallLog) PlanResourceOrphans(ctx context.Context, moduleInstAddr addrs.ModuleInstance, desiredResources iter.Seq[addrs.Resource]) tfdiags.Diagnostics {
func (p *planGlueCallLog) PlanResourceOrphans(ctx context.Context, moduleInstAddr addrs.ModuleInstance, desiredResources iter.Seq[addrs.Resource], oracle *eval.PlanningOracle) tfdiags.Diagnostics {
// We don't currently do anything with calls to this method, because
// no tests we've written so far rely on it.
return nil