package states import ( "sync" "github.com/zclconf/go-cty/cty" "github.com/hashicorp/terraform/addrs" ) // SyncState is a wrapper around State that provides concurrency-safe access to // various common operations that occur during a Terraform graph walk, or other // similar concurrent contexts. // // When a SyncState wrapper is in use, no concurrent direct access to the // underlying objects is permitted unless the caller first acquires an explicit // lock, using the Lock and Unlock methods. Most callers should _not_ // explicitly lock, and should instead use the other methods of this type that // handle locking automatically. // // Since SyncState is able to safely consolidate multiple updates into a single // atomic operation, many of its methods are at a higher level than those // of the underlying types, and operate on the state as a whole rather than // on individual sub-structures of the state. // // SyncState can only protect against races within its own methods. It cannot // provide any guarantees about the order in which concurrent operations will // be processed, so callers may still need to employ higher-level techniques // for ensuring correct operation sequencing, such as building and walking // a dependency graph. type SyncState struct { state *State lock sync.RWMutex } // Module returns a snapshot of the state of the module instance with the given // address, or nil if no such module is tracked. // // The return value is a pointer to a copy of the module state, which the // caller may then freely access and mutate. However, since the module state // tends to be a large data structure with many child objects, where possible // callers should prefer to use a more granular accessor to access a child // module directly, and thus reduce the amount of copying required. func (s *SyncState) Module(addr addrs.ModuleInstance) *Module { s.lock.RLock() ret := s.state.Module(addr).DeepCopy() s.lock.RUnlock() return ret } // OutputValue returns a snapshot of the state of the output value with the // given address, or nil if no such output value is tracked. // // The return value is a pointer to a copy of the output value state, which the // caller may then freely access and mutate. func (s *SyncState) OutputValue(addr addrs.AbsOutputValue) *OutputValue { s.lock.RLock() ret := s.state.OutputValue(addr).DeepCopy() s.lock.RUnlock() return ret } // SetOutputValue writes a given output value into the state, overwriting // any existing value of the same name. // // If the module containing the output is not yet tracked in state then it // be added as a side-effect. func (s *SyncState) SetOutputValue(addr addrs.AbsOutputValue, value cty.Value, sensitive bool) { s.lock.Lock() defer s.lock.Unlock() ms := s.state.EnsureModule(addr.Module) ms.SetOutputValue(addr.OutputValue.Name, value, sensitive) } // RemoveOutputValue removes the stored value for the output value with the // given address. // // If this results in its containing module being empty, the module will be // pruned from the state as a side-effect. func (s *SyncState) RemoveOutputValue(addr addrs.AbsOutputValue) { s.lock.Lock() defer s.lock.Unlock() ms := s.state.Module(addr.Module) if ms == nil { return } ms.RemoveOutputValue(addr.OutputValue.Name) s.maybePruneModule(addr.Module) } // LocalValue returns the current value associated with the given local value // address. func (s *SyncState) LocalValue(addr addrs.AbsLocalValue) cty.Value { s.lock.RLock() // cty.Value is immutable, so we don't need any extra copying here. ret := s.state.LocalValue(addr) s.lock.RUnlock() return ret } // SetLocalValue writes a given output value into the state, overwriting // any existing value of the same name. // // If the module containing the local value is not yet tracked in state then it // will be added as a side-effect. func (s *SyncState) SetLocalValue(addr addrs.AbsLocalValue, value cty.Value) { s.lock.Lock() defer s.lock.Unlock() ms := s.state.EnsureModule(addr.Module) ms.SetLocalValue(addr.LocalValue.Name, value) } // RemoveLocalValue removes the stored value for the local value with the // given address. // // If this results in its containing module being empty, the module will be // pruned from the state as a side-effect. func (s *SyncState) RemoveLocalValue(addr addrs.AbsLocalValue) { s.lock.Lock() defer s.lock.Unlock() ms := s.state.Module(addr.Module) if ms == nil { return } ms.RemoveLocalValue(addr.LocalValue.Name) s.maybePruneModule(addr.Module) } // Resource returns a snapshot of the state of the resource with the given // address, or nil if no such resource is tracked. // // The return value is a pointer to a copy of the resource state, which the // caller may then freely access and mutate. func (s *SyncState) Resource(addr addrs.AbsResource) *Resource { s.lock.RLock() ret := s.state.Resource(addr).DeepCopy() s.lock.RUnlock() return ret } // ResourceInstance returns a snapshot of the state the resource instance with // the given address, or nil if no such instance is tracked. // // The return value is a pointer to a copy of the instance state, which the // caller may then freely access and mutate. func (s *SyncState) ResourceInstance(addr addrs.AbsResourceInstance) *ResourceInstance { s.lock.RLock() ret := s.state.ResourceInstance(addr).DeepCopy() s.lock.RUnlock() return ret } // ResourceInstanceObject returns a snapshot of the current instance object // of the given generation belonging to the instance with the given address, // or nil if no such object is tracked.. // // The return value is a pointer to a copy of the object, which the caller may // then freely access and mutate. func (s *SyncState) ResourceInstanceObject(addr addrs.AbsResourceInstance, gen Generation) *ResourceInstanceObject { s.lock.RLock() defer s.lock.RUnlock() inst := s.state.ResourceInstance(addr) if inst == nil { return nil } return inst.GetGeneration(gen) } // SetResourceMeta updates the resource-level metadata for the resource at // the given address, creating the containing module state and resource state // as a side-effect if not already present. func (s *SyncState) SetResourceMeta(addr addrs.AbsResource, eachMode EachMode, provider addrs.AbsProviderConfig) { s.lock.Lock() defer s.lock.Unlock() ms := s.state.EnsureModule(addr.Module) ms.SetResourceMeta(addr.Resource, eachMode, provider) } // RemoveResource removes the entire state for the given resource, taking with // it any instances associated with the resource. This should generally be // called only for resource objects whose instances have all been destroyed, // but that is not enforced by this method. func (s *SyncState) RemoveResource(addr addrs.AbsResource) { s.lock.Lock() defer s.lock.Unlock() ms := s.state.EnsureModule(addr.Module) ms.RemoveResource(addr.Resource) s.maybePruneModule(addr.Module) } // SetResourceInstanceCurrent saves the given instance object as the current // generation of the resource instance with the given address, simulataneously // updating the recorded provider configuration address, dependencies, and // resource EachMode. // // Any existing current instance object for the given resource is overwritten. // Set obj to nil to remove the primary generation object altogether. If there // are no deposed objects then the instance as a whole will be removed, which // may in turn also remove the containing module if it becomes empty. // // The caller must ensure that the given ResourceInstanceObject is not // concurrently mutated during this call, but may be freely used again once // this function returns. // // The provider address and "each mode" are resource-wide settings and so they // are updated for all other instances of the same resource as a side-effect of // this call. // // If the containing module for this resource or the resource itself are not // already tracked in state then they will be added as a side-effect. func (s *SyncState) SetResourceInstanceCurrent(addr addrs.AbsResourceInstance, obj *ResourceInstanceObject, provider addrs.AbsProviderConfig) { s.lock.Lock() defer s.lock.Unlock() ms := s.state.EnsureModule(addr.Module) ms.SetResourceInstanceCurrent(addr.Resource, obj.DeepCopy(), provider) } // SetResourceInstanceDeposed saves the given instance object as a deposed // generation of the resource instance with the given address and deposed key. // // Call this method only for pre-existing deposed objects that already have // a known DeposedKey. For example, this method is useful if reloading objects // that were persisted to a state file. To mark the current object as deposed, // use DeposeResourceInstanceObject instead. // // The caller must ensure that the given ResourceInstanceObject is not // concurrently mutated during this call, but may be freely used again once // this function returns. // // The resource that contains the given instance must already exist in the // state, or this method will panic. Use Resource to check first if its // presence is not already guaranteed. // // Any existing current instance object for the given resource and deposed key // is overwritten. Set obj to nil to remove the deposed object altogether. If // the instance is left with no objects after this operation then it will // be removed from its containing resource altogether. // // If the containing module for this resource or the resource itself are not // already tracked in state then they will be added as a side-effect. func (s *SyncState) SetResourceInstanceDeposed(addr addrs.AbsResourceInstance, key DeposedKey, obj *ResourceInstanceObject) { s.lock.Lock() defer s.lock.Unlock() ms := s.state.EnsureModule(addr.Module) ms.SetResourceInstanceDeposed(addr.Resource, key, obj.DeepCopy()) } // DeposeResourceInstanceObject moves the current instance object for the // given resource instance address into the deposed set, leaving the instance // without a current object. // // The return value is the newly-allocated deposed key, or NotDeposed if the // given instance is already lacking a current object. // // If the containing module for this resource or the resource itself are not // already tracked in state then there cannot be a current object for the // given instance, and so NotDeposed will be returned without modifying the // state at all. func (s *SyncState) DeposeResourceInstanceObject(addr addrs.AbsResourceInstance) DeposedKey { s.lock.Lock() defer s.lock.Unlock() ms := s.state.Module(addr.Module) if ms == nil { return NotDeposed } return ms.deposeResourceInstanceObject(addr.Resource) } // Lock acquires an explicit lock on the state, allowing direct read and write // access to the returned state object. The caller must call Unlock once // access is no longer needed, and then immediately discard the state pointer // pointer. // // Most callers should not use this. Instead, use the concurrency-safe // accessors and mutators provided directly on SyncState. func (s *SyncState) Lock() *State { s.lock.Lock() return s.state } // Unlock releases a lock previously acquired by Lock, at which point the // caller must cease all use of the state pointer that was returned. // // Do not call this method except to end an explicit lock acquired by // Lock. If a caller calls Unlock without first holding the lock, behavior // is undefined. func (s *SyncState) Unlock() { s.lock.Unlock() } // maybePruneModule will remove a module from the state altogether if it is // empty, unless it's the root module which must always be present. // // This helper method is not concurrency-safe on its own, so must only be // called while the caller is already holding the lock for writing. func (s *SyncState) maybePruneModule(addr addrs.ModuleInstance) { if addr.IsRoot() { // We never prune the root. return } ms := s.state.Module(addr) if ms == nil { return } if ms.empty() { s.state.RemoveModule(addr) } }