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