github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/states/state.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package states 5 6 import ( 7 "fmt" 8 "sort" 9 10 "github.com/zclconf/go-cty/cty" 11 12 "github.com/terramate-io/tf/addrs" 13 "github.com/terramate-io/tf/getproviders" 14 ) 15 16 // State is the top-level type of a Terraform state. 17 // 18 // A state should be mutated only via its accessor methods, to ensure that 19 // invariants are preserved. 20 // 21 // Access to State and the nested values within it is not concurrency-safe, 22 // so when accessing a State object concurrently it is the caller's 23 // responsibility to ensure that only one write is in progress at a time 24 // and that reads only occur when no write is in progress. The most common 25 // way to achieve this is to wrap the State in a SyncState and use the 26 // higher-level atomic operations supported by that type. 27 type State struct { 28 // Modules contains the state for each module. The keys in this map are 29 // an implementation detail and must not be used by outside callers. 30 Modules map[string]*Module 31 32 // CheckResults contains a snapshot of the statuses of checks at the 33 // end of the most recent update to the state. Callers might compare 34 // checks between runs to see if e.g. a previously-failing check has 35 // been fixed since the last run, or similar. 36 // 37 // CheckResults can be nil to indicate that there are no check results 38 // from the previous run at all, which is subtly different than the 39 // previous run having affirmatively recorded that there are no checks 40 // to run. For example, if this object was created from a state snapshot 41 // created by a version of Terraform that didn't yet support checks 42 // then this field will be nil. 43 CheckResults *CheckResults 44 } 45 46 // NewState constructs a minimal empty state, containing an empty root module. 47 func NewState() *State { 48 modules := map[string]*Module{} 49 modules[addrs.RootModuleInstance.String()] = NewModule(addrs.RootModuleInstance) 50 return &State{ 51 Modules: modules, 52 } 53 } 54 55 // BuildState is a helper -- primarily intended for tests -- to build a state 56 // using imperative code against the StateSync type while still acting as 57 // an expression of type *State to assign into a containing struct. 58 func BuildState(cb func(*SyncState)) *State { 59 s := NewState() 60 cb(s.SyncWrapper()) 61 return s 62 } 63 64 // Empty returns true if there are no resources or populated output values 65 // in the receiver. In other words, if this state could be safely replaced 66 // with the return value of NewState and be functionally equivalent. 67 func (s *State) Empty() bool { 68 if s == nil { 69 return true 70 } 71 for _, ms := range s.Modules { 72 if len(ms.Resources) != 0 { 73 return false 74 } 75 if len(ms.OutputValues) != 0 { 76 return false 77 } 78 } 79 return true 80 } 81 82 // Module returns the state for the module with the given address, or nil if 83 // the requested module is not tracked in the state. 84 func (s *State) Module(addr addrs.ModuleInstance) *Module { 85 if s == nil { 86 panic("State.Module on nil *State") 87 } 88 return s.Modules[addr.String()] 89 } 90 91 // ModuleInstances returns the set of Module states that matches the given path. 92 func (s *State) ModuleInstances(addr addrs.Module) []*Module { 93 var ms []*Module 94 for _, m := range s.Modules { 95 if m.Addr.Module().Equal(addr) { 96 ms = append(ms, m) 97 } 98 } 99 return ms 100 } 101 102 // ModuleOutputs returns all outputs for the given module call under the 103 // parentAddr instance. 104 func (s *State) ModuleOutputs(parentAddr addrs.ModuleInstance, module addrs.ModuleCall) []*OutputValue { 105 var os []*OutputValue 106 for _, m := range s.Modules { 107 // can't get outputs from the root module 108 if m.Addr.IsRoot() { 109 continue 110 } 111 112 parent, call := m.Addr.Call() 113 // make sure this is a descendent in the correct path 114 if !parentAddr.Equal(parent) { 115 continue 116 } 117 118 // and check if this is the correct child 119 if call.Name != module.Name { 120 continue 121 } 122 123 for _, o := range m.OutputValues { 124 os = append(os, o) 125 } 126 } 127 128 return os 129 } 130 131 // RemoveModule removes the module with the given address from the state, 132 // unless it is the root module. The root module cannot be deleted, and so 133 // this method will panic if that is attempted. 134 // 135 // Removing a module implicitly discards all of the resources, outputs and 136 // local values within it, and so this should usually be done only for empty 137 // modules. For callers accessing the state through a SyncState wrapper, modules 138 // are automatically pruned if they are empty after one of their contained 139 // elements is removed. 140 func (s *State) RemoveModule(addr addrs.ModuleInstance) { 141 if addr.IsRoot() { 142 panic("attempted to remove root module") 143 } 144 145 delete(s.Modules, addr.String()) 146 } 147 148 // RootModule is a convenient alias for Module(addrs.RootModuleInstance). 149 func (s *State) RootModule() *Module { 150 if s == nil { 151 panic("RootModule called on nil State") 152 } 153 return s.Modules[addrs.RootModuleInstance.String()] 154 } 155 156 // EnsureModule returns the state for the module with the given address, 157 // creating and adding a new one if necessary. 158 // 159 // Since this might modify the state to add a new instance, it is considered 160 // to be a write operation. 161 func (s *State) EnsureModule(addr addrs.ModuleInstance) *Module { 162 ms := s.Module(addr) 163 if ms == nil { 164 ms = NewModule(addr) 165 s.Modules[addr.String()] = ms 166 } 167 return ms 168 } 169 170 // HasManagedResourceInstanceObjects returns true if there is at least one 171 // resource instance object (current or deposed) associated with a managed 172 // resource in the receiving state. 173 // 174 // A true result would suggest that just discarding this state without first 175 // destroying these objects could leave "dangling" objects in remote systems, 176 // no longer tracked by any Terraform state. 177 func (s *State) HasManagedResourceInstanceObjects() bool { 178 if s == nil { 179 return false 180 } 181 for _, ms := range s.Modules { 182 for _, rs := range ms.Resources { 183 if rs.Addr.Resource.Mode != addrs.ManagedResourceMode { 184 continue 185 } 186 for _, is := range rs.Instances { 187 if is.Current != nil || len(is.Deposed) != 0 { 188 return true 189 } 190 } 191 } 192 } 193 return false 194 } 195 196 // Resource returns the state for the resource with the given address, or nil 197 // if no such resource is tracked in the state. 198 func (s *State) Resource(addr addrs.AbsResource) *Resource { 199 ms := s.Module(addr.Module) 200 if ms == nil { 201 return nil 202 } 203 return ms.Resource(addr.Resource) 204 } 205 206 // Resources returns the set of resources that match the given configuration path. 207 func (s *State) Resources(addr addrs.ConfigResource) []*Resource { 208 var ret []*Resource 209 for _, m := range s.ModuleInstances(addr.Module) { 210 r := m.Resource(addr.Resource) 211 if r != nil { 212 ret = append(ret, r) 213 } 214 } 215 return ret 216 } 217 218 // AllManagedResourceInstanceObjectAddrs returns a set of addresses for all of 219 // the leaf resource instance objects associated with managed resources that 220 // are tracked in this state. 221 // 222 // This result is the set of objects that would be effectively "forgotten" 223 // (like "terraform state rm") if this state were totally discarded, such as 224 // by deleting a workspace. This function is intended only for reporting 225 // context in error messages, such as when we reject deleting a "non-empty" 226 // workspace as detected by s.HasManagedResourceInstanceObjects. 227 // 228 // The ordering of the result is meaningless but consistent. DeposedKey will 229 // be NotDeposed (the zero value of DeposedKey) for any "current" objects. 230 // This method is guaranteed to return at least one item if 231 // s.HasManagedResourceInstanceObjects returns true for the same state, and 232 // to return a zero-length slice if it returns false. 233 func (s *State) AllResourceInstanceObjectAddrs() []struct { 234 Instance addrs.AbsResourceInstance 235 DeposedKey DeposedKey 236 } { 237 if s == nil { 238 return nil 239 } 240 241 // We use an unnamed return type here just because we currently have no 242 // general need to return pairs of instance address and deposed key aside 243 // from this method, and this method itself is only of marginal value 244 // when producing some error messages. 245 // 246 // If that need ends up arising more in future then it might make sense to 247 // name this as addrs.AbsResourceInstanceObject, although that would require 248 // moving DeposedKey into the addrs package too. 249 type ResourceInstanceObject = struct { 250 Instance addrs.AbsResourceInstance 251 DeposedKey DeposedKey 252 } 253 var ret []ResourceInstanceObject 254 255 for _, ms := range s.Modules { 256 for _, rs := range ms.Resources { 257 if rs.Addr.Resource.Mode != addrs.ManagedResourceMode { 258 continue 259 } 260 261 for instKey, is := range rs.Instances { 262 instAddr := rs.Addr.Instance(instKey) 263 if is.Current != nil { 264 ret = append(ret, ResourceInstanceObject{instAddr, NotDeposed}) 265 } 266 for deposedKey := range is.Deposed { 267 ret = append(ret, ResourceInstanceObject{instAddr, deposedKey}) 268 } 269 } 270 } 271 } 272 273 sort.SliceStable(ret, func(i, j int) bool { 274 objI, objJ := ret[i], ret[j] 275 switch { 276 case !objI.Instance.Equal(objJ.Instance): 277 return objI.Instance.Less(objJ.Instance) 278 default: 279 return objI.DeposedKey < objJ.DeposedKey 280 } 281 }) 282 283 return ret 284 } 285 286 // ResourceInstance returns the state for the resource instance with the given 287 // address, or nil if no such resource is tracked in the state. 288 func (s *State) ResourceInstance(addr addrs.AbsResourceInstance) *ResourceInstance { 289 if s == nil { 290 panic("State.ResourceInstance on nil *State") 291 } 292 ms := s.Module(addr.Module) 293 if ms == nil { 294 return nil 295 } 296 return ms.ResourceInstance(addr.Resource) 297 } 298 299 // OutputValue returns the state for the output value with the given address, 300 // or nil if no such output value is tracked in the state. 301 func (s *State) OutputValue(addr addrs.AbsOutputValue) *OutputValue { 302 ms := s.Module(addr.Module) 303 if ms == nil { 304 return nil 305 } 306 return ms.OutputValues[addr.OutputValue.Name] 307 } 308 309 // LocalValue returns the value of the named local value with the given address, 310 // or cty.NilVal if no such value is tracked in the state. 311 func (s *State) LocalValue(addr addrs.AbsLocalValue) cty.Value { 312 ms := s.Module(addr.Module) 313 if ms == nil { 314 return cty.NilVal 315 } 316 return ms.LocalValues[addr.LocalValue.Name] 317 } 318 319 // ProviderAddrs returns a list of all of the provider configuration addresses 320 // referenced throughout the receiving state. 321 // 322 // The result is de-duplicated so that each distinct address appears only once. 323 func (s *State) ProviderAddrs() []addrs.AbsProviderConfig { 324 if s == nil { 325 return nil 326 } 327 328 m := map[string]addrs.AbsProviderConfig{} 329 for _, ms := range s.Modules { 330 for _, rc := range ms.Resources { 331 m[rc.ProviderConfig.String()] = rc.ProviderConfig 332 } 333 } 334 if len(m) == 0 { 335 return nil 336 } 337 338 // This is mainly just so we'll get stable results for testing purposes. 339 keys := make([]string, 0, len(m)) 340 for k := range m { 341 keys = append(keys, k) 342 } 343 sort.Strings(keys) 344 345 ret := make([]addrs.AbsProviderConfig, len(keys)) 346 for i, key := range keys { 347 ret[i] = m[key] 348 } 349 350 return ret 351 } 352 353 // ProviderRequirements returns a description of all of the providers that 354 // are required to work with the receiving state. 355 // 356 // Because the state does not track specific version information for providers, 357 // the requirements returned by this method will always be unconstrained. 358 // The result should usually be merged with a Requirements derived from the 359 // current configuration in order to apply some constraints. 360 func (s *State) ProviderRequirements() getproviders.Requirements { 361 configAddrs := s.ProviderAddrs() 362 ret := make(getproviders.Requirements, len(configAddrs)) 363 for _, configAddr := range configAddrs { 364 ret[configAddr.Provider] = nil // unconstrained dependency 365 } 366 return ret 367 } 368 369 // PruneResourceHusks is a specialized method that will remove any Resource 370 // objects that do not contain any instances, even if they have an EachMode. 371 // 372 // This should generally be used only after a "terraform destroy" operation, 373 // to finalize the cleanup of the state. It is not correct to use this after 374 // other operations because if a resource has "count = 0" or "for_each" over 375 // an empty collection then we want to retain it in the state so that references 376 // to it, particularly in "strange" contexts like "terraform console", can be 377 // properly resolved. 378 // 379 // This method MUST NOT be called concurrently with other readers and writers 380 // of the receiving state. 381 func (s *State) PruneResourceHusks() { 382 for _, m := range s.Modules { 383 m.PruneResourceHusks() 384 if len(m.Resources) == 0 && !m.Addr.IsRoot() { 385 s.RemoveModule(m.Addr) 386 } 387 } 388 } 389 390 // SyncWrapper returns a SyncState object wrapping the receiver. 391 func (s *State) SyncWrapper() *SyncState { 392 return &SyncState{ 393 state: s, 394 } 395 } 396 397 // MoveAbsResource moves the given src AbsResource's current state to the new 398 // dst address. This will panic if the src AbsResource does not exist in state, 399 // or if there is already a resource at the dst address. It is the caller's 400 // responsibility to verify the validity of the move (for example, that the src 401 // and dst are compatible types). 402 func (s *State) MoveAbsResource(src, dst addrs.AbsResource) { 403 // verify that the src address exists and the dst address does not 404 rs := s.Resource(src) 405 if rs == nil { 406 panic(fmt.Sprintf("no state for src address %s", src.String())) 407 } 408 409 ds := s.Resource(dst) 410 if ds != nil { 411 panic(fmt.Sprintf("dst resource %s already exists", dst.String())) 412 } 413 414 ms := s.Module(src.Module) 415 ms.RemoveResource(src.Resource) 416 417 // Remove the module if it is empty (and not root) after removing the 418 // resource. 419 if !ms.Addr.IsRoot() && ms.empty() { 420 s.RemoveModule(src.Module) 421 } 422 423 // Update the address before adding it to the state 424 rs.Addr = dst 425 s.EnsureModule(dst.Module).Resources[dst.Resource.String()] = rs 426 } 427 428 // MaybeMoveAbsResource moves the given src AbsResource's current state to the 429 // new dst address. This function will succeed if both the src address does not 430 // exist in state and the dst address does; the return value indicates whether 431 // or not the move occurred. This function will panic if either the src does not 432 // exist or the dst does exist (but not both). 433 func (s *State) MaybeMoveAbsResource(src, dst addrs.AbsResource) bool { 434 // Get the source and destinatation addresses from state. 435 rs := s.Resource(src) 436 ds := s.Resource(dst) 437 438 // Normal case: the src exists in state, dst does not 439 if rs != nil && ds == nil { 440 s.MoveAbsResource(src, dst) 441 return true 442 } 443 444 if rs == nil && ds != nil { 445 // The source is not in state, the destination is. This is not 446 // guaranteed to be idempotent since we aren't tracking exact moves, but 447 // it's useful information for the caller. 448 return false 449 } else { 450 panic("invalid move") 451 } 452 } 453 454 // MoveAbsResourceInstance moves the given src AbsResourceInstance's current state to 455 // the new dst address. This will panic if the src AbsResourceInstance does not 456 // exist in state, or if there is already a resource at the dst address. It is 457 // the caller's responsibility to verify the validity of the move (for example, 458 // that the src and dst are compatible types). 459 func (s *State) MoveAbsResourceInstance(src, dst addrs.AbsResourceInstance) { 460 srcInstanceState := s.ResourceInstance(src) 461 if srcInstanceState == nil { 462 panic(fmt.Sprintf("no state for src address %s", src.String())) 463 } 464 465 dstInstanceState := s.ResourceInstance(dst) 466 if dstInstanceState != nil { 467 panic(fmt.Sprintf("dst resource %s already exists", dst.String())) 468 } 469 470 srcResourceState := s.Resource(src.ContainingResource()) 471 srcProviderAddr := srcResourceState.ProviderConfig 472 dstResourceAddr := dst.ContainingResource() 473 474 // Remove the source resource instance from the module's state, and then the 475 // module if empty. 476 ms := s.Module(src.Module) 477 ms.ForgetResourceInstanceAll(src.Resource) 478 if !ms.Addr.IsRoot() && ms.empty() { 479 s.RemoveModule(src.Module) 480 } 481 482 dstModule := s.EnsureModule(dst.Module) 483 484 // See if there is already a resource we can add this instance to. 485 dstResourceState := s.Resource(dstResourceAddr) 486 if dstResourceState == nil { 487 // If we're moving to an address without an index then that 488 // suggests the user's intent is to establish both the 489 // resource and the instance at the same time (since the 490 // address covers both). If there's an index in the 491 // target then allow creating the new instance here. 492 dstModule.SetResourceProvider( 493 dstResourceAddr.Resource, 494 srcProviderAddr, // in this case, we bring the provider along as if we were moving the whole resource 495 ) 496 dstResourceState = dstModule.Resource(dstResourceAddr.Resource) 497 } 498 499 dstResourceState.Instances[dst.Resource.Key] = srcInstanceState 500 } 501 502 // MaybeMoveAbsResourceInstance moves the given src AbsResourceInstance's 503 // current state to the new dst address. This function will succeed if both the 504 // src address does not exist in state and the dst address does; the return 505 // value indicates whether or not the move occured. This function will panic if 506 // either the src does not exist or the dst does exist (but not both). 507 func (s *State) MaybeMoveAbsResourceInstance(src, dst addrs.AbsResourceInstance) bool { 508 // get the src and dst resource instances from state 509 rs := s.ResourceInstance(src) 510 ds := s.ResourceInstance(dst) 511 512 // Normal case: the src exists in state, dst does not 513 if rs != nil && ds == nil { 514 s.MoveAbsResourceInstance(src, dst) 515 return true 516 } 517 518 if rs == nil && ds != nil { 519 // The source is not in state, the destination is. This is not 520 // guaranteed to be idempotent since we aren't tracking exact moves, but 521 // it's useful information. 522 return false 523 } else { 524 panic("invalid move") 525 } 526 } 527 528 // MoveModuleInstance moves the given src ModuleInstance's current state to the 529 // new dst address. This will panic if the src ModuleInstance does not 530 // exist in state, or if there is already a resource at the dst address. It is 531 // the caller's responsibility to verify the validity of the move. 532 func (s *State) MoveModuleInstance(src, dst addrs.ModuleInstance) { 533 if src.IsRoot() || dst.IsRoot() { 534 panic("cannot move to or from root module") 535 } 536 537 srcMod := s.Module(src) 538 if srcMod == nil { 539 panic(fmt.Sprintf("no state for src module %s", src.String())) 540 } 541 542 dstMod := s.Module(dst) 543 if dstMod != nil { 544 panic(fmt.Sprintf("dst module %s already exists in state", dst.String())) 545 } 546 547 s.RemoveModule(src) 548 549 srcMod.Addr = dst 550 s.EnsureModule(dst) 551 s.Modules[dst.String()] = srcMod 552 553 // Update any Resource's addresses. 554 if srcMod.Resources != nil { 555 for _, r := range srcMod.Resources { 556 r.Addr.Module = dst 557 } 558 } 559 560 // Update any OutputValues's addresses. 561 if srcMod.OutputValues != nil { 562 for _, ov := range srcMod.OutputValues { 563 ov.Addr.Module = dst 564 } 565 } 566 } 567 568 // MaybeMoveModuleInstance moves the given src ModuleInstance's current state to 569 // the new dst address. This function will succeed if both the src address does 570 // not exist in state and the dst address does; the return value indicates 571 // whether or not the move occured. This function will panic if either the src 572 // does not exist or the dst does exist (but not both). 573 func (s *State) MaybeMoveModuleInstance(src, dst addrs.ModuleInstance) bool { 574 if src.IsRoot() || dst.IsRoot() { 575 panic("cannot move to or from root module") 576 } 577 578 srcMod := s.Module(src) 579 dstMod := s.Module(dst) 580 581 // Normal case: the src exists in state, dst does not 582 if srcMod != nil && dstMod == nil { 583 s.MoveModuleInstance(src, dst) 584 return true 585 } 586 587 if srcMod == nil || src.IsRoot() && dstMod != nil { 588 // The source is not in state, the destination is. This is not 589 // guaranteed to be idempotent since we aren't tracking exact moves, but 590 // it's useful information. 591 return false 592 } else { 593 panic("invalid move") 594 } 595 } 596 597 // MoveModule takes a source and destination addrs.Module address, and moves all 598 // state Modules which are contained by the src address to the new address. 599 func (s *State) MoveModule(src, dst addrs.AbsModuleCall) { 600 if src.Module.IsRoot() || dst.Module.IsRoot() { 601 panic("cannot move to or from root module") 602 } 603 604 // Modules only exist as ModuleInstances in state, so we need to check each 605 // state Module and see if it is contained by the src address to get a full 606 // list of modules to move. 607 var srcMIs []*Module 608 for _, module := range s.Modules { 609 if !module.Addr.IsRoot() { 610 if src.Module.TargetContains(module.Addr) { 611 srcMIs = append(srcMIs, module) 612 } 613 } 614 } 615 616 if len(srcMIs) == 0 { 617 panic(fmt.Sprintf("no matching module instances found for src module %s", src.String())) 618 } 619 620 for _, ms := range srcMIs { 621 newInst := make(addrs.ModuleInstance, len(ms.Addr)) 622 copy(newInst, ms.Addr) 623 if ms.Addr.IsDeclaredByCall(src) { 624 // Easy case: we just need to update the last step with the new name 625 newInst[len(newInst)-1].Name = dst.Call.Name 626 } else { 627 // Trickier: this Module is a submodule. we need to find and update 628 // only that appropriate step 629 for s := range newInst { 630 if newInst[s].Name == src.Call.Name { 631 newInst[s].Name = dst.Call.Name 632 } 633 } 634 } 635 s.MoveModuleInstance(ms.Addr, newInst) 636 } 637 }