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