| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| package stackstate | |
| import ( | |
| "iter" | |
| "github.com/zclconf/go-cty/cty" | |
| "google.golang.org/protobuf/types/known/anypb" | |
| "github.com/hashicorp/terraform/internal/addrs" | |
| "github.com/hashicorp/terraform/internal/collections" | |
| "github.com/hashicorp/terraform/internal/stacks/stackaddrs" | |
| "github.com/hashicorp/terraform/internal/stacks/stackstate/statekeys" | |
| "github.com/hashicorp/terraform/internal/states" | |
| ) | |
| // State represents a previous run's state snapshot. | |
| // | |
| // Unlike [states.State] and its associates, State is an immutable data | |
| // structure constructed to represent only the previous run state. It should | |
| // not be modified after it's been constructed; results of planning or applying | |
| // changes are represented in other ways inside the stacks language runtime. | |
| type State struct { | |
| root *stackInstanceState | |
| outputs map[stackaddrs.OutputValue]cty.Value | |
| inputs map[stackaddrs.InputVariable]cty.Value | |
| // discardUnsupportedKeys is the set of state keys that we encountered | |
| // during decoding which are of types that are not supported by this | |
| // version of Terraform, if and only if they are of a type which is | |
| // specified as being discarded when unrecognized. We should emit | |
| // events during the apply phase to delete the objects associated with | |
| // these keys. | |
| discardUnsupportedKeys collections.Set[statekeys.Key] | |
| inputRaw map[string]*anypb.Any | |
| } | |
| // NewState constructs a new, empty state. | |
| func NewState() *State { | |
| return &State{ | |
| root: newStackInstanceState(stackaddrs.RootStackInstance), | |
| outputs: make(map[stackaddrs.OutputValue]cty.Value), | |
| inputs: make(map[stackaddrs.InputVariable]cty.Value), | |
| discardUnsupportedKeys: statekeys.NewKeySet(), | |
| inputRaw: nil, | |
| } | |
| } | |
| // RootInputVariables returns the values for the input variables currently in | |
| // the state. An address that is in the map and maps to cty.NilVal is an | |
| // ephemeral input, so it was present during the last operation but the value | |
| // in unknown. Compared to an input variable not in the map at all, which | |
| // indicates a new input variable that wasn't in the configuration during the | |
| // last operation. | |
| func (s *State) RootInputVariables() map[stackaddrs.InputVariable]cty.Value { | |
| return s.inputs | |
| } | |
| // RootInputVariable returns the input variable defined at the given address. | |
| // If the second return value is true, then the value is present but is | |
| // ephemeral and not known. If the first returned value is cty.NilVal and the | |
| // second is false then the value isn't present in the state. | |
| func (s *State) RootInputVariable(addr stackaddrs.InputVariable) cty.Value { | |
| return s.inputs[addr] | |
| } | |
| func (s *State) RootOutputValues() map[stackaddrs.OutputValue]cty.Value { | |
| return s.outputs | |
| } | |
| func (s *State) RootOutputValue(addr stackaddrs.OutputValue) cty.Value { | |
| return s.outputs[addr] | |
| } | |
| func (s *State) HasComponentInstance(addr stackaddrs.AbsComponentInstance) bool { | |
| stack := s.root.getDescendent(addr.Stack) | |
| if stack == nil { | |
| return false | |
| } | |
| return stack.getComponentInstance(addr.Item) != nil | |
| } | |
| func (s *State) HasStackInstance(addr stackaddrs.StackInstance) bool { | |
| stack := s.root.getDescendent(addr) | |
| return stack != nil | |
| } | |
| // AllComponentInstances returns a set of addresses for all of the component | |
| // instances that are tracked in the state. | |
| // | |
| // This includes both instances that were explicitly represented in the source | |
| // raw state _and_ any that were missing but implied by a resource instance | |
| // existing inside them. There should typically be an explicit component | |
| // instance record tracked in raw state, but it can potentially be absent in | |
| // exceptional cases such as if Terraform Core crashed partway through the | |
| // previous run. | |
| func (s *State) AllComponentInstances() iter.Seq[stackaddrs.AbsComponentInstance] { | |
| return func(yield func(stackaddrs.AbsComponentInstance) bool) { | |
| s.root.iterate(yield) | |
| } | |
| } | |
| // ComponentInstances returns the set of component instances that belong to the | |
| // given component, or an empty set if no such component is tracked in the | |
| // state. | |
| // | |
| // This will always be a subset of AllComponentInstances. | |
| func (s *State) ComponentInstances(addr stackaddrs.AbsComponent) iter.Seq[stackaddrs.ComponentInstance] { | |
| return func(yield func(stackaddrs.ComponentInstance) bool) { | |
| target := s.root.getDescendent(addr.Stack) | |
| if target == nil { | |
| return | |
| } | |
| for key := range target.components[addr.Item] { | |
| yield(stackaddrs.ComponentInstance{ | |
| Component: addr.Item, | |
| Key: key, | |
| }) | |
| } | |
| } | |
| } | |
| // StackInstances returns the set of known stack instances for the given stack | |
| // call. | |
| func (s *State) StackInstances(call stackaddrs.AbsStackCall) iter.Seq[stackaddrs.StackInstance] { | |
| return func(yield func(stackaddrs.StackInstance) bool) { | |
| target := s.root.getDescendent(call.Stack) | |
| if target == nil { | |
| return | |
| } | |
| for _, stack := range target.children[call.Item.Name] { | |
| yield(stack.address) | |
| } | |
| } | |
| } | |
| func (s *State) componentInstanceState(addr stackaddrs.AbsComponentInstance) *componentInstanceState { | |
| target := s.root.getDescendent(addr.Stack) | |
| if target == nil { | |
| return nil | |
| } | |
| return target.getComponentInstance(addr.Item) | |
| } | |
| // DependenciesForComponent returns the list of components that are required by | |
| // the given component instance, or an empty set if no such component instance | |
| // is tracked in the state. | |
| func (s *State) DependenciesForComponent(addr stackaddrs.AbsComponentInstance) collections.Set[stackaddrs.AbsComponent] { | |
| cs := s.componentInstanceState(addr) | |
| if cs == nil { | |
| return collections.NewSet[stackaddrs.AbsComponent]() | |
| } | |
| return cs.dependencies | |
| } | |
| // DependentsForComponent returns the list of components that are require the | |
| // given component instance, or an empty set if no such component instance is | |
| // tracked in the state. | |
| func (s *State) DependentsForComponent(addr stackaddrs.AbsComponentInstance) collections.Set[stackaddrs.AbsComponent] { | |
| cs := s.componentInstanceState(addr) | |
| if cs == nil { | |
| return collections.NewSet[stackaddrs.AbsComponent]() | |
| } | |
| return cs.dependents | |
| } | |
| // ResultsForComponent returns the output values for the given component | |
| // instance, or nil if no such component instance is tracked in the state. | |
| func (s *State) ResultsForComponent(addr stackaddrs.AbsComponentInstance) map[addrs.OutputValue]cty.Value { | |
| cs := s.componentInstanceState(addr) | |
| if cs == nil { | |
| return nil | |
| } | |
| return cs.outputValues | |
| } | |
| // InputsForComponent returns the input values for the given component | |
| // instance, or nil if no such component instance is tracked in the state. | |
| func (s *State) InputsForComponent(addr stackaddrs.AbsComponentInstance) map[addrs.InputVariable]cty.Value { | |
| cs := s.componentInstanceState(addr) | |
| if cs == nil { | |
| return nil | |
| } | |
| return cs.inputVariables | |
| } | |
| type IdentitySrc struct { | |
| IdentitySchemaVersion uint64 | |
| IdentityJSON []byte | |
| } | |
| // IdentitiesForComponent returns the identity values for the given component | |
| // instance, or nil if no such component instance is tracked in the state. | |
| func (s *State) IdentitiesForComponent(addr stackaddrs.AbsComponentInstance) map[*addrs.AbsResourceInstanceObject]IdentitySrc { | |
| cs := s.componentInstanceState(addr) | |
| if cs == nil { | |
| return nil | |
| } | |
| res := make(map[*addrs.AbsResourceInstanceObject]IdentitySrc) | |
| for _, rio := range cs.resourceInstanceObjects.Elements() { | |
| res[&rio.Key] = IdentitySrc{ | |
| IdentitySchemaVersion: rio.Value.src.IdentitySchemaVersion, | |
| IdentityJSON: rio.Value.src.IdentityJSON, | |
| } | |
| } | |
| return res | |
| } | |
| // ComponentInstanceResourceInstanceObjects returns a set of addresses for | |
| // all of the resource instance objects belonging to the component instance | |
| // with the given address. | |
| func (s *State) ComponentInstanceResourceInstanceObjects(addr stackaddrs.AbsComponentInstance) collections.Set[stackaddrs.AbsResourceInstanceObject] { | |
| var ret collections.Set[stackaddrs.AbsResourceInstanceObject] | |
| cs := s.componentInstanceState(addr) | |
| if cs == nil { | |
| return ret | |
| } | |
| ret = collections.NewSet[stackaddrs.AbsResourceInstanceObject]() | |
| for _, elem := range cs.resourceInstanceObjects.Elems { | |
| objKey := stackaddrs.AbsResourceInstanceObject{ | |
| Component: addr, | |
| Item: elem.Key, | |
| } | |
| ret.Add(objKey) | |
| } | |
| return ret | |
| } | |
| // ResourceInstanceObjectSrc returns the source (i.e. still encoded) version of | |
| // the resource instance object for the given address, or nil if no such | |
| // object is tracked in the state. | |
| func (s *State) ResourceInstanceObjectSrc(addr stackaddrs.AbsResourceInstanceObject) *states.ResourceInstanceObjectSrc { | |
| rios := s.resourceInstanceObjectState(addr) | |
| if rios == nil { | |
| return nil | |
| } | |
| return rios.src | |
| } | |
| // RequiredProviderInstances returns a description of all of the provider | |
| // instance slots that are required to satisfy the resource instances | |
| // belonging to the given component instance. | |
| // | |
| // See also stackeval.ComponentConfig.RequiredProviderInstances for a similar | |
| // function that operates on the configuration of a component instance rather | |
| // than the state of one. | |
| func (s *State) RequiredProviderInstances(component stackaddrs.AbsComponentInstance) addrs.Set[addrs.RootProviderConfig] { | |
| state := s.componentInstanceState(component) | |
| if state == nil { | |
| // Then we have no state for this component, which is fine. | |
| return addrs.MakeSet[addrs.RootProviderConfig]() | |
| } | |
| providerInstances := addrs.MakeSet[addrs.RootProviderConfig]() | |
| for _, elem := range state.resourceInstanceObjects.Elems { | |
| providerInstances.Add(addrs.RootProviderConfig{ | |
| Provider: elem.Value.providerConfigAddr.Provider, | |
| Alias: elem.Value.providerConfigAddr.Alias, | |
| }) | |
| } | |
| return providerInstances | |
| } | |
| func (s *State) resourceInstanceObjectState(addr stackaddrs.AbsResourceInstanceObject) *resourceInstanceObjectState { | |
| cs := s.componentInstanceState(addr.Component) | |
| if cs == nil { | |
| return nil | |
| } | |
| return cs.resourceInstanceObjects.Get(addr.Item) | |
| } | |
| // ComponentInstanceStateForModulesRuntime returns a [states.State] | |
| // representation of the objects tracked for the given component instance. | |
| // | |
| // This produces only a very bare-bones [states.State] that should be | |
| // sufficient for use as a prior state for the modules runtime's plan function | |
| // to consider, but likely won't be of much other use. | |
| func (s *State) ComponentInstanceStateForModulesRuntime(addr stackaddrs.AbsComponentInstance) *states.State { | |
| return states.BuildState(func(ss *states.SyncState) { | |
| objAddrs := s.ComponentInstanceResourceInstanceObjects(addr) | |
| for objAddr := range objAddrs.All() { | |
| rios := s.resourceInstanceObjectState(objAddr) | |
| if objAddr.Item.IsCurrent() { | |
| ss.SetResourceInstanceCurrent( | |
| objAddr.Item.ResourceInstance, | |
| rios.src, rios.providerConfigAddr, | |
| ) | |
| } else { | |
| ss.SetResourceInstanceDeposed( | |
| objAddr.Item.ResourceInstance, objAddr.Item.DeposedKey, | |
| rios.src, rios.providerConfigAddr, | |
| ) | |
| } | |
| } | |
| }) | |
| } | |
| // RawKeysToDiscard returns a set of raw state keys that the apply phase should | |
| // emit "delete" events for to remove objects from the raw state map that | |
| // will no longer be relevant or meaningful after this plan is applied. | |
| // | |
| // Do not modify the returned set. | |
| func (s *State) RawKeysToDiscard() collections.Set[statekeys.Key] { | |
| return s.discardUnsupportedKeys | |
| } | |
| // InputRaw returns the raw representation of state that this object was built | |
| // from, or nil if this object wasn't constructed by decoding a protocol buffers | |
| // representation. | |
| // | |
| // All callers of this method get the same map, so callers must not modify | |
| // the map or anything reachable through it. | |
| func (s *State) InputRaw() map[string]*anypb.Any { | |
| return s.inputRaw | |
| } | |
| func (s *State) addOutputValue(addr stackaddrs.OutputValue, value cty.Value) { | |
| s.outputs[addr] = value | |
| } | |
| func (s *State) addInputVariable(addr stackaddrs.InputVariable, value cty.Value) { | |
| s.inputs[addr] = value | |
| } | |
| func (s *State) ensureComponentInstanceState(addr stackaddrs.AbsComponentInstance) *componentInstanceState { | |
| current := s.root | |
| for _, step := range addr.Stack { | |
| next := current.getChild(step) | |
| if next == nil { | |
| next = newStackInstanceState(append(current.address, step)) | |
| children, ok := current.children[step.Name] | |
| if !ok { | |
| children = make(map[addrs.InstanceKey]*stackInstanceState) | |
| } | |
| children[step.Key] = next | |
| current.children[step.Name] = children | |
| } | |
| current = next | |
| } | |
| component := current.getComponentInstance(addr.Item) | |
| if component == nil { | |
| component = &componentInstanceState{ | |
| dependencies: collections.NewSet[stackaddrs.AbsComponent](), | |
| dependents: collections.NewSet[stackaddrs.AbsComponent](), | |
| outputValues: make(map[addrs.OutputValue]cty.Value), | |
| inputVariables: make(map[addrs.InputVariable]cty.Value), | |
| resourceInstanceObjects: addrs.MakeMap[addrs.AbsResourceInstanceObject, *resourceInstanceObjectState](), | |
| } | |
| components, ok := current.components[addr.Item.Component] | |
| if !ok { | |
| components = make(map[addrs.InstanceKey]*componentInstanceState) | |
| } | |
| components[addr.Item.Key] = component | |
| current.components[addr.Item.Component] = components | |
| } | |
| return component | |
| } | |
| func (s *State) addResourceInstanceObject(addr stackaddrs.AbsResourceInstanceObject, src *states.ResourceInstanceObjectSrc, providerConfigAddr addrs.AbsProviderConfig) { | |
| cs := s.ensureComponentInstanceState(addr.Component) | |
| cs.resourceInstanceObjects.Put(addr.Item, &resourceInstanceObjectState{ | |
| src: src, | |
| providerConfigAddr: providerConfigAddr, | |
| }) | |
| } | |
| type componentInstanceState struct { | |
| // dependencies is the set of component instances that this component | |
| // depended on the last time it was updated. | |
| dependencies collections.Set[stackaddrs.AbsComponent] | |
| // dependents is a set of component instances that depended on this | |
| // component the last time it was updated. | |
| dependents collections.Set[stackaddrs.AbsComponent] | |
| // outputValues is a map from output value addresses to their values at | |
| // completion of the last apply operation. | |
| outputValues map[addrs.OutputValue]cty.Value | |
| // inputVariables is a map from input variable addresses to their values at | |
| // completion of the last apply operation. | |
| inputVariables map[addrs.InputVariable]cty.Value | |
| // resourceInstanceObjects is a map from resource instance object addresses | |
| // to their state. | |
| resourceInstanceObjects addrs.Map[addrs.AbsResourceInstanceObject, *resourceInstanceObjectState] | |
| } | |
| type resourceInstanceObjectState struct { | |
| src *states.ResourceInstanceObjectSrc | |
| providerConfigAddr addrs.AbsProviderConfig | |
| } | |
| type stackInstanceState struct { | |
| address stackaddrs.StackInstance | |
| components map[stackaddrs.Component]map[addrs.InstanceKey]*componentInstanceState | |
| children map[string]map[addrs.InstanceKey]*stackInstanceState | |
| } | |
| func newStackInstanceState(address stackaddrs.StackInstance) *stackInstanceState { | |
| return &stackInstanceState{ | |
| address: address, | |
| components: make(map[stackaddrs.Component]map[addrs.InstanceKey]*componentInstanceState), | |
| children: make(map[string]map[addrs.InstanceKey]*stackInstanceState), | |
| } | |
| } | |
| func (s *stackInstanceState) getDescendent(stack stackaddrs.StackInstance) *stackInstanceState { | |
| if len(stack) == 0 { | |
| return s | |
| } | |
| next := s.getChild(stack[0]) | |
| if next == nil { | |
| return nil | |
| } | |
| return next.getDescendent(stack[1:]) | |
| } | |
| func (s *stackInstanceState) getChild(step stackaddrs.StackInstanceStep) *stackInstanceState { | |
| stacks, ok := s.children[step.Name] | |
| if !ok { | |
| return nil | |
| } | |
| return stacks[step.Key] | |
| } | |
| func (s *stackInstanceState) getComponentInstance(component stackaddrs.ComponentInstance) *componentInstanceState { | |
| components, ok := s.components[component.Component] | |
| if !ok { | |
| return nil | |
| } | |
| return components[component.Key] | |
| } | |
| func (s *stackInstanceState) iterate(yield func(stackaddrs.AbsComponentInstance) bool) bool { | |
| for component, components := range s.components { | |
| for key := range components { | |
| proceed := yield(stackaddrs.AbsComponentInstance{ | |
| Stack: s.address, | |
| Item: stackaddrs.ComponentInstance{ | |
| Component: component, | |
| Key: key, | |
| }, | |
| }) | |
| if !proceed { | |
| return false | |
| } | |
| } | |
| } | |
| for _, children := range s.children { | |
| for _, child := range children { | |
| if !child.iterate(yield) { | |
| return false | |
| } | |
| } | |
| } | |
| return true | |
| } |