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