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