mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-03-22 10:01:39 -04:00
As I started writing analysis code based on this intermediate representation it ended up having all of the same challenges that the traditional runtime's "dag" model has where it was repeatedly materializing copies of data structures just to iterate over them, because it isn't reasonable to return an iter.Seq or iter.Seq2 over a map that's guarded by a mutex. Therefore this now adopts a similar pattern to what we've been following elsewhere: a wrapper type that exists only during the construction phase, using a mutex to allow concurrent calls, but then after construction the object is "frozen" in an immutable form where we no longer need to hold mutexes to interact with it. and so we can safely return iterable sequences over the internal maps. Future commits will use these new iter.Seq-based accessors to access the relationships that a resourceInstanceObjects object encapsulates _without_ creating direct copies of that information and without holding any mutexes. Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
108 lines
3.7 KiB
Go
108 lines
3.7 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"
|
|
"slices"
|
|
"sync"
|
|
|
|
"github.com/opentofu/opentofu/internal/addrs"
|
|
"github.com/opentofu/opentofu/internal/engine/plugins"
|
|
"github.com/opentofu/opentofu/internal/lang/eval"
|
|
"github.com/opentofu/opentofu/internal/states"
|
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
|
)
|
|
|
|
// planContext is our shared state for the various parts of a single call
|
|
// to [PlanChanges], and is mainly used as part of our [eval.PlanGlue]
|
|
// implementation [planGlue], through which the evaluator calls us to ask for
|
|
// planning results.
|
|
type planContext struct {
|
|
evalCtx *eval.EvalContext
|
|
|
|
// resourceInstObjs is where we gradually construct our intermediate
|
|
// representation of the graph of resource instance objects.
|
|
//
|
|
// This gets modified by methods of [planGlue] gradually as we learn of
|
|
// new resource instance objects. Use [planContext.Close] after the
|
|
// work is complete to obtain the finalized object.
|
|
resourceInstObjs *resourceInstanceObjectsBuilder
|
|
|
|
// TODO: The following should probably track a reason why each resource
|
|
// instance was deferred, but since deferral is not the focus of this
|
|
// current experiment we'll just keep this boolean for now.
|
|
deferred addrs.Map[addrs.AbsResourceInstance, struct{}]
|
|
|
|
// prevRoundState MUST be treated as immutable
|
|
prevRoundState *states.State
|
|
|
|
// refreshedState is where we record the results of refreshing
|
|
// resource instances as we visit them. This starts as a deep copy
|
|
// of prevRoundState.
|
|
refreshedState *states.SyncState
|
|
|
|
providers plugins.Providers
|
|
|
|
providerInstances *providerInstances
|
|
|
|
// Stack of ephemeral and provider close functions
|
|
// Given the current state of the planning engine, we wait until
|
|
// the end of the run to close all of the "opened" items. We
|
|
// also need to close them in a specific order to prevent dependency
|
|
// conflicts. We posit that for plan, closing in the reverse order of opens
|
|
// will ensure that this order is correctly preserved.
|
|
closeStackMu sync.Mutex
|
|
closeStack []func(context.Context) tfdiags.Diagnostics
|
|
}
|
|
|
|
func newPlanContext(evalCtx *eval.EvalContext, prevRoundState *states.State, providers plugins.Providers) *planContext {
|
|
if prevRoundState == nil {
|
|
prevRoundState = states.NewState()
|
|
}
|
|
refreshedState := prevRoundState.DeepCopy()
|
|
|
|
return &planContext{
|
|
evalCtx: evalCtx,
|
|
resourceInstObjs: newResourceInstanceObjectsBuilder(),
|
|
prevRoundState: prevRoundState,
|
|
refreshedState: refreshedState.SyncWrapper(),
|
|
providerInstances: newProviderInstances(),
|
|
providers: providers,
|
|
}
|
|
}
|
|
|
|
// Close marks the end of the use of the [planContext] object, returning a
|
|
// [plans.Plan] representation of the plan that was created.
|
|
//
|
|
// After calling this function the [planContext] object is invalid and must
|
|
// not be used anymore.
|
|
func (p *planContext) Close(ctx context.Context) (*planContextResult, tfdiags.Diagnostics) {
|
|
var diags tfdiags.Diagnostics
|
|
|
|
p.closeStackMu.Lock()
|
|
slices.Reverse(p.closeStack)
|
|
for _, closer := range p.closeStack {
|
|
diags = diags.Append(closer(ctx))
|
|
}
|
|
p.closeStackMu.Unlock()
|
|
|
|
return &planContextResult{
|
|
ResourceInstanceObjects: p.resourceInstObjs.Close(),
|
|
PrevRoundState: p.prevRoundState,
|
|
RefreshedState: p.refreshedState.Close(),
|
|
}, diags
|
|
}
|
|
|
|
// planContextResult collects together the intermediate results produced by
|
|
// [planContext], ready to be used by the next pass of the planning engine
|
|
// to produce the finalized changes and execution graph.
|
|
type planContextResult struct {
|
|
ResourceInstanceObjects *resourceInstanceObjects
|
|
PrevRoundState *states.State
|
|
RefreshedState *states.State
|
|
}
|