package states import ( "github.com/zclconf/go-cty/cty" "github.com/hashicorp/terraform/addrs" ) // Module is a container for the states of objects within a particular module. type Module struct { Addr addrs.ModuleInstance // Resources contains the state for each resource. The keys in this map are // an implementation detail and must not be used by outside callers. Resources map[string]*Resource // OutputValues contains the state for each output value. The keys in this // map are output value names. OutputValues map[string]*OutputValue // LocalValues contains the value for each named output value. The keys // in this map are local value names. LocalValues map[string]cty.Value } // NewModule constructs an empty module state for the given module address. func NewModule(addr addrs.ModuleInstance) *Module { return &Module{ Addr: addr, Resources: map[string]*Resource{}, OutputValues: map[string]*OutputValue{}, LocalValues: map[string]cty.Value{}, } } // Resource returns the state for the resource with the given address within // the receiving module state, or nil if the requested resource is not tracked // in the state. func (ms *Module) Resource(addr addrs.Resource) *Resource { return ms.Resources[addr.String()] } // ResourceInstance returns the state for the resource instance with the given // address within the receiving module state, or nil if the requested instance // is not tracked in the state. func (ms *Module) ResourceInstance(addr addrs.ResourceInstance) *ResourceInstance { rs := ms.Resource(addr.Resource) if rs == nil { return nil } return rs.Instance(addr.Key) } // SetResourceMeta updates the resource-level metadata for the resource // with the given address, creating the resource state for it if it doesn't // already exist. func (ms *Module) SetResourceMeta(addr addrs.Resource, eachMode EachMode, provider addrs.AbsProviderConfig) { rs := ms.Resource(addr) if rs == nil { rs = &Resource{ Addr: addr.Absolute(ms.Addr), Instances: map[addrs.InstanceKey]*ResourceInstance{}, } ms.Resources[addr.String()] = rs } rs.EachMode = eachMode rs.ProviderConfig = 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. func (ms *Module) RemoveResource(addr addrs.Resource) { delete(ms.Resources, addr.String()) } // SetResourceInstanceCurrent saves the given instance object as the current // generation of the resource instance with the given address, simultaneously // 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 will be removed altogether. // // 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. func (ms *Module) SetResourceInstanceCurrent(addr addrs.ResourceInstance, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig) { rs := ms.Resource(addr.Resource) // if the resource is nil and the object is nil, don't do anything! // you'll probably just cause issues if obj == nil && rs == nil { return } if obj == nil && rs != nil { // does the resource have any other objects? // if not then delete the whole resource // When deleting the resource, ensure that its EachMode is NoEach, // as a resource with EachList or EachMap can have 0 instances and be valid if rs.EachMode == NoEach && len(rs.Instances) == 0 { delete(ms.Resources, addr.Resource.String()) return } // check for an existing resource, now that we've ensured that rs.Instances is more than 0/not nil is := rs.Instance(addr.Key) if is == nil { // if there is no instance on the resource with this address and obj is nil, return and change nothing return } // if we have an instance, update the current is.Current = obj if !is.HasObjects() { // If we have no objects at all then we'll clean up. delete(rs.Instances, addr.Key) // Delete the resource if it has no instances, but only if NoEach if rs.EachMode == NoEach && len(rs.Instances) == 0 { delete(ms.Resources, addr.Resource.String()) return } } // Nothing more to do here, so return! return } if rs == nil && obj != nil { // We don't have have a resource so make one, which is a side effect of setResourceMeta ms.SetResourceMeta(addr.Resource, eachModeForInstanceKey(addr.Key), provider) // now we have a resource! so update the rs value to point to it rs = ms.Resource(addr.Resource) } // Get our instance from the resource; it could be there or not at this point is := rs.Instance(addr.Key) if is == nil { // if we don't have a resource, create one and add to the instances is = rs.CreateInstance(addr.Key) // update the resource meta because we have a new instance, so EachMode may have changed ms.SetResourceMeta(addr.Resource, eachModeForInstanceKey(addr.Key), provider) } // Update the resource's ProviderConfig, in case the provider has updated rs.ProviderConfig = provider is.Current = obj } // 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 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. func (ms *Module) SetResourceInstanceDeposed(addr addrs.ResourceInstance, key DeposedKey, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig) { ms.SetResourceMeta(addr.Resource, eachModeForInstanceKey(addr.Key), provider) rs := ms.Resource(addr.Resource) is := rs.EnsureInstance(addr.Key) if obj != nil { is.Deposed[key] = obj } else { delete(is.Deposed, key) } if !is.HasObjects() { // If we have no objects at all then we'll clean up. delete(rs.Instances, addr.Key) } if rs.EachMode == NoEach && len(rs.Instances) == 0 { // Also clean up if we only expect to have one instance anyway // and there are none. We leave the resource behind if an each mode // is active because an empty list or map of instances is a valid state. delete(ms.Resources, addr.Resource.String()) } } // ForgetResourceInstanceAll removes the record of all objects associated with // the specified resource instance, if present. If not present, this is a no-op. func (ms *Module) ForgetResourceInstanceAll(addr addrs.ResourceInstance) { rs := ms.Resource(addr.Resource) if rs == nil { return } delete(rs.Instances, addr.Key) if rs.EachMode == NoEach && len(rs.Instances) == 0 { // Also clean up if we only expect to have one instance anyway // and there are none. We leave the resource behind if an each mode // is active because an empty list or map of instances is a valid state. delete(ms.Resources, addr.Resource.String()) } } // ForgetResourceInstanceDeposed removes the record of the deposed object with // the given address and key, if present. If not present, this is a no-op. func (ms *Module) ForgetResourceInstanceDeposed(addr addrs.ResourceInstance, key DeposedKey) { rs := ms.Resource(addr.Resource) if rs == nil { return } is := rs.Instance(addr.Key) if is == nil { return } delete(is.Deposed, key) if !is.HasObjects() { // If we have no objects at all then we'll clean up. delete(rs.Instances, addr.Key) } if rs.EachMode == NoEach && len(rs.Instances) == 0 { // Also clean up if we only expect to have one instance anyway // and there are none. We leave the resource behind if an each mode // is active because an empty list or map of instances is a valid state. delete(ms.Resources, addr.Resource.String()) } } // deposeResourceInstanceObject is the real implementation of // SyncState.DeposeResourceInstanceObject. func (ms *Module) deposeResourceInstanceObject(addr addrs.ResourceInstance, forceKey DeposedKey) DeposedKey { is := ms.ResourceInstance(addr) if is == nil { return NotDeposed } return is.deposeCurrentObject(forceKey) } // maybeRestoreResourceInstanceDeposed is the real implementation of // SyncState.MaybeRestoreResourceInstanceDeposed. func (ms *Module) maybeRestoreResourceInstanceDeposed(addr addrs.ResourceInstance, key DeposedKey) bool { rs := ms.Resource(addr.Resource) if rs == nil { return false } is := rs.Instance(addr.Key) if is == nil { return false } if is.Current != nil { return false } if len(is.Deposed) == 0 { return false } is.Current = is.Deposed[key] delete(is.Deposed, key) return true } // SetOutputValue writes an output value into the state, overwriting any // existing value of the same name. func (ms *Module) SetOutputValue(name string, value cty.Value, sensitive bool) *OutputValue { os := &OutputValue{ Addr: addrs.AbsOutputValue{ Module: ms.Addr, OutputValue: addrs.OutputValue{ Name: name, }, }, Value: value, Sensitive: sensitive, } ms.OutputValues[name] = os return os } // RemoveOutputValue removes the output value of the given name from the state, // if it exists. This method is a no-op if there is no value of the given // name. func (ms *Module) RemoveOutputValue(name string) { delete(ms.OutputValues, name) } // SetLocalValue writes a local value into the state, overwriting any // existing value of the same name. func (ms *Module) SetLocalValue(name string, value cty.Value) { ms.LocalValues[name] = value } // RemoveLocalValue removes the local value of the given name from the state, // if it exists. This method is a no-op if there is no value of the given // name. func (ms *Module) RemoveLocalValue(name string) { delete(ms.LocalValues, name) } // PruneResourceHusks is a specialized method that will remove any Resource // objects that do not contain any instances, even if they have an EachMode. // // You probably shouldn't call this! See the method of the same name on // type State for more information on what this is for and the rare situations // where it is safe to use. func (ms *Module) PruneResourceHusks() { for _, rs := range ms.Resources { if len(rs.Instances) == 0 { ms.RemoveResource(rs.Addr.Resource) } } } // empty returns true if the receving module state is contributing nothing // to the state. In other words, it returns true if the module could be // removed from the state altogether without changing the meaning of the state. // // In practice a module containing no objects is the same as a non-existent // module, and so we can opportunistically clean up once a module becomes // empty on the assumption that it will be re-added if needed later. func (ms *Module) empty() bool { if ms == nil { return true } // This must be updated to cover any new collections added to Module // in future. return (len(ms.Resources) == 0 && len(ms.OutputValues) == 0 && len(ms.LocalValues) == 0) }