mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-05-16 07:01:54 -04:00
398 lines
18 KiB
Go
398 lines
18 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 (
|
|
"fmt"
|
|
"iter"
|
|
"slices"
|
|
"sync"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/opentofu/opentofu/internal/addrs"
|
|
"github.com/opentofu/opentofu/internal/plans"
|
|
)
|
|
|
|
// resourceInstanceObject is the planning engine's internal intermediate
|
|
// representation of a resource instance object that's participating in the
|
|
// planning process.
|
|
//
|
|
// Objects of this type form an intermediate "resource instance graph" that
|
|
// gathers together all of the information needed to produce the final
|
|
// "execution graph" that describes what ought to happen during a subsequent
|
|
// apply phase.
|
|
type resourceInstanceObject struct {
|
|
// Addr is the unique address of this object.
|
|
//
|
|
// If an object's address is expected to change during the apply phase,
|
|
// such as if it's subject to "moved" blocks, then this returns the expected
|
|
// final address after the changes have been made. The address from the
|
|
// prior state is recorded in the PlannedChange object.
|
|
//
|
|
// If the planned change has action [plans.CreateThenDelete] then the
|
|
// "old" object will be turned into a deposed object for the part of the
|
|
// operation where both new and old object need to exist concurrently. The
|
|
// deposed key for that temporary object is only calculated just in time
|
|
// during the apply phase and so is not recorded anywhere in
|
|
// [resourceInstanceObject].
|
|
Addr addrs.AbsResourceInstanceObject
|
|
|
|
// PlannedChange is the change that the planning engine has decided for
|
|
// this resource instance object.
|
|
//
|
|
// If no actual change is needed then this object should not be present,
|
|
// and the effective result value should be placed in the PlaceholderValue
|
|
// field. Objects without a planned change are included in the execution
|
|
// graph only if a planned change for another object refers to them, and
|
|
// in that case only as a read from the prior state, and so PlaceholderValue
|
|
// should represent what would be read from the prior state during apply.
|
|
//
|
|
// If the planned action is to "replace" (for managed resource instance
|
|
// objects) and the ReplaceOrder field is set to [replaceAnyOrder] then
|
|
// the planned action can be set to either [plans.DestroyThenCreate] or
|
|
// [plans.CreateThenDestroy] and then will be overridden once the
|
|
// replace order has been finalized in subsequent analysis. If ReplaceOrder
|
|
// is set to a specific value then the planned change's action must
|
|
// immediately agree with the selected order.
|
|
// TODO: As we consider adjusting the plan model to better suit this new
|
|
// runtime, hopefully we can have the plan action initially set to just
|
|
// a generic "replace" and then let ReplaceOrder alone specify which order
|
|
// to use, so that we can avoid this redundancy.
|
|
PlannedChange *plans.ResourceInstanceChange
|
|
|
|
// PlaceholderValue is the value that should be used to satisfy references
|
|
// to this object in expressions elsewhere in the configuration when
|
|
// the PlannedChange field contains a nil change.
|
|
//
|
|
// This MUST Be set to a valid value unless the PlannedChange field is
|
|
// populated. If PlannedChange is populated then this field is completely
|
|
// ignored.
|
|
//
|
|
// The placeholder value is a conservative approximation of what we know
|
|
// should definitely match a hypothetical successful plan for this object,
|
|
// so that downstream references can still be typechecked even when
|
|
// their upstreams could not complete planning. This allows us to give as
|
|
// much information as possible about an invalid configuration, but that
|
|
// relies on the placeholder having unknown values in any position where
|
|
// we cannot predict the final result. If we don't know anything at all
|
|
// about the final result then it's valid to use [cty.DynamicVal] here.
|
|
PlaceholderValue cty.Value
|
|
|
|
// Provider is the address of the provider the resource type of this
|
|
// object belongs to, and thus the provider whose schema we must use
|
|
// when interpreting the old, new, or placeholder values for this object.
|
|
//
|
|
// This must always be populated, even if we don't know exactly which
|
|
// instance of this provider is responsible for it. If PlannedChange is
|
|
// also populated then the provider instance recorded as being responsible
|
|
// for the change must belong to the same provider recorded here.
|
|
Provider addrs.Provider
|
|
|
|
// ProviderInst is a substitute for the ProviderAddr field of the object
|
|
// in the PlannedChange field, compensating for the fact that our old plan
|
|
// models use an incorrect and incomplete model of provider instance
|
|
// addresses.
|
|
//
|
|
// This must be valid whenever PlannedChange is non-nil. It's invalid and
|
|
// ignored otherwise, because conceptually this is a field of PlannedChange
|
|
// that's out here only so we can delay updating the plan model that the
|
|
// traditional runtime is also relying on.
|
|
//
|
|
// TODO: Remove this once our plan model is updated to use the "correct"
|
|
// representation of provider instance addresses.
|
|
ProviderInst addrs.AbsProviderInstanceCorrect
|
|
|
|
// ReplaceOrder specifies what order the create and destroy steps of a
|
|
// "replace" must happen in for this object.
|
|
//
|
|
// When returning from one of the planning functions on [planGlue] this
|
|
// should focus on describing only the configured constraints of the
|
|
// specific object in question, using [replaceAnyOrder] if there is no
|
|
// constraint.
|
|
//
|
|
// Subsequent processing elsewhere in the planning engine will decide
|
|
// a final effective constraint to replace any [replaceAnyOrder]
|
|
// constraints, by analyzing the dependency flow between objects.
|
|
//
|
|
// Currently the planning engine is allowed to return only either
|
|
// [replaceAnyOrder] or [replaceCreateThenDestroy] in this field.
|
|
// [replaceDestroyThenCreate] is then inferred automatically for any
|
|
// object that isn't forced to be [replaceCreateThenDestroy] by one of
|
|
// its dependency neighbors.
|
|
ReplaceOrder resourceInstanceReplaceOrder
|
|
|
|
// Dependencies is the set of all resource instance objects that this
|
|
// object's resource instance depends on either directly or indirectly.
|
|
//
|
|
// Note that this describes the dependencies between the resource instance
|
|
// objects as declared or implied in configuration, NOT the ordering
|
|
// requirements of the specific change in the PlannedChange field. In
|
|
// particular, if the planned action is [plans.Delete] then this field must
|
|
// still record the normal dependencies of the resource instances whose
|
|
// object is being destroyed and NOT the "inverted" dependencies that would
|
|
// be reflected in the final execution graph.
|
|
//
|
|
// For orphan or deposed objects which therefore appear only in state and
|
|
// have no current configured dependencies, this should describe all of
|
|
// the resource instance objects in the prior state that the state object
|
|
// was recorded as depending on, instead of dependencies detected through
|
|
// the configuration. We assume that the dependencies recorded in the state
|
|
// match what was declared in an earlier version of the configuration.
|
|
Dependencies addrs.Set[addrs.AbsResourceInstanceObject]
|
|
}
|
|
|
|
// ResultValue returns the value that should be sent to the evaluator for
|
|
// use in resolving downstream expressions that refer to this resource instance
|
|
// object.
|
|
//
|
|
// If the PlannedChange field is populated then this returns its "After" value.
|
|
//
|
|
// Otherwise, this returns PlaceholderValue so that downstream planning can
|
|
// potentially still proceed based on partial information.
|
|
func (rio *resourceInstanceObject) ResultValue() cty.Value {
|
|
if rio.PlannedChange != nil {
|
|
return rio.PlannedChange.After
|
|
}
|
|
if rio.PlaceholderValue != cty.NilVal {
|
|
return rio.PlaceholderValue
|
|
}
|
|
// We should not get here for correctly-constructed objects, but for
|
|
// robustness we'll use cty.DynamicVal as the ultimate placeholder.
|
|
return cty.DynamicVal
|
|
}
|
|
|
|
// resourceInstanceObjects is conceptually a map from
|
|
// [addrs.AbsResourceInstanceObject] to [*resourceInstanceObject], but
|
|
// it supports concurrent writes and also allows querying the dependency
|
|
// relationships between objects in both directions.
|
|
//
|
|
// Collections of this type and everything inside them should be treated as
|
|
// immutable. Use [newResourceInstanceObjectsBuilder] to obtain a temporary
|
|
// object for constructing a new objects collection, and then call
|
|
// [resourceInstanceObjectsBuilder.Close] to derive the final immutable
|
|
// collection from it.
|
|
type resourceInstanceObjects struct {
|
|
// objects are the resource instance objects that have been added so far.
|
|
objects addrs.Map[addrs.AbsResourceInstanceObject, *resourceInstanceObject]
|
|
|
|
// reverseDeps describes the same relationships as in
|
|
// [resourceInstanceObject.Dependencies] but viewed from the opposite
|
|
// direction: the map keys are dependencies of the objects in the map
|
|
// values.
|
|
//
|
|
// We maintain this so that we can efficiently traverse the graph in both
|
|
// directions when performing further analysis.
|
|
reverseDeps addrs.Map[addrs.AbsResourceInstanceObject, addrs.Set[addrs.AbsResourceInstanceObject]]
|
|
|
|
// providerInstDeps describes the resource instances that each provider
|
|
// instance depends on, which are therefore effectively additional indirect
|
|
// dependencies for any resource instance that belongs to a given
|
|
// provider instance and must be represented in the final execution graph.
|
|
providerInstDeps addrs.Map[addrs.AbsProviderInstanceCorrect, addrs.Set[addrs.AbsResourceInstance]]
|
|
}
|
|
|
|
// Get returns the resource instance object with the given address, or nil if
|
|
// no such object has been added.
|
|
//
|
|
// The caller must not mutate anything accessible through the returned pointer.
|
|
func (rios *resourceInstanceObjects) Get(addr addrs.AbsResourceInstanceObject) *resourceInstanceObject {
|
|
return rios.objects.Get(addr)
|
|
}
|
|
|
|
// AllAddrs returns a sequence over all of the resource instance objects known
|
|
// to this collection.
|
|
func (rios *resourceInstanceObjects) All() iter.Seq2[addrs.AbsResourceInstanceObject, *resourceInstanceObject] {
|
|
return func(yield func(addrs.AbsResourceInstanceObject, *resourceInstanceObject) bool) {
|
|
for _, elem := range rios.objects.Elems {
|
|
if !yield(elem.Key, elem.Value) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Dependencies returns the addresses of all resource instance objects that the
|
|
// resource instance object of the given address depends on.
|
|
//
|
|
// The caller must not modify any object reachable through the results.
|
|
func (rios *resourceInstanceObjects) Dependencies(of addrs.AbsResourceInstanceObject) iter.Seq[addrs.AbsResourceInstanceObject] {
|
|
obj := rios.objects.Get(of)
|
|
if obj == nil {
|
|
return func(yield func(addrs.AbsResourceInstanceObject) bool) {}
|
|
}
|
|
return obj.Dependencies.All()
|
|
}
|
|
|
|
// Dependents returns the addresses of all resource instance objects that have
|
|
// the resource instance object with the given address as one of their
|
|
// dependencies. In other words, this queries the dependencies "backwards".
|
|
//
|
|
// The caller must not modify any object reachable through the results.
|
|
//
|
|
// Note that not all of the returned addresses necessarily match an object
|
|
// that can be retrieved using [resourceInstanceObjects.Get]. The collection
|
|
// of resource instance objects is populated in a "forward dependency" order,
|
|
// and so dependencies are added before their dependents and the dependents
|
|
// might not be added at all if the planning process failed partway through.
|
|
func (rios *resourceInstanceObjects) Dependendents(of addrs.AbsResourceInstanceObject) iter.Seq[addrs.AbsResourceInstanceObject] {
|
|
return rios.reverseDeps.Get(of).All()
|
|
}
|
|
|
|
// DependenciesAndDependents is a convenience helper that concatenates together
|
|
// the results of both [resourceInstanceObjects.Dependencies] and
|
|
// [resourceInstanceObjects.Dependents] into a single flat sequence. It does
|
|
// not transform those sequences in any other way.
|
|
func (rios *resourceInstanceObjects) DependenciesAndDependents(of addrs.AbsResourceInstanceObject) iter.Seq[addrs.AbsResourceInstanceObject] {
|
|
return func(yield func(addrs.AbsResourceInstanceObject) bool) {
|
|
for addr := range rios.Dependencies(of) {
|
|
if !yield(addr) {
|
|
return
|
|
}
|
|
}
|
|
for addr := range rios.Dependendents(of) {
|
|
if !yield(addr) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ProviderInstanceDependencies returns the addresses of all resource instance
|
|
// objects that the given provider instance depends on to be configured.
|
|
//
|
|
// The caller must not modify any object reachable through the results.
|
|
func (rios *resourceInstanceObjects) ProviderInstanceDependencies(of addrs.AbsProviderInstanceCorrect) iter.Seq[addrs.AbsResourceInstanceObject] {
|
|
// We internally track these as relationships to resource instances rather
|
|
// than resource instance objects because provider instances are not allowed
|
|
// to depend on deposed objects, but for consistency with the other methods
|
|
// of this type we'll translate them into resource instance object addresses
|
|
// referring to the current object of each instance as we stream them out.
|
|
return func(yield func(addrs.AbsResourceInstanceObject) bool) {
|
|
for _, instAddr := range rios.providerInstDeps.Get(of) {
|
|
if !yield(instAddr.CurrentObject()) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// resourceInstanceObjectsBuilder is a wrapper around a
|
|
// [resourceInstanceObjects] that allows new objects to be inserted in a
|
|
// concurrency-safe way.
|
|
type resourceInstanceObjectsBuilder struct {
|
|
mu sync.Mutex
|
|
result *resourceInstanceObjects
|
|
}
|
|
|
|
func newResourceInstanceObjectsBuilder() *resourceInstanceObjectsBuilder {
|
|
return &resourceInstanceObjectsBuilder{
|
|
result: &resourceInstanceObjects{
|
|
objects: addrs.MakeMap[addrs.AbsResourceInstanceObject, *resourceInstanceObject](),
|
|
reverseDeps: addrs.MakeMap[addrs.AbsResourceInstanceObject, addrs.Set[addrs.AbsResourceInstanceObject]](),
|
|
providerInstDeps: addrs.MakeMap[addrs.AbsProviderInstanceCorrect, addrs.Set[addrs.AbsResourceInstance]](),
|
|
},
|
|
}
|
|
}
|
|
|
|
// Put inserts the given resource instance object into the collection.
|
|
//
|
|
// Resource instance objects are uniquely identified by the addresses
|
|
// in [resourceInstanceObject.Addr]. Attempting to add an object with the same
|
|
// address as a previously-added object causes a panic, because it suggests a
|
|
// bug in the caller.
|
|
//
|
|
// A [resourceInstanceObject] value must not be modified once it has been passed
|
|
// to this method.
|
|
func (b *resourceInstanceObjectsBuilder) Put(obj *resourceInstanceObject) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
if b.result.objects.Has(obj.Addr) {
|
|
panic(fmt.Sprintf("%s is already tracked in this resourceInstanceObjects collection", obj.Addr))
|
|
}
|
|
b.result.objects.Put(obj.Addr, obj)
|
|
|
|
// We also update the reverseDeps structure here to ensure that our
|
|
// records of the graph edges are always consistent across both directions.
|
|
for depAddr := range obj.Dependencies.All() {
|
|
if !b.result.reverseDeps.Has(depAddr) {
|
|
b.result.reverseDeps.Put(depAddr, addrs.MakeSet[addrs.AbsResourceInstanceObject]())
|
|
}
|
|
b.result.reverseDeps.Get(depAddr).Add(obj.Addr)
|
|
}
|
|
}
|
|
|
|
// PutProviderInstanceDependencies records the set of resource instance objects
|
|
// that the given provider instance depends on in order to be configured.
|
|
//
|
|
// This takes a set of provider instance addresses rather than provider instance
|
|
// _object_ addresses because it's never valid for a provider instance to
|
|
// depend on deposed resource instance objects.
|
|
//
|
|
// This function panics if there have been multiple calls for the same provider
|
|
// instance. The caller must not modify the given set or anything reachable
|
|
// through it after calling this function, because it becomes part of the
|
|
// internal state of the receiver.
|
|
func (b *resourceInstanceObjectsBuilder) PutProviderInstanceDependencies(addr addrs.AbsProviderInstanceCorrect, deps addrs.Set[addrs.AbsResourceInstance]) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
if b.result.providerInstDeps.Has(addr) {
|
|
panic(fmt.Sprintf("%s dependencies already tracked in this resourceInstanceObjects collection", addr))
|
|
}
|
|
b.result.providerInstDeps.Put(addr, deps)
|
|
|
|
// We don't retain reverse dependencies for provider instances, because
|
|
// they are never "destroyed" and so backward edges are never needed.
|
|
}
|
|
|
|
func (b *resourceInstanceObjectsBuilder) Close() *resourceInstanceObjects {
|
|
b.mu.Lock()
|
|
ret := b.result
|
|
b.result = nil // this builder can't be used anymore
|
|
b.mu.Unlock()
|
|
return ret
|
|
}
|
|
|
|
// sortedResourceInstanceObjectAddrs consumes all items from the given sequence
|
|
// and then sorts them into an unspecified but deterministic order.
|
|
func sortedResourceInstanceObjectAddrs(seq iter.Seq[addrs.AbsResourceInstanceObject]) []addrs.AbsResourceInstanceObject {
|
|
sorted := slices.Collect(seq)
|
|
slices.SortFunc(sorted, func(a, b addrs.AbsResourceInstanceObject) int {
|
|
// The types in package addrs were written in a time when
|
|
// sort.Interface was idiomatic and therefore have "Less" rather
|
|
// than "Equal", and so we'll adapt that to the modern Compare
|
|
// interface for now, which unfortunately means we need two
|
|
// comparisons.
|
|
// TODO: Add Compare-style methods to the address types that already
|
|
// implement Less, reimplement their Less methods in terms of compare,
|
|
// and then change this to just directly use
|
|
// addrs.AbsResourceInstanceObject.Compare .
|
|
if a.Less(b) {
|
|
return -1
|
|
}
|
|
if b.Less(a) {
|
|
return 1
|
|
}
|
|
return 0
|
|
})
|
|
return sorted
|
|
}
|
|
|
|
// sortedResourceInstanceObjectAddrs consumes all items from the given sequence
|
|
// and then sorts them into an unspecified but deterministic order.
|
|
func sortedResourceInstanceObjectAddrKeys[T any](seq iter.Seq2[addrs.AbsResourceInstanceObject, T]) []addrs.AbsResourceInstanceObject {
|
|
keys := func(yield func(addrs.AbsResourceInstanceObject) bool) {
|
|
for k := range seq {
|
|
if !yield(k) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
return sortedResourceInstanceObjectAddrs(keys)
|
|
}
|