1package states 2 3import ( 4 "github.com/zclconf/go-cty/cty" 5 6 "github.com/hashicorp/terraform/internal/addrs" 7) 8 9// Module is a container for the states of objects within a particular module. 10type Module struct { 11 Addr addrs.ModuleInstance 12 13 // Resources contains the state for each resource. The keys in this map are 14 // an implementation detail and must not be used by outside callers. 15 Resources map[string]*Resource 16 17 // OutputValues contains the state for each output value. The keys in this 18 // map are output value names. 19 OutputValues map[string]*OutputValue 20 21 // LocalValues contains the value for each named output value. The keys 22 // in this map are local value names. 23 LocalValues map[string]cty.Value 24} 25 26// NewModule constructs an empty module state for the given module address. 27func NewModule(addr addrs.ModuleInstance) *Module { 28 return &Module{ 29 Addr: addr, 30 Resources: map[string]*Resource{}, 31 OutputValues: map[string]*OutputValue{}, 32 LocalValues: map[string]cty.Value{}, 33 } 34} 35 36// Resource returns the state for the resource with the given address within 37// the receiving module state, or nil if the requested resource is not tracked 38// in the state. 39func (ms *Module) Resource(addr addrs.Resource) *Resource { 40 return ms.Resources[addr.String()] 41} 42 43// ResourceInstance returns the state for the resource instance with the given 44// address within the receiving module state, or nil if the requested instance 45// is not tracked in the state. 46func (ms *Module) ResourceInstance(addr addrs.ResourceInstance) *ResourceInstance { 47 rs := ms.Resource(addr.Resource) 48 if rs == nil { 49 return nil 50 } 51 return rs.Instance(addr.Key) 52} 53 54// SetResourceProvider updates the resource-level metadata for the resource 55// with the given address, creating the resource state for it if it doesn't 56// already exist. 57func (ms *Module) SetResourceProvider(addr addrs.Resource, provider addrs.AbsProviderConfig) { 58 rs := ms.Resource(addr) 59 if rs == nil { 60 rs = &Resource{ 61 Addr: addr.Absolute(ms.Addr), 62 Instances: map[addrs.InstanceKey]*ResourceInstance{}, 63 } 64 ms.Resources[addr.String()] = rs 65 } 66 67 rs.ProviderConfig = provider 68} 69 70// RemoveResource removes the entire state for the given resource, taking with 71// it any instances associated with the resource. This should generally be 72// called only for resource objects whose instances have all been destroyed. 73func (ms *Module) RemoveResource(addr addrs.Resource) { 74 delete(ms.Resources, addr.String()) 75} 76 77// SetResourceInstanceCurrent saves the given instance object as the current 78// generation of the resource instance with the given address, simultaneously 79// updating the recorded provider configuration address and dependencies. 80// 81// Any existing current instance object for the given resource is overwritten. 82// Set obj to nil to remove the primary generation object altogether. If there 83// are no deposed objects then the instance will be removed altogether. 84// 85// The provider address is a resource-wide setting and is updated for all other 86// instances of the same resource as a side-effect of this call. 87func (ms *Module) SetResourceInstanceCurrent(addr addrs.ResourceInstance, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig) { 88 rs := ms.Resource(addr.Resource) 89 // if the resource is nil and the object is nil, don't do anything! 90 // you'll probably just cause issues 91 if obj == nil && rs == nil { 92 return 93 } 94 if obj == nil && rs != nil { 95 // does the resource have any other objects? 96 // if not then delete the whole resource 97 if len(rs.Instances) == 0 { 98 delete(ms.Resources, addr.Resource.String()) 99 return 100 } 101 // check for an existing resource, now that we've ensured that rs.Instances is more than 0/not nil 102 is := rs.Instance(addr.Key) 103 if is == nil { 104 // if there is no instance on the resource with this address and obj is nil, return and change nothing 105 return 106 } 107 // if we have an instance, update the current 108 is.Current = obj 109 if !is.HasObjects() { 110 // If we have no objects at all then we'll clean up. 111 delete(rs.Instances, addr.Key) 112 // Delete the resource if it has no instances, but only if NoEach 113 if len(rs.Instances) == 0 { 114 delete(ms.Resources, addr.Resource.String()) 115 return 116 } 117 } 118 // Nothing more to do here, so return! 119 return 120 } 121 if rs == nil && obj != nil { 122 // We don't have have a resource so make one, which is a side effect of setResourceMeta 123 ms.SetResourceProvider(addr.Resource, provider) 124 // now we have a resource! so update the rs value to point to it 125 rs = ms.Resource(addr.Resource) 126 } 127 // Get our instance from the resource; it could be there or not at this point 128 is := rs.Instance(addr.Key) 129 if is == nil { 130 // if we don't have a resource, create one and add to the instances 131 is = rs.CreateInstance(addr.Key) 132 // update the resource meta because we have a new 133 ms.SetResourceProvider(addr.Resource, provider) 134 } 135 // Update the resource's ProviderConfig, in case the provider has updated 136 rs.ProviderConfig = provider 137 is.Current = obj 138} 139 140// SetResourceInstanceDeposed saves the given instance object as a deposed 141// generation of the resource instance with the given address and deposed key. 142// 143// Call this method only for pre-existing deposed objects that already have 144// a known DeposedKey. For example, this method is useful if reloading objects 145// that were persisted to a state file. To mark the current object as deposed, 146// use DeposeResourceInstanceObject instead. 147// 148// The resource that contains the given instance must already exist in the 149// state, or this method will panic. Use Resource to check first if its 150// presence is not already guaranteed. 151// 152// Any existing current instance object for the given resource and deposed key 153// is overwritten. Set obj to nil to remove the deposed object altogether. If 154// the instance is left with no objects after this operation then it will 155// be removed from its containing resource altogether. 156func (ms *Module) SetResourceInstanceDeposed(addr addrs.ResourceInstance, key DeposedKey, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig) { 157 ms.SetResourceProvider(addr.Resource, provider) 158 159 rs := ms.Resource(addr.Resource) 160 is := rs.EnsureInstance(addr.Key) 161 if obj != nil { 162 is.Deposed[key] = obj 163 } else { 164 delete(is.Deposed, key) 165 } 166 167 if !is.HasObjects() { 168 // If we have no objects at all then we'll clean up. 169 delete(rs.Instances, addr.Key) 170 } 171 if len(rs.Instances) == 0 { 172 // Also clean up if we only expect to have one instance anyway 173 // and there are none. We leave the resource behind if an each mode 174 // is active because an empty list or map of instances is a valid state. 175 delete(ms.Resources, addr.Resource.String()) 176 } 177} 178 179// ForgetResourceInstanceAll removes the record of all objects associated with 180// the specified resource instance, if present. If not present, this is a no-op. 181func (ms *Module) ForgetResourceInstanceAll(addr addrs.ResourceInstance) { 182 rs := ms.Resource(addr.Resource) 183 if rs == nil { 184 return 185 } 186 delete(rs.Instances, addr.Key) 187 188 if len(rs.Instances) == 0 { 189 // Also clean up if we only expect to have one instance anyway 190 // and there are none. We leave the resource behind if an each mode 191 // is active because an empty list or map of instances is a valid state. 192 delete(ms.Resources, addr.Resource.String()) 193 } 194} 195 196// ForgetResourceInstanceDeposed removes the record of the deposed object with 197// the given address and key, if present. If not present, this is a no-op. 198func (ms *Module) ForgetResourceInstanceDeposed(addr addrs.ResourceInstance, key DeposedKey) { 199 rs := ms.Resource(addr.Resource) 200 if rs == nil { 201 return 202 } 203 is := rs.Instance(addr.Key) 204 if is == nil { 205 return 206 } 207 delete(is.Deposed, key) 208 209 if !is.HasObjects() { 210 // If we have no objects at all then we'll clean up. 211 delete(rs.Instances, addr.Key) 212 } 213 if len(rs.Instances) == 0 { 214 // Also clean up if we only expect to have one instance anyway 215 // and there are none. We leave the resource behind if an each mode 216 // is active because an empty list or map of instances is a valid state. 217 delete(ms.Resources, addr.Resource.String()) 218 } 219} 220 221// deposeResourceInstanceObject is the real implementation of 222// SyncState.DeposeResourceInstanceObject. 223func (ms *Module) deposeResourceInstanceObject(addr addrs.ResourceInstance, forceKey DeposedKey) DeposedKey { 224 is := ms.ResourceInstance(addr) 225 if is == nil { 226 return NotDeposed 227 } 228 return is.deposeCurrentObject(forceKey) 229} 230 231// maybeRestoreResourceInstanceDeposed is the real implementation of 232// SyncState.MaybeRestoreResourceInstanceDeposed. 233func (ms *Module) maybeRestoreResourceInstanceDeposed(addr addrs.ResourceInstance, key DeposedKey) bool { 234 rs := ms.Resource(addr.Resource) 235 if rs == nil { 236 return false 237 } 238 is := rs.Instance(addr.Key) 239 if is == nil { 240 return false 241 } 242 if is.Current != nil { 243 return false 244 } 245 if len(is.Deposed) == 0 { 246 return false 247 } 248 is.Current = is.Deposed[key] 249 delete(is.Deposed, key) 250 return true 251} 252 253// SetOutputValue writes an output value into the state, overwriting any 254// existing value of the same name. 255func (ms *Module) SetOutputValue(name string, value cty.Value, sensitive bool) *OutputValue { 256 os := &OutputValue{ 257 Addr: addrs.AbsOutputValue{ 258 Module: ms.Addr, 259 OutputValue: addrs.OutputValue{ 260 Name: name, 261 }, 262 }, 263 Value: value, 264 Sensitive: sensitive, 265 } 266 ms.OutputValues[name] = os 267 return os 268} 269 270// RemoveOutputValue removes the output value of the given name from the state, 271// if it exists. This method is a no-op if there is no value of the given 272// name. 273func (ms *Module) RemoveOutputValue(name string) { 274 delete(ms.OutputValues, name) 275} 276 277// SetLocalValue writes a local value into the state, overwriting any 278// existing value of the same name. 279func (ms *Module) SetLocalValue(name string, value cty.Value) { 280 ms.LocalValues[name] = value 281} 282 283// RemoveLocalValue removes the local value of the given name from the state, 284// if it exists. This method is a no-op if there is no value of the given 285// name. 286func (ms *Module) RemoveLocalValue(name string) { 287 delete(ms.LocalValues, name) 288} 289 290// PruneResourceHusks is a specialized method that will remove any Resource 291// objects that do not contain any instances, even if they have an EachMode. 292// 293// You probably shouldn't call this! See the method of the same name on 294// type State for more information on what this is for and the rare situations 295// where it is safe to use. 296func (ms *Module) PruneResourceHusks() { 297 for _, rs := range ms.Resources { 298 if len(rs.Instances) == 0 { 299 ms.RemoveResource(rs.Addr.Resource) 300 } 301 } 302} 303 304// empty returns true if the receving module state is contributing nothing 305// to the state. In other words, it returns true if the module could be 306// removed from the state altogether without changing the meaning of the state. 307// 308// In practice a module containing no objects is the same as a non-existent 309// module, and so we can opportunistically clean up once a module becomes 310// empty on the assumption that it will be re-added if needed later. 311func (ms *Module) empty() bool { 312 if ms == nil { 313 return true 314 } 315 316 // This must be updated to cover any new collections added to Module 317 // in future. 318 return (len(ms.Resources) == 0 && 319 len(ms.OutputValues) == 0 && 320 len(ms.LocalValues) == 0) 321} 322