| // 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 | |
| // ConfigDependencies 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. | |
| // | |
| // This should be empty for orphan or deposed objects which therefore appear | |
| // only in state and have no current configured dependencies. State-based | |
| // dependencies appear in StateDependencies instead. | |
| ConfigDependencies addrs.Set[addrs.AbsResourceInstanceObject] | |
| // StateDependencies is similar to | |
| // [resourceInstanceObject.ConfigDependencies] but describes dependencies | |
| // take from the previous round state instead of from the configuration. | |
| // We assume that dependencies recorded here were based on configuration | |
| // dependencies from the previous round. | |
| // | |
| // This should be empty for "create" because there is no previous round | |
| // state in that case. | |
| StateDependencies 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 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] | |
| // reverseConfigDeps describes the same relationships as in | |
| // [resourceInstanceObject.ConfigDependencies] 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. | |
| reverseConfigDeps addrs.Map[addrs.AbsResourceInstanceObject, addrs.Set[addrs.AbsResourceInstanceObject]] | |
| // reverseConfigDeps describes the same relationships as in | |
| // [resourceInstanceObject.StateDependencies] 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. | |
| reverseStateDeps addrs.Map[addrs.AbsResourceInstanceObject, addrs.Set[addrs.AbsResourceInstanceObject]] | |
| } | |
| // 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 | |
| } | |
| } | |
| } | |
| } | |
| // ConfigDependencies returns the addresses of all resource instance objects | |
| // that the configuration of the resource instance object at the given address | |
| // depends on. | |
| // | |
| // The caller must not modify any object reachable through the results. | |
| func (rios *resourceInstanceObjects) ConfigDependencies(of addrs.AbsResourceInstanceObject) iter.Seq[addrs.AbsResourceInstanceObject] { | |
| obj := rios.objects.Get(of) | |
| if obj == nil { | |
| return func(yield func(addrs.AbsResourceInstanceObject) bool) {} | |
| } | |
| return obj.ConfigDependencies.All() | |
| } | |
| // StateDependencies returns the addresses of all resource instance objects that | |
| // the previous round state for the resource instance object of the given | |
| // address depends on. | |
| // | |
| // The caller must not modify any object reachable through the results. | |
| func (rios *resourceInstanceObjects) StateDependencies(of addrs.AbsResourceInstanceObject) iter.Seq[addrs.AbsResourceInstanceObject] { | |
| obj := rios.objects.Get(of) | |
| if obj == nil { | |
| return func(yield func(addrs.AbsResourceInstanceObject) bool) {} | |
| } | |
| return obj.StateDependencies.All() | |
| } | |
| // ConfigDependents returns the addresses of all resource instance objects that | |
| // have the resource instance object with the given address as one of their | |
| // config dependencies. In other words, this queries the config 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) ConfigDependents(of addrs.AbsResourceInstanceObject) iter.Seq[addrs.AbsResourceInstanceObject] { | |
| return rios.reverseConfigDeps.Get(of).All() | |
| } | |
| // StateDependents returns the addresses of all resource instance objects that | |
| // have the resource instance object with the given address as one of their | |
| // state dependencies. In other words, this queries the state 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) StateDependents(of addrs.AbsResourceInstanceObject) iter.Seq[addrs.AbsResourceInstanceObject] { | |
| return rios.reverseStateDeps.Get(of).All() | |
| } | |
| // ConfigDependenciesAndDependents is a convenience helper that concatenates | |
| // together the results of both [resourceInstanceObjects.ConfigDependencies] and | |
| // [resourceInstanceObjects.ConfigDependents] into a single flat sequence. It does | |
| // not transform those sequences in any other way. | |
| func (rios *resourceInstanceObjects) ConfigDependenciesAndDependents(of addrs.AbsResourceInstanceObject) iter.Seq[addrs.AbsResourceInstanceObject] { | |
| return func(yield func(addrs.AbsResourceInstanceObject) bool) { | |
| for addr := range rios.ConfigDependencies(of) { | |
| if !yield(addr) { | |
| return | |
| } | |
| } | |
| for addr := range rios.ConfigDependents(of) { | |
| if !yield(addr) { | |
| return | |
| } | |
| } | |
| } | |
| } | |
| // StateDependenciesAndDependents is a convenience helper that concatenates | |
| // together the results of both [resourceInstanceObjects.StateDependencies] and | |
| // [resourceInstanceObjects.StateDependents] into a single flat sequence. It does | |
| // not transform those sequences in any other way. | |
| func (rios *resourceInstanceObjects) StateDependenciesAndDependents(of addrs.AbsResourceInstanceObject) iter.Seq[addrs.AbsResourceInstanceObject] { | |
| return func(yield func(addrs.AbsResourceInstanceObject) bool) { | |
| for addr := range rios.StateDependencies(of) { | |
| if !yield(addr) { | |
| return | |
| } | |
| } | |
| for addr := range rios.StateDependents(of) { | |
| if !yield(addr) { | |
| return | |
| } | |
| } | |
| } | |
| } | |
| // AllDependents combines the results of | |
| // [*resourceInstanceObjects.ConfigDependents] and | |
| // [*resourceInstanceObjects.StateDependents] into a single | |
| // flat sequence. | |
| func (rios *resourceInstanceObjects) AllDependents(of addrs.AbsResourceInstanceObject) iter.Seq[addrs.AbsResourceInstanceObject] { | |
| return func(yield func(addrs.AbsResourceInstanceObject) bool) { | |
| for addr := range rios.ConfigDependents(of) { | |
| if !yield(addr) { | |
| return | |
| } | |
| } | |
| for addr := range rios.StateDependents(of) { | |
| if !yield(addr) { | |
| 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](), | |
| reverseConfigDeps: addrs.MakeMap[addrs.AbsResourceInstanceObject, addrs.Set[addrs.AbsResourceInstanceObject]](), | |
| reverseStateDeps: addrs.MakeMap[addrs.AbsResourceInstanceObject, addrs.Set[addrs.AbsResourceInstanceObject]](), | |
| }, | |
| } | |
| } | |
| func (b *resourceInstanceObjectsBuilder) Get(addr addrs.AbsResourceInstanceObject) (*resourceInstanceObject, bool) { | |
| b.mu.Lock() | |
| defer b.mu.Unlock() | |
| return b.result.objects.GetOk(addr) | |
| } | |
| // 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 reverse maps here to ensure that our records of the | |
| // graph edges are always consistent across both directions. | |
| for depAddr := range obj.ConfigDependencies.All() { | |
| if !b.result.reverseConfigDeps.Has(depAddr) { | |
| b.result.reverseConfigDeps.Put(depAddr, addrs.MakeSet[addrs.AbsResourceInstanceObject]()) | |
| } | |
| b.result.reverseConfigDeps.Get(depAddr).Add(obj.Addr) | |
| } | |
| for depAddr := range obj.StateDependencies.All() { | |
| if !b.result.reverseStateDeps.Has(depAddr) { | |
| b.result.reverseStateDeps.Put(depAddr, addrs.MakeSet[addrs.AbsResourceInstanceObject]()) | |
| } | |
| b.result.reverseStateDeps.Get(depAddr).Add(obj.Addr) | |
| } | |
| } | |
| 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) | |
| } |