github.com/ben-turner/terraform@v0.11.8-0.20180503104400-0cc9e050ecd4/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 } 1032 for _, tc := range cases { 1033 t.Run(tc.Name, func(t *testing.T) { 1034 m := schemaMap(tc.Schema) 1035 d := newResourceDiff(m, tc.Config, tc.State, tc.Diff) 1036 err := d.Clear(tc.Key) 1037 switch { 1038 case err != nil && !tc.ExpectedError: 1039 t.Fatalf("bad: %s", err) 1040 case err == nil && tc.ExpectedError: 1041 t.Fatalf("Expected error, got none") 1042 case err != nil && tc.ExpectedError: 1043 return 1044 } 1045 for _, k := range d.UpdatedKeys() { 1046 if err := m.diff(k, m[k], tc.Diff, d, false); err != nil { 1047 t.Fatalf("bad: %s", err) 1048 } 1049 } 1050 if !reflect.DeepEqual(tc.Expected, tc.Diff) { 1051 t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff)) 1052 } 1053 }) 1054 } 1055 } 1056 1057 func TestGetChangedKeysPrefix(t *testing.T) { 1058 cases := []resourceDiffTestCase{ 1059 resourceDiffTestCase{ 1060 Name: "basic primitive diff", 1061 Schema: map[string]*Schema{ 1062 "foo": &Schema{ 1063 Type: TypeString, 1064 Optional: true, 1065 Computed: true, 1066 }, 1067 }, 1068 State: &terraform.InstanceState{ 1069 Attributes: map[string]string{ 1070 "foo": "bar", 1071 }, 1072 }, 1073 Config: testConfig(t, map[string]interface{}{ 1074 "foo": "baz", 1075 }), 1076 Diff: &terraform.InstanceDiff{ 1077 Attributes: map[string]*terraform.ResourceAttrDiff{ 1078 "foo": &terraform.ResourceAttrDiff{ 1079 Old: "bar", 1080 New: "baz", 1081 }, 1082 }, 1083 }, 1084 Key: "foo", 1085 ExpectedKeys: []string{ 1086 "foo", 1087 }, 1088 }, 1089 resourceDiffTestCase{ 1090 Name: "nested field filtering", 1091 Schema: map[string]*Schema{ 1092 "testfield": &Schema{ 1093 Type: TypeString, 1094 Required: true, 1095 }, 1096 "foo": &Schema{ 1097 Type: TypeList, 1098 Required: true, 1099 MaxItems: 1, 1100 Elem: &Resource{ 1101 Schema: map[string]*Schema{ 1102 "bar": { 1103 Type: TypeString, 1104 Optional: true, 1105 }, 1106 "baz": { 1107 Type: TypeString, 1108 Optional: true, 1109 }, 1110 }, 1111 }, 1112 }, 1113 }, 1114 State: &terraform.InstanceState{ 1115 Attributes: map[string]string{ 1116 "testfield": "blablah", 1117 "foo.#": "1", 1118 "foo.0.bar": "abc", 1119 "foo.0.baz": "xyz", 1120 }, 1121 }, 1122 Config: testConfig(t, map[string]interface{}{ 1123 "testfield": "modified", 1124 "foo": []map[string]interface{}{ 1125 map[string]interface{}{ 1126 "bar": "abcdefg", 1127 "baz": "changed", 1128 }, 1129 }, 1130 }), 1131 Diff: &terraform.InstanceDiff{ 1132 Attributes: map[string]*terraform.ResourceAttrDiff{ 1133 "testfield": &terraform.ResourceAttrDiff{ 1134 Old: "blablah", 1135 New: "modified", 1136 }, 1137 "foo.0.bar": &terraform.ResourceAttrDiff{ 1138 Old: "abc", 1139 New: "abcdefg", 1140 }, 1141 "foo.0.baz": &terraform.ResourceAttrDiff{ 1142 Old: "xyz", 1143 New: "changed", 1144 }, 1145 }, 1146 }, 1147 Key: "foo", 1148 ExpectedKeys: []string{ 1149 "foo.0.bar", 1150 "foo.0.baz", 1151 }, 1152 }, 1153 } 1154 for _, tc := range cases { 1155 t.Run(tc.Name, func(t *testing.T) { 1156 m := schemaMap(tc.Schema) 1157 d := newResourceDiff(m, tc.Config, tc.State, tc.Diff) 1158 keys := d.GetChangedKeysPrefix(tc.Key) 1159 1160 for _, k := range d.UpdatedKeys() { 1161 if err := m.diff(k, m[k], tc.Diff, d, false); err != nil { 1162 t.Fatalf("bad: %s", err) 1163 } 1164 } 1165 1166 sort.Strings(keys) 1167 1168 if !reflect.DeepEqual(tc.ExpectedKeys, keys) { 1169 t.Fatalf("Expected %s, got %s", spew.Sdump(tc.ExpectedKeys), spew.Sdump(keys)) 1170 } 1171 }) 1172 } 1173 } 1174 1175 func TestResourceDiffGetOkExists(t *testing.T) { 1176 cases := []struct { 1177 Name string 1178 Schema map[string]*Schema 1179 State *terraform.InstanceState 1180 Config *terraform.ResourceConfig 1181 Diff *terraform.InstanceDiff 1182 Key string 1183 Value interface{} 1184 Ok bool 1185 }{ 1186 /* 1187 * Primitives 1188 */ 1189 { 1190 Name: "string-literal-empty", 1191 Schema: map[string]*Schema{ 1192 "availability_zone": { 1193 Type: TypeString, 1194 Optional: true, 1195 Computed: true, 1196 ForceNew: true, 1197 }, 1198 }, 1199 1200 State: nil, 1201 Config: nil, 1202 1203 Diff: &terraform.InstanceDiff{ 1204 Attributes: map[string]*terraform.ResourceAttrDiff{ 1205 "availability_zone": { 1206 Old: "", 1207 New: "", 1208 }, 1209 }, 1210 }, 1211 1212 Key: "availability_zone", 1213 Value: "", 1214 Ok: true, 1215 }, 1216 1217 { 1218 Name: "string-computed-empty", 1219 Schema: map[string]*Schema{ 1220 "availability_zone": { 1221 Type: TypeString, 1222 Optional: true, 1223 Computed: true, 1224 ForceNew: true, 1225 }, 1226 }, 1227 1228 State: nil, 1229 Config: nil, 1230 1231 Diff: &terraform.InstanceDiff{ 1232 Attributes: map[string]*terraform.ResourceAttrDiff{ 1233 "availability_zone": { 1234 Old: "", 1235 New: "", 1236 NewComputed: true, 1237 }, 1238 }, 1239 }, 1240 1241 Key: "availability_zone", 1242 Value: "", 1243 Ok: false, 1244 }, 1245 1246 { 1247 Name: "string-optional-computed-nil-diff", 1248 Schema: map[string]*Schema{ 1249 "availability_zone": { 1250 Type: TypeString, 1251 Optional: true, 1252 Computed: true, 1253 ForceNew: true, 1254 }, 1255 }, 1256 1257 State: nil, 1258 Config: nil, 1259 1260 Diff: nil, 1261 1262 Key: "availability_zone", 1263 Value: "", 1264 Ok: false, 1265 }, 1266 1267 /* 1268 * Lists 1269 */ 1270 1271 { 1272 Name: "list-optional", 1273 Schema: map[string]*Schema{ 1274 "ports": { 1275 Type: TypeList, 1276 Optional: true, 1277 Elem: &Schema{Type: TypeInt}, 1278 }, 1279 }, 1280 1281 State: nil, 1282 Config: nil, 1283 1284 Diff: nil, 1285 1286 Key: "ports", 1287 Value: []interface{}{}, 1288 Ok: false, 1289 }, 1290 1291 /* 1292 * Map 1293 */ 1294 1295 { 1296 Name: "map-optional", 1297 Schema: map[string]*Schema{ 1298 "ports": { 1299 Type: TypeMap, 1300 Optional: true, 1301 }, 1302 }, 1303 1304 State: nil, 1305 Config: nil, 1306 1307 Diff: nil, 1308 1309 Key: "ports", 1310 Value: map[string]interface{}{}, 1311 Ok: false, 1312 }, 1313 1314 /* 1315 * Set 1316 */ 1317 1318 { 1319 Name: "set-optional", 1320 Schema: map[string]*Schema{ 1321 "ports": { 1322 Type: TypeSet, 1323 Optional: true, 1324 Elem: &Schema{Type: TypeInt}, 1325 Set: func(a interface{}) int { return a.(int) }, 1326 }, 1327 }, 1328 1329 State: nil, 1330 Config: nil, 1331 1332 Diff: nil, 1333 1334 Key: "ports", 1335 Value: []interface{}{}, 1336 Ok: false, 1337 }, 1338 1339 { 1340 Name: "set-optional-key", 1341 Schema: map[string]*Schema{ 1342 "ports": { 1343 Type: TypeSet, 1344 Optional: true, 1345 Elem: &Schema{Type: TypeInt}, 1346 Set: func(a interface{}) int { return a.(int) }, 1347 }, 1348 }, 1349 1350 State: nil, 1351 Config: nil, 1352 1353 Diff: nil, 1354 1355 Key: "ports.0", 1356 Value: 0, 1357 Ok: false, 1358 }, 1359 1360 { 1361 Name: "bool-literal-empty", 1362 Schema: map[string]*Schema{ 1363 "availability_zone": { 1364 Type: TypeBool, 1365 Optional: true, 1366 Computed: true, 1367 ForceNew: true, 1368 }, 1369 }, 1370 1371 State: nil, 1372 Config: nil, 1373 Diff: &terraform.InstanceDiff{ 1374 Attributes: map[string]*terraform.ResourceAttrDiff{ 1375 "availability_zone": { 1376 Old: "", 1377 New: "", 1378 }, 1379 }, 1380 }, 1381 1382 Key: "availability_zone", 1383 Value: false, 1384 Ok: true, 1385 }, 1386 1387 { 1388 Name: "bool-literal-set", 1389 Schema: map[string]*Schema{ 1390 "availability_zone": { 1391 Type: TypeBool, 1392 Optional: true, 1393 Computed: true, 1394 ForceNew: true, 1395 }, 1396 }, 1397 1398 State: nil, 1399 Config: nil, 1400 1401 Diff: &terraform.InstanceDiff{ 1402 Attributes: map[string]*terraform.ResourceAttrDiff{ 1403 "availability_zone": { 1404 New: "true", 1405 }, 1406 }, 1407 }, 1408 1409 Key: "availability_zone", 1410 Value: true, 1411 Ok: true, 1412 }, 1413 { 1414 Name: "value-in-config", 1415 Schema: map[string]*Schema{ 1416 "availability_zone": { 1417 Type: TypeString, 1418 Optional: true, 1419 }, 1420 }, 1421 1422 State: &terraform.InstanceState{ 1423 Attributes: map[string]string{ 1424 "availability_zone": "foo", 1425 }, 1426 }, 1427 Config: testConfig(t, map[string]interface{}{ 1428 "availability_zone": "foo", 1429 }), 1430 1431 Diff: &terraform.InstanceDiff{ 1432 Attributes: map[string]*terraform.ResourceAttrDiff{}, 1433 }, 1434 1435 Key: "availability_zone", 1436 Value: "foo", 1437 Ok: true, 1438 }, 1439 { 1440 Name: "new-value-in-config", 1441 Schema: map[string]*Schema{ 1442 "availability_zone": { 1443 Type: TypeString, 1444 Optional: true, 1445 }, 1446 }, 1447 1448 State: nil, 1449 Config: testConfig(t, map[string]interface{}{ 1450 "availability_zone": "foo", 1451 }), 1452 1453 Diff: &terraform.InstanceDiff{ 1454 Attributes: map[string]*terraform.ResourceAttrDiff{ 1455 "availability_zone": { 1456 Old: "", 1457 New: "foo", 1458 }, 1459 }, 1460 }, 1461 1462 Key: "availability_zone", 1463 Value: "foo", 1464 Ok: true, 1465 }, 1466 { 1467 Name: "optional-computed-value-in-config", 1468 Schema: map[string]*Schema{ 1469 "availability_zone": { 1470 Type: TypeString, 1471 Optional: true, 1472 Computed: true, 1473 }, 1474 }, 1475 1476 State: &terraform.InstanceState{ 1477 Attributes: map[string]string{ 1478 "availability_zone": "foo", 1479 }, 1480 }, 1481 Config: testConfig(t, map[string]interface{}{ 1482 "availability_zone": "bar", 1483 }), 1484 1485 Diff: &terraform.InstanceDiff{ 1486 Attributes: map[string]*terraform.ResourceAttrDiff{ 1487 "availability_zone": { 1488 Old: "foo", 1489 New: "bar", 1490 }, 1491 }, 1492 }, 1493 1494 Key: "availability_zone", 1495 Value: "bar", 1496 Ok: true, 1497 }, 1498 { 1499 Name: "removed-value", 1500 Schema: map[string]*Schema{ 1501 "availability_zone": { 1502 Type: TypeString, 1503 Optional: true, 1504 }, 1505 }, 1506 1507 State: &terraform.InstanceState{ 1508 Attributes: map[string]string{ 1509 "availability_zone": "foo", 1510 }, 1511 }, 1512 Config: testConfig(t, map[string]interface{}{}), 1513 1514 Diff: &terraform.InstanceDiff{ 1515 Attributes: map[string]*terraform.ResourceAttrDiff{ 1516 "availability_zone": { 1517 Old: "foo", 1518 New: "", 1519 NewRemoved: true, 1520 }, 1521 }, 1522 }, 1523 1524 Key: "availability_zone", 1525 Value: "", 1526 Ok: true, 1527 }, 1528 } 1529 1530 for i, tc := range cases { 1531 t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { 1532 d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff) 1533 1534 v, ok := d.GetOkExists(tc.Key) 1535 if s, ok := v.(*Set); ok { 1536 v = s.List() 1537 } 1538 1539 if !reflect.DeepEqual(v, tc.Value) { 1540 t.Fatalf("Bad %s: \n%#v", tc.Name, v) 1541 } 1542 if ok != tc.Ok { 1543 t.Fatalf("%s: expected ok: %t, got: %t", tc.Name, tc.Ok, ok) 1544 } 1545 }) 1546 } 1547 } 1548 1549 func TestResourceDiffGetOkExistsSetNew(t *testing.T) { 1550 tc := struct { 1551 Schema map[string]*Schema 1552 State *terraform.InstanceState 1553 Diff *terraform.InstanceDiff 1554 Key string 1555 Value interface{} 1556 Ok bool 1557 }{ 1558 Schema: map[string]*Schema{ 1559 "availability_zone": { 1560 Type: TypeString, 1561 Optional: true, 1562 Computed: true, 1563 }, 1564 }, 1565 1566 State: nil, 1567 1568 Diff: &terraform.InstanceDiff{ 1569 Attributes: map[string]*terraform.ResourceAttrDiff{}, 1570 }, 1571 1572 Key: "availability_zone", 1573 Value: "foobar", 1574 Ok: true, 1575 } 1576 1577 d := newResourceDiff(tc.Schema, testConfig(t, map[string]interface{}{}), tc.State, tc.Diff) 1578 d.SetNew(tc.Key, tc.Value) 1579 1580 v, ok := d.GetOkExists(tc.Key) 1581 if s, ok := v.(*Set); ok { 1582 v = s.List() 1583 } 1584 1585 if !reflect.DeepEqual(v, tc.Value) { 1586 t.Fatalf("Bad: \n%#v", v) 1587 } 1588 if ok != tc.Ok { 1589 t.Fatalf("expected ok: %t, got: %t", tc.Ok, ok) 1590 } 1591 } 1592 1593 func TestResourceDiffGetOkExistsSetNewComputed(t *testing.T) { 1594 tc := struct { 1595 Schema map[string]*Schema 1596 State *terraform.InstanceState 1597 Diff *terraform.InstanceDiff 1598 Key string 1599 Value interface{} 1600 Ok bool 1601 }{ 1602 Schema: map[string]*Schema{ 1603 "availability_zone": { 1604 Type: TypeString, 1605 Optional: true, 1606 Computed: true, 1607 }, 1608 }, 1609 1610 State: &terraform.InstanceState{ 1611 Attributes: map[string]string{ 1612 "availability_zone": "foo", 1613 }, 1614 }, 1615 1616 Diff: &terraform.InstanceDiff{ 1617 Attributes: map[string]*terraform.ResourceAttrDiff{}, 1618 }, 1619 1620 Key: "availability_zone", 1621 Value: "foobar", 1622 Ok: false, 1623 } 1624 1625 d := newResourceDiff(tc.Schema, testConfig(t, map[string]interface{}{}), tc.State, tc.Diff) 1626 d.SetNewComputed(tc.Key) 1627 1628 _, ok := d.GetOkExists(tc.Key) 1629 1630 if ok != tc.Ok { 1631 t.Fatalf("expected ok: %t, got: %t", tc.Ok, ok) 1632 } 1633 } 1634 1635 func TestResourceDiffNewValueKnown(t *testing.T) { 1636 cases := []struct { 1637 Name string 1638 Schema map[string]*Schema 1639 State *terraform.InstanceState 1640 Config *terraform.ResourceConfig 1641 Diff *terraform.InstanceDiff 1642 Key string 1643 Expected bool 1644 }{ 1645 { 1646 Name: "in config, no state", 1647 Schema: map[string]*Schema{ 1648 "availability_zone": { 1649 Type: TypeString, 1650 Optional: true, 1651 }, 1652 }, 1653 State: nil, 1654 Config: testConfig(t, map[string]interface{}{ 1655 "availability_zone": "foo", 1656 }), 1657 Diff: &terraform.InstanceDiff{ 1658 Attributes: map[string]*terraform.ResourceAttrDiff{ 1659 "availability_zone": { 1660 Old: "", 1661 New: "foo", 1662 }, 1663 }, 1664 }, 1665 Key: "availability_zone", 1666 Expected: true, 1667 }, 1668 { 1669 Name: "in config, has state, no diff", 1670 Schema: map[string]*Schema{ 1671 "availability_zone": { 1672 Type: TypeString, 1673 Optional: true, 1674 }, 1675 }, 1676 State: &terraform.InstanceState{ 1677 Attributes: map[string]string{ 1678 "availability_zone": "foo", 1679 }, 1680 }, 1681 Config: testConfig(t, map[string]interface{}{ 1682 "availability_zone": "foo", 1683 }), 1684 Diff: &terraform.InstanceDiff{ 1685 Attributes: map[string]*terraform.ResourceAttrDiff{}, 1686 }, 1687 Key: "availability_zone", 1688 Expected: true, 1689 }, 1690 { 1691 Name: "computed attribute, in state, no diff", 1692 Schema: map[string]*Schema{ 1693 "availability_zone": { 1694 Type: TypeString, 1695 Computed: true, 1696 }, 1697 }, 1698 State: &terraform.InstanceState{ 1699 Attributes: map[string]string{ 1700 "availability_zone": "foo", 1701 }, 1702 }, 1703 Config: testConfig(t, map[string]interface{}{}), 1704 Diff: &terraform.InstanceDiff{ 1705 Attributes: map[string]*terraform.ResourceAttrDiff{}, 1706 }, 1707 Key: "availability_zone", 1708 Expected: true, 1709 }, 1710 { 1711 Name: "optional and computed attribute, in state, no config", 1712 Schema: map[string]*Schema{ 1713 "availability_zone": { 1714 Type: TypeString, 1715 Optional: true, 1716 Computed: true, 1717 }, 1718 }, 1719 State: &terraform.InstanceState{ 1720 Attributes: map[string]string{ 1721 "availability_zone": "foo", 1722 }, 1723 }, 1724 Config: testConfig(t, map[string]interface{}{}), 1725 Diff: &terraform.InstanceDiff{ 1726 Attributes: map[string]*terraform.ResourceAttrDiff{}, 1727 }, 1728 Key: "availability_zone", 1729 Expected: true, 1730 }, 1731 { 1732 Name: "optional and computed attribute, in state, with config", 1733 Schema: map[string]*Schema{ 1734 "availability_zone": { 1735 Type: TypeString, 1736 Optional: true, 1737 Computed: true, 1738 }, 1739 }, 1740 State: &terraform.InstanceState{ 1741 Attributes: map[string]string{ 1742 "availability_zone": "foo", 1743 }, 1744 }, 1745 Config: testConfig(t, map[string]interface{}{ 1746 "availability_zone": "foo", 1747 }), 1748 Diff: &terraform.InstanceDiff{ 1749 Attributes: map[string]*terraform.ResourceAttrDiff{}, 1750 }, 1751 Key: "availability_zone", 1752 Expected: true, 1753 }, 1754 { 1755 Name: "computed value, through config reader", 1756 Schema: map[string]*Schema{ 1757 "availability_zone": { 1758 Type: TypeString, 1759 Optional: true, 1760 }, 1761 }, 1762 State: &terraform.InstanceState{ 1763 Attributes: map[string]string{ 1764 "availability_zone": "foo", 1765 }, 1766 }, 1767 Config: testConfigInterpolate( 1768 t, 1769 map[string]interface{}{ 1770 "availability_zone": "${var.foo}", 1771 }, 1772 map[string]ast.Variable{ 1773 "var.foo": ast.Variable{ 1774 Value: config.UnknownVariableValue, 1775 Type: ast.TypeString, 1776 }, 1777 }, 1778 ), 1779 Diff: &terraform.InstanceDiff{ 1780 Attributes: map[string]*terraform.ResourceAttrDiff{}, 1781 }, 1782 Key: "availability_zone", 1783 Expected: false, 1784 }, 1785 { 1786 Name: "computed value, through diff reader", 1787 Schema: map[string]*Schema{ 1788 "availability_zone": { 1789 Type: TypeString, 1790 Optional: true, 1791 }, 1792 }, 1793 State: &terraform.InstanceState{ 1794 Attributes: map[string]string{ 1795 "availability_zone": "foo", 1796 }, 1797 }, 1798 Config: testConfigInterpolate( 1799 t, 1800 map[string]interface{}{ 1801 "availability_zone": "${var.foo}", 1802 }, 1803 map[string]ast.Variable{ 1804 "var.foo": ast.Variable{ 1805 Value: config.UnknownVariableValue, 1806 Type: ast.TypeString, 1807 }, 1808 }, 1809 ), 1810 Diff: &terraform.InstanceDiff{ 1811 Attributes: map[string]*terraform.ResourceAttrDiff{ 1812 "availability_zone": { 1813 Old: "foo", 1814 New: "", 1815 NewComputed: true, 1816 }, 1817 }, 1818 }, 1819 Key: "availability_zone", 1820 Expected: false, 1821 }, 1822 } 1823 1824 for i, tc := range cases { 1825 t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { 1826 d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff) 1827 1828 actual := d.NewValueKnown(tc.Key) 1829 if tc.Expected != actual { 1830 t.Fatalf("%s: expected ok: %t, got: %t", tc.Name, tc.Expected, actual) 1831 } 1832 }) 1833 } 1834 } 1835 1836 func TestResourceDiffNewValueKnownSetNew(t *testing.T) { 1837 tc := struct { 1838 Schema map[string]*Schema 1839 State *terraform.InstanceState 1840 Config *terraform.ResourceConfig 1841 Diff *terraform.InstanceDiff 1842 Key string 1843 Value interface{} 1844 Expected bool 1845 }{ 1846 Schema: map[string]*Schema{ 1847 "availability_zone": { 1848 Type: TypeString, 1849 Optional: true, 1850 Computed: true, 1851 }, 1852 }, 1853 State: &terraform.InstanceState{ 1854 Attributes: map[string]string{ 1855 "availability_zone": "foo", 1856 }, 1857 }, 1858 Config: testConfigInterpolate( 1859 t, 1860 map[string]interface{}{ 1861 "availability_zone": "${var.foo}", 1862 }, 1863 map[string]ast.Variable{ 1864 "var.foo": ast.Variable{ 1865 Value: config.UnknownVariableValue, 1866 Type: ast.TypeString, 1867 }, 1868 }, 1869 ), 1870 Diff: &terraform.InstanceDiff{ 1871 Attributes: map[string]*terraform.ResourceAttrDiff{ 1872 "availability_zone": { 1873 Old: "foo", 1874 New: "", 1875 NewComputed: true, 1876 }, 1877 }, 1878 }, 1879 Key: "availability_zone", 1880 Value: "bar", 1881 Expected: true, 1882 } 1883 1884 d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff) 1885 d.SetNew(tc.Key, tc.Value) 1886 1887 actual := d.NewValueKnown(tc.Key) 1888 if tc.Expected != actual { 1889 t.Fatalf("expected ok: %t, got: %t", tc.Expected, actual) 1890 } 1891 } 1892 1893 func TestResourceDiffNewValueKnownSetNewComputed(t *testing.T) { 1894 tc := struct { 1895 Schema map[string]*Schema 1896 State *terraform.InstanceState 1897 Config *terraform.ResourceConfig 1898 Diff *terraform.InstanceDiff 1899 Key string 1900 Expected bool 1901 }{ 1902 Schema: map[string]*Schema{ 1903 "availability_zone": { 1904 Type: TypeString, 1905 Computed: true, 1906 }, 1907 }, 1908 State: &terraform.InstanceState{ 1909 Attributes: map[string]string{ 1910 "availability_zone": "foo", 1911 }, 1912 }, 1913 Config: testConfig(t, map[string]interface{}{}), 1914 Diff: &terraform.InstanceDiff{ 1915 Attributes: map[string]*terraform.ResourceAttrDiff{}, 1916 }, 1917 Key: "availability_zone", 1918 Expected: false, 1919 } 1920 1921 d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff) 1922 d.SetNewComputed(tc.Key) 1923 1924 actual := d.NewValueKnown(tc.Key) 1925 if tc.Expected != actual { 1926 t.Fatalf("expected ok: %t, got: %t", tc.Expected, actual) 1927 } 1928 }