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