mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 17:59:05 -05:00
This is just a minimal set of changes to introduce uses of the new states.ResourceInstanceObjectFull to all of the leaf functions related to planning managed and data resource instances. The main goal here was just to prove that we'd reasonably be able to look up objects with the new type in all of the places we'd need to. We're planning some more substantial changes to the planning engine in future commits (e.g. to generate execution graphs instead of traditional plans) and so we'll plumb this in better as part of that work. Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
130 lines
6.0 KiB
Go
130 lines
6.0 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package planning
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/opentofu/opentofu/internal/engine/plugins"
|
|
"github.com/opentofu/opentofu/internal/lang/eval"
|
|
"github.com/opentofu/opentofu/internal/lang/grapheval"
|
|
"github.com/opentofu/opentofu/internal/plans"
|
|
"github.com/opentofu/opentofu/internal/states"
|
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
|
)
|
|
|
|
// PlanChanges is the main entry point, taking a state snapshot from the end
|
|
// of the previous plan/apply round and an instantiated configuration (bound
|
|
// to some input variable definitions) and returning a plan containing a set of
|
|
// proposed actions.
|
|
//
|
|
// This is currently really just a placeholder to demonstrate the role that the
|
|
// functionality in lang/eval might play in a planning process and what other
|
|
// work would need to happen in a planning engine that is beyond the scope
|
|
// of lang/eval. For now then it's just using our existing models of state
|
|
// and plan, but our larger ambitions also involve some other changes that
|
|
// would likely cause the signature here to change significantly:
|
|
//
|
|
// - We're considering changing the apply phase implementation to just be a
|
|
// walk of an execution graph calculated during the planning phase, which
|
|
// implies that the "plan" model would need to change significantly to
|
|
// be able to directly represent that graph, whereas the current model only
|
|
// _implies_ that graph at a high level while expecting the apply phase
|
|
// itself to construct the finalized graph.
|
|
// - We're considering switching from a "state snapshot" model to a more
|
|
// granular model where we request individual objects from the state storage
|
|
// as needed, in which case we'd likely change our usage pattern so that
|
|
// the planning phase is able to create a "provider-like" live object that
|
|
// offers an API for fetching items from the state as needed, rather than
|
|
// the current pure-data snapshot representation.
|
|
//
|
|
// Therefore readers of this code should focus mainly on the inner
|
|
// implementation of how it decides what to plan and how to plan it, and less
|
|
// on where it gets the information to make those decisions and how it
|
|
// represents those decisions in its return value.
|
|
func PlanChanges(ctx context.Context, prevRoundState *states.State, configInst *eval.ConfigInstance, providers plugins.Providers) (*plans.Plan, tfdiags.Diagnostics) {
|
|
var diags tfdiags.Diagnostics
|
|
|
|
planCtx := newPlanContext(configInst.EvalContext(), prevRoundState, providers)
|
|
|
|
// This configInst.DrivePlanning call blocks until the evaluator has
|
|
// visited all expressions in the configuration and calls
|
|
// [planContext.PlanDesiredResourceInstance] on the [planGlue] object for
|
|
// each resource instance it discovers so that we can produce a planned
|
|
// action and result value for each one.
|
|
//
|
|
// It also calls the various "Plan*Orphans" methods at different levels
|
|
// of granularity once it's determined the full set of objects under
|
|
// a given prefix, which planGlue uses to notice when there are
|
|
// prevRoundState resource instances that are no longer in the desired
|
|
// state and so plan to delete or forget them.
|
|
evalResult, moreDiags := configInst.DrivePlanning(ctx, func(oracle *eval.PlanningOracle) eval.PlanGlue {
|
|
return &planGlue{
|
|
planCtx: planCtx,
|
|
oracle: oracle,
|
|
}
|
|
})
|
|
diags = diags.Append(moreDiags)
|
|
if moreDiags.HasErrors() {
|
|
// If we encountered errors during the eval-based phase then we'll halt
|
|
// here but we'll still produce a best-effort [plans.Plan] describing
|
|
// the situation because that often gives useful information for debugging
|
|
// what caused the errors.
|
|
plan := planCtx.Close()
|
|
plan.Errored = true
|
|
return plan, diags
|
|
}
|
|
if evalResult == nil {
|
|
// This should not happen: we should always have an evalResult if
|
|
// there weren't any errors.
|
|
panic(fmt.Sprintf("%T.DrivePlanning returned nil result without any error diagnostics", configInst))
|
|
}
|
|
|
|
// We also need to deal with any "deposed" resource instances that were
|
|
// in the previous round state. We do this separately afterwards because
|
|
// these have no direct representation in the configuration at all and
|
|
// so are not in scope for the config eval system. It's also relatively
|
|
// rare for a previous round state to include deposed instances, since it
|
|
// can happen only if the "delete" leg of a create-before-destroy replace
|
|
// failed in the previous round.
|
|
//
|
|
// The provider instance manager should've planned ahead and arranged for
|
|
// any providers we need for these to still be open, waiting for the
|
|
// completion reports generated by our planning calls in this loop.
|
|
ctx = grapheval.ContextWithNewWorker(ctx)
|
|
planGlue := evalResult.Glue.(*planGlue)
|
|
for _, moduleState := range prevRoundState.Modules {
|
|
for _, resourceState := range moduleState.Resources {
|
|
for instKey, instState := range resourceState.Instances {
|
|
instAddr := resourceState.Addr.Instance(instKey)
|
|
for dk := range instState.Deposed {
|
|
// We currently have a schism where we do all of the
|
|
// discovery work using the traditional state model but
|
|
// we then switch to using our new-style "full" object model
|
|
// to act on what we've discovered. This is hopefully just
|
|
// a temporary situation while we're operating in a mixed
|
|
// world where most of the system doesn't know about the
|
|
// new runtime yet.
|
|
objState := prevRoundState.SyncWrapper().ResourceInstanceObjectFull(instAddr, dk)
|
|
if objState == nil {
|
|
// If we get here then there's a bug in the
|
|
// ResourceInstanceObjectFull function, because we
|
|
// should only be here if instAddr and dk correspond.
|
|
// to an actual deposed object.
|
|
panic(fmt.Sprintf("state has %s deposed object %q, but ResourceInstanceObjectFull didn't return it", instAddr, dk))
|
|
}
|
|
diags = diags.Append(
|
|
planGlue.planDeposedResourceInstanceObject(ctx, instAddr, dk, objState),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return planCtx.Close(), diags
|
|
}
|