github.com/opentofu/opentofu@v1.7.1/internal/legacy/tofu/resource_address_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 "reflect" 11 "testing" 12 13 "github.com/opentofu/opentofu/internal/addrs" 14 "github.com/opentofu/opentofu/internal/configs" 15 ) 16 17 func TestParseResourceAddressInternal(t *testing.T) { 18 cases := map[string]struct { 19 Input string 20 Expected *ResourceAddress 21 Output string 22 }{ 23 "basic resource": { 24 "aws_instance.foo", 25 &ResourceAddress{ 26 Mode: ManagedResourceMode, 27 Type: "aws_instance", 28 Name: "foo", 29 InstanceType: TypePrimary, 30 Index: -1, 31 }, 32 "aws_instance.foo", 33 }, 34 35 "basic resource with count": { 36 "aws_instance.foo.1", 37 &ResourceAddress{ 38 Mode: ManagedResourceMode, 39 Type: "aws_instance", 40 Name: "foo", 41 InstanceType: TypePrimary, 42 Index: 1, 43 }, 44 "aws_instance.foo[1]", 45 }, 46 47 "data resource": { 48 "data.aws_ami.foo", 49 &ResourceAddress{ 50 Mode: DataResourceMode, 51 Type: "aws_ami", 52 Name: "foo", 53 InstanceType: TypePrimary, 54 Index: -1, 55 }, 56 "data.aws_ami.foo", 57 }, 58 59 "data resource with count": { 60 "data.aws_ami.foo.1", 61 &ResourceAddress{ 62 Mode: DataResourceMode, 63 Type: "aws_ami", 64 Name: "foo", 65 InstanceType: TypePrimary, 66 Index: 1, 67 }, 68 "data.aws_ami.foo[1]", 69 }, 70 71 "non-data resource with 4 elements": { 72 "aws_instance.foo.bar.1", 73 nil, 74 "", 75 }, 76 } 77 78 for tn, tc := range cases { 79 t.Run(tc.Input, func(t *testing.T) { 80 out, err := parseResourceAddressInternal(tc.Input) 81 if (err != nil) != (tc.Expected == nil) { 82 t.Fatalf("%s: unexpected err: %#v", tn, err) 83 } 84 if err != nil { 85 return 86 } 87 88 if !reflect.DeepEqual(out, tc.Expected) { 89 t.Fatalf("bad: %q\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.Expected, out) 90 } 91 92 // Compare outputs if those exist 93 expected := tc.Input 94 if tc.Output != "" { 95 expected = tc.Output 96 } 97 if out.String() != expected { 98 t.Fatalf("bad: %q\n\nexpected: %s\n\ngot: %s", tn, expected, out) 99 } 100 101 // Compare equality because the internal parse is used 102 // to compare equality to equal inputs. 103 if !out.Equals(tc.Expected) { 104 t.Fatalf("expected equality:\n\n%#v\n\n%#v", out, tc.Expected) 105 } 106 }) 107 } 108 } 109 110 func TestParseResourceAddress(t *testing.T) { 111 cases := map[string]struct { 112 Input string 113 Expected *ResourceAddress 114 Output string 115 Err bool 116 }{ 117 "implicit primary managed instance, no specific index": { 118 "aws_instance.foo", 119 &ResourceAddress{ 120 Mode: ManagedResourceMode, 121 Type: "aws_instance", 122 Name: "foo", 123 InstanceType: TypePrimary, 124 Index: -1, 125 }, 126 "", 127 false, 128 }, 129 "implicit primary data instance, no specific index": { 130 "data.aws_instance.foo", 131 &ResourceAddress{ 132 Mode: DataResourceMode, 133 Type: "aws_instance", 134 Name: "foo", 135 InstanceType: TypePrimary, 136 Index: -1, 137 }, 138 "", 139 false, 140 }, 141 "implicit primary, explicit index": { 142 "aws_instance.foo[2]", 143 &ResourceAddress{ 144 Mode: ManagedResourceMode, 145 Type: "aws_instance", 146 Name: "foo", 147 InstanceType: TypePrimary, 148 Index: 2, 149 }, 150 "", 151 false, 152 }, 153 "implicit primary, explicit index over ten": { 154 "aws_instance.foo[12]", 155 &ResourceAddress{ 156 Mode: ManagedResourceMode, 157 Type: "aws_instance", 158 Name: "foo", 159 InstanceType: TypePrimary, 160 Index: 12, 161 }, 162 "", 163 false, 164 }, 165 "explicit primary, explicit index": { 166 "aws_instance.foo.primary[2]", 167 &ResourceAddress{ 168 Mode: ManagedResourceMode, 169 Type: "aws_instance", 170 Name: "foo", 171 InstanceType: TypePrimary, 172 InstanceTypeSet: true, 173 Index: 2, 174 }, 175 "", 176 false, 177 }, 178 "tainted": { 179 "aws_instance.foo.tainted", 180 &ResourceAddress{ 181 Mode: ManagedResourceMode, 182 Type: "aws_instance", 183 Name: "foo", 184 InstanceType: TypeTainted, 185 InstanceTypeSet: true, 186 Index: -1, 187 }, 188 "", 189 false, 190 }, 191 "deposed": { 192 "aws_instance.foo.deposed", 193 &ResourceAddress{ 194 Mode: ManagedResourceMode, 195 Type: "aws_instance", 196 Name: "foo", 197 InstanceType: TypeDeposed, 198 InstanceTypeSet: true, 199 Index: -1, 200 }, 201 "", 202 false, 203 }, 204 "with a hyphen": { 205 "aws_instance.foo-bar", 206 &ResourceAddress{ 207 Mode: ManagedResourceMode, 208 Type: "aws_instance", 209 Name: "foo-bar", 210 InstanceType: TypePrimary, 211 Index: -1, 212 }, 213 "", 214 false, 215 }, 216 "managed in a module": { 217 "module.child.aws_instance.foo", 218 &ResourceAddress{ 219 Path: []string{"child"}, 220 Mode: ManagedResourceMode, 221 Type: "aws_instance", 222 Name: "foo", 223 InstanceType: TypePrimary, 224 Index: -1, 225 }, 226 "", 227 false, 228 }, 229 "data in a module": { 230 "module.child.data.aws_instance.foo", 231 &ResourceAddress{ 232 Path: []string{"child"}, 233 Mode: DataResourceMode, 234 Type: "aws_instance", 235 Name: "foo", 236 InstanceType: TypePrimary, 237 Index: -1, 238 }, 239 "", 240 false, 241 }, 242 "nested modules": { 243 "module.a.module.b.module.forever.aws_instance.foo", 244 &ResourceAddress{ 245 Path: []string{"a", "b", "forever"}, 246 Mode: ManagedResourceMode, 247 Type: "aws_instance", 248 Name: "foo", 249 InstanceType: TypePrimary, 250 Index: -1, 251 }, 252 "", 253 false, 254 }, 255 "just a module": { 256 "module.a", 257 &ResourceAddress{ 258 Path: []string{"a"}, 259 Type: "", 260 Name: "", 261 InstanceType: TypePrimary, 262 Index: -1, 263 }, 264 "", 265 false, 266 }, 267 "just a nested module": { 268 "module.a.module.b", 269 &ResourceAddress{ 270 Path: []string{"a", "b"}, 271 Type: "", 272 Name: "", 273 InstanceType: TypePrimary, 274 Index: -1, 275 }, 276 "", 277 false, 278 }, 279 "module missing resource type": { 280 "module.name.foo", 281 nil, 282 "", 283 true, 284 }, 285 } 286 287 for tn, tc := range cases { 288 t.Run(tn, func(t *testing.T) { 289 out, err := ParseResourceAddress(tc.Input) 290 if (err != nil) != tc.Err { 291 t.Fatalf("%s: unexpected err: %#v", tn, err) 292 } 293 if tc.Err { 294 return 295 } 296 297 if !reflect.DeepEqual(out, tc.Expected) { 298 t.Fatalf("bad: %q\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.Expected, out) 299 } 300 301 expected := tc.Input 302 if tc.Output != "" { 303 expected = tc.Output 304 } 305 if out.String() != expected { 306 t.Fatalf("bad: %q\n\nexpected: %s\n\ngot: %s", tn, expected, out) 307 } 308 }) 309 } 310 } 311 312 func TestResourceAddressContains(t *testing.T) { 313 tests := []struct { 314 Address *ResourceAddress 315 Other *ResourceAddress 316 Want bool 317 }{ 318 { 319 &ResourceAddress{ 320 Mode: ManagedResourceMode, 321 Type: "aws_instance", 322 Name: "foo", 323 InstanceTypeSet: true, 324 InstanceType: TypePrimary, 325 Index: 0, 326 }, 327 &ResourceAddress{ 328 Mode: ManagedResourceMode, 329 Type: "aws_instance", 330 Name: "foo", 331 InstanceTypeSet: true, 332 InstanceType: TypePrimary, 333 Index: 0, 334 }, 335 true, 336 }, 337 { 338 &ResourceAddress{ 339 Mode: ManagedResourceMode, 340 Type: "aws_instance", 341 Name: "foo", 342 InstanceTypeSet: false, 343 Index: 0, 344 }, 345 &ResourceAddress{ 346 Mode: ManagedResourceMode, 347 Type: "aws_instance", 348 Name: "foo", 349 InstanceTypeSet: true, 350 InstanceType: TypePrimary, 351 Index: 0, 352 }, 353 true, 354 }, 355 { 356 &ResourceAddress{ 357 Mode: ManagedResourceMode, 358 Type: "aws_instance", 359 Name: "foo", 360 InstanceTypeSet: false, 361 Index: -1, 362 }, 363 &ResourceAddress{ 364 Mode: ManagedResourceMode, 365 Type: "aws_instance", 366 Name: "foo", 367 InstanceTypeSet: true, 368 InstanceType: TypePrimary, 369 Index: 0, 370 }, 371 true, 372 }, 373 { 374 &ResourceAddress{ 375 Mode: ManagedResourceMode, 376 Type: "aws_instance", 377 Name: "foo", 378 InstanceTypeSet: false, 379 Index: -1, 380 }, 381 &ResourceAddress{ 382 Mode: ManagedResourceMode, 383 Type: "aws_instance", 384 Name: "foo", 385 InstanceTypeSet: false, 386 Index: -1, 387 }, 388 true, 389 }, 390 { 391 &ResourceAddress{ 392 InstanceTypeSet: false, 393 Index: -1, 394 }, 395 &ResourceAddress{ 396 Mode: ManagedResourceMode, 397 Type: "aws_instance", 398 Name: "foo", 399 InstanceTypeSet: false, 400 Index: -1, 401 }, 402 true, 403 }, 404 { 405 &ResourceAddress{ 406 InstanceTypeSet: false, 407 Index: -1, 408 }, 409 &ResourceAddress{ 410 Path: []string{"bar"}, 411 Mode: ManagedResourceMode, 412 Type: "aws_instance", 413 Name: "foo", 414 InstanceTypeSet: false, 415 Index: -1, 416 }, 417 true, 418 }, 419 { 420 &ResourceAddress{ 421 Path: []string{"bar"}, 422 InstanceTypeSet: false, 423 Index: -1, 424 }, 425 &ResourceAddress{ 426 Path: []string{"bar"}, 427 Mode: ManagedResourceMode, 428 Type: "aws_instance", 429 Name: "foo", 430 InstanceTypeSet: false, 431 Index: -1, 432 }, 433 true, 434 }, 435 { 436 &ResourceAddress{ 437 Path: []string{"bar"}, 438 InstanceTypeSet: false, 439 Index: -1, 440 }, 441 &ResourceAddress{ 442 Path: []string{"bar", "baz"}, 443 Mode: ManagedResourceMode, 444 Type: "aws_instance", 445 Name: "foo", 446 InstanceTypeSet: false, 447 Index: -1, 448 }, 449 true, 450 }, 451 { 452 &ResourceAddress{ 453 Path: []string{"bar"}, 454 InstanceTypeSet: false, 455 Index: -1, 456 }, 457 &ResourceAddress{ 458 Path: []string{"bar", "baz"}, 459 InstanceTypeSet: false, 460 Index: -1, 461 }, 462 true, 463 }, 464 { 465 &ResourceAddress{ 466 Path: []string{"bar"}, 467 InstanceTypeSet: false, 468 Index: -1, 469 }, 470 &ResourceAddress{ 471 Path: []string{"bar", "baz", "foo", "pizza"}, 472 InstanceTypeSet: false, 473 Index: -1, 474 }, 475 true, 476 }, 477 478 { 479 &ResourceAddress{ 480 Mode: ManagedResourceMode, 481 Type: "aws_instance", 482 Name: "bar", 483 InstanceTypeSet: true, 484 InstanceType: TypePrimary, 485 Index: 0, 486 }, 487 &ResourceAddress{ 488 Mode: ManagedResourceMode, 489 Type: "aws_instance", 490 Name: "foo", 491 InstanceTypeSet: true, 492 InstanceType: TypePrimary, 493 Index: 0, 494 }, 495 false, 496 }, 497 { 498 &ResourceAddress{ 499 Mode: ManagedResourceMode, 500 Type: "aws_instance", 501 Name: "foo", 502 InstanceTypeSet: true, 503 InstanceType: TypePrimary, 504 Index: 0, 505 }, 506 &ResourceAddress{ 507 Mode: DataResourceMode, 508 Type: "aws_instance", 509 Name: "foo", 510 InstanceTypeSet: true, 511 InstanceType: TypePrimary, 512 Index: 0, 513 }, 514 false, 515 }, 516 { 517 &ResourceAddress{ 518 Path: []string{"bar"}, 519 InstanceTypeSet: false, 520 Index: -1, 521 }, 522 &ResourceAddress{ 523 Path: []string{"baz"}, 524 Mode: ManagedResourceMode, 525 Type: "aws_instance", 526 Name: "foo", 527 InstanceTypeSet: false, 528 Index: -1, 529 }, 530 false, 531 }, 532 { 533 &ResourceAddress{ 534 Path: []string{"bar"}, 535 InstanceTypeSet: false, 536 Index: -1, 537 }, 538 &ResourceAddress{ 539 Path: []string{"baz", "bar"}, 540 Mode: ManagedResourceMode, 541 Type: "aws_instance", 542 Name: "foo", 543 InstanceTypeSet: false, 544 Index: -1, 545 }, 546 false, 547 }, 548 { 549 &ResourceAddress{ 550 Mode: ManagedResourceMode, 551 Type: "aws_instance", 552 Name: "foo", 553 InstanceTypeSet: true, 554 InstanceType: TypePrimary, 555 Index: 0, 556 }, 557 &ResourceAddress{ 558 Mode: ManagedResourceMode, 559 Type: "aws_instance", 560 Name: "foo", 561 InstanceTypeSet: false, 562 Index: 0, 563 }, 564 false, 565 }, 566 { 567 &ResourceAddress{ 568 Path: []string{"bar", "baz"}, 569 InstanceTypeSet: false, 570 Index: -1, 571 }, 572 &ResourceAddress{ 573 Path: []string{"bar"}, 574 InstanceTypeSet: false, 575 Index: -1, 576 }, 577 false, 578 }, 579 { 580 &ResourceAddress{ 581 Type: "aws_instance", 582 Name: "foo", 583 Index: 1, 584 InstanceType: TypePrimary, 585 Mode: ManagedResourceMode, 586 }, 587 &ResourceAddress{ 588 Type: "aws_instance", 589 Name: "foo", 590 Index: -1, 591 InstanceType: TypePrimary, 592 Mode: ManagedResourceMode, 593 }, 594 false, 595 }, 596 } 597 598 for _, test := range tests { 599 t.Run(fmt.Sprintf("%s contains %s", test.Address, test.Other), func(t *testing.T) { 600 got := test.Address.Contains(test.Other) 601 if got != test.Want { 602 t.Errorf( 603 "wrong result\nrecv: %s\ngiven: %s\ngot: %#v\nwant: %#v", 604 test.Address, test.Other, 605 got, test.Want, 606 ) 607 } 608 }) 609 } 610 } 611 612 func TestResourceAddressEquals(t *testing.T) { 613 cases := map[string]struct { 614 Address *ResourceAddress 615 Other interface{} 616 Expect bool 617 }{ 618 "basic match": { 619 Address: &ResourceAddress{ 620 Mode: ManagedResourceMode, 621 Type: "aws_instance", 622 Name: "foo", 623 InstanceType: TypePrimary, 624 Index: 0, 625 }, 626 Other: &ResourceAddress{ 627 Mode: ManagedResourceMode, 628 Type: "aws_instance", 629 Name: "foo", 630 InstanceType: TypePrimary, 631 Index: 0, 632 }, 633 Expect: true, 634 }, 635 "address does not set index": { 636 Address: &ResourceAddress{ 637 Mode: ManagedResourceMode, 638 Type: "aws_instance", 639 Name: "foo", 640 InstanceType: TypePrimary, 641 Index: -1, 642 }, 643 Other: &ResourceAddress{ 644 Mode: ManagedResourceMode, 645 Type: "aws_instance", 646 Name: "foo", 647 InstanceType: TypePrimary, 648 Index: 3, 649 }, 650 Expect: true, 651 }, 652 "other does not set index": { 653 Address: &ResourceAddress{ 654 Mode: ManagedResourceMode, 655 Type: "aws_instance", 656 Name: "foo", 657 InstanceType: TypePrimary, 658 Index: 3, 659 }, 660 Other: &ResourceAddress{ 661 Mode: ManagedResourceMode, 662 Type: "aws_instance", 663 Name: "foo", 664 InstanceType: TypePrimary, 665 Index: -1, 666 }, 667 Expect: true, 668 }, 669 "neither sets index": { 670 Address: &ResourceAddress{ 671 Mode: ManagedResourceMode, 672 Type: "aws_instance", 673 Name: "foo", 674 InstanceType: TypePrimary, 675 Index: -1, 676 }, 677 Other: &ResourceAddress{ 678 Mode: ManagedResourceMode, 679 Type: "aws_instance", 680 Name: "foo", 681 InstanceType: TypePrimary, 682 Index: -1, 683 }, 684 Expect: true, 685 }, 686 "index over ten": { 687 Address: &ResourceAddress{ 688 Mode: ManagedResourceMode, 689 Type: "aws_instance", 690 Name: "foo", 691 InstanceType: TypePrimary, 692 Index: 1, 693 }, 694 Other: &ResourceAddress{ 695 Mode: ManagedResourceMode, 696 Type: "aws_instance", 697 Name: "foo", 698 InstanceType: TypePrimary, 699 Index: 13, 700 }, 701 Expect: false, 702 }, 703 "different type": { 704 Address: &ResourceAddress{ 705 Mode: ManagedResourceMode, 706 Type: "aws_instance", 707 Name: "foo", 708 InstanceType: TypePrimary, 709 Index: 0, 710 }, 711 Other: &ResourceAddress{ 712 Mode: ManagedResourceMode, 713 Type: "aws_vpc", 714 Name: "foo", 715 InstanceType: TypePrimary, 716 Index: 0, 717 }, 718 Expect: false, 719 }, 720 "different mode": { 721 Address: &ResourceAddress{ 722 Mode: ManagedResourceMode, 723 Type: "aws_instance", 724 Name: "foo", 725 InstanceType: TypePrimary, 726 Index: 0, 727 }, 728 Other: &ResourceAddress{ 729 Mode: DataResourceMode, 730 Type: "aws_instance", 731 Name: "foo", 732 InstanceType: TypePrimary, 733 Index: 0, 734 }, 735 Expect: false, 736 }, 737 "different name": { 738 Address: &ResourceAddress{ 739 Mode: ManagedResourceMode, 740 Type: "aws_instance", 741 Name: "foo", 742 InstanceType: TypePrimary, 743 Index: 0, 744 }, 745 Other: &ResourceAddress{ 746 Mode: ManagedResourceMode, 747 Type: "aws_instance", 748 Name: "bar", 749 InstanceType: TypePrimary, 750 Index: 0, 751 }, 752 Expect: false, 753 }, 754 "different instance type": { 755 Address: &ResourceAddress{ 756 Mode: ManagedResourceMode, 757 Type: "aws_instance", 758 Name: "foo", 759 InstanceType: TypePrimary, 760 Index: 0, 761 }, 762 Other: &ResourceAddress{ 763 Mode: ManagedResourceMode, 764 Type: "aws_instance", 765 Name: "foo", 766 InstanceType: TypeTainted, 767 Index: 0, 768 }, 769 Expect: false, 770 }, 771 "different index": { 772 Address: &ResourceAddress{ 773 Mode: ManagedResourceMode, 774 Type: "aws_instance", 775 Name: "foo", 776 InstanceType: TypePrimary, 777 Index: 0, 778 }, 779 Other: &ResourceAddress{ 780 Mode: ManagedResourceMode, 781 Type: "aws_instance", 782 Name: "foo", 783 InstanceType: TypePrimary, 784 Index: 1, 785 }, 786 Expect: false, 787 }, 788 "module address matches address of managed resource inside module": { 789 Address: &ResourceAddress{ 790 Path: []string{"a", "b"}, 791 Type: "", 792 Name: "", 793 InstanceType: TypePrimary, 794 Index: -1, 795 }, 796 Other: &ResourceAddress{ 797 Path: []string{"a", "b"}, 798 Mode: ManagedResourceMode, 799 Type: "aws_instance", 800 Name: "foo", 801 InstanceType: TypePrimary, 802 Index: 0, 803 }, 804 Expect: true, 805 }, 806 "module address matches address of data resource inside module": { 807 Address: &ResourceAddress{ 808 Path: []string{"a", "b"}, 809 Type: "", 810 Name: "", 811 InstanceType: TypePrimary, 812 Index: -1, 813 }, 814 Other: &ResourceAddress{ 815 Path: []string{"a", "b"}, 816 Mode: DataResourceMode, 817 Type: "aws_instance", 818 Name: "foo", 819 InstanceType: TypePrimary, 820 Index: 0, 821 }, 822 Expect: true, 823 }, 824 "module address doesn't match managed resource outside module": { 825 Address: &ResourceAddress{ 826 Path: []string{"a", "b"}, 827 Type: "", 828 Name: "", 829 InstanceType: TypePrimary, 830 Index: -1, 831 }, 832 Other: &ResourceAddress{ 833 Path: []string{"a"}, 834 Mode: ManagedResourceMode, 835 Type: "aws_instance", 836 Name: "foo", 837 InstanceType: TypePrimary, 838 Index: 0, 839 }, 840 Expect: false, 841 }, 842 "module address doesn't match data resource outside module": { 843 Address: &ResourceAddress{ 844 Path: []string{"a", "b"}, 845 Type: "", 846 Name: "", 847 InstanceType: TypePrimary, 848 Index: -1, 849 }, 850 Other: &ResourceAddress{ 851 Path: []string{"a"}, 852 Mode: DataResourceMode, 853 Type: "aws_instance", 854 Name: "foo", 855 InstanceType: TypePrimary, 856 Index: 0, 857 }, 858 Expect: false, 859 }, 860 "nil path vs empty path should match": { 861 Address: &ResourceAddress{ 862 Path: []string{}, 863 Mode: ManagedResourceMode, 864 Type: "aws_instance", 865 Name: "foo", 866 InstanceType: TypePrimary, 867 Index: -1, 868 }, 869 Other: &ResourceAddress{ 870 Path: nil, 871 Mode: ManagedResourceMode, 872 Type: "aws_instance", 873 Name: "foo", 874 InstanceType: TypePrimary, 875 Index: 0, 876 }, 877 Expect: true, 878 }, 879 } 880 881 for tn, tc := range cases { 882 actual := tc.Address.Equals(tc.Other) 883 if actual != tc.Expect { 884 t.Fatalf("%q: expected equals: %t, got %t for:\n%#v\n%#v", 885 tn, tc.Expect, actual, tc.Address, tc.Other) 886 } 887 } 888 } 889 890 func TestResourceAddressStateId(t *testing.T) { 891 cases := map[string]struct { 892 Input *ResourceAddress 893 Expected string 894 }{ 895 "basic resource": { 896 &ResourceAddress{ 897 Mode: ManagedResourceMode, 898 Type: "aws_instance", 899 Name: "foo", 900 InstanceType: TypePrimary, 901 Index: -1, 902 }, 903 "aws_instance.foo", 904 }, 905 906 "basic resource with index": { 907 &ResourceAddress{ 908 Mode: ManagedResourceMode, 909 Type: "aws_instance", 910 Name: "foo", 911 InstanceType: TypePrimary, 912 Index: 2, 913 }, 914 "aws_instance.foo.2", 915 }, 916 917 "data resource": { 918 &ResourceAddress{ 919 Mode: DataResourceMode, 920 Type: "aws_instance", 921 Name: "foo", 922 InstanceType: TypePrimary, 923 Index: -1, 924 }, 925 "data.aws_instance.foo", 926 }, 927 } 928 929 for tn, tc := range cases { 930 t.Run(tn, func(t *testing.T) { 931 actual := tc.Input.stateId() 932 if actual != tc.Expected { 933 t.Fatalf("bad: %q\n\nexpected: %s\n\ngot: %s", tn, tc.Expected, actual) 934 } 935 }) 936 } 937 } 938 939 func TestResourceAddressHasResourceSpec(t *testing.T) { 940 cases := []struct { 941 Input string 942 Want bool 943 }{ 944 { 945 "module.foo", 946 false, 947 }, 948 { 949 "module.foo.module.bar", 950 false, 951 }, 952 { 953 "null_resource.baz", 954 true, 955 }, 956 { 957 "null_resource.baz[0]", 958 true, 959 }, 960 { 961 "data.null_data_source.baz", 962 true, 963 }, 964 { 965 "data.null_data_source.baz[0]", 966 true, 967 }, 968 { 969 "module.foo.null_resource.baz", 970 true, 971 }, 972 { 973 "module.foo.data.null_data_source.baz", 974 true, 975 }, 976 { 977 "module.foo.module.bar.null_resource.baz", 978 true, 979 }, 980 } 981 982 for _, test := range cases { 983 t.Run(test.Input, func(t *testing.T) { 984 addr, err := ParseResourceAddress(test.Input) 985 if err != nil { 986 t.Fatalf("error parsing address: %s", err) 987 } 988 got := addr.HasResourceSpec() 989 if got != test.Want { 990 t.Fatalf("%q: wrong result %#v; want %#v", test.Input, got, test.Want) 991 } 992 }) 993 } 994 } 995 996 func TestResourceAddressWholeModuleAddress(t *testing.T) { 997 cases := []struct { 998 Input string 999 Want string 1000 }{ 1001 { 1002 "module.foo", 1003 "module.foo", 1004 }, 1005 { 1006 "module.foo.module.bar", 1007 "module.foo.module.bar", 1008 }, 1009 { 1010 "null_resource.baz", 1011 "", 1012 }, 1013 { 1014 "null_resource.baz[0]", 1015 "", 1016 }, 1017 { 1018 "data.null_data_source.baz", 1019 "", 1020 }, 1021 { 1022 "data.null_data_source.baz[0]", 1023 "", 1024 }, 1025 { 1026 "module.foo.null_resource.baz", 1027 "module.foo", 1028 }, 1029 { 1030 "module.foo.data.null_data_source.baz", 1031 "module.foo", 1032 }, 1033 { 1034 "module.foo.module.bar.null_resource.baz", 1035 "module.foo.module.bar", 1036 }, 1037 } 1038 1039 for _, test := range cases { 1040 t.Run(test.Input, func(t *testing.T) { 1041 addr, err := ParseResourceAddress(test.Input) 1042 if err != nil { 1043 t.Fatalf("error parsing address: %s", err) 1044 } 1045 gotAddr := addr.WholeModuleAddress() 1046 got := gotAddr.String() 1047 if got != test.Want { 1048 t.Fatalf("%q: wrong result %#v; want %#v", test.Input, got, test.Want) 1049 } 1050 }) 1051 } 1052 } 1053 1054 func TestResourceAddressMatchesResourceConfig(t *testing.T) { 1055 root := []string(nil) 1056 child := []string{"child"} 1057 grandchild := []string{"child", "grandchild"} 1058 irrelevant := []string{"irrelevant"} 1059 1060 tests := []struct { 1061 Addr *ResourceAddress 1062 ModulePath []string 1063 Resource *configs.Resource 1064 Want bool 1065 }{ 1066 { 1067 &ResourceAddress{ 1068 Mode: ManagedResourceMode, 1069 Type: "null_resource", 1070 Name: "baz", 1071 Index: -1, 1072 }, 1073 root, 1074 &configs.Resource{ 1075 Mode: addrs.ManagedResourceMode, 1076 Type: "null_resource", 1077 Name: "baz", 1078 }, 1079 true, 1080 }, 1081 { 1082 &ResourceAddress{ 1083 Path: []string{"child"}, 1084 Mode: ManagedResourceMode, 1085 Type: "null_resource", 1086 Name: "baz", 1087 Index: -1, 1088 }, 1089 child, 1090 &configs.Resource{ 1091 Mode: addrs.ManagedResourceMode, 1092 Type: "null_resource", 1093 Name: "baz", 1094 }, 1095 true, 1096 }, 1097 { 1098 &ResourceAddress{ 1099 Path: []string{"child", "grandchild"}, 1100 Mode: ManagedResourceMode, 1101 Type: "null_resource", 1102 Name: "baz", 1103 Index: -1, 1104 }, 1105 grandchild, 1106 &configs.Resource{ 1107 Mode: addrs.ManagedResourceMode, 1108 Type: "null_resource", 1109 Name: "baz", 1110 }, 1111 true, 1112 }, 1113 { 1114 &ResourceAddress{ 1115 Path: []string{"child"}, 1116 Index: -1, 1117 }, 1118 child, 1119 &configs.Resource{ 1120 Mode: addrs.ManagedResourceMode, 1121 Type: "null_resource", 1122 Name: "baz", 1123 }, 1124 true, 1125 }, 1126 { 1127 &ResourceAddress{ 1128 Path: []string{"child", "grandchild"}, 1129 Index: -1, 1130 }, 1131 grandchild, 1132 &configs.Resource{ 1133 Mode: addrs.ManagedResourceMode, 1134 Type: "null_resource", 1135 Name: "baz", 1136 }, 1137 true, 1138 }, 1139 { 1140 &ResourceAddress{ 1141 Mode: DataResourceMode, 1142 Type: "null_resource", 1143 Name: "baz", 1144 Index: -1, 1145 }, 1146 irrelevant, 1147 &configs.Resource{ 1148 Mode: addrs.ManagedResourceMode, 1149 Type: "null_resource", 1150 Name: "baz", 1151 }, 1152 false, 1153 }, 1154 { 1155 &ResourceAddress{ 1156 Mode: ManagedResourceMode, 1157 Type: "null_resource", 1158 Name: "baz", 1159 Index: -1, 1160 }, 1161 irrelevant, 1162 &configs.Resource{ 1163 Mode: addrs.ManagedResourceMode, 1164 Type: "null_resource", 1165 Name: "pizza", 1166 }, 1167 false, 1168 }, 1169 { 1170 &ResourceAddress{ 1171 Mode: ManagedResourceMode, 1172 Type: "null_resource", 1173 Name: "baz", 1174 Index: -1, 1175 }, 1176 irrelevant, 1177 &configs.Resource{ 1178 Mode: addrs.ManagedResourceMode, 1179 Type: "aws_instance", 1180 Name: "baz", 1181 }, 1182 false, 1183 }, 1184 { 1185 &ResourceAddress{ 1186 Path: []string{"child", "grandchild"}, 1187 Mode: ManagedResourceMode, 1188 Type: "null_resource", 1189 Name: "baz", 1190 Index: -1, 1191 }, 1192 child, 1193 &configs.Resource{ 1194 Mode: addrs.ManagedResourceMode, 1195 Type: "null_resource", 1196 Name: "baz", 1197 }, 1198 false, 1199 }, 1200 { 1201 &ResourceAddress{ 1202 Path: []string{"child"}, 1203 Mode: ManagedResourceMode, 1204 Type: "null_resource", 1205 Name: "baz", 1206 Index: -1, 1207 }, 1208 grandchild, 1209 &configs.Resource{ 1210 Mode: addrs.ManagedResourceMode, 1211 Type: "null_resource", 1212 Name: "baz", 1213 }, 1214 false, 1215 }, 1216 } 1217 1218 for i, test := range tests { 1219 t.Run(fmt.Sprintf("%02d-%s", i, test.Addr), func(t *testing.T) { 1220 got := test.Addr.MatchesResourceConfig(test.ModulePath, test.Resource) 1221 if got != test.Want { 1222 t.Errorf( 1223 "wrong result\naddr: %s\nmod: %#v\nrsrc: %#v\ngot: %#v\nwant: %#v", 1224 test.Addr, test.ModulePath, test.Resource, got, test.Want, 1225 ) 1226 } 1227 }) 1228 } 1229 } 1230 1231 func TestResourceAddressLess(t *testing.T) { 1232 tests := []struct { 1233 A string 1234 B string 1235 Want bool 1236 }{ 1237 { 1238 "foo.bar", 1239 "module.baz.foo.bar", 1240 true, 1241 }, 1242 { 1243 "module.baz.foo.bar", 1244 "zzz.bar", // would sort after "module" in lexicographical sort 1245 false, 1246 }, 1247 { 1248 "module.baz.foo.bar", 1249 "module.baz.foo.bar", 1250 false, 1251 }, 1252 { 1253 "module.baz.foo.bar", 1254 "module.boz.foo.bar", 1255 true, 1256 }, 1257 { 1258 "module.boz.foo.bar", 1259 "module.baz.foo.bar", 1260 false, 1261 }, 1262 { 1263 "a.b", 1264 "b.c", 1265 true, 1266 }, 1267 { 1268 "a.b", 1269 "a.c", 1270 true, 1271 }, 1272 { 1273 "c.b", 1274 "b.c", 1275 false, 1276 }, 1277 { 1278 "a.b[9]", 1279 "a.b[10]", 1280 true, 1281 }, 1282 { 1283 "b.b[9]", 1284 "a.b[10]", 1285 false, 1286 }, 1287 { 1288 "a.b", 1289 "a.b.deposed", 1290 true, 1291 }, 1292 { 1293 "a.b.tainted", 1294 "a.b.deposed", 1295 true, 1296 }, 1297 } 1298 1299 for _, test := range tests { 1300 t.Run(fmt.Sprintf("%s < %s", test.A, test.B), func(t *testing.T) { 1301 addrA, err := ParseResourceAddress(test.A) 1302 if err != nil { 1303 t.Fatal(err) 1304 } 1305 addrB, err := ParseResourceAddress(test.B) 1306 if err != nil { 1307 t.Fatal(err) 1308 } 1309 got := addrA.Less(addrB) 1310 invGot := addrB.Less(addrA) 1311 if got != test.Want { 1312 t.Errorf( 1313 "wrong result\ntest: %s < %s\ngot: %#v\nwant: %#v", 1314 test.A, test.B, got, test.Want, 1315 ) 1316 } 1317 if test.A != test.B { // inverse test doesn't apply when equal 1318 if invGot != !test.Want { 1319 t.Errorf( 1320 "wrong inverse result\ntest: %s < %s\ngot: %#v\nwant: %#v", 1321 test.B, test.A, invGot, !test.Want, 1322 ) 1323 } 1324 } else { 1325 if invGot != test.Want { 1326 t.Errorf( 1327 "wrong inverse result\ntest: %s < %s\ngot: %#v\nwant: %#v", 1328 test.B, test.A, invGot, test.Want, 1329 ) 1330 } 1331 } 1332 }) 1333 } 1334 }