// Copyright (c) The OpenTofu Authors // SPDX-License-Identifier: MPL-2.0 // Copyright (c) 2023 HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package states import ( "fmt" "sort" "github.com/zclconf/go-cty/cty" "github.com/opentofu/opentofu/internal/addrs" "github.com/opentofu/opentofu/internal/getproviders" ) // State is the top-level type of OpenTofu state. // // A state should be mutated only via its accessor methods, to ensure that // invariants are preserved. // // Access to State and the nested values within it is not concurrency-safe, // so when accessing a State object concurrently it is the caller's // responsibility to ensure that only one write is in progress at a time // and that reads only occur when no write is in progress. The most common // way to achieve this is to wrap the State in a SyncState and use the // higher-level atomic operations supported by that type. type State struct { // Modules contains the state for each module. The keys in this map are // an implementation detail and must not be used by outside callers. Modules map[string]*Module // CheckResults contains a snapshot of the statuses of checks at the // end of the most recent update to the state. Callers might compare // checks between runs to see if e.g. a previously-failing check has // been fixed since the last run, or similar. // // CheckResults can be nil to indicate that there are no check results // from the previous run at all, which is subtly different than the // previous run having affirmatively recorded that there are no checks // to run. For example, if this object was created from a state snapshot // created by a version of OpenTofu that didn't yet support checks // then this field will be nil. CheckResults *CheckResults } // NewState constructs a minimal empty state, containing an empty root module. func NewState() *State { modules := map[string]*Module{} modules[addrs.RootModuleInstance.String()] = NewModule(addrs.RootModuleInstance) return &State{ Modules: modules, } } // BuildState is a helper -- primarily intended for tests -- to build a state // using imperative code against the StateSync type while still acting as // an expression of type *State to assign into a containing struct. func BuildState(cb func(*SyncState)) *State { s := NewState() cb(s.SyncWrapper()) return s } // Empty returns true if there are no resources or populated output values // in the receiver. In other words, if this state could be safely replaced // with the return value of NewState and be functionally equivalent. func (s *State) Empty() bool { if s == nil { return true } for _, ms := range s.Modules { if len(ms.Resources) != 0 { return false } if len(ms.OutputValues) != 0 { return false } } return true } // Module returns the state for the module with the given address, or nil if // the requested module is not tracked in the state. func (s *State) Module(addr addrs.ModuleInstance) *Module { if s == nil { panic("State.Module on nil *State") } return s.Modules[addr.String()] } // ModuleInstances returns the set of Module states that matches the given path. func (s *State) ModuleInstances(addr addrs.Module) []*Module { var ms []*Module for _, m := range s.Modules { if m.Addr.Module().Equal(addr) { ms = append(ms, m) } } return ms } // ModuleOutputs returns all outputs for the given module call under the // parentAddr instance. func (s *State) ModuleOutputs(parentAddr addrs.ModuleInstance, module addrs.ModuleCall) []*OutputValue { var os []*OutputValue for _, m := range s.Modules { // can't get outputs from the root module if m.Addr.IsRoot() { continue } parent, call := m.Addr.Call() // make sure this is a descendent in the correct path if !parentAddr.Equal(parent) { continue } // and check if this is the correct child if call.Name != module.Name { continue } for _, o := range m.OutputValues { os = append(os, o) } } return os } // RemoveModule removes the module with the given address from the state, // unless it is the root module. The root module cannot be deleted, and so // this method will panic if that is attempted. // // Removing a module implicitly discards all of the resources, outputs and // local values within it, and so this should usually be done only for empty // modules. For callers accessing the state through a SyncState wrapper, modules // are automatically pruned if they are empty after one of their contained // elements is removed. func (s *State) RemoveModule(addr addrs.ModuleInstance) { if addr.IsRoot() { panic("attempted to remove root module") } delete(s.Modules, addr.String()) } // RootModule is a convenient alias for Module(addrs.RootModuleInstance). func (s *State) RootModule() *Module { if s == nil { panic("RootModule called on nil State") } return s.Modules[addrs.RootModuleInstance.String()] } // EnsureModule returns the state for the module with the given address, // creating and adding a new one if necessary. // // Since this might modify the state to add a new instance, it is considered // to be a write operation. func (s *State) EnsureModule(addr addrs.ModuleInstance) *Module { ms := s.Module(addr) if ms == nil { ms = NewModule(addr) s.Modules[addr.String()] = ms } return ms } // HasManagedResourceInstanceObjects returns true if there is at least one // resource instance object (current or deposed) associated with a managed // resource in the receiving state. // // A true result would suggest that just discarding this state without first // destroying these objects could leave "dangling" objects in remote systems, // no longer tracked by any OpenTofu state. func (s *State) HasManagedResourceInstanceObjects() bool { if s == nil { return false } for _, ms := range s.Modules { for _, rs := range ms.Resources { if rs.Addr.Resource.Mode != addrs.ManagedResourceMode { continue } for _, is := range rs.Instances { if is.Current != nil || len(is.Deposed) != 0 { return true } } } } return false } // Resource returns the state for the resource with the given address, or nil // if no such resource is tracked in the state. func (s *State) Resource(addr addrs.AbsResource) *Resource { ms := s.Module(addr.Module) if ms == nil { return nil } return ms.Resource(addr.Resource) } // Resources returns the set of resources that match the given configuration path. func (s *State) Resources(addr addrs.ConfigResource) []*Resource { var ret []*Resource for _, m := range s.ModuleInstances(addr.Module) { r := m.Resource(addr.Resource) if r != nil { ret = append(ret, r) } } return ret } // AllManagedResourceInstanceObjectAddrs returns a set of addresses for all of // the leaf resource instance objects associated with managed resources that // are tracked in this state. // // This result is the set of objects that would be effectively "forgotten" // (like "tofu state rm") if this state were totally discarded, such as // by deleting a workspace. This function is intended only for reporting // context in error messages, such as when we reject deleting a "non-empty" // workspace as detected by s.HasManagedResourceInstanceObjects. // // The ordering of the result is meaningless but consistent. DeposedKey will // be NotDeposed (the zero value of DeposedKey) for any "current" objects. // This method is guaranteed to return at least one item if // s.HasManagedResourceInstanceObjects returns true for the same state, and // to return a zero-length slice if it returns false. func (s *State) AllResourceInstanceObjectAddrs() []struct { Instance addrs.AbsResourceInstance DeposedKey DeposedKey } { if s == nil { return nil } // We use an unnamed return type here just because we currently have no // general need to return pairs of instance address and deposed key aside // from this method, and this method itself is only of marginal value // when producing some error messages. // // If that need ends up arising more in future then it might make sense to // name this as addrs.AbsResourceInstanceObject, although that would require // moving DeposedKey into the addrs package too. type ResourceInstanceObject = struct { Instance addrs.AbsResourceInstance DeposedKey DeposedKey } var ret []ResourceInstanceObject for _, ms := range s.Modules { for _, rs := range ms.Resources { if rs.Addr.Resource.Mode != addrs.ManagedResourceMode { continue } for instKey, is := range rs.Instances { instAddr := rs.Addr.Instance(instKey) if is.Current != nil { ret = append(ret, ResourceInstanceObject{instAddr, NotDeposed}) } for deposedKey := range is.Deposed { ret = append(ret, ResourceInstanceObject{instAddr, deposedKey}) } } } } sort.SliceStable(ret, func(i, j int) bool { objI, objJ := ret[i], ret[j] switch { case !objI.Instance.Equal(objJ.Instance): return objI.Instance.Less(objJ.Instance) default: return objI.DeposedKey < objJ.DeposedKey } }) return ret } // ResourceInstance returns the state for the resource instance with the given // address, or nil if no such resource is tracked in the state. func (s *State) ResourceInstance(addr addrs.AbsResourceInstance) *ResourceInstance { if s == nil { panic("State.ResourceInstance on nil *State") } ms := s.Module(addr.Module) if ms == nil { return nil } return ms.ResourceInstance(addr.Resource) } // OutputValue returns the state for the output value with the given address, // or nil if no such output value is tracked in the state. func (s *State) OutputValue(addr addrs.AbsOutputValue) *OutputValue { ms := s.Module(addr.Module) if ms == nil { return nil } return ms.OutputValues[addr.OutputValue.Name] } // LocalValue returns the value of the named local value with the given address, // or cty.NilVal if no such value is tracked in the state. func (s *State) LocalValue(addr addrs.AbsLocalValue) cty.Value { ms := s.Module(addr.Module) if ms == nil { return cty.NilVal } return ms.LocalValues[addr.LocalValue.Name] } // ProviderAddrs returns a list of all of the provider configuration addresses // referenced throughout the receiving state. // // The result is de-duplicated so that each distinct address appears only once. func (s *State) ProviderAddrs() []addrs.AbsProviderConfig { if s == nil { return nil } m := map[string]addrs.AbsProviderConfig{} for _, ms := range s.Modules { for _, rc := range ms.Resources { m[rc.ProviderConfig.String()] = rc.ProviderConfig } } if len(m) == 0 { return nil } // This is mainly just so we'll get stable results for testing purposes. keys := make([]string, 0, len(m)) for k := range m { keys = append(keys, k) } sort.Strings(keys) ret := make([]addrs.AbsProviderConfig, len(keys)) for i, key := range keys { ret[i] = m[key] } return ret } // ProviderRequirements returns a description of all of the providers that // are required to work with the receiving state. // // Because the state does not track specific version information for providers, // the requirements returned by this method will always be unconstrained. // The result should usually be merged with a Requirements derived from the // current configuration in order to apply some constraints. func (s *State) ProviderRequirements() getproviders.Requirements { configAddrs := s.ProviderAddrs() ret := make(getproviders.Requirements, len(configAddrs)) for _, configAddr := range configAddrs { ret[configAddr.Provider] = nil // unconstrained dependency } return ret } // PruneResourceHusks is a specialized method that will remove any Resource // objects that do not contain any instances, even if they have an EachMode. // // This should generally be used only after a "tofu destroy" operation, // to finalize the cleanup of the state. It is not correct to use this after // other operations because if a resource has "count = 0" or "for_each" over // an empty collection then we want to retain it in the state so that references // to it, particularly in "strange" contexts like "tofu console", can be // properly resolved. // // This method MUST NOT be called concurrently with other readers and writers // of the receiving state. func (s *State) PruneResourceHusks() { for _, m := range s.Modules { m.PruneResourceHusks() if len(m.Resources) == 0 && !m.Addr.IsRoot() { s.RemoveModule(m.Addr) } } } // SyncWrapper returns a SyncState object wrapping the receiver. func (s *State) SyncWrapper() *SyncState { return &SyncState{ state: s, } } // MoveAbsResource moves the given src AbsResource's current state to the new // dst address. This will panic if the src AbsResource does not exist in state, // or if there is already a resource at the dst address. It is the caller's // responsibility to verify the validity of the move (for example, that the src // and dst are compatible types). func (s *State) MoveAbsResource(src, dst addrs.AbsResource) { // verify that the src address exists and the dst address does not rs := s.Resource(src) if rs == nil { panic(fmt.Sprintf("no state for src address %s", src.String())) } ds := s.Resource(dst) if ds != nil { panic(fmt.Sprintf("dst resource %s already exists", dst.String())) } ms := s.Module(src.Module) ms.RemoveResource(src.Resource) // Remove the module if it is empty (and not root) after removing the // resource. if !ms.Addr.IsRoot() && ms.empty() { s.RemoveModule(src.Module) } // Update the address before adding it to the state rs.Addr = dst s.EnsureModule(dst.Module).Resources[dst.Resource.String()] = rs } // MaybeMoveAbsResource moves the given src AbsResource's current state to the // new dst address. This function will succeed if both the src address does not // exist in state and the dst address does; the return value indicates whether // or not the move occurred. This function will panic if either the src does not // exist or the dst does exist (but not both). func (s *State) MaybeMoveAbsResource(src, dst addrs.AbsResource) bool { // Get the source and destinatation addresses from state. rs := s.Resource(src) ds := s.Resource(dst) // Normal case: the src exists in state, dst does not if rs != nil && ds == nil { s.MoveAbsResource(src, dst) return true } if rs == nil && ds != nil { // The source is not in state, the destination is. This is not // guaranteed to be idempotent since we aren't tracking exact moves, but // it's useful information for the caller. return false } else { panic("invalid move") } } // MoveAbsResourceInstance moves the given src AbsResourceInstance's current state to // the new dst address. This will panic if the src AbsResourceInstance does not // exist in state, or if there is already a resource at the dst address. It is // the caller's responsibility to verify the validity of the move (for example, // that the src and dst are compatible types). func (s *State) MoveAbsResourceInstance(src, dst addrs.AbsResourceInstance) { srcInstanceState := s.ResourceInstance(src) if srcInstanceState == nil { panic(fmt.Sprintf("no state for src address %s", src.String())) } dstInstanceState := s.ResourceInstance(dst) if dstInstanceState != nil { panic(fmt.Sprintf("dst resource %s already exists", dst.String())) } srcResourceState := s.Resource(src.ContainingResource()) srcProviderAddr := srcResourceState.ProviderConfig dstResourceAddr := dst.ContainingResource() // Remove the source resource instance from the module's state, and then the // module if empty. ms := s.Module(src.Module) ms.ForgetResourceInstanceAll(src.Resource) if !ms.Addr.IsRoot() && ms.empty() { s.RemoveModule(src.Module) } dstModule := s.EnsureModule(dst.Module) // See if there is already a resource we can add this instance to. dstResourceState := s.Resource(dstResourceAddr) if dstResourceState == nil { // If we're moving to an address without an index then that // suggests the user's intent is to establish both the // resource and the instance at the same time (since the // address covers both). If there's an index in the // target then allow creating the new instance here. dstModule.SetResourceProvider( dstResourceAddr.Resource, srcProviderAddr, // in this case, we bring the provider along as if we were moving the whole resource ) dstResourceState = dstModule.Resource(dstResourceAddr.Resource) } dstResourceState.Instances[dst.Resource.Key] = srcInstanceState } // MaybeMoveAbsResourceInstance moves the given src AbsResourceInstance's // current state to the new dst address. This function will succeed if both the // src address does not exist in state and the dst address does; the return // value indicates whether or not the move occured. This function will panic if // either the src does not exist or the dst does exist (but not both). func (s *State) MaybeMoveAbsResourceInstance(src, dst addrs.AbsResourceInstance) bool { // get the src and dst resource instances from state rs := s.ResourceInstance(src) ds := s.ResourceInstance(dst) // Normal case: the src exists in state, dst does not if rs != nil && ds == nil { s.MoveAbsResourceInstance(src, dst) return true } if rs == nil && ds != nil { // The source is not in state, the destination is. This is not // guaranteed to be idempotent since we aren't tracking exact moves, but // it's useful information. return false } else { panic("invalid move") } } // MoveModuleInstance moves the given src ModuleInstance's current state to the // new dst address. This will panic if the src ModuleInstance does not // exist in state, or if there is already a resource at the dst address. It is // the caller's responsibility to verify the validity of the move. func (s *State) MoveModuleInstance(src, dst addrs.ModuleInstance) { if src.IsRoot() || dst.IsRoot() { panic("cannot move to or from root module") } srcMod := s.Module(src) if srcMod == nil { panic(fmt.Sprintf("no state for src module %s", src.String())) } dstMod := s.Module(dst) if dstMod != nil { panic(fmt.Sprintf("dst module %s already exists in state", dst.String())) } s.RemoveModule(src) srcMod.Addr = dst s.EnsureModule(dst) s.Modules[dst.String()] = srcMod // Update any Resource's addresses. if srcMod.Resources != nil { for _, r := range srcMod.Resources { r.Addr.Module = dst } } // Update any OutputValues's addresses. if srcMod.OutputValues != nil { for _, ov := range srcMod.OutputValues { ov.Addr.Module = dst } } } // MaybeMoveModuleInstance moves the given src ModuleInstance's current state to // the new dst address. This function will succeed if both the src address does // not exist in state and the dst address does; the return value indicates // whether or not the move occured. This function will panic if either the src // does not exist or the dst does exist (but not both). func (s *State) MaybeMoveModuleInstance(src, dst addrs.ModuleInstance) bool { if src.IsRoot() || dst.IsRoot() { panic("cannot move to or from root module") } srcMod := s.Module(src) dstMod := s.Module(dst) // Normal case: the src exists in state, dst does not if srcMod != nil && dstMod == nil { s.MoveModuleInstance(src, dst) return true } if srcMod == nil || src.IsRoot() && dstMod != nil { // The source is not in state, the destination is. This is not // guaranteed to be idempotent since we aren't tracking exact moves, but // it's useful information. return false } else { panic("invalid move") } } // MoveModule takes a source and destination addrs.Module address, and moves all // state Modules which are contained by the src address to the new address. func (s *State) MoveModule(src, dst addrs.AbsModuleCall) { if src.Module.IsRoot() || dst.Module.IsRoot() { panic("cannot move to or from root module") } // Modules only exist as ModuleInstances in state, so we need to check each // state Module and see if it is contained by the src address to get a full // list of modules to move. var srcMIs []*Module for _, module := range s.Modules { if !module.Addr.IsRoot() { if src.Module.TargetContains(module.Addr) { srcMIs = append(srcMIs, module) } } } if len(srcMIs) == 0 { panic(fmt.Sprintf("no matching module instances found for src module %s", src.String())) } for _, ms := range srcMIs { newInst := make(addrs.ModuleInstance, len(ms.Addr)) copy(newInst, ms.Addr) if ms.Addr.IsDeclaredByCall(src) { // Easy case: we just need to update the last step with the new name newInst[len(newInst)-1].Name = dst.Call.Name } else { // Trickier: this Module is a submodule. we need to find and update // only that appropriate step for s := range newInst { if newInst[s].Name == src.Call.Name { newInst[s].Name = dst.Call.Name } } } s.MoveModuleInstance(ms.Addr, newInst) } }