github.com/opentofu/opentofu@v1.7.1/internal/states/state_test.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package states 7 8 import ( 9 "fmt" 10 "reflect" 11 "testing" 12 13 "github.com/go-test/deep" 14 "github.com/zclconf/go-cty/cty" 15 16 "github.com/opentofu/opentofu/internal/addrs" 17 "github.com/opentofu/opentofu/internal/lang/marks" 18 ) 19 20 func TestState(t *testing.T) { 21 // This basic tests exercises the main mutation methods to construct 22 // a state. It is not fully comprehensive, so other tests should visit 23 // more esoteric codepaths. 24 25 state := NewState() 26 27 rootModule := state.RootModule() 28 if rootModule == nil { 29 t.Errorf("root module is nil; want valid object") 30 } 31 32 rootModule.SetLocalValue("foo", cty.StringVal("foo value")) 33 rootModule.SetOutputValue("bar", cty.StringVal("bar value"), false) 34 rootModule.SetOutputValue("secret", cty.StringVal("secret value"), true) 35 rootModule.SetResourceInstanceCurrent( 36 addrs.Resource{ 37 Mode: addrs.ManagedResourceMode, 38 Type: "test_thing", 39 Name: "baz", 40 }.Instance(addrs.IntKey(0)), 41 &ResourceInstanceObjectSrc{ 42 Status: ObjectReady, 43 SchemaVersion: 1, 44 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 45 }, 46 addrs.AbsProviderConfig{ 47 Provider: addrs.NewDefaultProvider("test"), 48 Module: addrs.RootModule, 49 }, 50 ) 51 52 childModule := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 53 childModule.SetOutputValue("pizza", cty.StringVal("hawaiian"), false) 54 multiModA := state.EnsureModule(addrs.RootModuleInstance.Child("multi", addrs.StringKey("a"))) 55 multiModA.SetOutputValue("pizza", cty.StringVal("cheese"), false) 56 multiModB := state.EnsureModule(addrs.RootModuleInstance.Child("multi", addrs.StringKey("b"))) 57 multiModB.SetOutputValue("pizza", cty.StringVal("sausage"), false) 58 59 want := &State{ 60 Modules: map[string]*Module{ 61 "": { 62 Addr: addrs.RootModuleInstance, 63 LocalValues: map[string]cty.Value{ 64 "foo": cty.StringVal("foo value"), 65 }, 66 OutputValues: map[string]*OutputValue{ 67 "bar": { 68 Addr: addrs.AbsOutputValue{ 69 OutputValue: addrs.OutputValue{ 70 Name: "bar", 71 }, 72 }, 73 Value: cty.StringVal("bar value"), 74 Sensitive: false, 75 }, 76 "secret": { 77 Addr: addrs.AbsOutputValue{ 78 OutputValue: addrs.OutputValue{ 79 Name: "secret", 80 }, 81 }, 82 Value: cty.StringVal("secret value"), 83 Sensitive: true, 84 }, 85 }, 86 Resources: map[string]*Resource{ 87 "test_thing.baz": { 88 Addr: addrs.Resource{ 89 Mode: addrs.ManagedResourceMode, 90 Type: "test_thing", 91 Name: "baz", 92 }.Absolute(addrs.RootModuleInstance), 93 94 Instances: map[addrs.InstanceKey]*ResourceInstance{ 95 addrs.IntKey(0): { 96 Current: &ResourceInstanceObjectSrc{ 97 SchemaVersion: 1, 98 Status: ObjectReady, 99 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 100 }, 101 Deposed: map[DeposedKey]*ResourceInstanceObjectSrc{}, 102 }, 103 }, 104 ProviderConfig: addrs.AbsProviderConfig{ 105 Provider: addrs.NewDefaultProvider("test"), 106 Module: addrs.RootModule, 107 }, 108 }, 109 }, 110 }, 111 "module.child": { 112 Addr: addrs.RootModuleInstance.Child("child", addrs.NoKey), 113 LocalValues: map[string]cty.Value{}, 114 OutputValues: map[string]*OutputValue{ 115 "pizza": { 116 Addr: addrs.AbsOutputValue{ 117 Module: addrs.RootModuleInstance.Child("child", addrs.NoKey), 118 OutputValue: addrs.OutputValue{ 119 Name: "pizza", 120 }, 121 }, 122 Value: cty.StringVal("hawaiian"), 123 Sensitive: false, 124 }, 125 }, 126 Resources: map[string]*Resource{}, 127 }, 128 `module.multi["a"]`: { 129 Addr: addrs.RootModuleInstance.Child("multi", addrs.StringKey("a")), 130 LocalValues: map[string]cty.Value{}, 131 OutputValues: map[string]*OutputValue{ 132 "pizza": { 133 Addr: addrs.AbsOutputValue{ 134 Module: addrs.RootModuleInstance.Child("multi", addrs.StringKey("a")), 135 OutputValue: addrs.OutputValue{ 136 Name: "pizza", 137 }, 138 }, 139 Value: cty.StringVal("cheese"), 140 Sensitive: false, 141 }, 142 }, 143 Resources: map[string]*Resource{}, 144 }, 145 `module.multi["b"]`: { 146 Addr: addrs.RootModuleInstance.Child("multi", addrs.StringKey("b")), 147 LocalValues: map[string]cty.Value{}, 148 OutputValues: map[string]*OutputValue{ 149 "pizza": { 150 Addr: addrs.AbsOutputValue{ 151 Module: addrs.RootModuleInstance.Child("multi", addrs.StringKey("b")), 152 OutputValue: addrs.OutputValue{ 153 Name: "pizza", 154 }, 155 }, 156 Value: cty.StringVal("sausage"), 157 Sensitive: false, 158 }, 159 }, 160 Resources: map[string]*Resource{}, 161 }, 162 }, 163 } 164 165 { 166 // Our structure goes deep, so we need to temporarily override the 167 // deep package settings to ensure that we visit the full structure. 168 oldDeepDepth := deep.MaxDepth 169 oldDeepCompareUnexp := deep.CompareUnexportedFields 170 deep.MaxDepth = 50 171 deep.CompareUnexportedFields = true 172 defer func() { 173 deep.MaxDepth = oldDeepDepth 174 deep.CompareUnexportedFields = oldDeepCompareUnexp 175 }() 176 } 177 178 for _, problem := range deep.Equal(state, want) { 179 t.Error(problem) 180 } 181 182 expectedOutputs := map[string]string{ 183 `module.multi["a"].output.pizza`: "cheese", 184 `module.multi["b"].output.pizza`: "sausage", 185 } 186 187 for _, o := range state.ModuleOutputs(addrs.RootModuleInstance, addrs.ModuleCall{Name: "multi"}) { 188 addr := o.Addr.String() 189 expected := expectedOutputs[addr] 190 delete(expectedOutputs, addr) 191 192 if expected != o.Value.AsString() { 193 t.Fatalf("expected %q:%q, got %q", addr, expected, o.Value.AsString()) 194 } 195 } 196 197 for addr, o := range expectedOutputs { 198 t.Fatalf("missing output %q:%q", addr, o) 199 } 200 } 201 202 func TestStateDeepCopyObject(t *testing.T) { 203 obj := &ResourceInstanceObject{ 204 Value: cty.ObjectVal(map[string]cty.Value{ 205 "id": cty.StringVal("id"), 206 }), 207 Private: []byte("private"), 208 Status: ObjectReady, 209 Dependencies: []addrs.ConfigResource{ 210 { 211 Module: addrs.RootModule, 212 Resource: addrs.Resource{ 213 Mode: addrs.ManagedResourceMode, 214 Type: "test_instance", 215 Name: "bar", 216 }, 217 }, 218 }, 219 CreateBeforeDestroy: true, 220 } 221 222 objCopy := obj.DeepCopy() 223 if !reflect.DeepEqual(obj, objCopy) { 224 t.Fatalf("not equal\n%#v\n%#v", obj, objCopy) 225 } 226 } 227 228 func TestStateDeepCopy(t *testing.T) { 229 state := NewState() 230 231 rootModule := state.RootModule() 232 if rootModule == nil { 233 t.Errorf("root module is nil; want valid object") 234 } 235 236 rootModule.SetLocalValue("foo", cty.StringVal("foo value")) 237 rootModule.SetOutputValue("bar", cty.StringVal("bar value"), false) 238 rootModule.SetOutputValue("secret", cty.StringVal("secret value"), true) 239 rootModule.SetResourceInstanceCurrent( 240 addrs.Resource{ 241 Mode: addrs.ManagedResourceMode, 242 Type: "test_thing", 243 Name: "baz", 244 }.Instance(addrs.IntKey(0)), 245 &ResourceInstanceObjectSrc{ 246 Status: ObjectReady, 247 SchemaVersion: 1, 248 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 249 Private: []byte("private data"), 250 Dependencies: []addrs.ConfigResource{}, 251 CreateBeforeDestroy: true, 252 }, 253 addrs.AbsProviderConfig{ 254 Provider: addrs.NewDefaultProvider("test"), 255 Module: addrs.RootModule, 256 }, 257 ) 258 rootModule.SetResourceInstanceCurrent( 259 addrs.Resource{ 260 Mode: addrs.ManagedResourceMode, 261 Type: "test_thing", 262 Name: "bar", 263 }.Instance(addrs.IntKey(0)), 264 &ResourceInstanceObjectSrc{ 265 Status: ObjectReady, 266 SchemaVersion: 1, 267 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 268 // Sensitive path at "woozles" 269 AttrSensitivePaths: []cty.PathValueMarks{ 270 { 271 Path: cty.Path{cty.GetAttrStep{Name: "woozles"}}, 272 Marks: cty.NewValueMarks(marks.Sensitive), 273 }, 274 }, 275 Private: []byte("private data"), 276 Dependencies: []addrs.ConfigResource{ 277 { 278 Module: addrs.RootModule, 279 Resource: addrs.Resource{ 280 Mode: addrs.ManagedResourceMode, 281 Type: "test_thing", 282 Name: "baz", 283 }, 284 }, 285 }, 286 }, 287 addrs.AbsProviderConfig{ 288 Provider: addrs.NewDefaultProvider("test"), 289 Module: addrs.RootModule, 290 }, 291 ) 292 293 childModule := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 294 childModule.SetOutputValue("pizza", cty.StringVal("hawaiian"), false) 295 296 stateCopy := state.DeepCopy() 297 if !state.Equal(stateCopy) { 298 t.Fatalf("\nexpected:\n%q\ngot:\n%q\n", state, stateCopy) 299 } 300 } 301 302 func TestStateHasResourceInstanceObjects(t *testing.T) { 303 providerConfig := addrs.AbsProviderConfig{ 304 Module: addrs.RootModule, 305 Provider: addrs.MustParseProviderSourceString("test/test"), 306 } 307 childModuleProviderConfig := addrs.AbsProviderConfig{ 308 Module: addrs.RootModule.Child("child"), 309 Provider: addrs.MustParseProviderSourceString("test/test"), 310 } 311 312 tests := map[string]struct { 313 Setup func(ss *SyncState) 314 Want bool 315 }{ 316 "empty": { 317 func(ss *SyncState) {}, 318 false, 319 }, 320 "one current, ready object in root module": { 321 func(ss *SyncState) { 322 ss.SetResourceInstanceCurrent( 323 mustAbsResourceAddr("test.foo").Instance(addrs.NoKey), 324 &ResourceInstanceObjectSrc{ 325 AttrsJSON: []byte(`{}`), 326 Status: ObjectReady, 327 }, 328 providerConfig, 329 ) 330 }, 331 true, 332 }, 333 "one current, ready object in child module": { 334 func(ss *SyncState) { 335 ss.SetResourceInstanceCurrent( 336 mustAbsResourceAddr("module.child.test.foo").Instance(addrs.NoKey), 337 &ResourceInstanceObjectSrc{ 338 AttrsJSON: []byte(`{}`), 339 Status: ObjectReady, 340 }, 341 childModuleProviderConfig, 342 ) 343 }, 344 true, 345 }, 346 "one current, tainted object in root module": { 347 func(ss *SyncState) { 348 ss.SetResourceInstanceCurrent( 349 mustAbsResourceAddr("test.foo").Instance(addrs.NoKey), 350 &ResourceInstanceObjectSrc{ 351 AttrsJSON: []byte(`{}`), 352 Status: ObjectTainted, 353 }, 354 providerConfig, 355 ) 356 }, 357 true, 358 }, 359 "one deposed, ready object in root module": { 360 func(ss *SyncState) { 361 ss.SetResourceInstanceDeposed( 362 mustAbsResourceAddr("test.foo").Instance(addrs.NoKey), 363 DeposedKey("uhoh"), 364 &ResourceInstanceObjectSrc{ 365 AttrsJSON: []byte(`{}`), 366 Status: ObjectTainted, 367 }, 368 providerConfig, 369 ) 370 }, 371 true, 372 }, 373 "one empty resource husk in root module": { 374 func(ss *SyncState) { 375 // Current OpenTofu doesn't actually create resource husks 376 // as part of its everyday work, so this is a "should never 377 // happen" case but we'll test to make sure we're robust to 378 // it anyway, because this was a historical bug blocking 379 // "tofu workspace delete" and similar. 380 ss.SetResourceInstanceCurrent( 381 mustAbsResourceAddr("test.foo").Instance(addrs.NoKey), 382 &ResourceInstanceObjectSrc{ 383 AttrsJSON: []byte(`{}`), 384 Status: ObjectTainted, 385 }, 386 providerConfig, 387 ) 388 s := ss.Lock() 389 delete(s.Modules[""].Resources["test.foo"].Instances, addrs.NoKey) 390 ss.Unlock() 391 }, 392 false, 393 }, 394 "one current data resource object in root module": { 395 func(ss *SyncState) { 396 ss.SetResourceInstanceCurrent( 397 mustAbsResourceAddr("data.test.foo").Instance(addrs.NoKey), 398 &ResourceInstanceObjectSrc{ 399 AttrsJSON: []byte(`{}`), 400 Status: ObjectReady, 401 }, 402 providerConfig, 403 ) 404 }, 405 false, // data resources aren't managed resources, so they don't count 406 }, 407 } 408 409 for name, test := range tests { 410 t.Run(name, func(t *testing.T) { 411 state := BuildState(test.Setup) 412 got := state.HasManagedResourceInstanceObjects() 413 if got != test.Want { 414 t.Errorf("wrong result\nstate content: (using legacy state string format; might not be comprehensive)\n%s\n\ngot: %t\nwant: %t", state, got, test.Want) 415 } 416 }) 417 } 418 419 } 420 421 func TestState_MoveAbsResource(t *testing.T) { 422 // Set up a starter state for the embedded tests, which should start from a copy of this state. 423 state := NewState() 424 rootModule := state.RootModule() 425 rootModule.SetResourceInstanceCurrent( 426 addrs.Resource{ 427 Mode: addrs.ManagedResourceMode, 428 Type: "test_thing", 429 Name: "foo", 430 }.Instance(addrs.IntKey(0)), 431 &ResourceInstanceObjectSrc{ 432 Status: ObjectReady, 433 SchemaVersion: 1, 434 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 435 }, 436 addrs.AbsProviderConfig{ 437 Provider: addrs.NewDefaultProvider("test"), 438 Module: addrs.RootModule, 439 }, 440 ) 441 src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Absolute(addrs.RootModuleInstance) 442 443 t.Run("basic move", func(t *testing.T) { 444 s := state.DeepCopy() 445 dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "bar"}.Absolute(addrs.RootModuleInstance) 446 447 s.MoveAbsResource(src, dst) 448 449 if s.Empty() { 450 t.Fatal("unexpected empty state") 451 } 452 453 if len(s.RootModule().Resources) != 1 { 454 t.Fatalf("wrong number of resources in state; expected 1, found %d", len(state.RootModule().Resources)) 455 } 456 457 got := s.Resource(dst) 458 if got.Addr.Resource != dst.Resource { 459 t.Fatalf("dst resource not in state") 460 } 461 }) 462 463 t.Run("move to new module", func(t *testing.T) { 464 s := state.DeepCopy() 465 dstModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("one")) 466 dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "bar"}.Absolute(dstModule) 467 468 s.MoveAbsResource(src, dst) 469 470 if s.Empty() { 471 t.Fatal("unexpected empty state") 472 } 473 474 if s.Module(dstModule) == nil { 475 t.Fatalf("child module %s not in state", dstModule.String()) 476 } 477 478 if len(s.Module(dstModule).Resources) != 1 { 479 t.Fatalf("wrong number of resources in state; expected 1, found %d", len(s.Module(dstModule).Resources)) 480 } 481 482 got := s.Resource(dst) 483 if got.Addr.Resource != dst.Resource { 484 t.Fatalf("dst resource not in state") 485 } 486 }) 487 488 t.Run("from a child module to root", func(t *testing.T) { 489 s := state.DeepCopy() 490 srcModule := addrs.RootModuleInstance.Child("kinder", addrs.NoKey) 491 cm := s.EnsureModule(srcModule) 492 cm.SetResourceInstanceCurrent( 493 addrs.Resource{ 494 Mode: addrs.ManagedResourceMode, 495 Type: "test_thing", 496 Name: "child", 497 }.Instance(addrs.IntKey(0)), // Moving the AbsResouce moves all instances 498 &ResourceInstanceObjectSrc{ 499 Status: ObjectReady, 500 SchemaVersion: 1, 501 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 502 }, 503 addrs.AbsProviderConfig{ 504 Provider: addrs.NewDefaultProvider("test"), 505 Module: addrs.RootModule, 506 }, 507 ) 508 cm.SetResourceInstanceCurrent( 509 addrs.Resource{ 510 Mode: addrs.ManagedResourceMode, 511 Type: "test_thing", 512 Name: "child", 513 }.Instance(addrs.IntKey(1)), // Moving the AbsResouce moves all instances 514 &ResourceInstanceObjectSrc{ 515 Status: ObjectReady, 516 SchemaVersion: 1, 517 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 518 }, 519 addrs.AbsProviderConfig{ 520 Provider: addrs.NewDefaultProvider("test"), 521 Module: addrs.RootModule, 522 }, 523 ) 524 525 src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(srcModule) 526 dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(addrs.RootModuleInstance) 527 s.MoveAbsResource(src, dst) 528 529 if s.Empty() { 530 t.Fatal("unexpected empty state") 531 } 532 533 // The child module should have been removed after removing its only resource 534 if s.Module(srcModule) != nil { 535 t.Fatalf("child module %s was not removed from state after mv", srcModule.String()) 536 } 537 538 if len(s.RootModule().Resources) != 2 { 539 t.Fatalf("wrong number of resources in state; expected 2, found %d", len(s.RootModule().Resources)) 540 } 541 542 if len(s.Resource(dst).Instances) != 2 { 543 t.Fatalf("wrong number of resource instances for dst, got %d expected 2", len(s.Resource(dst).Instances)) 544 } 545 546 got := s.Resource(dst) 547 if got.Addr.Resource != dst.Resource { 548 t.Fatalf("dst resource not in state") 549 } 550 }) 551 552 t.Run("module to new module", func(t *testing.T) { 553 s := NewState() 554 srcModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("exists")) 555 dstModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("new")) 556 cm := s.EnsureModule(srcModule) 557 cm.SetResourceInstanceCurrent( 558 addrs.Resource{ 559 Mode: addrs.ManagedResourceMode, 560 Type: "test_thing", 561 Name: "child", 562 }.Instance(addrs.NoKey), 563 &ResourceInstanceObjectSrc{ 564 Status: ObjectReady, 565 SchemaVersion: 1, 566 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 567 }, 568 addrs.AbsProviderConfig{ 569 Provider: addrs.NewDefaultProvider("test"), 570 Module: addrs.RootModule, 571 }, 572 ) 573 574 src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(srcModule) 575 dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(dstModule) 576 s.MoveAbsResource(src, dst) 577 578 if s.Empty() { 579 t.Fatal("unexpected empty state") 580 } 581 582 // The child module should have been removed after removing its only resource 583 if s.Module(srcModule) != nil { 584 t.Fatalf("child module %s was not removed from state after mv", srcModule.String()) 585 } 586 587 gotMod := s.Module(dstModule) 588 if len(gotMod.Resources) != 1 { 589 t.Fatalf("wrong number of resources in state; expected 1, found %d", len(gotMod.Resources)) 590 } 591 592 got := s.Resource(dst) 593 if got.Addr.Resource != dst.Resource { 594 t.Fatalf("dst resource not in state") 595 } 596 }) 597 598 t.Run("module to new module", func(t *testing.T) { 599 s := NewState() 600 srcModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("exists")) 601 dstModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("new")) 602 cm := s.EnsureModule(srcModule) 603 cm.SetResourceInstanceCurrent( 604 addrs.Resource{ 605 Mode: addrs.ManagedResourceMode, 606 Type: "test_thing", 607 Name: "child", 608 }.Instance(addrs.NoKey), 609 &ResourceInstanceObjectSrc{ 610 Status: ObjectReady, 611 SchemaVersion: 1, 612 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 613 }, 614 addrs.AbsProviderConfig{ 615 Provider: addrs.NewDefaultProvider("test"), 616 Module: addrs.RootModule, 617 }, 618 ) 619 620 src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(srcModule) 621 dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(dstModule) 622 s.MoveAbsResource(src, dst) 623 624 if s.Empty() { 625 t.Fatal("unexpected empty state") 626 } 627 628 // The child module should have been removed after removing its only resource 629 if s.Module(srcModule) != nil { 630 t.Fatalf("child module %s was not removed from state after mv", srcModule.String()) 631 } 632 633 gotMod := s.Module(dstModule) 634 if len(gotMod.Resources) != 1 { 635 t.Fatalf("wrong number of resources in state; expected 1, found %d", len(gotMod.Resources)) 636 } 637 638 got := s.Resource(dst) 639 if got.Addr.Resource != dst.Resource { 640 t.Fatalf("dst resource not in state") 641 } 642 }) 643 } 644 645 func TestState_MaybeMoveAbsResource(t *testing.T) { 646 state := NewState() 647 rootModule := state.RootModule() 648 rootModule.SetResourceInstanceCurrent( 649 addrs.Resource{ 650 Mode: addrs.ManagedResourceMode, 651 Type: "test_thing", 652 Name: "foo", 653 }.Instance(addrs.IntKey(0)), 654 &ResourceInstanceObjectSrc{ 655 Status: ObjectReady, 656 SchemaVersion: 1, 657 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 658 }, 659 addrs.AbsProviderConfig{ 660 Provider: addrs.NewDefaultProvider("test"), 661 Module: addrs.RootModule, 662 }, 663 ) 664 665 src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Absolute(addrs.RootModuleInstance) 666 dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "bar"}.Absolute(addrs.RootModuleInstance) 667 668 // First move, success 669 t.Run("first move", func(t *testing.T) { 670 moved := state.MaybeMoveAbsResource(src, dst) 671 if !moved { 672 t.Fatal("wrong result") 673 } 674 }) 675 676 // Trying to move a resource that doesn't exist in state to a resource which does exist should be a noop. 677 t.Run("noop", func(t *testing.T) { 678 moved := state.MaybeMoveAbsResource(src, dst) 679 if moved { 680 t.Fatal("wrong result") 681 } 682 }) 683 } 684 685 func TestState_MoveAbsResourceInstance(t *testing.T) { 686 state := NewState() 687 rootModule := state.RootModule() 688 rootModule.SetResourceInstanceCurrent( 689 addrs.Resource{ 690 Mode: addrs.ManagedResourceMode, 691 Type: "test_thing", 692 Name: "foo", 693 }.Instance(addrs.NoKey), 694 &ResourceInstanceObjectSrc{ 695 Status: ObjectReady, 696 SchemaVersion: 1, 697 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 698 }, 699 addrs.AbsProviderConfig{ 700 Provider: addrs.NewDefaultProvider("test"), 701 Module: addrs.RootModule, 702 }, 703 ) 704 // src resource from the state above 705 src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance) 706 707 t.Run("resource to resource instance", func(t *testing.T) { 708 s := state.DeepCopy() 709 // For a little extra fun, move a resource to a resource instance: test_thing.foo to test_thing.foo[1] 710 dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance) 711 712 s.MoveAbsResourceInstance(src, dst) 713 714 if s.Empty() { 715 t.Fatal("unexpected empty state") 716 } 717 718 if len(s.RootModule().Resources) != 1 { 719 t.Fatalf("wrong number of resources in state; expected 1, found %d", len(state.RootModule().Resources)) 720 } 721 722 got := s.ResourceInstance(dst) 723 if got == nil { 724 t.Fatalf("dst resource not in state") 725 } 726 }) 727 728 t.Run("move to new module", func(t *testing.T) { 729 s := state.DeepCopy() 730 // test_thing.foo to module.kinder.test_thing.foo["baz"] 731 dstModule := addrs.RootModuleInstance.Child("kinder", addrs.NoKey) 732 dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.IntKey(1)).Absolute(dstModule) 733 734 s.MoveAbsResourceInstance(src, dst) 735 736 if s.Empty() { 737 t.Fatal("unexpected empty state") 738 } 739 740 if s.Module(dstModule) == nil { 741 t.Fatalf("child module %s not in state", dstModule.String()) 742 } 743 744 if len(s.Module(dstModule).Resources) != 1 { 745 t.Fatalf("wrong number of resources in state; expected 1, found %d", len(s.Module(dstModule).Resources)) 746 } 747 748 got := s.ResourceInstance(dst) 749 if got == nil { 750 t.Fatalf("dst resource not in state") 751 } 752 }) 753 } 754 755 func TestState_MaybeMoveAbsResourceInstance(t *testing.T) { 756 state := NewState() 757 rootModule := state.RootModule() 758 rootModule.SetResourceInstanceCurrent( 759 addrs.Resource{ 760 Mode: addrs.ManagedResourceMode, 761 Type: "test_thing", 762 Name: "foo", 763 }.Instance(addrs.NoKey), 764 &ResourceInstanceObjectSrc{ 765 Status: ObjectReady, 766 SchemaVersion: 1, 767 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 768 }, 769 addrs.AbsProviderConfig{ 770 Provider: addrs.NewDefaultProvider("test"), 771 Module: addrs.RootModule, 772 }, 773 ) 774 775 // For a little extra fun, let's go from a resource to a resource instance: test_thing.foo to test_thing.bar[1] 776 src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance) 777 dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance) 778 779 // First move, success 780 t.Run("first move", func(t *testing.T) { 781 moved := state.MaybeMoveAbsResourceInstance(src, dst) 782 if !moved { 783 t.Fatal("wrong result") 784 } 785 got := state.ResourceInstance(dst) 786 if got == nil { 787 t.Fatal("destination resource instance not in state") 788 } 789 }) 790 791 // Moving a resource instance that doesn't exist in state to a resource which does exist should be a noop. 792 t.Run("noop", func(t *testing.T) { 793 moved := state.MaybeMoveAbsResourceInstance(src, dst) 794 if moved { 795 t.Fatal("wrong result") 796 } 797 }) 798 } 799 800 func TestState_MoveModuleInstance(t *testing.T) { 801 state := NewState() 802 srcModule := addrs.RootModuleInstance.Child("kinder", addrs.NoKey) 803 m := state.EnsureModule(srcModule) 804 m.SetResourceInstanceCurrent( 805 addrs.Resource{ 806 Mode: addrs.ManagedResourceMode, 807 Type: "test_thing", 808 Name: "foo", 809 }.Instance(addrs.NoKey), 810 &ResourceInstanceObjectSrc{ 811 Status: ObjectReady, 812 SchemaVersion: 1, 813 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 814 }, 815 addrs.AbsProviderConfig{ 816 Provider: addrs.NewDefaultProvider("test"), 817 Module: addrs.RootModule, 818 }, 819 ) 820 821 dstModule := addrs.RootModuleInstance.Child("child", addrs.IntKey(3)) 822 state.MoveModuleInstance(srcModule, dstModule) 823 824 // srcModule should have been removed, dstModule should exist and have one resource 825 if len(state.Modules) != 2 { // kinder[3] and root 826 t.Fatalf("wrong number of modules in state. Expected 2, got %d", len(state.Modules)) 827 } 828 829 got := state.Module(dstModule) 830 if got == nil { 831 t.Fatal("dstModule not found") 832 } 833 834 gone := state.Module(srcModule) 835 if gone != nil { 836 t.Fatal("srcModule not removed from state") 837 } 838 839 r := got.Resource(mustAbsResourceAddr("test_thing.foo").Resource) 840 if r.Addr.Module.String() != dstModule.String() { 841 fmt.Println(r.Addr.Module.String()) 842 t.Fatal("resource address was not updated") 843 } 844 845 } 846 847 func TestState_MaybeMoveModuleInstance(t *testing.T) { 848 state := NewState() 849 src := addrs.RootModuleInstance.Child("child", addrs.StringKey("a")) 850 cm := state.EnsureModule(src) 851 cm.SetResourceInstanceCurrent( 852 addrs.Resource{ 853 Mode: addrs.ManagedResourceMode, 854 Type: "test_thing", 855 Name: "foo", 856 }.Instance(addrs.NoKey), 857 &ResourceInstanceObjectSrc{ 858 Status: ObjectReady, 859 SchemaVersion: 1, 860 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 861 }, 862 addrs.AbsProviderConfig{ 863 Provider: addrs.NewDefaultProvider("test"), 864 Module: addrs.RootModule, 865 }, 866 ) 867 868 dst := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("b")) 869 870 // First move, success 871 t.Run("first move", func(t *testing.T) { 872 moved := state.MaybeMoveModuleInstance(src, dst) 873 if !moved { 874 t.Fatal("wrong result") 875 } 876 }) 877 878 // Second move, should be a noop 879 t.Run("noop", func(t *testing.T) { 880 moved := state.MaybeMoveModuleInstance(src, dst) 881 if moved { 882 t.Fatal("wrong result") 883 } 884 }) 885 } 886 887 func TestState_MoveModule(t *testing.T) { 888 // For this test, add two module instances (kinder and kinder["a"]). 889 // MoveModule(kinder) should move both instances. 890 state := NewState() // starter state, should be copied by the subtests. 891 srcModule := addrs.RootModule.Child("kinder") 892 m := state.EnsureModule(srcModule.UnkeyedInstanceShim()) 893 m.SetResourceInstanceCurrent( 894 addrs.Resource{ 895 Mode: addrs.ManagedResourceMode, 896 Type: "test_thing", 897 Name: "foo", 898 }.Instance(addrs.NoKey), 899 &ResourceInstanceObjectSrc{ 900 Status: ObjectReady, 901 SchemaVersion: 1, 902 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 903 }, 904 addrs.AbsProviderConfig{ 905 Provider: addrs.NewDefaultProvider("test"), 906 Module: addrs.RootModule, 907 }, 908 ) 909 910 moduleInstance := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("a")) 911 mi := state.EnsureModule(moduleInstance) 912 mi.SetResourceInstanceCurrent( 913 addrs.Resource{ 914 Mode: addrs.ManagedResourceMode, 915 Type: "test_thing", 916 Name: "foo", 917 }.Instance(addrs.NoKey), 918 &ResourceInstanceObjectSrc{ 919 Status: ObjectReady, 920 SchemaVersion: 1, 921 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 922 }, 923 addrs.AbsProviderConfig{ 924 Provider: addrs.NewDefaultProvider("test"), 925 Module: addrs.RootModule, 926 }, 927 ) 928 929 _, mc := srcModule.Call() 930 src := mc.Absolute(addrs.RootModuleInstance.Child("kinder", addrs.NoKey)) 931 932 t.Run("basic", func(t *testing.T) { 933 s := state.DeepCopy() 934 _, dstMC := addrs.RootModule.Child("child").Call() 935 dst := dstMC.Absolute(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 936 s.MoveModule(src, dst) 937 938 // srcModule should have been removed, dstModule should exist and have one resource 939 if len(s.Modules) != 3 { // child, child["a"] and root 940 t.Fatalf("wrong number of modules in state. Expected 3, got %d", len(s.Modules)) 941 } 942 943 got := s.Module(dst.Module) 944 if got == nil { 945 t.Fatal("dstModule not found") 946 } 947 948 got = s.Module(addrs.RootModuleInstance.Child("child", addrs.StringKey("a"))) 949 if got == nil { 950 t.Fatal("dstModule instance \"a\" not found") 951 } 952 953 gone := s.Module(srcModule.UnkeyedInstanceShim()) 954 if gone != nil { 955 t.Fatal("srcModule not removed from state") 956 } 957 }) 958 959 t.Run("nested modules", func(t *testing.T) { 960 s := state.DeepCopy() 961 962 // add a child module to module.kinder 963 mi := mustParseModuleInstanceStr(`module.kinder.module.grand[1]`) 964 m := s.EnsureModule(mi) 965 m.SetResourceInstanceCurrent( 966 addrs.Resource{ 967 Mode: addrs.ManagedResourceMode, 968 Type: "test_thing", 969 Name: "foo", 970 }.Instance(addrs.NoKey), 971 &ResourceInstanceObjectSrc{ 972 Status: ObjectReady, 973 SchemaVersion: 1, 974 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 975 }, 976 addrs.AbsProviderConfig{ 977 Provider: addrs.NewDefaultProvider("test"), 978 Module: addrs.RootModule, 979 }, 980 ) 981 982 _, dstMC := addrs.RootModule.Child("child").Call() 983 dst := dstMC.Absolute(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 984 s.MoveModule(src, dst) 985 986 moved := s.Module(addrs.RootModuleInstance.Child("child", addrs.StringKey("a"))) 987 if moved == nil { 988 t.Fatal("dstModule not found") 989 } 990 991 // The nested module's relative address should also have been updated 992 nested := s.Module(mustParseModuleInstanceStr(`module.child.module.grand[1]`)) 993 if nested == nil { 994 t.Fatal("nested child module of src wasn't moved") 995 } 996 }) 997 } 998 999 func mustParseModuleInstanceStr(str string) addrs.ModuleInstance { 1000 addr, diags := addrs.ParseModuleInstanceStr(str) 1001 if diags.HasErrors() { 1002 panic(diags.Err()) 1003 } 1004 return addr 1005 } 1006 1007 func mustAbsResourceAddr(s string) addrs.AbsResource { 1008 addr, diags := addrs.ParseAbsResourceStr(s) 1009 if diags.HasErrors() { 1010 panic(diags.Err()) 1011 } 1012 return addr 1013 }