github.com/hashicorp/terraform-plugin-sdk@v1.17.2/helper/schema/schema_test.go (about) 1 package schema 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "os" 8 "reflect" 9 "sort" 10 "strconv" 11 "strings" 12 "testing" 13 14 "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" 15 "github.com/hashicorp/terraform-plugin-sdk/internal/configs/hcl2shim" 16 "github.com/hashicorp/terraform-plugin-sdk/terraform" 17 ) 18 19 func TestEnvDefaultFunc(t *testing.T) { 20 key := "TF_TEST_ENV_DEFAULT_FUNC" 21 defer os.Unsetenv(key) 22 23 f := EnvDefaultFunc(key, "42") 24 if err := os.Setenv(key, "foo"); err != nil { 25 t.Fatalf("err: %s", err) 26 } 27 28 actual, err := f() 29 if err != nil { 30 t.Fatalf("err: %s", err) 31 } 32 if actual != "foo" { 33 t.Fatalf("bad: %#v", actual) 34 } 35 36 if err := os.Unsetenv(key); err != nil { 37 t.Fatalf("err: %s", err) 38 } 39 40 actual, err = f() 41 if err != nil { 42 t.Fatalf("err: %s", err) 43 } 44 if actual != "42" { 45 t.Fatalf("bad: %#v", actual) 46 } 47 } 48 49 func TestMultiEnvDefaultFunc(t *testing.T) { 50 keys := []string{ 51 "TF_TEST_MULTI_ENV_DEFAULT_FUNC1", 52 "TF_TEST_MULTI_ENV_DEFAULT_FUNC2", 53 } 54 defer func() { 55 for _, k := range keys { 56 os.Unsetenv(k) 57 } 58 }() 59 60 // Test that the first key is returned first 61 f := MultiEnvDefaultFunc(keys, "42") 62 if err := os.Setenv(keys[0], "foo"); err != nil { 63 t.Fatalf("err: %s", err) 64 } 65 66 actual, err := f() 67 if err != nil { 68 t.Fatalf("err: %s", err) 69 } 70 if actual != "foo" { 71 t.Fatalf("bad: %#v", actual) 72 } 73 74 if err := os.Unsetenv(keys[0]); err != nil { 75 t.Fatalf("err: %s", err) 76 } 77 78 // Test that the second key is returned if the first one is empty 79 f = MultiEnvDefaultFunc(keys, "42") 80 if err := os.Setenv(keys[1], "foo"); err != nil { 81 t.Fatalf("err: %s", err) 82 } 83 84 actual, err = f() 85 if err != nil { 86 t.Fatalf("err: %s", err) 87 } 88 if actual != "foo" { 89 t.Fatalf("bad: %#v", actual) 90 } 91 92 if err := os.Unsetenv(keys[1]); err != nil { 93 t.Fatalf("err: %s", err) 94 } 95 96 // Test that the default value is returned when no keys are set 97 actual, err = f() 98 if err != nil { 99 t.Fatalf("err: %s", err) 100 } 101 if actual != "42" { 102 t.Fatalf("bad: %#v", actual) 103 } 104 } 105 106 func TestValueType_Zero(t *testing.T) { 107 cases := []struct { 108 Type ValueType 109 Value interface{} 110 }{ 111 {TypeBool, false}, 112 {TypeInt, 0}, 113 {TypeFloat, 0.0}, 114 {TypeString, ""}, 115 {TypeList, []interface{}{}}, 116 {TypeMap, map[string]interface{}{}}, 117 {TypeSet, new(Set)}, 118 } 119 120 for i, tc := range cases { 121 actual := tc.Type.Zero() 122 if !reflect.DeepEqual(actual, tc.Value) { 123 t.Fatalf("%d: %#v != %#v", i, actual, tc.Value) 124 } 125 } 126 } 127 128 func TestSchemaMap_Diff(t *testing.T) { 129 cases := []struct { 130 Name string 131 Schema map[string]*Schema 132 State *terraform.InstanceState 133 Config map[string]interface{} 134 CustomizeDiff CustomizeDiffFunc 135 Diff *terraform.InstanceDiff 136 Err bool 137 }{ 138 { 139 Schema: map[string]*Schema{ 140 "availability_zone": { 141 Type: TypeString, 142 Optional: true, 143 Computed: true, 144 ForceNew: true, 145 }, 146 }, 147 148 State: nil, 149 150 Config: map[string]interface{}{ 151 "availability_zone": "foo", 152 }, 153 154 Diff: &terraform.InstanceDiff{ 155 Attributes: map[string]*terraform.ResourceAttrDiff{ 156 "availability_zone": { 157 Old: "", 158 New: "foo", 159 RequiresNew: true, 160 }, 161 }, 162 }, 163 164 Err: false, 165 }, 166 167 { 168 Schema: map[string]*Schema{ 169 "availability_zone": { 170 Type: TypeString, 171 Optional: true, 172 Computed: true, 173 ForceNew: true, 174 }, 175 }, 176 177 State: nil, 178 179 Config: map[string]interface{}{}, 180 181 Diff: &terraform.InstanceDiff{ 182 Attributes: map[string]*terraform.ResourceAttrDiff{ 183 "availability_zone": { 184 Old: "", 185 NewComputed: true, 186 RequiresNew: true, 187 }, 188 }, 189 }, 190 191 Err: false, 192 }, 193 194 { 195 Schema: map[string]*Schema{ 196 "availability_zone": { 197 Type: TypeString, 198 Optional: true, 199 Computed: true, 200 ForceNew: true, 201 }, 202 }, 203 204 State: &terraform.InstanceState{ 205 ID: "foo", 206 }, 207 208 Config: map[string]interface{}{}, 209 210 Diff: nil, 211 212 Err: false, 213 }, 214 215 { 216 Name: "Computed, but set in config", 217 Schema: map[string]*Schema{ 218 "availability_zone": { 219 Type: TypeString, 220 Optional: true, 221 Computed: true, 222 }, 223 }, 224 225 State: &terraform.InstanceState{ 226 Attributes: map[string]string{ 227 "availability_zone": "foo", 228 }, 229 }, 230 231 Config: map[string]interface{}{ 232 "availability_zone": "bar", 233 }, 234 235 Diff: &terraform.InstanceDiff{ 236 Attributes: map[string]*terraform.ResourceAttrDiff{ 237 "availability_zone": { 238 Old: "foo", 239 New: "bar", 240 }, 241 }, 242 }, 243 244 Err: false, 245 }, 246 247 { 248 Name: "Default", 249 Schema: map[string]*Schema{ 250 "availability_zone": { 251 Type: TypeString, 252 Optional: true, 253 Default: "foo", 254 }, 255 }, 256 257 State: nil, 258 259 Config: nil, 260 261 Diff: &terraform.InstanceDiff{ 262 Attributes: map[string]*terraform.ResourceAttrDiff{ 263 "availability_zone": { 264 Old: "", 265 New: "foo", 266 }, 267 }, 268 }, 269 270 Err: false, 271 }, 272 273 { 274 Name: "DefaultFunc, value", 275 Schema: map[string]*Schema{ 276 "availability_zone": { 277 Type: TypeString, 278 Optional: true, 279 DefaultFunc: func() (interface{}, error) { 280 return "foo", nil 281 }, 282 }, 283 }, 284 285 State: nil, 286 287 Config: nil, 288 289 Diff: &terraform.InstanceDiff{ 290 Attributes: map[string]*terraform.ResourceAttrDiff{ 291 "availability_zone": { 292 Old: "", 293 New: "foo", 294 }, 295 }, 296 }, 297 298 Err: false, 299 }, 300 301 { 302 Name: "DefaultFunc, configuration set", 303 Schema: map[string]*Schema{ 304 "availability_zone": { 305 Type: TypeString, 306 Optional: true, 307 DefaultFunc: func() (interface{}, error) { 308 return "foo", nil 309 }, 310 }, 311 }, 312 313 State: nil, 314 315 Config: map[string]interface{}{ 316 "availability_zone": "bar", 317 }, 318 319 Diff: &terraform.InstanceDiff{ 320 Attributes: map[string]*terraform.ResourceAttrDiff{ 321 "availability_zone": { 322 Old: "", 323 New: "bar", 324 }, 325 }, 326 }, 327 328 Err: false, 329 }, 330 331 { 332 Name: "String with StateFunc", 333 Schema: map[string]*Schema{ 334 "availability_zone": { 335 Type: TypeString, 336 Optional: true, 337 Computed: true, 338 StateFunc: func(a interface{}) string { 339 return a.(string) + "!" 340 }, 341 }, 342 }, 343 344 State: nil, 345 346 Config: map[string]interface{}{ 347 "availability_zone": "foo", 348 }, 349 350 Diff: &terraform.InstanceDiff{ 351 Attributes: map[string]*terraform.ResourceAttrDiff{ 352 "availability_zone": { 353 Old: "", 354 New: "foo!", 355 NewExtra: "foo", 356 }, 357 }, 358 }, 359 360 Err: false, 361 }, 362 363 { 364 Name: "StateFunc not called with nil value", 365 Schema: map[string]*Schema{ 366 "availability_zone": { 367 Type: TypeString, 368 Optional: true, 369 Computed: true, 370 StateFunc: func(a interface{}) string { 371 t.Fatalf("should not get here!") 372 return "" 373 }, 374 }, 375 }, 376 377 State: nil, 378 379 Config: map[string]interface{}{}, 380 381 Diff: &terraform.InstanceDiff{ 382 Attributes: map[string]*terraform.ResourceAttrDiff{ 383 "availability_zone": { 384 Old: "", 385 New: "", 386 NewComputed: true, 387 }, 388 }, 389 }, 390 391 Err: false, 392 }, 393 394 { 395 Name: "Variable computed", 396 Schema: map[string]*Schema{ 397 "availability_zone": { 398 Type: TypeString, 399 Optional: true, 400 }, 401 }, 402 403 State: nil, 404 405 Config: map[string]interface{}{ 406 "availability_zone": hcl2shim.UnknownVariableValue, 407 }, 408 409 Diff: &terraform.InstanceDiff{ 410 Attributes: map[string]*terraform.ResourceAttrDiff{ 411 "availability_zone": { 412 Old: "", 413 New: hcl2shim.UnknownVariableValue, 414 NewComputed: true, 415 }, 416 }, 417 }, 418 419 Err: false, 420 }, 421 422 { 423 Name: "Int decode", 424 Schema: map[string]*Schema{ 425 "port": { 426 Type: TypeInt, 427 Optional: true, 428 Computed: true, 429 ForceNew: true, 430 }, 431 }, 432 433 State: nil, 434 435 Config: map[string]interface{}{ 436 "port": 27, 437 }, 438 439 Diff: &terraform.InstanceDiff{ 440 Attributes: map[string]*terraform.ResourceAttrDiff{ 441 "port": { 442 Old: "", 443 New: "27", 444 RequiresNew: true, 445 }, 446 }, 447 }, 448 449 Err: false, 450 }, 451 452 { 453 Name: "bool decode", 454 Schema: map[string]*Schema{ 455 "port": { 456 Type: TypeBool, 457 Optional: true, 458 Computed: true, 459 ForceNew: true, 460 }, 461 }, 462 463 State: nil, 464 465 Config: map[string]interface{}{ 466 "port": false, 467 }, 468 469 Diff: &terraform.InstanceDiff{ 470 Attributes: map[string]*terraform.ResourceAttrDiff{ 471 "port": { 472 Old: "", 473 New: "false", 474 RequiresNew: true, 475 }, 476 }, 477 }, 478 479 Err: false, 480 }, 481 482 { 483 Name: "Bool", 484 Schema: map[string]*Schema{ 485 "delete": { 486 Type: TypeBool, 487 Optional: true, 488 Default: false, 489 }, 490 }, 491 492 State: &terraform.InstanceState{ 493 Attributes: map[string]string{ 494 "delete": "false", 495 }, 496 }, 497 498 Config: nil, 499 500 Diff: nil, 501 502 Err: false, 503 }, 504 505 { 506 Name: "List decode", 507 Schema: map[string]*Schema{ 508 "ports": { 509 Type: TypeList, 510 Required: true, 511 Elem: &Schema{Type: TypeInt}, 512 }, 513 }, 514 515 State: nil, 516 517 Config: map[string]interface{}{ 518 "ports": []interface{}{1, 2, 5}, 519 }, 520 521 Diff: &terraform.InstanceDiff{ 522 Attributes: map[string]*terraform.ResourceAttrDiff{ 523 "ports.#": { 524 Old: "0", 525 New: "3", 526 }, 527 "ports.0": { 528 Old: "", 529 New: "1", 530 }, 531 "ports.1": { 532 Old: "", 533 New: "2", 534 }, 535 "ports.2": { 536 Old: "", 537 New: "5", 538 }, 539 }, 540 }, 541 542 Err: false, 543 }, 544 545 { 546 Name: "List decode with promotion", 547 Schema: map[string]*Schema{ 548 "ports": { 549 Type: TypeList, 550 Required: true, 551 Elem: &Schema{Type: TypeInt}, 552 PromoteSingle: true, 553 }, 554 }, 555 556 State: nil, 557 558 Config: map[string]interface{}{ 559 "ports": "5", 560 }, 561 562 Diff: &terraform.InstanceDiff{ 563 Attributes: map[string]*terraform.ResourceAttrDiff{ 564 "ports.#": { 565 Old: "0", 566 New: "1", 567 }, 568 "ports.0": { 569 Old: "", 570 New: "5", 571 }, 572 }, 573 }, 574 575 Err: false, 576 }, 577 578 { 579 Name: "List decode with promotion with list", 580 Schema: map[string]*Schema{ 581 "ports": { 582 Type: TypeList, 583 Required: true, 584 Elem: &Schema{Type: TypeInt}, 585 PromoteSingle: true, 586 }, 587 }, 588 589 State: nil, 590 591 Config: map[string]interface{}{ 592 "ports": []interface{}{"5"}, 593 }, 594 595 Diff: &terraform.InstanceDiff{ 596 Attributes: map[string]*terraform.ResourceAttrDiff{ 597 "ports.#": { 598 Old: "0", 599 New: "1", 600 }, 601 "ports.0": { 602 Old: "", 603 New: "5", 604 }, 605 }, 606 }, 607 608 Err: false, 609 }, 610 611 { 612 Schema: map[string]*Schema{ 613 "ports": { 614 Type: TypeList, 615 Required: true, 616 Elem: &Schema{Type: TypeInt}, 617 }, 618 }, 619 620 State: nil, 621 622 Config: map[string]interface{}{ 623 "ports": []interface{}{1, 2, 5}, 624 }, 625 626 Diff: &terraform.InstanceDiff{ 627 Attributes: map[string]*terraform.ResourceAttrDiff{ 628 "ports.#": { 629 Old: "0", 630 New: "3", 631 }, 632 "ports.0": { 633 Old: "", 634 New: "1", 635 }, 636 "ports.1": { 637 Old: "", 638 New: "2", 639 }, 640 "ports.2": { 641 Old: "", 642 New: "5", 643 }, 644 }, 645 }, 646 647 Err: false, 648 }, 649 650 { 651 Schema: map[string]*Schema{ 652 "ports": { 653 Type: TypeList, 654 Required: true, 655 Elem: &Schema{Type: TypeInt}, 656 }, 657 }, 658 659 State: nil, 660 661 Config: map[string]interface{}{ 662 "ports": []interface{}{1, hcl2shim.UnknownVariableValue, 5}, 663 }, 664 665 Diff: &terraform.InstanceDiff{ 666 Attributes: map[string]*terraform.ResourceAttrDiff{ 667 "ports.#": { 668 Old: "0", 669 New: "", 670 NewComputed: true, 671 }, 672 }, 673 }, 674 675 Err: false, 676 }, 677 678 { 679 Schema: map[string]*Schema{ 680 "ports": { 681 Type: TypeList, 682 Required: true, 683 Elem: &Schema{Type: TypeInt}, 684 }, 685 }, 686 687 State: &terraform.InstanceState{ 688 Attributes: map[string]string{ 689 "ports.#": "3", 690 "ports.0": "1", 691 "ports.1": "2", 692 "ports.2": "5", 693 }, 694 }, 695 696 Config: map[string]interface{}{ 697 "ports": []interface{}{1, 2, 5}, 698 }, 699 700 Diff: nil, 701 702 Err: false, 703 }, 704 705 { 706 Name: "", 707 Schema: map[string]*Schema{ 708 "ports": { 709 Type: TypeList, 710 Required: true, 711 Elem: &Schema{Type: TypeInt}, 712 }, 713 }, 714 715 State: &terraform.InstanceState{ 716 Attributes: map[string]string{ 717 "ports.#": "2", 718 "ports.0": "1", 719 "ports.1": "2", 720 }, 721 }, 722 723 Config: map[string]interface{}{ 724 "ports": []interface{}{1, 2, 5}, 725 }, 726 727 Diff: &terraform.InstanceDiff{ 728 Attributes: map[string]*terraform.ResourceAttrDiff{ 729 "ports.#": { 730 Old: "2", 731 New: "3", 732 }, 733 "ports.2": { 734 Old: "", 735 New: "5", 736 }, 737 }, 738 }, 739 740 Err: false, 741 }, 742 743 { 744 Name: "", 745 Schema: map[string]*Schema{ 746 "ports": { 747 Type: TypeList, 748 Required: true, 749 Elem: &Schema{Type: TypeInt}, 750 ForceNew: true, 751 }, 752 }, 753 754 State: nil, 755 756 Config: map[string]interface{}{ 757 "ports": []interface{}{1, 2, 5}, 758 }, 759 760 Diff: &terraform.InstanceDiff{ 761 Attributes: map[string]*terraform.ResourceAttrDiff{ 762 "ports.#": { 763 Old: "0", 764 New: "3", 765 RequiresNew: true, 766 }, 767 "ports.0": { 768 Old: "", 769 New: "1", 770 RequiresNew: true, 771 }, 772 "ports.1": { 773 Old: "", 774 New: "2", 775 RequiresNew: true, 776 }, 777 "ports.2": { 778 Old: "", 779 New: "5", 780 RequiresNew: true, 781 }, 782 }, 783 }, 784 785 Err: false, 786 }, 787 788 { 789 Name: "", 790 Schema: map[string]*Schema{ 791 "ports": { 792 Type: TypeList, 793 Optional: true, 794 Computed: true, 795 Elem: &Schema{Type: TypeInt}, 796 }, 797 }, 798 799 State: nil, 800 801 Config: map[string]interface{}{}, 802 803 Diff: &terraform.InstanceDiff{ 804 Attributes: map[string]*terraform.ResourceAttrDiff{ 805 "ports.#": { 806 Old: "", 807 NewComputed: true, 808 }, 809 }, 810 }, 811 812 Err: false, 813 }, 814 815 { 816 Name: "List with computed set", 817 Schema: map[string]*Schema{ 818 "config": { 819 Type: TypeList, 820 Optional: true, 821 ForceNew: true, 822 MinItems: 1, 823 Elem: &Resource{ 824 Schema: map[string]*Schema{ 825 "name": { 826 Type: TypeString, 827 Required: true, 828 }, 829 830 "rules": { 831 Type: TypeSet, 832 Computed: true, 833 Elem: &Schema{Type: TypeString}, 834 Set: HashString, 835 }, 836 }, 837 }, 838 }, 839 }, 840 841 State: nil, 842 843 Config: map[string]interface{}{ 844 "config": []interface{}{ 845 map[string]interface{}{ 846 "name": "hello", 847 }, 848 }, 849 }, 850 851 Diff: &terraform.InstanceDiff{ 852 Attributes: map[string]*terraform.ResourceAttrDiff{ 853 "config.#": { 854 Old: "0", 855 New: "1", 856 RequiresNew: true, 857 }, 858 859 "config.0.name": { 860 Old: "", 861 New: "hello", 862 }, 863 864 "config.0.rules.#": { 865 Old: "", 866 NewComputed: true, 867 }, 868 }, 869 }, 870 871 Err: false, 872 }, 873 874 { 875 Name: "Set", 876 Schema: map[string]*Schema{ 877 "ports": { 878 Type: TypeSet, 879 Required: true, 880 Elem: &Schema{Type: TypeInt}, 881 Set: func(a interface{}) int { 882 return a.(int) 883 }, 884 }, 885 }, 886 887 State: nil, 888 889 Config: map[string]interface{}{ 890 "ports": []interface{}{5, 2, 1}, 891 }, 892 893 Diff: &terraform.InstanceDiff{ 894 Attributes: map[string]*terraform.ResourceAttrDiff{ 895 "ports.#": { 896 Old: "0", 897 New: "3", 898 }, 899 "ports.1": { 900 Old: "", 901 New: "1", 902 }, 903 "ports.2": { 904 Old: "", 905 New: "2", 906 }, 907 "ports.5": { 908 Old: "", 909 New: "5", 910 }, 911 }, 912 }, 913 914 Err: false, 915 }, 916 917 { 918 Name: "Set", 919 Schema: map[string]*Schema{ 920 "ports": { 921 Type: TypeSet, 922 Computed: true, 923 Required: true, 924 Elem: &Schema{Type: TypeInt}, 925 Set: func(a interface{}) int { 926 return a.(int) 927 }, 928 }, 929 }, 930 931 State: &terraform.InstanceState{ 932 Attributes: map[string]string{ 933 "ports.#": "0", 934 }, 935 }, 936 937 Config: nil, 938 939 Diff: nil, 940 941 Err: false, 942 }, 943 944 { 945 Name: "Set", 946 Schema: map[string]*Schema{ 947 "ports": { 948 Type: TypeSet, 949 Optional: true, 950 Computed: true, 951 Elem: &Schema{Type: TypeInt}, 952 Set: func(a interface{}) int { 953 return a.(int) 954 }, 955 }, 956 }, 957 958 State: nil, 959 960 Config: nil, 961 962 Diff: &terraform.InstanceDiff{ 963 Attributes: map[string]*terraform.ResourceAttrDiff{ 964 "ports.#": { 965 Old: "", 966 NewComputed: true, 967 }, 968 }, 969 }, 970 971 Err: false, 972 }, 973 974 { 975 Name: "Set", 976 Schema: map[string]*Schema{ 977 "ports": { 978 Type: TypeSet, 979 Required: true, 980 Elem: &Schema{Type: TypeInt}, 981 Set: func(a interface{}) int { 982 return a.(int) 983 }, 984 }, 985 }, 986 987 State: nil, 988 989 Config: map[string]interface{}{ 990 "ports": []interface{}{"2", "5", 1}, 991 }, 992 993 Diff: &terraform.InstanceDiff{ 994 Attributes: map[string]*terraform.ResourceAttrDiff{ 995 "ports.#": { 996 Old: "0", 997 New: "3", 998 }, 999 "ports.1": { 1000 Old: "", 1001 New: "1", 1002 }, 1003 "ports.2": { 1004 Old: "", 1005 New: "2", 1006 }, 1007 "ports.5": { 1008 Old: "", 1009 New: "5", 1010 }, 1011 }, 1012 }, 1013 1014 Err: false, 1015 }, 1016 1017 { 1018 Name: "Set", 1019 Schema: map[string]*Schema{ 1020 "ports": { 1021 Type: TypeSet, 1022 Required: true, 1023 Elem: &Schema{Type: TypeInt}, 1024 Set: func(a interface{}) int { 1025 return a.(int) 1026 }, 1027 }, 1028 }, 1029 1030 State: nil, 1031 1032 Config: map[string]interface{}{ 1033 "ports": []interface{}{1, hcl2shim.UnknownVariableValue, "5"}, 1034 }, 1035 1036 Diff: &terraform.InstanceDiff{ 1037 Attributes: map[string]*terraform.ResourceAttrDiff{ 1038 "ports.#": { 1039 Old: "", 1040 New: "", 1041 NewComputed: true, 1042 }, 1043 }, 1044 }, 1045 1046 Err: false, 1047 }, 1048 1049 { 1050 Name: "Set", 1051 Schema: map[string]*Schema{ 1052 "ports": { 1053 Type: TypeSet, 1054 Required: true, 1055 Elem: &Schema{Type: TypeInt}, 1056 Set: func(a interface{}) int { 1057 return a.(int) 1058 }, 1059 }, 1060 }, 1061 1062 State: &terraform.InstanceState{ 1063 Attributes: map[string]string{ 1064 "ports.#": "2", 1065 "ports.1": "1", 1066 "ports.2": "2", 1067 }, 1068 }, 1069 1070 Config: map[string]interface{}{ 1071 "ports": []interface{}{5, 2, 1}, 1072 }, 1073 1074 Diff: &terraform.InstanceDiff{ 1075 Attributes: map[string]*terraform.ResourceAttrDiff{ 1076 "ports.#": { 1077 Old: "2", 1078 New: "3", 1079 }, 1080 "ports.1": { 1081 Old: "1", 1082 New: "1", 1083 }, 1084 "ports.2": { 1085 Old: "2", 1086 New: "2", 1087 }, 1088 "ports.5": { 1089 Old: "", 1090 New: "5", 1091 }, 1092 }, 1093 }, 1094 1095 Err: false, 1096 }, 1097 1098 { 1099 Name: "Set", 1100 Schema: map[string]*Schema{ 1101 "ports": { 1102 Type: TypeSet, 1103 Required: true, 1104 Elem: &Schema{Type: TypeInt}, 1105 Set: func(a interface{}) int { 1106 return a.(int) 1107 }, 1108 }, 1109 }, 1110 1111 State: &terraform.InstanceState{ 1112 Attributes: map[string]string{ 1113 "ports.#": "2", 1114 "ports.1": "1", 1115 "ports.2": "2", 1116 }, 1117 }, 1118 1119 Config: map[string]interface{}{}, 1120 1121 Diff: &terraform.InstanceDiff{ 1122 Attributes: map[string]*terraform.ResourceAttrDiff{ 1123 "ports.#": { 1124 Old: "2", 1125 New: "0", 1126 }, 1127 "ports.1": { 1128 Old: "1", 1129 New: "0", 1130 NewRemoved: true, 1131 }, 1132 "ports.2": { 1133 Old: "2", 1134 New: "0", 1135 NewRemoved: true, 1136 }, 1137 }, 1138 }, 1139 1140 Err: false, 1141 }, 1142 1143 { 1144 Name: "Set", 1145 Schema: map[string]*Schema{ 1146 "ports": { 1147 Type: TypeSet, 1148 Optional: true, 1149 Computed: true, 1150 Elem: &Schema{Type: TypeInt}, 1151 Set: func(a interface{}) int { 1152 return a.(int) 1153 }, 1154 }, 1155 }, 1156 1157 State: &terraform.InstanceState{ 1158 Attributes: map[string]string{ 1159 "availability_zone": "bar", 1160 "ports.#": "1", 1161 "ports.80": "80", 1162 }, 1163 }, 1164 1165 Config: map[string]interface{}{}, 1166 1167 Diff: nil, 1168 1169 Err: false, 1170 }, 1171 1172 { 1173 Name: "Set", 1174 Schema: map[string]*Schema{ 1175 "ingress": { 1176 Type: TypeSet, 1177 Required: true, 1178 Elem: &Resource{ 1179 Schema: map[string]*Schema{ 1180 "ports": { 1181 Type: TypeList, 1182 Optional: true, 1183 Elem: &Schema{Type: TypeInt}, 1184 }, 1185 }, 1186 }, 1187 Set: func(v interface{}) int { 1188 m := v.(map[string]interface{}) 1189 ps := m["ports"].([]interface{}) 1190 result := 0 1191 for _, p := range ps { 1192 result += p.(int) 1193 } 1194 return result 1195 }, 1196 }, 1197 }, 1198 1199 State: &terraform.InstanceState{ 1200 Attributes: map[string]string{ 1201 "ingress.#": "2", 1202 "ingress.80.ports.#": "1", 1203 "ingress.80.ports.0": "80", 1204 "ingress.443.ports.#": "1", 1205 "ingress.443.ports.0": "443", 1206 }, 1207 }, 1208 1209 Config: map[string]interface{}{ 1210 "ingress": []interface{}{ 1211 map[string]interface{}{ 1212 "ports": []interface{}{443}, 1213 }, 1214 map[string]interface{}{ 1215 "ports": []interface{}{80}, 1216 }, 1217 }, 1218 }, 1219 1220 Diff: nil, 1221 1222 Err: false, 1223 }, 1224 1225 { 1226 Name: "List of structure decode", 1227 Schema: map[string]*Schema{ 1228 "ingress": { 1229 Type: TypeList, 1230 Required: true, 1231 Elem: &Resource{ 1232 Schema: map[string]*Schema{ 1233 "from": { 1234 Type: TypeInt, 1235 Required: true, 1236 }, 1237 }, 1238 }, 1239 }, 1240 }, 1241 1242 State: nil, 1243 1244 Config: map[string]interface{}{ 1245 "ingress": []interface{}{ 1246 map[string]interface{}{ 1247 "from": 8080, 1248 }, 1249 }, 1250 }, 1251 1252 Diff: &terraform.InstanceDiff{ 1253 Attributes: map[string]*terraform.ResourceAttrDiff{ 1254 "ingress.#": { 1255 Old: "0", 1256 New: "1", 1257 }, 1258 "ingress.0.from": { 1259 Old: "", 1260 New: "8080", 1261 }, 1262 }, 1263 }, 1264 1265 Err: false, 1266 }, 1267 1268 { 1269 Name: "ComputedWhen", 1270 Schema: map[string]*Schema{ 1271 "availability_zone": { 1272 Type: TypeString, 1273 Computed: true, 1274 ComputedWhen: []string{"port"}, 1275 }, 1276 1277 "port": { 1278 Type: TypeInt, 1279 Optional: true, 1280 }, 1281 }, 1282 1283 State: &terraform.InstanceState{ 1284 Attributes: map[string]string{ 1285 "availability_zone": "foo", 1286 "port": "80", 1287 }, 1288 }, 1289 1290 Config: map[string]interface{}{ 1291 "port": 80, 1292 }, 1293 1294 Diff: nil, 1295 1296 Err: false, 1297 }, 1298 1299 { 1300 Name: "", 1301 Schema: map[string]*Schema{ 1302 "availability_zone": { 1303 Type: TypeString, 1304 Computed: true, 1305 ComputedWhen: []string{"port"}, 1306 }, 1307 1308 "port": { 1309 Type: TypeInt, 1310 Optional: true, 1311 }, 1312 }, 1313 1314 State: &terraform.InstanceState{ 1315 Attributes: map[string]string{ 1316 "port": "80", 1317 }, 1318 }, 1319 1320 Config: map[string]interface{}{ 1321 "port": 80, 1322 }, 1323 1324 Diff: &terraform.InstanceDiff{ 1325 Attributes: map[string]*terraform.ResourceAttrDiff{ 1326 "availability_zone": { 1327 NewComputed: true, 1328 }, 1329 }, 1330 }, 1331 1332 Err: false, 1333 }, 1334 1335 /* TODO 1336 { 1337 Schema: map[string]*Schema{ 1338 "availability_zone": &Schema{ 1339 Type: TypeString, 1340 Computed: true, 1341 ComputedWhen: []string{"port"}, 1342 }, 1343 1344 "port": &Schema{ 1345 Type: TypeInt, 1346 Optional: true, 1347 }, 1348 }, 1349 1350 State: &terraform.InstanceState{ 1351 Attributes: map[string]string{ 1352 "availability_zone": "foo", 1353 "port": "80", 1354 }, 1355 }, 1356 1357 Config: map[string]interface{}{ 1358 "port": 8080, 1359 }, 1360 1361 Diff: &terraform.ResourceDiff{ 1362 Attributes: map[string]*terraform.ResourceAttrDiff{ 1363 "availability_zone": &terraform.ResourceAttrDiff{ 1364 Old: "foo", 1365 NewComputed: true, 1366 }, 1367 "port": &terraform.ResourceAttrDiff{ 1368 Old: "80", 1369 New: "8080", 1370 }, 1371 }, 1372 }, 1373 1374 Err: false, 1375 }, 1376 */ 1377 1378 { 1379 Name: "Maps", 1380 Schema: map[string]*Schema{ 1381 "config_vars": { 1382 Type: TypeMap, 1383 }, 1384 }, 1385 1386 State: nil, 1387 1388 Config: map[string]interface{}{ 1389 "config_vars": []interface{}{ 1390 map[string]interface{}{ 1391 "bar": "baz", 1392 }, 1393 }, 1394 }, 1395 1396 Diff: &terraform.InstanceDiff{ 1397 Attributes: map[string]*terraform.ResourceAttrDiff{ 1398 "config_vars.%": { 1399 Old: "0", 1400 New: "1", 1401 }, 1402 1403 "config_vars.bar": { 1404 Old: "", 1405 New: "baz", 1406 }, 1407 }, 1408 }, 1409 1410 Err: false, 1411 }, 1412 1413 { 1414 Name: "Maps", 1415 Schema: map[string]*Schema{ 1416 "config_vars": { 1417 Type: TypeMap, 1418 }, 1419 }, 1420 1421 State: &terraform.InstanceState{ 1422 Attributes: map[string]string{ 1423 "config_vars.foo": "bar", 1424 }, 1425 }, 1426 1427 Config: map[string]interface{}{ 1428 "config_vars": []interface{}{ 1429 map[string]interface{}{ 1430 "bar": "baz", 1431 }, 1432 }, 1433 }, 1434 1435 Diff: &terraform.InstanceDiff{ 1436 Attributes: map[string]*terraform.ResourceAttrDiff{ 1437 "config_vars.foo": { 1438 Old: "bar", 1439 NewRemoved: true, 1440 }, 1441 "config_vars.bar": { 1442 Old: "", 1443 New: "baz", 1444 }, 1445 }, 1446 }, 1447 1448 Err: false, 1449 }, 1450 1451 { 1452 Name: "Maps", 1453 Schema: map[string]*Schema{ 1454 "vars": { 1455 Type: TypeMap, 1456 Optional: true, 1457 Computed: true, 1458 }, 1459 }, 1460 1461 State: &terraform.InstanceState{ 1462 Attributes: map[string]string{ 1463 "vars.foo": "bar", 1464 }, 1465 }, 1466 1467 Config: map[string]interface{}{ 1468 "vars": []interface{}{ 1469 map[string]interface{}{ 1470 "bar": "baz", 1471 }, 1472 }, 1473 }, 1474 1475 Diff: &terraform.InstanceDiff{ 1476 Attributes: map[string]*terraform.ResourceAttrDiff{ 1477 "vars.foo": { 1478 Old: "bar", 1479 New: "", 1480 NewRemoved: true, 1481 }, 1482 "vars.bar": { 1483 Old: "", 1484 New: "baz", 1485 }, 1486 }, 1487 }, 1488 1489 Err: false, 1490 }, 1491 1492 { 1493 Name: "Maps", 1494 Schema: map[string]*Schema{ 1495 "vars": { 1496 Type: TypeMap, 1497 Computed: true, 1498 }, 1499 }, 1500 1501 State: &terraform.InstanceState{ 1502 Attributes: map[string]string{ 1503 "vars.foo": "bar", 1504 }, 1505 }, 1506 1507 Config: nil, 1508 1509 Diff: nil, 1510 1511 Err: false, 1512 }, 1513 1514 { 1515 Name: "Maps", 1516 Schema: map[string]*Schema{ 1517 "config_vars": { 1518 Type: TypeList, 1519 Elem: &Schema{Type: TypeMap}, 1520 }, 1521 }, 1522 1523 State: &terraform.InstanceState{ 1524 Attributes: map[string]string{ 1525 "config_vars.#": "1", 1526 "config_vars.0.foo": "bar", 1527 }, 1528 }, 1529 1530 Config: map[string]interface{}{ 1531 "config_vars": []interface{}{ 1532 map[string]interface{}{ 1533 "bar": "baz", 1534 }, 1535 }, 1536 }, 1537 1538 Diff: &terraform.InstanceDiff{ 1539 Attributes: map[string]*terraform.ResourceAttrDiff{ 1540 "config_vars.0.foo": { 1541 Old: "bar", 1542 NewRemoved: true, 1543 }, 1544 "config_vars.0.bar": { 1545 Old: "", 1546 New: "baz", 1547 }, 1548 }, 1549 }, 1550 1551 Err: false, 1552 }, 1553 1554 { 1555 Name: "Maps", 1556 Schema: map[string]*Schema{ 1557 "config_vars": { 1558 Type: TypeList, 1559 Elem: &Schema{Type: TypeMap}, 1560 }, 1561 }, 1562 1563 State: &terraform.InstanceState{ 1564 Attributes: map[string]string{ 1565 "config_vars.#": "1", 1566 "config_vars.0.foo": "bar", 1567 "config_vars.0.bar": "baz", 1568 }, 1569 }, 1570 1571 Config: map[string]interface{}{}, 1572 1573 Diff: &terraform.InstanceDiff{ 1574 Attributes: map[string]*terraform.ResourceAttrDiff{ 1575 "config_vars.#": { 1576 Old: "1", 1577 New: "0", 1578 }, 1579 "config_vars.0.%": { 1580 Old: "2", 1581 New: "0", 1582 }, 1583 "config_vars.0.foo": { 1584 Old: "bar", 1585 NewRemoved: true, 1586 }, 1587 "config_vars.0.bar": { 1588 Old: "baz", 1589 NewRemoved: true, 1590 }, 1591 }, 1592 }, 1593 1594 Err: false, 1595 }, 1596 1597 { 1598 Name: "ForceNews", 1599 Schema: map[string]*Schema{ 1600 "availability_zone": { 1601 Type: TypeString, 1602 Optional: true, 1603 ForceNew: true, 1604 }, 1605 1606 "address": { 1607 Type: TypeString, 1608 Optional: true, 1609 Computed: true, 1610 }, 1611 }, 1612 1613 State: &terraform.InstanceState{ 1614 Attributes: map[string]string{ 1615 "availability_zone": "bar", 1616 "address": "foo", 1617 }, 1618 }, 1619 1620 Config: map[string]interface{}{ 1621 "availability_zone": "foo", 1622 }, 1623 1624 Diff: &terraform.InstanceDiff{ 1625 Attributes: map[string]*terraform.ResourceAttrDiff{ 1626 "availability_zone": { 1627 Old: "bar", 1628 New: "foo", 1629 RequiresNew: true, 1630 }, 1631 1632 "address": { 1633 Old: "foo", 1634 New: "", 1635 NewComputed: true, 1636 }, 1637 }, 1638 }, 1639 1640 Err: false, 1641 }, 1642 1643 { 1644 Name: "Set", 1645 Schema: map[string]*Schema{ 1646 "availability_zone": { 1647 Type: TypeString, 1648 Optional: true, 1649 ForceNew: true, 1650 }, 1651 1652 "ports": { 1653 Type: TypeSet, 1654 Optional: true, 1655 Computed: true, 1656 Elem: &Schema{Type: TypeInt}, 1657 Set: func(a interface{}) int { 1658 return a.(int) 1659 }, 1660 }, 1661 }, 1662 1663 State: &terraform.InstanceState{ 1664 Attributes: map[string]string{ 1665 "availability_zone": "bar", 1666 "ports.#": "1", 1667 "ports.80": "80", 1668 }, 1669 }, 1670 1671 Config: map[string]interface{}{ 1672 "availability_zone": "foo", 1673 }, 1674 1675 Diff: &terraform.InstanceDiff{ 1676 Attributes: map[string]*terraform.ResourceAttrDiff{ 1677 "availability_zone": { 1678 Old: "bar", 1679 New: "foo", 1680 RequiresNew: true, 1681 }, 1682 1683 "ports.#": { 1684 Old: "1", 1685 New: "", 1686 NewComputed: true, 1687 }, 1688 }, 1689 }, 1690 1691 Err: false, 1692 }, 1693 1694 { 1695 Name: "Set", 1696 Schema: map[string]*Schema{ 1697 "instances": { 1698 Type: TypeSet, 1699 Elem: &Schema{Type: TypeString}, 1700 Optional: true, 1701 Computed: true, 1702 Set: func(v interface{}) int { 1703 return len(v.(string)) 1704 }, 1705 }, 1706 }, 1707 1708 State: &terraform.InstanceState{ 1709 Attributes: map[string]string{ 1710 "instances.#": "0", 1711 }, 1712 }, 1713 1714 Config: map[string]interface{}{ 1715 "instances": []interface{}{hcl2shim.UnknownVariableValue}, 1716 }, 1717 1718 Diff: &terraform.InstanceDiff{ 1719 Attributes: map[string]*terraform.ResourceAttrDiff{ 1720 "instances.#": { 1721 NewComputed: true, 1722 }, 1723 }, 1724 }, 1725 1726 Err: false, 1727 }, 1728 1729 { 1730 Name: "Set", 1731 Schema: map[string]*Schema{ 1732 "route": { 1733 Type: TypeSet, 1734 Optional: true, 1735 Elem: &Resource{ 1736 Schema: map[string]*Schema{ 1737 "index": { 1738 Type: TypeInt, 1739 Required: true, 1740 }, 1741 1742 "gateway": { 1743 Type: TypeString, 1744 Optional: true, 1745 }, 1746 }, 1747 }, 1748 Set: func(v interface{}) int { 1749 m := v.(map[string]interface{}) 1750 return m["index"].(int) 1751 }, 1752 }, 1753 }, 1754 1755 State: nil, 1756 1757 Config: map[string]interface{}{ 1758 "route": []interface{}{ 1759 map[string]interface{}{ 1760 "index": "1", 1761 "gateway": hcl2shim.UnknownVariableValue, 1762 }, 1763 }, 1764 }, 1765 1766 Diff: &terraform.InstanceDiff{ 1767 Attributes: map[string]*terraform.ResourceAttrDiff{ 1768 "route.#": { 1769 Old: "0", 1770 New: "1", 1771 }, 1772 "route.~1.index": { 1773 Old: "", 1774 New: "1", 1775 }, 1776 "route.~1.gateway": { 1777 Old: "", 1778 New: hcl2shim.UnknownVariableValue, 1779 NewComputed: true, 1780 }, 1781 }, 1782 }, 1783 1784 Err: false, 1785 }, 1786 1787 { 1788 Name: "Set", 1789 Schema: map[string]*Schema{ 1790 "route": { 1791 Type: TypeSet, 1792 Optional: true, 1793 Elem: &Resource{ 1794 Schema: map[string]*Schema{ 1795 "index": { 1796 Type: TypeInt, 1797 Required: true, 1798 }, 1799 1800 "gateway": { 1801 Type: TypeSet, 1802 Optional: true, 1803 Elem: &Schema{Type: TypeInt}, 1804 Set: func(a interface{}) int { 1805 return a.(int) 1806 }, 1807 }, 1808 }, 1809 }, 1810 Set: func(v interface{}) int { 1811 m := v.(map[string]interface{}) 1812 return m["index"].(int) 1813 }, 1814 }, 1815 }, 1816 1817 State: nil, 1818 1819 Config: map[string]interface{}{ 1820 "route": []interface{}{ 1821 map[string]interface{}{ 1822 "index": "1", 1823 "gateway": []interface{}{ 1824 hcl2shim.UnknownVariableValue, 1825 }, 1826 }, 1827 }, 1828 }, 1829 1830 Diff: &terraform.InstanceDiff{ 1831 Attributes: map[string]*terraform.ResourceAttrDiff{ 1832 "route.#": { 1833 Old: "0", 1834 New: "1", 1835 }, 1836 "route.~1.index": { 1837 Old: "", 1838 New: "1", 1839 }, 1840 "route.~1.gateway.#": { 1841 NewComputed: true, 1842 }, 1843 }, 1844 }, 1845 1846 Err: false, 1847 }, 1848 1849 { 1850 Name: "Computed maps", 1851 Schema: map[string]*Schema{ 1852 "vars": { 1853 Type: TypeMap, 1854 Computed: true, 1855 }, 1856 }, 1857 1858 State: nil, 1859 1860 Config: nil, 1861 1862 Diff: &terraform.InstanceDiff{ 1863 Attributes: map[string]*terraform.ResourceAttrDiff{ 1864 "vars.%": { 1865 Old: "", 1866 NewComputed: true, 1867 }, 1868 }, 1869 }, 1870 1871 Err: false, 1872 }, 1873 1874 { 1875 Name: "Computed maps", 1876 Schema: map[string]*Schema{ 1877 "vars": { 1878 Type: TypeMap, 1879 Computed: true, 1880 }, 1881 }, 1882 1883 State: &terraform.InstanceState{ 1884 Attributes: map[string]string{ 1885 "vars.%": "0", 1886 }, 1887 }, 1888 1889 Config: map[string]interface{}{ 1890 "vars": map[string]interface{}{ 1891 "bar": hcl2shim.UnknownVariableValue, 1892 }, 1893 }, 1894 1895 Diff: &terraform.InstanceDiff{ 1896 Attributes: map[string]*terraform.ResourceAttrDiff{ 1897 "vars.%": { 1898 Old: "", 1899 NewComputed: true, 1900 }, 1901 }, 1902 }, 1903 1904 Err: false, 1905 }, 1906 1907 { 1908 Name: " - Empty", 1909 Schema: map[string]*Schema{}, 1910 1911 State: &terraform.InstanceState{}, 1912 1913 Config: map[string]interface{}{}, 1914 1915 Diff: nil, 1916 1917 Err: false, 1918 }, 1919 1920 { 1921 Name: "Float", 1922 Schema: map[string]*Schema{ 1923 "some_threshold": { 1924 Type: TypeFloat, 1925 }, 1926 }, 1927 1928 State: &terraform.InstanceState{ 1929 Attributes: map[string]string{ 1930 "some_threshold": "567.8", 1931 }, 1932 }, 1933 1934 Config: map[string]interface{}{ 1935 "some_threshold": 12.34, 1936 }, 1937 1938 Diff: &terraform.InstanceDiff{ 1939 Attributes: map[string]*terraform.ResourceAttrDiff{ 1940 "some_threshold": { 1941 Old: "567.8", 1942 New: "12.34", 1943 }, 1944 }, 1945 }, 1946 1947 Err: false, 1948 }, 1949 1950 { 1951 Name: "https://github.com/hashicorp/terraform-plugin-sdk/issues/824", 1952 Schema: map[string]*Schema{ 1953 "block_device": { 1954 Type: TypeSet, 1955 Optional: true, 1956 Computed: true, 1957 Elem: &Resource{ 1958 Schema: map[string]*Schema{ 1959 "device_name": { 1960 Type: TypeString, 1961 Required: true, 1962 }, 1963 "delete_on_termination": { 1964 Type: TypeBool, 1965 Optional: true, 1966 Default: true, 1967 }, 1968 }, 1969 }, 1970 Set: func(v interface{}) int { 1971 var buf bytes.Buffer 1972 m := v.(map[string]interface{}) 1973 buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) 1974 buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool))) 1975 return hashcode.String(buf.String()) 1976 }, 1977 }, 1978 }, 1979 1980 State: &terraform.InstanceState{ 1981 Attributes: map[string]string{ 1982 "block_device.#": "2", 1983 "block_device.616397234.delete_on_termination": "true", 1984 "block_device.616397234.device_name": "/dev/sda1", 1985 "block_device.2801811477.delete_on_termination": "true", 1986 "block_device.2801811477.device_name": "/dev/sdx", 1987 }, 1988 }, 1989 1990 Config: map[string]interface{}{ 1991 "block_device": []interface{}{ 1992 map[string]interface{}{ 1993 "device_name": "/dev/sda1", 1994 }, 1995 map[string]interface{}{ 1996 "device_name": "/dev/sdx", 1997 }, 1998 }, 1999 }, 2000 Diff: nil, 2001 Err: false, 2002 }, 2003 2004 { 2005 Name: "Zero value in state shouldn't result in diff", 2006 Schema: map[string]*Schema{ 2007 "port": { 2008 Type: TypeBool, 2009 Optional: true, 2010 ForceNew: true, 2011 }, 2012 }, 2013 2014 State: &terraform.InstanceState{ 2015 Attributes: map[string]string{ 2016 "port": "false", 2017 }, 2018 }, 2019 2020 Config: map[string]interface{}{}, 2021 2022 Diff: nil, 2023 2024 Err: false, 2025 }, 2026 2027 { 2028 Name: "Same as prev, but for sets", 2029 Schema: map[string]*Schema{ 2030 "route": { 2031 Type: TypeSet, 2032 Optional: true, 2033 Elem: &Resource{ 2034 Schema: map[string]*Schema{ 2035 "index": { 2036 Type: TypeInt, 2037 Required: true, 2038 }, 2039 2040 "gateway": { 2041 Type: TypeSet, 2042 Optional: true, 2043 Elem: &Schema{Type: TypeInt}, 2044 Set: func(a interface{}) int { 2045 return a.(int) 2046 }, 2047 }, 2048 }, 2049 }, 2050 Set: func(v interface{}) int { 2051 m := v.(map[string]interface{}) 2052 return m["index"].(int) 2053 }, 2054 }, 2055 }, 2056 2057 State: &terraform.InstanceState{ 2058 Attributes: map[string]string{ 2059 "route.#": "0", 2060 }, 2061 }, 2062 2063 Config: map[string]interface{}{}, 2064 2065 Diff: nil, 2066 2067 Err: false, 2068 }, 2069 2070 { 2071 Name: "A set computed element shouldn't cause a diff", 2072 Schema: map[string]*Schema{ 2073 "active": { 2074 Type: TypeBool, 2075 Computed: true, 2076 ForceNew: true, 2077 }, 2078 }, 2079 2080 State: &terraform.InstanceState{ 2081 Attributes: map[string]string{ 2082 "active": "true", 2083 }, 2084 }, 2085 2086 Config: map[string]interface{}{}, 2087 2088 Diff: nil, 2089 2090 Err: false, 2091 }, 2092 2093 { 2094 Name: "An empty set should show up in the diff", 2095 Schema: map[string]*Schema{ 2096 "instances": { 2097 Type: TypeSet, 2098 Elem: &Schema{Type: TypeString}, 2099 Optional: true, 2100 ForceNew: true, 2101 Set: func(v interface{}) int { 2102 return len(v.(string)) 2103 }, 2104 }, 2105 }, 2106 2107 State: &terraform.InstanceState{ 2108 Attributes: map[string]string{ 2109 "instances.#": "1", 2110 "instances.3": "foo", 2111 }, 2112 }, 2113 2114 Config: map[string]interface{}{}, 2115 2116 Diff: &terraform.InstanceDiff{ 2117 Attributes: map[string]*terraform.ResourceAttrDiff{ 2118 "instances.#": { 2119 Old: "1", 2120 New: "0", 2121 RequiresNew: true, 2122 }, 2123 "instances.3": { 2124 Old: "foo", 2125 New: "", 2126 NewRemoved: true, 2127 RequiresNew: true, 2128 }, 2129 }, 2130 }, 2131 2132 Err: false, 2133 }, 2134 2135 { 2136 Name: "Map with empty value", 2137 Schema: map[string]*Schema{ 2138 "vars": { 2139 Type: TypeMap, 2140 }, 2141 }, 2142 2143 State: nil, 2144 2145 Config: map[string]interface{}{ 2146 "vars": map[string]interface{}{ 2147 "foo": "", 2148 }, 2149 }, 2150 2151 Diff: &terraform.InstanceDiff{ 2152 Attributes: map[string]*terraform.ResourceAttrDiff{ 2153 "vars.%": { 2154 Old: "0", 2155 New: "1", 2156 }, 2157 "vars.foo": { 2158 Old: "", 2159 New: "", 2160 }, 2161 }, 2162 }, 2163 2164 Err: false, 2165 }, 2166 2167 { 2168 Name: "Unset bool, not in state", 2169 Schema: map[string]*Schema{ 2170 "force": { 2171 Type: TypeBool, 2172 Optional: true, 2173 ForceNew: true, 2174 }, 2175 }, 2176 2177 State: nil, 2178 2179 Config: map[string]interface{}{}, 2180 2181 Diff: nil, 2182 2183 Err: false, 2184 }, 2185 2186 { 2187 Name: "Unset set, not in state", 2188 Schema: map[string]*Schema{ 2189 "metadata_keys": { 2190 Type: TypeSet, 2191 Optional: true, 2192 ForceNew: true, 2193 Elem: &Schema{Type: TypeInt}, 2194 Set: func(interface{}) int { return 0 }, 2195 }, 2196 }, 2197 2198 State: nil, 2199 2200 Config: map[string]interface{}{}, 2201 2202 Diff: nil, 2203 2204 Err: false, 2205 }, 2206 2207 { 2208 Name: "Unset list in state, should not show up computed", 2209 Schema: map[string]*Schema{ 2210 "metadata_keys": { 2211 Type: TypeList, 2212 Optional: true, 2213 Computed: true, 2214 ForceNew: true, 2215 Elem: &Schema{Type: TypeInt}, 2216 }, 2217 }, 2218 2219 State: &terraform.InstanceState{ 2220 Attributes: map[string]string{ 2221 "metadata_keys.#": "0", 2222 }, 2223 }, 2224 2225 Config: map[string]interface{}{}, 2226 2227 Diff: nil, 2228 2229 Err: false, 2230 }, 2231 2232 { 2233 Name: "Set element computed element", 2234 Schema: map[string]*Schema{ 2235 "ports": { 2236 Type: TypeSet, 2237 Required: true, 2238 Elem: &Schema{Type: TypeInt}, 2239 Set: func(a interface{}) int { 2240 return a.(int) 2241 }, 2242 }, 2243 }, 2244 2245 State: nil, 2246 2247 Config: map[string]interface{}{ 2248 "ports": []interface{}{1, hcl2shim.UnknownVariableValue}, 2249 }, 2250 2251 Diff: &terraform.InstanceDiff{ 2252 Attributes: map[string]*terraform.ResourceAttrDiff{ 2253 "ports.#": { 2254 Old: "", 2255 New: "", 2256 NewComputed: true, 2257 }, 2258 }, 2259 }, 2260 2261 Err: false, 2262 }, 2263 2264 { 2265 Name: "Computed map without config that's known to be empty does not generate diff", 2266 Schema: map[string]*Schema{ 2267 "tags": { 2268 Type: TypeMap, 2269 Computed: true, 2270 }, 2271 }, 2272 2273 Config: nil, 2274 2275 State: &terraform.InstanceState{ 2276 Attributes: map[string]string{ 2277 "tags.%": "0", 2278 }, 2279 }, 2280 2281 Diff: nil, 2282 2283 Err: false, 2284 }, 2285 2286 { 2287 Name: "Set with hyphen keys", 2288 Schema: map[string]*Schema{ 2289 "route": { 2290 Type: TypeSet, 2291 Optional: true, 2292 Elem: &Resource{ 2293 Schema: map[string]*Schema{ 2294 "index": { 2295 Type: TypeInt, 2296 Required: true, 2297 }, 2298 2299 "gateway-name": { 2300 Type: TypeString, 2301 Optional: true, 2302 }, 2303 }, 2304 }, 2305 Set: func(v interface{}) int { 2306 m := v.(map[string]interface{}) 2307 return m["index"].(int) 2308 }, 2309 }, 2310 }, 2311 2312 State: nil, 2313 2314 Config: map[string]interface{}{ 2315 "route": []interface{}{ 2316 map[string]interface{}{ 2317 "index": "1", 2318 "gateway-name": "hello", 2319 }, 2320 }, 2321 }, 2322 2323 Diff: &terraform.InstanceDiff{ 2324 Attributes: map[string]*terraform.ResourceAttrDiff{ 2325 "route.#": { 2326 Old: "0", 2327 New: "1", 2328 }, 2329 "route.1.index": { 2330 Old: "", 2331 New: "1", 2332 }, 2333 "route.1.gateway-name": { 2334 Old: "", 2335 New: "hello", 2336 }, 2337 }, 2338 }, 2339 2340 Err: false, 2341 }, 2342 2343 { 2344 Name: ": StateFunc in nested set (#1759)", 2345 Schema: map[string]*Schema{ 2346 "service_account": { 2347 Type: TypeList, 2348 Optional: true, 2349 ForceNew: true, 2350 Elem: &Resource{ 2351 Schema: map[string]*Schema{ 2352 "scopes": { 2353 Type: TypeSet, 2354 Required: true, 2355 ForceNew: true, 2356 Elem: &Schema{ 2357 Type: TypeString, 2358 StateFunc: func(v interface{}) string { 2359 return v.(string) + "!" 2360 }, 2361 }, 2362 Set: func(v interface{}) int { 2363 i, err := strconv.Atoi(v.(string)) 2364 if err != nil { 2365 t.Fatalf("err: %s", err) 2366 } 2367 return i 2368 }, 2369 }, 2370 }, 2371 }, 2372 }, 2373 }, 2374 2375 State: nil, 2376 2377 Config: map[string]interface{}{ 2378 "service_account": []interface{}{ 2379 map[string]interface{}{ 2380 "scopes": []interface{}{"123"}, 2381 }, 2382 }, 2383 }, 2384 2385 Diff: &terraform.InstanceDiff{ 2386 Attributes: map[string]*terraform.ResourceAttrDiff{ 2387 "service_account.#": { 2388 Old: "0", 2389 New: "1", 2390 RequiresNew: true, 2391 }, 2392 "service_account.0.scopes.#": { 2393 Old: "0", 2394 New: "1", 2395 RequiresNew: true, 2396 }, 2397 "service_account.0.scopes.123": { 2398 Old: "", 2399 New: "123!", 2400 NewExtra: "123", 2401 RequiresNew: true, 2402 }, 2403 }, 2404 }, 2405 2406 Err: false, 2407 }, 2408 2409 { 2410 Name: "Removing set elements", 2411 Schema: map[string]*Schema{ 2412 "instances": { 2413 Type: TypeSet, 2414 Elem: &Schema{Type: TypeString}, 2415 Optional: true, 2416 ForceNew: true, 2417 Set: func(v interface{}) int { 2418 return len(v.(string)) 2419 }, 2420 }, 2421 }, 2422 2423 State: &terraform.InstanceState{ 2424 Attributes: map[string]string{ 2425 "instances.#": "2", 2426 "instances.3": "333", 2427 "instances.2": "22", 2428 }, 2429 }, 2430 2431 Config: map[string]interface{}{ 2432 "instances": []interface{}{"333", "4444"}, 2433 }, 2434 2435 Diff: &terraform.InstanceDiff{ 2436 Attributes: map[string]*terraform.ResourceAttrDiff{ 2437 "instances.#": { 2438 Old: "2", 2439 New: "2", 2440 }, 2441 "instances.2": { 2442 Old: "22", 2443 New: "", 2444 NewRemoved: true, 2445 RequiresNew: true, 2446 }, 2447 "instances.3": { 2448 Old: "333", 2449 New: "333", 2450 }, 2451 "instances.4": { 2452 Old: "", 2453 New: "4444", 2454 RequiresNew: true, 2455 }, 2456 }, 2457 }, 2458 2459 Err: false, 2460 }, 2461 2462 { 2463 Name: "Bools can be set with 0/1 in config, still get true/false", 2464 Schema: map[string]*Schema{ 2465 "one": { 2466 Type: TypeBool, 2467 Optional: true, 2468 }, 2469 "two": { 2470 Type: TypeBool, 2471 Optional: true, 2472 }, 2473 "three": { 2474 Type: TypeBool, 2475 Optional: true, 2476 }, 2477 }, 2478 2479 State: &terraform.InstanceState{ 2480 Attributes: map[string]string{ 2481 "one": "false", 2482 "two": "true", 2483 "three": "true", 2484 }, 2485 }, 2486 2487 Config: map[string]interface{}{ 2488 "one": "1", 2489 "two": "0", 2490 }, 2491 2492 Diff: &terraform.InstanceDiff{ 2493 Attributes: map[string]*terraform.ResourceAttrDiff{ 2494 "one": { 2495 Old: "false", 2496 New: "true", 2497 }, 2498 "two": { 2499 Old: "true", 2500 New: "false", 2501 }, 2502 "three": { 2503 Old: "true", 2504 New: "false", 2505 NewRemoved: true, 2506 }, 2507 }, 2508 }, 2509 2510 Err: false, 2511 }, 2512 2513 { 2514 Name: "tainted in state w/ no attr changes is still a replacement", 2515 Schema: map[string]*Schema{}, 2516 2517 State: &terraform.InstanceState{ 2518 Attributes: map[string]string{ 2519 "id": "someid", 2520 }, 2521 Tainted: true, 2522 }, 2523 2524 Config: map[string]interface{}{}, 2525 2526 Diff: &terraform.InstanceDiff{ 2527 Attributes: map[string]*terraform.ResourceAttrDiff{}, 2528 DestroyTainted: true, 2529 }, 2530 2531 Err: false, 2532 }, 2533 2534 { 2535 Name: "Set ForceNew only marks the changing element as ForceNew", 2536 Schema: map[string]*Schema{ 2537 "ports": { 2538 Type: TypeSet, 2539 Required: true, 2540 ForceNew: true, 2541 Elem: &Schema{Type: TypeInt}, 2542 Set: func(a interface{}) int { 2543 return a.(int) 2544 }, 2545 }, 2546 }, 2547 2548 State: &terraform.InstanceState{ 2549 Attributes: map[string]string{ 2550 "ports.#": "3", 2551 "ports.1": "1", 2552 "ports.2": "2", 2553 "ports.4": "4", 2554 }, 2555 }, 2556 2557 Config: map[string]interface{}{ 2558 "ports": []interface{}{5, 2, 1}, 2559 }, 2560 2561 Diff: &terraform.InstanceDiff{ 2562 Attributes: map[string]*terraform.ResourceAttrDiff{ 2563 "ports.#": { 2564 Old: "3", 2565 New: "3", 2566 }, 2567 "ports.1": { 2568 Old: "1", 2569 New: "1", 2570 }, 2571 "ports.2": { 2572 Old: "2", 2573 New: "2", 2574 }, 2575 "ports.5": { 2576 Old: "", 2577 New: "5", 2578 RequiresNew: true, 2579 }, 2580 "ports.4": { 2581 Old: "4", 2582 New: "0", 2583 NewRemoved: true, 2584 RequiresNew: true, 2585 }, 2586 }, 2587 }, 2588 }, 2589 2590 { 2591 Name: "removed optional items should trigger ForceNew", 2592 Schema: map[string]*Schema{ 2593 "description": { 2594 Type: TypeString, 2595 ForceNew: true, 2596 Optional: true, 2597 }, 2598 }, 2599 2600 State: &terraform.InstanceState{ 2601 Attributes: map[string]string{ 2602 "description": "foo", 2603 }, 2604 }, 2605 2606 Config: map[string]interface{}{}, 2607 2608 Diff: &terraform.InstanceDiff{ 2609 Attributes: map[string]*terraform.ResourceAttrDiff{ 2610 "description": { 2611 Old: "foo", 2612 New: "", 2613 RequiresNew: true, 2614 NewRemoved: true, 2615 }, 2616 }, 2617 }, 2618 2619 Err: false, 2620 }, 2621 2622 // GH-7715 2623 { 2624 Name: "computed value for boolean field", 2625 Schema: map[string]*Schema{ 2626 "foo": { 2627 Type: TypeBool, 2628 ForceNew: true, 2629 Computed: true, 2630 Optional: true, 2631 }, 2632 }, 2633 2634 State: &terraform.InstanceState{}, 2635 2636 Config: map[string]interface{}{ 2637 "foo": hcl2shim.UnknownVariableValue, 2638 }, 2639 2640 Diff: &terraform.InstanceDiff{ 2641 Attributes: map[string]*terraform.ResourceAttrDiff{ 2642 "foo": { 2643 Old: "", 2644 New: "false", 2645 NewComputed: true, 2646 RequiresNew: true, 2647 }, 2648 }, 2649 }, 2650 2651 Err: false, 2652 }, 2653 2654 { 2655 Name: "Set ForceNew marks count as ForceNew if computed", 2656 Schema: map[string]*Schema{ 2657 "ports": { 2658 Type: TypeSet, 2659 Required: true, 2660 ForceNew: true, 2661 Elem: &Schema{Type: TypeInt}, 2662 Set: func(a interface{}) int { 2663 return a.(int) 2664 }, 2665 }, 2666 }, 2667 2668 State: &terraform.InstanceState{ 2669 Attributes: map[string]string{ 2670 "ports.#": "3", 2671 "ports.1": "1", 2672 "ports.2": "2", 2673 "ports.4": "4", 2674 }, 2675 }, 2676 2677 Config: map[string]interface{}{ 2678 "ports": []interface{}{hcl2shim.UnknownVariableValue, 2, 1}, 2679 }, 2680 2681 Diff: &terraform.InstanceDiff{ 2682 Attributes: map[string]*terraform.ResourceAttrDiff{ 2683 "ports.#": { 2684 Old: "3", 2685 New: "", 2686 NewComputed: true, 2687 RequiresNew: true, 2688 }, 2689 }, 2690 }, 2691 }, 2692 2693 { 2694 Name: "List with computed schema and ForceNew", 2695 Schema: map[string]*Schema{ 2696 "config": { 2697 Type: TypeList, 2698 Optional: true, 2699 ForceNew: true, 2700 Elem: &Schema{ 2701 Type: TypeString, 2702 }, 2703 }, 2704 }, 2705 2706 State: &terraform.InstanceState{ 2707 Attributes: map[string]string{ 2708 "config.#": "2", 2709 "config.0": "a", 2710 "config.1": "b", 2711 }, 2712 }, 2713 2714 Config: map[string]interface{}{ 2715 "config": []interface{}{hcl2shim.UnknownVariableValue, hcl2shim.UnknownVariableValue}, 2716 }, 2717 2718 Diff: &terraform.InstanceDiff{ 2719 Attributes: map[string]*terraform.ResourceAttrDiff{ 2720 "config.#": { 2721 Old: "2", 2722 New: "", 2723 RequiresNew: true, 2724 NewComputed: true, 2725 }, 2726 }, 2727 }, 2728 2729 Err: false, 2730 }, 2731 2732 { 2733 Name: "overridden diff with a CustomizeDiff function, ForceNew not in schema", 2734 Schema: map[string]*Schema{ 2735 "availability_zone": { 2736 Type: TypeString, 2737 Optional: true, 2738 Computed: true, 2739 }, 2740 }, 2741 2742 State: nil, 2743 2744 Config: map[string]interface{}{ 2745 "availability_zone": "foo", 2746 }, 2747 2748 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 2749 if err := d.SetNew("availability_zone", "bar"); err != nil { 2750 return err 2751 } 2752 if err := d.ForceNew("availability_zone"); err != nil { 2753 return err 2754 } 2755 return nil 2756 }, 2757 2758 Diff: &terraform.InstanceDiff{ 2759 Attributes: map[string]*terraform.ResourceAttrDiff{ 2760 "availability_zone": { 2761 Old: "", 2762 New: "bar", 2763 RequiresNew: true, 2764 }, 2765 }, 2766 }, 2767 2768 Err: false, 2769 }, 2770 2771 { 2772 // NOTE: This case is technically impossible in the current 2773 // implementation, because optional+computed values never show up in the 2774 // diff. In the event behavior changes this test should ensure that the 2775 // intended diff still shows up. 2776 Name: "overridden removed attribute diff with a CustomizeDiff function, ForceNew not in schema", 2777 Schema: map[string]*Schema{ 2778 "availability_zone": { 2779 Type: TypeString, 2780 Optional: true, 2781 Computed: true, 2782 }, 2783 }, 2784 2785 State: nil, 2786 2787 Config: map[string]interface{}{}, 2788 2789 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 2790 if err := d.SetNew("availability_zone", "bar"); err != nil { 2791 return err 2792 } 2793 if err := d.ForceNew("availability_zone"); err != nil { 2794 return err 2795 } 2796 return nil 2797 }, 2798 2799 Diff: &terraform.InstanceDiff{ 2800 Attributes: map[string]*terraform.ResourceAttrDiff{ 2801 "availability_zone": { 2802 Old: "", 2803 New: "bar", 2804 RequiresNew: true, 2805 }, 2806 }, 2807 }, 2808 2809 Err: false, 2810 }, 2811 2812 { 2813 2814 Name: "overridden diff with a CustomizeDiff function, ForceNew in schema", 2815 Schema: map[string]*Schema{ 2816 "availability_zone": { 2817 Type: TypeString, 2818 Optional: true, 2819 Computed: true, 2820 ForceNew: true, 2821 }, 2822 }, 2823 2824 State: nil, 2825 2826 Config: map[string]interface{}{ 2827 "availability_zone": "foo", 2828 }, 2829 2830 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 2831 if err := d.SetNew("availability_zone", "bar"); err != nil { 2832 return err 2833 } 2834 return nil 2835 }, 2836 2837 Diff: &terraform.InstanceDiff{ 2838 Attributes: map[string]*terraform.ResourceAttrDiff{ 2839 "availability_zone": { 2840 Old: "", 2841 New: "bar", 2842 RequiresNew: true, 2843 }, 2844 }, 2845 }, 2846 2847 Err: false, 2848 }, 2849 2850 { 2851 Name: "required field with computed diff added with CustomizeDiff function", 2852 Schema: map[string]*Schema{ 2853 "ami_id": { 2854 Type: TypeString, 2855 Required: true, 2856 }, 2857 "instance_id": { 2858 Type: TypeString, 2859 Computed: true, 2860 }, 2861 }, 2862 2863 State: nil, 2864 2865 Config: map[string]interface{}{ 2866 "ami_id": "foo", 2867 }, 2868 2869 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 2870 if err := d.SetNew("instance_id", "bar"); err != nil { 2871 return err 2872 } 2873 return nil 2874 }, 2875 2876 Diff: &terraform.InstanceDiff{ 2877 Attributes: map[string]*terraform.ResourceAttrDiff{ 2878 "ami_id": { 2879 Old: "", 2880 New: "foo", 2881 }, 2882 "instance_id": { 2883 Old: "", 2884 New: "bar", 2885 }, 2886 }, 2887 }, 2888 2889 Err: false, 2890 }, 2891 2892 { 2893 Name: "Set ForceNew only marks the changing element as ForceNew - CustomizeDiffFunc edition", 2894 Schema: map[string]*Schema{ 2895 "ports": { 2896 Type: TypeSet, 2897 Optional: true, 2898 Computed: true, 2899 Elem: &Schema{Type: TypeInt}, 2900 Set: func(a interface{}) int { 2901 return a.(int) 2902 }, 2903 }, 2904 }, 2905 2906 State: &terraform.InstanceState{ 2907 Attributes: map[string]string{ 2908 "ports.#": "3", 2909 "ports.1": "1", 2910 "ports.2": "2", 2911 "ports.4": "4", 2912 }, 2913 }, 2914 2915 Config: map[string]interface{}{ 2916 "ports": []interface{}{5, 2, 6}, 2917 }, 2918 2919 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 2920 if err := d.SetNew("ports", []interface{}{5, 2, 1}); err != nil { 2921 return err 2922 } 2923 if err := d.ForceNew("ports"); err != nil { 2924 return err 2925 } 2926 return nil 2927 }, 2928 2929 Diff: &terraform.InstanceDiff{ 2930 Attributes: map[string]*terraform.ResourceAttrDiff{ 2931 "ports.#": { 2932 Old: "3", 2933 New: "3", 2934 }, 2935 "ports.1": { 2936 Old: "1", 2937 New: "1", 2938 }, 2939 "ports.2": { 2940 Old: "2", 2941 New: "2", 2942 }, 2943 "ports.5": { 2944 Old: "", 2945 New: "5", 2946 RequiresNew: true, 2947 }, 2948 "ports.4": { 2949 Old: "4", 2950 New: "0", 2951 NewRemoved: true, 2952 RequiresNew: true, 2953 }, 2954 }, 2955 }, 2956 }, 2957 2958 { 2959 Name: "tainted resource does not run CustomizeDiffFunc", 2960 Schema: map[string]*Schema{}, 2961 2962 State: &terraform.InstanceState{ 2963 Attributes: map[string]string{ 2964 "id": "someid", 2965 }, 2966 Tainted: true, 2967 }, 2968 2969 Config: map[string]interface{}{}, 2970 2971 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 2972 return errors.New("diff customization should not have run") 2973 }, 2974 2975 Diff: &terraform.InstanceDiff{ 2976 Attributes: map[string]*terraform.ResourceAttrDiff{}, 2977 DestroyTainted: true, 2978 }, 2979 2980 Err: false, 2981 }, 2982 2983 { 2984 Name: "NewComputed based on a conditional with CustomizeDiffFunc", 2985 Schema: map[string]*Schema{ 2986 "etag": { 2987 Type: TypeString, 2988 Optional: true, 2989 Computed: true, 2990 }, 2991 "version_id": { 2992 Type: TypeString, 2993 Computed: true, 2994 }, 2995 }, 2996 2997 State: &terraform.InstanceState{ 2998 Attributes: map[string]string{ 2999 "etag": "foo", 3000 "version_id": "1", 3001 }, 3002 }, 3003 3004 Config: map[string]interface{}{ 3005 "etag": "bar", 3006 }, 3007 3008 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 3009 if d.HasChange("etag") { 3010 d.SetNewComputed("version_id") 3011 } 3012 return nil 3013 }, 3014 3015 Diff: &terraform.InstanceDiff{ 3016 Attributes: map[string]*terraform.ResourceAttrDiff{ 3017 "etag": { 3018 Old: "foo", 3019 New: "bar", 3020 }, 3021 "version_id": { 3022 Old: "1", 3023 New: "", 3024 NewComputed: true, 3025 }, 3026 }, 3027 }, 3028 3029 Err: false, 3030 }, 3031 3032 { 3033 Name: "NewComputed should always propagate with CustomizeDiff", 3034 Schema: map[string]*Schema{ 3035 "foo": { 3036 Type: TypeString, 3037 Computed: true, 3038 }, 3039 }, 3040 3041 State: &terraform.InstanceState{ 3042 Attributes: map[string]string{ 3043 "foo": "", 3044 }, 3045 ID: "pre-existing", 3046 }, 3047 3048 Config: map[string]interface{}{}, 3049 3050 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 3051 d.SetNewComputed("foo") 3052 return nil 3053 }, 3054 3055 Diff: &terraform.InstanceDiff{ 3056 Attributes: map[string]*terraform.ResourceAttrDiff{ 3057 "foo": { 3058 NewComputed: true, 3059 }, 3060 }, 3061 }, 3062 3063 Err: false, 3064 }, 3065 3066 { 3067 Name: "vetoing a diff", 3068 Schema: map[string]*Schema{ 3069 "foo": { 3070 Type: TypeString, 3071 Optional: true, 3072 Computed: true, 3073 }, 3074 }, 3075 3076 State: &terraform.InstanceState{ 3077 Attributes: map[string]string{ 3078 "foo": "bar", 3079 }, 3080 }, 3081 3082 Config: map[string]interface{}{ 3083 "foo": "baz", 3084 }, 3085 3086 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 3087 return fmt.Errorf("diff vetoed") 3088 }, 3089 3090 Err: true, 3091 }, 3092 3093 // A lot of resources currently depended on using the empty string as a 3094 // nil/unset value. 3095 // FIXME: We want this to eventually produce a diff, since there 3096 // technically is a new value in the config. 3097 { 3098 Name: "optional, computed, empty string", 3099 Schema: map[string]*Schema{ 3100 "attr": { 3101 Type: TypeString, 3102 Optional: true, 3103 Computed: true, 3104 }, 3105 }, 3106 3107 State: &terraform.InstanceState{ 3108 Attributes: map[string]string{ 3109 "attr": "bar", 3110 }, 3111 }, 3112 3113 Config: map[string]interface{}{ 3114 "attr": "", 3115 }, 3116 }, 3117 3118 { 3119 Name: "optional, computed, empty string should not crash in CustomizeDiff", 3120 Schema: map[string]*Schema{ 3121 "unrelated_set": { 3122 Type: TypeSet, 3123 Optional: true, 3124 Elem: &Schema{Type: TypeString}, 3125 }, 3126 "stream_enabled": { 3127 Type: TypeBool, 3128 Optional: true, 3129 }, 3130 "stream_view_type": { 3131 Type: TypeString, 3132 Optional: true, 3133 Computed: true, 3134 }, 3135 }, 3136 3137 State: &terraform.InstanceState{ 3138 Attributes: map[string]string{ 3139 "unrelated_set.#": "0", 3140 "stream_enabled": "true", 3141 "stream_view_type": "KEYS_ONLY", 3142 }, 3143 }, 3144 Config: map[string]interface{}{ 3145 "stream_enabled": false, 3146 "stream_view_type": "", 3147 }, 3148 CustomizeDiff: func(diff *ResourceDiff, v interface{}) error { 3149 v, ok := diff.GetOk("unrelated_set") 3150 if ok { 3151 return fmt.Errorf("Didn't expect unrelated_set: %#v", v) 3152 } 3153 return nil 3154 }, 3155 Diff: &terraform.InstanceDiff{ 3156 Attributes: map[string]*terraform.ResourceAttrDiff{ 3157 "stream_enabled": { 3158 Old: "true", 3159 New: "false", 3160 }, 3161 }, 3162 }, 3163 }, 3164 } 3165 3166 for i, tc := range cases { 3167 t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { 3168 c := terraform.NewResourceConfigRaw(tc.Config) 3169 3170 d, err := schemaMap(tc.Schema).Diff(tc.State, c, tc.CustomizeDiff, nil, true) 3171 if err != nil != tc.Err { 3172 t.Fatalf("err: %s", err) 3173 } 3174 3175 if !reflect.DeepEqual(tc.Diff, d) { 3176 t.Fatalf("expected:\n%#v\n\ngot:\n%#v", tc.Diff, d) 3177 } 3178 }) 3179 } 3180 } 3181 3182 func TestSchemaMap_Input(t *testing.T) { 3183 cases := map[string]struct { 3184 Schema map[string]*Schema 3185 Config map[string]interface{} 3186 Input map[string]string 3187 Result map[string]interface{} 3188 Err bool 3189 }{ 3190 /* 3191 * String decode 3192 */ 3193 3194 "no input on optional field with no config": { 3195 Schema: map[string]*Schema{ 3196 "availability_zone": { 3197 Type: TypeString, 3198 Optional: true, 3199 }, 3200 }, 3201 3202 Input: map[string]string{}, 3203 Result: map[string]interface{}{}, 3204 Err: false, 3205 }, 3206 3207 "input ignored when config has a value": { 3208 Schema: map[string]*Schema{ 3209 "availability_zone": { 3210 Type: TypeString, 3211 Optional: true, 3212 }, 3213 }, 3214 3215 Config: map[string]interface{}{ 3216 "availability_zone": "bar", 3217 }, 3218 3219 Input: map[string]string{ 3220 "availability_zone": "foo", 3221 }, 3222 3223 Result: map[string]interface{}{}, 3224 3225 Err: false, 3226 }, 3227 3228 "input ignored when schema has a default": { 3229 Schema: map[string]*Schema{ 3230 "availability_zone": { 3231 Type: TypeString, 3232 Default: "foo", 3233 Optional: true, 3234 }, 3235 }, 3236 3237 Input: map[string]string{ 3238 "availability_zone": "bar", 3239 }, 3240 3241 Result: map[string]interface{}{}, 3242 3243 Err: false, 3244 }, 3245 3246 "input ignored when default function returns a value": { 3247 Schema: map[string]*Schema{ 3248 "availability_zone": { 3249 Type: TypeString, 3250 DefaultFunc: func() (interface{}, error) { 3251 return "foo", nil 3252 }, 3253 Optional: true, 3254 }, 3255 }, 3256 3257 Input: map[string]string{ 3258 "availability_zone": "bar", 3259 }, 3260 3261 Result: map[string]interface{}{}, 3262 3263 Err: false, 3264 }, 3265 3266 "input ignored when default function returns an empty string": { 3267 Schema: map[string]*Schema{ 3268 "availability_zone": { 3269 Type: TypeString, 3270 Default: "", 3271 Optional: true, 3272 }, 3273 }, 3274 3275 Input: map[string]string{ 3276 "availability_zone": "bar", 3277 }, 3278 3279 Result: map[string]interface{}{}, 3280 3281 Err: false, 3282 }, 3283 3284 "input used when default function returns nil": { 3285 Schema: map[string]*Schema{ 3286 "availability_zone": { 3287 Type: TypeString, 3288 DefaultFunc: func() (interface{}, error) { 3289 return nil, nil 3290 }, 3291 Required: true, 3292 }, 3293 }, 3294 3295 Input: map[string]string{ 3296 "availability_zone": "bar", 3297 }, 3298 3299 Result: map[string]interface{}{ 3300 "availability_zone": "bar", 3301 }, 3302 3303 Err: false, 3304 }, 3305 3306 "input not used when optional default function returns nil": { 3307 Schema: map[string]*Schema{ 3308 "availability_zone": { 3309 Type: TypeString, 3310 DefaultFunc: func() (interface{}, error) { 3311 return nil, nil 3312 }, 3313 Optional: true, 3314 }, 3315 }, 3316 3317 Input: map[string]string{}, 3318 Result: map[string]interface{}{}, 3319 Err: false, 3320 }, 3321 } 3322 3323 for i, tc := range cases { 3324 if tc.Config == nil { 3325 tc.Config = make(map[string]interface{}) 3326 } 3327 3328 input := new(terraform.MockUIInput) 3329 input.InputReturnMap = tc.Input 3330 3331 rc := terraform.NewResourceConfigRaw(tc.Config) 3332 rc.Config = make(map[string]interface{}) 3333 3334 actual, err := schemaMap(tc.Schema).Input(input, rc) 3335 if err != nil != tc.Err { 3336 t.Fatalf("#%v err: %s", i, err) 3337 } 3338 3339 if !reflect.DeepEqual(tc.Result, actual.Config) { 3340 t.Fatalf("#%v: bad:\n\ngot: %#v\nexpected: %#v", i, actual.Config, tc.Result) 3341 } 3342 } 3343 } 3344 3345 func TestSchemaMap_InputDefault(t *testing.T) { 3346 emptyConfig := make(map[string]interface{}) 3347 rc := terraform.NewResourceConfigRaw(emptyConfig) 3348 rc.Config = make(map[string]interface{}) 3349 3350 input := new(terraform.MockUIInput) 3351 input.InputFn = func(opts *terraform.InputOpts) (string, error) { 3352 t.Fatalf("InputFn should not be called on: %#v", opts) 3353 return "", nil 3354 } 3355 3356 schema := map[string]*Schema{ 3357 "availability_zone": { 3358 Type: TypeString, 3359 Default: "foo", 3360 Optional: true, 3361 }, 3362 } 3363 actual, err := schemaMap(schema).Input(input, rc) 3364 if err != nil { 3365 t.Fatalf("err: %s", err) 3366 } 3367 3368 expected := map[string]interface{}{} 3369 3370 if !reflect.DeepEqual(expected, actual.Config) { 3371 t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected) 3372 } 3373 } 3374 3375 func TestSchemaMap_InputDeprecated(t *testing.T) { 3376 emptyConfig := make(map[string]interface{}) 3377 rc := terraform.NewResourceConfigRaw(emptyConfig) 3378 rc.Config = make(map[string]interface{}) 3379 3380 input := new(terraform.MockUIInput) 3381 input.InputFn = func(opts *terraform.InputOpts) (string, error) { 3382 t.Fatalf("InputFn should not be called on: %#v", opts) 3383 return "", nil 3384 } 3385 3386 schema := map[string]*Schema{ 3387 "availability_zone": { 3388 Type: TypeString, 3389 Deprecated: "long gone", 3390 Optional: true, 3391 }, 3392 } 3393 actual, err := schemaMap(schema).Input(input, rc) 3394 if err != nil { 3395 t.Fatalf("err: %s", err) 3396 } 3397 3398 expected := map[string]interface{}{} 3399 3400 if !reflect.DeepEqual(expected, actual.Config) { 3401 t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected) 3402 } 3403 } 3404 3405 func TestSchemaMap_InternalValidate(t *testing.T) { 3406 cases := map[string]struct { 3407 In map[string]*Schema 3408 Err bool 3409 }{ 3410 "nothing": { 3411 nil, 3412 false, 3413 }, 3414 3415 "Both optional and required": { 3416 map[string]*Schema{ 3417 "foo": { 3418 Type: TypeInt, 3419 Optional: true, 3420 Required: true, 3421 }, 3422 }, 3423 true, 3424 }, 3425 3426 "No optional and no required": { 3427 map[string]*Schema{ 3428 "foo": { 3429 Type: TypeInt, 3430 }, 3431 }, 3432 true, 3433 }, 3434 3435 "Missing Type": { 3436 map[string]*Schema{ 3437 "foo": { 3438 Required: true, 3439 }, 3440 }, 3441 true, 3442 }, 3443 3444 "Required but computed": { 3445 map[string]*Schema{ 3446 "foo": { 3447 Type: TypeInt, 3448 Required: true, 3449 Computed: true, 3450 }, 3451 }, 3452 true, 3453 }, 3454 3455 "Looks good": { 3456 map[string]*Schema{ 3457 "foo": { 3458 Type: TypeString, 3459 Required: true, 3460 }, 3461 }, 3462 false, 3463 }, 3464 3465 "Computed but has default": { 3466 map[string]*Schema{ 3467 "foo": { 3468 Type: TypeInt, 3469 Optional: true, 3470 Computed: true, 3471 Default: "foo", 3472 }, 3473 }, 3474 true, 3475 }, 3476 3477 "Required but has default": { 3478 map[string]*Schema{ 3479 "foo": { 3480 Type: TypeInt, 3481 Optional: true, 3482 Required: true, 3483 Default: "foo", 3484 }, 3485 }, 3486 true, 3487 }, 3488 3489 "List element not set": { 3490 map[string]*Schema{ 3491 "foo": { 3492 Type: TypeList, 3493 }, 3494 }, 3495 true, 3496 }, 3497 3498 "List default": { 3499 map[string]*Schema{ 3500 "foo": { 3501 Type: TypeList, 3502 Elem: &Schema{Type: TypeInt}, 3503 Default: "foo", 3504 }, 3505 }, 3506 true, 3507 }, 3508 3509 "List element computed": { 3510 map[string]*Schema{ 3511 "foo": { 3512 Type: TypeList, 3513 Optional: true, 3514 Elem: &Schema{ 3515 Type: TypeInt, 3516 Computed: true, 3517 }, 3518 }, 3519 }, 3520 true, 3521 }, 3522 3523 "List element with Set set": { 3524 map[string]*Schema{ 3525 "foo": { 3526 Type: TypeList, 3527 Elem: &Schema{Type: TypeInt}, 3528 Set: func(interface{}) int { return 0 }, 3529 Optional: true, 3530 }, 3531 }, 3532 true, 3533 }, 3534 3535 "Set element with no Set set": { 3536 map[string]*Schema{ 3537 "foo": { 3538 Type: TypeSet, 3539 Elem: &Schema{Type: TypeInt}, 3540 Optional: true, 3541 }, 3542 }, 3543 false, 3544 }, 3545 3546 "Required but computedWhen": { 3547 map[string]*Schema{ 3548 "foo": { 3549 Type: TypeInt, 3550 Required: true, 3551 ComputedWhen: []string{"foo"}, 3552 }, 3553 }, 3554 true, 3555 }, 3556 3557 "Conflicting attributes cannot be required": { 3558 map[string]*Schema{ 3559 "blacklist": { 3560 Type: TypeBool, 3561 Required: true, 3562 }, 3563 "whitelist": { 3564 Type: TypeBool, 3565 Optional: true, 3566 ConflictsWith: []string{"blacklist"}, 3567 }, 3568 }, 3569 true, 3570 }, 3571 3572 "Attribute with conflicts cannot be required": { 3573 map[string]*Schema{ 3574 "whitelist": { 3575 Type: TypeBool, 3576 Required: true, 3577 ConflictsWith: []string{"blacklist"}, 3578 }, 3579 }, 3580 true, 3581 }, 3582 3583 "ConflictsWith cannot be used w/ ComputedWhen": { 3584 map[string]*Schema{ 3585 "blacklist": { 3586 Type: TypeBool, 3587 ComputedWhen: []string{"foor"}, 3588 }, 3589 "whitelist": { 3590 Type: TypeBool, 3591 Required: true, 3592 ConflictsWith: []string{"blacklist"}, 3593 }, 3594 }, 3595 true, 3596 }, 3597 3598 "AtLeastOneOf list index syntax with self reference": { 3599 map[string]*Schema{ 3600 "config_block_attr": { 3601 Type: TypeList, 3602 Optional: true, 3603 MaxItems: 1, 3604 Elem: &Resource{ 3605 Schema: map[string]*Schema{ 3606 "nested_attr": { 3607 Type: TypeString, 3608 Optional: true, 3609 AtLeastOneOf: []string{"config_block_attr.0.nested_attr"}, 3610 }, 3611 }, 3612 }, 3613 }, 3614 }, 3615 false, 3616 }, 3617 3618 "AtLeastOneOf list index syntax with list configuration block existing attribute": { 3619 map[string]*Schema{ 3620 "config_block_attr": { 3621 Type: TypeList, 3622 Optional: true, 3623 MaxItems: 1, 3624 Elem: &Resource{ 3625 Schema: map[string]*Schema{ 3626 "nested_attr": { 3627 Type: TypeString, 3628 Optional: true, 3629 }, 3630 }, 3631 }, 3632 }, 3633 "test": { 3634 Type: TypeBool, 3635 Optional: true, 3636 AtLeastOneOf: []string{"config_block_attr.0.nested_attr"}, 3637 }, 3638 }, 3639 false, 3640 }, 3641 3642 "AtLeastOneOf list index syntax with list configuration block missing attribute": { 3643 map[string]*Schema{ 3644 "config_block_attr": { 3645 Type: TypeList, 3646 Optional: true, 3647 Elem: &Resource{ 3648 Schema: map[string]*Schema{ 3649 "nested_attr": { 3650 Type: TypeString, 3651 Optional: true, 3652 }, 3653 }, 3654 }, 3655 }, 3656 "test": { 3657 Type: TypeBool, 3658 Optional: true, 3659 AtLeastOneOf: []string{"config_block_attr.0.missing_attr"}, 3660 }, 3661 }, 3662 true, 3663 }, 3664 3665 "AtLeastOneOf list index syntax with list configuration block missing MaxItems": { 3666 map[string]*Schema{ 3667 "config_block_attr": { 3668 Type: TypeList, 3669 Optional: true, 3670 Elem: &Resource{ 3671 Schema: map[string]*Schema{ 3672 "nested_attr": { 3673 Type: TypeString, 3674 Optional: true, 3675 }, 3676 }, 3677 }, 3678 }, 3679 "test": { 3680 Type: TypeBool, 3681 Optional: true, 3682 AtLeastOneOf: []string{"config_block_attr.0.missing_attr"}, 3683 }, 3684 }, 3685 true, 3686 }, 3687 3688 "AtLeastOneOf list index syntax with set configuration block existing attribute": { 3689 map[string]*Schema{ 3690 "config_block_attr": { 3691 Type: TypeSet, 3692 Optional: true, 3693 Elem: &Resource{ 3694 Schema: map[string]*Schema{ 3695 "nested_attr": { 3696 Type: TypeString, 3697 Optional: true, 3698 }, 3699 }, 3700 }, 3701 }, 3702 "test": { 3703 Type: TypeBool, 3704 Optional: true, 3705 AtLeastOneOf: []string{"config_block_attr.0.nested_attr"}, 3706 }, 3707 }, 3708 true, 3709 }, 3710 3711 "AtLeastOneOf list index syntax with set configuration block missing attribute": { 3712 map[string]*Schema{ 3713 "config_block_attr": { 3714 Type: TypeSet, 3715 Optional: true, 3716 Elem: &Resource{ 3717 Schema: map[string]*Schema{ 3718 "nested_attr": { 3719 Type: TypeString, 3720 Optional: true, 3721 }, 3722 }, 3723 }, 3724 }, 3725 "test": { 3726 Type: TypeBool, 3727 Optional: true, 3728 AtLeastOneOf: []string{"config_block_attr.0.missing_attr"}, 3729 }, 3730 }, 3731 true, 3732 }, 3733 3734 "AtLeastOneOf map key syntax with list configuration block existing attribute": { 3735 map[string]*Schema{ 3736 "config_block_attr": { 3737 Type: TypeList, 3738 Optional: true, 3739 Elem: &Resource{ 3740 Schema: map[string]*Schema{ 3741 "nested_attr": { 3742 Type: TypeString, 3743 Optional: true, 3744 }, 3745 }, 3746 }, 3747 }, 3748 "test": { 3749 Type: TypeBool, 3750 Optional: true, 3751 AtLeastOneOf: []string{"config_block_attr.nested_attr"}, 3752 }, 3753 }, 3754 true, 3755 }, 3756 3757 "AtLeastOneOf map key syntax with list configuration block self reference": { 3758 map[string]*Schema{ 3759 "config_block_attr": { 3760 Type: TypeList, 3761 Optional: true, 3762 Elem: &Resource{ 3763 Schema: map[string]*Schema{ 3764 "nested_attr": { 3765 Type: TypeString, 3766 Optional: true, 3767 AtLeastOneOf: []string{"config_block_attr.nested_attr"}, 3768 }, 3769 }, 3770 }, 3771 }, 3772 }, 3773 true, 3774 }, 3775 3776 "AtLeastOneOf map key syntax with set configuration block existing attribute": { 3777 map[string]*Schema{ 3778 "config_block_attr": { 3779 Type: TypeSet, 3780 Optional: true, 3781 Elem: &Resource{ 3782 Schema: map[string]*Schema{ 3783 "nested_attr": { 3784 Type: TypeString, 3785 Optional: true, 3786 }, 3787 }, 3788 }, 3789 }, 3790 "test": { 3791 Type: TypeBool, 3792 Optional: true, 3793 AtLeastOneOf: []string{"config_block_attr.nested_attr"}, 3794 }, 3795 }, 3796 true, 3797 }, 3798 3799 "AtLeastOneOf map key syntax with set configuration block self reference": { 3800 map[string]*Schema{ 3801 "config_block_attr": { 3802 Type: TypeSet, 3803 Optional: true, 3804 Elem: &Resource{ 3805 Schema: map[string]*Schema{ 3806 "nested_attr": { 3807 Type: TypeString, 3808 Optional: true, 3809 AtLeastOneOf: []string{"config_block_attr.nested_attr"}, 3810 }, 3811 }, 3812 }, 3813 }, 3814 }, 3815 true, 3816 }, 3817 3818 "AtLeastOneOf map key syntax with map attribute": { 3819 map[string]*Schema{ 3820 "map_attr": { 3821 Type: TypeMap, 3822 Optional: true, 3823 Elem: &Schema{Type: TypeString}, 3824 }, 3825 "test": { 3826 Type: TypeBool, 3827 Optional: true, 3828 AtLeastOneOf: []string{"map_attr.some_key"}, 3829 }, 3830 }, 3831 true, 3832 }, 3833 3834 "AtLeastOneOf string syntax with list attribute": { 3835 map[string]*Schema{ 3836 "list_attr": { 3837 Type: TypeList, 3838 Optional: true, 3839 Elem: &Schema{Type: TypeString}, 3840 }, 3841 "test": { 3842 Type: TypeBool, 3843 Optional: true, 3844 AtLeastOneOf: []string{"list_attr"}, 3845 }, 3846 }, 3847 false, 3848 }, 3849 3850 "AtLeastOneOf string syntax with list configuration block": { 3851 map[string]*Schema{ 3852 "config_block_attr": { 3853 Type: TypeList, 3854 Optional: true, 3855 Elem: &Resource{ 3856 Schema: map[string]*Schema{ 3857 "nested_attr": { 3858 Type: TypeString, 3859 Optional: true, 3860 }, 3861 }, 3862 }, 3863 }, 3864 "test": { 3865 Type: TypeBool, 3866 Optional: true, 3867 AtLeastOneOf: []string{"config_block_attr"}, 3868 }, 3869 }, 3870 false, 3871 }, 3872 3873 "AtLeastOneOf string syntax with map attribute": { 3874 map[string]*Schema{ 3875 "map_attr": { 3876 Type: TypeMap, 3877 Optional: true, 3878 Elem: &Schema{Type: TypeString}, 3879 }, 3880 "test": { 3881 Type: TypeBool, 3882 Optional: true, 3883 AtLeastOneOf: []string{"map_attr"}, 3884 }, 3885 }, 3886 false, 3887 }, 3888 3889 "AtLeastOneOf string syntax with set attribute": { 3890 map[string]*Schema{ 3891 "set_attr": { 3892 Type: TypeSet, 3893 Optional: true, 3894 Elem: &Schema{Type: TypeString}, 3895 }, 3896 "test": { 3897 Type: TypeBool, 3898 Optional: true, 3899 AtLeastOneOf: []string{"set_attr"}, 3900 }, 3901 }, 3902 false, 3903 }, 3904 3905 "AtLeastOneOf string syntax with set configuration block": { 3906 map[string]*Schema{ 3907 "config_block_attr": { 3908 Type: TypeSet, 3909 Optional: true, 3910 Elem: &Resource{ 3911 Schema: map[string]*Schema{ 3912 "nested_attr": { 3913 Type: TypeString, 3914 Optional: true, 3915 }, 3916 }, 3917 }, 3918 }, 3919 "test": { 3920 Type: TypeBool, 3921 Optional: true, 3922 AtLeastOneOf: []string{"config_block_attr"}, 3923 }, 3924 }, 3925 false, 3926 }, 3927 3928 "AtLeastOneOf string syntax with self reference": { 3929 map[string]*Schema{ 3930 "test": { 3931 Type: TypeBool, 3932 Optional: true, 3933 AtLeastOneOf: []string{"test"}, 3934 }, 3935 }, 3936 false, 3937 }, 3938 3939 "ConflictsWith list index syntax with self reference": { 3940 map[string]*Schema{ 3941 "config_block_attr": { 3942 Type: TypeList, 3943 Optional: true, 3944 Elem: &Resource{ 3945 Schema: map[string]*Schema{ 3946 "nested_attr": { 3947 Type: TypeString, 3948 Optional: true, 3949 ConflictsWith: []string{"config_block_attr.0.nested_attr"}, 3950 }, 3951 }, 3952 }, 3953 }, 3954 }, 3955 true, 3956 }, 3957 3958 "ConflictsWith list index syntax with list configuration block existing attribute": { 3959 map[string]*Schema{ 3960 "config_block_attr": { 3961 Type: TypeList, 3962 Optional: true, 3963 MaxItems: 1, 3964 Elem: &Resource{ 3965 Schema: map[string]*Schema{ 3966 "nested_attr": { 3967 Type: TypeString, 3968 Optional: true, 3969 }, 3970 }, 3971 }, 3972 }, 3973 "test": { 3974 Type: TypeBool, 3975 Optional: true, 3976 ConflictsWith: []string{"config_block_attr.0.nested_attr"}, 3977 }, 3978 }, 3979 false, 3980 }, 3981 3982 "ConflictsWith list index syntax with list configuration block missing attribute": { 3983 map[string]*Schema{ 3984 "config_block_attr": { 3985 Type: TypeList, 3986 Optional: true, 3987 Elem: &Resource{ 3988 Schema: map[string]*Schema{ 3989 "nested_attr": { 3990 Type: TypeString, 3991 Optional: true, 3992 }, 3993 }, 3994 }, 3995 }, 3996 "test": { 3997 Type: TypeBool, 3998 Optional: true, 3999 ConflictsWith: []string{"config_block_attr.0.missing_attr"}, 4000 }, 4001 }, 4002 true, 4003 }, 4004 4005 "ConflictsWith list index syntax with list configuration block missing MaxItems": { 4006 map[string]*Schema{ 4007 "config_block_attr": { 4008 Type: TypeList, 4009 Optional: true, 4010 Elem: &Resource{ 4011 Schema: map[string]*Schema{ 4012 "nested_attr": { 4013 Type: TypeString, 4014 Optional: true, 4015 }, 4016 }, 4017 }, 4018 }, 4019 "test": { 4020 Type: TypeBool, 4021 Optional: true, 4022 ConflictsWith: []string{"config_block_attr.0.missing_attr"}, 4023 }, 4024 }, 4025 true, 4026 }, 4027 4028 "ConflictsWith list index syntax with set configuration block existing attribute": { 4029 map[string]*Schema{ 4030 "config_block_attr": { 4031 Type: TypeSet, 4032 Optional: true, 4033 Elem: &Resource{ 4034 Schema: map[string]*Schema{ 4035 "nested_attr": { 4036 Type: TypeString, 4037 Optional: true, 4038 }, 4039 }, 4040 }, 4041 }, 4042 "test": { 4043 Type: TypeBool, 4044 Optional: true, 4045 ConflictsWith: []string{"config_block_attr.0.nested_attr"}, 4046 }, 4047 }, 4048 true, 4049 }, 4050 4051 "ConflictsWith list index syntax with set configuration block missing attribute": { 4052 map[string]*Schema{ 4053 "config_block_attr": { 4054 Type: TypeSet, 4055 Optional: true, 4056 Elem: &Resource{ 4057 Schema: map[string]*Schema{ 4058 "nested_attr": { 4059 Type: TypeString, 4060 Optional: true, 4061 }, 4062 }, 4063 }, 4064 }, 4065 "test": { 4066 Type: TypeBool, 4067 Optional: true, 4068 ConflictsWith: []string{"config_block_attr.0.missing_attr"}, 4069 }, 4070 }, 4071 true, 4072 }, 4073 4074 "ConflictsWith map key syntax with list configuration block existing attribute": { 4075 map[string]*Schema{ 4076 "config_block_attr": { 4077 Type: TypeList, 4078 Optional: true, 4079 Elem: &Resource{ 4080 Schema: map[string]*Schema{ 4081 "nested_attr": { 4082 Type: TypeString, 4083 Optional: true, 4084 }, 4085 }, 4086 }, 4087 }, 4088 "test": { 4089 Type: TypeBool, 4090 Optional: true, 4091 ConflictsWith: []string{"config_block_attr.nested_attr"}, 4092 }, 4093 }, 4094 true, 4095 }, 4096 4097 "ConflictsWith map key syntax with list configuration block self reference": { 4098 map[string]*Schema{ 4099 "config_block_attr": { 4100 Type: TypeList, 4101 Optional: true, 4102 Elem: &Resource{ 4103 Schema: map[string]*Schema{ 4104 "nested_attr": { 4105 Type: TypeString, 4106 Optional: true, 4107 ConflictsWith: []string{"config_block_attr.nested_attr"}, 4108 }, 4109 }, 4110 }, 4111 }, 4112 }, 4113 true, 4114 }, 4115 4116 "ConflictsWith map key syntax with set configuration block existing attribute": { 4117 map[string]*Schema{ 4118 "config_block_attr": { 4119 Type: TypeSet, 4120 Optional: true, 4121 Elem: &Resource{ 4122 Schema: map[string]*Schema{ 4123 "nested_attr": { 4124 Type: TypeString, 4125 Optional: true, 4126 }, 4127 }, 4128 }, 4129 }, 4130 "test": { 4131 Type: TypeBool, 4132 Optional: true, 4133 ConflictsWith: []string{"config_block_attr.nested_attr"}, 4134 }, 4135 }, 4136 true, 4137 }, 4138 4139 "ConflictsWith map key syntax with set configuration block self reference": { 4140 map[string]*Schema{ 4141 "config_block_attr": { 4142 Type: TypeSet, 4143 Optional: true, 4144 Elem: &Resource{ 4145 Schema: map[string]*Schema{ 4146 "nested_attr": { 4147 Type: TypeString, 4148 Optional: true, 4149 ConflictsWith: []string{"config_block_attr.nested_attr"}, 4150 }, 4151 }, 4152 }, 4153 }, 4154 }, 4155 true, 4156 }, 4157 4158 "ConflictsWith map key syntax with map attribute": { 4159 map[string]*Schema{ 4160 "map_attr": { 4161 Type: TypeMap, 4162 Optional: true, 4163 Elem: &Schema{Type: TypeString}, 4164 }, 4165 "test": { 4166 Type: TypeBool, 4167 Optional: true, 4168 ConflictsWith: []string{"map_attr.some_key"}, 4169 }, 4170 }, 4171 true, 4172 }, 4173 4174 "ConflictsWith string syntax with list attribute": { 4175 map[string]*Schema{ 4176 "list_attr": { 4177 Type: TypeList, 4178 Optional: true, 4179 Elem: &Schema{Type: TypeString}, 4180 }, 4181 "test": { 4182 Type: TypeBool, 4183 Optional: true, 4184 ConflictsWith: []string{"list_attr"}, 4185 }, 4186 }, 4187 false, 4188 }, 4189 4190 "ConflictsWith string syntax with list configuration block": { 4191 map[string]*Schema{ 4192 "config_block_attr": { 4193 Type: TypeList, 4194 Optional: true, 4195 Elem: &Resource{ 4196 Schema: map[string]*Schema{ 4197 "nested_attr": { 4198 Type: TypeString, 4199 Optional: true, 4200 }, 4201 }, 4202 }, 4203 }, 4204 "test": { 4205 Type: TypeBool, 4206 Optional: true, 4207 ConflictsWith: []string{"config_block_attr"}, 4208 }, 4209 }, 4210 false, 4211 }, 4212 4213 "ConflictsWith string syntax with map attribute": { 4214 map[string]*Schema{ 4215 "map_attr": { 4216 Type: TypeMap, 4217 Optional: true, 4218 Elem: &Schema{Type: TypeString}, 4219 }, 4220 "test": { 4221 Type: TypeBool, 4222 Optional: true, 4223 ConflictsWith: []string{"map_attr"}, 4224 }, 4225 }, 4226 false, 4227 }, 4228 4229 "ConflictsWith string syntax with set attribute": { 4230 map[string]*Schema{ 4231 "set_attr": { 4232 Type: TypeSet, 4233 Optional: true, 4234 Elem: &Schema{Type: TypeString}, 4235 }, 4236 "test": { 4237 Type: TypeBool, 4238 Optional: true, 4239 ConflictsWith: []string{"set_attr"}, 4240 }, 4241 }, 4242 false, 4243 }, 4244 4245 "ConflictsWith string syntax with set configuration block": { 4246 map[string]*Schema{ 4247 "config_block_attr": { 4248 Type: TypeSet, 4249 Optional: true, 4250 Elem: &Resource{ 4251 Schema: map[string]*Schema{ 4252 "nested_attr": { 4253 Type: TypeString, 4254 Optional: true, 4255 }, 4256 }, 4257 }, 4258 }, 4259 "test": { 4260 Type: TypeBool, 4261 Optional: true, 4262 ConflictsWith: []string{"config_block_attr"}, 4263 }, 4264 }, 4265 false, 4266 }, 4267 4268 "ConflictsWith string syntax with self reference": { 4269 map[string]*Schema{ 4270 "test": { 4271 Type: TypeBool, 4272 Optional: true, 4273 ConflictsWith: []string{"test"}, 4274 }, 4275 }, 4276 true, 4277 }, 4278 4279 "ExactlyOneOf list index syntax with self reference": { 4280 map[string]*Schema{ 4281 "config_block_attr": { 4282 Type: TypeList, 4283 Optional: true, 4284 MaxItems: 1, 4285 Elem: &Resource{ 4286 Schema: map[string]*Schema{ 4287 "nested_attr": { 4288 Type: TypeString, 4289 Optional: true, 4290 ExactlyOneOf: []string{"config_block_attr.0.nested_attr"}, 4291 }, 4292 }, 4293 }, 4294 }, 4295 }, 4296 false, 4297 }, 4298 4299 "ExactlyOneOf list index syntax with list configuration block existing attribute": { 4300 map[string]*Schema{ 4301 "config_block_attr": { 4302 Type: TypeList, 4303 Optional: true, 4304 MaxItems: 1, 4305 Elem: &Resource{ 4306 Schema: map[string]*Schema{ 4307 "nested_attr": { 4308 Type: TypeString, 4309 Optional: true, 4310 }, 4311 }, 4312 }, 4313 }, 4314 "test": { 4315 Type: TypeBool, 4316 Optional: true, 4317 ExactlyOneOf: []string{"config_block_attr.0.nested_attr"}, 4318 }, 4319 }, 4320 false, 4321 }, 4322 4323 "ExactlyOneOf list index syntax with list configuration block missing attribute": { 4324 map[string]*Schema{ 4325 "config_block_attr": { 4326 Type: TypeList, 4327 Optional: true, 4328 Elem: &Resource{ 4329 Schema: map[string]*Schema{ 4330 "nested_attr": { 4331 Type: TypeString, 4332 Optional: true, 4333 }, 4334 }, 4335 }, 4336 }, 4337 "test": { 4338 Type: TypeBool, 4339 Optional: true, 4340 ExactlyOneOf: []string{"config_block_attr.0.missing_attr"}, 4341 }, 4342 }, 4343 true, 4344 }, 4345 4346 "ExactlyOneOf list index syntax with list configuration block missing MaxItems": { 4347 map[string]*Schema{ 4348 "config_block_attr": { 4349 Type: TypeList, 4350 Optional: true, 4351 Elem: &Resource{ 4352 Schema: map[string]*Schema{ 4353 "nested_attr": { 4354 Type: TypeString, 4355 Optional: true, 4356 }, 4357 }, 4358 }, 4359 }, 4360 "test": { 4361 Type: TypeBool, 4362 Optional: true, 4363 ExactlyOneOf: []string{"config_block_attr.0.missing_attr"}, 4364 }, 4365 }, 4366 true, 4367 }, 4368 4369 "ExactlyOneOf list index syntax with set configuration block existing attribute": { 4370 map[string]*Schema{ 4371 "config_block_attr": { 4372 Type: TypeSet, 4373 Optional: true, 4374 Elem: &Resource{ 4375 Schema: map[string]*Schema{ 4376 "nested_attr": { 4377 Type: TypeString, 4378 Optional: true, 4379 }, 4380 }, 4381 }, 4382 }, 4383 "test": { 4384 Type: TypeBool, 4385 Optional: true, 4386 ExactlyOneOf: []string{"config_block_attr.0.nested_attr"}, 4387 }, 4388 }, 4389 true, 4390 }, 4391 4392 "ExactlyOneOf list index syntax with set configuration block missing attribute": { 4393 map[string]*Schema{ 4394 "config_block_attr": { 4395 Type: TypeSet, 4396 Optional: true, 4397 Elem: &Resource{ 4398 Schema: map[string]*Schema{ 4399 "nested_attr": { 4400 Type: TypeString, 4401 Optional: true, 4402 }, 4403 }, 4404 }, 4405 }, 4406 "test": { 4407 Type: TypeBool, 4408 Optional: true, 4409 ExactlyOneOf: []string{"config_block_attr.0.missing_attr"}, 4410 }, 4411 }, 4412 true, 4413 }, 4414 4415 "ExactlyOneOf map key syntax with list configuration block existing attribute": { 4416 map[string]*Schema{ 4417 "config_block_attr": { 4418 Type: TypeList, 4419 Optional: true, 4420 Elem: &Resource{ 4421 Schema: map[string]*Schema{ 4422 "nested_attr": { 4423 Type: TypeString, 4424 Optional: true, 4425 }, 4426 }, 4427 }, 4428 }, 4429 "test": { 4430 Type: TypeBool, 4431 Optional: true, 4432 ExactlyOneOf: []string{"config_block_attr.nested_attr"}, 4433 }, 4434 }, 4435 true, 4436 }, 4437 4438 "ExactlyOneOf map key syntax with list configuration block self reference": { 4439 map[string]*Schema{ 4440 "config_block_attr": { 4441 Type: TypeList, 4442 Optional: true, 4443 Elem: &Resource{ 4444 Schema: map[string]*Schema{ 4445 "nested_attr": { 4446 Type: TypeString, 4447 Optional: true, 4448 ExactlyOneOf: []string{"config_block_attr.nested_attr"}, 4449 }, 4450 }, 4451 }, 4452 }, 4453 }, 4454 true, 4455 }, 4456 4457 "ExactlyOneOf map key syntax with set configuration block existing attribute": { 4458 map[string]*Schema{ 4459 "config_block_attr": { 4460 Type: TypeSet, 4461 Optional: true, 4462 Elem: &Resource{ 4463 Schema: map[string]*Schema{ 4464 "nested_attr": { 4465 Type: TypeString, 4466 Optional: true, 4467 }, 4468 }, 4469 }, 4470 }, 4471 "test": { 4472 Type: TypeBool, 4473 Optional: true, 4474 ExactlyOneOf: []string{"config_block_attr.nested_attr"}, 4475 }, 4476 }, 4477 true, 4478 }, 4479 4480 "ExactlyOneOf map key syntax with set configuration block self reference": { 4481 map[string]*Schema{ 4482 "config_block_attr": { 4483 Type: TypeSet, 4484 Optional: true, 4485 Elem: &Resource{ 4486 Schema: map[string]*Schema{ 4487 "nested_attr": { 4488 Type: TypeString, 4489 Optional: true, 4490 ExactlyOneOf: []string{"config_block_attr.nested_attr"}, 4491 }, 4492 }, 4493 }, 4494 }, 4495 }, 4496 true, 4497 }, 4498 4499 "ExactlyOneOf map key syntax with map attribute": { 4500 map[string]*Schema{ 4501 "map_attr": { 4502 Type: TypeMap, 4503 Optional: true, 4504 Elem: &Schema{Type: TypeString}, 4505 }, 4506 "test": { 4507 Type: TypeBool, 4508 Optional: true, 4509 ExactlyOneOf: []string{"map_attr.some_key"}, 4510 }, 4511 }, 4512 true, 4513 }, 4514 4515 "ExactlyOneOf string syntax with list attribute": { 4516 map[string]*Schema{ 4517 "list_attr": { 4518 Type: TypeList, 4519 Optional: true, 4520 Elem: &Schema{Type: TypeString}, 4521 }, 4522 "test": { 4523 Type: TypeBool, 4524 Optional: true, 4525 ExactlyOneOf: []string{"list_attr"}, 4526 }, 4527 }, 4528 false, 4529 }, 4530 4531 "ExactlyOneOf string syntax with list configuration block": { 4532 map[string]*Schema{ 4533 "config_block_attr": { 4534 Type: TypeList, 4535 Optional: true, 4536 Elem: &Resource{ 4537 Schema: map[string]*Schema{ 4538 "nested_attr": { 4539 Type: TypeString, 4540 Optional: true, 4541 }, 4542 }, 4543 }, 4544 }, 4545 "test": { 4546 Type: TypeBool, 4547 Optional: true, 4548 ExactlyOneOf: []string{"config_block_attr"}, 4549 }, 4550 }, 4551 false, 4552 }, 4553 4554 "ExactlyOneOf string syntax with map attribute": { 4555 map[string]*Schema{ 4556 "map_attr": { 4557 Type: TypeMap, 4558 Optional: true, 4559 Elem: &Schema{Type: TypeString}, 4560 }, 4561 "test": { 4562 Type: TypeBool, 4563 Optional: true, 4564 ExactlyOneOf: []string{"map_attr"}, 4565 }, 4566 }, 4567 false, 4568 }, 4569 4570 "ExactlyOneOf string syntax with set attribute": { 4571 map[string]*Schema{ 4572 "set_attr": { 4573 Type: TypeSet, 4574 Optional: true, 4575 Elem: &Schema{Type: TypeString}, 4576 }, 4577 "test": { 4578 Type: TypeBool, 4579 Optional: true, 4580 ExactlyOneOf: []string{"set_attr"}, 4581 }, 4582 }, 4583 false, 4584 }, 4585 4586 "ExactlyOneOf string syntax with set configuration block": { 4587 map[string]*Schema{ 4588 "config_block_attr": { 4589 Type: TypeSet, 4590 Optional: true, 4591 Elem: &Resource{ 4592 Schema: map[string]*Schema{ 4593 "nested_attr": { 4594 Type: TypeString, 4595 Optional: true, 4596 }, 4597 }, 4598 }, 4599 }, 4600 "test": { 4601 Type: TypeBool, 4602 Optional: true, 4603 ExactlyOneOf: []string{"config_block_attr"}, 4604 }, 4605 }, 4606 false, 4607 }, 4608 4609 "ExactlyOneOf string syntax with self reference": { 4610 map[string]*Schema{ 4611 "test": { 4612 Type: TypeBool, 4613 Optional: true, 4614 AtLeastOneOf: []string{"test"}, 4615 }, 4616 }, 4617 false, 4618 }, 4619 4620 "RequiredWith list index syntax with self reference": { 4621 map[string]*Schema{ 4622 "config_block_attr": { 4623 Type: TypeList, 4624 Optional: true, 4625 MaxItems: 1, 4626 Elem: &Resource{ 4627 Schema: map[string]*Schema{ 4628 "nested_attr": { 4629 Type: TypeString, 4630 Optional: true, 4631 RequiredWith: []string{"config_block_attr.0.nested_attr"}, 4632 }, 4633 }, 4634 }, 4635 }, 4636 }, 4637 false, 4638 }, 4639 4640 "RequiredWith list index syntax with list configuration block existing attribute": { 4641 map[string]*Schema{ 4642 "config_block_attr": { 4643 Type: TypeList, 4644 Optional: true, 4645 MaxItems: 1, 4646 Elem: &Resource{ 4647 Schema: map[string]*Schema{ 4648 "nested_attr": { 4649 Type: TypeString, 4650 Optional: true, 4651 }, 4652 }, 4653 }, 4654 }, 4655 "test": { 4656 Type: TypeBool, 4657 Optional: true, 4658 RequiredWith: []string{"config_block_attr.0.nested_attr"}, 4659 }, 4660 }, 4661 false, 4662 }, 4663 4664 "RequiredWith list index syntax with list configuration block missing attribute": { 4665 map[string]*Schema{ 4666 "config_block_attr": { 4667 Type: TypeList, 4668 Optional: true, 4669 Elem: &Resource{ 4670 Schema: map[string]*Schema{ 4671 "nested_attr": { 4672 Type: TypeString, 4673 Optional: true, 4674 }, 4675 }, 4676 }, 4677 }, 4678 "test": { 4679 Type: TypeBool, 4680 Optional: true, 4681 RequiredWith: []string{"config_block_attr.0.missing_attr"}, 4682 }, 4683 }, 4684 true, 4685 }, 4686 4687 "RequiredWith list index syntax with list configuration block missing MaxItems": { 4688 map[string]*Schema{ 4689 "config_block_attr": { 4690 Type: TypeList, 4691 Optional: true, 4692 Elem: &Resource{ 4693 Schema: map[string]*Schema{ 4694 "nested_attr": { 4695 Type: TypeString, 4696 Optional: true, 4697 }, 4698 }, 4699 }, 4700 }, 4701 "test": { 4702 Type: TypeBool, 4703 Optional: true, 4704 RequiredWith: []string{"config_block_attr.0.missing_attr"}, 4705 }, 4706 }, 4707 true, 4708 }, 4709 4710 "RequiredWith list index syntax with set configuration block existing attribute": { 4711 map[string]*Schema{ 4712 "config_block_attr": { 4713 Type: TypeSet, 4714 Optional: true, 4715 Elem: &Resource{ 4716 Schema: map[string]*Schema{ 4717 "nested_attr": { 4718 Type: TypeString, 4719 Optional: true, 4720 }, 4721 }, 4722 }, 4723 }, 4724 "test": { 4725 Type: TypeBool, 4726 Optional: true, 4727 RequiredWith: []string{"config_block_attr.0.nested_attr"}, 4728 }, 4729 }, 4730 true, 4731 }, 4732 4733 "RequiredWith list index syntax with set configuration block missing attribute": { 4734 map[string]*Schema{ 4735 "config_block_attr": { 4736 Type: TypeSet, 4737 Optional: true, 4738 Elem: &Resource{ 4739 Schema: map[string]*Schema{ 4740 "nested_attr": { 4741 Type: TypeString, 4742 Optional: true, 4743 }, 4744 }, 4745 }, 4746 }, 4747 "test": { 4748 Type: TypeBool, 4749 Optional: true, 4750 RequiredWith: []string{"config_block_attr.0.missing_attr"}, 4751 }, 4752 }, 4753 true, 4754 }, 4755 4756 "RequiredWith map key syntax with list configuration block existing attribute": { 4757 map[string]*Schema{ 4758 "config_block_attr": { 4759 Type: TypeList, 4760 Optional: true, 4761 Elem: &Resource{ 4762 Schema: map[string]*Schema{ 4763 "nested_attr": { 4764 Type: TypeString, 4765 Optional: true, 4766 }, 4767 }, 4768 }, 4769 }, 4770 "test": { 4771 Type: TypeBool, 4772 Optional: true, 4773 RequiredWith: []string{"config_block_attr.nested_attr"}, 4774 }, 4775 }, 4776 true, 4777 }, 4778 4779 "RequiredWith map key syntax with list configuration block self reference": { 4780 map[string]*Schema{ 4781 "config_block_attr": { 4782 Type: TypeList, 4783 Optional: true, 4784 Elem: &Resource{ 4785 Schema: map[string]*Schema{ 4786 "nested_attr": { 4787 Type: TypeString, 4788 Optional: true, 4789 RequiredWith: []string{"config_block_attr.nested_attr"}, 4790 }, 4791 }, 4792 }, 4793 }, 4794 }, 4795 true, 4796 }, 4797 4798 "RequiredWith map key syntax with set configuration block existing attribute": { 4799 map[string]*Schema{ 4800 "config_block_attr": { 4801 Type: TypeSet, 4802 Optional: true, 4803 Elem: &Resource{ 4804 Schema: map[string]*Schema{ 4805 "nested_attr": { 4806 Type: TypeString, 4807 Optional: true, 4808 }, 4809 }, 4810 }, 4811 }, 4812 "test": { 4813 Type: TypeBool, 4814 Optional: true, 4815 RequiredWith: []string{"config_block_attr.nested_attr"}, 4816 }, 4817 }, 4818 true, 4819 }, 4820 4821 "RequiredWith map key syntax with set configuration block self reference": { 4822 map[string]*Schema{ 4823 "config_block_attr": { 4824 Type: TypeSet, 4825 Optional: true, 4826 Elem: &Resource{ 4827 Schema: map[string]*Schema{ 4828 "nested_attr": { 4829 Type: TypeString, 4830 Optional: true, 4831 RequiredWith: []string{"config_block_attr.nested_attr"}, 4832 }, 4833 }, 4834 }, 4835 }, 4836 }, 4837 true, 4838 }, 4839 4840 "RequiredWith map key syntax with map attribute": { 4841 map[string]*Schema{ 4842 "map_attr": { 4843 Type: TypeMap, 4844 Optional: true, 4845 Elem: &Schema{Type: TypeString}, 4846 }, 4847 "test": { 4848 Type: TypeBool, 4849 Optional: true, 4850 RequiredWith: []string{"map_attr.some_key"}, 4851 }, 4852 }, 4853 true, 4854 }, 4855 4856 "RequiredWith string syntax with list attribute": { 4857 map[string]*Schema{ 4858 "list_attr": { 4859 Type: TypeList, 4860 Optional: true, 4861 Elem: &Schema{Type: TypeString}, 4862 }, 4863 "test": { 4864 Type: TypeBool, 4865 Optional: true, 4866 RequiredWith: []string{"list_attr"}, 4867 }, 4868 }, 4869 false, 4870 }, 4871 4872 "RequiredWith string syntax with list configuration block": { 4873 map[string]*Schema{ 4874 "config_block_attr": { 4875 Type: TypeList, 4876 Optional: true, 4877 Elem: &Resource{ 4878 Schema: map[string]*Schema{ 4879 "nested_attr": { 4880 Type: TypeString, 4881 Optional: true, 4882 }, 4883 }, 4884 }, 4885 }, 4886 "test": { 4887 Type: TypeBool, 4888 Optional: true, 4889 RequiredWith: []string{"config_block_attr"}, 4890 }, 4891 }, 4892 false, 4893 }, 4894 4895 "RequiredWith string syntax with map attribute": { 4896 map[string]*Schema{ 4897 "map_attr": { 4898 Type: TypeMap, 4899 Optional: true, 4900 Elem: &Schema{Type: TypeString}, 4901 }, 4902 "test": { 4903 Type: TypeBool, 4904 Optional: true, 4905 RequiredWith: []string{"map_attr"}, 4906 }, 4907 }, 4908 false, 4909 }, 4910 4911 "RequiredWith string syntax with set attribute": { 4912 map[string]*Schema{ 4913 "set_attr": { 4914 Type: TypeSet, 4915 Optional: true, 4916 Elem: &Schema{Type: TypeString}, 4917 }, 4918 "test": { 4919 Type: TypeBool, 4920 Optional: true, 4921 RequiredWith: []string{"set_attr"}, 4922 }, 4923 }, 4924 false, 4925 }, 4926 4927 "RequiredWith string syntax with set configuration block": { 4928 map[string]*Schema{ 4929 "config_block_attr": { 4930 Type: TypeSet, 4931 Optional: true, 4932 Elem: &Resource{ 4933 Schema: map[string]*Schema{ 4934 "nested_attr": { 4935 Type: TypeString, 4936 Optional: true, 4937 }, 4938 }, 4939 }, 4940 }, 4941 "test": { 4942 Type: TypeBool, 4943 Optional: true, 4944 RequiredWith: []string{"config_block_attr"}, 4945 }, 4946 }, 4947 false, 4948 }, 4949 4950 "RequiredWith string syntax with self reference": { 4951 map[string]*Schema{ 4952 "test": { 4953 Type: TypeBool, 4954 Optional: true, 4955 RequiredWith: []string{"test"}, 4956 }, 4957 }, 4958 false, 4959 }, 4960 4961 "Sub-resource invalid": { 4962 map[string]*Schema{ 4963 "foo": { 4964 Type: TypeList, 4965 Optional: true, 4966 Elem: &Resource{ 4967 Schema: map[string]*Schema{ 4968 "foo": new(Schema), 4969 }, 4970 }, 4971 }, 4972 }, 4973 true, 4974 }, 4975 4976 "Sub-resource valid": { 4977 map[string]*Schema{ 4978 "foo": { 4979 Type: TypeList, 4980 Optional: true, 4981 Elem: &Resource{ 4982 Schema: map[string]*Schema{ 4983 "foo": { 4984 Type: TypeInt, 4985 Optional: true, 4986 }, 4987 }, 4988 }, 4989 }, 4990 }, 4991 false, 4992 }, 4993 4994 "ValidateFunc on non-primitive": { 4995 map[string]*Schema{ 4996 "foo": { 4997 Type: TypeSet, 4998 Required: true, 4999 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 5000 return 5001 }, 5002 }, 5003 }, 5004 true, 5005 }, 5006 5007 "computed-only field with validateFunc": { 5008 map[string]*Schema{ 5009 "string": { 5010 Type: TypeString, 5011 Computed: true, 5012 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 5013 es = append(es, fmt.Errorf("this is not fine")) 5014 return 5015 }, 5016 }, 5017 }, 5018 true, 5019 }, 5020 5021 "computed-only field with diffSuppressFunc": { 5022 map[string]*Schema{ 5023 "string": { 5024 Type: TypeString, 5025 Computed: true, 5026 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 5027 // Always suppress any diff 5028 return false 5029 }, 5030 }, 5031 }, 5032 true, 5033 }, 5034 5035 "invalid field name format #1": { 5036 map[string]*Schema{ 5037 "with space": { 5038 Type: TypeString, 5039 Optional: true, 5040 }, 5041 }, 5042 true, 5043 }, 5044 5045 "invalid field name format #2": { 5046 map[string]*Schema{ 5047 "WithCapitals": { 5048 Type: TypeString, 5049 Optional: true, 5050 }, 5051 }, 5052 true, 5053 }, 5054 5055 "invalid field name format of a Deprecated field": { 5056 map[string]*Schema{ 5057 "WithCapitals": { 5058 Type: TypeString, 5059 Optional: true, 5060 Deprecated: "Use with_underscores instead", 5061 }, 5062 }, 5063 false, 5064 }, 5065 5066 "invalid field name format of a Removed field": { 5067 map[string]*Schema{ 5068 "WithCapitals": { 5069 Type: TypeString, 5070 Optional: true, 5071 Removed: "Use with_underscores instead", 5072 }, 5073 }, 5074 false, 5075 }, 5076 5077 "ConfigModeBlock with Elem *Resource": { 5078 map[string]*Schema{ 5079 "block": { 5080 Type: TypeList, 5081 ConfigMode: SchemaConfigModeBlock, 5082 Optional: true, 5083 Elem: &Resource{}, 5084 }, 5085 }, 5086 false, 5087 }, 5088 5089 "ConfigModeBlock Computed with Elem *Resource": { 5090 map[string]*Schema{ 5091 "block": { 5092 Type: TypeList, 5093 ConfigMode: SchemaConfigModeBlock, 5094 Computed: true, 5095 Elem: &Resource{}, 5096 }, 5097 }, 5098 true, // ConfigMode of block cannot be used for computed schema 5099 }, 5100 5101 "ConfigModeBlock with Elem *Schema": { 5102 map[string]*Schema{ 5103 "block": { 5104 Type: TypeList, 5105 ConfigMode: SchemaConfigModeBlock, 5106 Optional: true, 5107 Elem: &Schema{ 5108 Type: TypeString, 5109 }, 5110 }, 5111 }, 5112 true, 5113 }, 5114 5115 "ConfigModeBlock with no Elem": { 5116 map[string]*Schema{ 5117 "block": { 5118 Type: TypeString, 5119 ConfigMode: SchemaConfigModeBlock, 5120 Optional: true, 5121 }, 5122 }, 5123 true, 5124 }, 5125 5126 "ConfigModeBlock inside ConfigModeAttr": { 5127 map[string]*Schema{ 5128 "block": { 5129 Type: TypeList, 5130 ConfigMode: SchemaConfigModeAttr, 5131 Optional: true, 5132 Elem: &Resource{ 5133 Schema: map[string]*Schema{ 5134 "sub": { 5135 Type: TypeList, 5136 ConfigMode: SchemaConfigModeBlock, 5137 Elem: &Resource{}, 5138 }, 5139 }, 5140 }, 5141 }, 5142 }, 5143 true, // ConfigMode of block cannot be used in child of schema with ConfigMode of attribute 5144 }, 5145 5146 "ConfigModeAuto with *Resource inside ConfigModeAttr": { 5147 map[string]*Schema{ 5148 "block": { 5149 Type: TypeList, 5150 ConfigMode: SchemaConfigModeAttr, 5151 Optional: true, 5152 Elem: &Resource{ 5153 Schema: map[string]*Schema{ 5154 "sub": { 5155 Type: TypeList, 5156 Elem: &Resource{}, 5157 }, 5158 }, 5159 }, 5160 }, 5161 }, 5162 true, // in *schema.Resource with ConfigMode of attribute, so must also have ConfigMode of attribute 5163 }, 5164 } 5165 5166 for tn, tc := range cases { 5167 t.Run(tn, func(t *testing.T) { 5168 err := schemaMap(tc.In).InternalValidate(nil) 5169 if err != nil != tc.Err { 5170 if tc.Err { 5171 t.Fatalf("%q: Expected error did not occur:\n\n%#v", tn, tc.In) 5172 } 5173 t.Fatalf("%q: Unexpected error occurred: %s\n\n%#v", tn, err, tc.In) 5174 } 5175 }) 5176 } 5177 5178 } 5179 5180 func TestSchemaMap_DiffSuppress(t *testing.T) { 5181 cases := map[string]struct { 5182 Schema map[string]*Schema 5183 State *terraform.InstanceState 5184 Config map[string]interface{} 5185 ExpectedDiff *terraform.InstanceDiff 5186 Err bool 5187 }{ 5188 "#0 - Suppress otherwise valid diff by returning true": { 5189 Schema: map[string]*Schema{ 5190 "availability_zone": { 5191 Type: TypeString, 5192 Optional: true, 5193 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 5194 // Always suppress any diff 5195 return true 5196 }, 5197 }, 5198 }, 5199 5200 State: nil, 5201 5202 Config: map[string]interface{}{ 5203 "availability_zone": "foo", 5204 }, 5205 5206 ExpectedDiff: nil, 5207 5208 Err: false, 5209 }, 5210 5211 "#1 - Don't suppress diff by returning false": { 5212 Schema: map[string]*Schema{ 5213 "availability_zone": { 5214 Type: TypeString, 5215 Optional: true, 5216 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 5217 // Always suppress any diff 5218 return false 5219 }, 5220 }, 5221 }, 5222 5223 State: nil, 5224 5225 Config: map[string]interface{}{ 5226 "availability_zone": "foo", 5227 }, 5228 5229 ExpectedDiff: &terraform.InstanceDiff{ 5230 Attributes: map[string]*terraform.ResourceAttrDiff{ 5231 "availability_zone": { 5232 Old: "", 5233 New: "foo", 5234 }, 5235 }, 5236 }, 5237 5238 Err: false, 5239 }, 5240 5241 "Default with suppress makes no diff": { 5242 Schema: map[string]*Schema{ 5243 "availability_zone": { 5244 Type: TypeString, 5245 Optional: true, 5246 Default: "foo", 5247 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 5248 return true 5249 }, 5250 }, 5251 }, 5252 5253 State: nil, 5254 5255 Config: map[string]interface{}{}, 5256 5257 ExpectedDiff: nil, 5258 5259 Err: false, 5260 }, 5261 5262 "Default with false suppress makes diff": { 5263 Schema: map[string]*Schema{ 5264 "availability_zone": { 5265 Type: TypeString, 5266 Optional: true, 5267 Default: "foo", 5268 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 5269 return false 5270 }, 5271 }, 5272 }, 5273 5274 State: nil, 5275 5276 Config: map[string]interface{}{}, 5277 5278 ExpectedDiff: &terraform.InstanceDiff{ 5279 Attributes: map[string]*terraform.ResourceAttrDiff{ 5280 "availability_zone": { 5281 Old: "", 5282 New: "foo", 5283 }, 5284 }, 5285 }, 5286 5287 Err: false, 5288 }, 5289 5290 "Complex structure with set of computed string should mark root set as computed": { 5291 Schema: map[string]*Schema{ 5292 "outer": { 5293 Type: TypeSet, 5294 Optional: true, 5295 Elem: &Resource{ 5296 Schema: map[string]*Schema{ 5297 "outer_str": { 5298 Type: TypeString, 5299 Optional: true, 5300 }, 5301 "inner": { 5302 Type: TypeSet, 5303 Optional: true, 5304 Elem: &Resource{ 5305 Schema: map[string]*Schema{ 5306 "inner_str": { 5307 Type: TypeString, 5308 Optional: true, 5309 }, 5310 }, 5311 }, 5312 Set: func(v interface{}) int { 5313 return 2 5314 }, 5315 }, 5316 }, 5317 }, 5318 Set: func(v interface{}) int { 5319 return 1 5320 }, 5321 }, 5322 }, 5323 5324 State: nil, 5325 5326 Config: map[string]interface{}{ 5327 "outer": []interface{}{ 5328 map[string]interface{}{ 5329 "outer_str": "foo", 5330 "inner": []interface{}{ 5331 map[string]interface{}{ 5332 "inner_str": hcl2shim.UnknownVariableValue, 5333 }, 5334 }, 5335 }, 5336 }, 5337 }, 5338 5339 ExpectedDiff: &terraform.InstanceDiff{ 5340 Attributes: map[string]*terraform.ResourceAttrDiff{ 5341 "outer.#": { 5342 Old: "0", 5343 New: "1", 5344 }, 5345 "outer.~1.outer_str": { 5346 Old: "", 5347 New: "foo", 5348 }, 5349 "outer.~1.inner.#": { 5350 Old: "0", 5351 New: "1", 5352 }, 5353 "outer.~1.inner.~2.inner_str": { 5354 Old: "", 5355 New: hcl2shim.UnknownVariableValue, 5356 NewComputed: true, 5357 }, 5358 }, 5359 }, 5360 5361 Err: false, 5362 }, 5363 5364 "Complex structure with complex list of computed string should mark root set as computed": { 5365 Schema: map[string]*Schema{ 5366 "outer": { 5367 Type: TypeSet, 5368 Optional: true, 5369 Elem: &Resource{ 5370 Schema: map[string]*Schema{ 5371 "outer_str": { 5372 Type: TypeString, 5373 Optional: true, 5374 }, 5375 "inner": { 5376 Type: TypeList, 5377 Optional: true, 5378 Elem: &Resource{ 5379 Schema: map[string]*Schema{ 5380 "inner_str": { 5381 Type: TypeString, 5382 Optional: true, 5383 }, 5384 }, 5385 }, 5386 }, 5387 }, 5388 }, 5389 Set: func(v interface{}) int { 5390 return 1 5391 }, 5392 }, 5393 }, 5394 5395 State: nil, 5396 5397 Config: map[string]interface{}{ 5398 "outer": []interface{}{ 5399 map[string]interface{}{ 5400 "outer_str": "foo", 5401 "inner": []interface{}{ 5402 map[string]interface{}{ 5403 "inner_str": hcl2shim.UnknownVariableValue, 5404 }, 5405 }, 5406 }, 5407 }, 5408 }, 5409 5410 ExpectedDiff: &terraform.InstanceDiff{ 5411 Attributes: map[string]*terraform.ResourceAttrDiff{ 5412 "outer.#": { 5413 Old: "0", 5414 New: "1", 5415 }, 5416 "outer.~1.outer_str": { 5417 Old: "", 5418 New: "foo", 5419 }, 5420 "outer.~1.inner.#": { 5421 Old: "0", 5422 New: "1", 5423 }, 5424 "outer.~1.inner.0.inner_str": { 5425 Old: "", 5426 New: hcl2shim.UnknownVariableValue, 5427 NewComputed: true, 5428 }, 5429 }, 5430 }, 5431 5432 Err: false, 5433 }, 5434 } 5435 5436 for tn, tc := range cases { 5437 t.Run(tn, func(t *testing.T) { 5438 c := terraform.NewResourceConfigRaw(tc.Config) 5439 5440 d, err := schemaMap(tc.Schema).Diff(tc.State, c, nil, nil, true) 5441 if err != nil != tc.Err { 5442 t.Fatalf("#%q err: %s", tn, err) 5443 } 5444 5445 if !reflect.DeepEqual(tc.ExpectedDiff, d) { 5446 t.Fatalf("#%q:\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.ExpectedDiff, d) 5447 } 5448 }) 5449 } 5450 } 5451 5452 func TestSchemaMap_Validate(t *testing.T) { 5453 cases := map[string]struct { 5454 Schema map[string]*Schema 5455 Config map[string]interface{} 5456 Err bool 5457 Errors []error 5458 Warnings []string 5459 }{ 5460 "Good": { 5461 Schema: map[string]*Schema{ 5462 "availability_zone": { 5463 Type: TypeString, 5464 Optional: true, 5465 Computed: true, 5466 ForceNew: true, 5467 }, 5468 }, 5469 5470 Config: map[string]interface{}{ 5471 "availability_zone": "foo", 5472 }, 5473 }, 5474 5475 "Good, because the var is not set and that error will come elsewhere": { 5476 Schema: map[string]*Schema{ 5477 "size": { 5478 Type: TypeInt, 5479 Required: true, 5480 }, 5481 }, 5482 5483 Config: map[string]interface{}{ 5484 "size": hcl2shim.UnknownVariableValue, 5485 }, 5486 }, 5487 5488 "Required field not set": { 5489 Schema: map[string]*Schema{ 5490 "availability_zone": { 5491 Type: TypeString, 5492 Required: true, 5493 }, 5494 }, 5495 5496 Config: map[string]interface{}{}, 5497 5498 Err: true, 5499 }, 5500 5501 "Invalid basic type": { 5502 Schema: map[string]*Schema{ 5503 "port": { 5504 Type: TypeInt, 5505 Required: true, 5506 }, 5507 }, 5508 5509 Config: map[string]interface{}{ 5510 "port": "I am invalid", 5511 }, 5512 5513 Err: true, 5514 }, 5515 5516 "Invalid complex type": { 5517 Schema: map[string]*Schema{ 5518 "user_data": { 5519 Type: TypeString, 5520 Optional: true, 5521 }, 5522 }, 5523 5524 Config: map[string]interface{}{ 5525 "user_data": []interface{}{ 5526 map[string]interface{}{ 5527 "foo": "bar", 5528 }, 5529 }, 5530 }, 5531 5532 Err: true, 5533 }, 5534 5535 "Bad type": { 5536 Schema: map[string]*Schema{ 5537 "size": { 5538 Type: TypeInt, 5539 Required: true, 5540 }, 5541 }, 5542 5543 Config: map[string]interface{}{ 5544 "size": "nope", 5545 }, 5546 5547 Err: true, 5548 }, 5549 5550 "Required but has DefaultFunc": { 5551 Schema: map[string]*Schema{ 5552 "availability_zone": { 5553 Type: TypeString, 5554 Required: true, 5555 DefaultFunc: func() (interface{}, error) { 5556 return "foo", nil 5557 }, 5558 }, 5559 }, 5560 5561 Config: nil, 5562 }, 5563 5564 "Required but has DefaultFunc return nil": { 5565 Schema: map[string]*Schema{ 5566 "availability_zone": { 5567 Type: TypeString, 5568 Required: true, 5569 DefaultFunc: func() (interface{}, error) { 5570 return nil, nil 5571 }, 5572 }, 5573 }, 5574 5575 Config: nil, 5576 5577 Err: true, 5578 }, 5579 5580 "List with promotion": { 5581 Schema: map[string]*Schema{ 5582 "ingress": { 5583 Type: TypeList, 5584 Elem: &Schema{Type: TypeInt}, 5585 PromoteSingle: true, 5586 Optional: true, 5587 }, 5588 }, 5589 5590 Config: map[string]interface{}{ 5591 "ingress": "5", 5592 }, 5593 5594 Err: false, 5595 }, 5596 5597 "List with promotion set as list": { 5598 Schema: map[string]*Schema{ 5599 "ingress": { 5600 Type: TypeList, 5601 Elem: &Schema{Type: TypeInt}, 5602 PromoteSingle: true, 5603 Optional: true, 5604 }, 5605 }, 5606 5607 Config: map[string]interface{}{ 5608 "ingress": []interface{}{"5"}, 5609 }, 5610 5611 Err: false, 5612 }, 5613 5614 "Optional sub-resource": { 5615 Schema: map[string]*Schema{ 5616 "ingress": { 5617 Type: TypeList, 5618 Elem: &Resource{ 5619 Schema: map[string]*Schema{ 5620 "from": { 5621 Type: TypeInt, 5622 Required: true, 5623 }, 5624 }, 5625 }, 5626 }, 5627 }, 5628 5629 Config: map[string]interface{}{}, 5630 5631 Err: false, 5632 }, 5633 5634 "Sub-resource is the wrong type": { 5635 Schema: map[string]*Schema{ 5636 "ingress": { 5637 Type: TypeList, 5638 Required: true, 5639 Elem: &Resource{ 5640 Schema: map[string]*Schema{ 5641 "from": { 5642 Type: TypeInt, 5643 Required: true, 5644 }, 5645 }, 5646 }, 5647 }, 5648 }, 5649 5650 Config: map[string]interface{}{ 5651 "ingress": []interface{}{"foo"}, 5652 }, 5653 5654 Err: true, 5655 }, 5656 5657 "Not a list nested block": { 5658 Schema: map[string]*Schema{ 5659 "ingress": { 5660 Type: TypeList, 5661 Optional: true, 5662 Elem: &Resource{ 5663 Schema: map[string]*Schema{ 5664 "from": { 5665 Type: TypeInt, 5666 Required: true, 5667 }, 5668 }, 5669 }, 5670 }, 5671 }, 5672 5673 Config: map[string]interface{}{ 5674 "ingress": "foo", 5675 }, 5676 5677 Err: true, 5678 Errors: []error{ 5679 fmt.Errorf(`ingress: should be a list`), 5680 }, 5681 }, 5682 5683 "Not a list primitive": { 5684 Schema: map[string]*Schema{ 5685 "strings": { 5686 Type: TypeList, 5687 Optional: true, 5688 Elem: &Schema{ 5689 Type: TypeString, 5690 }, 5691 }, 5692 }, 5693 5694 Config: map[string]interface{}{ 5695 "strings": "foo", 5696 }, 5697 5698 Err: true, 5699 Errors: []error{ 5700 fmt.Errorf(`strings: should be a list`), 5701 }, 5702 }, 5703 5704 "Unknown list": { 5705 Schema: map[string]*Schema{ 5706 "strings": { 5707 Type: TypeList, 5708 Optional: true, 5709 Elem: &Schema{ 5710 Type: TypeString, 5711 }, 5712 }, 5713 }, 5714 5715 Config: map[string]interface{}{ 5716 "strings": hcl2shim.UnknownVariableValue, 5717 }, 5718 5719 Err: false, 5720 }, 5721 5722 "Unknown + Deprecation": { 5723 Schema: map[string]*Schema{ 5724 "old_news": { 5725 Type: TypeString, 5726 Optional: true, 5727 Deprecated: "please use 'new_news' instead", 5728 }, 5729 }, 5730 5731 Config: map[string]interface{}{ 5732 "old_news": hcl2shim.UnknownVariableValue, 5733 }, 5734 5735 Warnings: []string{ 5736 "\"old_news\": [DEPRECATED] please use 'new_news' instead", 5737 }, 5738 }, 5739 5740 "Required sub-resource field": { 5741 Schema: map[string]*Schema{ 5742 "ingress": { 5743 Type: TypeList, 5744 Elem: &Resource{ 5745 Schema: map[string]*Schema{ 5746 "from": { 5747 Type: TypeInt, 5748 Required: true, 5749 }, 5750 }, 5751 }, 5752 }, 5753 }, 5754 5755 Config: map[string]interface{}{ 5756 "ingress": []interface{}{ 5757 map[string]interface{}{}, 5758 }, 5759 }, 5760 5761 Err: true, 5762 }, 5763 5764 "Good sub-resource": { 5765 Schema: map[string]*Schema{ 5766 "ingress": { 5767 Type: TypeList, 5768 Optional: true, 5769 Elem: &Resource{ 5770 Schema: map[string]*Schema{ 5771 "from": { 5772 Type: TypeInt, 5773 Required: true, 5774 }, 5775 }, 5776 }, 5777 }, 5778 }, 5779 5780 Config: map[string]interface{}{ 5781 "ingress": []interface{}{ 5782 map[string]interface{}{ 5783 "from": 80, 5784 }, 5785 }, 5786 }, 5787 5788 Err: false, 5789 }, 5790 5791 "Good sub-resource, computed value": { 5792 Schema: map[string]*Schema{ 5793 "ingress": { 5794 Type: TypeList, 5795 Optional: true, 5796 Elem: &Resource{ 5797 Schema: map[string]*Schema{ 5798 "from": { 5799 Type: TypeInt, 5800 Optional: true, 5801 }, 5802 }, 5803 }, 5804 }, 5805 }, 5806 5807 Config: map[string]interface{}{ 5808 "ingress": []interface{}{ 5809 map[string]interface{}{ 5810 "from": hcl2shim.UnknownVariableValue, 5811 }, 5812 }, 5813 }, 5814 5815 Err: false, 5816 }, 5817 5818 "Invalid/unknown field": { 5819 Schema: map[string]*Schema{ 5820 "availability_zone": { 5821 Type: TypeString, 5822 Optional: true, 5823 Computed: true, 5824 ForceNew: true, 5825 }, 5826 }, 5827 5828 Config: map[string]interface{}{ 5829 "foo": "bar", 5830 }, 5831 5832 Err: true, 5833 }, 5834 5835 "Invalid/unknown field with computed value": { 5836 Schema: map[string]*Schema{ 5837 "availability_zone": { 5838 Type: TypeString, 5839 Optional: true, 5840 Computed: true, 5841 ForceNew: true, 5842 }, 5843 }, 5844 5845 Config: map[string]interface{}{ 5846 "foo": hcl2shim.UnknownVariableValue, 5847 }, 5848 5849 Err: true, 5850 }, 5851 5852 "Computed field set": { 5853 Schema: map[string]*Schema{ 5854 "availability_zone": { 5855 Type: TypeString, 5856 Computed: true, 5857 }, 5858 }, 5859 5860 Config: map[string]interface{}{ 5861 "availability_zone": "bar", 5862 }, 5863 5864 Err: true, 5865 }, 5866 5867 "Not a set": { 5868 Schema: map[string]*Schema{ 5869 "ports": { 5870 Type: TypeSet, 5871 Required: true, 5872 Elem: &Schema{Type: TypeInt}, 5873 Set: func(a interface{}) int { 5874 return a.(int) 5875 }, 5876 }, 5877 }, 5878 5879 Config: map[string]interface{}{ 5880 "ports": "foo", 5881 }, 5882 5883 Err: true, 5884 }, 5885 5886 "Maps": { 5887 Schema: map[string]*Schema{ 5888 "user_data": { 5889 Type: TypeMap, 5890 Optional: true, 5891 }, 5892 }, 5893 5894 Config: map[string]interface{}{ 5895 "user_data": "foo", 5896 }, 5897 5898 Err: true, 5899 }, 5900 5901 "Good map: data surrounded by extra slice": { 5902 Schema: map[string]*Schema{ 5903 "user_data": { 5904 Type: TypeMap, 5905 Optional: true, 5906 }, 5907 }, 5908 5909 Config: map[string]interface{}{ 5910 "user_data": []interface{}{ 5911 map[string]interface{}{ 5912 "foo": "bar", 5913 }, 5914 }, 5915 }, 5916 }, 5917 5918 "Good map": { 5919 Schema: map[string]*Schema{ 5920 "user_data": { 5921 Type: TypeMap, 5922 Optional: true, 5923 }, 5924 }, 5925 5926 Config: map[string]interface{}{ 5927 "user_data": map[string]interface{}{ 5928 "foo": "bar", 5929 }, 5930 }, 5931 }, 5932 5933 "Map with type specified as value type": { 5934 Schema: map[string]*Schema{ 5935 "user_data": { 5936 Type: TypeMap, 5937 Optional: true, 5938 Elem: TypeBool, 5939 }, 5940 }, 5941 5942 Config: map[string]interface{}{ 5943 "user_data": map[string]interface{}{ 5944 "foo": "not_a_bool", 5945 }, 5946 }, 5947 5948 Err: true, 5949 }, 5950 5951 "Map with type specified as nested Schema": { 5952 Schema: map[string]*Schema{ 5953 "user_data": { 5954 Type: TypeMap, 5955 Optional: true, 5956 Elem: &Schema{Type: TypeBool}, 5957 }, 5958 }, 5959 5960 Config: map[string]interface{}{ 5961 "user_data": map[string]interface{}{ 5962 "foo": "not_a_bool", 5963 }, 5964 }, 5965 5966 Err: true, 5967 }, 5968 5969 "Bad map: just a slice": { 5970 Schema: map[string]*Schema{ 5971 "user_data": { 5972 Type: TypeMap, 5973 Optional: true, 5974 }, 5975 }, 5976 5977 Config: map[string]interface{}{ 5978 "user_data": []interface{}{ 5979 "foo", 5980 }, 5981 }, 5982 5983 Err: true, 5984 }, 5985 5986 "Good set: config has slice with single interpolated value": { 5987 Schema: map[string]*Schema{ 5988 "security_groups": { 5989 Type: TypeSet, 5990 Optional: true, 5991 Computed: true, 5992 ForceNew: true, 5993 Elem: &Schema{Type: TypeString}, 5994 Set: func(v interface{}) int { 5995 return len(v.(string)) 5996 }, 5997 }, 5998 }, 5999 6000 Config: map[string]interface{}{ 6001 "security_groups": []interface{}{"${var.foo}"}, 6002 }, 6003 6004 Err: false, 6005 }, 6006 6007 "Bad set: config has single interpolated value": { 6008 Schema: map[string]*Schema{ 6009 "security_groups": { 6010 Type: TypeSet, 6011 Optional: true, 6012 Computed: true, 6013 ForceNew: true, 6014 Elem: &Schema{Type: TypeString}, 6015 }, 6016 }, 6017 6018 Config: map[string]interface{}{ 6019 "security_groups": "${var.foo}", 6020 }, 6021 6022 Err: true, 6023 }, 6024 6025 "Bad, subresource should not allow unknown elements": { 6026 Schema: map[string]*Schema{ 6027 "ingress": { 6028 Type: TypeList, 6029 Optional: true, 6030 Elem: &Resource{ 6031 Schema: map[string]*Schema{ 6032 "port": { 6033 Type: TypeInt, 6034 Required: true, 6035 }, 6036 }, 6037 }, 6038 }, 6039 }, 6040 6041 Config: map[string]interface{}{ 6042 "ingress": []interface{}{ 6043 map[string]interface{}{ 6044 "port": 80, 6045 "other": "yes", 6046 }, 6047 }, 6048 }, 6049 6050 Err: true, 6051 }, 6052 6053 "Bad, subresource should not allow invalid types": { 6054 Schema: map[string]*Schema{ 6055 "ingress": { 6056 Type: TypeList, 6057 Optional: true, 6058 Elem: &Resource{ 6059 Schema: map[string]*Schema{ 6060 "port": { 6061 Type: TypeInt, 6062 Required: true, 6063 }, 6064 }, 6065 }, 6066 }, 6067 }, 6068 6069 Config: map[string]interface{}{ 6070 "ingress": []interface{}{ 6071 map[string]interface{}{ 6072 "port": "bad", 6073 }, 6074 }, 6075 }, 6076 6077 Err: true, 6078 }, 6079 6080 "Bad, should not allow lists to be assigned to string attributes": { 6081 Schema: map[string]*Schema{ 6082 "availability_zone": { 6083 Type: TypeString, 6084 Required: true, 6085 }, 6086 }, 6087 6088 Config: map[string]interface{}{ 6089 "availability_zone": []interface{}{"foo", "bar", "baz"}, 6090 }, 6091 6092 Err: true, 6093 }, 6094 6095 "Bad, should not allow maps to be assigned to string attributes": { 6096 Schema: map[string]*Schema{ 6097 "availability_zone": { 6098 Type: TypeString, 6099 Required: true, 6100 }, 6101 }, 6102 6103 Config: map[string]interface{}{ 6104 "availability_zone": map[string]interface{}{"foo": "bar", "baz": "thing"}, 6105 }, 6106 6107 Err: true, 6108 }, 6109 6110 "Deprecated attribute usage generates warning, but not error": { 6111 Schema: map[string]*Schema{ 6112 "old_news": { 6113 Type: TypeString, 6114 Optional: true, 6115 Deprecated: "please use 'new_news' instead", 6116 }, 6117 }, 6118 6119 Config: map[string]interface{}{ 6120 "old_news": "extra extra!", 6121 }, 6122 6123 Err: false, 6124 6125 Warnings: []string{ 6126 "\"old_news\": [DEPRECATED] please use 'new_news' instead", 6127 }, 6128 }, 6129 6130 "Deprecated generates no warnings if attr not used": { 6131 Schema: map[string]*Schema{ 6132 "old_news": { 6133 Type: TypeString, 6134 Optional: true, 6135 Deprecated: "please use 'new_news' instead", 6136 }, 6137 }, 6138 6139 Err: false, 6140 6141 Warnings: nil, 6142 }, 6143 6144 "Removed attribute usage generates error": { 6145 Schema: map[string]*Schema{ 6146 "long_gone": { 6147 Type: TypeString, 6148 Optional: true, 6149 Removed: "no longer supported by Cloud API", 6150 }, 6151 }, 6152 6153 Config: map[string]interface{}{ 6154 "long_gone": "still here!", 6155 }, 6156 6157 Err: true, 6158 Errors: []error{ 6159 fmt.Errorf("\"long_gone\": [REMOVED] no longer supported by Cloud API"), 6160 }, 6161 }, 6162 6163 "Removed generates no errors if attr not used": { 6164 Schema: map[string]*Schema{ 6165 "long_gone": { 6166 Type: TypeString, 6167 Optional: true, 6168 Removed: "no longer supported by Cloud API", 6169 }, 6170 }, 6171 6172 Err: false, 6173 }, 6174 6175 "Conflicting attributes generate error": { 6176 Schema: map[string]*Schema{ 6177 "whitelist": { 6178 Type: TypeString, 6179 Optional: true, 6180 }, 6181 "blacklist": { 6182 Type: TypeString, 6183 Optional: true, 6184 ConflictsWith: []string{"whitelist"}, 6185 }, 6186 }, 6187 6188 Config: map[string]interface{}{ 6189 "whitelist": "white-val", 6190 "blacklist": "black-val", 6191 }, 6192 6193 Err: true, 6194 Errors: []error{ 6195 fmt.Errorf("\"blacklist\": conflicts with whitelist"), 6196 }, 6197 }, 6198 6199 "Conflicting attributes okay when unknown 1": { 6200 Schema: map[string]*Schema{ 6201 "whitelist": { 6202 Type: TypeString, 6203 Optional: true, 6204 }, 6205 "blacklist": { 6206 Type: TypeString, 6207 Optional: true, 6208 ConflictsWith: []string{"whitelist"}, 6209 }, 6210 }, 6211 6212 Config: map[string]interface{}{ 6213 "whitelist": "white-val", 6214 "blacklist": hcl2shim.UnknownVariableValue, 6215 }, 6216 6217 Err: false, 6218 }, 6219 6220 "Conflicting list attributes okay when unknown 1": { 6221 Schema: map[string]*Schema{ 6222 "whitelist": { 6223 Type: TypeList, 6224 Optional: true, 6225 Elem: &Schema{Type: TypeString}, 6226 }, 6227 "blacklist": { 6228 Type: TypeList, 6229 Optional: true, 6230 Elem: &Schema{Type: TypeString}, 6231 ConflictsWith: []string{"whitelist"}, 6232 }, 6233 }, 6234 6235 Config: map[string]interface{}{ 6236 "whitelist": []interface{}{"white-val"}, 6237 "blacklist": []interface{}{hcl2shim.UnknownVariableValue}, 6238 }, 6239 6240 Err: false, 6241 }, 6242 6243 "Conflicting attributes okay when unknown 2": { 6244 Schema: map[string]*Schema{ 6245 "whitelist": { 6246 Type: TypeString, 6247 Optional: true, 6248 }, 6249 "blacklist": { 6250 Type: TypeString, 6251 Optional: true, 6252 ConflictsWith: []string{"whitelist"}, 6253 }, 6254 }, 6255 6256 Config: map[string]interface{}{ 6257 "whitelist": hcl2shim.UnknownVariableValue, 6258 "blacklist": "black-val", 6259 }, 6260 6261 Err: false, 6262 }, 6263 6264 "Conflicting attributes generate error even if one is unknown": { 6265 Schema: map[string]*Schema{ 6266 "whitelist": { 6267 Type: TypeString, 6268 Optional: true, 6269 ConflictsWith: []string{"blacklist", "greenlist"}, 6270 }, 6271 "blacklist": { 6272 Type: TypeString, 6273 Optional: true, 6274 ConflictsWith: []string{"whitelist", "greenlist"}, 6275 }, 6276 "greenlist": { 6277 Type: TypeString, 6278 Optional: true, 6279 ConflictsWith: []string{"whitelist", "blacklist"}, 6280 }, 6281 }, 6282 6283 Config: map[string]interface{}{ 6284 "whitelist": hcl2shim.UnknownVariableValue, 6285 "blacklist": "black-val", 6286 "greenlist": "green-val", 6287 }, 6288 6289 Err: true, 6290 Errors: []error{ 6291 fmt.Errorf("\"blacklist\": conflicts with greenlist"), 6292 fmt.Errorf("\"greenlist\": conflicts with blacklist"), 6293 }, 6294 }, 6295 6296 "Required attribute & undefined conflicting optional are good": { 6297 Schema: map[string]*Schema{ 6298 "required_att": { 6299 Type: TypeString, 6300 Required: true, 6301 }, 6302 "optional_att": { 6303 Type: TypeString, 6304 Optional: true, 6305 ConflictsWith: []string{"required_att"}, 6306 }, 6307 }, 6308 6309 Config: map[string]interface{}{ 6310 "required_att": "required-val", 6311 }, 6312 6313 Err: false, 6314 }, 6315 6316 "Required conflicting attribute & defined optional generate error": { 6317 Schema: map[string]*Schema{ 6318 "required_att": { 6319 Type: TypeString, 6320 Required: true, 6321 }, 6322 "optional_att": { 6323 Type: TypeString, 6324 Optional: true, 6325 ConflictsWith: []string{"required_att"}, 6326 }, 6327 }, 6328 6329 Config: map[string]interface{}{ 6330 "required_att": "required-val", 6331 "optional_att": "optional-val", 6332 }, 6333 6334 Err: true, 6335 Errors: []error{ 6336 fmt.Errorf(`"optional_att": conflicts with required_att`), 6337 }, 6338 }, 6339 6340 "Computed + Optional fields conflicting with each other": { 6341 Schema: map[string]*Schema{ 6342 "foo_att": { 6343 Type: TypeString, 6344 Optional: true, 6345 Computed: true, 6346 ConflictsWith: []string{"bar_att"}, 6347 }, 6348 "bar_att": { 6349 Type: TypeString, 6350 Optional: true, 6351 Computed: true, 6352 ConflictsWith: []string{"foo_att"}, 6353 }, 6354 }, 6355 6356 Config: map[string]interface{}{ 6357 "foo_att": "foo-val", 6358 "bar_att": "bar-val", 6359 }, 6360 6361 Err: true, 6362 Errors: []error{ 6363 fmt.Errorf(`"foo_att": conflicts with bar_att`), 6364 fmt.Errorf(`"bar_att": conflicts with foo_att`), 6365 }, 6366 }, 6367 6368 "Computed + Optional fields NOT conflicting with each other": { 6369 Schema: map[string]*Schema{ 6370 "foo_att": { 6371 Type: TypeString, 6372 Optional: true, 6373 Computed: true, 6374 ConflictsWith: []string{"bar_att"}, 6375 }, 6376 "bar_att": { 6377 Type: TypeString, 6378 Optional: true, 6379 Computed: true, 6380 ConflictsWith: []string{"foo_att"}, 6381 }, 6382 }, 6383 6384 Config: map[string]interface{}{ 6385 "foo_att": "foo-val", 6386 }, 6387 6388 Err: false, 6389 }, 6390 6391 "Computed + Optional fields that conflict with none set": { 6392 Schema: map[string]*Schema{ 6393 "foo_att": { 6394 Type: TypeString, 6395 Optional: true, 6396 Computed: true, 6397 ConflictsWith: []string{"bar_att"}, 6398 }, 6399 "bar_att": { 6400 Type: TypeString, 6401 Optional: true, 6402 Computed: true, 6403 ConflictsWith: []string{"foo_att"}, 6404 }, 6405 }, 6406 6407 Config: map[string]interface{}{}, 6408 6409 Err: false, 6410 }, 6411 6412 "Good with ValidateFunc": { 6413 Schema: map[string]*Schema{ 6414 "validate_me": { 6415 Type: TypeString, 6416 Required: true, 6417 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 6418 return 6419 }, 6420 }, 6421 }, 6422 Config: map[string]interface{}{ 6423 "validate_me": "valid", 6424 }, 6425 Err: false, 6426 }, 6427 6428 "Bad with ValidateFunc": { 6429 Schema: map[string]*Schema{ 6430 "validate_me": { 6431 Type: TypeString, 6432 Required: true, 6433 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 6434 es = append(es, fmt.Errorf("something is not right here")) 6435 return 6436 }, 6437 }, 6438 }, 6439 Config: map[string]interface{}{ 6440 "validate_me": "invalid", 6441 }, 6442 Err: true, 6443 Errors: []error{ 6444 fmt.Errorf(`something is not right here`), 6445 }, 6446 }, 6447 6448 "ValidateFunc not called when type does not match": { 6449 Schema: map[string]*Schema{ 6450 "number": { 6451 Type: TypeInt, 6452 Required: true, 6453 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 6454 t.Fatalf("Should not have gotten validate call") 6455 return 6456 }, 6457 }, 6458 }, 6459 Config: map[string]interface{}{ 6460 "number": "NaN", 6461 }, 6462 Err: true, 6463 }, 6464 6465 "ValidateFunc gets decoded type": { 6466 Schema: map[string]*Schema{ 6467 "maybe": { 6468 Type: TypeBool, 6469 Required: true, 6470 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 6471 if _, ok := v.(bool); !ok { 6472 t.Fatalf("Expected bool, got: %#v", v) 6473 } 6474 return 6475 }, 6476 }, 6477 }, 6478 Config: map[string]interface{}{ 6479 "maybe": "true", 6480 }, 6481 }, 6482 6483 "ValidateFunc is not called with a computed value": { 6484 Schema: map[string]*Schema{ 6485 "validate_me": { 6486 Type: TypeString, 6487 Required: true, 6488 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 6489 es = append(es, fmt.Errorf("something is not right here")) 6490 return 6491 }, 6492 }, 6493 }, 6494 Config: map[string]interface{}{ 6495 "validate_me": hcl2shim.UnknownVariableValue, 6496 }, 6497 6498 Err: false, 6499 }, 6500 6501 "special timeouts field": { 6502 Schema: map[string]*Schema{ 6503 "availability_zone": { 6504 Type: TypeString, 6505 Optional: true, 6506 Computed: true, 6507 ForceNew: true, 6508 }, 6509 }, 6510 6511 Config: map[string]interface{}{ 6512 TimeoutsConfigKey: "bar", 6513 }, 6514 6515 Err: false, 6516 }, 6517 6518 "invalid bool field": { 6519 Schema: map[string]*Schema{ 6520 "bool_field": { 6521 Type: TypeBool, 6522 Optional: true, 6523 }, 6524 }, 6525 Config: map[string]interface{}{ 6526 "bool_field": "abcdef", 6527 }, 6528 Err: true, 6529 }, 6530 "invalid integer field": { 6531 Schema: map[string]*Schema{ 6532 "integer_field": { 6533 Type: TypeInt, 6534 Optional: true, 6535 }, 6536 }, 6537 Config: map[string]interface{}{ 6538 "integer_field": "abcdef", 6539 }, 6540 Err: true, 6541 }, 6542 "invalid float field": { 6543 Schema: map[string]*Schema{ 6544 "float_field": { 6545 Type: TypeFloat, 6546 Optional: true, 6547 }, 6548 }, 6549 Config: map[string]interface{}{ 6550 "float_field": "abcdef", 6551 }, 6552 Err: true, 6553 }, 6554 6555 // Invalid map values 6556 "invalid bool map value": { 6557 Schema: map[string]*Schema{ 6558 "boolMap": { 6559 Type: TypeMap, 6560 Elem: TypeBool, 6561 Optional: true, 6562 }, 6563 }, 6564 Config: map[string]interface{}{ 6565 "boolMap": map[string]interface{}{ 6566 "boolField": "notbool", 6567 }, 6568 }, 6569 Err: true, 6570 }, 6571 "invalid int map value": { 6572 Schema: map[string]*Schema{ 6573 "intMap": { 6574 Type: TypeMap, 6575 Elem: TypeInt, 6576 Optional: true, 6577 }, 6578 }, 6579 Config: map[string]interface{}{ 6580 "intMap": map[string]interface{}{ 6581 "intField": "notInt", 6582 }, 6583 }, 6584 Err: true, 6585 }, 6586 "invalid float map value": { 6587 Schema: map[string]*Schema{ 6588 "floatMap": { 6589 Type: TypeMap, 6590 Elem: TypeFloat, 6591 Optional: true, 6592 }, 6593 }, 6594 Config: map[string]interface{}{ 6595 "floatMap": map[string]interface{}{ 6596 "floatField": "notFloat", 6597 }, 6598 }, 6599 Err: true, 6600 }, 6601 6602 "map with positive validate function": { 6603 Schema: map[string]*Schema{ 6604 "floatInt": { 6605 Type: TypeMap, 6606 Elem: TypeInt, 6607 Optional: true, 6608 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 6609 return 6610 }, 6611 }, 6612 }, 6613 Config: map[string]interface{}{ 6614 "floatInt": map[string]interface{}{ 6615 "rightAnswer": "42", 6616 "tooMuch": "43", 6617 }, 6618 }, 6619 Err: false, 6620 }, 6621 "map with negative validate function": { 6622 Schema: map[string]*Schema{ 6623 "floatInt": { 6624 Type: TypeMap, 6625 Elem: TypeInt, 6626 Optional: true, 6627 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 6628 es = append(es, fmt.Errorf("this is not fine")) 6629 return 6630 }, 6631 }, 6632 }, 6633 Config: map[string]interface{}{ 6634 "floatInt": map[string]interface{}{ 6635 "rightAnswer": "42", 6636 "tooMuch": "43", 6637 }, 6638 }, 6639 Err: true, 6640 }, 6641 6642 // The Validation function should not see interpolation strings from 6643 // non-computed values. 6644 "set with partially computed list and map": { 6645 Schema: map[string]*Schema{ 6646 "outer": { 6647 Type: TypeSet, 6648 Optional: true, 6649 Computed: true, 6650 Elem: &Resource{ 6651 Schema: map[string]*Schema{ 6652 "list": { 6653 Type: TypeList, 6654 Optional: true, 6655 Elem: &Schema{ 6656 Type: TypeString, 6657 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 6658 if strings.HasPrefix(v.(string), "${") { 6659 es = append(es, fmt.Errorf("should not have interpolations")) 6660 } 6661 return 6662 }, 6663 }, 6664 }, 6665 }, 6666 }, 6667 }, 6668 }, 6669 Config: map[string]interface{}{ 6670 "outer": []interface{}{ 6671 map[string]interface{}{ 6672 "list": []interface{}{"A", hcl2shim.UnknownVariableValue, "c"}, 6673 }, 6674 }, 6675 }, 6676 Err: false, 6677 }, 6678 "unexpected nils values": { 6679 Schema: map[string]*Schema{ 6680 "strings": { 6681 Type: TypeList, 6682 Optional: true, 6683 Elem: &Schema{ 6684 Type: TypeString, 6685 }, 6686 }, 6687 "block": { 6688 Type: TypeList, 6689 Optional: true, 6690 Elem: &Resource{ 6691 Schema: map[string]*Schema{ 6692 "int": { 6693 Type: TypeInt, 6694 Required: true, 6695 }, 6696 }, 6697 }, 6698 }, 6699 }, 6700 6701 Config: map[string]interface{}{ 6702 "strings": []interface{}{"1", nil}, 6703 "block": []interface{}{map[string]interface{}{ 6704 "int": nil, 6705 }, 6706 nil, 6707 }, 6708 }, 6709 Err: true, 6710 }, 6711 } 6712 6713 for tn, tc := range cases { 6714 t.Run(tn, func(t *testing.T) { 6715 c := terraform.NewResourceConfigRaw(tc.Config) 6716 6717 ws, es := schemaMap(tc.Schema).Validate(c) 6718 if len(es) > 0 != tc.Err { 6719 if len(es) == 0 { 6720 t.Errorf("%q: no errors", tn) 6721 } 6722 6723 for _, e := range es { 6724 t.Errorf("%q: err: %s", tn, e) 6725 } 6726 6727 t.FailNow() 6728 } 6729 6730 if !reflect.DeepEqual(ws, tc.Warnings) { 6731 t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws) 6732 } 6733 6734 if tc.Errors != nil { 6735 sort.Sort(errorSort(es)) 6736 sort.Sort(errorSort(tc.Errors)) 6737 6738 if !reflect.DeepEqual(es, tc.Errors) { 6739 t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es) 6740 } 6741 } 6742 }) 6743 6744 } 6745 } 6746 6747 func TestSchemaSet_ValidateMaxItems(t *testing.T) { 6748 cases := map[string]struct { 6749 Schema map[string]*Schema 6750 State *terraform.InstanceState 6751 Config map[string]interface{} 6752 ConfigVariables map[string]string 6753 Diff *terraform.InstanceDiff 6754 Err bool 6755 Errors []error 6756 }{ 6757 "#0": { 6758 Schema: map[string]*Schema{ 6759 "aliases": { 6760 Type: TypeSet, 6761 Optional: true, 6762 MaxItems: 1, 6763 Elem: &Schema{Type: TypeString}, 6764 }, 6765 }, 6766 State: nil, 6767 Config: map[string]interface{}{ 6768 "aliases": []interface{}{"foo", "bar"}, 6769 }, 6770 Diff: nil, 6771 Err: true, 6772 Errors: []error{ 6773 fmt.Errorf("aliases: attribute supports 1 item maximum, config has 2 declared"), 6774 }, 6775 }, 6776 "#1": { 6777 Schema: map[string]*Schema{ 6778 "aliases": { 6779 Type: TypeSet, 6780 Optional: true, 6781 Elem: &Schema{Type: TypeString}, 6782 }, 6783 }, 6784 State: nil, 6785 Config: map[string]interface{}{ 6786 "aliases": []interface{}{"foo", "bar"}, 6787 }, 6788 Diff: nil, 6789 Err: false, 6790 Errors: nil, 6791 }, 6792 "#2": { 6793 Schema: map[string]*Schema{ 6794 "aliases": { 6795 Type: TypeSet, 6796 Optional: true, 6797 MaxItems: 1, 6798 Elem: &Schema{Type: TypeString}, 6799 }, 6800 }, 6801 State: nil, 6802 Config: map[string]interface{}{ 6803 "aliases": []interface{}{"foo"}, 6804 }, 6805 Diff: nil, 6806 Err: false, 6807 Errors: nil, 6808 }, 6809 } 6810 6811 for tn, tc := range cases { 6812 c := terraform.NewResourceConfigRaw(tc.Config) 6813 _, es := schemaMap(tc.Schema).Validate(c) 6814 6815 if len(es) > 0 != tc.Err { 6816 if len(es) == 0 { 6817 t.Errorf("%q: no errors", tn) 6818 } 6819 6820 for _, e := range es { 6821 t.Errorf("%q: err: %s", tn, e) 6822 } 6823 6824 t.FailNow() 6825 } 6826 6827 if tc.Errors != nil { 6828 if !reflect.DeepEqual(es, tc.Errors) { 6829 t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es) 6830 } 6831 } 6832 } 6833 } 6834 6835 func TestSchemaSet_ValidateMinItems(t *testing.T) { 6836 cases := map[string]struct { 6837 Schema map[string]*Schema 6838 State *terraform.InstanceState 6839 Config map[string]interface{} 6840 ConfigVariables map[string]string 6841 Diff *terraform.InstanceDiff 6842 Err bool 6843 Errors []error 6844 }{ 6845 "#0": { 6846 Schema: map[string]*Schema{ 6847 "aliases": { 6848 Type: TypeSet, 6849 Optional: true, 6850 MinItems: 2, 6851 Elem: &Schema{Type: TypeString}, 6852 }, 6853 }, 6854 State: nil, 6855 Config: map[string]interface{}{ 6856 "aliases": []interface{}{"foo", "bar"}, 6857 }, 6858 Diff: nil, 6859 Err: false, 6860 Errors: nil, 6861 }, 6862 "#1": { 6863 Schema: map[string]*Schema{ 6864 "aliases": { 6865 Type: TypeSet, 6866 Optional: true, 6867 Elem: &Schema{Type: TypeString}, 6868 }, 6869 }, 6870 State: nil, 6871 Config: map[string]interface{}{ 6872 "aliases": []interface{}{"foo", "bar"}, 6873 }, 6874 Diff: nil, 6875 Err: false, 6876 Errors: nil, 6877 }, 6878 "#2": { 6879 Schema: map[string]*Schema{ 6880 "aliases": { 6881 Type: TypeSet, 6882 Optional: true, 6883 MinItems: 2, 6884 Elem: &Schema{Type: TypeString}, 6885 }, 6886 }, 6887 State: nil, 6888 Config: map[string]interface{}{ 6889 "aliases": []interface{}{"foo"}, 6890 }, 6891 Diff: nil, 6892 Err: true, 6893 Errors: []error{ 6894 fmt.Errorf("aliases: attribute supports 2 item as a minimum, config has 1 declared"), 6895 }, 6896 }, 6897 } 6898 6899 for tn, tc := range cases { 6900 c := terraform.NewResourceConfigRaw(tc.Config) 6901 _, es := schemaMap(tc.Schema).Validate(c) 6902 6903 if len(es) > 0 != tc.Err { 6904 if len(es) == 0 { 6905 t.Errorf("%q: no errors", tn) 6906 } 6907 6908 for _, e := range es { 6909 t.Errorf("%q: err: %s", tn, e) 6910 } 6911 6912 t.FailNow() 6913 } 6914 6915 if tc.Errors != nil { 6916 if !reflect.DeepEqual(es, tc.Errors) { 6917 t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es) 6918 } 6919 } 6920 } 6921 } 6922 6923 // errorSort implements sort.Interface to sort errors by their error message 6924 type errorSort []error 6925 6926 func (e errorSort) Len() int { return len(e) } 6927 func (e errorSort) Swap(i, j int) { e[i], e[j] = e[j], e[i] } 6928 func (e errorSort) Less(i, j int) bool { 6929 return e[i].Error() < e[j].Error() 6930 } 6931 6932 func TestSchemaMapDeepCopy(t *testing.T) { 6933 schema := map[string]*Schema{ 6934 "foo": { 6935 Type: TypeString, 6936 }, 6937 } 6938 source := schemaMap(schema) 6939 dest := source.DeepCopy() 6940 dest["foo"].ForceNew = true 6941 if reflect.DeepEqual(source, dest) { 6942 t.Fatalf("source and dest should not match") 6943 } 6944 } 6945 6946 func TestValidateConflictingAttributes(t *testing.T) { 6947 cases := map[string]struct { 6948 Key string 6949 Schema *Schema 6950 Config map[string]interface{} 6951 Err bool 6952 }{ 6953 "root attribute self conflicting": { 6954 Key: "self", 6955 Schema: &Schema{ 6956 Type: TypeBool, 6957 Optional: true, 6958 ConflictsWith: []string{"self"}, 6959 }, 6960 Config: map[string]interface{}{ 6961 "self": true, 6962 }, 6963 Err: true, 6964 }, 6965 6966 "root attribute conflicting unconfigured self unconfigured": { 6967 Key: "self", 6968 Schema: &Schema{ 6969 Type: TypeBool, 6970 Optional: true, 6971 ConflictsWith: []string{"root_attr"}, 6972 }, 6973 Config: map[string]interface{}{}, 6974 Err: false, 6975 }, 6976 6977 "root attribute conflicting unconfigured self unknown": { 6978 Key: "self", 6979 Schema: &Schema{ 6980 Type: TypeBool, 6981 Optional: true, 6982 ConflictsWith: []string{"root_attr"}, 6983 }, 6984 Config: map[string]interface{}{ 6985 "self": hcl2shim.UnknownVariableValue, 6986 }, 6987 Err: false, 6988 }, 6989 6990 "root attribute conflicting unconfigured self known": { 6991 Key: "self", 6992 Schema: &Schema{ 6993 Type: TypeBool, 6994 Optional: true, 6995 ConflictsWith: []string{"root_attr"}, 6996 }, 6997 Config: map[string]interface{}{ 6998 "self": true, 6999 }, 7000 Err: false, 7001 }, 7002 7003 "root attribute conflicting unknown self unconfigured": { 7004 Key: "self", 7005 Schema: &Schema{ 7006 Type: TypeBool, 7007 Optional: true, 7008 ConflictsWith: []string{"root_attr"}, 7009 }, 7010 Config: map[string]interface{}{ 7011 "root_attr": hcl2shim.UnknownVariableValue, 7012 }, 7013 Err: false, 7014 }, 7015 7016 "root attribute conflicting unknown self unknown": { 7017 Key: "self", 7018 Schema: &Schema{ 7019 Type: TypeBool, 7020 Optional: true, 7021 ConflictsWith: []string{"root_attr"}, 7022 }, 7023 Config: map[string]interface{}{ 7024 "root_attr": hcl2shim.UnknownVariableValue, 7025 "self": hcl2shim.UnknownVariableValue, 7026 }, 7027 Err: false, 7028 }, 7029 7030 "root attribute conflicting unknown self known": { 7031 Key: "self", 7032 Schema: &Schema{ 7033 Type: TypeBool, 7034 Optional: true, 7035 ConflictsWith: []string{"root_attr"}, 7036 }, 7037 Config: map[string]interface{}{ 7038 "root_attr": hcl2shim.UnknownVariableValue, 7039 "self": true, 7040 }, 7041 Err: false, 7042 }, 7043 7044 "root attribute conflicting known self unconfigured": { 7045 Key: "self", 7046 Schema: &Schema{ 7047 Type: TypeBool, 7048 Optional: true, 7049 ConflictsWith: []string{"root_attr"}, 7050 }, 7051 Config: map[string]interface{}{ 7052 "root_attr": true, 7053 }, 7054 Err: true, 7055 }, 7056 7057 "root attribute conflicting known self unknown": { 7058 Key: "self", 7059 Schema: &Schema{ 7060 Type: TypeBool, 7061 Optional: true, 7062 ConflictsWith: []string{"root_attr"}, 7063 }, 7064 Config: map[string]interface{}{ 7065 "root_attr": true, 7066 "self": hcl2shim.UnknownVariableValue, 7067 }, 7068 Err: true, 7069 }, 7070 7071 "root attribute conflicting known self known": { 7072 Key: "self", 7073 Schema: &Schema{ 7074 Type: TypeBool, 7075 Optional: true, 7076 ConflictsWith: []string{"root_attr"}, 7077 }, 7078 Config: map[string]interface{}{ 7079 "root_attr": true, 7080 "self": true, 7081 }, 7082 Err: true, 7083 }, 7084 7085 "configuration block attribute list index syntax self conflicting": { 7086 Key: "self", 7087 Schema: &Schema{ 7088 Type: TypeBool, 7089 Optional: true, 7090 ConflictsWith: []string{"config_block_attr.0.self"}, 7091 }, 7092 Config: map[string]interface{}{ 7093 "config_block_attr": []interface{}{ 7094 map[string]interface{}{ 7095 "self": true, 7096 }, 7097 }, 7098 }, 7099 Err: true, 7100 }, 7101 7102 "configuration block attribute list index syntax conflicting unconfigured self unconfigured": { 7103 Key: "self", 7104 Schema: &Schema{ 7105 Type: TypeBool, 7106 Optional: true, 7107 ConflictsWith: []string{"config_block_attr.0.nested_attr"}, 7108 }, 7109 Config: map[string]interface{}{}, 7110 Err: false, 7111 }, 7112 7113 "configuration block attribute list index syntax conflicting unconfigured self unknown": { 7114 Key: "self", 7115 Schema: &Schema{ 7116 Type: TypeBool, 7117 Optional: true, 7118 ConflictsWith: []string{"config_block_attr.0.nested_attr"}, 7119 }, 7120 Config: map[string]interface{}{ 7121 "config_block_attr": []interface{}{ 7122 map[string]interface{}{ 7123 "self": hcl2shim.UnknownVariableValue, 7124 }, 7125 }, 7126 }, 7127 Err: false, 7128 }, 7129 7130 "configuration block attribute list index syntax conflicting unconfigured self known": { 7131 Key: "self", 7132 Schema: &Schema{ 7133 Type: TypeBool, 7134 Optional: true, 7135 ConflictsWith: []string{"config_block_attr.0.nested_attr"}, 7136 }, 7137 Config: map[string]interface{}{ 7138 "config_block_attr": []interface{}{ 7139 map[string]interface{}{ 7140 "self": true, 7141 }, 7142 }, 7143 }, 7144 Err: false, 7145 }, 7146 7147 "configuration block attribute list index syntax conflicting unknown self unconfigured": { 7148 Key: "self", 7149 Schema: &Schema{ 7150 Type: TypeBool, 7151 Optional: true, 7152 ConflictsWith: []string{"config_block_attr.0.nested_attr"}, 7153 }, 7154 Config: map[string]interface{}{ 7155 "config_block_attr": []interface{}{ 7156 map[string]interface{}{ 7157 "nested_attr": hcl2shim.UnknownVariableValue, 7158 }, 7159 }, 7160 }, 7161 Err: false, 7162 }, 7163 7164 "configuration block attribute list index syntax conflicting unknown self unknown": { 7165 Key: "self", 7166 Schema: &Schema{ 7167 Type: TypeBool, 7168 Optional: true, 7169 ConflictsWith: []string{"config_block_attr.0.nested_attr"}, 7170 }, 7171 Config: map[string]interface{}{ 7172 "config_block_attr": []interface{}{ 7173 map[string]interface{}{ 7174 "nested_attr": hcl2shim.UnknownVariableValue, 7175 "self": hcl2shim.UnknownVariableValue, 7176 }, 7177 }, 7178 }, 7179 Err: false, 7180 }, 7181 7182 "configuration block attribute list index syntax conflicting unknown self known": { 7183 Key: "self", 7184 Schema: &Schema{ 7185 Type: TypeBool, 7186 Optional: true, 7187 ConflictsWith: []string{"config_block_attr.0.nested_attr"}, 7188 }, 7189 Config: map[string]interface{}{ 7190 "config_block_attr": []interface{}{ 7191 map[string]interface{}{ 7192 "nested_attr": hcl2shim.UnknownVariableValue, 7193 "self": true, 7194 }, 7195 }, 7196 }, 7197 Err: false, 7198 }, 7199 7200 "configuration block attribute list index syntax conflicting known self unconfigured": { 7201 Key: "self", 7202 Schema: &Schema{ 7203 Type: TypeBool, 7204 Optional: true, 7205 ConflictsWith: []string{"config_block_attr.0.nested_attr"}, 7206 }, 7207 Config: map[string]interface{}{ 7208 "config_block_attr": []interface{}{ 7209 map[string]interface{}{ 7210 "nested_attr": true, 7211 }, 7212 }, 7213 }, 7214 Err: true, 7215 }, 7216 7217 "configuration block attribute list index syntax conflicting known self unknown": { 7218 Key: "self", 7219 Schema: &Schema{ 7220 Type: TypeBool, 7221 Optional: true, 7222 ConflictsWith: []string{"config_block_attr.0.nested_attr"}, 7223 }, 7224 Config: map[string]interface{}{ 7225 "config_block_attr": []interface{}{ 7226 map[string]interface{}{ 7227 "nested_attr": true, 7228 "self": hcl2shim.UnknownVariableValue, 7229 }, 7230 }, 7231 }, 7232 Err: true, 7233 }, 7234 7235 "configuration block attribute list index syntax conflicting known self known": { 7236 Key: "self", 7237 Schema: &Schema{ 7238 Type: TypeBool, 7239 Optional: true, 7240 ConflictsWith: []string{"config_block_attr.0.nested_attr"}, 7241 }, 7242 Config: map[string]interface{}{ 7243 "config_block_attr": []interface{}{ 7244 map[string]interface{}{ 7245 "nested_attr": true, 7246 "self": true, 7247 }, 7248 }, 7249 }, 7250 Err: true, 7251 }, 7252 7253 "configuration block attribute map key syntax self conflicting": { 7254 Key: "self", 7255 Schema: &Schema{ 7256 Type: TypeBool, 7257 Optional: true, 7258 ConflictsWith: []string{"config_block_attr.self"}, 7259 }, 7260 Config: map[string]interface{}{ 7261 "config_block_attr": []interface{}{ 7262 map[string]interface{}{ 7263 "self": true, 7264 }, 7265 }, 7266 }, 7267 Err: false, 7268 }, 7269 7270 "configuration block attribute map key syntax conflicting known self unconfigured": { 7271 Key: "self", 7272 Schema: &Schema{ 7273 Type: TypeBool, 7274 Optional: true, 7275 ConflictsWith: []string{"config_block_attr.nested_attr"}, 7276 }, 7277 Config: map[string]interface{}{ 7278 "config_block_attr": []interface{}{ 7279 map[string]interface{}{ 7280 "nested_attr": true, 7281 }, 7282 }, 7283 }, 7284 Err: false, 7285 }, 7286 7287 "configuration block attribute map key syntax conflicting known self unknown": { 7288 Key: "self", 7289 Schema: &Schema{ 7290 Type: TypeBool, 7291 Optional: true, 7292 ConflictsWith: []string{"config_block_attr.nested_attr"}, 7293 }, 7294 Config: map[string]interface{}{ 7295 "config_block_attr": []interface{}{ 7296 map[string]interface{}{ 7297 "nested_attr": true, 7298 "self": hcl2shim.UnknownVariableValue, 7299 }, 7300 }, 7301 }, 7302 Err: false, 7303 }, 7304 7305 "configuration block attribute map key syntax conflicting known self known": { 7306 Key: "self", 7307 Schema: &Schema{ 7308 Type: TypeBool, 7309 Optional: true, 7310 ConflictsWith: []string{"config_block_attr.nested_attr"}, 7311 }, 7312 Config: map[string]interface{}{ 7313 "config_block_attr": []interface{}{ 7314 map[string]interface{}{ 7315 "nested_attr": true, 7316 "self": true, 7317 }, 7318 }, 7319 }, 7320 Err: false, 7321 }, 7322 } 7323 7324 for tn, tc := range cases { 7325 t.Run(tn, func(t *testing.T) { 7326 c := terraform.NewResourceConfigRaw(tc.Config) 7327 7328 err := validateConflictingAttributes(tc.Key, tc.Schema, c) 7329 if err == nil && tc.Err { 7330 t.Fatalf("expected error") 7331 } 7332 7333 if err != nil && !tc.Err { 7334 t.Fatalf("didn't expect error, got error: %+v", err) 7335 } 7336 }) 7337 } 7338 7339 } 7340 7341 func TestValidateExactlyOneOfAttributes(t *testing.T) { 7342 cases := map[string]struct { 7343 Key string 7344 Schema map[string]*Schema 7345 Config map[string]interface{} 7346 Err bool 7347 }{ 7348 7349 "two attributes specified": { 7350 Key: "whitelist", 7351 Schema: map[string]*Schema{ 7352 "whitelist": { 7353 Type: TypeBool, 7354 Optional: true, 7355 ExactlyOneOf: []string{"blacklist"}, 7356 }, 7357 "blacklist": { 7358 Type: TypeBool, 7359 Optional: true, 7360 ExactlyOneOf: []string{"whitelist"}, 7361 }, 7362 }, 7363 7364 Config: map[string]interface{}{ 7365 "whitelist": true, 7366 "blacklist": true, 7367 }, 7368 Err: true, 7369 }, 7370 7371 "one attributes specified": { 7372 Key: "whitelist", 7373 Schema: map[string]*Schema{ 7374 "whitelist": { 7375 Type: TypeBool, 7376 Optional: true, 7377 ExactlyOneOf: []string{"blacklist"}, 7378 }, 7379 "blacklist": { 7380 Type: TypeBool, 7381 Optional: true, 7382 ExactlyOneOf: []string{"whitelist"}, 7383 }, 7384 }, 7385 7386 Config: map[string]interface{}{ 7387 "whitelist": true, 7388 }, 7389 Err: false, 7390 }, 7391 7392 "two attributes of three specified": { 7393 Key: "whitelist", 7394 Schema: map[string]*Schema{ 7395 "whitelist": { 7396 Type: TypeBool, 7397 Optional: true, 7398 ExactlyOneOf: []string{"blacklist", "purplelist"}, 7399 }, 7400 "blacklist": { 7401 Type: TypeBool, 7402 Optional: true, 7403 ExactlyOneOf: []string{"whitelist", "purplelist"}, 7404 }, 7405 "purplelist": { 7406 Type: TypeBool, 7407 Optional: true, 7408 ExactlyOneOf: []string{"whitelist", "blacklist"}, 7409 }, 7410 }, 7411 7412 Config: map[string]interface{}{ 7413 "whitelist": true, 7414 "purplelist": true, 7415 }, 7416 Err: true, 7417 }, 7418 7419 "one attributes of three specified": { 7420 Key: "whitelist", 7421 Schema: map[string]*Schema{ 7422 "whitelist": { 7423 Type: TypeBool, 7424 Optional: true, 7425 ExactlyOneOf: []string{"blacklist", "purplelist"}, 7426 }, 7427 "blacklist": { 7428 Type: TypeBool, 7429 Optional: true, 7430 ExactlyOneOf: []string{"whitelist", "purplelist"}, 7431 }, 7432 "purplelist": { 7433 Type: TypeBool, 7434 Optional: true, 7435 ExactlyOneOf: []string{"whitelist", "blacklist"}, 7436 }, 7437 }, 7438 7439 Config: map[string]interface{}{ 7440 "purplelist": true, 7441 }, 7442 Err: false, 7443 }, 7444 7445 "no attributes of three specified": { 7446 Key: "whitelist", 7447 Schema: map[string]*Schema{ 7448 "whitelist": { 7449 Type: TypeBool, 7450 Optional: true, 7451 ExactlyOneOf: []string{"blacklist", "purplelist"}, 7452 }, 7453 "blacklist": { 7454 Type: TypeBool, 7455 Optional: true, 7456 ExactlyOneOf: []string{"whitelist", "purplelist"}, 7457 }, 7458 "purplelist": { 7459 Type: TypeBool, 7460 Optional: true, 7461 ExactlyOneOf: []string{"whitelist", "blacklist"}, 7462 }, 7463 }, 7464 7465 Config: map[string]interface{}{}, 7466 Err: true, 7467 }, 7468 7469 "Only Unknown Variable Value": { 7470 Key: "whitelist", 7471 Schema: map[string]*Schema{ 7472 "whitelist": { 7473 Type: TypeBool, 7474 Optional: true, 7475 ExactlyOneOf: []string{"blacklist", "purplelist"}, 7476 }, 7477 "blacklist": { 7478 Type: TypeBool, 7479 Optional: true, 7480 ExactlyOneOf: []string{"whitelist", "purplelist"}, 7481 }, 7482 "purplelist": { 7483 Type: TypeBool, 7484 Optional: true, 7485 ExactlyOneOf: []string{"whitelist", "blacklist"}, 7486 }, 7487 }, 7488 7489 Config: map[string]interface{}{ 7490 "purplelist": hcl2shim.UnknownVariableValue, 7491 }, 7492 Err: false, 7493 }, 7494 7495 "Unknown Variable Value and Known Value": { 7496 Key: "whitelist", 7497 Schema: map[string]*Schema{ 7498 "whitelist": { 7499 Type: TypeBool, 7500 Optional: true, 7501 ExactlyOneOf: []string{"blacklist", "purplelist"}, 7502 }, 7503 "blacklist": { 7504 Type: TypeBool, 7505 Optional: true, 7506 ExactlyOneOf: []string{"whitelist", "purplelist"}, 7507 }, 7508 "purplelist": { 7509 Type: TypeBool, 7510 Optional: true, 7511 ExactlyOneOf: []string{"whitelist", "blacklist"}, 7512 }, 7513 }, 7514 7515 Config: map[string]interface{}{ 7516 "purplelist": hcl2shim.UnknownVariableValue, 7517 "whitelist": true, 7518 }, 7519 Err: false, 7520 }, 7521 7522 "Unknown Variable Value and 2 Known Value": { 7523 Key: "whitelist", 7524 Schema: map[string]*Schema{ 7525 "whitelist": { 7526 Type: TypeBool, 7527 Optional: true, 7528 ExactlyOneOf: []string{"blacklist", "purplelist"}, 7529 }, 7530 "blacklist": { 7531 Type: TypeBool, 7532 Optional: true, 7533 ExactlyOneOf: []string{"whitelist", "purplelist"}, 7534 }, 7535 "purplelist": { 7536 Type: TypeBool, 7537 Optional: true, 7538 ExactlyOneOf: []string{"whitelist", "blacklist"}, 7539 }, 7540 }, 7541 7542 Config: map[string]interface{}{ 7543 "purplelist": hcl2shim.UnknownVariableValue, 7544 "whitelist": true, 7545 "blacklist": true, 7546 }, 7547 Err: true, 7548 }, 7549 7550 "unknown list values": { 7551 Key: "allow", 7552 Schema: map[string]*Schema{ 7553 "allow": { 7554 Type: TypeList, 7555 Optional: true, 7556 Elem: &Resource{ 7557 Schema: map[string]*Schema{ 7558 "protocol": { 7559 Type: TypeString, 7560 Required: true, 7561 }, 7562 "ports": { 7563 Type: TypeList, 7564 Optional: true, 7565 Elem: &Schema{ 7566 Type: TypeString, 7567 }, 7568 }, 7569 }, 7570 }, 7571 ExactlyOneOf: []string{"allow", "deny"}, 7572 }, 7573 "deny": { 7574 Type: TypeList, 7575 Optional: true, 7576 Elem: &Resource{ 7577 Schema: map[string]*Schema{ 7578 "protocol": { 7579 Type: TypeString, 7580 Required: true, 7581 }, 7582 "ports": { 7583 Type: TypeList, 7584 Optional: true, 7585 Elem: &Schema{ 7586 Type: TypeString, 7587 }, 7588 }, 7589 }, 7590 }, 7591 ExactlyOneOf: []string{"allow", "deny"}, 7592 }, 7593 "purplelist": { 7594 Type: TypeString, 7595 Optional: true, 7596 }, 7597 }, 7598 Config: map[string]interface{}{ 7599 "allow": []interface{}{map[string]interface{}{ 7600 "ports": hcl2shim.UnknownVariableValue, 7601 "protocol": hcl2shim.UnknownVariableValue, 7602 }}, 7603 "deny": []interface{}{map[string]interface{}{ 7604 "ports": hcl2shim.UnknownVariableValue, 7605 "protocol": hcl2shim.UnknownVariableValue, 7606 }}, 7607 "purplelist": "blah", 7608 }, 7609 Err: false, 7610 }, 7611 7612 // This should probably fail, but we let it pass and rely on 2nd 7613 // validation phase when unknowns become known, which will then fail. 7614 "partially known list values": { 7615 Key: "allow", 7616 Schema: map[string]*Schema{ 7617 "allow": { 7618 Type: TypeList, 7619 Optional: true, 7620 Elem: &Resource{ 7621 Schema: map[string]*Schema{ 7622 "protocol": { 7623 Type: TypeString, 7624 Required: true, 7625 }, 7626 "ports": { 7627 Type: TypeList, 7628 Optional: true, 7629 Elem: &Schema{ 7630 Type: TypeString, 7631 }, 7632 }, 7633 }, 7634 }, 7635 ExactlyOneOf: []string{"allow", "deny"}, 7636 }, 7637 "deny": { 7638 Type: TypeList, 7639 Optional: true, 7640 Elem: &Resource{ 7641 Schema: map[string]*Schema{ 7642 "protocol": { 7643 Type: TypeString, 7644 Required: true, 7645 }, 7646 "ports": { 7647 Type: TypeList, 7648 Optional: true, 7649 Elem: &Schema{ 7650 Type: TypeString, 7651 }, 7652 }, 7653 }, 7654 }, 7655 ExactlyOneOf: []string{"allow", "deny"}, 7656 }, 7657 "purplelist": { 7658 Type: TypeString, 7659 Optional: true, 7660 }, 7661 }, 7662 Config: map[string]interface{}{ 7663 "allow": []interface{}{map[string]interface{}{ 7664 "ports": hcl2shim.UnknownVariableValue, 7665 "protocol": "TCP", 7666 }}, 7667 "deny": []interface{}{map[string]interface{}{ 7668 "ports": hcl2shim.UnknownVariableValue, 7669 "protocol": "TCP", 7670 }}, 7671 "purplelist": "blah", 7672 }, 7673 Err: false, 7674 }, 7675 7676 "known list values": { 7677 Key: "allow", 7678 Schema: map[string]*Schema{ 7679 "allow": { 7680 Type: TypeList, 7681 Optional: true, 7682 Elem: &Resource{ 7683 Schema: map[string]*Schema{ 7684 "protocol": { 7685 Type: TypeString, 7686 Required: true, 7687 }, 7688 "ports": { 7689 Type: TypeList, 7690 Optional: true, 7691 Elem: &Schema{ 7692 Type: TypeString, 7693 }, 7694 }, 7695 }, 7696 }, 7697 ExactlyOneOf: []string{"allow", "deny"}, 7698 }, 7699 "deny": { 7700 Type: TypeList, 7701 Optional: true, 7702 Elem: &Resource{ 7703 Schema: map[string]*Schema{ 7704 "protocol": { 7705 Type: TypeString, 7706 Required: true, 7707 }, 7708 "ports": { 7709 Type: TypeList, 7710 Optional: true, 7711 Elem: &Schema{ 7712 Type: TypeString, 7713 }, 7714 }, 7715 }, 7716 }, 7717 ExactlyOneOf: []string{"allow", "deny"}, 7718 }, 7719 }, 7720 Config: map[string]interface{}{ 7721 "allow": []interface{}{map[string]interface{}{ 7722 "ports": []interface{}{"80"}, 7723 "protocol": "TCP", 7724 }}, 7725 "deny": []interface{}{map[string]interface{}{ 7726 "ports": []interface{}{"80"}, 7727 "protocol": "TCP", 7728 }}, 7729 }, 7730 Err: true, 7731 }, 7732 7733 "wholly unknown set values": { 7734 Key: "allow", 7735 Schema: map[string]*Schema{ 7736 "allow": { 7737 Type: TypeSet, 7738 Optional: true, 7739 Elem: &Resource{ 7740 Schema: map[string]*Schema{ 7741 "protocol": { 7742 Type: TypeString, 7743 Required: true, 7744 }, 7745 "ports": { 7746 Type: TypeList, 7747 Optional: true, 7748 Elem: &Schema{ 7749 Type: TypeString, 7750 }, 7751 }, 7752 }, 7753 }, 7754 ExactlyOneOf: []string{"allow", "deny"}, 7755 }, 7756 "deny": { 7757 Type: TypeSet, 7758 Optional: true, 7759 Elem: &Resource{ 7760 Schema: map[string]*Schema{ 7761 "protocol": { 7762 Type: TypeString, 7763 Required: true, 7764 }, 7765 "ports": { 7766 Type: TypeList, 7767 Optional: true, 7768 Elem: &Schema{ 7769 Type: TypeString, 7770 }, 7771 }, 7772 }, 7773 }, 7774 ExactlyOneOf: []string{"allow", "deny"}, 7775 }, 7776 "purplelist": { 7777 Type: TypeString, 7778 Optional: true, 7779 }, 7780 }, 7781 Config: map[string]interface{}{ 7782 "allow": []interface{}{map[string]interface{}{ 7783 "ports": hcl2shim.UnknownVariableValue, 7784 "protocol": hcl2shim.UnknownVariableValue, 7785 }}, 7786 "deny": []interface{}{map[string]interface{}{ 7787 "ports": hcl2shim.UnknownVariableValue, 7788 "protocol": hcl2shim.UnknownVariableValue, 7789 }}, 7790 "purplelist": "blah", 7791 }, 7792 Err: false, 7793 }, 7794 7795 // This should probably fail, but we let it pass and rely on 2nd 7796 // validation phase when unknowns become known, which will then fail. 7797 "partially known set values": { 7798 Key: "allow", 7799 Schema: map[string]*Schema{ 7800 "allow": { 7801 Type: TypeSet, 7802 Optional: true, 7803 Elem: &Resource{ 7804 Schema: map[string]*Schema{ 7805 "protocol": { 7806 Type: TypeString, 7807 Required: true, 7808 }, 7809 "ports": { 7810 Type: TypeList, 7811 Optional: true, 7812 Elem: &Schema{ 7813 Type: TypeString, 7814 }, 7815 }, 7816 }, 7817 }, 7818 ExactlyOneOf: []string{"allow", "deny"}, 7819 }, 7820 "deny": { 7821 Type: TypeSet, 7822 Optional: true, 7823 Elem: &Resource{ 7824 Schema: map[string]*Schema{ 7825 "protocol": { 7826 Type: TypeString, 7827 Required: true, 7828 }, 7829 "ports": { 7830 Type: TypeList, 7831 Optional: true, 7832 Elem: &Schema{ 7833 Type: TypeString, 7834 }, 7835 }, 7836 }, 7837 }, 7838 ExactlyOneOf: []string{"allow", "deny"}, 7839 }, 7840 }, 7841 Config: map[string]interface{}{ 7842 "allow": []interface{}{map[string]interface{}{ 7843 "ports": hcl2shim.UnknownVariableValue, 7844 "protocol": "TCP", 7845 }}, 7846 "deny": []interface{}{map[string]interface{}{ 7847 "ports": hcl2shim.UnknownVariableValue, 7848 "protocol": "UDP", 7849 }}, 7850 }, 7851 Err: false, 7852 }, 7853 7854 "known set values": { 7855 Key: "allow", 7856 Schema: map[string]*Schema{ 7857 "allow": { 7858 Type: TypeSet, 7859 Optional: true, 7860 Elem: &Resource{ 7861 Schema: map[string]*Schema{ 7862 "protocol": { 7863 Type: TypeString, 7864 Required: true, 7865 }, 7866 "ports": { 7867 Type: TypeList, 7868 Optional: true, 7869 Elem: &Schema{ 7870 Type: TypeString, 7871 }, 7872 }, 7873 }, 7874 }, 7875 ExactlyOneOf: []string{"allow", "deny"}, 7876 }, 7877 "deny": { 7878 Type: TypeSet, 7879 Optional: true, 7880 Elem: &Resource{ 7881 Schema: map[string]*Schema{ 7882 "protocol": { 7883 Type: TypeString, 7884 Required: true, 7885 }, 7886 "ports": { 7887 Type: TypeList, 7888 Optional: true, 7889 Elem: &Schema{ 7890 Type: TypeString, 7891 }, 7892 }, 7893 }, 7894 }, 7895 ExactlyOneOf: []string{"allow", "deny"}, 7896 }, 7897 }, 7898 Config: map[string]interface{}{ 7899 "allow": []interface{}{map[string]interface{}{ 7900 "ports": []interface{}{"80"}, 7901 "protocol": "TCP", 7902 }}, 7903 "deny": []interface{}{map[string]interface{}{ 7904 "ports": []interface{}{"80"}, 7905 "protocol": "TCP", 7906 }}, 7907 }, 7908 Err: true, 7909 }, 7910 7911 "wholly unknown simple lists": { 7912 Key: "allow", 7913 Schema: map[string]*Schema{ 7914 "allow": { 7915 Type: TypeList, 7916 Optional: true, 7917 Elem: &Schema{Type: TypeString}, 7918 ExactlyOneOf: []string{"allow", "deny"}, 7919 }, 7920 "deny": { 7921 Type: TypeList, 7922 Optional: true, 7923 Elem: &Schema{Type: TypeString}, 7924 ExactlyOneOf: []string{"allow", "deny"}, 7925 }, 7926 "purplelist": { 7927 Type: TypeString, 7928 Optional: true, 7929 }, 7930 }, 7931 Config: map[string]interface{}{ 7932 "allow": []interface{}{ 7933 hcl2shim.UnknownVariableValue, 7934 hcl2shim.UnknownVariableValue, 7935 }, 7936 "deny": []interface{}{ 7937 hcl2shim.UnknownVariableValue, 7938 hcl2shim.UnknownVariableValue, 7939 }, 7940 "purplelist": "blah", 7941 }, 7942 Err: false, 7943 }, 7944 7945 // This should probably fail, but we let it pass and rely on 2nd 7946 // validation phase when unknowns become known, which will then fail. 7947 "partially known simple lists": { 7948 Key: "allow", 7949 Schema: map[string]*Schema{ 7950 "allow": { 7951 Type: TypeList, 7952 Optional: true, 7953 Elem: &Schema{Type: TypeString}, 7954 ExactlyOneOf: []string{"allow", "deny"}, 7955 }, 7956 "deny": { 7957 Type: TypeList, 7958 Optional: true, 7959 Elem: &Schema{Type: TypeString}, 7960 ExactlyOneOf: []string{"allow", "deny"}, 7961 }, 7962 }, 7963 Config: map[string]interface{}{ 7964 "allow": []interface{}{ 7965 hcl2shim.UnknownVariableValue, 7966 "known", 7967 }, 7968 "deny": []interface{}{ 7969 hcl2shim.UnknownVariableValue, 7970 "known", 7971 }, 7972 }, 7973 Err: false, 7974 }, 7975 7976 "known simple lists": { 7977 Key: "allow", 7978 Schema: map[string]*Schema{ 7979 "allow": { 7980 Type: TypeList, 7981 Optional: true, 7982 Elem: &Schema{Type: TypeString}, 7983 ExactlyOneOf: []string{"allow", "deny"}, 7984 }, 7985 "deny": { 7986 Type: TypeList, 7987 Optional: true, 7988 Elem: &Schema{Type: TypeString}, 7989 ExactlyOneOf: []string{"allow", "deny"}, 7990 }, 7991 }, 7992 Config: map[string]interface{}{ 7993 "allow": []interface{}{ 7994 "blah", 7995 "known", 7996 }, 7997 "deny": []interface{}{ 7998 "known", 7999 }, 8000 }, 8001 Err: true, 8002 }, 8003 8004 "wholly unknown map keys and values": { 8005 Key: "allow", 8006 Schema: map[string]*Schema{ 8007 "allow": { 8008 Type: TypeMap, 8009 Optional: true, 8010 ExactlyOneOf: []string{"allow", "deny"}, 8011 }, 8012 "deny": { 8013 Type: TypeList, 8014 Optional: true, 8015 ExactlyOneOf: []string{"allow", "deny"}, 8016 }, 8017 "purplelist": { 8018 Type: TypeString, 8019 Optional: true, 8020 }, 8021 }, 8022 Config: map[string]interface{}{ 8023 "allow": map[string]interface{}{ 8024 hcl2shim.UnknownVariableValue: hcl2shim.UnknownVariableValue, 8025 }, 8026 "deny": map[string]interface{}{ 8027 hcl2shim.UnknownVariableValue: hcl2shim.UnknownVariableValue, 8028 }, 8029 "purplelist": "blah", 8030 }, 8031 Err: false, 8032 }, 8033 8034 // This should probably fail, but we let it pass and rely on 2nd 8035 // validation phase when unknowns become known, which will then fail. 8036 "wholly unknown map values": { 8037 Key: "allow", 8038 Schema: map[string]*Schema{ 8039 "allow": { 8040 Type: TypeMap, 8041 Optional: true, 8042 ExactlyOneOf: []string{"allow", "deny"}, 8043 }, 8044 "deny": { 8045 Type: TypeList, 8046 Optional: true, 8047 ExactlyOneOf: []string{"allow", "deny"}, 8048 }, 8049 "purplelist": { 8050 Type: TypeString, 8051 Optional: true, 8052 }, 8053 }, 8054 Config: map[string]interface{}{ 8055 "allow": map[string]interface{}{ 8056 "key": hcl2shim.UnknownVariableValue, 8057 }, 8058 "deny": map[string]interface{}{ 8059 "key": hcl2shim.UnknownVariableValue, 8060 }, 8061 "purplelist": "blah", 8062 }, 8063 Err: false, 8064 }, 8065 8066 // This should probably fail, but we let it pass and rely on 2nd 8067 // validation phase when unknowns become known, which will then fail. 8068 "partially known maps": { 8069 Key: "allow", 8070 Schema: map[string]*Schema{ 8071 "allow": { 8072 Type: TypeMap, 8073 Optional: true, 8074 ExactlyOneOf: []string{"allow", "deny"}, 8075 }, 8076 "deny": { 8077 Type: TypeList, 8078 Optional: true, 8079 ExactlyOneOf: []string{"allow", "deny"}, 8080 }, 8081 "purplelist": { 8082 Type: TypeString, 8083 Optional: true, 8084 }, 8085 }, 8086 Config: map[string]interface{}{ 8087 "allow": map[string]interface{}{ 8088 "first": "value", 8089 "second": hcl2shim.UnknownVariableValue, 8090 }, 8091 "deny": map[string]interface{}{ 8092 "first": "value", 8093 "second": hcl2shim.UnknownVariableValue, 8094 }, 8095 "purplelist": "blah", 8096 }, 8097 Err: false, 8098 }, 8099 8100 "known maps": { 8101 Key: "allow", 8102 Schema: map[string]*Schema{ 8103 "allow": { 8104 Type: TypeMap, 8105 Optional: true, 8106 ExactlyOneOf: []string{"allow", "deny"}, 8107 }, 8108 "deny": { 8109 Type: TypeList, 8110 Optional: true, 8111 ExactlyOneOf: []string{"allow", "deny"}, 8112 }, 8113 }, 8114 Config: map[string]interface{}{ 8115 "allow": map[string]interface{}{ 8116 "first": "value", 8117 "second": "blah", 8118 }, 8119 "deny": map[string]interface{}{ 8120 "first": "value", 8121 "second": "boo", 8122 }, 8123 }, 8124 Err: true, 8125 }, 8126 } 8127 8128 for tn, tc := range cases { 8129 t.Run(tn, func(t *testing.T) { 8130 c := terraform.NewResourceConfigRaw(tc.Config) 8131 8132 err := validateExactlyOneAttribute(tc.Key, tc.Schema[tc.Key], c) 8133 if err == nil && tc.Err { 8134 t.Fatalf("expected error") 8135 } 8136 8137 if err != nil && !tc.Err { 8138 t.Fatalf("didn't expect error, got error: %+v", err) 8139 } 8140 }) 8141 } 8142 8143 } 8144 8145 func TestValidateAtLeastOneOfAttributes(t *testing.T) { 8146 cases := map[string]struct { 8147 Key string 8148 Schema map[string]*Schema 8149 Config map[string]interface{} 8150 Err bool 8151 }{ 8152 8153 "two attributes specified": { 8154 Key: "whitelist", 8155 Schema: map[string]*Schema{ 8156 "whitelist": { 8157 Type: TypeBool, 8158 Optional: true, 8159 AtLeastOneOf: []string{"blacklist"}, 8160 }, 8161 "blacklist": { 8162 Type: TypeBool, 8163 Optional: true, 8164 AtLeastOneOf: []string{"whitelist"}, 8165 }, 8166 }, 8167 8168 Config: map[string]interface{}{ 8169 "whitelist": true, 8170 "blacklist": true, 8171 }, 8172 Err: false, 8173 }, 8174 8175 "one attributes specified": { 8176 Key: "whitelist", 8177 Schema: map[string]*Schema{ 8178 "whitelist": { 8179 Type: TypeBool, 8180 Optional: true, 8181 AtLeastOneOf: []string{"blacklist"}, 8182 }, 8183 "blacklist": { 8184 Type: TypeBool, 8185 Optional: true, 8186 AtLeastOneOf: []string{"whitelist"}, 8187 }, 8188 }, 8189 8190 Config: map[string]interface{}{ 8191 "whitelist": true, 8192 }, 8193 Err: false, 8194 }, 8195 8196 "two attributes of three specified": { 8197 Key: "whitelist", 8198 Schema: map[string]*Schema{ 8199 "whitelist": { 8200 Type: TypeBool, 8201 Optional: true, 8202 AtLeastOneOf: []string{"blacklist", "purplelist"}, 8203 }, 8204 "blacklist": { 8205 Type: TypeBool, 8206 Optional: true, 8207 AtLeastOneOf: []string{"whitelist", "purplelist"}, 8208 }, 8209 "purplelist": { 8210 Type: TypeBool, 8211 Optional: true, 8212 AtLeastOneOf: []string{"whitelist", "blacklist"}, 8213 }, 8214 }, 8215 8216 Config: map[string]interface{}{ 8217 "whitelist": true, 8218 "purplelist": true, 8219 }, 8220 Err: false, 8221 }, 8222 8223 "three attributes of three specified": { 8224 Key: "whitelist", 8225 Schema: map[string]*Schema{ 8226 "whitelist": { 8227 Type: TypeBool, 8228 Optional: true, 8229 AtLeastOneOf: []string{"blacklist", "purplelist"}, 8230 }, 8231 "blacklist": { 8232 Type: TypeBool, 8233 Optional: true, 8234 AtLeastOneOf: []string{"whitelist", "purplelist"}, 8235 }, 8236 "purplelist": { 8237 Type: TypeBool, 8238 Optional: true, 8239 AtLeastOneOf: []string{"whitelist", "blacklist"}, 8240 }, 8241 }, 8242 8243 Config: map[string]interface{}{ 8244 "whitelist": true, 8245 "purplelist": true, 8246 "blacklist": true, 8247 }, 8248 Err: false, 8249 }, 8250 8251 "one attributes of three specified": { 8252 Key: "whitelist", 8253 Schema: map[string]*Schema{ 8254 "whitelist": { 8255 Type: TypeBool, 8256 Optional: true, 8257 AtLeastOneOf: []string{"blacklist", "purplelist"}, 8258 }, 8259 "blacklist": { 8260 Type: TypeBool, 8261 Optional: true, 8262 AtLeastOneOf: []string{"whitelist", "purplelist"}, 8263 }, 8264 "purplelist": { 8265 Type: TypeBool, 8266 Optional: true, 8267 AtLeastOneOf: []string{"whitelist", "blacklist"}, 8268 }, 8269 }, 8270 8271 Config: map[string]interface{}{ 8272 "purplelist": true, 8273 }, 8274 Err: false, 8275 }, 8276 8277 "no attributes of three specified": { 8278 Key: "whitelist", 8279 Schema: map[string]*Schema{ 8280 "whitelist": { 8281 Type: TypeBool, 8282 Optional: true, 8283 AtLeastOneOf: []string{"whitelist", "blacklist", "purplelist"}, 8284 }, 8285 "blacklist": { 8286 Type: TypeBool, 8287 Optional: true, 8288 AtLeastOneOf: []string{"whitelist", "blacklist", "purplelist"}, 8289 }, 8290 "purplelist": { 8291 Type: TypeBool, 8292 Optional: true, 8293 AtLeastOneOf: []string{"whitelist", "blacklist", "purplelist"}, 8294 }, 8295 }, 8296 8297 Config: map[string]interface{}{}, 8298 Err: true, 8299 }, 8300 8301 "Only Unknown Variable Value": { 8302 Schema: map[string]*Schema{ 8303 "whitelist": { 8304 Type: TypeBool, 8305 Optional: true, 8306 AtLeastOneOf: []string{"whitelist", "blacklist", "purplelist"}, 8307 }, 8308 "blacklist": { 8309 Type: TypeBool, 8310 Optional: true, 8311 AtLeastOneOf: []string{"whitelist", "blacklist", "purplelist"}, 8312 }, 8313 "purplelist": { 8314 Type: TypeBool, 8315 Optional: true, 8316 AtLeastOneOf: []string{"whitelist", "blacklist", "purplelist"}, 8317 }, 8318 }, 8319 8320 Config: map[string]interface{}{ 8321 "whitelist": hcl2shim.UnknownVariableValue, 8322 }, 8323 8324 Err: false, 8325 }, 8326 8327 "only unknown list value": { 8328 Schema: map[string]*Schema{ 8329 "whitelist": { 8330 Type: TypeList, 8331 Optional: true, 8332 Elem: &Schema{Type: TypeString}, 8333 AtLeastOneOf: []string{"whitelist", "blacklist"}, 8334 }, 8335 "blacklist": { 8336 Type: TypeList, 8337 Optional: true, 8338 Elem: &Schema{Type: TypeString}, 8339 AtLeastOneOf: []string{"whitelist", "blacklist"}, 8340 }, 8341 }, 8342 8343 Config: map[string]interface{}{ 8344 "whitelist": []interface{}{hcl2shim.UnknownVariableValue}, 8345 }, 8346 8347 Err: false, 8348 }, 8349 8350 "Unknown Variable Value and Known Value": { 8351 Schema: map[string]*Schema{ 8352 "whitelist": { 8353 Type: TypeBool, 8354 Optional: true, 8355 AtLeastOneOf: []string{"whitelist", "blacklist", "purplelist"}, 8356 }, 8357 "blacklist": { 8358 Type: TypeBool, 8359 Optional: true, 8360 AtLeastOneOf: []string{"whitelist", "blacklist", "purplelist"}, 8361 }, 8362 "purplelist": { 8363 Type: TypeBool, 8364 Optional: true, 8365 AtLeastOneOf: []string{"whitelist", "blacklist", "purplelist"}, 8366 }, 8367 }, 8368 8369 Config: map[string]interface{}{ 8370 "whitelist": hcl2shim.UnknownVariableValue, 8371 "blacklist": true, 8372 }, 8373 8374 Err: false, 8375 }, 8376 } 8377 8378 for tn, tc := range cases { 8379 t.Run(tn, func(t *testing.T) { 8380 c := terraform.NewResourceConfigRaw(tc.Config) 8381 _, es := schemaMap(tc.Schema).Validate(c) 8382 if len(es) > 0 != tc.Err { 8383 if len(es) == 0 { 8384 t.Fatalf("expected error") 8385 } 8386 8387 for _, e := range es { 8388 t.Fatalf("didn't expect error, got error: %+v", e) 8389 } 8390 8391 t.FailNow() 8392 } 8393 }) 8394 } 8395 } 8396 8397 func TestValidateRequiredWithAttributes(t *testing.T) { 8398 cases := map[string]struct { 8399 Key string 8400 Schema map[string]*Schema 8401 Config map[string]interface{} 8402 Err bool 8403 }{ 8404 8405 "two attributes specified": { 8406 Key: "whitelist", 8407 Schema: map[string]*Schema{ 8408 "whitelist": { 8409 Type: TypeBool, 8410 Optional: true, 8411 RequiredWith: []string{"blacklist"}, 8412 }, 8413 "blacklist": { 8414 Type: TypeBool, 8415 Optional: true, 8416 RequiredWith: []string{"whitelist"}, 8417 }, 8418 }, 8419 8420 Config: map[string]interface{}{ 8421 "whitelist": true, 8422 "blacklist": true, 8423 }, 8424 Err: false, 8425 }, 8426 8427 "one attributes specified": { 8428 Key: "whitelist", 8429 Schema: map[string]*Schema{ 8430 "whitelist": { 8431 Type: TypeBool, 8432 Optional: true, 8433 RequiredWith: []string{"blacklist"}, 8434 }, 8435 "blacklist": { 8436 Type: TypeBool, 8437 Optional: true, 8438 RequiredWith: []string{"whitelist"}, 8439 }, 8440 }, 8441 8442 Config: map[string]interface{}{ 8443 "whitelist": true, 8444 }, 8445 Err: true, 8446 }, 8447 8448 "no attributes specified": { 8449 Key: "whitelist", 8450 Schema: map[string]*Schema{ 8451 "whitelist": { 8452 Type: TypeBool, 8453 Optional: true, 8454 RequiredWith: []string{"blacklist"}, 8455 }, 8456 "blacklist": { 8457 Type: TypeBool, 8458 Optional: true, 8459 RequiredWith: []string{"whitelist"}, 8460 }, 8461 }, 8462 8463 Config: map[string]interface{}{}, 8464 Err: false, 8465 }, 8466 8467 "two attributes of three specified": { 8468 Key: "whitelist", 8469 Schema: map[string]*Schema{ 8470 "whitelist": { 8471 Type: TypeBool, 8472 Optional: true, 8473 RequiredWith: []string{"purplelist"}, 8474 }, 8475 "blacklist": { 8476 Type: TypeBool, 8477 Optional: true, 8478 RequiredWith: []string{"whitelist", "purplelist"}, 8479 }, 8480 "purplelist": { 8481 Type: TypeBool, 8482 Optional: true, 8483 RequiredWith: []string{"whitelist"}, 8484 }, 8485 }, 8486 8487 Config: map[string]interface{}{ 8488 "whitelist": true, 8489 "purplelist": true, 8490 }, 8491 Err: false, 8492 }, 8493 8494 "three attributes of three specified": { 8495 Key: "whitelist", 8496 Schema: map[string]*Schema{ 8497 "whitelist": { 8498 Type: TypeBool, 8499 Optional: true, 8500 RequiredWith: []string{"blacklist", "purplelist"}, 8501 }, 8502 "blacklist": { 8503 Type: TypeBool, 8504 Optional: true, 8505 RequiredWith: []string{"whitelist", "purplelist"}, 8506 }, 8507 "purplelist": { 8508 Type: TypeBool, 8509 Optional: true, 8510 RequiredWith: []string{"whitelist", "blacklist"}, 8511 }, 8512 }, 8513 8514 Config: map[string]interface{}{ 8515 "whitelist": true, 8516 "purplelist": true, 8517 "blacklist": true, 8518 }, 8519 Err: false, 8520 }, 8521 8522 "one attributes of three specified": { 8523 Key: "whitelist", 8524 Schema: map[string]*Schema{ 8525 "whitelist": { 8526 Type: TypeBool, 8527 Optional: true, 8528 RequiredWith: []string{"blacklist", "purplelist"}, 8529 }, 8530 "blacklist": { 8531 Type: TypeBool, 8532 Optional: true, 8533 RequiredWith: []string{"whitelist", "purplelist"}, 8534 }, 8535 "purplelist": { 8536 Type: TypeBool, 8537 Optional: true, 8538 RequiredWith: []string{"whitelist", "blacklist"}, 8539 }, 8540 }, 8541 8542 Config: map[string]interface{}{ 8543 "purplelist": true, 8544 }, 8545 Err: true, 8546 }, 8547 8548 "no attributes of three specified": { 8549 Key: "whitelist", 8550 Schema: map[string]*Schema{ 8551 "whitelist": { 8552 Type: TypeBool, 8553 Optional: true, 8554 RequiredWith: []string{"whitelist", "blacklist", "purplelist"}, 8555 }, 8556 "blacklist": { 8557 Type: TypeBool, 8558 Optional: true, 8559 RequiredWith: []string{"whitelist", "blacklist", "purplelist"}, 8560 }, 8561 "purplelist": { 8562 Type: TypeBool, 8563 Optional: true, 8564 RequiredWith: []string{"whitelist", "blacklist", "purplelist"}, 8565 }, 8566 }, 8567 8568 Config: map[string]interface{}{}, 8569 Err: false, 8570 }, 8571 8572 "Only Unknown Variable Value": { 8573 Schema: map[string]*Schema{ 8574 "whitelist": { 8575 Type: TypeBool, 8576 Optional: true, 8577 RequiredWith: []string{"whitelist", "blacklist", "purplelist"}, 8578 }, 8579 "blacklist": { 8580 Type: TypeBool, 8581 Optional: true, 8582 RequiredWith: []string{"whitelist", "blacklist", "purplelist"}, 8583 }, 8584 "purplelist": { 8585 Type: TypeBool, 8586 Optional: true, 8587 RequiredWith: []string{"whitelist", "blacklist", "purplelist"}, 8588 }, 8589 }, 8590 8591 Config: map[string]interface{}{ 8592 "whitelist": hcl2shim.UnknownVariableValue, 8593 }, 8594 8595 Err: true, 8596 }, 8597 8598 "only unknown list value": { 8599 Schema: map[string]*Schema{ 8600 "whitelist": { 8601 Type: TypeList, 8602 Optional: true, 8603 Elem: &Schema{Type: TypeString}, 8604 RequiredWith: []string{"whitelist", "blacklist"}, 8605 }, 8606 "blacklist": { 8607 Type: TypeList, 8608 Optional: true, 8609 Elem: &Schema{Type: TypeString}, 8610 RequiredWith: []string{"whitelist", "blacklist"}, 8611 }, 8612 }, 8613 8614 Config: map[string]interface{}{ 8615 "whitelist": []interface{}{hcl2shim.UnknownVariableValue}, 8616 }, 8617 8618 Err: true, 8619 }, 8620 8621 "Unknown Variable Value and Known Value": { 8622 Schema: map[string]*Schema{ 8623 "whitelist": { 8624 Type: TypeBool, 8625 Optional: true, 8626 RequiredWith: []string{"whitelist", "blacklist", "purplelist"}, 8627 }, 8628 "blacklist": { 8629 Type: TypeBool, 8630 Optional: true, 8631 RequiredWith: []string{"whitelist", "blacklist", "purplelist"}, 8632 }, 8633 "purplelist": { 8634 Type: TypeBool, 8635 Optional: true, 8636 RequiredWith: []string{"whitelist", "blacklist", "purplelist"}, 8637 }, 8638 }, 8639 8640 Config: map[string]interface{}{ 8641 "whitelist": hcl2shim.UnknownVariableValue, 8642 "blacklist": true, 8643 }, 8644 8645 Err: true, 8646 }, 8647 } 8648 8649 for tn, tc := range cases { 8650 t.Run(tn, func(t *testing.T) { 8651 c := terraform.NewResourceConfigRaw(tc.Config) 8652 _, es := schemaMap(tc.Schema).Validate(c) 8653 if len(es) > 0 != tc.Err { 8654 if len(es) == 0 { 8655 t.Fatalf("expected error") 8656 } 8657 8658 for _, e := range es { 8659 t.Fatalf("didn't expect error, got error: %+v", e) 8660 } 8661 8662 t.FailNow() 8663 } 8664 }) 8665 } 8666 }