github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/legacy/helper/schema/shims_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package schema 5 6 import ( 7 "bytes" 8 "errors" 9 "fmt" 10 "reflect" 11 "strconv" 12 "testing" 13 "time" 14 15 "github.com/google/go-cmp/cmp" 16 "github.com/google/go-cmp/cmp/cmpopts" 17 "github.com/terramate-io/tf/configs/configschema" 18 "github.com/terramate-io/tf/configs/hcl2shim" 19 "github.com/terramate-io/tf/legacy/helper/hashcode" 20 "github.com/terramate-io/tf/legacy/terraform" 21 "github.com/terramate-io/tf/providers" 22 "github.com/terramate-io/tf/tfdiags" 23 "github.com/zclconf/go-cty/cty" 24 ) 25 26 var ( 27 typeComparer = cmp.Comparer(cty.Type.Equals) 28 valueComparer = cmp.Comparer(cty.Value.RawEquals) 29 equateEmpty = cmpopts.EquateEmpty() 30 ) 31 32 func testApplyDiff(t *testing.T, 33 resource *Resource, 34 state, expected *terraform.InstanceState, 35 diff *terraform.InstanceDiff) { 36 37 testSchema := providers.Schema{ 38 Version: int64(resource.SchemaVersion), 39 Block: resourceSchemaToBlock(resource.Schema), 40 } 41 42 stateVal, err := StateValueFromInstanceState(state, testSchema.Block.ImpliedType()) 43 if err != nil { 44 t.Fatal(err) 45 } 46 47 newState, err := ApplyDiff(stateVal, diff, testSchema.Block) 48 if err != nil { 49 t.Fatal(err) 50 } 51 52 // verify that "id" is correct 53 id := newState.AsValueMap()["id"] 54 55 switch { 56 case diff.Destroy || diff.DestroyDeposed || diff.DestroyTainted: 57 // there should be no id 58 if !id.IsNull() { 59 t.Fatalf("destroyed instance should have no id: %#v", id) 60 } 61 default: 62 // the "id" field always exists and is computed, so it must have a 63 // valid value or be unknown. 64 if id.IsNull() { 65 t.Fatal("new instance state cannot have a null id") 66 } 67 68 if id.IsKnown() && id.AsString() == "" { 69 t.Fatal("new instance id cannot be an empty string") 70 } 71 } 72 73 // Resource.Meta will be hanlded separately, so it's OK that we lose the 74 // timeout values here. 75 expectedState, err := StateValueFromInstanceState(expected, testSchema.Block.ImpliedType()) 76 if err != nil { 77 t.Fatal(err) 78 } 79 80 if !cmp.Equal(expectedState, newState, equateEmpty, typeComparer, valueComparer) { 81 t.Fatalf(cmp.Diff(expectedState, newState, equateEmpty, typeComparer, valueComparer)) 82 } 83 } 84 85 func TestShimResourcePlan_destroyCreate(t *testing.T) { 86 r := &Resource{ 87 SchemaVersion: 2, 88 Schema: map[string]*Schema{ 89 "foo": &Schema{ 90 Type: TypeInt, 91 Optional: true, 92 ForceNew: true, 93 }, 94 }, 95 } 96 97 d := &terraform.InstanceDiff{ 98 Attributes: map[string]*terraform.ResourceAttrDiff{ 99 "foo": &terraform.ResourceAttrDiff{ 100 RequiresNew: true, 101 Old: "3", 102 New: "42", 103 }, 104 }, 105 } 106 107 state := &terraform.InstanceState{ 108 Attributes: map[string]string{"foo": "3"}, 109 } 110 111 expected := &terraform.InstanceState{ 112 ID: hcl2shim.UnknownVariableValue, 113 Attributes: map[string]string{ 114 "id": hcl2shim.UnknownVariableValue, 115 "foo": "42", 116 }, 117 Meta: map[string]interface{}{ 118 "schema_version": "2", 119 }, 120 } 121 122 testApplyDiff(t, r, state, expected, d) 123 } 124 125 func TestShimResourceApply_create(t *testing.T) { 126 r := &Resource{ 127 SchemaVersion: 2, 128 Schema: map[string]*Schema{ 129 "foo": &Schema{ 130 Type: TypeInt, 131 Optional: true, 132 }, 133 }, 134 } 135 136 called := false 137 r.Create = func(d *ResourceData, m interface{}) error { 138 called = true 139 d.SetId("foo") 140 return nil 141 } 142 143 var s *terraform.InstanceState = nil 144 145 d := &terraform.InstanceDiff{ 146 Attributes: map[string]*terraform.ResourceAttrDiff{ 147 "foo": &terraform.ResourceAttrDiff{ 148 New: "42", 149 }, 150 }, 151 } 152 153 actual, err := r.Apply(s, d, nil) 154 if err != nil { 155 t.Fatalf("err: %s", err) 156 } 157 158 if !called { 159 t.Fatal("not called") 160 } 161 162 expected := &terraform.InstanceState{ 163 ID: "foo", 164 Attributes: map[string]string{ 165 "id": "foo", 166 "foo": "42", 167 }, 168 Meta: map[string]interface{}{ 169 "schema_version": "2", 170 }, 171 } 172 173 if !reflect.DeepEqual(actual, expected) { 174 t.Fatalf("bad: %#v", actual) 175 } 176 177 // Shim 178 // now that we have our diff and desired state, see if we can reproduce 179 // that with the shim 180 // we're not testing Resource.Create, so we need to start with the "created" state 181 createdState := &terraform.InstanceState{ 182 ID: "foo", 183 Attributes: map[string]string{"id": "foo"}, 184 } 185 186 testApplyDiff(t, r, createdState, expected, d) 187 } 188 189 func TestShimResourceApply_Timeout_state(t *testing.T) { 190 r := &Resource{ 191 SchemaVersion: 2, 192 Schema: map[string]*Schema{ 193 "foo": &Schema{ 194 Type: TypeInt, 195 Optional: true, 196 }, 197 }, 198 Timeouts: &ResourceTimeout{ 199 Create: DefaultTimeout(40 * time.Minute), 200 Update: DefaultTimeout(80 * time.Minute), 201 Delete: DefaultTimeout(40 * time.Minute), 202 }, 203 } 204 205 called := false 206 r.Create = func(d *ResourceData, m interface{}) error { 207 called = true 208 d.SetId("foo") 209 return nil 210 } 211 212 var s *terraform.InstanceState = nil 213 214 d := &terraform.InstanceDiff{ 215 Attributes: map[string]*terraform.ResourceAttrDiff{ 216 "foo": &terraform.ResourceAttrDiff{ 217 New: "42", 218 }, 219 }, 220 } 221 222 diffTimeout := &ResourceTimeout{ 223 Create: DefaultTimeout(40 * time.Minute), 224 Update: DefaultTimeout(80 * time.Minute), 225 Delete: DefaultTimeout(40 * time.Minute), 226 } 227 228 if err := diffTimeout.DiffEncode(d); err != nil { 229 t.Fatalf("Error encoding timeout to diff: %s", err) 230 } 231 232 actual, err := r.Apply(s, d, nil) 233 if err != nil { 234 t.Fatalf("err: %s", err) 235 } 236 237 if !called { 238 t.Fatal("not called") 239 } 240 241 expected := &terraform.InstanceState{ 242 ID: "foo", 243 Attributes: map[string]string{ 244 "id": "foo", 245 "foo": "42", 246 }, 247 Meta: map[string]interface{}{ 248 "schema_version": "2", 249 TimeoutKey: expectedForValues(40, 0, 80, 40, 0), 250 }, 251 } 252 253 if !reflect.DeepEqual(actual, expected) { 254 t.Fatalf("Not equal in Timeout State:\n\texpected: %#v\n\tactual: %#v", expected.Meta, actual.Meta) 255 } 256 257 // Shim 258 // we're not testing Resource.Create, so we need to start with the "created" state 259 createdState := &terraform.InstanceState{ 260 ID: "foo", 261 Attributes: map[string]string{"id": "foo"}, 262 } 263 264 testApplyDiff(t, r, createdState, expected, d) 265 } 266 267 func TestShimResourceDiff_Timeout_diff(t *testing.T) { 268 r := &Resource{ 269 Schema: map[string]*Schema{ 270 "foo": &Schema{ 271 Type: TypeInt, 272 Optional: true, 273 }, 274 }, 275 Timeouts: &ResourceTimeout{ 276 Create: DefaultTimeout(40 * time.Minute), 277 Update: DefaultTimeout(80 * time.Minute), 278 Delete: DefaultTimeout(40 * time.Minute), 279 }, 280 } 281 282 r.Create = func(d *ResourceData, m interface{}) error { 283 d.SetId("foo") 284 return nil 285 } 286 287 conf := terraform.NewResourceConfigRaw(map[string]interface{}{ 288 "foo": 42, 289 TimeoutsConfigKey: map[string]interface{}{ 290 "create": "2h", 291 }, 292 }) 293 var s *terraform.InstanceState 294 295 actual, err := r.Diff(s, conf, nil) 296 if err != nil { 297 t.Fatalf("err: %s", err) 298 } 299 300 expected := &terraform.InstanceDiff{ 301 Attributes: map[string]*terraform.ResourceAttrDiff{ 302 "foo": &terraform.ResourceAttrDiff{ 303 New: "42", 304 }, 305 }, 306 } 307 308 diffTimeout := &ResourceTimeout{ 309 Create: DefaultTimeout(120 * time.Minute), 310 Update: DefaultTimeout(80 * time.Minute), 311 Delete: DefaultTimeout(40 * time.Minute), 312 } 313 314 if err := diffTimeout.DiffEncode(expected); err != nil { 315 t.Fatalf("Error encoding timeout to diff: %s", err) 316 } 317 318 if !reflect.DeepEqual(actual, expected) { 319 t.Fatalf("Not equal in Timeout Diff:\n\texpected: %#v\n\tactual: %#v", expected.Meta, actual.Meta) 320 } 321 322 // Shim 323 // apply this diff, so we have a state to compare 324 applied, err := r.Apply(s, actual, nil) 325 if err != nil { 326 t.Fatal(err) 327 } 328 329 // we're not testing Resource.Create, so we need to start with the "created" state 330 createdState := &terraform.InstanceState{ 331 ID: "foo", 332 Attributes: map[string]string{"id": "foo"}, 333 } 334 335 testSchema := providers.Schema{ 336 Version: int64(r.SchemaVersion), 337 Block: resourceSchemaToBlock(r.Schema), 338 } 339 340 initialVal, err := StateValueFromInstanceState(createdState, testSchema.Block.ImpliedType()) 341 if err != nil { 342 t.Fatal(err) 343 } 344 345 appliedVal, err := StateValueFromInstanceState(applied, testSchema.Block.ImpliedType()) 346 if err != nil { 347 t.Fatal(err) 348 } 349 350 d, err := DiffFromValues(initialVal, appliedVal, r) 351 if err != nil { 352 t.Fatal(err) 353 } 354 if eq, _ := d.Same(expected); !eq { 355 t.Fatal(cmp.Diff(d, expected)) 356 } 357 } 358 359 func TestShimResourceApply_destroy(t *testing.T) { 360 r := &Resource{ 361 Schema: map[string]*Schema{ 362 "foo": &Schema{ 363 Type: TypeInt, 364 Optional: true, 365 }, 366 }, 367 } 368 369 called := false 370 r.Delete = func(d *ResourceData, m interface{}) error { 371 called = true 372 return nil 373 } 374 375 s := &terraform.InstanceState{ 376 ID: "bar", 377 } 378 379 d := &terraform.InstanceDiff{ 380 Destroy: true, 381 } 382 383 actual, err := r.Apply(s, d, nil) 384 if err != nil { 385 t.Fatalf("err: %s", err) 386 } 387 388 if !called { 389 t.Fatal("delete not called") 390 } 391 392 if actual != nil { 393 t.Fatalf("bad: %#v", actual) 394 } 395 396 // Shim 397 // now that we have our diff and desired state, see if we can reproduce 398 // that with the shim 399 testApplyDiff(t, r, s, actual, d) 400 } 401 402 func TestShimResourceApply_destroyCreate(t *testing.T) { 403 r := &Resource{ 404 Schema: map[string]*Schema{ 405 "foo": &Schema{ 406 Type: TypeInt, 407 Optional: true, 408 ForceNew: true, 409 }, 410 411 "tags": &Schema{ 412 Type: TypeMap, 413 Optional: true, 414 Computed: true, 415 }, 416 }, 417 } 418 419 change := false 420 r.Create = func(d *ResourceData, m interface{}) error { 421 change = d.HasChange("tags") 422 d.SetId("foo") 423 return nil 424 } 425 r.Delete = func(d *ResourceData, m interface{}) error { 426 return nil 427 } 428 429 var s *terraform.InstanceState = &terraform.InstanceState{ 430 ID: "bar", 431 Attributes: map[string]string{ 432 "foo": "7", 433 "tags.Name": "foo", 434 }, 435 } 436 437 d := &terraform.InstanceDiff{ 438 Attributes: map[string]*terraform.ResourceAttrDiff{ 439 "id": &terraform.ResourceAttrDiff{ 440 New: "foo", 441 }, 442 "foo": &terraform.ResourceAttrDiff{ 443 Old: "7", 444 New: "42", 445 RequiresNew: true, 446 }, 447 "tags.Name": &terraform.ResourceAttrDiff{ 448 Old: "foo", 449 New: "foo", 450 RequiresNew: true, 451 }, 452 }, 453 } 454 455 actual, err := r.Apply(s, d, nil) 456 if err != nil { 457 t.Fatalf("err: %s", err) 458 } 459 460 if !change { 461 t.Fatal("should have change") 462 } 463 464 expected := &terraform.InstanceState{ 465 ID: "foo", 466 Attributes: map[string]string{ 467 "id": "foo", 468 "foo": "42", 469 "tags.%": "1", 470 "tags.Name": "foo", 471 }, 472 } 473 474 if !reflect.DeepEqual(actual, expected) { 475 cmp.Diff(actual, expected) 476 } 477 478 // Shim 479 // now that we have our diff and desired state, see if we can reproduce 480 // that with the shim 481 // we're not testing Resource.Create, so we need to start with the "created" state 482 createdState := &terraform.InstanceState{ 483 ID: "foo", 484 Attributes: map[string]string{ 485 "id": "foo", 486 "foo": "7", 487 "tags.%": "1", 488 "tags.Name": "foo", 489 }, 490 } 491 492 testApplyDiff(t, r, createdState, expected, d) 493 } 494 495 func TestShimSchemaMap_Diff(t *testing.T) { 496 cases := []struct { 497 Name string 498 Schema map[string]*Schema 499 State *terraform.InstanceState 500 Config map[string]interface{} 501 CustomizeDiff CustomizeDiffFunc 502 Diff *terraform.InstanceDiff 503 Err bool 504 }{ 505 { 506 Name: "diff-1", 507 Schema: map[string]*Schema{ 508 "availability_zone": &Schema{ 509 Type: TypeString, 510 Optional: true, 511 Computed: true, 512 ForceNew: true, 513 }, 514 }, 515 516 State: nil, 517 518 Config: map[string]interface{}{ 519 "availability_zone": "foo", 520 }, 521 522 Diff: &terraform.InstanceDiff{ 523 Attributes: map[string]*terraform.ResourceAttrDiff{ 524 "availability_zone": &terraform.ResourceAttrDiff{ 525 Old: "", 526 New: "foo", 527 RequiresNew: true, 528 }, 529 }, 530 }, 531 532 Err: false, 533 }, 534 535 { 536 Name: "diff-2", 537 Schema: map[string]*Schema{ 538 "availability_zone": &Schema{ 539 Type: TypeString, 540 Optional: true, 541 Computed: true, 542 ForceNew: true, 543 }, 544 }, 545 546 State: nil, 547 548 Config: map[string]interface{}{}, 549 550 Diff: &terraform.InstanceDiff{ 551 Attributes: map[string]*terraform.ResourceAttrDiff{ 552 "availability_zone": &terraform.ResourceAttrDiff{ 553 Old: "", 554 NewComputed: true, 555 RequiresNew: true, 556 }, 557 }, 558 }, 559 560 Err: false, 561 }, 562 563 { 564 Name: "diff-3", 565 Schema: map[string]*Schema{ 566 "availability_zone": &Schema{ 567 Type: TypeString, 568 Optional: true, 569 Computed: true, 570 ForceNew: true, 571 }, 572 }, 573 574 State: &terraform.InstanceState{ 575 ID: "foo", 576 }, 577 578 Config: map[string]interface{}{}, 579 580 Diff: nil, 581 582 Err: false, 583 }, 584 585 { 586 Name: "Computed, but set in config", 587 Schema: map[string]*Schema{ 588 "availability_zone": &Schema{ 589 Type: TypeString, 590 Optional: true, 591 Computed: true, 592 }, 593 }, 594 595 State: &terraform.InstanceState{ 596 ID: "id", 597 Attributes: map[string]string{ 598 "availability_zone": "foo", 599 }, 600 }, 601 602 Config: map[string]interface{}{ 603 "availability_zone": "bar", 604 }, 605 606 Diff: &terraform.InstanceDiff{ 607 Attributes: map[string]*terraform.ResourceAttrDiff{ 608 "availability_zone": &terraform.ResourceAttrDiff{ 609 Old: "foo", 610 New: "bar", 611 }, 612 }, 613 }, 614 615 Err: false, 616 }, 617 618 { 619 Name: "Default", 620 Schema: map[string]*Schema{ 621 "availability_zone": &Schema{ 622 Type: TypeString, 623 Optional: true, 624 Default: "foo", 625 }, 626 }, 627 628 State: nil, 629 630 Config: nil, 631 632 Diff: &terraform.InstanceDiff{ 633 Attributes: map[string]*terraform.ResourceAttrDiff{ 634 "availability_zone": &terraform.ResourceAttrDiff{ 635 Old: "", 636 New: "foo", 637 }, 638 }, 639 }, 640 641 Err: false, 642 }, 643 644 { 645 Name: "DefaultFunc, value", 646 Schema: map[string]*Schema{ 647 "availability_zone": &Schema{ 648 Type: TypeString, 649 Optional: true, 650 DefaultFunc: func() (interface{}, error) { 651 return "foo", nil 652 }, 653 }, 654 }, 655 656 State: nil, 657 658 Config: nil, 659 660 Diff: &terraform.InstanceDiff{ 661 Attributes: map[string]*terraform.ResourceAttrDiff{ 662 "availability_zone": &terraform.ResourceAttrDiff{ 663 Old: "", 664 New: "foo", 665 }, 666 }, 667 }, 668 669 Err: false, 670 }, 671 672 { 673 Name: "DefaultFunc, configuration set", 674 Schema: map[string]*Schema{ 675 "availability_zone": &Schema{ 676 Type: TypeString, 677 Optional: true, 678 DefaultFunc: func() (interface{}, error) { 679 return "foo", nil 680 }, 681 }, 682 }, 683 684 State: nil, 685 686 Config: map[string]interface{}{ 687 "availability_zone": "bar", 688 }, 689 690 Diff: &terraform.InstanceDiff{ 691 Attributes: map[string]*terraform.ResourceAttrDiff{ 692 "availability_zone": &terraform.ResourceAttrDiff{ 693 Old: "", 694 New: "bar", 695 }, 696 }, 697 }, 698 699 Err: false, 700 }, 701 702 { 703 Name: "String with StateFunc", 704 Schema: map[string]*Schema{ 705 "availability_zone": &Schema{ 706 Type: TypeString, 707 Optional: true, 708 Computed: true, 709 StateFunc: func(a interface{}) string { 710 return a.(string) + "!" 711 }, 712 }, 713 }, 714 715 State: nil, 716 717 Config: map[string]interface{}{ 718 "availability_zone": "foo", 719 }, 720 721 Diff: &terraform.InstanceDiff{ 722 Attributes: map[string]*terraform.ResourceAttrDiff{ 723 "availability_zone": &terraform.ResourceAttrDiff{ 724 Old: "", 725 New: "foo!", 726 NewExtra: "foo", 727 }, 728 }, 729 }, 730 731 Err: false, 732 }, 733 734 { 735 Name: "StateFunc not called with nil value", 736 Schema: map[string]*Schema{ 737 "availability_zone": &Schema{ 738 Type: TypeString, 739 Optional: true, 740 Computed: true, 741 StateFunc: func(a interface{}) string { 742 t.Error("should not get here!") 743 return "" 744 }, 745 }, 746 }, 747 748 State: nil, 749 750 Config: map[string]interface{}{}, 751 752 Diff: &terraform.InstanceDiff{ 753 Attributes: map[string]*terraform.ResourceAttrDiff{ 754 "availability_zone": &terraform.ResourceAttrDiff{ 755 Old: "", 756 New: "", 757 NewComputed: true, 758 }, 759 }, 760 }, 761 762 Err: false, 763 }, 764 765 { 766 Name: "Variable computed", 767 Schema: map[string]*Schema{ 768 "availability_zone": &Schema{ 769 Type: TypeString, 770 Optional: true, 771 }, 772 }, 773 774 State: nil, 775 776 Config: map[string]interface{}{ 777 "availability_zone": hcl2shim.UnknownVariableValue, 778 }, 779 780 Diff: &terraform.InstanceDiff{ 781 Attributes: map[string]*terraform.ResourceAttrDiff{ 782 "availability_zone": &terraform.ResourceAttrDiff{ 783 Old: "", 784 New: hcl2shim.UnknownVariableValue, 785 NewComputed: true, 786 }, 787 }, 788 }, 789 790 Err: false, 791 }, 792 793 { 794 Name: "Int decode", 795 Schema: map[string]*Schema{ 796 "port": &Schema{ 797 Type: TypeInt, 798 Optional: true, 799 Computed: true, 800 ForceNew: true, 801 }, 802 }, 803 804 State: nil, 805 806 Config: map[string]interface{}{ 807 "port": 27, 808 }, 809 810 Diff: &terraform.InstanceDiff{ 811 Attributes: map[string]*terraform.ResourceAttrDiff{ 812 "port": &terraform.ResourceAttrDiff{ 813 Old: "", 814 New: "27", 815 RequiresNew: true, 816 }, 817 }, 818 }, 819 820 Err: false, 821 }, 822 823 { 824 Name: "bool decode", 825 Schema: map[string]*Schema{ 826 "port": &Schema{ 827 Type: TypeBool, 828 Optional: true, 829 Computed: true, 830 ForceNew: true, 831 }, 832 }, 833 834 State: nil, 835 836 Config: map[string]interface{}{ 837 "port": false, 838 }, 839 840 Diff: &terraform.InstanceDiff{ 841 Attributes: map[string]*terraform.ResourceAttrDiff{ 842 "port": &terraform.ResourceAttrDiff{ 843 Old: "", 844 New: "false", 845 RequiresNew: true, 846 }, 847 }, 848 }, 849 850 Err: false, 851 }, 852 853 { 854 Name: "Bool", 855 Schema: map[string]*Schema{ 856 "delete": &Schema{ 857 Type: TypeBool, 858 Optional: true, 859 Default: false, 860 }, 861 }, 862 863 State: &terraform.InstanceState{ 864 ID: "id", 865 Attributes: map[string]string{ 866 "delete": "false", 867 }, 868 }, 869 870 Config: nil, 871 872 Diff: nil, 873 874 Err: false, 875 }, 876 877 { 878 Name: "List decode", 879 Schema: map[string]*Schema{ 880 "ports": &Schema{ 881 Type: TypeList, 882 Required: true, 883 Elem: &Schema{Type: TypeInt}, 884 }, 885 }, 886 887 State: nil, 888 889 Config: map[string]interface{}{ 890 "ports": []interface{}{1, 2, 5}, 891 }, 892 893 Diff: &terraform.InstanceDiff{ 894 Attributes: map[string]*terraform.ResourceAttrDiff{ 895 "ports.#": &terraform.ResourceAttrDiff{ 896 Old: "0", 897 New: "3", 898 }, 899 "ports.0": &terraform.ResourceAttrDiff{ 900 Old: "", 901 New: "1", 902 }, 903 "ports.1": &terraform.ResourceAttrDiff{ 904 Old: "", 905 New: "2", 906 }, 907 "ports.2": &terraform.ResourceAttrDiff{ 908 Old: "", 909 New: "5", 910 }, 911 }, 912 }, 913 914 Err: false, 915 }, 916 917 { 918 Name: "List decode with promotion with list", 919 Schema: map[string]*Schema{ 920 "ports": &Schema{ 921 Type: TypeList, 922 Required: true, 923 Elem: &Schema{Type: TypeInt}, 924 PromoteSingle: true, 925 }, 926 }, 927 928 State: nil, 929 930 Config: map[string]interface{}{ 931 "ports": []interface{}{"5"}, 932 }, 933 934 Diff: &terraform.InstanceDiff{ 935 Attributes: map[string]*terraform.ResourceAttrDiff{ 936 "ports.#": &terraform.ResourceAttrDiff{ 937 Old: "0", 938 New: "1", 939 }, 940 "ports.0": &terraform.ResourceAttrDiff{ 941 Old: "", 942 New: "5", 943 }, 944 }, 945 }, 946 947 Err: false, 948 }, 949 950 { 951 Schema: map[string]*Schema{ 952 "ports": &Schema{ 953 Type: TypeList, 954 Required: true, 955 Elem: &Schema{Type: TypeInt}, 956 }, 957 }, 958 959 State: nil, 960 961 Config: map[string]interface{}{ 962 "ports": []interface{}{1, 2, 5}, 963 }, 964 965 Diff: &terraform.InstanceDiff{ 966 Attributes: map[string]*terraform.ResourceAttrDiff{ 967 "ports.#": &terraform.ResourceAttrDiff{ 968 Old: "0", 969 New: "3", 970 }, 971 "ports.0": &terraform.ResourceAttrDiff{ 972 Old: "", 973 New: "1", 974 }, 975 "ports.1": &terraform.ResourceAttrDiff{ 976 Old: "", 977 New: "2", 978 }, 979 "ports.2": &terraform.ResourceAttrDiff{ 980 Old: "", 981 New: "5", 982 }, 983 }, 984 }, 985 986 Err: false, 987 }, 988 989 { 990 Schema: map[string]*Schema{ 991 "ports": &Schema{ 992 Type: TypeList, 993 Required: true, 994 Elem: &Schema{Type: TypeInt}, 995 }, 996 }, 997 998 State: nil, 999 1000 Config: map[string]interface{}{ 1001 "ports": []interface{}{1, hcl2shim.UnknownVariableValue, "5"}, 1002 }, 1003 1004 Diff: &terraform.InstanceDiff{ 1005 Attributes: map[string]*terraform.ResourceAttrDiff{ 1006 "ports.#": &terraform.ResourceAttrDiff{ 1007 Old: "0", 1008 New: "", 1009 NewComputed: true, 1010 }, 1011 }, 1012 }, 1013 1014 Err: false, 1015 }, 1016 1017 { 1018 Schema: map[string]*Schema{ 1019 "ports": &Schema{ 1020 Type: TypeList, 1021 Required: true, 1022 Elem: &Schema{Type: TypeInt}, 1023 }, 1024 }, 1025 1026 State: &terraform.InstanceState{ 1027 ID: "id", 1028 Attributes: map[string]string{ 1029 "ports.#": "3", 1030 "ports.0": "1", 1031 "ports.1": "2", 1032 "ports.2": "5", 1033 }, 1034 }, 1035 1036 Config: map[string]interface{}{ 1037 "ports": []interface{}{1, 2, 5}, 1038 }, 1039 1040 Diff: nil, 1041 1042 Err: false, 1043 }, 1044 1045 { 1046 Name: "", 1047 Schema: map[string]*Schema{ 1048 "ports": &Schema{ 1049 Type: TypeList, 1050 Required: true, 1051 Elem: &Schema{Type: TypeInt}, 1052 }, 1053 }, 1054 1055 State: &terraform.InstanceState{ 1056 ID: "id", 1057 Attributes: map[string]string{ 1058 "ports.#": "2", 1059 "ports.0": "1", 1060 "ports.1": "2", 1061 }, 1062 }, 1063 1064 Config: map[string]interface{}{ 1065 "ports": []interface{}{1, 2, 5}, 1066 }, 1067 1068 Diff: &terraform.InstanceDiff{ 1069 Attributes: map[string]*terraform.ResourceAttrDiff{ 1070 "ports.#": &terraform.ResourceAttrDiff{ 1071 Old: "2", 1072 New: "3", 1073 }, 1074 "ports.2": &terraform.ResourceAttrDiff{ 1075 Old: "", 1076 New: "5", 1077 }, 1078 }, 1079 }, 1080 1081 Err: false, 1082 }, 1083 1084 { 1085 Name: "", 1086 Schema: map[string]*Schema{ 1087 "ports": &Schema{ 1088 Type: TypeList, 1089 Required: true, 1090 Elem: &Schema{Type: TypeInt}, 1091 ForceNew: true, 1092 }, 1093 }, 1094 1095 State: nil, 1096 1097 Config: map[string]interface{}{ 1098 "ports": []interface{}{1, 2, 5}, 1099 }, 1100 1101 Diff: &terraform.InstanceDiff{ 1102 Attributes: map[string]*terraform.ResourceAttrDiff{ 1103 "ports.#": &terraform.ResourceAttrDiff{ 1104 Old: "0", 1105 New: "3", 1106 RequiresNew: true, 1107 }, 1108 "ports.0": &terraform.ResourceAttrDiff{ 1109 Old: "", 1110 New: "1", 1111 RequiresNew: true, 1112 }, 1113 "ports.1": &terraform.ResourceAttrDiff{ 1114 Old: "", 1115 New: "2", 1116 RequiresNew: true, 1117 }, 1118 "ports.2": &terraform.ResourceAttrDiff{ 1119 Old: "", 1120 New: "5", 1121 RequiresNew: true, 1122 }, 1123 }, 1124 }, 1125 1126 Err: false, 1127 }, 1128 1129 { 1130 Name: "", 1131 Schema: map[string]*Schema{ 1132 "ports": &Schema{ 1133 Type: TypeList, 1134 Optional: true, 1135 Computed: true, 1136 Elem: &Schema{Type: TypeInt}, 1137 }, 1138 }, 1139 1140 State: nil, 1141 1142 Config: map[string]interface{}{}, 1143 1144 Diff: &terraform.InstanceDiff{ 1145 Attributes: map[string]*terraform.ResourceAttrDiff{ 1146 "ports.#": &terraform.ResourceAttrDiff{ 1147 Old: "", 1148 NewComputed: true, 1149 }, 1150 }, 1151 }, 1152 1153 Err: false, 1154 }, 1155 1156 { 1157 Name: "List with computed set", 1158 Schema: map[string]*Schema{ 1159 "config": &Schema{ 1160 Type: TypeList, 1161 Optional: true, 1162 ForceNew: true, 1163 MinItems: 1, 1164 Elem: &Resource{ 1165 Schema: map[string]*Schema{ 1166 "name": { 1167 Type: TypeString, 1168 Required: true, 1169 }, 1170 1171 "rules": { 1172 Type: TypeSet, 1173 Computed: true, 1174 Elem: &Schema{Type: TypeString}, 1175 Set: HashString, 1176 }, 1177 }, 1178 }, 1179 }, 1180 }, 1181 1182 State: nil, 1183 1184 Config: map[string]interface{}{ 1185 "config": []interface{}{ 1186 map[string]interface{}{ 1187 "name": "hello", 1188 }, 1189 }, 1190 }, 1191 1192 Diff: &terraform.InstanceDiff{ 1193 Attributes: map[string]*terraform.ResourceAttrDiff{ 1194 "config.#": &terraform.ResourceAttrDiff{ 1195 Old: "0", 1196 New: "1", 1197 RequiresNew: true, 1198 }, 1199 1200 "config.0.name": &terraform.ResourceAttrDiff{ 1201 Old: "", 1202 New: "hello", 1203 }, 1204 1205 "config.0.rules.#": &terraform.ResourceAttrDiff{ 1206 Old: "", 1207 NewComputed: true, 1208 }, 1209 }, 1210 }, 1211 1212 Err: false, 1213 }, 1214 1215 { 1216 Name: "Set-1", 1217 Schema: map[string]*Schema{ 1218 "ports": &Schema{ 1219 Type: TypeSet, 1220 Required: true, 1221 Elem: &Schema{Type: TypeInt}, 1222 Set: func(a interface{}) int { 1223 return a.(int) 1224 }, 1225 }, 1226 }, 1227 1228 State: nil, 1229 1230 Config: map[string]interface{}{ 1231 "ports": []interface{}{5, 2, 1}, 1232 }, 1233 1234 Diff: &terraform.InstanceDiff{ 1235 Attributes: map[string]*terraform.ResourceAttrDiff{ 1236 "ports.#": &terraform.ResourceAttrDiff{ 1237 Old: "0", 1238 New: "3", 1239 }, 1240 "ports.1": &terraform.ResourceAttrDiff{ 1241 Old: "", 1242 New: "1", 1243 }, 1244 "ports.2": &terraform.ResourceAttrDiff{ 1245 Old: "", 1246 New: "2", 1247 }, 1248 "ports.5": &terraform.ResourceAttrDiff{ 1249 Old: "", 1250 New: "5", 1251 }, 1252 }, 1253 }, 1254 1255 Err: false, 1256 }, 1257 1258 { 1259 Name: "Set-2", 1260 Schema: map[string]*Schema{ 1261 "ports": &Schema{ 1262 Type: TypeSet, 1263 Computed: true, 1264 Required: true, 1265 Elem: &Schema{Type: TypeInt}, 1266 Set: func(a interface{}) int { 1267 return a.(int) 1268 }, 1269 }, 1270 }, 1271 1272 State: &terraform.InstanceState{ 1273 ID: "id", 1274 Attributes: map[string]string{ 1275 "ports.#": "0", 1276 }, 1277 }, 1278 1279 Config: nil, 1280 1281 Diff: nil, 1282 1283 Err: false, 1284 }, 1285 1286 { 1287 Name: "Set-3", 1288 Schema: map[string]*Schema{ 1289 "ports": &Schema{ 1290 Type: TypeSet, 1291 Optional: true, 1292 Computed: true, 1293 Elem: &Schema{Type: TypeInt}, 1294 Set: func(a interface{}) int { 1295 return a.(int) 1296 }, 1297 }, 1298 }, 1299 1300 State: nil, 1301 1302 Config: nil, 1303 1304 Diff: &terraform.InstanceDiff{ 1305 Attributes: map[string]*terraform.ResourceAttrDiff{ 1306 "ports.#": &terraform.ResourceAttrDiff{ 1307 Old: "", 1308 NewComputed: true, 1309 }, 1310 }, 1311 }, 1312 1313 Err: false, 1314 }, 1315 1316 { 1317 Name: "Set-4", 1318 Schema: map[string]*Schema{ 1319 "ports": &Schema{ 1320 Type: TypeSet, 1321 Required: true, 1322 Elem: &Schema{Type: TypeInt}, 1323 Set: func(a interface{}) int { 1324 return a.(int) 1325 }, 1326 }, 1327 }, 1328 1329 State: nil, 1330 1331 Config: map[string]interface{}{ 1332 "ports": []interface{}{"2", "5", 1}, 1333 }, 1334 1335 Diff: &terraform.InstanceDiff{ 1336 Attributes: map[string]*terraform.ResourceAttrDiff{ 1337 "ports.#": &terraform.ResourceAttrDiff{ 1338 Old: "0", 1339 New: "3", 1340 }, 1341 "ports.1": &terraform.ResourceAttrDiff{ 1342 Old: "", 1343 New: "1", 1344 }, 1345 "ports.2": &terraform.ResourceAttrDiff{ 1346 Old: "", 1347 New: "2", 1348 }, 1349 "ports.5": &terraform.ResourceAttrDiff{ 1350 Old: "", 1351 New: "5", 1352 }, 1353 }, 1354 }, 1355 1356 Err: false, 1357 }, 1358 1359 { 1360 Name: "Set-5", 1361 Schema: map[string]*Schema{ 1362 "ports": &Schema{ 1363 Type: TypeSet, 1364 Required: true, 1365 Elem: &Schema{Type: TypeInt}, 1366 Set: func(a interface{}) int { 1367 return a.(int) 1368 }, 1369 }, 1370 }, 1371 1372 State: nil, 1373 1374 Config: map[string]interface{}{ 1375 "ports": []interface{}{1, hcl2shim.UnknownVariableValue, 5}, 1376 }, 1377 1378 Diff: &terraform.InstanceDiff{ 1379 Attributes: map[string]*terraform.ResourceAttrDiff{ 1380 "ports.#": &terraform.ResourceAttrDiff{ 1381 Old: "", 1382 New: "", 1383 NewComputed: true, 1384 }, 1385 }, 1386 }, 1387 1388 Err: false, 1389 }, 1390 1391 { 1392 Name: "Set-6", 1393 Schema: map[string]*Schema{ 1394 "ports": &Schema{ 1395 Type: TypeSet, 1396 Required: true, 1397 Elem: &Schema{Type: TypeInt}, 1398 Set: func(a interface{}) int { 1399 return a.(int) 1400 }, 1401 }, 1402 }, 1403 1404 State: &terraform.InstanceState{ 1405 ID: "id", 1406 Attributes: map[string]string{ 1407 "ports.#": "2", 1408 "ports.1": "1", 1409 "ports.2": "2", 1410 }, 1411 }, 1412 1413 Config: map[string]interface{}{ 1414 "ports": []interface{}{5, 2, 1}, 1415 }, 1416 1417 Diff: &terraform.InstanceDiff{ 1418 Attributes: map[string]*terraform.ResourceAttrDiff{ 1419 "ports.#": &terraform.ResourceAttrDiff{ 1420 Old: "2", 1421 New: "3", 1422 }, 1423 "ports.1": &terraform.ResourceAttrDiff{ 1424 Old: "1", 1425 New: "1", 1426 }, 1427 "ports.2": &terraform.ResourceAttrDiff{ 1428 Old: "2", 1429 New: "2", 1430 }, 1431 "ports.5": &terraform.ResourceAttrDiff{ 1432 Old: "", 1433 New: "5", 1434 }, 1435 }, 1436 }, 1437 1438 Err: false, 1439 }, 1440 1441 { 1442 Name: "Set-8", 1443 Schema: map[string]*Schema{ 1444 "ports": &Schema{ 1445 Type: TypeSet, 1446 Optional: true, 1447 Computed: true, 1448 Elem: &Schema{Type: TypeInt}, 1449 Set: func(a interface{}) int { 1450 return a.(int) 1451 }, 1452 }, 1453 }, 1454 1455 State: &terraform.InstanceState{ 1456 ID: "id", 1457 Attributes: map[string]string{ 1458 "availability_zone": "bar", 1459 "ports.#": "1", 1460 "ports.80": "80", 1461 }, 1462 }, 1463 1464 Config: map[string]interface{}{}, 1465 1466 Diff: nil, 1467 1468 Err: false, 1469 }, 1470 1471 { 1472 Name: "Set-9", 1473 Schema: map[string]*Schema{ 1474 "ingress": &Schema{ 1475 Type: TypeSet, 1476 Required: true, 1477 Elem: &Resource{ 1478 Schema: map[string]*Schema{ 1479 "ports": &Schema{ 1480 Type: TypeList, 1481 Optional: true, 1482 Elem: &Schema{Type: TypeInt}, 1483 }, 1484 }, 1485 }, 1486 Set: func(v interface{}) int { 1487 m := v.(map[string]interface{}) 1488 ps := m["ports"].([]interface{}) 1489 result := 0 1490 for _, p := range ps { 1491 result += p.(int) 1492 } 1493 return result 1494 }, 1495 }, 1496 }, 1497 1498 State: &terraform.InstanceState{ 1499 ID: "id", 1500 Attributes: map[string]string{ 1501 "ingress.#": "2", 1502 "ingress.80.ports.#": "1", 1503 "ingress.80.ports.0": "80", 1504 "ingress.443.ports.#": "1", 1505 "ingress.443.ports.0": "443", 1506 }, 1507 }, 1508 1509 Config: map[string]interface{}{ 1510 "ingress": []interface{}{ 1511 map[string]interface{}{ 1512 "ports": []interface{}{443}, 1513 }, 1514 map[string]interface{}{ 1515 "ports": []interface{}{80}, 1516 }, 1517 }, 1518 }, 1519 1520 Diff: nil, 1521 1522 Err: false, 1523 }, 1524 1525 { 1526 Name: "List of structure decode", 1527 Schema: map[string]*Schema{ 1528 "ingress": &Schema{ 1529 Type: TypeList, 1530 Required: true, 1531 Elem: &Resource{ 1532 Schema: map[string]*Schema{ 1533 "from": &Schema{ 1534 Type: TypeInt, 1535 Required: true, 1536 }, 1537 }, 1538 }, 1539 }, 1540 }, 1541 1542 State: nil, 1543 1544 Config: map[string]interface{}{ 1545 "ingress": []interface{}{ 1546 map[string]interface{}{ 1547 "from": 8080, 1548 }, 1549 }, 1550 }, 1551 1552 Diff: &terraform.InstanceDiff{ 1553 Attributes: map[string]*terraform.ResourceAttrDiff{ 1554 "ingress.#": &terraform.ResourceAttrDiff{ 1555 Old: "0", 1556 New: "1", 1557 }, 1558 "ingress.0.from": &terraform.ResourceAttrDiff{ 1559 Old: "", 1560 New: "8080", 1561 }, 1562 }, 1563 }, 1564 1565 Err: false, 1566 }, 1567 1568 { 1569 Name: "ComputedWhen", 1570 Schema: map[string]*Schema{ 1571 "availability_zone": &Schema{ 1572 Type: TypeString, 1573 Computed: true, 1574 ComputedWhen: []string{"port"}, 1575 }, 1576 1577 "port": &Schema{ 1578 Type: TypeInt, 1579 Optional: true, 1580 }, 1581 }, 1582 1583 State: &terraform.InstanceState{ 1584 ID: "id", 1585 Attributes: map[string]string{ 1586 "availability_zone": "foo", 1587 "port": "80", 1588 }, 1589 }, 1590 1591 Config: map[string]interface{}{ 1592 "port": 80, 1593 }, 1594 1595 Diff: nil, 1596 1597 Err: false, 1598 }, 1599 1600 { 1601 Name: "computed", 1602 Schema: map[string]*Schema{ 1603 "availability_zone": &Schema{ 1604 Type: TypeString, 1605 Computed: true, 1606 ComputedWhen: []string{"port"}, 1607 }, 1608 1609 "port": &Schema{ 1610 Type: TypeInt, 1611 Optional: true, 1612 }, 1613 }, 1614 1615 State: nil, 1616 1617 Config: map[string]interface{}{ 1618 "port": 80, 1619 }, 1620 1621 Diff: &terraform.InstanceDiff{ 1622 Attributes: map[string]*terraform.ResourceAttrDiff{ 1623 "availability_zone": &terraform.ResourceAttrDiff{ 1624 NewComputed: true, 1625 }, 1626 "port": &terraform.ResourceAttrDiff{ 1627 New: "80", 1628 }, 1629 }, 1630 }, 1631 1632 Err: false, 1633 }, 1634 1635 { 1636 Name: "computed, exists", 1637 Schema: map[string]*Schema{ 1638 "availability_zone": &Schema{ 1639 Type: TypeString, 1640 Computed: true, 1641 ComputedWhen: []string{"port"}, 1642 }, 1643 1644 "port": &Schema{ 1645 Type: TypeInt, 1646 Optional: true, 1647 }, 1648 }, 1649 1650 State: &terraform.InstanceState{ 1651 ID: "id", 1652 Attributes: map[string]string{ 1653 "port": "80", 1654 }, 1655 }, 1656 1657 Config: map[string]interface{}{ 1658 "port": 80, 1659 }, 1660 1661 // there is no computed diff when the instance exists already 1662 Diff: nil, 1663 1664 Err: false, 1665 }, 1666 1667 { 1668 Name: "Maps-1", 1669 Schema: map[string]*Schema{ 1670 "config_vars": &Schema{ 1671 Type: TypeMap, 1672 }, 1673 }, 1674 1675 State: nil, 1676 1677 Config: map[string]interface{}{ 1678 "config_vars": map[string]interface{}{ 1679 "bar": "baz", 1680 }, 1681 }, 1682 1683 Diff: &terraform.InstanceDiff{ 1684 Attributes: map[string]*terraform.ResourceAttrDiff{ 1685 "config_vars.%": &terraform.ResourceAttrDiff{ 1686 Old: "0", 1687 New: "1", 1688 }, 1689 1690 "config_vars.bar": &terraform.ResourceAttrDiff{ 1691 Old: "", 1692 New: "baz", 1693 }, 1694 }, 1695 }, 1696 1697 Err: false, 1698 }, 1699 1700 { 1701 Name: "Maps-2", 1702 Schema: map[string]*Schema{ 1703 "config_vars": &Schema{ 1704 Type: TypeMap, 1705 }, 1706 }, 1707 1708 State: &terraform.InstanceState{ 1709 ID: "id", 1710 Attributes: map[string]string{ 1711 "config_vars.%": "1", 1712 "config_vars.foo": "bar", 1713 }, 1714 }, 1715 1716 Config: map[string]interface{}{ 1717 "config_vars": map[string]interface{}{ 1718 "bar": "baz", 1719 }, 1720 }, 1721 1722 Diff: &terraform.InstanceDiff{ 1723 Attributes: map[string]*terraform.ResourceAttrDiff{ 1724 "config_vars.foo": &terraform.ResourceAttrDiff{ 1725 Old: "bar", 1726 NewRemoved: true, 1727 }, 1728 "config_vars.bar": &terraform.ResourceAttrDiff{ 1729 Old: "", 1730 New: "baz", 1731 }, 1732 }, 1733 }, 1734 1735 Err: false, 1736 }, 1737 1738 { 1739 Name: "Maps-3", 1740 Schema: map[string]*Schema{ 1741 "vars": &Schema{ 1742 Type: TypeMap, 1743 Optional: true, 1744 Computed: true, 1745 }, 1746 }, 1747 1748 State: &terraform.InstanceState{ 1749 ID: "id", 1750 Attributes: map[string]string{ 1751 "vars.%": "1", 1752 "vars.foo": "bar", 1753 }, 1754 }, 1755 1756 Config: map[string]interface{}{ 1757 "vars": map[string]interface{}{ 1758 "bar": "baz", 1759 }, 1760 }, 1761 1762 Diff: &terraform.InstanceDiff{ 1763 Attributes: map[string]*terraform.ResourceAttrDiff{ 1764 "vars.foo": &terraform.ResourceAttrDiff{ 1765 Old: "bar", 1766 New: "", 1767 NewRemoved: true, 1768 }, 1769 "vars.bar": &terraform.ResourceAttrDiff{ 1770 Old: "", 1771 New: "baz", 1772 }, 1773 }, 1774 }, 1775 1776 Err: false, 1777 }, 1778 1779 { 1780 Name: "Maps-4", 1781 Schema: map[string]*Schema{ 1782 "vars": &Schema{ 1783 Type: TypeMap, 1784 Computed: true, 1785 }, 1786 }, 1787 1788 State: &terraform.InstanceState{ 1789 ID: "id", 1790 Attributes: map[string]string{ 1791 "vars.%": "1", 1792 "vars.foo": "bar", 1793 }, 1794 }, 1795 1796 Config: nil, 1797 1798 Diff: nil, 1799 1800 Err: false, 1801 }, 1802 1803 { 1804 Name: "Maps-5", 1805 Schema: map[string]*Schema{ 1806 "config_vars": &Schema{ 1807 Type: TypeList, 1808 Elem: &Schema{Type: TypeMap}, 1809 }, 1810 }, 1811 1812 State: &terraform.InstanceState{ 1813 ID: "id", 1814 Attributes: map[string]string{ 1815 "config_vars.#": "1", 1816 "config_vars.0.%": "1", 1817 "config_vars.0.foo": "bar", 1818 }, 1819 }, 1820 1821 Config: map[string]interface{}{ 1822 "config_vars": []interface{}{ 1823 map[string]interface{}{ 1824 "bar": "baz", 1825 }, 1826 }, 1827 }, 1828 1829 Diff: &terraform.InstanceDiff{ 1830 Attributes: map[string]*terraform.ResourceAttrDiff{ 1831 "config_vars.0.foo": &terraform.ResourceAttrDiff{ 1832 Old: "bar", 1833 NewRemoved: true, 1834 }, 1835 "config_vars.0.bar": &terraform.ResourceAttrDiff{ 1836 Old: "", 1837 New: "baz", 1838 }, 1839 }, 1840 }, 1841 1842 Err: false, 1843 }, 1844 1845 { 1846 Name: "Maps-6", 1847 Schema: map[string]*Schema{ 1848 "config_vars": &Schema{ 1849 Type: TypeList, 1850 Elem: &Schema{Type: TypeMap}, 1851 Optional: true, 1852 }, 1853 }, 1854 1855 State: &terraform.InstanceState{ 1856 ID: "id", 1857 Attributes: map[string]string{ 1858 "config_vars.#": "1", 1859 "config_vars.0.%": "2", 1860 "config_vars.0.foo": "bar", 1861 "config_vars.0.bar": "baz", 1862 }, 1863 }, 1864 1865 Config: map[string]interface{}{}, 1866 1867 Diff: &terraform.InstanceDiff{ 1868 Attributes: map[string]*terraform.ResourceAttrDiff{ 1869 "config_vars.#": &terraform.ResourceAttrDiff{ 1870 Old: "1", 1871 New: "0", 1872 }, 1873 "config_vars.0.%": &terraform.ResourceAttrDiff{ 1874 Old: "2", 1875 New: "0", 1876 }, 1877 "config_vars.0.foo": &terraform.ResourceAttrDiff{ 1878 Old: "bar", 1879 NewRemoved: true, 1880 }, 1881 "config_vars.0.bar": &terraform.ResourceAttrDiff{ 1882 Old: "baz", 1883 NewRemoved: true, 1884 }, 1885 }, 1886 }, 1887 1888 Err: false, 1889 }, 1890 1891 { 1892 Name: "ForceNews", 1893 Schema: map[string]*Schema{ 1894 "availability_zone": &Schema{ 1895 Type: TypeString, 1896 Optional: true, 1897 ForceNew: true, 1898 }, 1899 1900 "address": &Schema{ 1901 Type: TypeString, 1902 Optional: true, 1903 Computed: true, 1904 }, 1905 }, 1906 1907 State: &terraform.InstanceState{ 1908 ID: "id", 1909 Attributes: map[string]string{ 1910 "availability_zone": "bar", 1911 "address": "foo", 1912 }, 1913 }, 1914 1915 Config: map[string]interface{}{ 1916 "availability_zone": "foo", 1917 }, 1918 1919 Diff: &terraform.InstanceDiff{ 1920 Attributes: map[string]*terraform.ResourceAttrDiff{ 1921 "availability_zone": &terraform.ResourceAttrDiff{ 1922 Old: "bar", 1923 New: "foo", 1924 RequiresNew: true, 1925 }, 1926 }, 1927 }, 1928 1929 Err: false, 1930 }, 1931 1932 { 1933 Name: "Set-10", 1934 Schema: map[string]*Schema{ 1935 "availability_zone": &Schema{ 1936 Type: TypeString, 1937 Optional: true, 1938 ForceNew: true, 1939 }, 1940 1941 "ports": &Schema{ 1942 Type: TypeSet, 1943 Optional: true, 1944 Computed: true, 1945 Elem: &Schema{Type: TypeInt}, 1946 Set: func(a interface{}) int { 1947 return a.(int) 1948 }, 1949 }, 1950 }, 1951 1952 State: &terraform.InstanceState{ 1953 ID: "id", 1954 Attributes: map[string]string{ 1955 "availability_zone": "bar", 1956 "ports.#": "1", 1957 "ports.80": "80", 1958 }, 1959 }, 1960 1961 Config: map[string]interface{}{ 1962 "availability_zone": "foo", 1963 }, 1964 1965 Diff: &terraform.InstanceDiff{ 1966 Attributes: map[string]*terraform.ResourceAttrDiff{ 1967 "availability_zone": &terraform.ResourceAttrDiff{ 1968 Old: "bar", 1969 New: "foo", 1970 RequiresNew: true, 1971 }, 1972 }, 1973 }, 1974 1975 Err: false, 1976 }, 1977 1978 { 1979 Name: "Set-11", 1980 Schema: map[string]*Schema{ 1981 "instances": &Schema{ 1982 Type: TypeSet, 1983 Elem: &Schema{Type: TypeString}, 1984 Optional: true, 1985 Computed: true, 1986 Set: func(v interface{}) int { 1987 return len(v.(string)) 1988 }, 1989 }, 1990 }, 1991 1992 State: &terraform.InstanceState{ 1993 ID: "id", 1994 Attributes: map[string]string{ 1995 "instances.#": "0", 1996 }, 1997 }, 1998 1999 Config: map[string]interface{}{ 2000 "instances": []interface{}{hcl2shim.UnknownVariableValue}, 2001 }, 2002 2003 Diff: &terraform.InstanceDiff{ 2004 Attributes: map[string]*terraform.ResourceAttrDiff{ 2005 "instances.#": &terraform.ResourceAttrDiff{ 2006 NewComputed: true, 2007 }, 2008 }, 2009 }, 2010 2011 Err: false, 2012 }, 2013 2014 { 2015 Name: "Set-12", 2016 Schema: map[string]*Schema{ 2017 "route": &Schema{ 2018 Type: TypeSet, 2019 Optional: true, 2020 Elem: &Resource{ 2021 Schema: map[string]*Schema{ 2022 "index": &Schema{ 2023 Type: TypeInt, 2024 Required: true, 2025 }, 2026 2027 "gateway": &Schema{ 2028 Type: TypeString, 2029 Optional: true, 2030 }, 2031 }, 2032 }, 2033 Set: func(v interface{}) int { 2034 m := v.(map[string]interface{}) 2035 return m["index"].(int) 2036 }, 2037 }, 2038 }, 2039 2040 State: nil, 2041 2042 Config: map[string]interface{}{ 2043 "route": []interface{}{ 2044 map[string]interface{}{ 2045 "index": "1", 2046 "gateway": hcl2shim.UnknownVariableValue, 2047 }, 2048 }, 2049 }, 2050 2051 Diff: &terraform.InstanceDiff{ 2052 Attributes: map[string]*terraform.ResourceAttrDiff{ 2053 "route.#": &terraform.ResourceAttrDiff{ 2054 Old: "0", 2055 New: "1", 2056 }, 2057 "route.~1.index": &terraform.ResourceAttrDiff{ 2058 Old: "", 2059 New: "1", 2060 }, 2061 "route.~1.gateway": &terraform.ResourceAttrDiff{ 2062 Old: "", 2063 New: hcl2shim.UnknownVariableValue, 2064 NewComputed: true, 2065 }, 2066 }, 2067 }, 2068 2069 Err: false, 2070 }, 2071 2072 { 2073 Name: "Set-13", 2074 Schema: map[string]*Schema{ 2075 "route": &Schema{ 2076 Type: TypeSet, 2077 Optional: true, 2078 Elem: &Resource{ 2079 Schema: map[string]*Schema{ 2080 "index": &Schema{ 2081 Type: TypeInt, 2082 Required: true, 2083 }, 2084 2085 "gateway": &Schema{ 2086 Type: TypeSet, 2087 Optional: true, 2088 Elem: &Schema{Type: TypeInt}, 2089 Set: func(a interface{}) int { 2090 return a.(int) 2091 }, 2092 }, 2093 }, 2094 }, 2095 Set: func(v interface{}) int { 2096 m := v.(map[string]interface{}) 2097 return m["index"].(int) 2098 }, 2099 }, 2100 }, 2101 2102 State: nil, 2103 2104 Config: map[string]interface{}{ 2105 "route": []interface{}{ 2106 map[string]interface{}{ 2107 "index": "1", 2108 "gateway": []interface{}{ 2109 hcl2shim.UnknownVariableValue, 2110 }, 2111 }, 2112 }, 2113 }, 2114 2115 Diff: &terraform.InstanceDiff{ 2116 Attributes: map[string]*terraform.ResourceAttrDiff{ 2117 "route.#": &terraform.ResourceAttrDiff{ 2118 Old: "0", 2119 New: "1", 2120 }, 2121 "route.~1.index": &terraform.ResourceAttrDiff{ 2122 Old: "", 2123 New: "1", 2124 }, 2125 "route.~1.gateway.#": &terraform.ResourceAttrDiff{ 2126 NewComputed: true, 2127 }, 2128 }, 2129 }, 2130 2131 Err: false, 2132 }, 2133 2134 { 2135 Name: "Computed maps", 2136 Schema: map[string]*Schema{ 2137 "vars": &Schema{ 2138 Type: TypeMap, 2139 Computed: true, 2140 }, 2141 }, 2142 2143 State: nil, 2144 2145 Config: nil, 2146 2147 Diff: &terraform.InstanceDiff{ 2148 Attributes: map[string]*terraform.ResourceAttrDiff{ 2149 "vars.%": &terraform.ResourceAttrDiff{ 2150 Old: "", 2151 NewComputed: true, 2152 }, 2153 }, 2154 }, 2155 2156 Err: false, 2157 }, 2158 2159 { 2160 Name: "Computed maps", 2161 Schema: map[string]*Schema{ 2162 "vars": &Schema{ 2163 Type: TypeMap, 2164 Computed: true, 2165 }, 2166 }, 2167 2168 State: &terraform.InstanceState{ 2169 ID: "id", 2170 Attributes: map[string]string{ 2171 "vars.%": "0", 2172 }, 2173 }, 2174 2175 Config: map[string]interface{}{ 2176 "vars": map[string]interface{}{ 2177 "bar": hcl2shim.UnknownVariableValue, 2178 }, 2179 }, 2180 2181 Diff: &terraform.InstanceDiff{ 2182 Attributes: map[string]*terraform.ResourceAttrDiff{ 2183 "vars.%": &terraform.ResourceAttrDiff{ 2184 Old: "", 2185 NewComputed: true, 2186 }, 2187 }, 2188 }, 2189 2190 Err: false, 2191 }, 2192 2193 { 2194 Name: "Empty", 2195 Schema: map[string]*Schema{}, 2196 2197 State: &terraform.InstanceState{}, 2198 2199 Config: map[string]interface{}{}, 2200 2201 Diff: nil, 2202 2203 Err: false, 2204 }, 2205 2206 { 2207 Name: "Float", 2208 Schema: map[string]*Schema{ 2209 "some_threshold": &Schema{ 2210 Type: TypeFloat, 2211 }, 2212 }, 2213 2214 State: &terraform.InstanceState{ 2215 ID: "id", 2216 Attributes: map[string]string{ 2217 "some_threshold": "567.8", 2218 }, 2219 }, 2220 2221 Config: map[string]interface{}{ 2222 "some_threshold": 12.34, 2223 }, 2224 2225 Diff: &terraform.InstanceDiff{ 2226 Attributes: map[string]*terraform.ResourceAttrDiff{ 2227 "some_threshold": &terraform.ResourceAttrDiff{ 2228 Old: "567.8", 2229 New: "12.34", 2230 }, 2231 }, 2232 }, 2233 2234 Err: false, 2235 }, 2236 2237 { 2238 Name: "https://github.com/terramate-io/tf/issues/824", 2239 Schema: map[string]*Schema{ 2240 "block_device": &Schema{ 2241 Type: TypeSet, 2242 Optional: true, 2243 Computed: true, 2244 Elem: &Resource{ 2245 Schema: map[string]*Schema{ 2246 "device_name": &Schema{ 2247 Type: TypeString, 2248 Required: true, 2249 }, 2250 "delete_on_termination": &Schema{ 2251 Type: TypeBool, 2252 Optional: true, 2253 Default: true, 2254 }, 2255 }, 2256 }, 2257 Set: func(v interface{}) int { 2258 var buf bytes.Buffer 2259 m := v.(map[string]interface{}) 2260 buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) 2261 buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool))) 2262 return hashcode.String(buf.String()) 2263 }, 2264 }, 2265 }, 2266 2267 State: &terraform.InstanceState{ 2268 ID: "id", 2269 Attributes: map[string]string{ 2270 "block_device.#": "2", 2271 "block_device.616397234.delete_on_termination": "true", 2272 "block_device.616397234.device_name": "/dev/sda1", 2273 "block_device.2801811477.delete_on_termination": "true", 2274 "block_device.2801811477.device_name": "/dev/sdx", 2275 }, 2276 }, 2277 2278 Config: map[string]interface{}{ 2279 "block_device": []interface{}{ 2280 map[string]interface{}{ 2281 "device_name": "/dev/sda1", 2282 }, 2283 map[string]interface{}{ 2284 "device_name": "/dev/sdx", 2285 }, 2286 }, 2287 }, 2288 Diff: nil, 2289 Err: false, 2290 }, 2291 2292 { 2293 Name: "Zero value in state shouldn't result in diff", 2294 Schema: map[string]*Schema{ 2295 "port": &Schema{ 2296 Type: TypeBool, 2297 Optional: true, 2298 ForceNew: true, 2299 }, 2300 }, 2301 2302 State: &terraform.InstanceState{ 2303 ID: "id", 2304 Attributes: map[string]string{ 2305 "port": "false", 2306 }, 2307 }, 2308 2309 Config: map[string]interface{}{}, 2310 2311 Diff: nil, 2312 2313 Err: false, 2314 }, 2315 2316 { 2317 Name: "Same as prev, but for sets", 2318 Schema: map[string]*Schema{ 2319 "route": &Schema{ 2320 Type: TypeSet, 2321 Optional: true, 2322 Elem: &Resource{ 2323 Schema: map[string]*Schema{ 2324 "index": &Schema{ 2325 Type: TypeInt, 2326 Required: true, 2327 }, 2328 2329 "gateway": &Schema{ 2330 Type: TypeSet, 2331 Optional: true, 2332 Elem: &Schema{Type: TypeInt}, 2333 Set: func(a interface{}) int { 2334 return a.(int) 2335 }, 2336 }, 2337 }, 2338 }, 2339 Set: func(v interface{}) int { 2340 m := v.(map[string]interface{}) 2341 return m["index"].(int) 2342 }, 2343 }, 2344 }, 2345 2346 State: &terraform.InstanceState{ 2347 ID: "id", 2348 Attributes: map[string]string{ 2349 "route.#": "0", 2350 }, 2351 }, 2352 2353 Config: map[string]interface{}{}, 2354 2355 Diff: nil, 2356 2357 Err: false, 2358 }, 2359 2360 { 2361 Name: "A set computed element shouldn't cause a diff", 2362 Schema: map[string]*Schema{ 2363 "active": &Schema{ 2364 Type: TypeBool, 2365 Computed: true, 2366 ForceNew: true, 2367 }, 2368 }, 2369 2370 State: &terraform.InstanceState{ 2371 ID: "id", 2372 Attributes: map[string]string{ 2373 "active": "true", 2374 }, 2375 }, 2376 2377 Config: map[string]interface{}{}, 2378 2379 Diff: nil, 2380 2381 Err: false, 2382 }, 2383 2384 { 2385 Name: "An empty set should show up in the diff", 2386 Schema: map[string]*Schema{ 2387 "instances": &Schema{ 2388 Type: TypeSet, 2389 Elem: &Schema{Type: TypeString}, 2390 Optional: true, 2391 ForceNew: true, 2392 Set: func(v interface{}) int { 2393 return len(v.(string)) 2394 }, 2395 }, 2396 }, 2397 2398 State: &terraform.InstanceState{ 2399 ID: "id", 2400 Attributes: map[string]string{ 2401 "instances.#": "1", 2402 "instances.3": "foo", 2403 }, 2404 }, 2405 2406 Config: map[string]interface{}{}, 2407 2408 Diff: &terraform.InstanceDiff{ 2409 Attributes: map[string]*terraform.ResourceAttrDiff{ 2410 "instances.#": &terraform.ResourceAttrDiff{ 2411 Old: "1", 2412 New: "0", 2413 RequiresNew: true, 2414 }, 2415 "instances.3": &terraform.ResourceAttrDiff{ 2416 Old: "foo", 2417 New: "", 2418 NewRemoved: true, 2419 RequiresNew: true, 2420 }, 2421 }, 2422 }, 2423 2424 Err: false, 2425 }, 2426 2427 { 2428 Name: "Map with empty value", 2429 Schema: map[string]*Schema{ 2430 "vars": &Schema{ 2431 Type: TypeMap, 2432 }, 2433 }, 2434 2435 State: nil, 2436 2437 Config: map[string]interface{}{ 2438 "vars": map[string]interface{}{ 2439 "foo": "", 2440 }, 2441 }, 2442 2443 Diff: &terraform.InstanceDiff{ 2444 Attributes: map[string]*terraform.ResourceAttrDiff{ 2445 "vars.%": &terraform.ResourceAttrDiff{ 2446 Old: "0", 2447 New: "1", 2448 }, 2449 "vars.foo": &terraform.ResourceAttrDiff{ 2450 Old: "", 2451 New: "", 2452 }, 2453 }, 2454 }, 2455 2456 Err: false, 2457 }, 2458 2459 { 2460 Name: "Unset bool, not in state", 2461 Schema: map[string]*Schema{ 2462 "force": &Schema{ 2463 Type: TypeBool, 2464 Optional: true, 2465 ForceNew: true, 2466 }, 2467 }, 2468 2469 State: nil, 2470 2471 Config: map[string]interface{}{}, 2472 2473 Diff: nil, 2474 2475 Err: false, 2476 }, 2477 2478 { 2479 Name: "Unset set, not in state", 2480 Schema: map[string]*Schema{ 2481 "metadata_keys": &Schema{ 2482 Type: TypeSet, 2483 Optional: true, 2484 ForceNew: true, 2485 Elem: &Schema{Type: TypeInt}, 2486 Set: func(interface{}) int { return 0 }, 2487 }, 2488 }, 2489 2490 State: nil, 2491 2492 Config: map[string]interface{}{}, 2493 2494 Diff: nil, 2495 2496 Err: false, 2497 }, 2498 2499 { 2500 Name: "Unset list in state, should not show up computed", 2501 Schema: map[string]*Schema{ 2502 "metadata_keys": &Schema{ 2503 Type: TypeList, 2504 Optional: true, 2505 Computed: true, 2506 ForceNew: true, 2507 Elem: &Schema{Type: TypeInt}, 2508 }, 2509 }, 2510 2511 State: &terraform.InstanceState{ 2512 ID: "id", 2513 Attributes: map[string]string{ 2514 "metadata_keys.#": "0", 2515 }, 2516 }, 2517 2518 Config: map[string]interface{}{}, 2519 2520 Diff: nil, 2521 2522 Err: false, 2523 }, 2524 2525 { 2526 Name: "Computed map without config that's known to be empty does not generate diff", 2527 Schema: map[string]*Schema{ 2528 "tags": &Schema{ 2529 Type: TypeMap, 2530 Computed: true, 2531 }, 2532 }, 2533 2534 Config: nil, 2535 2536 State: &terraform.InstanceState{ 2537 ID: "id", 2538 Attributes: map[string]string{ 2539 "tags.%": "0", 2540 }, 2541 }, 2542 2543 Diff: nil, 2544 2545 Err: false, 2546 }, 2547 2548 { 2549 Name: "Set with hyphen keys", 2550 Schema: map[string]*Schema{ 2551 "route": &Schema{ 2552 Type: TypeSet, 2553 Optional: true, 2554 Elem: &Resource{ 2555 Schema: map[string]*Schema{ 2556 "index": &Schema{ 2557 Type: TypeInt, 2558 Required: true, 2559 }, 2560 2561 "gateway-name": &Schema{ 2562 Type: TypeString, 2563 Optional: true, 2564 }, 2565 }, 2566 }, 2567 Set: func(v interface{}) int { 2568 m := v.(map[string]interface{}) 2569 return m["index"].(int) 2570 }, 2571 }, 2572 }, 2573 2574 State: nil, 2575 2576 Config: map[string]interface{}{ 2577 "route": []interface{}{ 2578 map[string]interface{}{ 2579 "index": "1", 2580 "gateway-name": "hello", 2581 }, 2582 }, 2583 }, 2584 2585 Diff: &terraform.InstanceDiff{ 2586 Attributes: map[string]*terraform.ResourceAttrDiff{ 2587 "route.#": &terraform.ResourceAttrDiff{ 2588 Old: "0", 2589 New: "1", 2590 }, 2591 "route.1.index": &terraform.ResourceAttrDiff{ 2592 Old: "", 2593 New: "1", 2594 }, 2595 "route.1.gateway-name": &terraform.ResourceAttrDiff{ 2596 Old: "", 2597 New: "hello", 2598 }, 2599 }, 2600 }, 2601 2602 Err: false, 2603 }, 2604 2605 { 2606 Name: "StateFunc in nested set (#1759)", 2607 Schema: map[string]*Schema{ 2608 "service_account": &Schema{ 2609 Type: TypeList, 2610 Optional: true, 2611 ForceNew: true, 2612 Elem: &Resource{ 2613 Schema: map[string]*Schema{ 2614 "scopes": &Schema{ 2615 Type: TypeSet, 2616 Required: true, 2617 ForceNew: true, 2618 Elem: &Schema{ 2619 Type: TypeString, 2620 StateFunc: func(v interface{}) string { 2621 return v.(string) + "!" 2622 }, 2623 }, 2624 Set: func(v interface{}) int { 2625 i, err := strconv.Atoi(v.(string)) 2626 if err != nil { 2627 t.Fatalf("err: %s", err) 2628 } 2629 return i 2630 }, 2631 }, 2632 }, 2633 }, 2634 }, 2635 }, 2636 2637 State: nil, 2638 2639 Config: map[string]interface{}{ 2640 "service_account": []interface{}{ 2641 map[string]interface{}{ 2642 "scopes": []interface{}{"123"}, 2643 }, 2644 }, 2645 }, 2646 2647 Diff: &terraform.InstanceDiff{ 2648 Attributes: map[string]*terraform.ResourceAttrDiff{ 2649 "service_account.#": &terraform.ResourceAttrDiff{ 2650 Old: "0", 2651 New: "1", 2652 RequiresNew: true, 2653 }, 2654 "service_account.0.scopes.#": &terraform.ResourceAttrDiff{ 2655 Old: "0", 2656 New: "1", 2657 RequiresNew: true, 2658 }, 2659 "service_account.0.scopes.123": &terraform.ResourceAttrDiff{ 2660 Old: "", 2661 New: "123!", 2662 NewExtra: "123", 2663 RequiresNew: true, 2664 }, 2665 }, 2666 }, 2667 2668 Err: false, 2669 }, 2670 2671 { 2672 Name: "Removing set elements", 2673 Schema: map[string]*Schema{ 2674 "instances": &Schema{ 2675 Type: TypeSet, 2676 Elem: &Schema{Type: TypeString}, 2677 Optional: true, 2678 ForceNew: true, 2679 Set: func(v interface{}) int { 2680 return len(v.(string)) 2681 }, 2682 }, 2683 }, 2684 2685 State: &terraform.InstanceState{ 2686 ID: "id", 2687 Attributes: map[string]string{ 2688 "instances.#": "2", 2689 "instances.3": "333", 2690 "instances.2": "22", 2691 }, 2692 }, 2693 2694 Config: map[string]interface{}{ 2695 "instances": []interface{}{"333", "4444"}, 2696 }, 2697 2698 Diff: &terraform.InstanceDiff{ 2699 Attributes: map[string]*terraform.ResourceAttrDiff{ 2700 "instances.2": &terraform.ResourceAttrDiff{ 2701 Old: "22", 2702 New: "", 2703 NewRemoved: true, 2704 RequiresNew: true, 2705 }, 2706 "instances.3": &terraform.ResourceAttrDiff{ 2707 Old: "333", 2708 New: "333", 2709 }, 2710 "instances.4": &terraform.ResourceAttrDiff{ 2711 Old: "", 2712 New: "4444", 2713 RequiresNew: true, 2714 }, 2715 }, 2716 }, 2717 2718 Err: false, 2719 }, 2720 2721 { 2722 Name: "Bools can be set with 0/1 in config, still get true/false", 2723 Schema: map[string]*Schema{ 2724 "one": &Schema{ 2725 Type: TypeBool, 2726 Optional: true, 2727 }, 2728 "two": &Schema{ 2729 Type: TypeBool, 2730 Optional: true, 2731 }, 2732 "three": &Schema{ 2733 Type: TypeBool, 2734 Optional: true, 2735 }, 2736 }, 2737 2738 State: &terraform.InstanceState{ 2739 ID: "id", 2740 Attributes: map[string]string{ 2741 "one": "false", 2742 "two": "true", 2743 "three": "true", 2744 }, 2745 }, 2746 2747 Config: map[string]interface{}{ 2748 "one": "1", 2749 "two": "0", 2750 }, 2751 2752 Diff: &terraform.InstanceDiff{ 2753 Attributes: map[string]*terraform.ResourceAttrDiff{ 2754 "one": &terraform.ResourceAttrDiff{ 2755 Old: "false", 2756 New: "true", 2757 }, 2758 "two": &terraform.ResourceAttrDiff{ 2759 Old: "true", 2760 New: "false", 2761 }, 2762 "three": &terraform.ResourceAttrDiff{ 2763 Old: "true", 2764 New: "false", 2765 NewRemoved: true, 2766 }, 2767 }, 2768 }, 2769 2770 Err: false, 2771 }, 2772 2773 { 2774 Name: "tainted in state w/ no attr changes is still a replacement", 2775 Schema: map[string]*Schema{}, 2776 2777 State: &terraform.InstanceState{ 2778 ID: "id", 2779 Attributes: map[string]string{ 2780 "id": "someid", 2781 }, 2782 Tainted: true, 2783 }, 2784 2785 Config: map[string]interface{}{}, 2786 2787 Diff: &terraform.InstanceDiff{ 2788 Attributes: map[string]*terraform.ResourceAttrDiff{}, 2789 DestroyTainted: true, 2790 }, 2791 }, 2792 2793 { 2794 Name: "Set ForceNew only marks the changing element as ForceNew", 2795 Schema: map[string]*Schema{ 2796 "ports": &Schema{ 2797 Type: TypeSet, 2798 Required: true, 2799 ForceNew: true, 2800 Elem: &Schema{Type: TypeInt}, 2801 Set: func(a interface{}) int { 2802 return a.(int) 2803 }, 2804 }, 2805 }, 2806 2807 State: &terraform.InstanceState{ 2808 ID: "id", 2809 Attributes: map[string]string{ 2810 "ports.#": "3", 2811 "ports.1": "1", 2812 "ports.2": "2", 2813 "ports.4": "4", 2814 }, 2815 }, 2816 2817 Config: map[string]interface{}{ 2818 "ports": []interface{}{5, 2, 1}, 2819 }, 2820 2821 Diff: &terraform.InstanceDiff{ 2822 Attributes: map[string]*terraform.ResourceAttrDiff{ 2823 "ports.1": &terraform.ResourceAttrDiff{ 2824 Old: "1", 2825 New: "1", 2826 }, 2827 "ports.2": &terraform.ResourceAttrDiff{ 2828 Old: "2", 2829 New: "2", 2830 }, 2831 "ports.5": &terraform.ResourceAttrDiff{ 2832 Old: "", 2833 New: "5", 2834 RequiresNew: true, 2835 }, 2836 "ports.4": &terraform.ResourceAttrDiff{ 2837 Old: "4", 2838 New: "0", 2839 NewRemoved: true, 2840 RequiresNew: true, 2841 }, 2842 }, 2843 }, 2844 }, 2845 2846 { 2847 Name: "removed optional items should trigger ForceNew", 2848 Schema: map[string]*Schema{ 2849 "description": &Schema{ 2850 Type: TypeString, 2851 ForceNew: true, 2852 Optional: true, 2853 }, 2854 }, 2855 2856 State: &terraform.InstanceState{ 2857 ID: "id", 2858 Attributes: map[string]string{ 2859 "description": "foo", 2860 }, 2861 }, 2862 2863 Config: map[string]interface{}{}, 2864 2865 Diff: &terraform.InstanceDiff{ 2866 Attributes: map[string]*terraform.ResourceAttrDiff{ 2867 "description": &terraform.ResourceAttrDiff{ 2868 Old: "foo", 2869 New: "", 2870 RequiresNew: true, 2871 NewRemoved: true, 2872 }, 2873 }, 2874 }, 2875 2876 Err: false, 2877 }, 2878 2879 // GH-7715 2880 { 2881 Name: "computed value for boolean field", 2882 Schema: map[string]*Schema{ 2883 "foo": &Schema{ 2884 Type: TypeBool, 2885 ForceNew: true, 2886 Computed: true, 2887 Optional: true, 2888 }, 2889 }, 2890 2891 State: &terraform.InstanceState{ 2892 ID: "id", 2893 }, 2894 2895 Config: map[string]interface{}{ 2896 "foo": hcl2shim.UnknownVariableValue, 2897 }, 2898 2899 Diff: &terraform.InstanceDiff{ 2900 Attributes: map[string]*terraform.ResourceAttrDiff{ 2901 "foo": &terraform.ResourceAttrDiff{ 2902 Old: "", 2903 New: "false", 2904 NewComputed: true, 2905 RequiresNew: true, 2906 }, 2907 }, 2908 }, 2909 2910 Err: false, 2911 }, 2912 2913 { 2914 Name: "Set ForceNew marks count as ForceNew if computed", 2915 Schema: map[string]*Schema{ 2916 "ports": &Schema{ 2917 Type: TypeSet, 2918 Required: true, 2919 ForceNew: true, 2920 Elem: &Schema{Type: TypeInt}, 2921 Set: func(a interface{}) int { 2922 return a.(int) 2923 }, 2924 }, 2925 }, 2926 2927 State: &terraform.InstanceState{ 2928 ID: "id", 2929 Attributes: map[string]string{ 2930 "ports.#": "3", 2931 "ports.1": "1", 2932 "ports.2": "2", 2933 "ports.4": "4", 2934 }, 2935 }, 2936 2937 Config: map[string]interface{}{ 2938 "ports": []interface{}{hcl2shim.UnknownVariableValue, 2, 1}, 2939 }, 2940 2941 Diff: &terraform.InstanceDiff{ 2942 Attributes: map[string]*terraform.ResourceAttrDiff{ 2943 "ports.#": &terraform.ResourceAttrDiff{ 2944 NewComputed: true, 2945 RequiresNew: true, 2946 }, 2947 }, 2948 }, 2949 }, 2950 2951 { 2952 Name: "List with computed schema and ForceNew", 2953 Schema: map[string]*Schema{ 2954 "config": &Schema{ 2955 Type: TypeList, 2956 Optional: true, 2957 ForceNew: true, 2958 Elem: &Schema{ 2959 Type: TypeString, 2960 }, 2961 }, 2962 }, 2963 2964 State: &terraform.InstanceState{ 2965 ID: "id", 2966 Attributes: map[string]string{ 2967 "config.#": "2", 2968 "config.0": "a", 2969 "config.1": "b", 2970 }, 2971 }, 2972 2973 Config: map[string]interface{}{ 2974 "config": []interface{}{hcl2shim.UnknownVariableValue, hcl2shim.UnknownVariableValue}, 2975 }, 2976 2977 Diff: &terraform.InstanceDiff{ 2978 Attributes: map[string]*terraform.ResourceAttrDiff{ 2979 "config.#": &terraform.ResourceAttrDiff{ 2980 Old: "2", 2981 New: "", 2982 RequiresNew: true, 2983 NewComputed: true, 2984 }, 2985 }, 2986 }, 2987 2988 Err: false, 2989 }, 2990 2991 { 2992 Name: "overridden diff with a CustomizeDiff function, ForceNew not in schema", 2993 Schema: map[string]*Schema{ 2994 "availability_zone": &Schema{ 2995 Type: TypeString, 2996 Optional: true, 2997 Computed: true, 2998 }, 2999 }, 3000 3001 State: nil, 3002 3003 Config: map[string]interface{}{ 3004 "availability_zone": "foo", 3005 }, 3006 3007 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 3008 if err := d.SetNew("availability_zone", "bar"); err != nil { 3009 return err 3010 } 3011 if err := d.ForceNew("availability_zone"); err != nil { 3012 return err 3013 } 3014 return nil 3015 }, 3016 3017 Diff: &terraform.InstanceDiff{ 3018 Attributes: map[string]*terraform.ResourceAttrDiff{ 3019 "availability_zone": &terraform.ResourceAttrDiff{ 3020 Old: "", 3021 New: "bar", 3022 RequiresNew: true, 3023 }, 3024 }, 3025 }, 3026 3027 Err: false, 3028 }, 3029 3030 { 3031 // NOTE: This case is technically impossible in the current 3032 // implementation, because optional+computed values never show up in the 3033 // diff. In the event behavior changes this test should ensure that the 3034 // intended diff still shows up. 3035 Name: "overridden removed attribute diff with a CustomizeDiff function, ForceNew not in schema", 3036 Schema: map[string]*Schema{ 3037 "availability_zone": &Schema{ 3038 Type: TypeString, 3039 Optional: true, 3040 Computed: true, 3041 }, 3042 }, 3043 3044 State: nil, 3045 3046 Config: map[string]interface{}{}, 3047 3048 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 3049 if err := d.SetNew("availability_zone", "bar"); err != nil { 3050 return err 3051 } 3052 if err := d.ForceNew("availability_zone"); err != nil { 3053 return err 3054 } 3055 return nil 3056 }, 3057 3058 Diff: &terraform.InstanceDiff{ 3059 Attributes: map[string]*terraform.ResourceAttrDiff{ 3060 "availability_zone": &terraform.ResourceAttrDiff{ 3061 Old: "", 3062 New: "bar", 3063 RequiresNew: true, 3064 }, 3065 }, 3066 }, 3067 3068 Err: false, 3069 }, 3070 3071 { 3072 3073 Name: "overridden diff with a CustomizeDiff function, ForceNew in schema", 3074 Schema: map[string]*Schema{ 3075 "availability_zone": &Schema{ 3076 Type: TypeString, 3077 Optional: true, 3078 Computed: true, 3079 ForceNew: true, 3080 }, 3081 }, 3082 3083 State: nil, 3084 3085 Config: map[string]interface{}{ 3086 "availability_zone": "foo", 3087 }, 3088 3089 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 3090 if err := d.SetNew("availability_zone", "bar"); err != nil { 3091 return err 3092 } 3093 return nil 3094 }, 3095 3096 Diff: &terraform.InstanceDiff{ 3097 Attributes: map[string]*terraform.ResourceAttrDiff{ 3098 "availability_zone": &terraform.ResourceAttrDiff{ 3099 Old: "", 3100 New: "bar", 3101 RequiresNew: true, 3102 }, 3103 }, 3104 }, 3105 3106 Err: false, 3107 }, 3108 3109 { 3110 Name: "required field with computed diff added with CustomizeDiff function", 3111 Schema: map[string]*Schema{ 3112 "ami_id": &Schema{ 3113 Type: TypeString, 3114 Required: true, 3115 }, 3116 "instance_id": &Schema{ 3117 Type: TypeString, 3118 Computed: true, 3119 }, 3120 }, 3121 3122 State: nil, 3123 3124 Config: map[string]interface{}{ 3125 "ami_id": "foo", 3126 }, 3127 3128 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 3129 if err := d.SetNew("instance_id", "bar"); err != nil { 3130 return err 3131 } 3132 return nil 3133 }, 3134 3135 Diff: &terraform.InstanceDiff{ 3136 Attributes: map[string]*terraform.ResourceAttrDiff{ 3137 "ami_id": &terraform.ResourceAttrDiff{ 3138 Old: "", 3139 New: "foo", 3140 }, 3141 "instance_id": &terraform.ResourceAttrDiff{ 3142 Old: "", 3143 New: "bar", 3144 }, 3145 }, 3146 }, 3147 3148 Err: false, 3149 }, 3150 3151 { 3152 Name: "Set ForceNew only marks the changing element as ForceNew - CustomizeDiffFunc edition", 3153 Schema: map[string]*Schema{ 3154 "ports": &Schema{ 3155 Type: TypeSet, 3156 Optional: true, 3157 Computed: true, 3158 Elem: &Schema{Type: TypeInt}, 3159 Set: func(a interface{}) int { 3160 return a.(int) 3161 }, 3162 }, 3163 }, 3164 3165 State: &terraform.InstanceState{ 3166 ID: "id", 3167 Attributes: map[string]string{ 3168 "ports.#": "3", 3169 "ports.1": "1", 3170 "ports.2": "2", 3171 "ports.4": "4", 3172 }, 3173 }, 3174 3175 Config: map[string]interface{}{ 3176 "ports": []interface{}{5, 2, 6}, 3177 }, 3178 3179 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 3180 if err := d.SetNew("ports", []interface{}{5, 2, 1}); err != nil { 3181 return err 3182 } 3183 if err := d.ForceNew("ports"); err != nil { 3184 return err 3185 } 3186 return nil 3187 }, 3188 3189 Diff: &terraform.InstanceDiff{ 3190 Attributes: map[string]*terraform.ResourceAttrDiff{ 3191 "ports.1": &terraform.ResourceAttrDiff{ 3192 Old: "1", 3193 New: "1", 3194 }, 3195 "ports.2": &terraform.ResourceAttrDiff{ 3196 Old: "2", 3197 New: "2", 3198 }, 3199 "ports.5": &terraform.ResourceAttrDiff{ 3200 Old: "", 3201 New: "5", 3202 RequiresNew: true, 3203 }, 3204 "ports.4": &terraform.ResourceAttrDiff{ 3205 Old: "4", 3206 New: "0", 3207 NewRemoved: true, 3208 RequiresNew: true, 3209 }, 3210 }, 3211 }, 3212 }, 3213 3214 { 3215 Name: "tainted resource does not run CustomizeDiffFunc", 3216 Schema: map[string]*Schema{}, 3217 3218 State: &terraform.InstanceState{ 3219 ID: "someid", 3220 Attributes: map[string]string{ 3221 "id": "someid", 3222 }, 3223 Tainted: true, 3224 }, 3225 3226 Config: map[string]interface{}{}, 3227 3228 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 3229 return errors.New("diff customization should not have run") 3230 }, 3231 3232 Diff: &terraform.InstanceDiff{ 3233 Attributes: map[string]*terraform.ResourceAttrDiff{}, 3234 DestroyTainted: true, 3235 }, 3236 3237 Err: false, 3238 }, 3239 3240 { 3241 Name: "NewComputed based on a conditional with CustomizeDiffFunc", 3242 Schema: map[string]*Schema{ 3243 "etag": &Schema{ 3244 Type: TypeString, 3245 Optional: true, 3246 Computed: true, 3247 }, 3248 "version_id": &Schema{ 3249 Type: TypeString, 3250 Computed: true, 3251 }, 3252 }, 3253 3254 State: &terraform.InstanceState{ 3255 ID: "id", 3256 Attributes: map[string]string{ 3257 "etag": "foo", 3258 "version_id": "1", 3259 }, 3260 }, 3261 3262 Config: map[string]interface{}{ 3263 "etag": "bar", 3264 }, 3265 3266 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 3267 if d.HasChange("etag") { 3268 d.SetNewComputed("version_id") 3269 } 3270 return nil 3271 }, 3272 3273 Diff: &terraform.InstanceDiff{ 3274 Attributes: map[string]*terraform.ResourceAttrDiff{ 3275 "etag": &terraform.ResourceAttrDiff{ 3276 Old: "foo", 3277 New: "bar", 3278 }, 3279 "version_id": &terraform.ResourceAttrDiff{ 3280 Old: "1", 3281 New: "", 3282 NewComputed: true, 3283 }, 3284 }, 3285 }, 3286 3287 Err: false, 3288 }, 3289 3290 { 3291 Name: "vetoing a diff", 3292 Schema: map[string]*Schema{ 3293 "foo": &Schema{ 3294 Type: TypeString, 3295 Optional: true, 3296 Computed: true, 3297 }, 3298 }, 3299 3300 State: &terraform.InstanceState{ 3301 ID: "id", 3302 Attributes: map[string]string{ 3303 "foo": "bar", 3304 }, 3305 }, 3306 3307 Config: map[string]interface{}{ 3308 "foo": "baz", 3309 }, 3310 3311 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 3312 return fmt.Errorf("diff vetoed") 3313 }, 3314 3315 Err: true, 3316 }, 3317 3318 // A lot of resources currently depended on using the empty string as a 3319 // nil/unset value. 3320 { 3321 Name: "optional, computed, empty string", 3322 Schema: map[string]*Schema{ 3323 "attr": &Schema{ 3324 Type: TypeString, 3325 Optional: true, 3326 Computed: true, 3327 }, 3328 }, 3329 3330 State: &terraform.InstanceState{ 3331 ID: "id", 3332 Attributes: map[string]string{ 3333 "attr": "bar", 3334 }, 3335 }, 3336 3337 Config: map[string]interface{}{ 3338 "attr": "", 3339 }, 3340 }, 3341 3342 { 3343 Name: "optional, computed, empty string should not crash in CustomizeDiff", 3344 Schema: map[string]*Schema{ 3345 "unrelated_set": { 3346 Type: TypeSet, 3347 Optional: true, 3348 Elem: &Schema{Type: TypeString}, 3349 }, 3350 "stream_enabled": { 3351 Type: TypeBool, 3352 Optional: true, 3353 }, 3354 "stream_view_type": { 3355 Type: TypeString, 3356 Optional: true, 3357 Computed: true, 3358 }, 3359 }, 3360 3361 State: &terraform.InstanceState{ 3362 ID: "id", 3363 Attributes: map[string]string{ 3364 "unrelated_set.#": "0", 3365 "stream_enabled": "true", 3366 "stream_view_type": "KEYS_ONLY", 3367 }, 3368 }, 3369 Config: map[string]interface{}{ 3370 "stream_enabled": false, 3371 "stream_view_type": "", 3372 }, 3373 CustomizeDiff: func(diff *ResourceDiff, v interface{}) error { 3374 v, ok := diff.GetOk("unrelated_set") 3375 if ok { 3376 return fmt.Errorf("Didn't expect unrelated_set: %#v", v) 3377 } 3378 return nil 3379 }, 3380 Diff: &terraform.InstanceDiff{ 3381 Attributes: map[string]*terraform.ResourceAttrDiff{ 3382 "stream_enabled": { 3383 Old: "true", 3384 New: "false", 3385 }, 3386 }, 3387 }, 3388 }, 3389 } 3390 3391 for i, tc := range cases { 3392 t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { 3393 c := terraform.NewResourceConfigRaw(tc.Config) 3394 3395 { 3396 d, err := schemaMap(tc.Schema).Diff(tc.State, c, tc.CustomizeDiff, nil, false) 3397 if err != nil != tc.Err { 3398 t.Fatalf("err: %s", err) 3399 } 3400 if !cmp.Equal(d, tc.Diff, equateEmpty) { 3401 t.Fatal(cmp.Diff(d, tc.Diff, equateEmpty)) 3402 } 3403 } 3404 // up to here is already tested in helper/schema; we're just 3405 // verify that we haven't broken any tests in transition. 3406 3407 // create a schema from the schemaMap 3408 testSchema := resourceSchemaToBlock(tc.Schema) 3409 3410 // get our initial state cty.Value 3411 stateVal, err := StateValueFromInstanceState(tc.State, testSchema.ImpliedType()) 3412 if err != nil { 3413 t.Fatal(err) 3414 } 3415 3416 // this is the desired cty.Value from the configuration 3417 configVal := hcl2shim.HCL2ValueFromConfigValue(c.Config) 3418 3419 // verify that we can round-trip the config 3420 origConfig := hcl2shim.ConfigValueFromHCL2(configVal) 3421 if !cmp.Equal(c.Config, origConfig, equateEmpty) { 3422 t.Fatal(cmp.Diff(c.Config, origConfig, equateEmpty)) 3423 } 3424 3425 // make sure our config conforms precisely to the schema 3426 configVal, err = testSchema.CoerceValue(configVal) 3427 if err != nil { 3428 t.Fatal(tfdiags.FormatError(err)) 3429 } 3430 3431 // The new API requires returning the desired state rather than a 3432 // diff, so we need to verify that we can combine the state and 3433 // diff and recreate a new state. 3434 3435 // now verify that we can create diff, using the new config and state values 3436 // customize isn't run on tainted resources 3437 tainted := tc.State != nil && tc.State.Tainted 3438 if tainted { 3439 tc.CustomizeDiff = nil 3440 } 3441 3442 res := &Resource{Schema: tc.Schema} 3443 3444 d, err := diffFromValues(stateVal, configVal, res, tc.CustomizeDiff) 3445 if err != nil { 3446 if !tc.Err { 3447 t.Fatal(err) 3448 } 3449 } 3450 3451 // In a real "apply" operation there would be no unknown values, 3452 // so for tests containing unknowns we'll stop here: the steps 3453 // after this point apply only to the apply phase. 3454 if !configVal.IsWhollyKnown() { 3455 return 3456 } 3457 3458 // our diff function can't set DestroyTainted, but match the 3459 // expected value here for the test fixtures 3460 if tainted { 3461 if d == nil { 3462 d = &terraform.InstanceDiff{} 3463 } 3464 d.DestroyTainted = true 3465 } 3466 3467 if eq, _ := d.Same(tc.Diff); !eq { 3468 t.Fatal(cmp.Diff(d, tc.Diff)) 3469 } 3470 3471 }) 3472 } 3473 } 3474 3475 func resourceSchemaToBlock(s map[string]*Schema) *configschema.Block { 3476 return (&Resource{Schema: s}).CoreConfigSchema() 3477 } 3478 3479 func TestRemoveConfigUnknowns(t *testing.T) { 3480 cfg := map[string]interface{}{ 3481 "id": "74D93920-ED26-11E3-AC10-0800200C9A66", 3482 "route_rules": []interface{}{ 3483 map[string]interface{}{ 3484 "cidr_block": "74D93920-ED26-11E3-AC10-0800200C9A66", 3485 "destination": "0.0.0.0/0", 3486 "destination_type": "CIDR_BLOCK", 3487 "network_entity_id": "1", 3488 }, 3489 map[string]interface{}{ 3490 "cidr_block": "74D93920-ED26-11E3-AC10-0800200C9A66", 3491 "destination": "0.0.0.0/0", 3492 "destination_type": "CIDR_BLOCK", 3493 "sub_block": []interface{}{ 3494 map[string]interface{}{ 3495 "computed": "74D93920-ED26-11E3-AC10-0800200C9A66", 3496 }, 3497 }, 3498 }, 3499 }, 3500 } 3501 3502 expect := map[string]interface{}{ 3503 "route_rules": []interface{}{ 3504 map[string]interface{}{ 3505 "destination": "0.0.0.0/0", 3506 "destination_type": "CIDR_BLOCK", 3507 "network_entity_id": "1", 3508 }, 3509 map[string]interface{}{ 3510 "destination": "0.0.0.0/0", 3511 "destination_type": "CIDR_BLOCK", 3512 "sub_block": []interface{}{ 3513 map[string]interface{}{}, 3514 }, 3515 }, 3516 }, 3517 } 3518 3519 removeConfigUnknowns(cfg) 3520 3521 if !reflect.DeepEqual(cfg, expect) { 3522 t.Fatalf("\nexpected: %#v\ngot: %#v", expect, cfg) 3523 } 3524 }