github.com/opentofu/opentofu@v1.7.1/internal/tofu/graph_builder_apply_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 tofu 7 8 import ( 9 "fmt" 10 "strings" 11 "testing" 12 13 "github.com/google/go-cmp/cmp" 14 "github.com/zclconf/go-cty/cty" 15 16 "github.com/opentofu/opentofu/internal/addrs" 17 "github.com/opentofu/opentofu/internal/plans" 18 "github.com/opentofu/opentofu/internal/providers" 19 "github.com/opentofu/opentofu/internal/states" 20 ) 21 22 func TestApplyGraphBuilder_impl(t *testing.T) { 23 var _ GraphBuilder = new(ApplyGraphBuilder) 24 } 25 26 func TestApplyGraphBuilder(t *testing.T) { 27 changes := &plans.Changes{ 28 Resources: []*plans.ResourceInstanceChangeSrc{ 29 { 30 Addr: mustResourceInstanceAddr("test_object.create"), 31 ChangeSrc: plans.ChangeSrc{ 32 Action: plans.Create, 33 }, 34 }, 35 { 36 Addr: mustResourceInstanceAddr("test_object.other"), 37 ChangeSrc: plans.ChangeSrc{ 38 Action: plans.Update, 39 }, 40 }, 41 { 42 Addr: mustResourceInstanceAddr("module.child.test_object.create"), 43 ChangeSrc: plans.ChangeSrc{ 44 Action: plans.Create, 45 }, 46 }, 47 { 48 Addr: mustResourceInstanceAddr("module.child.test_object.other"), 49 ChangeSrc: plans.ChangeSrc{ 50 Action: plans.Create, 51 }, 52 }, 53 }, 54 } 55 56 b := &ApplyGraphBuilder{ 57 Config: testModule(t, "graph-builder-apply-basic"), 58 Changes: changes, 59 Plugins: simpleMockPluginLibrary(), 60 } 61 62 g, err := b.Build(addrs.RootModuleInstance) 63 if err != nil { 64 t.Fatalf("err: %s", err) 65 } 66 67 if g.Path.String() != addrs.RootModuleInstance.String() { 68 t.Fatalf("wrong path %q", g.Path.String()) 69 } 70 71 got := strings.TrimSpace(g.String()) 72 want := strings.TrimSpace(testApplyGraphBuilderStr) 73 if diff := cmp.Diff(want, got); diff != "" { 74 t.Fatalf("wrong result\n%s", diff) 75 } 76 } 77 78 // This tests the ordering of two resources where a non-CBD depends 79 // on a CBD. GH-11349. 80 func TestApplyGraphBuilder_depCbd(t *testing.T) { 81 changes := &plans.Changes{ 82 Resources: []*plans.ResourceInstanceChangeSrc{ 83 { 84 Addr: mustResourceInstanceAddr("test_object.A"), 85 ChangeSrc: plans.ChangeSrc{ 86 Action: plans.CreateThenDelete, 87 }, 88 }, 89 { 90 Addr: mustResourceInstanceAddr("test_object.B"), 91 ChangeSrc: plans.ChangeSrc{ 92 Action: plans.Update, 93 }, 94 }, 95 }, 96 } 97 98 state := states.NewState() 99 root := state.EnsureModule(addrs.RootModuleInstance) 100 root.SetResourceInstanceCurrent( 101 mustResourceInstanceAddr("test_object.A").Resource, 102 &states.ResourceInstanceObjectSrc{ 103 Status: states.ObjectReady, 104 AttrsJSON: []byte(`{"id":"A"}`), 105 }, 106 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 107 ) 108 root.SetResourceInstanceCurrent( 109 mustResourceInstanceAddr("test_object.B").Resource, 110 &states.ResourceInstanceObjectSrc{ 111 Status: states.ObjectReady, 112 AttrsJSON: []byte(`{"id":"B","test_list":["x"]}`), 113 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.A")}, 114 }, 115 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 116 ) 117 118 b := &ApplyGraphBuilder{ 119 Config: testModule(t, "graph-builder-apply-dep-cbd"), 120 Changes: changes, 121 Plugins: simpleMockPluginLibrary(), 122 State: state, 123 } 124 125 g, err := b.Build(addrs.RootModuleInstance) 126 if err != nil { 127 t.Fatalf("err: %s", err) 128 } 129 130 if g.Path.String() != addrs.RootModuleInstance.String() { 131 t.Fatalf("wrong path %q", g.Path.String()) 132 } 133 134 // We're going to go hunting for our deposed instance node here, so we 135 // can find out its key to use in the assertions below. 136 var dk states.DeposedKey 137 for _, v := range g.Vertices() { 138 tv, ok := v.(*NodeDestroyDeposedResourceInstanceObject) 139 if !ok { 140 continue 141 } 142 if dk != states.NotDeposed { 143 t.Fatalf("more than one deposed instance node in the graph; want only one") 144 } 145 dk = tv.DeposedKey 146 } 147 if dk == states.NotDeposed { 148 t.Fatalf("no deposed instance node in the graph; want one") 149 } 150 151 destroyName := fmt.Sprintf("test_object.A (destroy deposed %s)", dk) 152 153 // Create A, Modify B, Destroy A 154 testGraphHappensBefore( 155 t, g, 156 "test_object.A", 157 destroyName, 158 ) 159 testGraphHappensBefore( 160 t, g, 161 "test_object.A", 162 "test_object.B", 163 ) 164 testGraphHappensBefore( 165 t, g, 166 "test_object.B", 167 destroyName, 168 ) 169 } 170 171 // This tests the ordering of two resources that are both CBD that 172 // require destroy/create. 173 func TestApplyGraphBuilder_doubleCBD(t *testing.T) { 174 changes := &plans.Changes{ 175 Resources: []*plans.ResourceInstanceChangeSrc{ 176 { 177 Addr: mustResourceInstanceAddr("test_object.A"), 178 ChangeSrc: plans.ChangeSrc{ 179 Action: plans.CreateThenDelete, 180 }, 181 }, 182 { 183 Addr: mustResourceInstanceAddr("test_object.B"), 184 ChangeSrc: plans.ChangeSrc{ 185 Action: plans.CreateThenDelete, 186 }, 187 }, 188 }, 189 } 190 191 b := &ApplyGraphBuilder{ 192 Config: testModule(t, "graph-builder-apply-double-cbd"), 193 Changes: changes, 194 Plugins: simpleMockPluginLibrary(), 195 } 196 197 g, err := b.Build(addrs.RootModuleInstance) 198 if err != nil { 199 t.Fatalf("err: %s", err) 200 } 201 202 if g.Path.String() != addrs.RootModuleInstance.String() { 203 t.Fatalf("wrong path %q", g.Path.String()) 204 } 205 206 // We're going to go hunting for our deposed instance node here, so we 207 // can find out its key to use in the assertions below. 208 var destroyA, destroyB string 209 for _, v := range g.Vertices() { 210 tv, ok := v.(*NodeDestroyDeposedResourceInstanceObject) 211 if !ok { 212 continue 213 } 214 215 switch tv.Addr.Resource.Resource.Name { 216 case "A": 217 destroyA = fmt.Sprintf("test_object.A (destroy deposed %s)", tv.DeposedKey) 218 case "B": 219 destroyB = fmt.Sprintf("test_object.B (destroy deposed %s)", tv.DeposedKey) 220 default: 221 t.Fatalf("unknown instance: %s", tv.Addr) 222 } 223 } 224 225 // Create A, Modify B, Destroy A 226 testGraphHappensBefore( 227 t, g, 228 "test_object.A", 229 destroyA, 230 ) 231 testGraphHappensBefore( 232 t, g, 233 "test_object.A", 234 "test_object.B", 235 ) 236 testGraphHappensBefore( 237 t, g, 238 "test_object.B", 239 destroyB, 240 ) 241 } 242 243 // This tests the ordering of two resources being destroyed that depend 244 // on each other from only state. GH-11749 245 func TestApplyGraphBuilder_destroyStateOnly(t *testing.T) { 246 changes := &plans.Changes{ 247 Resources: []*plans.ResourceInstanceChangeSrc{ 248 { 249 Addr: mustResourceInstanceAddr("module.child.test_object.A"), 250 ChangeSrc: plans.ChangeSrc{ 251 Action: plans.Delete, 252 }, 253 }, 254 { 255 Addr: mustResourceInstanceAddr("module.child.test_object.B"), 256 ChangeSrc: plans.ChangeSrc{ 257 Action: plans.Delete, 258 }, 259 }, 260 }, 261 } 262 263 state := states.NewState() 264 root := state.EnsureModule(addrs.RootModuleInstance) 265 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 266 root.SetResourceInstanceCurrent( 267 mustResourceInstanceAddr("test_object.A").Resource, 268 &states.ResourceInstanceObjectSrc{ 269 Status: states.ObjectReady, 270 AttrsJSON: []byte(`{"id":"foo"}`), 271 }, 272 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 273 ) 274 child.SetResourceInstanceCurrent( 275 mustResourceInstanceAddr("test_object.B").Resource, 276 &states.ResourceInstanceObjectSrc{ 277 Status: states.ObjectReady, 278 AttrsJSON: []byte(`{"id":"bar"}`), 279 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.child.test_object.A")}, 280 }, 281 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 282 ) 283 284 b := &ApplyGraphBuilder{ 285 Config: testModule(t, "empty"), 286 Changes: changes, 287 State: state, 288 Plugins: simpleMockPluginLibrary(), 289 } 290 291 g, diags := b.Build(addrs.RootModuleInstance) 292 if diags.HasErrors() { 293 t.Fatalf("err: %s", diags.Err()) 294 } 295 296 if g.Path.String() != addrs.RootModuleInstance.String() { 297 t.Fatalf("wrong path %q", g.Path.String()) 298 } 299 300 testGraphHappensBefore( 301 t, g, 302 "module.child.test_object.B (destroy)", 303 "module.child.test_object.A (destroy)") 304 } 305 306 // This tests the ordering of destroying a single count of a resource. 307 func TestApplyGraphBuilder_destroyCount(t *testing.T) { 308 changes := &plans.Changes{ 309 Resources: []*plans.ResourceInstanceChangeSrc{ 310 { 311 Addr: mustResourceInstanceAddr("test_object.A[1]"), 312 ChangeSrc: plans.ChangeSrc{ 313 Action: plans.Delete, 314 }, 315 }, 316 { 317 Addr: mustResourceInstanceAddr("test_object.B"), 318 ChangeSrc: plans.ChangeSrc{ 319 Action: plans.Update, 320 }, 321 }, 322 }, 323 } 324 325 state := states.NewState() 326 root := state.RootModule() 327 addrA := mustResourceInstanceAddr("test_object.A[1]") 328 root.SetResourceInstanceCurrent( 329 addrA.Resource, 330 &states.ResourceInstanceObjectSrc{ 331 Status: states.ObjectReady, 332 AttrsJSON: []byte(`{"id":"B"}`), 333 }, 334 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 335 ) 336 root.SetResourceInstanceCurrent( 337 mustResourceInstanceAddr("test_object.B").Resource, 338 &states.ResourceInstanceObjectSrc{ 339 Status: states.ObjectReady, 340 AttrsJSON: []byte(`{"id":"B"}`), 341 Dependencies: []addrs.ConfigResource{addrA.ContainingResource().Config()}, 342 }, 343 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 344 ) 345 346 b := &ApplyGraphBuilder{ 347 Config: testModule(t, "graph-builder-apply-count"), 348 Changes: changes, 349 Plugins: simpleMockPluginLibrary(), 350 State: state, 351 } 352 353 g, err := b.Build(addrs.RootModuleInstance) 354 if err != nil { 355 t.Fatalf("err: %s", err) 356 } 357 358 if g.Path.String() != addrs.RootModuleInstance.String() { 359 t.Fatalf("wrong module path %q", g.Path) 360 } 361 362 got := strings.TrimSpace(g.String()) 363 want := strings.TrimSpace(testApplyGraphBuilderDestroyCountStr) 364 if diff := cmp.Diff(want, got); diff != "" { 365 t.Fatalf("wrong result\n%s", diff) 366 } 367 } 368 369 func TestApplyGraphBuilder_moduleDestroy(t *testing.T) { 370 changes := &plans.Changes{ 371 Resources: []*plans.ResourceInstanceChangeSrc{ 372 { 373 Addr: mustResourceInstanceAddr("module.A.test_object.foo"), 374 ChangeSrc: plans.ChangeSrc{ 375 Action: plans.Delete, 376 }, 377 }, 378 { 379 Addr: mustResourceInstanceAddr("module.B.test_object.foo"), 380 ChangeSrc: plans.ChangeSrc{ 381 Action: plans.Delete, 382 }, 383 }, 384 }, 385 } 386 387 state := states.NewState() 388 modA := state.EnsureModule(addrs.RootModuleInstance.Child("A", addrs.NoKey)) 389 modA.SetResourceInstanceCurrent( 390 mustResourceInstanceAddr("test_object.foo").Resource, 391 &states.ResourceInstanceObjectSrc{ 392 Status: states.ObjectReady, 393 AttrsJSON: []byte(`{"id":"foo"}`), 394 }, 395 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 396 ) 397 modB := state.EnsureModule(addrs.RootModuleInstance.Child("B", addrs.NoKey)) 398 modB.SetResourceInstanceCurrent( 399 mustResourceInstanceAddr("test_object.foo").Resource, 400 &states.ResourceInstanceObjectSrc{ 401 Status: states.ObjectReady, 402 AttrsJSON: []byte(`{"id":"foo","value":"foo"}`), 403 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.A.test_object.foo")}, 404 }, 405 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 406 ) 407 408 b := &ApplyGraphBuilder{ 409 Config: testModule(t, "graph-builder-apply-module-destroy"), 410 Changes: changes, 411 Plugins: simpleMockPluginLibrary(), 412 State: state, 413 } 414 415 g, err := b.Build(addrs.RootModuleInstance) 416 if err != nil { 417 t.Fatalf("err: %s", err) 418 } 419 420 testGraphHappensBefore( 421 t, g, 422 "module.B.test_object.foo (destroy)", 423 "module.A.test_object.foo (destroy)", 424 ) 425 } 426 427 func TestApplyGraphBuilder_targetModule(t *testing.T) { 428 changes := &plans.Changes{ 429 Resources: []*plans.ResourceInstanceChangeSrc{ 430 { 431 Addr: mustResourceInstanceAddr("test_object.foo"), 432 ChangeSrc: plans.ChangeSrc{ 433 Action: plans.Update, 434 }, 435 }, 436 { 437 Addr: mustResourceInstanceAddr("module.child2.test_object.foo"), 438 ChangeSrc: plans.ChangeSrc{ 439 Action: plans.Update, 440 }, 441 }, 442 }, 443 } 444 445 b := &ApplyGraphBuilder{ 446 Config: testModule(t, "graph-builder-apply-target-module"), 447 Changes: changes, 448 Plugins: simpleMockPluginLibrary(), 449 Targets: []addrs.Targetable{ 450 addrs.RootModuleInstance.Child("child2", addrs.NoKey), 451 }, 452 } 453 454 g, err := b.Build(addrs.RootModuleInstance) 455 if err != nil { 456 t.Fatalf("err: %s", err) 457 } 458 459 testGraphNotContains(t, g, "module.child1.output.instance_id") 460 } 461 462 // Ensure that an update resulting from the removal of a resource happens after 463 // that resource is destroyed. 464 func TestApplyGraphBuilder_updateFromOrphan(t *testing.T) { 465 schemas := simpleTestSchemas() 466 instanceSchema := schemas.Providers[addrs.NewDefaultProvider("test")].ResourceTypes["test_object"] 467 468 bBefore, _ := plans.NewDynamicValue( 469 cty.ObjectVal(map[string]cty.Value{ 470 "id": cty.StringVal("b_id"), 471 "test_string": cty.StringVal("a_id"), 472 }), instanceSchema.Block.ImpliedType()) 473 bAfter, _ := plans.NewDynamicValue( 474 cty.ObjectVal(map[string]cty.Value{ 475 "id": cty.StringVal("b_id"), 476 "test_string": cty.StringVal("changed"), 477 }), instanceSchema.Block.ImpliedType()) 478 479 changes := &plans.Changes{ 480 Resources: []*plans.ResourceInstanceChangeSrc{ 481 { 482 Addr: mustResourceInstanceAddr("test_object.a"), 483 ChangeSrc: plans.ChangeSrc{ 484 Action: plans.Delete, 485 }, 486 }, 487 { 488 Addr: mustResourceInstanceAddr("test_object.b"), 489 ChangeSrc: plans.ChangeSrc{ 490 Action: plans.Update, 491 Before: bBefore, 492 After: bAfter, 493 }, 494 }, 495 }, 496 } 497 498 state := states.NewState() 499 root := state.EnsureModule(addrs.RootModuleInstance) 500 root.SetResourceInstanceCurrent( 501 addrs.Resource{ 502 Mode: addrs.ManagedResourceMode, 503 Type: "test_object", 504 Name: "a", 505 }.Instance(addrs.NoKey), 506 &states.ResourceInstanceObjectSrc{ 507 Status: states.ObjectReady, 508 AttrsJSON: []byte(`{"id":"a_id"}`), 509 }, 510 addrs.AbsProviderConfig{ 511 Provider: addrs.NewDefaultProvider("test"), 512 Module: addrs.RootModule, 513 }, 514 ) 515 root.SetResourceInstanceCurrent( 516 addrs.Resource{ 517 Mode: addrs.ManagedResourceMode, 518 Type: "test_object", 519 Name: "b", 520 }.Instance(addrs.NoKey), 521 &states.ResourceInstanceObjectSrc{ 522 Status: states.ObjectReady, 523 AttrsJSON: []byte(`{"id":"b_id","test_string":"a_id"}`), 524 Dependencies: []addrs.ConfigResource{ 525 { 526 Resource: addrs.Resource{ 527 Mode: addrs.ManagedResourceMode, 528 Type: "test_object", 529 Name: "a", 530 }, 531 Module: root.Addr.Module(), 532 }, 533 }, 534 }, 535 addrs.AbsProviderConfig{ 536 Provider: addrs.NewDefaultProvider("test"), 537 Module: addrs.RootModule, 538 }, 539 ) 540 541 b := &ApplyGraphBuilder{ 542 Config: testModule(t, "graph-builder-apply-orphan-update"), 543 Changes: changes, 544 Plugins: simpleMockPluginLibrary(), 545 State: state, 546 } 547 548 g, err := b.Build(addrs.RootModuleInstance) 549 if err != nil { 550 t.Fatalf("err: %s", err) 551 } 552 553 expected := strings.TrimSpace(` 554 test_object.a (destroy) 555 test_object.b 556 test_object.a (destroy) 557 `) 558 559 instanceGraph := filterInstances(g) 560 got := strings.TrimSpace(instanceGraph.String()) 561 562 if got != expected { 563 t.Fatalf("expected:\n%s\ngot:\n%s", expected, got) 564 } 565 } 566 567 // Ensure that an update resulting from the removal of a resource happens before 568 // a CBD resource is destroyed. 569 func TestApplyGraphBuilder_updateFromCBDOrphan(t *testing.T) { 570 schemas := simpleTestSchemas() 571 instanceSchema := schemas.Providers[addrs.NewDefaultProvider("test")].ResourceTypes["test_object"] 572 573 bBefore, _ := plans.NewDynamicValue( 574 cty.ObjectVal(map[string]cty.Value{ 575 "id": cty.StringVal("b_id"), 576 "test_string": cty.StringVal("a_id"), 577 }), instanceSchema.Block.ImpliedType()) 578 bAfter, _ := plans.NewDynamicValue( 579 cty.ObjectVal(map[string]cty.Value{ 580 "id": cty.StringVal("b_id"), 581 "test_string": cty.StringVal("changed"), 582 }), instanceSchema.Block.ImpliedType()) 583 584 changes := &plans.Changes{ 585 Resources: []*plans.ResourceInstanceChangeSrc{ 586 { 587 Addr: mustResourceInstanceAddr("test_object.a"), 588 ChangeSrc: plans.ChangeSrc{ 589 Action: plans.Delete, 590 }, 591 }, 592 { 593 Addr: mustResourceInstanceAddr("test_object.b"), 594 ChangeSrc: plans.ChangeSrc{ 595 Action: plans.Update, 596 Before: bBefore, 597 After: bAfter, 598 }, 599 }, 600 }, 601 } 602 603 state := states.NewState() 604 root := state.EnsureModule(addrs.RootModuleInstance) 605 root.SetResourceInstanceCurrent( 606 addrs.Resource{ 607 Mode: addrs.ManagedResourceMode, 608 Type: "test_object", 609 Name: "a", 610 }.Instance(addrs.NoKey), 611 &states.ResourceInstanceObjectSrc{ 612 Status: states.ObjectReady, 613 AttrsJSON: []byte(`{"id":"a_id"}`), 614 CreateBeforeDestroy: true, 615 }, 616 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 617 ) 618 root.SetResourceInstanceCurrent( 619 addrs.Resource{ 620 Mode: addrs.ManagedResourceMode, 621 Type: "test_object", 622 Name: "b", 623 }.Instance(addrs.NoKey), 624 &states.ResourceInstanceObjectSrc{ 625 Status: states.ObjectReady, 626 AttrsJSON: []byte(`{"id":"b_id","test_string":"a_id"}`), 627 Dependencies: []addrs.ConfigResource{ 628 { 629 Resource: addrs.Resource{ 630 Mode: addrs.ManagedResourceMode, 631 Type: "test_object", 632 Name: "a", 633 }, 634 Module: root.Addr.Module(), 635 }, 636 }, 637 }, 638 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 639 ) 640 641 b := &ApplyGraphBuilder{ 642 Config: testModule(t, "graph-builder-apply-orphan-update"), 643 Changes: changes, 644 Plugins: simpleMockPluginLibrary(), 645 State: state, 646 } 647 648 g, err := b.Build(addrs.RootModuleInstance) 649 if err != nil { 650 t.Fatalf("err: %s", err) 651 } 652 653 expected := strings.TrimSpace(` 654 test_object.a (destroy) 655 test_object.b 656 test_object.b 657 `) 658 659 instanceGraph := filterInstances(g) 660 got := strings.TrimSpace(instanceGraph.String()) 661 662 if got != expected { 663 t.Fatalf("expected:\n%s\ngot:\n%s", expected, got) 664 } 665 } 666 667 // The orphan clean up node should not be connected to a provider 668 func TestApplyGraphBuilder_orphanedWithProvider(t *testing.T) { 669 changes := &plans.Changes{ 670 Resources: []*plans.ResourceInstanceChangeSrc{ 671 { 672 Addr: mustResourceInstanceAddr("test_object.A"), 673 ChangeSrc: plans.ChangeSrc{ 674 Action: plans.Delete, 675 }, 676 }, 677 }, 678 } 679 680 state := states.NewState() 681 root := state.EnsureModule(addrs.RootModuleInstance) 682 root.SetResourceInstanceCurrent( 683 mustResourceInstanceAddr("test_object.A").Resource, 684 &states.ResourceInstanceObjectSrc{ 685 Status: states.ObjectReady, 686 AttrsJSON: []byte(`{"id":"A"}`), 687 }, 688 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"].foo`), 689 ) 690 691 b := &ApplyGraphBuilder{ 692 Config: testModule(t, "graph-builder-orphan-alias"), 693 Changes: changes, 694 Plugins: simpleMockPluginLibrary(), 695 State: state, 696 } 697 698 g, err := b.Build(addrs.RootModuleInstance) 699 if err != nil { 700 t.Fatal(err) 701 } 702 703 // The cleanup node has no state or config of its own, so would create a 704 // default provider which we don't want. 705 testGraphNotContains(t, g, "provider.test") 706 } 707 708 func TestApplyGraphBuilder_withChecks(t *testing.T) { 709 awsProvider := mockProviderWithResourceTypeSchema("aws_instance", simpleTestSchema()) 710 711 changes := &plans.Changes{ 712 Resources: []*plans.ResourceInstanceChangeSrc{ 713 { 714 Addr: mustResourceInstanceAddr("aws_instance.foo"), 715 ChangeSrc: plans.ChangeSrc{ 716 Action: plans.Create, 717 }, 718 }, 719 { 720 Addr: mustResourceInstanceAddr("aws_instance.baz"), 721 ChangeSrc: plans.ChangeSrc{ 722 Action: plans.Create, 723 }, 724 }, 725 { 726 Addr: mustResourceInstanceAddr("data.aws_data_source.bar"), 727 ChangeSrc: plans.ChangeSrc{ 728 Action: plans.Read, 729 }, 730 ActionReason: plans.ResourceInstanceReadBecauseCheckNested, 731 }, 732 }, 733 } 734 735 plugins := newContextPluginsForTest(map[addrs.Provider]providers.Factory{ 736 addrs.NewDefaultProvider("aws"): providers.FactoryFixed(awsProvider), 737 }, t) 738 739 b := &ApplyGraphBuilder{ 740 Config: testModule(t, "apply-with-checks"), 741 Changes: changes, 742 Plugins: plugins, 743 State: states.NewState(), 744 Operation: walkApply, 745 } 746 747 g, err := b.Build(addrs.RootModuleInstance) 748 if err != nil { 749 t.Fatalf("err: %s", err) 750 } 751 752 if g.Path.String() != addrs.RootModuleInstance.String() { 753 t.Fatalf("wrong path %q", g.Path.String()) 754 } 755 756 got := strings.TrimSpace(g.String()) 757 // We're especially looking for the edge here, where aws_instance.bat 758 // has a dependency on aws_instance.boo 759 want := strings.TrimSpace(testPlanWithCheckGraphBuilderStr) 760 if diff := cmp.Diff(want, got); diff != "" { 761 t.Fatalf("\ngot:\n%s\n\nwant:\n%s\n\ndiff:\n%s", got, want, diff) 762 } 763 764 } 765 766 const testPlanWithCheckGraphBuilderStr = ` 767 (execute checks) 768 aws_instance.baz 769 aws_instance.baz 770 aws_instance.baz (expand) 771 aws_instance.baz (expand) 772 aws_instance.foo 773 aws_instance.foo 774 aws_instance.foo (expand) 775 aws_instance.foo (expand) 776 provider["registry.opentofu.org/hashicorp/aws"] 777 check.my_check (expand) 778 data.aws_data_source.bar 779 data.aws_data_source.bar 780 (execute checks) 781 data.aws_data_source.bar (expand) 782 data.aws_data_source.bar (expand) 783 provider["registry.opentofu.org/hashicorp/aws"] 784 provider["registry.opentofu.org/hashicorp/aws"] 785 provider["registry.opentofu.org/hashicorp/aws"] (close) 786 data.aws_data_source.bar 787 root 788 check.my_check (expand) 789 provider["registry.opentofu.org/hashicorp/aws"] (close) 790 ` 791 792 const testApplyGraphBuilderStr = ` 793 module.child (close) 794 module.child.test_object.other 795 module.child (expand) 796 module.child.test_object.create 797 module.child.test_object.create (expand) 798 module.child.test_object.create (expand) 799 module.child (expand) 800 provider["registry.opentofu.org/hashicorp/test"] 801 module.child.test_object.other 802 module.child.test_object.other (expand) 803 module.child.test_object.other (expand) 804 module.child.test_object.create 805 provider["registry.opentofu.org/hashicorp/test"] 806 provider["registry.opentofu.org/hashicorp/test"] (close) 807 module.child.test_object.other 808 test_object.other 809 root 810 module.child (close) 811 provider["registry.opentofu.org/hashicorp/test"] (close) 812 test_object.create 813 test_object.create (expand) 814 test_object.create (expand) 815 provider["registry.opentofu.org/hashicorp/test"] 816 test_object.other 817 test_object.other (expand) 818 test_object.other (expand) 819 test_object.create 820 ` 821 822 const testApplyGraphBuilderDestroyCountStr = ` 823 provider["registry.opentofu.org/hashicorp/test"] 824 provider["registry.opentofu.org/hashicorp/test"] (close) 825 test_object.B 826 root 827 provider["registry.opentofu.org/hashicorp/test"] (close) 828 test_object.A (expand) 829 provider["registry.opentofu.org/hashicorp/test"] 830 test_object.A[1] (destroy) 831 provider["registry.opentofu.org/hashicorp/test"] 832 test_object.B 833 test_object.A[1] (destroy) 834 test_object.B (expand) 835 test_object.B (expand) 836 test_object.A (expand) 837 `