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