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