github.com/hashicorp/terraform-plugin-sdk@v1.17.2/helper/schema/resource_diff_test.go (about) 1 package schema 2 3 import ( 4 "fmt" 5 "reflect" 6 "sort" 7 "testing" 8 9 "github.com/davecgh/go-spew/spew" 10 "github.com/hashicorp/terraform-plugin-sdk/internal/configs/hcl2shim" 11 "github.com/hashicorp/terraform-plugin-sdk/terraform" 12 ) 13 14 // testSetFunc is a very simple function we use to test a foo/bar complex set. 15 // Both "foo" and "bar" are int values. 16 // 17 // This is not foolproof as since it performs sums, you can run into 18 // collisions. Spec tests accordingly. :P 19 func testSetFunc(v interface{}) int { 20 m := v.(map[string]interface{}) 21 return m["foo"].(int) + m["bar"].(int) 22 } 23 24 // resourceDiffTestCase provides a test case struct for SetNew and SetDiff. 25 type resourceDiffTestCase struct { 26 Name string 27 Schema map[string]*Schema 28 State *terraform.InstanceState 29 Config *terraform.ResourceConfig 30 Diff *terraform.InstanceDiff 31 Key string 32 OldValue interface{} 33 NewValue interface{} 34 Expected *terraform.InstanceDiff 35 ExpectedKeys []string 36 ExpectedError bool 37 } 38 39 // testDiffCases produces a list of test cases for use with SetNew and SetDiff. 40 func testDiffCases(t *testing.T, oldPrefix string, oldOffset int, computed bool) []resourceDiffTestCase { 41 return []resourceDiffTestCase{ 42 { 43 Name: "basic primitive diff", 44 Schema: map[string]*Schema{ 45 "foo": { 46 Type: TypeString, 47 Optional: true, 48 Computed: true, 49 }, 50 }, 51 State: &terraform.InstanceState{ 52 Attributes: map[string]string{ 53 "foo": "bar", 54 }, 55 }, 56 Config: testConfig(t, map[string]interface{}{ 57 "foo": "baz", 58 }), 59 Diff: &terraform.InstanceDiff{ 60 Attributes: map[string]*terraform.ResourceAttrDiff{ 61 "foo": { 62 Old: "bar", 63 New: "baz", 64 }, 65 }, 66 }, 67 Key: "foo", 68 NewValue: "qux", 69 Expected: &terraform.InstanceDiff{ 70 Attributes: map[string]*terraform.ResourceAttrDiff{ 71 "foo": { 72 Old: "bar", 73 New: func() string { 74 if computed { 75 return "" 76 } 77 return "qux" 78 }(), 79 NewComputed: computed, 80 }, 81 }, 82 }, 83 }, 84 { 85 Name: "basic set diff", 86 Schema: map[string]*Schema{ 87 "foo": { 88 Type: TypeSet, 89 Optional: true, 90 Computed: true, 91 Elem: &Schema{Type: TypeString}, 92 Set: HashString, 93 }, 94 }, 95 State: &terraform.InstanceState{ 96 Attributes: map[string]string{ 97 "foo.#": "1", 98 "foo.1996459178": "bar", 99 }, 100 }, 101 Config: testConfig(t, map[string]interface{}{ 102 "foo": []interface{}{"baz"}, 103 }), 104 Diff: &terraform.InstanceDiff{ 105 Attributes: map[string]*terraform.ResourceAttrDiff{ 106 "foo.1996459178": { 107 Old: "bar", 108 New: "", 109 NewRemoved: true, 110 }, 111 "foo.2015626392": { 112 Old: "", 113 New: "baz", 114 }, 115 }, 116 }, 117 Key: "foo", 118 NewValue: []interface{}{"qux"}, 119 Expected: &terraform.InstanceDiff{ 120 Attributes: func() map[string]*terraform.ResourceAttrDiff { 121 result := map[string]*terraform.ResourceAttrDiff{} 122 if computed { 123 result["foo.#"] = &terraform.ResourceAttrDiff{ 124 Old: "1", 125 New: "", 126 NewComputed: true, 127 } 128 } else { 129 result["foo.2800005064"] = &terraform.ResourceAttrDiff{ 130 Old: "", 131 New: "qux", 132 } 133 result["foo.1996459178"] = &terraform.ResourceAttrDiff{ 134 Old: "bar", 135 New: "", 136 NewRemoved: true, 137 } 138 } 139 return result 140 }(), 141 }, 142 }, 143 { 144 Name: "basic list diff", 145 Schema: map[string]*Schema{ 146 "foo": { 147 Type: TypeList, 148 Optional: true, 149 Computed: true, 150 Elem: &Schema{Type: TypeString}, 151 }, 152 }, 153 State: &terraform.InstanceState{ 154 Attributes: map[string]string{ 155 "foo.#": "1", 156 "foo.0": "bar", 157 }, 158 }, 159 Config: testConfig(t, map[string]interface{}{ 160 "foo": []interface{}{"baz"}, 161 }), 162 Diff: &terraform.InstanceDiff{ 163 Attributes: map[string]*terraform.ResourceAttrDiff{ 164 "foo.0": { 165 Old: "bar", 166 New: "baz", 167 }, 168 }, 169 }, 170 Key: "foo", 171 NewValue: []interface{}{"qux"}, 172 Expected: &terraform.InstanceDiff{ 173 Attributes: func() map[string]*terraform.ResourceAttrDiff { 174 result := make(map[string]*terraform.ResourceAttrDiff) 175 if computed { 176 result["foo.#"] = &terraform.ResourceAttrDiff{ 177 Old: "1", 178 New: "", 179 NewComputed: true, 180 } 181 } else { 182 result["foo.0"] = &terraform.ResourceAttrDiff{ 183 Old: "bar", 184 New: "qux", 185 } 186 } 187 return result 188 }(), 189 }, 190 }, 191 { 192 Name: "basic map diff", 193 Schema: map[string]*Schema{ 194 "foo": { 195 Type: TypeMap, 196 Optional: true, 197 Computed: true, 198 }, 199 }, 200 State: &terraform.InstanceState{ 201 Attributes: map[string]string{ 202 "foo.%": "1", 203 "foo.bar": "baz", 204 }, 205 }, 206 Config: testConfig(t, map[string]interface{}{ 207 "foo": map[string]interface{}{"bar": "qux"}, 208 }), 209 Diff: &terraform.InstanceDiff{ 210 Attributes: map[string]*terraform.ResourceAttrDiff{ 211 "foo.bar": { 212 Old: "baz", 213 New: "qux", 214 }, 215 }, 216 }, 217 Key: "foo", 218 NewValue: map[string]interface{}{"bar": "quux"}, 219 Expected: &terraform.InstanceDiff{ 220 Attributes: func() map[string]*terraform.ResourceAttrDiff { 221 result := make(map[string]*terraform.ResourceAttrDiff) 222 if computed { 223 result["foo.%"] = &terraform.ResourceAttrDiff{ 224 Old: "", 225 New: "", 226 NewComputed: true, 227 } 228 result["foo.bar"] = &terraform.ResourceAttrDiff{ 229 Old: "baz", 230 New: "", 231 NewRemoved: true, 232 } 233 } else { 234 result["foo.bar"] = &terraform.ResourceAttrDiff{ 235 Old: "baz", 236 New: "quux", 237 } 238 } 239 return result 240 }(), 241 }, 242 }, 243 { 244 Name: "additional diff with primitive", 245 Schema: map[string]*Schema{ 246 "foo": { 247 Type: TypeString, 248 Optional: true, 249 }, 250 "one": { 251 Type: TypeString, 252 Optional: true, 253 Computed: true, 254 }, 255 }, 256 State: &terraform.InstanceState{ 257 Attributes: map[string]string{ 258 "foo": "bar", 259 "one": "two", 260 }, 261 }, 262 Config: testConfig(t, map[string]interface{}{ 263 "foo": "baz", 264 }), 265 Diff: &terraform.InstanceDiff{ 266 Attributes: map[string]*terraform.ResourceAttrDiff{ 267 "foo": { 268 Old: "bar", 269 New: "baz", 270 }, 271 }, 272 }, 273 Key: "one", 274 NewValue: "four", 275 Expected: &terraform.InstanceDiff{ 276 Attributes: map[string]*terraform.ResourceAttrDiff{ 277 "foo": { 278 Old: "bar", 279 New: "baz", 280 }, 281 "one": { 282 Old: "two", 283 New: func() string { 284 if computed { 285 return "" 286 } 287 return "four" 288 }(), 289 NewComputed: computed, 290 }, 291 }, 292 }, 293 }, 294 { 295 Name: "additional diff with primitive computed only", 296 Schema: map[string]*Schema{ 297 "foo": { 298 Type: TypeString, 299 Optional: true, 300 }, 301 "one": { 302 Type: TypeString, 303 Computed: true, 304 }, 305 }, 306 State: &terraform.InstanceState{ 307 Attributes: map[string]string{ 308 "foo": "bar", 309 "one": "two", 310 }, 311 }, 312 Config: testConfig(t, map[string]interface{}{ 313 "foo": "baz", 314 }), 315 Diff: &terraform.InstanceDiff{ 316 Attributes: map[string]*terraform.ResourceAttrDiff{ 317 "foo": { 318 Old: "bar", 319 New: "baz", 320 }, 321 }, 322 }, 323 Key: "one", 324 NewValue: "three", 325 Expected: &terraform.InstanceDiff{ 326 Attributes: map[string]*terraform.ResourceAttrDiff{ 327 "foo": { 328 Old: "bar", 329 New: "baz", 330 }, 331 "one": { 332 Old: "two", 333 New: func() string { 334 if computed { 335 return "" 336 } 337 return "three" 338 }(), 339 NewComputed: computed, 340 }, 341 }, 342 }, 343 }, 344 { 345 Name: "complex-ish set diff", 346 Schema: map[string]*Schema{ 347 "top": { 348 Type: TypeSet, 349 Optional: true, 350 Computed: true, 351 Elem: &Resource{ 352 Schema: map[string]*Schema{ 353 "foo": { 354 Type: TypeInt, 355 Optional: true, 356 Computed: true, 357 }, 358 "bar": { 359 Type: TypeInt, 360 Optional: true, 361 Computed: true, 362 }, 363 }, 364 }, 365 Set: testSetFunc, 366 }, 367 }, 368 State: &terraform.InstanceState{ 369 Attributes: map[string]string{ 370 "top.#": "2", 371 "top.3.foo": "1", 372 "top.3.bar": "2", 373 "top.23.foo": "11", 374 "top.23.bar": "12", 375 }, 376 }, 377 Config: testConfig(t, map[string]interface{}{ 378 "top": []interface{}{ 379 map[string]interface{}{ 380 "foo": 1, 381 "bar": 3, 382 }, 383 map[string]interface{}{ 384 "foo": 12, 385 "bar": 12, 386 }, 387 }, 388 }), 389 Diff: &terraform.InstanceDiff{ 390 Attributes: map[string]*terraform.ResourceAttrDiff{ 391 "top.4.foo": { 392 Old: "", 393 New: "1", 394 }, 395 "top.4.bar": { 396 Old: "", 397 New: "3", 398 }, 399 "top.24.foo": { 400 Old: "", 401 New: "12", 402 }, 403 "top.24.bar": { 404 Old: "", 405 New: "12", 406 }, 407 }, 408 }, 409 Key: "top", 410 NewValue: NewSet(testSetFunc, []interface{}{ 411 map[string]interface{}{ 412 "foo": 1, 413 "bar": 4, 414 }, 415 map[string]interface{}{ 416 "foo": 13, 417 "bar": 12, 418 }, 419 map[string]interface{}{ 420 "foo": 21, 421 "bar": 22, 422 }, 423 }), 424 Expected: &terraform.InstanceDiff{ 425 Attributes: func() map[string]*terraform.ResourceAttrDiff { 426 result := make(map[string]*terraform.ResourceAttrDiff) 427 if computed { 428 result["top.#"] = &terraform.ResourceAttrDiff{ 429 Old: "2", 430 New: "", 431 NewComputed: true, 432 } 433 } else { 434 result["top.#"] = &terraform.ResourceAttrDiff{ 435 Old: "2", 436 New: "3", 437 } 438 result["top.5.foo"] = &terraform.ResourceAttrDiff{ 439 Old: "", 440 New: "1", 441 } 442 result["top.5.bar"] = &terraform.ResourceAttrDiff{ 443 Old: "", 444 New: "4", 445 } 446 result["top.25.foo"] = &terraform.ResourceAttrDiff{ 447 Old: "", 448 New: "13", 449 } 450 result["top.25.bar"] = &terraform.ResourceAttrDiff{ 451 Old: "", 452 New: "12", 453 } 454 result["top.43.foo"] = &terraform.ResourceAttrDiff{ 455 Old: "", 456 New: "21", 457 } 458 result["top.43.bar"] = &terraform.ResourceAttrDiff{ 459 Old: "", 460 New: "22", 461 } 462 } 463 return result 464 }(), 465 }, 466 }, 467 { 468 Name: "primitive, no diff, no refresh", 469 Schema: map[string]*Schema{ 470 "foo": { 471 Type: TypeString, 472 Computed: true, 473 }, 474 }, 475 State: &terraform.InstanceState{ 476 Attributes: map[string]string{ 477 "foo": "bar", 478 }, 479 }, 480 Config: testConfig(t, map[string]interface{}{}), 481 Diff: &terraform.InstanceDiff{Attributes: map[string]*terraform.ResourceAttrDiff{}}, 482 Key: "foo", 483 NewValue: "baz", 484 Expected: &terraform.InstanceDiff{ 485 Attributes: map[string]*terraform.ResourceAttrDiff{ 486 "foo": { 487 Old: "bar", 488 New: func() string { 489 if computed { 490 return "" 491 } 492 return "baz" 493 }(), 494 NewComputed: computed, 495 }, 496 }, 497 }, 498 }, 499 { 500 Name: "non-computed key, should error", 501 Schema: map[string]*Schema{ 502 "foo": { 503 Type: TypeString, 504 Required: true, 505 }, 506 }, 507 State: &terraform.InstanceState{ 508 Attributes: map[string]string{ 509 "foo": "bar", 510 }, 511 }, 512 Config: testConfig(t, map[string]interface{}{ 513 "foo": "baz", 514 }), 515 Diff: &terraform.InstanceDiff{ 516 Attributes: map[string]*terraform.ResourceAttrDiff{ 517 "foo": { 518 Old: "bar", 519 New: "baz", 520 }, 521 }, 522 }, 523 Key: "foo", 524 NewValue: "qux", 525 ExpectedError: true, 526 }, 527 { 528 Name: "bad key, should error", 529 Schema: map[string]*Schema{ 530 "foo": { 531 Type: TypeString, 532 Required: true, 533 }, 534 }, 535 State: &terraform.InstanceState{ 536 Attributes: map[string]string{ 537 "foo": "bar", 538 }, 539 }, 540 Config: testConfig(t, map[string]interface{}{ 541 "foo": "baz", 542 }), 543 Diff: &terraform.InstanceDiff{ 544 Attributes: map[string]*terraform.ResourceAttrDiff{ 545 "foo": { 546 Old: "bar", 547 New: "baz", 548 }, 549 }, 550 }, 551 Key: "bad", 552 NewValue: "qux", 553 ExpectedError: true, 554 }, 555 { 556 // NOTE: This case is technically impossible in the current 557 // implementation, because optional+computed values never show up in the 558 // diff, and we actually clear existing diffs when SetNew or 559 // SetNewComputed is run. This test is here to ensure that if either of 560 // these behaviors change that we don't introduce regressions. 561 Name: "NewRemoved in diff for Optional and Computed, should be fully overridden", 562 Schema: map[string]*Schema{ 563 "foo": { 564 Type: TypeString, 565 Optional: true, 566 Computed: true, 567 }, 568 }, 569 State: &terraform.InstanceState{ 570 Attributes: map[string]string{ 571 "foo": "bar", 572 }, 573 }, 574 Config: testConfig(t, map[string]interface{}{}), 575 Diff: &terraform.InstanceDiff{ 576 Attributes: map[string]*terraform.ResourceAttrDiff{ 577 "foo": { 578 Old: "bar", 579 New: "", 580 NewRemoved: true, 581 }, 582 }, 583 }, 584 Key: "foo", 585 NewValue: "qux", 586 Expected: &terraform.InstanceDiff{ 587 Attributes: map[string]*terraform.ResourceAttrDiff{ 588 "foo": { 589 Old: "bar", 590 New: func() string { 591 if computed { 592 return "" 593 } 594 return "qux" 595 }(), 596 NewComputed: computed, 597 }, 598 }, 599 }, 600 }, 601 { 602 Name: "NewComputed should always propagate", 603 Schema: map[string]*Schema{ 604 "foo": { 605 Type: TypeString, 606 Computed: true, 607 }, 608 }, 609 State: &terraform.InstanceState{ 610 Attributes: map[string]string{ 611 "foo": "", 612 }, 613 ID: "pre-existing", 614 }, 615 Config: testConfig(t, map[string]interface{}{}), 616 Diff: &terraform.InstanceDiff{Attributes: map[string]*terraform.ResourceAttrDiff{}}, 617 Key: "foo", 618 NewValue: "", 619 Expected: &terraform.InstanceDiff{ 620 Attributes: func() map[string]*terraform.ResourceAttrDiff { 621 if computed { 622 return map[string]*terraform.ResourceAttrDiff{ 623 "foo": { 624 NewComputed: computed, 625 }, 626 } 627 } 628 return map[string]*terraform.ResourceAttrDiff{} 629 }(), 630 }, 631 }, 632 } 633 } 634 635 func TestSetNew(t *testing.T) { 636 testCases := testDiffCases(t, "", 0, false) 637 for _, tc := range testCases { 638 t.Run(tc.Name, func(t *testing.T) { 639 m := schemaMap(tc.Schema) 640 d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff) 641 err := d.SetNew(tc.Key, tc.NewValue) 642 switch { 643 case err != nil && !tc.ExpectedError: 644 t.Fatalf("bad: %s", err) 645 case err == nil && tc.ExpectedError: 646 t.Fatalf("Expected error, got none") 647 case err != nil && tc.ExpectedError: 648 return 649 } 650 for _, k := range d.UpdatedKeys() { 651 if err := m.diff(k, m[k], tc.Diff, d, false); err != nil { 652 t.Fatalf("bad: %s", err) 653 } 654 } 655 if !reflect.DeepEqual(tc.Expected, tc.Diff) { 656 t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff)) 657 } 658 }) 659 } 660 } 661 662 func TestSetNewComputed(t *testing.T) { 663 testCases := testDiffCases(t, "", 0, true) 664 for _, tc := range testCases { 665 t.Run(tc.Name, func(t *testing.T) { 666 m := schemaMap(tc.Schema) 667 d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff) 668 err := d.SetNewComputed(tc.Key) 669 switch { 670 case err != nil && !tc.ExpectedError: 671 t.Fatalf("bad: %s", err) 672 case err == nil && tc.ExpectedError: 673 t.Fatalf("Expected error, got none") 674 case err != nil && tc.ExpectedError: 675 return 676 } 677 for _, k := range d.UpdatedKeys() { 678 if err := m.diff(k, m[k], tc.Diff, d, false); err != nil { 679 t.Fatalf("bad: %s", err) 680 } 681 } 682 if !reflect.DeepEqual(tc.Expected, tc.Diff) { 683 t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff)) 684 } 685 }) 686 } 687 } 688 689 func TestForceNew(t *testing.T) { 690 cases := []resourceDiffTestCase{ 691 { 692 Name: "basic primitive diff", 693 Schema: map[string]*Schema{ 694 "foo": { 695 Type: TypeString, 696 Optional: true, 697 Computed: true, 698 }, 699 }, 700 State: &terraform.InstanceState{ 701 Attributes: map[string]string{ 702 "foo": "bar", 703 }, 704 }, 705 Config: testConfig(t, map[string]interface{}{ 706 "foo": "baz", 707 }), 708 Diff: &terraform.InstanceDiff{ 709 Attributes: map[string]*terraform.ResourceAttrDiff{ 710 "foo": { 711 Old: "bar", 712 New: "baz", 713 }, 714 }, 715 }, 716 Key: "foo", 717 Expected: &terraform.InstanceDiff{ 718 Attributes: map[string]*terraform.ResourceAttrDiff{ 719 "foo": { 720 Old: "bar", 721 New: "baz", 722 RequiresNew: true, 723 }, 724 }, 725 }, 726 }, 727 { 728 Name: "no change, should error", 729 Schema: map[string]*Schema{ 730 "foo": { 731 Type: TypeString, 732 Optional: true, 733 Computed: true, 734 }, 735 }, 736 State: &terraform.InstanceState{ 737 Attributes: map[string]string{ 738 "foo": "bar", 739 }, 740 }, 741 Config: testConfig(t, map[string]interface{}{ 742 "foo": "bar", 743 }), 744 ExpectedError: true, 745 }, 746 { 747 Name: "basic primitive, non-computed key", 748 Schema: map[string]*Schema{ 749 "foo": { 750 Type: TypeString, 751 Required: true, 752 }, 753 }, 754 State: &terraform.InstanceState{ 755 Attributes: map[string]string{ 756 "foo": "bar", 757 }, 758 }, 759 Config: testConfig(t, map[string]interface{}{ 760 "foo": "baz", 761 }), 762 Diff: &terraform.InstanceDiff{ 763 Attributes: map[string]*terraform.ResourceAttrDiff{ 764 "foo": { 765 Old: "bar", 766 New: "baz", 767 }, 768 }, 769 }, 770 Key: "foo", 771 Expected: &terraform.InstanceDiff{ 772 Attributes: map[string]*terraform.ResourceAttrDiff{ 773 "foo": { 774 Old: "bar", 775 New: "baz", 776 RequiresNew: true, 777 }, 778 }, 779 }, 780 }, 781 { 782 Name: "nested field", 783 Schema: map[string]*Schema{ 784 "foo": { 785 Type: TypeList, 786 Required: true, 787 MaxItems: 1, 788 Elem: &Resource{ 789 Schema: map[string]*Schema{ 790 "bar": { 791 Type: TypeString, 792 Optional: true, 793 }, 794 "baz": { 795 Type: TypeString, 796 Optional: true, 797 }, 798 }, 799 }, 800 }, 801 }, 802 State: &terraform.InstanceState{ 803 Attributes: map[string]string{ 804 "foo.#": "1", 805 "foo.0.bar": "abc", 806 "foo.0.baz": "xyz", 807 }, 808 }, 809 Config: testConfig(t, map[string]interface{}{ 810 "foo": []interface{}{ 811 map[string]interface{}{ 812 "bar": "abcdefg", 813 "baz": "changed", 814 }, 815 }, 816 }), 817 Diff: &terraform.InstanceDiff{ 818 Attributes: map[string]*terraform.ResourceAttrDiff{ 819 "foo.0.bar": { 820 Old: "abc", 821 New: "abcdefg", 822 }, 823 "foo.0.baz": { 824 Old: "xyz", 825 New: "changed", 826 }, 827 }, 828 }, 829 Key: "foo.0.baz", 830 Expected: &terraform.InstanceDiff{ 831 Attributes: map[string]*terraform.ResourceAttrDiff{ 832 "foo.0.bar": { 833 Old: "abc", 834 New: "abcdefg", 835 }, 836 "foo.0.baz": { 837 Old: "xyz", 838 New: "changed", 839 RequiresNew: true, 840 }, 841 }, 842 }, 843 }, 844 { 845 Name: "preserve NewRemoved on existing diff", 846 Schema: map[string]*Schema{ 847 "foo": { 848 Type: TypeString, 849 Optional: true, 850 }, 851 }, 852 State: &terraform.InstanceState{ 853 Attributes: map[string]string{ 854 "foo": "bar", 855 }, 856 }, 857 Config: testConfig(t, map[string]interface{}{}), 858 Diff: &terraform.InstanceDiff{ 859 Attributes: map[string]*terraform.ResourceAttrDiff{ 860 "foo": { 861 Old: "bar", 862 New: "", 863 NewRemoved: true, 864 }, 865 }, 866 }, 867 Key: "foo", 868 Expected: &terraform.InstanceDiff{ 869 Attributes: map[string]*terraform.ResourceAttrDiff{ 870 "foo": { 871 Old: "bar", 872 New: "", 873 RequiresNew: true, 874 NewRemoved: true, 875 }, 876 }, 877 }, 878 }, 879 { 880 Name: "nested field, preserve original diff without zero values", 881 Schema: map[string]*Schema{ 882 "foo": { 883 Type: TypeList, 884 Required: true, 885 MaxItems: 1, 886 Elem: &Resource{ 887 Schema: map[string]*Schema{ 888 "bar": { 889 Type: TypeString, 890 Optional: true, 891 }, 892 "baz": { 893 Type: TypeInt, 894 Optional: true, 895 }, 896 }, 897 }, 898 }, 899 }, 900 State: &terraform.InstanceState{ 901 Attributes: map[string]string{ 902 "foo.#": "1", 903 "foo.0.bar": "abc", 904 }, 905 }, 906 Config: testConfig(t, map[string]interface{}{ 907 "foo": []interface{}{ 908 map[string]interface{}{ 909 "bar": "abcdefg", 910 }, 911 }, 912 }), 913 Diff: &terraform.InstanceDiff{ 914 Attributes: map[string]*terraform.ResourceAttrDiff{ 915 "foo.0.bar": { 916 Old: "abc", 917 New: "abcdefg", 918 }, 919 }, 920 }, 921 Key: "foo.0.bar", 922 Expected: &terraform.InstanceDiff{ 923 Attributes: map[string]*terraform.ResourceAttrDiff{ 924 "foo.0.bar": { 925 Old: "abc", 926 New: "abcdefg", 927 RequiresNew: true, 928 }, 929 }, 930 }, 931 }, 932 } 933 for _, tc := range cases { 934 t.Run(tc.Name, func(t *testing.T) { 935 m := schemaMap(tc.Schema) 936 d := newResourceDiff(m, tc.Config, tc.State, tc.Diff) 937 err := d.ForceNew(tc.Key) 938 switch { 939 case err != nil && !tc.ExpectedError: 940 t.Fatalf("bad: %s", err) 941 case err == nil && tc.ExpectedError: 942 t.Fatalf("Expected error, got none") 943 case err != nil && tc.ExpectedError: 944 return 945 } 946 for _, k := range d.UpdatedKeys() { 947 if err := m.diff(k, m[k], tc.Diff, d, false); err != nil { 948 t.Fatalf("bad: %s", err) 949 } 950 } 951 if !reflect.DeepEqual(tc.Expected, tc.Diff) { 952 t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff)) 953 } 954 }) 955 } 956 } 957 958 func TestClear(t *testing.T) { 959 cases := []resourceDiffTestCase{ 960 { 961 Name: "basic primitive diff", 962 Schema: map[string]*Schema{ 963 "foo": { 964 Type: TypeString, 965 Optional: true, 966 Computed: true, 967 }, 968 }, 969 State: &terraform.InstanceState{ 970 Attributes: map[string]string{ 971 "foo": "bar", 972 }, 973 }, 974 Config: testConfig(t, map[string]interface{}{ 975 "foo": "baz", 976 }), 977 Diff: &terraform.InstanceDiff{ 978 Attributes: map[string]*terraform.ResourceAttrDiff{ 979 "foo": { 980 Old: "bar", 981 New: "baz", 982 }, 983 }, 984 }, 985 Key: "foo", 986 Expected: &terraform.InstanceDiff{Attributes: map[string]*terraform.ResourceAttrDiff{}}, 987 }, 988 { 989 Name: "non-computed key, should error", 990 Schema: map[string]*Schema{ 991 "foo": { 992 Type: TypeString, 993 Required: true, 994 }, 995 }, 996 State: &terraform.InstanceState{ 997 Attributes: map[string]string{ 998 "foo": "bar", 999 }, 1000 }, 1001 Config: testConfig(t, map[string]interface{}{ 1002 "foo": "baz", 1003 }), 1004 Diff: &terraform.InstanceDiff{ 1005 Attributes: map[string]*terraform.ResourceAttrDiff{ 1006 "foo": { 1007 Old: "bar", 1008 New: "baz", 1009 }, 1010 }, 1011 }, 1012 Key: "foo", 1013 ExpectedError: true, 1014 }, 1015 { 1016 Name: "multi-value, one removed", 1017 Schema: map[string]*Schema{ 1018 "foo": { 1019 Type: TypeString, 1020 Optional: true, 1021 Computed: true, 1022 }, 1023 "one": { 1024 Type: TypeString, 1025 Optional: true, 1026 Computed: true, 1027 }, 1028 }, 1029 State: &terraform.InstanceState{ 1030 Attributes: map[string]string{ 1031 "foo": "bar", 1032 "one": "two", 1033 }, 1034 }, 1035 Config: testConfig(t, map[string]interface{}{ 1036 "foo": "baz", 1037 "one": "three", 1038 }), 1039 Diff: &terraform.InstanceDiff{ 1040 Attributes: map[string]*terraform.ResourceAttrDiff{ 1041 "foo": { 1042 Old: "bar", 1043 New: "baz", 1044 }, 1045 "one": { 1046 Old: "two", 1047 New: "three", 1048 }, 1049 }, 1050 }, 1051 Key: "one", 1052 Expected: &terraform.InstanceDiff{ 1053 Attributes: map[string]*terraform.ResourceAttrDiff{ 1054 "foo": { 1055 Old: "bar", 1056 New: "baz", 1057 }, 1058 }, 1059 }, 1060 }, 1061 { 1062 Name: "basic sub-block diff", 1063 Schema: map[string]*Schema{ 1064 "foo": { 1065 Type: TypeList, 1066 Optional: true, 1067 Computed: true, 1068 Elem: &Resource{ 1069 Schema: map[string]*Schema{ 1070 "bar": { 1071 Type: TypeString, 1072 Optional: true, 1073 Computed: true, 1074 }, 1075 "baz": { 1076 Type: TypeString, 1077 Optional: true, 1078 Computed: true, 1079 }, 1080 }, 1081 }, 1082 }, 1083 }, 1084 State: &terraform.InstanceState{ 1085 Attributes: map[string]string{ 1086 "foo.0.bar": "bar1", 1087 "foo.0.baz": "baz1", 1088 }, 1089 }, 1090 Config: testConfig(t, map[string]interface{}{ 1091 "foo": []interface{}{ 1092 map[string]interface{}{ 1093 "bar": "bar2", 1094 "baz": "baz1", 1095 }, 1096 }, 1097 }), 1098 Diff: &terraform.InstanceDiff{ 1099 Attributes: map[string]*terraform.ResourceAttrDiff{ 1100 "foo.0.bar": { 1101 Old: "bar1", 1102 New: "bar2", 1103 }, 1104 }, 1105 }, 1106 Key: "foo.0.bar", 1107 Expected: &terraform.InstanceDiff{Attributes: map[string]*terraform.ResourceAttrDiff{}}, 1108 }, 1109 { 1110 Name: "sub-block diff only partial clear", 1111 Schema: map[string]*Schema{ 1112 "foo": { 1113 Type: TypeList, 1114 Optional: true, 1115 Computed: true, 1116 Elem: &Resource{ 1117 Schema: map[string]*Schema{ 1118 "bar": { 1119 Type: TypeString, 1120 Optional: true, 1121 Computed: true, 1122 }, 1123 "baz": { 1124 Type: TypeString, 1125 Optional: true, 1126 Computed: true, 1127 }, 1128 }, 1129 }, 1130 }, 1131 }, 1132 State: &terraform.InstanceState{ 1133 Attributes: map[string]string{ 1134 "foo.0.bar": "bar1", 1135 "foo.0.baz": "baz1", 1136 }, 1137 }, 1138 Config: testConfig(t, map[string]interface{}{ 1139 "foo": []interface{}{ 1140 map[string]interface{}{ 1141 "bar": "bar2", 1142 "baz": "baz2", 1143 }, 1144 }, 1145 }), 1146 Diff: &terraform.InstanceDiff{ 1147 Attributes: map[string]*terraform.ResourceAttrDiff{ 1148 "foo.0.bar": { 1149 Old: "bar1", 1150 New: "bar2", 1151 }, 1152 "foo.0.baz": { 1153 Old: "baz1", 1154 New: "baz2", 1155 }, 1156 }, 1157 }, 1158 Key: "foo.0.bar", 1159 Expected: &terraform.InstanceDiff{Attributes: map[string]*terraform.ResourceAttrDiff{ 1160 "foo.0.baz": { 1161 Old: "baz1", 1162 New: "baz2", 1163 }, 1164 }}, 1165 }, 1166 } 1167 for _, tc := range cases { 1168 t.Run(tc.Name, func(t *testing.T) { 1169 m := schemaMap(tc.Schema) 1170 d := newResourceDiff(m, tc.Config, tc.State, tc.Diff) 1171 err := d.Clear(tc.Key) 1172 switch { 1173 case err != nil && !tc.ExpectedError: 1174 t.Fatalf("bad: %s", err) 1175 case err == nil && tc.ExpectedError: 1176 t.Fatalf("Expected error, got none") 1177 case err != nil && tc.ExpectedError: 1178 return 1179 } 1180 for _, k := range d.UpdatedKeys() { 1181 if err := m.diff(k, m[k], tc.Diff, d, false); err != nil { 1182 t.Fatalf("bad: %s", err) 1183 } 1184 } 1185 if !reflect.DeepEqual(tc.Expected, tc.Diff) { 1186 t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff)) 1187 } 1188 }) 1189 } 1190 } 1191 1192 func TestGetChangedKeysPrefix(t *testing.T) { 1193 cases := []resourceDiffTestCase{ 1194 { 1195 Name: "basic primitive diff", 1196 Schema: map[string]*Schema{ 1197 "foo": { 1198 Type: TypeString, 1199 Optional: true, 1200 Computed: true, 1201 }, 1202 }, 1203 State: &terraform.InstanceState{ 1204 Attributes: map[string]string{ 1205 "foo": "bar", 1206 }, 1207 }, 1208 Config: testConfig(t, map[string]interface{}{ 1209 "foo": "baz", 1210 }), 1211 Diff: &terraform.InstanceDiff{ 1212 Attributes: map[string]*terraform.ResourceAttrDiff{ 1213 "foo": { 1214 Old: "bar", 1215 New: "baz", 1216 }, 1217 }, 1218 }, 1219 Key: "foo", 1220 ExpectedKeys: []string{ 1221 "foo", 1222 }, 1223 }, 1224 { 1225 Name: "nested field filtering", 1226 Schema: map[string]*Schema{ 1227 "testfield": { 1228 Type: TypeString, 1229 Required: true, 1230 }, 1231 "foo": { 1232 Type: TypeList, 1233 Required: true, 1234 MaxItems: 1, 1235 Elem: &Resource{ 1236 Schema: map[string]*Schema{ 1237 "bar": { 1238 Type: TypeString, 1239 Optional: true, 1240 }, 1241 "baz": { 1242 Type: TypeString, 1243 Optional: true, 1244 }, 1245 }, 1246 }, 1247 }, 1248 }, 1249 State: &terraform.InstanceState{ 1250 Attributes: map[string]string{ 1251 "testfield": "blablah", 1252 "foo.#": "1", 1253 "foo.0.bar": "abc", 1254 "foo.0.baz": "xyz", 1255 }, 1256 }, 1257 Config: testConfig(t, map[string]interface{}{ 1258 "testfield": "modified", 1259 "foo": []interface{}{ 1260 map[string]interface{}{ 1261 "bar": "abcdefg", 1262 "baz": "changed", 1263 }, 1264 }, 1265 }), 1266 Diff: &terraform.InstanceDiff{ 1267 Attributes: map[string]*terraform.ResourceAttrDiff{ 1268 "testfield": { 1269 Old: "blablah", 1270 New: "modified", 1271 }, 1272 "foo.0.bar": { 1273 Old: "abc", 1274 New: "abcdefg", 1275 }, 1276 "foo.0.baz": { 1277 Old: "xyz", 1278 New: "changed", 1279 }, 1280 }, 1281 }, 1282 Key: "foo", 1283 ExpectedKeys: []string{ 1284 "foo.0.bar", 1285 "foo.0.baz", 1286 }, 1287 }, 1288 } 1289 for _, tc := range cases { 1290 t.Run(tc.Name, func(t *testing.T) { 1291 m := schemaMap(tc.Schema) 1292 d := newResourceDiff(m, tc.Config, tc.State, tc.Diff) 1293 keys := d.GetChangedKeysPrefix(tc.Key) 1294 1295 for _, k := range d.UpdatedKeys() { 1296 if err := m.diff(k, m[k], tc.Diff, d, false); err != nil { 1297 t.Fatalf("bad: %s", err) 1298 } 1299 } 1300 1301 sort.Strings(keys) 1302 1303 if !reflect.DeepEqual(tc.ExpectedKeys, keys) { 1304 t.Fatalf("Expected %s, got %s", spew.Sdump(tc.ExpectedKeys), spew.Sdump(keys)) 1305 } 1306 }) 1307 } 1308 } 1309 1310 func TestResourceDiffGetOkExists(t *testing.T) { 1311 cases := []struct { 1312 Name string 1313 Schema map[string]*Schema 1314 State *terraform.InstanceState 1315 Config *terraform.ResourceConfig 1316 Diff *terraform.InstanceDiff 1317 Key string 1318 Value interface{} 1319 Ok bool 1320 }{ 1321 /* 1322 * Primitives 1323 */ 1324 { 1325 Name: "string-literal-empty", 1326 Schema: map[string]*Schema{ 1327 "availability_zone": { 1328 Type: TypeString, 1329 Optional: true, 1330 Computed: true, 1331 ForceNew: true, 1332 }, 1333 }, 1334 1335 State: nil, 1336 Config: nil, 1337 1338 Diff: &terraform.InstanceDiff{ 1339 Attributes: map[string]*terraform.ResourceAttrDiff{ 1340 "availability_zone": { 1341 Old: "", 1342 New: "", 1343 }, 1344 }, 1345 }, 1346 1347 Key: "availability_zone", 1348 Value: "", 1349 Ok: true, 1350 }, 1351 1352 { 1353 Name: "string-computed-empty", 1354 Schema: map[string]*Schema{ 1355 "availability_zone": { 1356 Type: TypeString, 1357 Optional: true, 1358 Computed: true, 1359 ForceNew: true, 1360 }, 1361 }, 1362 1363 State: nil, 1364 Config: nil, 1365 1366 Diff: &terraform.InstanceDiff{ 1367 Attributes: map[string]*terraform.ResourceAttrDiff{ 1368 "availability_zone": { 1369 Old: "", 1370 New: "", 1371 NewComputed: true, 1372 }, 1373 }, 1374 }, 1375 1376 Key: "availability_zone", 1377 Value: "", 1378 Ok: false, 1379 }, 1380 1381 { 1382 Name: "string-optional-computed-nil-diff", 1383 Schema: map[string]*Schema{ 1384 "availability_zone": { 1385 Type: TypeString, 1386 Optional: true, 1387 Computed: true, 1388 ForceNew: true, 1389 }, 1390 }, 1391 1392 State: nil, 1393 Config: nil, 1394 1395 Diff: nil, 1396 1397 Key: "availability_zone", 1398 Value: "", 1399 Ok: false, 1400 }, 1401 1402 /* 1403 * Lists 1404 */ 1405 1406 { 1407 Name: "list-optional", 1408 Schema: map[string]*Schema{ 1409 "ports": { 1410 Type: TypeList, 1411 Optional: true, 1412 Elem: &Schema{Type: TypeInt}, 1413 }, 1414 }, 1415 1416 State: nil, 1417 Config: nil, 1418 1419 Diff: nil, 1420 1421 Key: "ports", 1422 Value: []interface{}{}, 1423 Ok: false, 1424 }, 1425 1426 /* 1427 * Map 1428 */ 1429 1430 { 1431 Name: "map-optional", 1432 Schema: map[string]*Schema{ 1433 "ports": { 1434 Type: TypeMap, 1435 Optional: true, 1436 }, 1437 }, 1438 1439 State: nil, 1440 Config: nil, 1441 1442 Diff: nil, 1443 1444 Key: "ports", 1445 Value: map[string]interface{}{}, 1446 Ok: false, 1447 }, 1448 1449 /* 1450 * Set 1451 */ 1452 1453 { 1454 Name: "set-optional", 1455 Schema: map[string]*Schema{ 1456 "ports": { 1457 Type: TypeSet, 1458 Optional: true, 1459 Elem: &Schema{Type: TypeInt}, 1460 Set: func(a interface{}) int { return a.(int) }, 1461 }, 1462 }, 1463 1464 State: nil, 1465 Config: nil, 1466 1467 Diff: nil, 1468 1469 Key: "ports", 1470 Value: []interface{}{}, 1471 Ok: false, 1472 }, 1473 1474 { 1475 Name: "set-optional-key", 1476 Schema: map[string]*Schema{ 1477 "ports": { 1478 Type: TypeSet, 1479 Optional: true, 1480 Elem: &Schema{Type: TypeInt}, 1481 Set: func(a interface{}) int { return a.(int) }, 1482 }, 1483 }, 1484 1485 State: nil, 1486 Config: nil, 1487 1488 Diff: nil, 1489 1490 Key: "ports.0", 1491 Value: 0, 1492 Ok: false, 1493 }, 1494 1495 { 1496 Name: "bool-literal-empty", 1497 Schema: map[string]*Schema{ 1498 "availability_zone": { 1499 Type: TypeBool, 1500 Optional: true, 1501 Computed: true, 1502 ForceNew: true, 1503 }, 1504 }, 1505 1506 State: nil, 1507 Config: nil, 1508 Diff: &terraform.InstanceDiff{ 1509 Attributes: map[string]*terraform.ResourceAttrDiff{ 1510 "availability_zone": { 1511 Old: "", 1512 New: "", 1513 }, 1514 }, 1515 }, 1516 1517 Key: "availability_zone", 1518 Value: false, 1519 Ok: true, 1520 }, 1521 1522 { 1523 Name: "bool-literal-set", 1524 Schema: map[string]*Schema{ 1525 "availability_zone": { 1526 Type: TypeBool, 1527 Optional: true, 1528 Computed: true, 1529 ForceNew: true, 1530 }, 1531 }, 1532 1533 State: nil, 1534 Config: nil, 1535 1536 Diff: &terraform.InstanceDiff{ 1537 Attributes: map[string]*terraform.ResourceAttrDiff{ 1538 "availability_zone": { 1539 New: "true", 1540 }, 1541 }, 1542 }, 1543 1544 Key: "availability_zone", 1545 Value: true, 1546 Ok: true, 1547 }, 1548 { 1549 Name: "value-in-config", 1550 Schema: map[string]*Schema{ 1551 "availability_zone": { 1552 Type: TypeString, 1553 Optional: true, 1554 }, 1555 }, 1556 1557 State: &terraform.InstanceState{ 1558 Attributes: map[string]string{ 1559 "availability_zone": "foo", 1560 }, 1561 }, 1562 Config: testConfig(t, map[string]interface{}{ 1563 "availability_zone": "foo", 1564 }), 1565 1566 Diff: &terraform.InstanceDiff{ 1567 Attributes: map[string]*terraform.ResourceAttrDiff{}, 1568 }, 1569 1570 Key: "availability_zone", 1571 Value: "foo", 1572 Ok: true, 1573 }, 1574 { 1575 Name: "new-value-in-config", 1576 Schema: map[string]*Schema{ 1577 "availability_zone": { 1578 Type: TypeString, 1579 Optional: true, 1580 }, 1581 }, 1582 1583 State: nil, 1584 Config: testConfig(t, map[string]interface{}{ 1585 "availability_zone": "foo", 1586 }), 1587 1588 Diff: &terraform.InstanceDiff{ 1589 Attributes: map[string]*terraform.ResourceAttrDiff{ 1590 "availability_zone": { 1591 Old: "", 1592 New: "foo", 1593 }, 1594 }, 1595 }, 1596 1597 Key: "availability_zone", 1598 Value: "foo", 1599 Ok: true, 1600 }, 1601 { 1602 Name: "optional-computed-value-in-config", 1603 Schema: map[string]*Schema{ 1604 "availability_zone": { 1605 Type: TypeString, 1606 Optional: true, 1607 Computed: true, 1608 }, 1609 }, 1610 1611 State: &terraform.InstanceState{ 1612 Attributes: map[string]string{ 1613 "availability_zone": "foo", 1614 }, 1615 }, 1616 Config: testConfig(t, map[string]interface{}{ 1617 "availability_zone": "bar", 1618 }), 1619 1620 Diff: &terraform.InstanceDiff{ 1621 Attributes: map[string]*terraform.ResourceAttrDiff{ 1622 "availability_zone": { 1623 Old: "foo", 1624 New: "bar", 1625 }, 1626 }, 1627 }, 1628 1629 Key: "availability_zone", 1630 Value: "bar", 1631 Ok: true, 1632 }, 1633 { 1634 Name: "removed-value", 1635 Schema: map[string]*Schema{ 1636 "availability_zone": { 1637 Type: TypeString, 1638 Optional: true, 1639 }, 1640 }, 1641 1642 State: &terraform.InstanceState{ 1643 Attributes: map[string]string{ 1644 "availability_zone": "foo", 1645 }, 1646 }, 1647 Config: testConfig(t, map[string]interface{}{}), 1648 1649 Diff: &terraform.InstanceDiff{ 1650 Attributes: map[string]*terraform.ResourceAttrDiff{ 1651 "availability_zone": { 1652 Old: "foo", 1653 New: "", 1654 NewRemoved: true, 1655 }, 1656 }, 1657 }, 1658 1659 Key: "availability_zone", 1660 Value: "", 1661 Ok: true, 1662 }, 1663 } 1664 1665 for i, tc := range cases { 1666 t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { 1667 d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff) 1668 1669 v, ok := d.GetOkExists(tc.Key) 1670 if s, ok := v.(*Set); ok { 1671 v = s.List() 1672 } 1673 1674 if !reflect.DeepEqual(v, tc.Value) { 1675 t.Fatalf("Bad %s: \n%#v", tc.Name, v) 1676 } 1677 if ok != tc.Ok { 1678 t.Fatalf("%s: expected ok: %t, got: %t", tc.Name, tc.Ok, ok) 1679 } 1680 }) 1681 } 1682 } 1683 1684 func TestResourceDiffGetOkExistsSetNew(t *testing.T) { 1685 tc := struct { 1686 Schema map[string]*Schema 1687 State *terraform.InstanceState 1688 Diff *terraform.InstanceDiff 1689 Key string 1690 Value interface{} 1691 Ok bool 1692 }{ 1693 Schema: map[string]*Schema{ 1694 "availability_zone": { 1695 Type: TypeString, 1696 Optional: true, 1697 Computed: true, 1698 }, 1699 }, 1700 1701 State: nil, 1702 1703 Diff: &terraform.InstanceDiff{ 1704 Attributes: map[string]*terraform.ResourceAttrDiff{}, 1705 }, 1706 1707 Key: "availability_zone", 1708 Value: "foobar", 1709 Ok: true, 1710 } 1711 1712 d := newResourceDiff(tc.Schema, testConfig(t, map[string]interface{}{}), tc.State, tc.Diff) 1713 d.SetNew(tc.Key, tc.Value) 1714 1715 v, ok := d.GetOkExists(tc.Key) 1716 if s, ok := v.(*Set); ok { 1717 v = s.List() 1718 } 1719 1720 if !reflect.DeepEqual(v, tc.Value) { 1721 t.Fatalf("Bad: \n%#v", v) 1722 } 1723 if ok != tc.Ok { 1724 t.Fatalf("expected ok: %t, got: %t", tc.Ok, ok) 1725 } 1726 } 1727 1728 func TestResourceDiffGetOkExistsSetNewComputed(t *testing.T) { 1729 tc := struct { 1730 Schema map[string]*Schema 1731 State *terraform.InstanceState 1732 Diff *terraform.InstanceDiff 1733 Key string 1734 Value interface{} 1735 Ok bool 1736 }{ 1737 Schema: map[string]*Schema{ 1738 "availability_zone": { 1739 Type: TypeString, 1740 Optional: true, 1741 Computed: true, 1742 }, 1743 }, 1744 1745 State: &terraform.InstanceState{ 1746 Attributes: map[string]string{ 1747 "availability_zone": "foo", 1748 }, 1749 }, 1750 1751 Diff: &terraform.InstanceDiff{ 1752 Attributes: map[string]*terraform.ResourceAttrDiff{}, 1753 }, 1754 1755 Key: "availability_zone", 1756 Value: "foobar", 1757 Ok: false, 1758 } 1759 1760 d := newResourceDiff(tc.Schema, testConfig(t, map[string]interface{}{}), tc.State, tc.Diff) 1761 d.SetNewComputed(tc.Key) 1762 1763 _, ok := d.GetOkExists(tc.Key) 1764 1765 if ok != tc.Ok { 1766 t.Fatalf("expected ok: %t, got: %t", tc.Ok, ok) 1767 } 1768 } 1769 1770 func TestResourceDiffNewValueKnown(t *testing.T) { 1771 cases := []struct { 1772 Name string 1773 Schema map[string]*Schema 1774 State *terraform.InstanceState 1775 Config *terraform.ResourceConfig 1776 Diff *terraform.InstanceDiff 1777 Key string 1778 Expected bool 1779 }{ 1780 { 1781 Name: "in config, no state", 1782 Schema: map[string]*Schema{ 1783 "availability_zone": { 1784 Type: TypeString, 1785 Optional: true, 1786 }, 1787 }, 1788 State: nil, 1789 Config: testConfig(t, map[string]interface{}{ 1790 "availability_zone": "foo", 1791 }), 1792 Diff: &terraform.InstanceDiff{ 1793 Attributes: map[string]*terraform.ResourceAttrDiff{ 1794 "availability_zone": { 1795 Old: "", 1796 New: "foo", 1797 }, 1798 }, 1799 }, 1800 Key: "availability_zone", 1801 Expected: true, 1802 }, 1803 { 1804 Name: "in config, has state, no diff", 1805 Schema: map[string]*Schema{ 1806 "availability_zone": { 1807 Type: TypeString, 1808 Optional: true, 1809 }, 1810 }, 1811 State: &terraform.InstanceState{ 1812 Attributes: map[string]string{ 1813 "availability_zone": "foo", 1814 }, 1815 }, 1816 Config: testConfig(t, map[string]interface{}{ 1817 "availability_zone": "foo", 1818 }), 1819 Diff: &terraform.InstanceDiff{ 1820 Attributes: map[string]*terraform.ResourceAttrDiff{}, 1821 }, 1822 Key: "availability_zone", 1823 Expected: true, 1824 }, 1825 { 1826 Name: "computed attribute, in state, no diff", 1827 Schema: map[string]*Schema{ 1828 "availability_zone": { 1829 Type: TypeString, 1830 Computed: true, 1831 }, 1832 }, 1833 State: &terraform.InstanceState{ 1834 Attributes: map[string]string{ 1835 "availability_zone": "foo", 1836 }, 1837 }, 1838 Config: testConfig(t, map[string]interface{}{}), 1839 Diff: &terraform.InstanceDiff{ 1840 Attributes: map[string]*terraform.ResourceAttrDiff{}, 1841 }, 1842 Key: "availability_zone", 1843 Expected: true, 1844 }, 1845 { 1846 Name: "optional and computed attribute, in state, no config", 1847 Schema: map[string]*Schema{ 1848 "availability_zone": { 1849 Type: TypeString, 1850 Optional: true, 1851 Computed: true, 1852 }, 1853 }, 1854 State: &terraform.InstanceState{ 1855 Attributes: map[string]string{ 1856 "availability_zone": "foo", 1857 }, 1858 }, 1859 Config: testConfig(t, map[string]interface{}{}), 1860 Diff: &terraform.InstanceDiff{ 1861 Attributes: map[string]*terraform.ResourceAttrDiff{}, 1862 }, 1863 Key: "availability_zone", 1864 Expected: true, 1865 }, 1866 { 1867 Name: "optional and computed attribute, in state, with config", 1868 Schema: map[string]*Schema{ 1869 "availability_zone": { 1870 Type: TypeString, 1871 Optional: true, 1872 Computed: true, 1873 }, 1874 }, 1875 State: &terraform.InstanceState{ 1876 Attributes: map[string]string{ 1877 "availability_zone": "foo", 1878 }, 1879 }, 1880 Config: testConfig(t, map[string]interface{}{ 1881 "availability_zone": "foo", 1882 }), 1883 Diff: &terraform.InstanceDiff{ 1884 Attributes: map[string]*terraform.ResourceAttrDiff{}, 1885 }, 1886 Key: "availability_zone", 1887 Expected: true, 1888 }, 1889 { 1890 Name: "computed value, through config reader", 1891 Schema: map[string]*Schema{ 1892 "availability_zone": { 1893 Type: TypeString, 1894 Optional: true, 1895 }, 1896 }, 1897 State: &terraform.InstanceState{ 1898 Attributes: map[string]string{ 1899 "availability_zone": "foo", 1900 }, 1901 }, 1902 Config: testConfig( 1903 t, 1904 map[string]interface{}{ 1905 "availability_zone": hcl2shim.UnknownVariableValue, 1906 }, 1907 ), 1908 Diff: &terraform.InstanceDiff{ 1909 Attributes: map[string]*terraform.ResourceAttrDiff{}, 1910 }, 1911 Key: "availability_zone", 1912 Expected: false, 1913 }, 1914 { 1915 Name: "computed value, through diff reader", 1916 Schema: map[string]*Schema{ 1917 "availability_zone": { 1918 Type: TypeString, 1919 Optional: true, 1920 }, 1921 }, 1922 State: &terraform.InstanceState{ 1923 Attributes: map[string]string{ 1924 "availability_zone": "foo", 1925 }, 1926 }, 1927 Config: testConfig( 1928 t, 1929 map[string]interface{}{ 1930 "availability_zone": hcl2shim.UnknownVariableValue, 1931 }, 1932 ), 1933 Diff: &terraform.InstanceDiff{ 1934 Attributes: map[string]*terraform.ResourceAttrDiff{ 1935 "availability_zone": { 1936 Old: "foo", 1937 New: "", 1938 NewComputed: true, 1939 }, 1940 }, 1941 }, 1942 Key: "availability_zone", 1943 Expected: false, 1944 }, 1945 } 1946 1947 for i, tc := range cases { 1948 t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { 1949 d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff) 1950 1951 actual := d.NewValueKnown(tc.Key) 1952 if tc.Expected != actual { 1953 t.Fatalf("%s: expected ok: %t, got: %t", tc.Name, tc.Expected, actual) 1954 } 1955 }) 1956 } 1957 } 1958 1959 func TestResourceDiffNewValueKnownSetNew(t *testing.T) { 1960 tc := struct { 1961 Schema map[string]*Schema 1962 State *terraform.InstanceState 1963 Config *terraform.ResourceConfig 1964 Diff *terraform.InstanceDiff 1965 Key string 1966 Value interface{} 1967 Expected bool 1968 }{ 1969 Schema: map[string]*Schema{ 1970 "availability_zone": { 1971 Type: TypeString, 1972 Optional: true, 1973 Computed: true, 1974 }, 1975 }, 1976 State: &terraform.InstanceState{ 1977 Attributes: map[string]string{ 1978 "availability_zone": "foo", 1979 }, 1980 }, 1981 Config: testConfig( 1982 t, 1983 map[string]interface{}{ 1984 "availability_zone": hcl2shim.UnknownVariableValue, 1985 }, 1986 ), 1987 Diff: &terraform.InstanceDiff{ 1988 Attributes: map[string]*terraform.ResourceAttrDiff{ 1989 "availability_zone": { 1990 Old: "foo", 1991 New: "", 1992 NewComputed: true, 1993 }, 1994 }, 1995 }, 1996 Key: "availability_zone", 1997 Value: "bar", 1998 Expected: true, 1999 } 2000 2001 d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff) 2002 d.SetNew(tc.Key, tc.Value) 2003 2004 actual := d.NewValueKnown(tc.Key) 2005 if tc.Expected != actual { 2006 t.Fatalf("expected ok: %t, got: %t", tc.Expected, actual) 2007 } 2008 } 2009 2010 func TestResourceDiffNewValueKnownSetNewComputed(t *testing.T) { 2011 tc := struct { 2012 Schema map[string]*Schema 2013 State *terraform.InstanceState 2014 Config *terraform.ResourceConfig 2015 Diff *terraform.InstanceDiff 2016 Key string 2017 Expected bool 2018 }{ 2019 Schema: map[string]*Schema{ 2020 "availability_zone": { 2021 Type: TypeString, 2022 Computed: true, 2023 }, 2024 }, 2025 State: &terraform.InstanceState{ 2026 Attributes: map[string]string{ 2027 "availability_zone": "foo", 2028 }, 2029 }, 2030 Config: testConfig(t, map[string]interface{}{}), 2031 Diff: &terraform.InstanceDiff{ 2032 Attributes: map[string]*terraform.ResourceAttrDiff{}, 2033 }, 2034 Key: "availability_zone", 2035 Expected: false, 2036 } 2037 2038 d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff) 2039 d.SetNewComputed(tc.Key) 2040 2041 actual := d.NewValueKnown(tc.Key) 2042 if tc.Expected != actual { 2043 t.Fatalf("expected ok: %t, got: %t", tc.Expected, actual) 2044 } 2045 }