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