mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-02-15 13:00:32 -05:00
The previous commit split the handling of provider instance items into separate dependency-analysis and execgraph construction steps, with the intention of avoiding the need for the execGraphBuilder to directly interact with the planning oracle and thus indirectly with the evaluator. Overall the hope is that execGraphBuilder will be a self-contained object that doesn't depend on anything else in the planning engine so that it's easier to write unit tests for it that don't require creating an entire fake planning context. However, on reflection that change introduced a completely unnecessary extra handoff from the execGraphBuilder to _another part of itself_, which is obviously unnecessary complexity because it doesn't serve to separate any concerns. This is therefore a further simplification that returns to just doing the entire handling of a provider instance's presence in the execution graph only once we've decided that at least one resource instance will definitely use the provider instance during the apply phase. There is still a separation of concerns where the planGlue type is responsible for calculating the provider dependencies and then the execGraphBuilder is only responsible for adding items to the execution graph based on that information. That separation makes sense because planGlue's job is to bridge between the planning engine and the evaluator, and it's the evaluator's job to calculate the dependencies for a provider instance, whereas execGraphBuilder is the component responsible for deciding exactly which low-level execgraph operations we'll use to describe the situation to the apply engine. Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
103 lines
4.5 KiB
Go
103 lines
4.5 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 (
|
|
"sync"
|
|
|
|
"github.com/opentofu/opentofu/internal/addrs"
|
|
"github.com/opentofu/opentofu/internal/engine/internal/exec"
|
|
"github.com/opentofu/opentofu/internal/engine/internal/execgraph"
|
|
)
|
|
|
|
// execGraphBuilder is a higher-level wrapper around [execgraph.Builder] that
|
|
// is tailored to the needs of the planning engine.
|
|
//
|
|
// Specifically:
|
|
// - Its exported methods that add to or modify the graph are all
|
|
// concurrency-safe, for convenient use during the concurrent planning work
|
|
// driven by the evaluator.
|
|
// - It keeps track of certain "singleton" collections of graph nodes that
|
|
// different parts of the planning engine all need to agree on for the
|
|
// execution graph to be correct, such as ensuring there's only one open
|
|
// and one close operation per distinct provider instance address.
|
|
// - Many of its methods can potentially add multiple operations to the graph
|
|
// at once, to let the planning engine work at a higher level of abstraction
|
|
// than just the individual raw operation types. The lower-level
|
|
// [execgraph.Builder] instead directly matches the abstraction level of
|
|
// [execgraph.Operations].
|
|
type execGraphBuilder struct {
|
|
// mu must be locked while accessing any of the other fields.
|
|
mu sync.Mutex
|
|
|
|
// lower is the lower-level graph builder that this utility is built in
|
|
// terms of.
|
|
lower *execgraph.Builder
|
|
|
|
// During construction we treat certain items as singletons so that
|
|
// we can do the associated work only once while providing it to
|
|
// multiple callers, and so these maps track those singletons but
|
|
// we throw these away after building is complete because the graph
|
|
// becomes immutable at that point.
|
|
resourceInstAddrRefs addrs.Map[addrs.AbsResourceInstance, execgraph.ResultRef[addrs.AbsResourceInstance]]
|
|
providerInstAddrRefs addrs.Map[addrs.AbsProviderInstanceCorrect, execgraph.ResultRef[addrs.AbsProviderInstanceCorrect]]
|
|
openProviderRefs addrs.Map[addrs.AbsProviderInstanceCorrect, execResultWithCloseBlockers[*exec.ProviderClient]]
|
|
openEphemeralRefs addrs.Map[addrs.AbsResourceInstance, registerExecCloseBlockerFunc]
|
|
}
|
|
|
|
// NOTE: There are additional methods for [execGraphBuilder] declared in
|
|
// the other files named execgraph_*.go , grouped by what kinds of objects they
|
|
// primarily work with.
|
|
|
|
func newExecGraphBuilder() *execGraphBuilder {
|
|
return &execGraphBuilder{
|
|
lower: execgraph.NewBuilder(),
|
|
resourceInstAddrRefs: addrs.MakeMap[addrs.AbsResourceInstance, execgraph.ResultRef[addrs.AbsResourceInstance]](),
|
|
providerInstAddrRefs: addrs.MakeMap[addrs.AbsProviderInstanceCorrect, execgraph.ResultRef[addrs.AbsProviderInstanceCorrect]](),
|
|
openProviderRefs: addrs.MakeMap[addrs.AbsProviderInstanceCorrect, execResultWithCloseBlockers[*exec.ProviderClient]](),
|
|
openEphemeralRefs: addrs.MakeMap[addrs.AbsResourceInstance, registerExecCloseBlockerFunc](),
|
|
}
|
|
}
|
|
|
|
// Finish returns the graph that has been built, which is then immutable.
|
|
//
|
|
// After calling this function the execGraphBuilder is invalid and must not be
|
|
// used anymore.
|
|
func (b *execGraphBuilder) Finish() *execgraph.Graph {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
return b.lower.Finish()
|
|
}
|
|
|
|
// execResultWithCloseBlockers associates a result for some object type that
|
|
// needs to be closed once other operations have finished using it with the
|
|
// operation that performs that close operation.
|
|
type execResultWithCloseBlockers[T any] struct {
|
|
Result execgraph.ResultRef[T]
|
|
CloseBlockerFunc registerExecCloseBlockerFunc
|
|
CloseBlockerResult execgraph.AnyResultRef
|
|
}
|
|
|
|
// registerExecCloseBlockerFunc is the signature of a function that adds a given
|
|
// result references as a blocker for something to be "closed".
|
|
//
|
|
// Exactly what means to be a "close blocker" depends on context. Refer to the
|
|
// documentation of whatever function is returning a value of this type.
|
|
type registerExecCloseBlockerFunc func(execgraph.AnyResultRef)
|
|
|
|
// makeCloseBlocker is a helper used by [execGraphBuilder] methods that produce
|
|
// open/close node pairs.
|
|
//
|
|
// Callers MUST hold a lock on b.mu throughout any call to this method, AND
|
|
// when calling the returned registerExecCloseBlockerFunc.
|
|
func (b *execGraphBuilder) makeCloseBlocker() (execgraph.AnyResultRef, registerExecCloseBlockerFunc) {
|
|
waiter, lowerRegister := b.lower.MutableWaiter()
|
|
registerFunc := registerExecCloseBlockerFunc(func(ref execgraph.AnyResultRef) {
|
|
lowerRegister(ref)
|
|
})
|
|
return waiter, registerFunc
|
|
}
|