Files
opentf/internal/engine/planning/plan_resource_instance.go
RoseSecurity 6e2250e050 chore: typo fixes (#3957)
Signed-off-by: RoseSecurity <michael@rosesecurity.dev>
2026-03-27 18:25:56 -03:00

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)
}