github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/states/sync.go (about) 1 package states 2 3 import ( 4 "log" 5 "sync" 6 7 "github.com/hashicorp/terraform-plugin-sdk/internal/addrs" 8 "github.com/zclconf/go-cty/cty" 9 ) 10 11 // SyncState is a wrapper around State that provides concurrency-safe access to 12 // various common operations that occur during a Terraform graph walk, or other 13 // similar concurrent contexts. 14 // 15 // When a SyncState wrapper is in use, no concurrent direct access to the 16 // underlying objects is permitted unless the caller first acquires an explicit 17 // lock, using the Lock and Unlock methods. Most callers should _not_ 18 // explicitly lock, and should instead use the other methods of this type that 19 // handle locking automatically. 20 // 21 // Since SyncState is able to safely consolidate multiple updates into a single 22 // atomic operation, many of its methods are at a higher level than those 23 // of the underlying types, and operate on the state as a whole rather than 24 // on individual sub-structures of the state. 25 // 26 // SyncState can only protect against races within its own methods. It cannot 27 // provide any guarantees about the order in which concurrent operations will 28 // be processed, so callers may still need to employ higher-level techniques 29 // for ensuring correct operation sequencing, such as building and walking 30 // a dependency graph. 31 type SyncState struct { 32 state *State 33 lock sync.RWMutex 34 } 35 36 // Module returns a snapshot of the state of the module instance with the given 37 // address, or nil if no such module is tracked. 38 // 39 // The return value is a pointer to a copy of the module state, which the 40 // caller may then freely access and mutate. However, since the module state 41 // tends to be a large data structure with many child objects, where possible 42 // callers should prefer to use a more granular accessor to access a child 43 // module directly, and thus reduce the amount of copying required. 44 func (s *SyncState) Module(addr addrs.ModuleInstance) *Module { 45 s.lock.RLock() 46 ret := s.state.Module(addr).DeepCopy() 47 s.lock.RUnlock() 48 return ret 49 } 50 51 // OutputValue returns a snapshot of the state of the output value with the 52 // given address, or nil if no such output value is tracked. 53 // 54 // The return value is a pointer to a copy of the output value state, which the 55 // caller may then freely access and mutate. 56 func (s *SyncState) OutputValue(addr addrs.AbsOutputValue) *OutputValue { 57 s.lock.RLock() 58 ret := s.state.OutputValue(addr).DeepCopy() 59 s.lock.RUnlock() 60 return ret 61 } 62 63 // SetOutputValue writes a given output value into the state, overwriting 64 // any existing value of the same name. 65 // 66 // If the module containing the output is not yet tracked in state then it 67 // be added as a side-effect. 68 func (s *SyncState) SetOutputValue(addr addrs.AbsOutputValue, value cty.Value, sensitive bool) { 69 s.lock.Lock() 70 defer s.lock.Unlock() 71 72 ms := s.state.EnsureModule(addr.Module) 73 ms.SetOutputValue(addr.OutputValue.Name, value, sensitive) 74 } 75 76 // RemoveOutputValue removes the stored value for the output value with the 77 // given address. 78 // 79 // If this results in its containing module being empty, the module will be 80 // pruned from the state as a side-effect. 81 func (s *SyncState) RemoveOutputValue(addr addrs.AbsOutputValue) { 82 s.lock.Lock() 83 defer s.lock.Unlock() 84 85 ms := s.state.Module(addr.Module) 86 if ms == nil { 87 return 88 } 89 ms.RemoveOutputValue(addr.OutputValue.Name) 90 s.maybePruneModule(addr.Module) 91 } 92 93 // LocalValue returns the current value associated with the given local value 94 // address. 95 func (s *SyncState) LocalValue(addr addrs.AbsLocalValue) cty.Value { 96 s.lock.RLock() 97 // cty.Value is immutable, so we don't need any extra copying here. 98 ret := s.state.LocalValue(addr) 99 s.lock.RUnlock() 100 return ret 101 } 102 103 // SetLocalValue writes a given output value into the state, overwriting 104 // any existing value of the same name. 105 // 106 // If the module containing the local value is not yet tracked in state then it 107 // will be added as a side-effect. 108 func (s *SyncState) SetLocalValue(addr addrs.AbsLocalValue, value cty.Value) { 109 s.lock.Lock() 110 defer s.lock.Unlock() 111 112 ms := s.state.EnsureModule(addr.Module) 113 ms.SetLocalValue(addr.LocalValue.Name, value) 114 } 115 116 // RemoveLocalValue removes the stored value for the local value with the 117 // given address. 118 // 119 // If this results in its containing module being empty, the module will be 120 // pruned from the state as a side-effect. 121 func (s *SyncState) RemoveLocalValue(addr addrs.AbsLocalValue) { 122 s.lock.Lock() 123 defer s.lock.Unlock() 124 125 ms := s.state.Module(addr.Module) 126 if ms == nil { 127 return 128 } 129 ms.RemoveLocalValue(addr.LocalValue.Name) 130 s.maybePruneModule(addr.Module) 131 } 132 133 // Resource returns a snapshot of the state of the resource with the given 134 // address, or nil if no such resource is tracked. 135 // 136 // The return value is a pointer to a copy of the resource state, which the 137 // caller may then freely access and mutate. 138 func (s *SyncState) Resource(addr addrs.AbsResource) *Resource { 139 s.lock.RLock() 140 ret := s.state.Resource(addr).DeepCopy() 141 s.lock.RUnlock() 142 return ret 143 } 144 145 // ResourceInstance returns a snapshot of the state the resource instance with 146 // the given address, or nil if no such instance is tracked. 147 // 148 // The return value is a pointer to a copy of the instance state, which the 149 // caller may then freely access and mutate. 150 func (s *SyncState) ResourceInstance(addr addrs.AbsResourceInstance) *ResourceInstance { 151 s.lock.RLock() 152 ret := s.state.ResourceInstance(addr).DeepCopy() 153 s.lock.RUnlock() 154 return ret 155 } 156 157 // ResourceInstanceObject returns a snapshot of the current instance object 158 // of the given generation belonging to the instance with the given address, 159 // or nil if no such object is tracked.. 160 // 161 // The return value is a pointer to a copy of the object, which the caller may 162 // then freely access and mutate. 163 func (s *SyncState) ResourceInstanceObject(addr addrs.AbsResourceInstance, gen Generation) *ResourceInstanceObjectSrc { 164 s.lock.RLock() 165 defer s.lock.RUnlock() 166 167 inst := s.state.ResourceInstance(addr) 168 if inst == nil { 169 return nil 170 } 171 return inst.GetGeneration(gen).DeepCopy() 172 } 173 174 // SetResourceMeta updates the resource-level metadata for the resource at 175 // the given address, creating the containing module state and resource state 176 // as a side-effect if not already present. 177 func (s *SyncState) SetResourceMeta(addr addrs.AbsResource, eachMode EachMode, provider addrs.AbsProviderConfig) { 178 s.lock.Lock() 179 defer s.lock.Unlock() 180 181 ms := s.state.EnsureModule(addr.Module) 182 ms.SetResourceMeta(addr.Resource, eachMode, provider) 183 } 184 185 // RemoveResourceIfEmpty is similar to RemoveResource but first checks to 186 // make sure there are no instances or objects left in the resource. 187 // 188 // Returns true if the resource was removed, or false if remaining child 189 // objects prevented its removal. Returns true also if the resource was 190 // already absent, and thus no action needed to be taken. 191 func (s *SyncState) RemoveResourceIfEmpty(addr addrs.AbsResource) bool { 192 s.lock.Lock() 193 defer s.lock.Unlock() 194 195 ms := s.state.Module(addr.Module) 196 if ms == nil { 197 return true // nothing to do 198 } 199 rs := ms.Resource(addr.Resource) 200 if rs == nil { 201 return true // nothing to do 202 } 203 if len(rs.Instances) != 0 { 204 // We don't check here for the possibility of instances that exist 205 // but don't have any objects because it's the responsibility of the 206 // instance-mutation methods to prune those away automatically. 207 return false 208 } 209 ms.RemoveResource(addr.Resource) 210 s.maybePruneModule(addr.Module) 211 return true 212 } 213 214 // MaybeFixUpResourceInstanceAddressForCount deals with the situation where a 215 // resource has changed from having "count" set to not set, or vice-versa, and 216 // so we need to rename the zeroth instance key to no key at all, or vice-versa. 217 // 218 // Set countEnabled to true if the resource has count set in its new 219 // configuration, or false if it does not. 220 // 221 // The state is modified in-place if necessary, moving a resource instance 222 // between the two addresses. The return value is true if a change was made, 223 // and false otherwise. 224 func (s *SyncState) MaybeFixUpResourceInstanceAddressForCount(addr addrs.AbsResource, countEnabled bool) bool { 225 s.lock.Lock() 226 defer s.lock.Unlock() 227 228 ms := s.state.Module(addr.Module) 229 if ms == nil { 230 return false 231 } 232 233 relAddr := addr.Resource 234 rs := ms.Resource(relAddr) 235 if rs == nil { 236 return false 237 } 238 huntKey := addrs.NoKey 239 replaceKey := addrs.InstanceKey(addrs.IntKey(0)) 240 if !countEnabled { 241 huntKey, replaceKey = replaceKey, huntKey 242 } 243 244 is, exists := rs.Instances[huntKey] 245 if !exists { 246 return false 247 } 248 249 if _, exists := rs.Instances[replaceKey]; exists { 250 // If the replacement key also exists then we'll do nothing and keep both. 251 return false 252 } 253 254 // If we get here then we need to "rename" from hunt to replace 255 rs.Instances[replaceKey] = is 256 delete(rs.Instances, huntKey) 257 return true 258 } 259 260 // SetResourceInstanceCurrent saves the given instance object as the current 261 // generation of the resource instance with the given address, simultaneously 262 // updating the recorded provider configuration address, dependencies, and 263 // resource EachMode. 264 // 265 // Any existing current instance object for the given resource is overwritten. 266 // Set obj to nil to remove the primary generation object altogether. If there 267 // are no deposed objects then the instance as a whole will be removed, which 268 // may in turn also remove the containing module if it becomes empty. 269 // 270 // The caller must ensure that the given ResourceInstanceObject is not 271 // concurrently mutated during this call, but may be freely used again once 272 // this function returns. 273 // 274 // The provider address and "each mode" are resource-wide settings and so they 275 // are updated for all other instances of the same resource as a side-effect of 276 // this call. 277 // 278 // If the containing module for this resource or the resource itself are not 279 // already tracked in state then they will be added as a side-effect. 280 func (s *SyncState) SetResourceInstanceCurrent(addr addrs.AbsResourceInstance, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig) { 281 s.lock.Lock() 282 defer s.lock.Unlock() 283 284 ms := s.state.EnsureModule(addr.Module) 285 ms.SetResourceInstanceCurrent(addr.Resource, obj.DeepCopy(), provider) 286 s.maybePruneModule(addr.Module) 287 } 288 289 // SetResourceInstanceDeposed saves the given instance object as a deposed 290 // generation of the resource instance with the given address and deposed key. 291 // 292 // Call this method only for pre-existing deposed objects that already have 293 // a known DeposedKey. For example, this method is useful if reloading objects 294 // that were persisted to a state file. To mark the current object as deposed, 295 // use DeposeResourceInstanceObject instead. 296 // 297 // The caller must ensure that the given ResourceInstanceObject is not 298 // concurrently mutated during this call, but may be freely used again once 299 // this function returns. 300 // 301 // The resource that contains the given instance must already exist in the 302 // state, or this method will panic. Use Resource to check first if its 303 // presence is not already guaranteed. 304 // 305 // Any existing current instance object for the given resource and deposed key 306 // is overwritten. Set obj to nil to remove the deposed object altogether. If 307 // the instance is left with no objects after this operation then it will 308 // be removed from its containing resource altogether. 309 // 310 // If the containing module for this resource or the resource itself are not 311 // already tracked in state then they will be added as a side-effect. 312 func (s *SyncState) SetResourceInstanceDeposed(addr addrs.AbsResourceInstance, key DeposedKey, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig) { 313 s.lock.Lock() 314 defer s.lock.Unlock() 315 316 ms := s.state.EnsureModule(addr.Module) 317 ms.SetResourceInstanceDeposed(addr.Resource, key, obj.DeepCopy(), provider) 318 s.maybePruneModule(addr.Module) 319 } 320 321 // DeposeResourceInstanceObject moves the current instance object for the 322 // given resource instance address into the deposed set, leaving the instance 323 // without a current object. 324 // 325 // The return value is the newly-allocated deposed key, or NotDeposed if the 326 // given instance is already lacking a current object. 327 // 328 // If the containing module for this resource or the resource itself are not 329 // already tracked in state then there cannot be a current object for the 330 // given instance, and so NotDeposed will be returned without modifying the 331 // state at all. 332 func (s *SyncState) DeposeResourceInstanceObject(addr addrs.AbsResourceInstance) DeposedKey { 333 s.lock.Lock() 334 defer s.lock.Unlock() 335 336 ms := s.state.Module(addr.Module) 337 if ms == nil { 338 return NotDeposed 339 } 340 341 return ms.deposeResourceInstanceObject(addr.Resource, NotDeposed) 342 } 343 344 // DeposeResourceInstanceObjectForceKey is like DeposeResourceInstanceObject 345 // but uses a pre-allocated key. It's the caller's responsibility to ensure 346 // that there aren't any races to use a particular key; this method will panic 347 // if the given key is already in use. 348 func (s *SyncState) DeposeResourceInstanceObjectForceKey(addr addrs.AbsResourceInstance, forcedKey DeposedKey) { 349 s.lock.Lock() 350 defer s.lock.Unlock() 351 352 if forcedKey == NotDeposed { 353 // Usage error: should use DeposeResourceInstanceObject in this case 354 panic("DeposeResourceInstanceObjectForceKey called without forced key") 355 } 356 357 ms := s.state.Module(addr.Module) 358 if ms == nil { 359 return // Nothing to do, since there can't be any current object either. 360 } 361 362 ms.deposeResourceInstanceObject(addr.Resource, forcedKey) 363 } 364 365 // MaybeRestoreResourceInstanceDeposed will restore the deposed object with the 366 // given key on the specified resource as the current object for that instance 367 // if and only if that would not cause us to forget an existing current 368 // object for that instance. 369 // 370 // Returns true if the object was restored to current, or false if no change 371 // was made at all. 372 func (s *SyncState) MaybeRestoreResourceInstanceDeposed(addr addrs.AbsResourceInstance, key DeposedKey) bool { 373 s.lock.Lock() 374 defer s.lock.Unlock() 375 376 if key == NotDeposed { 377 panic("MaybeRestoreResourceInstanceDeposed called without DeposedKey") 378 } 379 380 ms := s.state.Module(addr.Module) 381 if ms == nil { 382 // Nothing to do, since the specified deposed object cannot exist. 383 return false 384 } 385 386 return ms.maybeRestoreResourceInstanceDeposed(addr.Resource, key) 387 } 388 389 // RemovePlannedResourceInstanceObjects removes from the state any resource 390 // instance objects that have the status ObjectPlanned, indiciating that they 391 // are just transient placeholders created during planning. 392 // 393 // Note that this does not restore any "ready" or "tainted" object that might 394 // have been present before the planned object was written. The only real use 395 // for this method is in preparing the state created during a refresh walk, 396 // where we run the planning step for certain instances just to create enough 397 // information to allow correct expression evaluation within provider and 398 // data resource blocks. Discarding planned instances in that case is okay 399 // because the refresh phase only creates planned objects to stand in for 400 // objects that don't exist yet, and thus the planned object must have been 401 // absent before by definition. 402 func (s *SyncState) RemovePlannedResourceInstanceObjects() { 403 // TODO: Merge together the refresh and plan phases into a single walk, 404 // so we can remove the need to create this "partial plan" during refresh 405 // that we then need to clean up before proceeding. 406 407 s.lock.Lock() 408 defer s.lock.Unlock() 409 410 for _, ms := range s.state.Modules { 411 moduleAddr := ms.Addr 412 413 for _, rs := range ms.Resources { 414 resAddr := rs.Addr 415 416 for ik, is := range rs.Instances { 417 instAddr := resAddr.Instance(ik) 418 419 if is.Current != nil && is.Current.Status == ObjectPlanned { 420 // Setting the current instance to nil removes it from the 421 // state altogether if there are not also deposed instances. 422 ms.SetResourceInstanceCurrent(instAddr, nil, rs.ProviderConfig) 423 } 424 425 for dk, obj := range is.Deposed { 426 // Deposed objects should never be "planned", but we'll 427 // do this anyway for the sake of completeness. 428 if obj.Status == ObjectPlanned { 429 ms.ForgetResourceInstanceDeposed(instAddr, dk) 430 } 431 } 432 } 433 } 434 435 // We may have deleted some objects, which means that we may have 436 // left a module empty, and so we must prune to preserve the invariant 437 // that only the root module is allowed to be empty. 438 s.maybePruneModule(moduleAddr) 439 } 440 } 441 442 // Lock acquires an explicit lock on the state, allowing direct read and write 443 // access to the returned state object. The caller must call Unlock once 444 // access is no longer needed, and then immediately discard the state pointer 445 // pointer. 446 // 447 // Most callers should not use this. Instead, use the concurrency-safe 448 // accessors and mutators provided directly on SyncState. 449 func (s *SyncState) Lock() *State { 450 s.lock.Lock() 451 return s.state 452 } 453 454 // Unlock releases a lock previously acquired by Lock, at which point the 455 // caller must cease all use of the state pointer that was returned. 456 // 457 // Do not call this method except to end an explicit lock acquired by 458 // Lock. If a caller calls Unlock without first holding the lock, behavior 459 // is undefined. 460 func (s *SyncState) Unlock() { 461 s.lock.Unlock() 462 } 463 464 // maybePruneModule will remove a module from the state altogether if it is 465 // empty, unless it's the root module which must always be present. 466 // 467 // This helper method is not concurrency-safe on its own, so must only be 468 // called while the caller is already holding the lock for writing. 469 func (s *SyncState) maybePruneModule(addr addrs.ModuleInstance) { 470 if addr.IsRoot() { 471 // We never prune the root. 472 return 473 } 474 475 ms := s.state.Module(addr) 476 if ms == nil { 477 return 478 } 479 480 if ms.empty() { 481 log.Printf("[TRACE] states.SyncState: pruning %s because it is empty", addr) 482 s.state.RemoveModule(addr) 483 } 484 }