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