github.com/richardmarshall/terraform@v0.9.5-0.20170429023105-15704cc6ee35/helper/schema/schema_test.go (about) 1 package schema 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "reflect" 8 "sort" 9 "strconv" 10 "strings" 11 "testing" 12 13 "github.com/hashicorp/hil" 14 "github.com/hashicorp/hil/ast" 15 "github.com/hashicorp/terraform/config" 16 "github.com/hashicorp/terraform/helper/hashcode" 17 "github.com/hashicorp/terraform/terraform" 18 ) 19 20 func TestEnvDefaultFunc(t *testing.T) { 21 key := "TF_TEST_ENV_DEFAULT_FUNC" 22 defer os.Unsetenv(key) 23 24 f := EnvDefaultFunc(key, "42") 25 if err := os.Setenv(key, "foo"); err != nil { 26 t.Fatalf("err: %s", err) 27 } 28 29 actual, err := f() 30 if err != nil { 31 t.Fatalf("err: %s", err) 32 } 33 if actual != "foo" { 34 t.Fatalf("bad: %#v", actual) 35 } 36 37 if err := os.Unsetenv(key); err != nil { 38 t.Fatalf("err: %s", err) 39 } 40 41 actual, err = f() 42 if err != nil { 43 t.Fatalf("err: %s", err) 44 } 45 if actual != "42" { 46 t.Fatalf("bad: %#v", actual) 47 } 48 } 49 50 func TestMultiEnvDefaultFunc(t *testing.T) { 51 keys := []string{ 52 "TF_TEST_MULTI_ENV_DEFAULT_FUNC1", 53 "TF_TEST_MULTI_ENV_DEFAULT_FUNC2", 54 } 55 defer func() { 56 for _, k := range keys { 57 os.Unsetenv(k) 58 } 59 }() 60 61 // Test that the first key is returned first 62 f := MultiEnvDefaultFunc(keys, "42") 63 if err := os.Setenv(keys[0], "foo"); err != nil { 64 t.Fatalf("err: %s", err) 65 } 66 67 actual, err := f() 68 if err != nil { 69 t.Fatalf("err: %s", err) 70 } 71 if actual != "foo" { 72 t.Fatalf("bad: %#v", actual) 73 } 74 75 if err := os.Unsetenv(keys[0]); err != nil { 76 t.Fatalf("err: %s", err) 77 } 78 79 // Test that the second key is returned if the first one is empty 80 f = MultiEnvDefaultFunc(keys, "42") 81 if err := os.Setenv(keys[1], "foo"); err != nil { 82 t.Fatalf("err: %s", err) 83 } 84 85 actual, err = f() 86 if err != nil { 87 t.Fatalf("err: %s", err) 88 } 89 if actual != "foo" { 90 t.Fatalf("bad: %#v", actual) 91 } 92 93 if err := os.Unsetenv(keys[1]); err != nil { 94 t.Fatalf("err: %s", err) 95 } 96 97 // Test that the default value is returned when no keys are set 98 actual, err = f() 99 if err != nil { 100 t.Fatalf("err: %s", err) 101 } 102 if actual != "42" { 103 t.Fatalf("bad: %#v", actual) 104 } 105 } 106 107 func TestValueType_Zero(t *testing.T) { 108 cases := []struct { 109 Type ValueType 110 Value interface{} 111 }{ 112 {TypeBool, false}, 113 {TypeInt, 0}, 114 {TypeFloat, 0.0}, 115 {TypeString, ""}, 116 {TypeList, []interface{}{}}, 117 {TypeMap, map[string]interface{}{}}, 118 {TypeSet, new(Set)}, 119 } 120 121 for i, tc := range cases { 122 actual := tc.Type.Zero() 123 if !reflect.DeepEqual(actual, tc.Value) { 124 t.Fatalf("%d: %#v != %#v", i, actual, tc.Value) 125 } 126 } 127 } 128 129 func interfaceToVariableSwallowError(input interface{}) ast.Variable { 130 variable, _ := hil.InterfaceToVariable(input) 131 return variable 132 } 133 134 func TestSchemaMap_Diff(t *testing.T) { 135 cases := []struct { 136 Name string 137 Schema map[string]*Schema 138 State *terraform.InstanceState 139 Config map[string]interface{} 140 ConfigVariables map[string]ast.Variable 141 Diff *terraform.InstanceDiff 142 Err bool 143 }{ 144 { 145 Schema: map[string]*Schema{ 146 "availability_zone": &Schema{ 147 Type: TypeString, 148 Optional: true, 149 Computed: true, 150 ForceNew: true, 151 }, 152 }, 153 154 State: nil, 155 156 Config: map[string]interface{}{ 157 "availability_zone": "foo", 158 }, 159 160 Diff: &terraform.InstanceDiff{ 161 Attributes: map[string]*terraform.ResourceAttrDiff{ 162 "availability_zone": &terraform.ResourceAttrDiff{ 163 Old: "", 164 New: "foo", 165 RequiresNew: true, 166 }, 167 }, 168 }, 169 170 Err: false, 171 }, 172 173 { 174 Schema: map[string]*Schema{ 175 "availability_zone": &Schema{ 176 Type: TypeString, 177 Optional: true, 178 Computed: true, 179 ForceNew: true, 180 }, 181 }, 182 183 State: nil, 184 185 Config: map[string]interface{}{}, 186 187 Diff: &terraform.InstanceDiff{ 188 Attributes: map[string]*terraform.ResourceAttrDiff{ 189 "availability_zone": &terraform.ResourceAttrDiff{ 190 Old: "", 191 NewComputed: true, 192 RequiresNew: true, 193 }, 194 }, 195 }, 196 197 Err: false, 198 }, 199 200 { 201 Schema: map[string]*Schema{ 202 "availability_zone": &Schema{ 203 Type: TypeString, 204 Optional: true, 205 Computed: true, 206 ForceNew: true, 207 }, 208 }, 209 210 State: &terraform.InstanceState{ 211 ID: "foo", 212 }, 213 214 Config: map[string]interface{}{}, 215 216 Diff: nil, 217 218 Err: false, 219 }, 220 221 { 222 Name: "Computed, but set in config", 223 Schema: map[string]*Schema{ 224 "availability_zone": &Schema{ 225 Type: TypeString, 226 Optional: true, 227 Computed: true, 228 }, 229 }, 230 231 State: &terraform.InstanceState{ 232 Attributes: map[string]string{ 233 "availability_zone": "foo", 234 }, 235 }, 236 237 Config: map[string]interface{}{ 238 "availability_zone": "bar", 239 }, 240 241 Diff: &terraform.InstanceDiff{ 242 Attributes: map[string]*terraform.ResourceAttrDiff{ 243 "availability_zone": &terraform.ResourceAttrDiff{ 244 Old: "foo", 245 New: "bar", 246 }, 247 }, 248 }, 249 250 Err: false, 251 }, 252 253 { 254 Name: "Default", 255 Schema: map[string]*Schema{ 256 "availability_zone": &Schema{ 257 Type: TypeString, 258 Optional: true, 259 Default: "foo", 260 }, 261 }, 262 263 State: nil, 264 265 Config: nil, 266 267 Diff: &terraform.InstanceDiff{ 268 Attributes: map[string]*terraform.ResourceAttrDiff{ 269 "availability_zone": &terraform.ResourceAttrDiff{ 270 Old: "", 271 New: "foo", 272 }, 273 }, 274 }, 275 276 Err: false, 277 }, 278 279 { 280 Name: "DefaultFunc, value", 281 Schema: map[string]*Schema{ 282 "availability_zone": &Schema{ 283 Type: TypeString, 284 Optional: true, 285 DefaultFunc: func() (interface{}, error) { 286 return "foo", nil 287 }, 288 }, 289 }, 290 291 State: nil, 292 293 Config: nil, 294 295 Diff: &terraform.InstanceDiff{ 296 Attributes: map[string]*terraform.ResourceAttrDiff{ 297 "availability_zone": &terraform.ResourceAttrDiff{ 298 Old: "", 299 New: "foo", 300 }, 301 }, 302 }, 303 304 Err: false, 305 }, 306 307 { 308 Name: "DefaultFunc, configuration set", 309 Schema: map[string]*Schema{ 310 "availability_zone": &Schema{ 311 Type: TypeString, 312 Optional: true, 313 DefaultFunc: func() (interface{}, error) { 314 return "foo", nil 315 }, 316 }, 317 }, 318 319 State: nil, 320 321 Config: map[string]interface{}{ 322 "availability_zone": "bar", 323 }, 324 325 Diff: &terraform.InstanceDiff{ 326 Attributes: map[string]*terraform.ResourceAttrDiff{ 327 "availability_zone": &terraform.ResourceAttrDiff{ 328 Old: "", 329 New: "bar", 330 }, 331 }, 332 }, 333 334 Err: false, 335 }, 336 337 { 338 Name: "String with StateFunc", 339 Schema: map[string]*Schema{ 340 "availability_zone": &Schema{ 341 Type: TypeString, 342 Optional: true, 343 Computed: true, 344 StateFunc: func(a interface{}) string { 345 return a.(string) + "!" 346 }, 347 }, 348 }, 349 350 State: nil, 351 352 Config: map[string]interface{}{ 353 "availability_zone": "foo", 354 }, 355 356 Diff: &terraform.InstanceDiff{ 357 Attributes: map[string]*terraform.ResourceAttrDiff{ 358 "availability_zone": &terraform.ResourceAttrDiff{ 359 Old: "", 360 New: "foo!", 361 NewExtra: "foo", 362 }, 363 }, 364 }, 365 366 Err: false, 367 }, 368 369 { 370 Name: "StateFunc not called with nil value", 371 Schema: map[string]*Schema{ 372 "availability_zone": &Schema{ 373 Type: TypeString, 374 Optional: true, 375 Computed: true, 376 StateFunc: func(a interface{}) string { 377 t.Fatalf("should not get here!") 378 return "" 379 }, 380 }, 381 }, 382 383 State: nil, 384 385 Config: map[string]interface{}{}, 386 387 Diff: &terraform.InstanceDiff{ 388 Attributes: map[string]*terraform.ResourceAttrDiff{ 389 "availability_zone": &terraform.ResourceAttrDiff{ 390 Old: "", 391 New: "", 392 NewComputed: true, 393 }, 394 }, 395 }, 396 397 Err: false, 398 }, 399 400 { 401 Name: "Variable (just checking)", 402 Schema: map[string]*Schema{ 403 "availability_zone": &Schema{ 404 Type: TypeString, 405 Optional: true, 406 }, 407 }, 408 409 State: nil, 410 411 Config: map[string]interface{}{ 412 "availability_zone": "${var.foo}", 413 }, 414 415 ConfigVariables: map[string]ast.Variable{ 416 "var.foo": interfaceToVariableSwallowError("bar"), 417 }, 418 419 Diff: &terraform.InstanceDiff{ 420 Attributes: map[string]*terraform.ResourceAttrDiff{ 421 "availability_zone": &terraform.ResourceAttrDiff{ 422 Old: "", 423 New: "bar", 424 }, 425 }, 426 }, 427 428 Err: false, 429 }, 430 431 { 432 Name: "Variable computed", 433 Schema: map[string]*Schema{ 434 "availability_zone": &Schema{ 435 Type: TypeString, 436 Optional: true, 437 }, 438 }, 439 440 State: nil, 441 442 Config: map[string]interface{}{ 443 "availability_zone": "${var.foo}", 444 }, 445 446 ConfigVariables: map[string]ast.Variable{ 447 "var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue), 448 }, 449 450 Diff: &terraform.InstanceDiff{ 451 Attributes: map[string]*terraform.ResourceAttrDiff{ 452 "availability_zone": &terraform.ResourceAttrDiff{ 453 Old: "", 454 New: "${var.foo}", 455 NewComputed: true, 456 }, 457 }, 458 }, 459 460 Err: false, 461 }, 462 463 { 464 Name: "Int decode", 465 Schema: map[string]*Schema{ 466 "port": &Schema{ 467 Type: TypeInt, 468 Optional: true, 469 Computed: true, 470 ForceNew: true, 471 }, 472 }, 473 474 State: nil, 475 476 Config: map[string]interface{}{ 477 "port": 27, 478 }, 479 480 Diff: &terraform.InstanceDiff{ 481 Attributes: map[string]*terraform.ResourceAttrDiff{ 482 "port": &terraform.ResourceAttrDiff{ 483 Old: "", 484 New: "27", 485 RequiresNew: true, 486 }, 487 }, 488 }, 489 490 Err: false, 491 }, 492 493 { 494 Name: "bool decode", 495 Schema: map[string]*Schema{ 496 "port": &Schema{ 497 Type: TypeBool, 498 Optional: true, 499 Computed: true, 500 ForceNew: true, 501 }, 502 }, 503 504 State: nil, 505 506 Config: map[string]interface{}{ 507 "port": false, 508 }, 509 510 Diff: &terraform.InstanceDiff{ 511 Attributes: map[string]*terraform.ResourceAttrDiff{ 512 "port": &terraform.ResourceAttrDiff{ 513 Old: "", 514 New: "false", 515 RequiresNew: true, 516 }, 517 }, 518 }, 519 520 Err: false, 521 }, 522 523 { 524 Name: "Bool", 525 Schema: map[string]*Schema{ 526 "delete": &Schema{ 527 Type: TypeBool, 528 Optional: true, 529 Default: false, 530 }, 531 }, 532 533 State: &terraform.InstanceState{ 534 Attributes: map[string]string{ 535 "delete": "false", 536 }, 537 }, 538 539 Config: nil, 540 541 Diff: nil, 542 543 Err: false, 544 }, 545 546 { 547 Name: "List decode", 548 Schema: map[string]*Schema{ 549 "ports": &Schema{ 550 Type: TypeList, 551 Required: true, 552 Elem: &Schema{Type: TypeInt}, 553 }, 554 }, 555 556 State: nil, 557 558 Config: map[string]interface{}{ 559 "ports": []interface{}{1, 2, 5}, 560 }, 561 562 Diff: &terraform.InstanceDiff{ 563 Attributes: map[string]*terraform.ResourceAttrDiff{ 564 "ports.#": &terraform.ResourceAttrDiff{ 565 Old: "0", 566 New: "3", 567 }, 568 "ports.0": &terraform.ResourceAttrDiff{ 569 Old: "", 570 New: "1", 571 }, 572 "ports.1": &terraform.ResourceAttrDiff{ 573 Old: "", 574 New: "2", 575 }, 576 "ports.2": &terraform.ResourceAttrDiff{ 577 Old: "", 578 New: "5", 579 }, 580 }, 581 }, 582 583 Err: false, 584 }, 585 586 { 587 Name: "List decode with promotion", 588 Schema: map[string]*Schema{ 589 "ports": &Schema{ 590 Type: TypeList, 591 Required: true, 592 Elem: &Schema{Type: TypeInt}, 593 PromoteSingle: true, 594 }, 595 }, 596 597 State: nil, 598 599 Config: map[string]interface{}{ 600 "ports": "5", 601 }, 602 603 Diff: &terraform.InstanceDiff{ 604 Attributes: map[string]*terraform.ResourceAttrDiff{ 605 "ports.#": &terraform.ResourceAttrDiff{ 606 Old: "0", 607 New: "1", 608 }, 609 "ports.0": &terraform.ResourceAttrDiff{ 610 Old: "", 611 New: "5", 612 }, 613 }, 614 }, 615 616 Err: false, 617 }, 618 619 { 620 Name: "List decode with promotion with list", 621 Schema: map[string]*Schema{ 622 "ports": &Schema{ 623 Type: TypeList, 624 Required: true, 625 Elem: &Schema{Type: TypeInt}, 626 PromoteSingle: true, 627 }, 628 }, 629 630 State: nil, 631 632 Config: map[string]interface{}{ 633 "ports": []interface{}{"5"}, 634 }, 635 636 Diff: &terraform.InstanceDiff{ 637 Attributes: map[string]*terraform.ResourceAttrDiff{ 638 "ports.#": &terraform.ResourceAttrDiff{ 639 Old: "0", 640 New: "1", 641 }, 642 "ports.0": &terraform.ResourceAttrDiff{ 643 Old: "", 644 New: "5", 645 }, 646 }, 647 }, 648 649 Err: false, 650 }, 651 652 { 653 Schema: map[string]*Schema{ 654 "ports": &Schema{ 655 Type: TypeList, 656 Required: true, 657 Elem: &Schema{Type: TypeInt}, 658 }, 659 }, 660 661 State: nil, 662 663 Config: map[string]interface{}{ 664 "ports": []interface{}{1, "${var.foo}"}, 665 }, 666 667 ConfigVariables: map[string]ast.Variable{ 668 "var.foo": interfaceToVariableSwallowError([]interface{}{"2", "5"}), 669 }, 670 671 Diff: &terraform.InstanceDiff{ 672 Attributes: map[string]*terraform.ResourceAttrDiff{ 673 "ports.#": &terraform.ResourceAttrDiff{ 674 Old: "0", 675 New: "3", 676 }, 677 "ports.0": &terraform.ResourceAttrDiff{ 678 Old: "", 679 New: "1", 680 }, 681 "ports.1": &terraform.ResourceAttrDiff{ 682 Old: "", 683 New: "2", 684 }, 685 "ports.2": &terraform.ResourceAttrDiff{ 686 Old: "", 687 New: "5", 688 }, 689 }, 690 }, 691 692 Err: false, 693 }, 694 695 { 696 Schema: map[string]*Schema{ 697 "ports": &Schema{ 698 Type: TypeList, 699 Required: true, 700 Elem: &Schema{Type: TypeInt}, 701 }, 702 }, 703 704 State: nil, 705 706 Config: map[string]interface{}{ 707 "ports": []interface{}{1, "${var.foo}"}, 708 }, 709 710 ConfigVariables: map[string]ast.Variable{ 711 "var.foo": interfaceToVariableSwallowError([]interface{}{ 712 config.UnknownVariableValue, "5"}), 713 }, 714 715 Diff: &terraform.InstanceDiff{ 716 Attributes: map[string]*terraform.ResourceAttrDiff{ 717 "ports.#": &terraform.ResourceAttrDiff{ 718 Old: "0", 719 New: "", 720 NewComputed: true, 721 }, 722 }, 723 }, 724 725 Err: false, 726 }, 727 728 { 729 Schema: map[string]*Schema{ 730 "ports": &Schema{ 731 Type: TypeList, 732 Required: true, 733 Elem: &Schema{Type: TypeInt}, 734 }, 735 }, 736 737 State: &terraform.InstanceState{ 738 Attributes: map[string]string{ 739 "ports.#": "3", 740 "ports.0": "1", 741 "ports.1": "2", 742 "ports.2": "5", 743 }, 744 }, 745 746 Config: map[string]interface{}{ 747 "ports": []interface{}{1, 2, 5}, 748 }, 749 750 Diff: nil, 751 752 Err: false, 753 }, 754 755 { 756 Name: "", 757 Schema: map[string]*Schema{ 758 "ports": &Schema{ 759 Type: TypeList, 760 Required: true, 761 Elem: &Schema{Type: TypeInt}, 762 }, 763 }, 764 765 State: &terraform.InstanceState{ 766 Attributes: map[string]string{ 767 "ports.#": "2", 768 "ports.0": "1", 769 "ports.1": "2", 770 }, 771 }, 772 773 Config: map[string]interface{}{ 774 "ports": []interface{}{1, 2, 5}, 775 }, 776 777 Diff: &terraform.InstanceDiff{ 778 Attributes: map[string]*terraform.ResourceAttrDiff{ 779 "ports.#": &terraform.ResourceAttrDiff{ 780 Old: "2", 781 New: "3", 782 }, 783 "ports.2": &terraform.ResourceAttrDiff{ 784 Old: "", 785 New: "5", 786 }, 787 }, 788 }, 789 790 Err: false, 791 }, 792 793 { 794 Name: "", 795 Schema: map[string]*Schema{ 796 "ports": &Schema{ 797 Type: TypeList, 798 Required: true, 799 Elem: &Schema{Type: TypeInt}, 800 ForceNew: true, 801 }, 802 }, 803 804 State: nil, 805 806 Config: map[string]interface{}{ 807 "ports": []interface{}{1, 2, 5}, 808 }, 809 810 Diff: &terraform.InstanceDiff{ 811 Attributes: map[string]*terraform.ResourceAttrDiff{ 812 "ports.#": &terraform.ResourceAttrDiff{ 813 Old: "0", 814 New: "3", 815 RequiresNew: true, 816 }, 817 "ports.0": &terraform.ResourceAttrDiff{ 818 Old: "", 819 New: "1", 820 RequiresNew: true, 821 }, 822 "ports.1": &terraform.ResourceAttrDiff{ 823 Old: "", 824 New: "2", 825 RequiresNew: true, 826 }, 827 "ports.2": &terraform.ResourceAttrDiff{ 828 Old: "", 829 New: "5", 830 RequiresNew: true, 831 }, 832 }, 833 }, 834 835 Err: false, 836 }, 837 838 { 839 Name: "", 840 Schema: map[string]*Schema{ 841 "ports": &Schema{ 842 Type: TypeList, 843 Optional: true, 844 Computed: true, 845 Elem: &Schema{Type: TypeInt}, 846 }, 847 }, 848 849 State: nil, 850 851 Config: map[string]interface{}{}, 852 853 Diff: &terraform.InstanceDiff{ 854 Attributes: map[string]*terraform.ResourceAttrDiff{ 855 "ports.#": &terraform.ResourceAttrDiff{ 856 Old: "", 857 NewComputed: true, 858 }, 859 }, 860 }, 861 862 Err: false, 863 }, 864 865 { 866 Name: "List with computed set", 867 Schema: map[string]*Schema{ 868 "config": &Schema{ 869 Type: TypeList, 870 Optional: true, 871 ForceNew: true, 872 MinItems: 1, 873 Elem: &Resource{ 874 Schema: map[string]*Schema{ 875 "name": { 876 Type: TypeString, 877 Required: true, 878 }, 879 880 "rules": { 881 Type: TypeSet, 882 Computed: true, 883 Elem: &Schema{Type: TypeString}, 884 Set: HashString, 885 }, 886 }, 887 }, 888 }, 889 }, 890 891 State: nil, 892 893 Config: map[string]interface{}{ 894 "config": []interface{}{ 895 map[string]interface{}{ 896 "name": "hello", 897 }, 898 }, 899 }, 900 901 Diff: &terraform.InstanceDiff{ 902 Attributes: map[string]*terraform.ResourceAttrDiff{ 903 "config.#": &terraform.ResourceAttrDiff{ 904 Old: "0", 905 New: "1", 906 RequiresNew: true, 907 }, 908 909 "config.0.name": &terraform.ResourceAttrDiff{ 910 Old: "", 911 New: "hello", 912 }, 913 914 "config.0.rules.#": &terraform.ResourceAttrDiff{ 915 Old: "", 916 NewComputed: true, 917 }, 918 }, 919 }, 920 921 Err: false, 922 }, 923 924 { 925 Name: "Set", 926 Schema: map[string]*Schema{ 927 "ports": &Schema{ 928 Type: TypeSet, 929 Required: true, 930 Elem: &Schema{Type: TypeInt}, 931 Set: func(a interface{}) int { 932 return a.(int) 933 }, 934 }, 935 }, 936 937 State: nil, 938 939 Config: map[string]interface{}{ 940 "ports": []interface{}{5, 2, 1}, 941 }, 942 943 Diff: &terraform.InstanceDiff{ 944 Attributes: map[string]*terraform.ResourceAttrDiff{ 945 "ports.#": &terraform.ResourceAttrDiff{ 946 Old: "0", 947 New: "3", 948 }, 949 "ports.1": &terraform.ResourceAttrDiff{ 950 Old: "", 951 New: "1", 952 }, 953 "ports.2": &terraform.ResourceAttrDiff{ 954 Old: "", 955 New: "2", 956 }, 957 "ports.5": &terraform.ResourceAttrDiff{ 958 Old: "", 959 New: "5", 960 }, 961 }, 962 }, 963 964 Err: false, 965 }, 966 967 { 968 Name: "Set", 969 Schema: map[string]*Schema{ 970 "ports": &Schema{ 971 Type: TypeSet, 972 Computed: true, 973 Required: true, 974 Elem: &Schema{Type: TypeInt}, 975 Set: func(a interface{}) int { 976 return a.(int) 977 }, 978 }, 979 }, 980 981 State: &terraform.InstanceState{ 982 Attributes: map[string]string{ 983 "ports.#": "0", 984 }, 985 }, 986 987 Config: nil, 988 989 Diff: nil, 990 991 Err: false, 992 }, 993 994 { 995 Name: "Set", 996 Schema: map[string]*Schema{ 997 "ports": &Schema{ 998 Type: TypeSet, 999 Optional: true, 1000 Computed: true, 1001 Elem: &Schema{Type: TypeInt}, 1002 Set: func(a interface{}) int { 1003 return a.(int) 1004 }, 1005 }, 1006 }, 1007 1008 State: nil, 1009 1010 Config: nil, 1011 1012 Diff: &terraform.InstanceDiff{ 1013 Attributes: map[string]*terraform.ResourceAttrDiff{ 1014 "ports.#": &terraform.ResourceAttrDiff{ 1015 Old: "", 1016 NewComputed: true, 1017 }, 1018 }, 1019 }, 1020 1021 Err: false, 1022 }, 1023 1024 { 1025 Name: "Set", 1026 Schema: map[string]*Schema{ 1027 "ports": &Schema{ 1028 Type: TypeSet, 1029 Required: true, 1030 Elem: &Schema{Type: TypeInt}, 1031 Set: func(a interface{}) int { 1032 return a.(int) 1033 }, 1034 }, 1035 }, 1036 1037 State: nil, 1038 1039 Config: map[string]interface{}{ 1040 "ports": []interface{}{"${var.foo}", 1}, 1041 }, 1042 1043 ConfigVariables: map[string]ast.Variable{ 1044 "var.foo": interfaceToVariableSwallowError([]interface{}{"2", "5"}), 1045 }, 1046 1047 Diff: &terraform.InstanceDiff{ 1048 Attributes: map[string]*terraform.ResourceAttrDiff{ 1049 "ports.#": &terraform.ResourceAttrDiff{ 1050 Old: "0", 1051 New: "3", 1052 }, 1053 "ports.1": &terraform.ResourceAttrDiff{ 1054 Old: "", 1055 New: "1", 1056 }, 1057 "ports.2": &terraform.ResourceAttrDiff{ 1058 Old: "", 1059 New: "2", 1060 }, 1061 "ports.5": &terraform.ResourceAttrDiff{ 1062 Old: "", 1063 New: "5", 1064 }, 1065 }, 1066 }, 1067 1068 Err: false, 1069 }, 1070 1071 { 1072 Name: "Set", 1073 Schema: map[string]*Schema{ 1074 "ports": &Schema{ 1075 Type: TypeSet, 1076 Required: true, 1077 Elem: &Schema{Type: TypeInt}, 1078 Set: func(a interface{}) int { 1079 return a.(int) 1080 }, 1081 }, 1082 }, 1083 1084 State: nil, 1085 1086 Config: map[string]interface{}{ 1087 "ports": []interface{}{1, "${var.foo}"}, 1088 }, 1089 1090 ConfigVariables: map[string]ast.Variable{ 1091 "var.foo": interfaceToVariableSwallowError([]interface{}{ 1092 config.UnknownVariableValue, "5"}), 1093 }, 1094 1095 Diff: &terraform.InstanceDiff{ 1096 Attributes: map[string]*terraform.ResourceAttrDiff{ 1097 "ports.#": &terraform.ResourceAttrDiff{ 1098 Old: "", 1099 New: "", 1100 NewComputed: true, 1101 }, 1102 }, 1103 }, 1104 1105 Err: false, 1106 }, 1107 1108 { 1109 Name: "Set", 1110 Schema: map[string]*Schema{ 1111 "ports": &Schema{ 1112 Type: TypeSet, 1113 Required: true, 1114 Elem: &Schema{Type: TypeInt}, 1115 Set: func(a interface{}) int { 1116 return a.(int) 1117 }, 1118 }, 1119 }, 1120 1121 State: &terraform.InstanceState{ 1122 Attributes: map[string]string{ 1123 "ports.#": "2", 1124 "ports.1": "1", 1125 "ports.2": "2", 1126 }, 1127 }, 1128 1129 Config: map[string]interface{}{ 1130 "ports": []interface{}{5, 2, 1}, 1131 }, 1132 1133 Diff: &terraform.InstanceDiff{ 1134 Attributes: map[string]*terraform.ResourceAttrDiff{ 1135 "ports.#": &terraform.ResourceAttrDiff{ 1136 Old: "2", 1137 New: "3", 1138 }, 1139 "ports.1": &terraform.ResourceAttrDiff{ 1140 Old: "1", 1141 New: "1", 1142 }, 1143 "ports.2": &terraform.ResourceAttrDiff{ 1144 Old: "2", 1145 New: "2", 1146 }, 1147 "ports.5": &terraform.ResourceAttrDiff{ 1148 Old: "", 1149 New: "5", 1150 }, 1151 }, 1152 }, 1153 1154 Err: false, 1155 }, 1156 1157 { 1158 Name: "Set", 1159 Schema: map[string]*Schema{ 1160 "ports": &Schema{ 1161 Type: TypeSet, 1162 Required: true, 1163 Elem: &Schema{Type: TypeInt}, 1164 Set: func(a interface{}) int { 1165 return a.(int) 1166 }, 1167 }, 1168 }, 1169 1170 State: &terraform.InstanceState{ 1171 Attributes: map[string]string{ 1172 "ports.#": "2", 1173 "ports.1": "1", 1174 "ports.2": "2", 1175 }, 1176 }, 1177 1178 Config: map[string]interface{}{}, 1179 1180 Diff: &terraform.InstanceDiff{ 1181 Attributes: map[string]*terraform.ResourceAttrDiff{ 1182 "ports.#": &terraform.ResourceAttrDiff{ 1183 Old: "2", 1184 New: "0", 1185 }, 1186 "ports.1": &terraform.ResourceAttrDiff{ 1187 Old: "1", 1188 New: "0", 1189 NewRemoved: true, 1190 }, 1191 "ports.2": &terraform.ResourceAttrDiff{ 1192 Old: "2", 1193 New: "0", 1194 NewRemoved: true, 1195 }, 1196 }, 1197 }, 1198 1199 Err: false, 1200 }, 1201 1202 { 1203 Name: "Set", 1204 Schema: map[string]*Schema{ 1205 "ports": &Schema{ 1206 Type: TypeSet, 1207 Optional: true, 1208 Computed: true, 1209 Elem: &Schema{Type: TypeInt}, 1210 Set: func(a interface{}) int { 1211 return a.(int) 1212 }, 1213 }, 1214 }, 1215 1216 State: &terraform.InstanceState{ 1217 Attributes: map[string]string{ 1218 "availability_zone": "bar", 1219 "ports.#": "1", 1220 "ports.80": "80", 1221 }, 1222 }, 1223 1224 Config: map[string]interface{}{}, 1225 1226 Diff: nil, 1227 1228 Err: false, 1229 }, 1230 1231 { 1232 Name: "Set", 1233 Schema: map[string]*Schema{ 1234 "ingress": &Schema{ 1235 Type: TypeSet, 1236 Required: true, 1237 Elem: &Resource{ 1238 Schema: map[string]*Schema{ 1239 "ports": &Schema{ 1240 Type: TypeList, 1241 Optional: true, 1242 Elem: &Schema{Type: TypeInt}, 1243 }, 1244 }, 1245 }, 1246 Set: func(v interface{}) int { 1247 m := v.(map[string]interface{}) 1248 ps := m["ports"].([]interface{}) 1249 result := 0 1250 for _, p := range ps { 1251 result += p.(int) 1252 } 1253 return result 1254 }, 1255 }, 1256 }, 1257 1258 State: &terraform.InstanceState{ 1259 Attributes: map[string]string{ 1260 "ingress.#": "2", 1261 "ingress.80.ports.#": "1", 1262 "ingress.80.ports.0": "80", 1263 "ingress.443.ports.#": "1", 1264 "ingress.443.ports.0": "443", 1265 }, 1266 }, 1267 1268 Config: map[string]interface{}{ 1269 "ingress": []map[string]interface{}{ 1270 map[string]interface{}{ 1271 "ports": []interface{}{443}, 1272 }, 1273 map[string]interface{}{ 1274 "ports": []interface{}{80}, 1275 }, 1276 }, 1277 }, 1278 1279 Diff: nil, 1280 1281 Err: false, 1282 }, 1283 1284 { 1285 Name: "List of structure decode", 1286 Schema: map[string]*Schema{ 1287 "ingress": &Schema{ 1288 Type: TypeList, 1289 Required: true, 1290 Elem: &Resource{ 1291 Schema: map[string]*Schema{ 1292 "from": &Schema{ 1293 Type: TypeInt, 1294 Required: true, 1295 }, 1296 }, 1297 }, 1298 }, 1299 }, 1300 1301 State: nil, 1302 1303 Config: map[string]interface{}{ 1304 "ingress": []interface{}{ 1305 map[string]interface{}{ 1306 "from": 8080, 1307 }, 1308 }, 1309 }, 1310 1311 Diff: &terraform.InstanceDiff{ 1312 Attributes: map[string]*terraform.ResourceAttrDiff{ 1313 "ingress.#": &terraform.ResourceAttrDiff{ 1314 Old: "0", 1315 New: "1", 1316 }, 1317 "ingress.0.from": &terraform.ResourceAttrDiff{ 1318 Old: "", 1319 New: "8080", 1320 }, 1321 }, 1322 }, 1323 1324 Err: false, 1325 }, 1326 1327 { 1328 Name: "ComputedWhen", 1329 Schema: map[string]*Schema{ 1330 "availability_zone": &Schema{ 1331 Type: TypeString, 1332 Computed: true, 1333 ComputedWhen: []string{"port"}, 1334 }, 1335 1336 "port": &Schema{ 1337 Type: TypeInt, 1338 Optional: true, 1339 }, 1340 }, 1341 1342 State: &terraform.InstanceState{ 1343 Attributes: map[string]string{ 1344 "availability_zone": "foo", 1345 "port": "80", 1346 }, 1347 }, 1348 1349 Config: map[string]interface{}{ 1350 "port": 80, 1351 }, 1352 1353 Diff: nil, 1354 1355 Err: false, 1356 }, 1357 1358 { 1359 Name: "", 1360 Schema: map[string]*Schema{ 1361 "availability_zone": &Schema{ 1362 Type: TypeString, 1363 Computed: true, 1364 ComputedWhen: []string{"port"}, 1365 }, 1366 1367 "port": &Schema{ 1368 Type: TypeInt, 1369 Optional: true, 1370 }, 1371 }, 1372 1373 State: &terraform.InstanceState{ 1374 Attributes: map[string]string{ 1375 "port": "80", 1376 }, 1377 }, 1378 1379 Config: map[string]interface{}{ 1380 "port": 80, 1381 }, 1382 1383 Diff: &terraform.InstanceDiff{ 1384 Attributes: map[string]*terraform.ResourceAttrDiff{ 1385 "availability_zone": &terraform.ResourceAttrDiff{ 1386 NewComputed: true, 1387 }, 1388 }, 1389 }, 1390 1391 Err: false, 1392 }, 1393 1394 /* TODO 1395 { 1396 Schema: map[string]*Schema{ 1397 "availability_zone": &Schema{ 1398 Type: TypeString, 1399 Computed: true, 1400 ComputedWhen: []string{"port"}, 1401 }, 1402 1403 "port": &Schema{ 1404 Type: TypeInt, 1405 Optional: true, 1406 }, 1407 }, 1408 1409 State: &terraform.InstanceState{ 1410 Attributes: map[string]string{ 1411 "availability_zone": "foo", 1412 "port": "80", 1413 }, 1414 }, 1415 1416 Config: map[string]interface{}{ 1417 "port": 8080, 1418 }, 1419 1420 Diff: &terraform.ResourceDiff{ 1421 Attributes: map[string]*terraform.ResourceAttrDiff{ 1422 "availability_zone": &terraform.ResourceAttrDiff{ 1423 Old: "foo", 1424 NewComputed: true, 1425 }, 1426 "port": &terraform.ResourceAttrDiff{ 1427 Old: "80", 1428 New: "8080", 1429 }, 1430 }, 1431 }, 1432 1433 Err: false, 1434 }, 1435 */ 1436 1437 { 1438 Name: "Maps", 1439 Schema: map[string]*Schema{ 1440 "config_vars": &Schema{ 1441 Type: TypeMap, 1442 }, 1443 }, 1444 1445 State: nil, 1446 1447 Config: map[string]interface{}{ 1448 "config_vars": []interface{}{ 1449 map[string]interface{}{ 1450 "bar": "baz", 1451 }, 1452 }, 1453 }, 1454 1455 Diff: &terraform.InstanceDiff{ 1456 Attributes: map[string]*terraform.ResourceAttrDiff{ 1457 "config_vars.%": &terraform.ResourceAttrDiff{ 1458 Old: "0", 1459 New: "1", 1460 }, 1461 1462 "config_vars.bar": &terraform.ResourceAttrDiff{ 1463 Old: "", 1464 New: "baz", 1465 }, 1466 }, 1467 }, 1468 1469 Err: false, 1470 }, 1471 1472 { 1473 Name: "Maps", 1474 Schema: map[string]*Schema{ 1475 "config_vars": &Schema{ 1476 Type: TypeMap, 1477 }, 1478 }, 1479 1480 State: &terraform.InstanceState{ 1481 Attributes: map[string]string{ 1482 "config_vars.foo": "bar", 1483 }, 1484 }, 1485 1486 Config: map[string]interface{}{ 1487 "config_vars": []interface{}{ 1488 map[string]interface{}{ 1489 "bar": "baz", 1490 }, 1491 }, 1492 }, 1493 1494 Diff: &terraform.InstanceDiff{ 1495 Attributes: map[string]*terraform.ResourceAttrDiff{ 1496 "config_vars.foo": &terraform.ResourceAttrDiff{ 1497 Old: "bar", 1498 NewRemoved: true, 1499 }, 1500 "config_vars.bar": &terraform.ResourceAttrDiff{ 1501 Old: "", 1502 New: "baz", 1503 }, 1504 }, 1505 }, 1506 1507 Err: false, 1508 }, 1509 1510 { 1511 Name: "Maps", 1512 Schema: map[string]*Schema{ 1513 "vars": &Schema{ 1514 Type: TypeMap, 1515 Optional: true, 1516 Computed: true, 1517 }, 1518 }, 1519 1520 State: &terraform.InstanceState{ 1521 Attributes: map[string]string{ 1522 "vars.foo": "bar", 1523 }, 1524 }, 1525 1526 Config: map[string]interface{}{ 1527 "vars": []interface{}{ 1528 map[string]interface{}{ 1529 "bar": "baz", 1530 }, 1531 }, 1532 }, 1533 1534 Diff: &terraform.InstanceDiff{ 1535 Attributes: map[string]*terraform.ResourceAttrDiff{ 1536 "vars.foo": &terraform.ResourceAttrDiff{ 1537 Old: "bar", 1538 New: "", 1539 NewRemoved: true, 1540 }, 1541 "vars.bar": &terraform.ResourceAttrDiff{ 1542 Old: "", 1543 New: "baz", 1544 }, 1545 }, 1546 }, 1547 1548 Err: false, 1549 }, 1550 1551 { 1552 Name: "Maps", 1553 Schema: map[string]*Schema{ 1554 "vars": &Schema{ 1555 Type: TypeMap, 1556 Computed: true, 1557 }, 1558 }, 1559 1560 State: &terraform.InstanceState{ 1561 Attributes: map[string]string{ 1562 "vars.foo": "bar", 1563 }, 1564 }, 1565 1566 Config: nil, 1567 1568 Diff: nil, 1569 1570 Err: false, 1571 }, 1572 1573 { 1574 Name: "Maps", 1575 Schema: map[string]*Schema{ 1576 "config_vars": &Schema{ 1577 Type: TypeList, 1578 Elem: &Schema{Type: TypeMap}, 1579 }, 1580 }, 1581 1582 State: &terraform.InstanceState{ 1583 Attributes: map[string]string{ 1584 "config_vars.#": "1", 1585 "config_vars.0.foo": "bar", 1586 }, 1587 }, 1588 1589 Config: map[string]interface{}{ 1590 "config_vars": []interface{}{ 1591 map[string]interface{}{ 1592 "bar": "baz", 1593 }, 1594 }, 1595 }, 1596 1597 Diff: &terraform.InstanceDiff{ 1598 Attributes: map[string]*terraform.ResourceAttrDiff{ 1599 "config_vars.0.foo": &terraform.ResourceAttrDiff{ 1600 Old: "bar", 1601 NewRemoved: true, 1602 }, 1603 "config_vars.0.bar": &terraform.ResourceAttrDiff{ 1604 Old: "", 1605 New: "baz", 1606 }, 1607 }, 1608 }, 1609 1610 Err: false, 1611 }, 1612 1613 { 1614 Name: "Maps", 1615 Schema: map[string]*Schema{ 1616 "config_vars": &Schema{ 1617 Type: TypeList, 1618 Elem: &Schema{Type: TypeMap}, 1619 }, 1620 }, 1621 1622 State: &terraform.InstanceState{ 1623 Attributes: map[string]string{ 1624 "config_vars.#": "1", 1625 "config_vars.0.foo": "bar", 1626 "config_vars.0.bar": "baz", 1627 }, 1628 }, 1629 1630 Config: map[string]interface{}{}, 1631 1632 Diff: &terraform.InstanceDiff{ 1633 Attributes: map[string]*terraform.ResourceAttrDiff{ 1634 "config_vars.#": &terraform.ResourceAttrDiff{ 1635 Old: "1", 1636 New: "0", 1637 }, 1638 "config_vars.0.%": &terraform.ResourceAttrDiff{ 1639 Old: "2", 1640 New: "0", 1641 }, 1642 "config_vars.0.foo": &terraform.ResourceAttrDiff{ 1643 Old: "bar", 1644 NewRemoved: true, 1645 }, 1646 "config_vars.0.bar": &terraform.ResourceAttrDiff{ 1647 Old: "baz", 1648 NewRemoved: true, 1649 }, 1650 }, 1651 }, 1652 1653 Err: false, 1654 }, 1655 1656 { 1657 Name: "ForceNews", 1658 Schema: map[string]*Schema{ 1659 "availability_zone": &Schema{ 1660 Type: TypeString, 1661 Optional: true, 1662 ForceNew: true, 1663 }, 1664 1665 "address": &Schema{ 1666 Type: TypeString, 1667 Optional: true, 1668 Computed: true, 1669 }, 1670 }, 1671 1672 State: &terraform.InstanceState{ 1673 Attributes: map[string]string{ 1674 "availability_zone": "bar", 1675 "address": "foo", 1676 }, 1677 }, 1678 1679 Config: map[string]interface{}{ 1680 "availability_zone": "foo", 1681 }, 1682 1683 Diff: &terraform.InstanceDiff{ 1684 Attributes: map[string]*terraform.ResourceAttrDiff{ 1685 "availability_zone": &terraform.ResourceAttrDiff{ 1686 Old: "bar", 1687 New: "foo", 1688 RequiresNew: true, 1689 }, 1690 1691 "address": &terraform.ResourceAttrDiff{ 1692 Old: "foo", 1693 New: "", 1694 NewComputed: true, 1695 }, 1696 }, 1697 }, 1698 1699 Err: false, 1700 }, 1701 1702 { 1703 Name: "Set", 1704 Schema: map[string]*Schema{ 1705 "availability_zone": &Schema{ 1706 Type: TypeString, 1707 Optional: true, 1708 ForceNew: true, 1709 }, 1710 1711 "ports": &Schema{ 1712 Type: TypeSet, 1713 Optional: true, 1714 Computed: true, 1715 Elem: &Schema{Type: TypeInt}, 1716 Set: func(a interface{}) int { 1717 return a.(int) 1718 }, 1719 }, 1720 }, 1721 1722 State: &terraform.InstanceState{ 1723 Attributes: map[string]string{ 1724 "availability_zone": "bar", 1725 "ports.#": "1", 1726 "ports.80": "80", 1727 }, 1728 }, 1729 1730 Config: map[string]interface{}{ 1731 "availability_zone": "foo", 1732 }, 1733 1734 Diff: &terraform.InstanceDiff{ 1735 Attributes: map[string]*terraform.ResourceAttrDiff{ 1736 "availability_zone": &terraform.ResourceAttrDiff{ 1737 Old: "bar", 1738 New: "foo", 1739 RequiresNew: true, 1740 }, 1741 1742 "ports.#": &terraform.ResourceAttrDiff{ 1743 Old: "1", 1744 New: "", 1745 NewComputed: true, 1746 }, 1747 }, 1748 }, 1749 1750 Err: false, 1751 }, 1752 1753 { 1754 Name: "Set", 1755 Schema: map[string]*Schema{ 1756 "instances": &Schema{ 1757 Type: TypeSet, 1758 Elem: &Schema{Type: TypeString}, 1759 Optional: true, 1760 Computed: true, 1761 Set: func(v interface{}) int { 1762 return len(v.(string)) 1763 }, 1764 }, 1765 }, 1766 1767 State: &terraform.InstanceState{ 1768 Attributes: map[string]string{ 1769 "instances.#": "0", 1770 }, 1771 }, 1772 1773 Config: map[string]interface{}{ 1774 "instances": []interface{}{"${var.foo}"}, 1775 }, 1776 1777 ConfigVariables: map[string]ast.Variable{ 1778 "var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue), 1779 }, 1780 1781 Diff: &terraform.InstanceDiff{ 1782 Attributes: map[string]*terraform.ResourceAttrDiff{ 1783 "instances.#": &terraform.ResourceAttrDiff{ 1784 NewComputed: true, 1785 }, 1786 }, 1787 }, 1788 1789 Err: false, 1790 }, 1791 1792 { 1793 Name: "Set", 1794 Schema: map[string]*Schema{ 1795 "route": &Schema{ 1796 Type: TypeSet, 1797 Optional: true, 1798 Elem: &Resource{ 1799 Schema: map[string]*Schema{ 1800 "index": &Schema{ 1801 Type: TypeInt, 1802 Required: true, 1803 }, 1804 1805 "gateway": &Schema{ 1806 Type: TypeString, 1807 Optional: true, 1808 }, 1809 }, 1810 }, 1811 Set: func(v interface{}) int { 1812 m := v.(map[string]interface{}) 1813 return m["index"].(int) 1814 }, 1815 }, 1816 }, 1817 1818 State: nil, 1819 1820 Config: map[string]interface{}{ 1821 "route": []map[string]interface{}{ 1822 map[string]interface{}{ 1823 "index": "1", 1824 "gateway": "${var.foo}", 1825 }, 1826 }, 1827 }, 1828 1829 ConfigVariables: map[string]ast.Variable{ 1830 "var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue), 1831 }, 1832 1833 Diff: &terraform.InstanceDiff{ 1834 Attributes: map[string]*terraform.ResourceAttrDiff{ 1835 "route.#": &terraform.ResourceAttrDiff{ 1836 Old: "0", 1837 New: "1", 1838 }, 1839 "route.~1.index": &terraform.ResourceAttrDiff{ 1840 Old: "", 1841 New: "1", 1842 }, 1843 "route.~1.gateway": &terraform.ResourceAttrDiff{ 1844 Old: "", 1845 New: "${var.foo}", 1846 NewComputed: true, 1847 }, 1848 }, 1849 }, 1850 1851 Err: false, 1852 }, 1853 1854 { 1855 Name: "Set", 1856 Schema: map[string]*Schema{ 1857 "route": &Schema{ 1858 Type: TypeSet, 1859 Optional: true, 1860 Elem: &Resource{ 1861 Schema: map[string]*Schema{ 1862 "index": &Schema{ 1863 Type: TypeInt, 1864 Required: true, 1865 }, 1866 1867 "gateway": &Schema{ 1868 Type: TypeSet, 1869 Optional: true, 1870 Elem: &Schema{Type: TypeInt}, 1871 Set: func(a interface{}) int { 1872 return a.(int) 1873 }, 1874 }, 1875 }, 1876 }, 1877 Set: func(v interface{}) int { 1878 m := v.(map[string]interface{}) 1879 return m["index"].(int) 1880 }, 1881 }, 1882 }, 1883 1884 State: nil, 1885 1886 Config: map[string]interface{}{ 1887 "route": []map[string]interface{}{ 1888 map[string]interface{}{ 1889 "index": "1", 1890 "gateway": []interface{}{ 1891 "${var.foo}", 1892 }, 1893 }, 1894 }, 1895 }, 1896 1897 ConfigVariables: map[string]ast.Variable{ 1898 "var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue), 1899 }, 1900 1901 Diff: &terraform.InstanceDiff{ 1902 Attributes: map[string]*terraform.ResourceAttrDiff{ 1903 "route.#": &terraform.ResourceAttrDiff{ 1904 Old: "0", 1905 New: "1", 1906 }, 1907 "route.~1.index": &terraform.ResourceAttrDiff{ 1908 Old: "", 1909 New: "1", 1910 }, 1911 "route.~1.gateway.#": &terraform.ResourceAttrDiff{ 1912 NewComputed: true, 1913 }, 1914 }, 1915 }, 1916 1917 Err: false, 1918 }, 1919 1920 { 1921 Name: "Computed maps", 1922 Schema: map[string]*Schema{ 1923 "vars": &Schema{ 1924 Type: TypeMap, 1925 Computed: true, 1926 }, 1927 }, 1928 1929 State: nil, 1930 1931 Config: nil, 1932 1933 Diff: &terraform.InstanceDiff{ 1934 Attributes: map[string]*terraform.ResourceAttrDiff{ 1935 "vars.%": &terraform.ResourceAttrDiff{ 1936 Old: "", 1937 NewComputed: true, 1938 }, 1939 }, 1940 }, 1941 1942 Err: false, 1943 }, 1944 1945 { 1946 Name: "Computed maps", 1947 Schema: map[string]*Schema{ 1948 "vars": &Schema{ 1949 Type: TypeMap, 1950 Computed: true, 1951 }, 1952 }, 1953 1954 State: &terraform.InstanceState{ 1955 Attributes: map[string]string{ 1956 "vars.%": "0", 1957 }, 1958 }, 1959 1960 Config: map[string]interface{}{ 1961 "vars": map[string]interface{}{ 1962 "bar": "${var.foo}", 1963 }, 1964 }, 1965 1966 ConfigVariables: map[string]ast.Variable{ 1967 "var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue), 1968 }, 1969 1970 Diff: &terraform.InstanceDiff{ 1971 Attributes: map[string]*terraform.ResourceAttrDiff{ 1972 "vars.%": &terraform.ResourceAttrDiff{ 1973 Old: "", 1974 NewComputed: true, 1975 }, 1976 }, 1977 }, 1978 1979 Err: false, 1980 }, 1981 1982 { 1983 Name: " - Empty", 1984 Schema: map[string]*Schema{}, 1985 1986 State: &terraform.InstanceState{}, 1987 1988 Config: map[string]interface{}{}, 1989 1990 Diff: nil, 1991 1992 Err: false, 1993 }, 1994 1995 { 1996 Name: "Float", 1997 Schema: map[string]*Schema{ 1998 "some_threshold": &Schema{ 1999 Type: TypeFloat, 2000 }, 2001 }, 2002 2003 State: &terraform.InstanceState{ 2004 Attributes: map[string]string{ 2005 "some_threshold": "567.8", 2006 }, 2007 }, 2008 2009 Config: map[string]interface{}{ 2010 "some_threshold": 12.34, 2011 }, 2012 2013 Diff: &terraform.InstanceDiff{ 2014 Attributes: map[string]*terraform.ResourceAttrDiff{ 2015 "some_threshold": &terraform.ResourceAttrDiff{ 2016 Old: "567.8", 2017 New: "12.34", 2018 }, 2019 }, 2020 }, 2021 2022 Err: false, 2023 }, 2024 2025 { 2026 Name: "https://github.com/hashicorp/terraform/issues/824", 2027 Schema: map[string]*Schema{ 2028 "block_device": &Schema{ 2029 Type: TypeSet, 2030 Optional: true, 2031 Computed: true, 2032 Elem: &Resource{ 2033 Schema: map[string]*Schema{ 2034 "device_name": &Schema{ 2035 Type: TypeString, 2036 Required: true, 2037 }, 2038 "delete_on_termination": &Schema{ 2039 Type: TypeBool, 2040 Optional: true, 2041 Default: true, 2042 }, 2043 }, 2044 }, 2045 Set: func(v interface{}) int { 2046 var buf bytes.Buffer 2047 m := v.(map[string]interface{}) 2048 buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) 2049 buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool))) 2050 return hashcode.String(buf.String()) 2051 }, 2052 }, 2053 }, 2054 2055 State: &terraform.InstanceState{ 2056 Attributes: map[string]string{ 2057 "block_device.#": "2", 2058 "block_device.616397234.delete_on_termination": "true", 2059 "block_device.616397234.device_name": "/dev/sda1", 2060 "block_device.2801811477.delete_on_termination": "true", 2061 "block_device.2801811477.device_name": "/dev/sdx", 2062 }, 2063 }, 2064 2065 Config: map[string]interface{}{ 2066 "block_device": []map[string]interface{}{ 2067 map[string]interface{}{ 2068 "device_name": "/dev/sda1", 2069 }, 2070 map[string]interface{}{ 2071 "device_name": "/dev/sdx", 2072 }, 2073 }, 2074 }, 2075 Diff: nil, 2076 Err: false, 2077 }, 2078 2079 { 2080 Name: "Zero value in state shouldn't result in diff", 2081 Schema: map[string]*Schema{ 2082 "port": &Schema{ 2083 Type: TypeBool, 2084 Optional: true, 2085 ForceNew: true, 2086 }, 2087 }, 2088 2089 State: &terraform.InstanceState{ 2090 Attributes: map[string]string{ 2091 "port": "false", 2092 }, 2093 }, 2094 2095 Config: map[string]interface{}{}, 2096 2097 Diff: nil, 2098 2099 Err: false, 2100 }, 2101 2102 { 2103 Name: "Same as prev, but for sets", 2104 Schema: map[string]*Schema{ 2105 "route": &Schema{ 2106 Type: TypeSet, 2107 Optional: true, 2108 Elem: &Resource{ 2109 Schema: map[string]*Schema{ 2110 "index": &Schema{ 2111 Type: TypeInt, 2112 Required: true, 2113 }, 2114 2115 "gateway": &Schema{ 2116 Type: TypeSet, 2117 Optional: true, 2118 Elem: &Schema{Type: TypeInt}, 2119 Set: func(a interface{}) int { 2120 return a.(int) 2121 }, 2122 }, 2123 }, 2124 }, 2125 Set: func(v interface{}) int { 2126 m := v.(map[string]interface{}) 2127 return m["index"].(int) 2128 }, 2129 }, 2130 }, 2131 2132 State: &terraform.InstanceState{ 2133 Attributes: map[string]string{ 2134 "route.#": "0", 2135 }, 2136 }, 2137 2138 Config: map[string]interface{}{}, 2139 2140 Diff: nil, 2141 2142 Err: false, 2143 }, 2144 2145 { 2146 Name: "A set computed element shouldn't cause a diff", 2147 Schema: map[string]*Schema{ 2148 "active": &Schema{ 2149 Type: TypeBool, 2150 Computed: true, 2151 ForceNew: true, 2152 }, 2153 }, 2154 2155 State: &terraform.InstanceState{ 2156 Attributes: map[string]string{ 2157 "active": "true", 2158 }, 2159 }, 2160 2161 Config: map[string]interface{}{}, 2162 2163 Diff: nil, 2164 2165 Err: false, 2166 }, 2167 2168 { 2169 Name: "An empty set should show up in the diff", 2170 Schema: map[string]*Schema{ 2171 "instances": &Schema{ 2172 Type: TypeSet, 2173 Elem: &Schema{Type: TypeString}, 2174 Optional: true, 2175 ForceNew: true, 2176 Set: func(v interface{}) int { 2177 return len(v.(string)) 2178 }, 2179 }, 2180 }, 2181 2182 State: &terraform.InstanceState{ 2183 Attributes: map[string]string{ 2184 "instances.#": "1", 2185 "instances.3": "foo", 2186 }, 2187 }, 2188 2189 Config: map[string]interface{}{}, 2190 2191 Diff: &terraform.InstanceDiff{ 2192 Attributes: map[string]*terraform.ResourceAttrDiff{ 2193 "instances.#": &terraform.ResourceAttrDiff{ 2194 Old: "1", 2195 New: "0", 2196 RequiresNew: true, 2197 }, 2198 "instances.3": &terraform.ResourceAttrDiff{ 2199 Old: "foo", 2200 New: "", 2201 NewRemoved: true, 2202 RequiresNew: true, 2203 }, 2204 }, 2205 }, 2206 2207 Err: false, 2208 }, 2209 2210 { 2211 Name: "Map with empty value", 2212 Schema: map[string]*Schema{ 2213 "vars": &Schema{ 2214 Type: TypeMap, 2215 }, 2216 }, 2217 2218 State: nil, 2219 2220 Config: map[string]interface{}{ 2221 "vars": map[string]interface{}{ 2222 "foo": "", 2223 }, 2224 }, 2225 2226 Diff: &terraform.InstanceDiff{ 2227 Attributes: map[string]*terraform.ResourceAttrDiff{ 2228 "vars.%": &terraform.ResourceAttrDiff{ 2229 Old: "0", 2230 New: "1", 2231 }, 2232 "vars.foo": &terraform.ResourceAttrDiff{ 2233 Old: "", 2234 New: "", 2235 }, 2236 }, 2237 }, 2238 2239 Err: false, 2240 }, 2241 2242 { 2243 Name: "Unset bool, not in state", 2244 Schema: map[string]*Schema{ 2245 "force": &Schema{ 2246 Type: TypeBool, 2247 Optional: true, 2248 ForceNew: true, 2249 }, 2250 }, 2251 2252 State: nil, 2253 2254 Config: map[string]interface{}{}, 2255 2256 Diff: nil, 2257 2258 Err: false, 2259 }, 2260 2261 { 2262 Name: "Unset set, not in state", 2263 Schema: map[string]*Schema{ 2264 "metadata_keys": &Schema{ 2265 Type: TypeSet, 2266 Optional: true, 2267 ForceNew: true, 2268 Elem: &Schema{Type: TypeInt}, 2269 Set: func(interface{}) int { return 0 }, 2270 }, 2271 }, 2272 2273 State: nil, 2274 2275 Config: map[string]interface{}{}, 2276 2277 Diff: nil, 2278 2279 Err: false, 2280 }, 2281 2282 { 2283 Name: "Unset list in state, should not show up computed", 2284 Schema: map[string]*Schema{ 2285 "metadata_keys": &Schema{ 2286 Type: TypeList, 2287 Optional: true, 2288 Computed: true, 2289 ForceNew: true, 2290 Elem: &Schema{Type: TypeInt}, 2291 }, 2292 }, 2293 2294 State: &terraform.InstanceState{ 2295 Attributes: map[string]string{ 2296 "metadata_keys.#": "0", 2297 }, 2298 }, 2299 2300 Config: map[string]interface{}{}, 2301 2302 Diff: nil, 2303 2304 Err: false, 2305 }, 2306 2307 { 2308 Name: "Set element computed substring", 2309 Schema: map[string]*Schema{ 2310 "ports": &Schema{ 2311 Type: TypeSet, 2312 Required: true, 2313 Elem: &Schema{Type: TypeInt}, 2314 Set: func(a interface{}) int { 2315 return a.(int) 2316 }, 2317 }, 2318 }, 2319 2320 State: nil, 2321 2322 Config: map[string]interface{}{ 2323 "ports": []interface{}{1, "${var.foo}32"}, 2324 }, 2325 2326 ConfigVariables: map[string]ast.Variable{ 2327 "var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue), 2328 }, 2329 2330 Diff: &terraform.InstanceDiff{ 2331 Attributes: map[string]*terraform.ResourceAttrDiff{ 2332 "ports.#": &terraform.ResourceAttrDiff{ 2333 Old: "", 2334 New: "", 2335 NewComputed: true, 2336 }, 2337 }, 2338 }, 2339 2340 Err: false, 2341 }, 2342 2343 { 2344 Name: "Computed map without config that's known to be empty does not generate diff", 2345 Schema: map[string]*Schema{ 2346 "tags": &Schema{ 2347 Type: TypeMap, 2348 Computed: true, 2349 }, 2350 }, 2351 2352 Config: nil, 2353 2354 State: &terraform.InstanceState{ 2355 Attributes: map[string]string{ 2356 "tags.%": "0", 2357 }, 2358 }, 2359 2360 Diff: nil, 2361 2362 Err: false, 2363 }, 2364 2365 { 2366 Name: "Set with hyphen keys", 2367 Schema: map[string]*Schema{ 2368 "route": &Schema{ 2369 Type: TypeSet, 2370 Optional: true, 2371 Elem: &Resource{ 2372 Schema: map[string]*Schema{ 2373 "index": &Schema{ 2374 Type: TypeInt, 2375 Required: true, 2376 }, 2377 2378 "gateway-name": &Schema{ 2379 Type: TypeString, 2380 Optional: true, 2381 }, 2382 }, 2383 }, 2384 Set: func(v interface{}) int { 2385 m := v.(map[string]interface{}) 2386 return m["index"].(int) 2387 }, 2388 }, 2389 }, 2390 2391 State: nil, 2392 2393 Config: map[string]interface{}{ 2394 "route": []map[string]interface{}{ 2395 map[string]interface{}{ 2396 "index": "1", 2397 "gateway-name": "hello", 2398 }, 2399 }, 2400 }, 2401 2402 Diff: &terraform.InstanceDiff{ 2403 Attributes: map[string]*terraform.ResourceAttrDiff{ 2404 "route.#": &terraform.ResourceAttrDiff{ 2405 Old: "0", 2406 New: "1", 2407 }, 2408 "route.1.index": &terraform.ResourceAttrDiff{ 2409 Old: "", 2410 New: "1", 2411 }, 2412 "route.1.gateway-name": &terraform.ResourceAttrDiff{ 2413 Old: "", 2414 New: "hello", 2415 }, 2416 }, 2417 }, 2418 2419 Err: false, 2420 }, 2421 2422 { 2423 Name: ": StateFunc in nested set (#1759)", 2424 Schema: map[string]*Schema{ 2425 "service_account": &Schema{ 2426 Type: TypeList, 2427 Optional: true, 2428 ForceNew: true, 2429 Elem: &Resource{ 2430 Schema: map[string]*Schema{ 2431 "scopes": &Schema{ 2432 Type: TypeSet, 2433 Required: true, 2434 ForceNew: true, 2435 Elem: &Schema{ 2436 Type: TypeString, 2437 StateFunc: func(v interface{}) string { 2438 return v.(string) + "!" 2439 }, 2440 }, 2441 Set: func(v interface{}) int { 2442 i, err := strconv.Atoi(v.(string)) 2443 if err != nil { 2444 t.Fatalf("err: %s", err) 2445 } 2446 return i 2447 }, 2448 }, 2449 }, 2450 }, 2451 }, 2452 }, 2453 2454 State: nil, 2455 2456 Config: map[string]interface{}{ 2457 "service_account": []map[string]interface{}{ 2458 { 2459 "scopes": []interface{}{"123"}, 2460 }, 2461 }, 2462 }, 2463 2464 Diff: &terraform.InstanceDiff{ 2465 Attributes: map[string]*terraform.ResourceAttrDiff{ 2466 "service_account.#": &terraform.ResourceAttrDiff{ 2467 Old: "0", 2468 New: "1", 2469 RequiresNew: true, 2470 }, 2471 "service_account.0.scopes.#": &terraform.ResourceAttrDiff{ 2472 Old: "0", 2473 New: "1", 2474 RequiresNew: true, 2475 }, 2476 "service_account.0.scopes.123": &terraform.ResourceAttrDiff{ 2477 Old: "", 2478 New: "123!", 2479 NewExtra: "123", 2480 RequiresNew: true, 2481 }, 2482 }, 2483 }, 2484 2485 Err: false, 2486 }, 2487 2488 { 2489 Name: "Removing set elements", 2490 Schema: map[string]*Schema{ 2491 "instances": &Schema{ 2492 Type: TypeSet, 2493 Elem: &Schema{Type: TypeString}, 2494 Optional: true, 2495 ForceNew: true, 2496 Set: func(v interface{}) int { 2497 return len(v.(string)) 2498 }, 2499 }, 2500 }, 2501 2502 State: &terraform.InstanceState{ 2503 Attributes: map[string]string{ 2504 "instances.#": "2", 2505 "instances.3": "333", 2506 "instances.2": "22", 2507 }, 2508 }, 2509 2510 Config: map[string]interface{}{ 2511 "instances": []interface{}{"333", "4444"}, 2512 }, 2513 2514 Diff: &terraform.InstanceDiff{ 2515 Attributes: map[string]*terraform.ResourceAttrDiff{ 2516 "instances.#": &terraform.ResourceAttrDiff{ 2517 Old: "2", 2518 New: "2", 2519 }, 2520 "instances.2": &terraform.ResourceAttrDiff{ 2521 Old: "22", 2522 New: "", 2523 NewRemoved: true, 2524 RequiresNew: true, 2525 }, 2526 "instances.3": &terraform.ResourceAttrDiff{ 2527 Old: "333", 2528 New: "333", 2529 }, 2530 "instances.4": &terraform.ResourceAttrDiff{ 2531 Old: "", 2532 New: "4444", 2533 RequiresNew: true, 2534 }, 2535 }, 2536 }, 2537 2538 Err: false, 2539 }, 2540 2541 { 2542 Name: "Bools can be set with 0/1 in config, still get true/false", 2543 Schema: map[string]*Schema{ 2544 "one": &Schema{ 2545 Type: TypeBool, 2546 Optional: true, 2547 }, 2548 "two": &Schema{ 2549 Type: TypeBool, 2550 Optional: true, 2551 }, 2552 "three": &Schema{ 2553 Type: TypeBool, 2554 Optional: true, 2555 }, 2556 }, 2557 2558 State: &terraform.InstanceState{ 2559 Attributes: map[string]string{ 2560 "one": "false", 2561 "two": "true", 2562 "three": "true", 2563 }, 2564 }, 2565 2566 Config: map[string]interface{}{ 2567 "one": "1", 2568 "two": "0", 2569 }, 2570 2571 Diff: &terraform.InstanceDiff{ 2572 Attributes: map[string]*terraform.ResourceAttrDiff{ 2573 "one": &terraform.ResourceAttrDiff{ 2574 Old: "false", 2575 New: "true", 2576 }, 2577 "two": &terraform.ResourceAttrDiff{ 2578 Old: "true", 2579 New: "false", 2580 }, 2581 "three": &terraform.ResourceAttrDiff{ 2582 Old: "true", 2583 New: "false", 2584 NewRemoved: true, 2585 }, 2586 }, 2587 }, 2588 2589 Err: false, 2590 }, 2591 2592 { 2593 Name: "tainted in state w/ no attr changes is still a replacement", 2594 Schema: map[string]*Schema{}, 2595 2596 State: &terraform.InstanceState{ 2597 Attributes: map[string]string{ 2598 "id": "someid", 2599 }, 2600 Tainted: true, 2601 }, 2602 2603 Config: map[string]interface{}{}, 2604 2605 Diff: &terraform.InstanceDiff{ 2606 Attributes: map[string]*terraform.ResourceAttrDiff{}, 2607 DestroyTainted: true, 2608 }, 2609 2610 Err: false, 2611 }, 2612 2613 { 2614 Name: "Set ForceNew only marks the changing element as ForceNew", 2615 Schema: map[string]*Schema{ 2616 "ports": &Schema{ 2617 Type: TypeSet, 2618 Required: true, 2619 ForceNew: true, 2620 Elem: &Schema{Type: TypeInt}, 2621 Set: func(a interface{}) int { 2622 return a.(int) 2623 }, 2624 }, 2625 }, 2626 2627 State: &terraform.InstanceState{ 2628 Attributes: map[string]string{ 2629 "ports.#": "3", 2630 "ports.1": "1", 2631 "ports.2": "2", 2632 "ports.4": "4", 2633 }, 2634 }, 2635 2636 Config: map[string]interface{}{ 2637 "ports": []interface{}{5, 2, 1}, 2638 }, 2639 2640 Diff: &terraform.InstanceDiff{ 2641 Attributes: map[string]*terraform.ResourceAttrDiff{ 2642 "ports.#": &terraform.ResourceAttrDiff{ 2643 Old: "3", 2644 New: "3", 2645 }, 2646 "ports.1": &terraform.ResourceAttrDiff{ 2647 Old: "1", 2648 New: "1", 2649 }, 2650 "ports.2": &terraform.ResourceAttrDiff{ 2651 Old: "2", 2652 New: "2", 2653 }, 2654 "ports.5": &terraform.ResourceAttrDiff{ 2655 Old: "", 2656 New: "5", 2657 RequiresNew: true, 2658 }, 2659 "ports.4": &terraform.ResourceAttrDiff{ 2660 Old: "4", 2661 New: "0", 2662 NewRemoved: true, 2663 RequiresNew: true, 2664 }, 2665 }, 2666 }, 2667 }, 2668 2669 { 2670 Name: "removed optional items should trigger ForceNew", 2671 Schema: map[string]*Schema{ 2672 "description": &Schema{ 2673 Type: TypeString, 2674 ForceNew: true, 2675 Optional: true, 2676 }, 2677 }, 2678 2679 State: &terraform.InstanceState{ 2680 Attributes: map[string]string{ 2681 "description": "foo", 2682 }, 2683 }, 2684 2685 Config: map[string]interface{}{}, 2686 2687 Diff: &terraform.InstanceDiff{ 2688 Attributes: map[string]*terraform.ResourceAttrDiff{ 2689 "description": &terraform.ResourceAttrDiff{ 2690 Old: "foo", 2691 New: "", 2692 RequiresNew: true, 2693 NewRemoved: true, 2694 }, 2695 }, 2696 }, 2697 2698 Err: false, 2699 }, 2700 2701 // GH-7715 2702 { 2703 Name: "computed value for boolean field", 2704 Schema: map[string]*Schema{ 2705 "foo": &Schema{ 2706 Type: TypeBool, 2707 ForceNew: true, 2708 Computed: true, 2709 Optional: true, 2710 }, 2711 }, 2712 2713 State: &terraform.InstanceState{}, 2714 2715 Config: map[string]interface{}{ 2716 "foo": "${var.foo}", 2717 }, 2718 2719 ConfigVariables: map[string]ast.Variable{ 2720 "var.foo": interfaceToVariableSwallowError( 2721 config.UnknownVariableValue), 2722 }, 2723 2724 Diff: &terraform.InstanceDiff{ 2725 Attributes: map[string]*terraform.ResourceAttrDiff{ 2726 "foo": &terraform.ResourceAttrDiff{ 2727 Old: "", 2728 New: "false", 2729 NewComputed: true, 2730 RequiresNew: true, 2731 }, 2732 }, 2733 }, 2734 2735 Err: false, 2736 }, 2737 2738 { 2739 Name: "Set ForceNew marks count as ForceNew if computed", 2740 Schema: map[string]*Schema{ 2741 "ports": &Schema{ 2742 Type: TypeSet, 2743 Required: true, 2744 ForceNew: true, 2745 Elem: &Schema{Type: TypeInt}, 2746 Set: func(a interface{}) int { 2747 return a.(int) 2748 }, 2749 }, 2750 }, 2751 2752 State: &terraform.InstanceState{ 2753 Attributes: map[string]string{ 2754 "ports.#": "3", 2755 "ports.1": "1", 2756 "ports.2": "2", 2757 "ports.4": "4", 2758 }, 2759 }, 2760 2761 Config: map[string]interface{}{ 2762 "ports": []interface{}{"${var.foo}", 2, 1}, 2763 }, 2764 2765 ConfigVariables: map[string]ast.Variable{ 2766 "var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue), 2767 }, 2768 2769 Diff: &terraform.InstanceDiff{ 2770 Attributes: map[string]*terraform.ResourceAttrDiff{ 2771 "ports.#": &terraform.ResourceAttrDiff{ 2772 Old: "3", 2773 New: "", 2774 NewComputed: true, 2775 RequiresNew: true, 2776 }, 2777 }, 2778 }, 2779 }, 2780 2781 { 2782 Name: "List with computed schema and ForceNew", 2783 Schema: map[string]*Schema{ 2784 "config": &Schema{ 2785 Type: TypeList, 2786 Optional: true, 2787 ForceNew: true, 2788 Elem: &Schema{ 2789 Type: TypeString, 2790 }, 2791 }, 2792 }, 2793 2794 State: &terraform.InstanceState{ 2795 Attributes: map[string]string{ 2796 "config.#": "2", 2797 "config.0": "a", 2798 "config.1": "b", 2799 }, 2800 }, 2801 2802 Config: map[string]interface{}{ 2803 "config": []interface{}{"${var.a}", "${var.b}"}, 2804 }, 2805 2806 ConfigVariables: map[string]ast.Variable{ 2807 "var.a": interfaceToVariableSwallowError( 2808 config.UnknownVariableValue), 2809 "var.b": interfaceToVariableSwallowError( 2810 config.UnknownVariableValue), 2811 }, 2812 2813 Diff: &terraform.InstanceDiff{ 2814 Attributes: map[string]*terraform.ResourceAttrDiff{ 2815 "config.#": &terraform.ResourceAttrDiff{ 2816 Old: "2", 2817 New: "", 2818 RequiresNew: true, 2819 NewComputed: true, 2820 }, 2821 }, 2822 }, 2823 2824 Err: false, 2825 }, 2826 } 2827 2828 for i, tc := range cases { 2829 t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { 2830 c, err := config.NewRawConfig(tc.Config) 2831 if err != nil { 2832 t.Fatalf("err: %s", err) 2833 } 2834 2835 if len(tc.ConfigVariables) > 0 { 2836 if err := c.Interpolate(tc.ConfigVariables); err != nil { 2837 t.Fatalf("err: %s", err) 2838 } 2839 } 2840 2841 d, err := schemaMap(tc.Schema).Diff( 2842 tc.State, terraform.NewResourceConfig(c)) 2843 if err != nil != tc.Err { 2844 t.Fatalf("err: %s", err) 2845 } 2846 2847 if !reflect.DeepEqual(tc.Diff, d) { 2848 t.Fatalf("expected:\n%#v\n\ngot:\n%#v", tc.Diff, d) 2849 } 2850 }) 2851 } 2852 } 2853 2854 func TestSchemaMap_Input(t *testing.T) { 2855 cases := map[string]struct { 2856 Schema map[string]*Schema 2857 Config map[string]interface{} 2858 Input map[string]string 2859 Result map[string]interface{} 2860 Err bool 2861 }{ 2862 /* 2863 * String decode 2864 */ 2865 2866 "no input on optional field with no config": { 2867 Schema: map[string]*Schema{ 2868 "availability_zone": &Schema{ 2869 Type: TypeString, 2870 Optional: true, 2871 }, 2872 }, 2873 2874 Input: map[string]string{}, 2875 Result: map[string]interface{}{}, 2876 Err: false, 2877 }, 2878 2879 "input ignored when config has a value": { 2880 Schema: map[string]*Schema{ 2881 "availability_zone": &Schema{ 2882 Type: TypeString, 2883 Optional: true, 2884 }, 2885 }, 2886 2887 Config: map[string]interface{}{ 2888 "availability_zone": "bar", 2889 }, 2890 2891 Input: map[string]string{ 2892 "availability_zone": "foo", 2893 }, 2894 2895 Result: map[string]interface{}{}, 2896 2897 Err: false, 2898 }, 2899 2900 "input ignored when schema has a default": { 2901 Schema: map[string]*Schema{ 2902 "availability_zone": &Schema{ 2903 Type: TypeString, 2904 Default: "foo", 2905 Optional: true, 2906 }, 2907 }, 2908 2909 Input: map[string]string{ 2910 "availability_zone": "bar", 2911 }, 2912 2913 Result: map[string]interface{}{}, 2914 2915 Err: false, 2916 }, 2917 2918 "input ignored when default function returns a value": { 2919 Schema: map[string]*Schema{ 2920 "availability_zone": &Schema{ 2921 Type: TypeString, 2922 DefaultFunc: func() (interface{}, error) { 2923 return "foo", nil 2924 }, 2925 Optional: true, 2926 }, 2927 }, 2928 2929 Input: map[string]string{ 2930 "availability_zone": "bar", 2931 }, 2932 2933 Result: map[string]interface{}{}, 2934 2935 Err: false, 2936 }, 2937 2938 "input ignored when default function returns an empty string": { 2939 Schema: map[string]*Schema{ 2940 "availability_zone": &Schema{ 2941 Type: TypeString, 2942 Default: "", 2943 Optional: true, 2944 }, 2945 }, 2946 2947 Input: map[string]string{ 2948 "availability_zone": "bar", 2949 }, 2950 2951 Result: map[string]interface{}{}, 2952 2953 Err: false, 2954 }, 2955 2956 "input used when default function returns nil": { 2957 Schema: map[string]*Schema{ 2958 "availability_zone": &Schema{ 2959 Type: TypeString, 2960 DefaultFunc: func() (interface{}, error) { 2961 return nil, nil 2962 }, 2963 Required: true, 2964 }, 2965 }, 2966 2967 Input: map[string]string{ 2968 "availability_zone": "bar", 2969 }, 2970 2971 Result: map[string]interface{}{ 2972 "availability_zone": "bar", 2973 }, 2974 2975 Err: false, 2976 }, 2977 2978 "input not used when optional default function returns nil": { 2979 Schema: map[string]*Schema{ 2980 "availability_zone": &Schema{ 2981 Type: TypeString, 2982 DefaultFunc: func() (interface{}, error) { 2983 return nil, nil 2984 }, 2985 Optional: true, 2986 }, 2987 }, 2988 2989 Input: map[string]string{}, 2990 Result: map[string]interface{}{}, 2991 Err: false, 2992 }, 2993 } 2994 2995 for i, tc := range cases { 2996 if tc.Config == nil { 2997 tc.Config = make(map[string]interface{}) 2998 } 2999 3000 c, err := config.NewRawConfig(tc.Config) 3001 if err != nil { 3002 t.Fatalf("err: %s", err) 3003 } 3004 3005 input := new(terraform.MockUIInput) 3006 input.InputReturnMap = tc.Input 3007 3008 rc := terraform.NewResourceConfig(c) 3009 rc.Config = make(map[string]interface{}) 3010 3011 actual, err := schemaMap(tc.Schema).Input(input, rc) 3012 if err != nil != tc.Err { 3013 t.Fatalf("#%v err: %s", i, err) 3014 } 3015 3016 if !reflect.DeepEqual(tc.Result, actual.Config) { 3017 t.Fatalf("#%v: bad:\n\ngot: %#v\nexpected: %#v", i, actual.Config, tc.Result) 3018 } 3019 } 3020 } 3021 3022 func TestSchemaMap_InputDefault(t *testing.T) { 3023 emptyConfig := make(map[string]interface{}) 3024 c, err := config.NewRawConfig(emptyConfig) 3025 if err != nil { 3026 t.Fatalf("err: %s", err) 3027 } 3028 rc := terraform.NewResourceConfig(c) 3029 rc.Config = make(map[string]interface{}) 3030 3031 input := new(terraform.MockUIInput) 3032 input.InputFn = func(opts *terraform.InputOpts) (string, error) { 3033 t.Fatalf("InputFn should not be called on: %#v", opts) 3034 return "", nil 3035 } 3036 3037 schema := map[string]*Schema{ 3038 "availability_zone": &Schema{ 3039 Type: TypeString, 3040 Default: "foo", 3041 Optional: true, 3042 }, 3043 } 3044 actual, err := schemaMap(schema).Input(input, rc) 3045 if err != nil { 3046 t.Fatalf("err: %s", err) 3047 } 3048 3049 expected := map[string]interface{}{} 3050 3051 if !reflect.DeepEqual(expected, actual.Config) { 3052 t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected) 3053 } 3054 } 3055 3056 func TestSchemaMap_InputDeprecated(t *testing.T) { 3057 emptyConfig := make(map[string]interface{}) 3058 c, err := config.NewRawConfig(emptyConfig) 3059 if err != nil { 3060 t.Fatalf("err: %s", err) 3061 } 3062 rc := terraform.NewResourceConfig(c) 3063 rc.Config = make(map[string]interface{}) 3064 3065 input := new(terraform.MockUIInput) 3066 input.InputFn = func(opts *terraform.InputOpts) (string, error) { 3067 t.Fatalf("InputFn should not be called on: %#v", opts) 3068 return "", nil 3069 } 3070 3071 schema := map[string]*Schema{ 3072 "availability_zone": &Schema{ 3073 Type: TypeString, 3074 Deprecated: "long gone", 3075 Optional: true, 3076 }, 3077 } 3078 actual, err := schemaMap(schema).Input(input, rc) 3079 if err != nil { 3080 t.Fatalf("err: %s", err) 3081 } 3082 3083 expected := map[string]interface{}{} 3084 3085 if !reflect.DeepEqual(expected, actual.Config) { 3086 t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected) 3087 } 3088 } 3089 3090 func TestSchemaMap_InternalValidate(t *testing.T) { 3091 cases := map[string]struct { 3092 In map[string]*Schema 3093 Err bool 3094 }{ 3095 "nothing": { 3096 nil, 3097 false, 3098 }, 3099 3100 "Both optional and required": { 3101 map[string]*Schema{ 3102 "foo": &Schema{ 3103 Type: TypeInt, 3104 Optional: true, 3105 Required: true, 3106 }, 3107 }, 3108 true, 3109 }, 3110 3111 "No optional and no required": { 3112 map[string]*Schema{ 3113 "foo": &Schema{ 3114 Type: TypeInt, 3115 }, 3116 }, 3117 true, 3118 }, 3119 3120 "Missing Type": { 3121 map[string]*Schema{ 3122 "foo": &Schema{ 3123 Required: true, 3124 }, 3125 }, 3126 true, 3127 }, 3128 3129 "Required but computed": { 3130 map[string]*Schema{ 3131 "foo": &Schema{ 3132 Type: TypeInt, 3133 Required: true, 3134 Computed: true, 3135 }, 3136 }, 3137 true, 3138 }, 3139 3140 "Looks good": { 3141 map[string]*Schema{ 3142 "foo": &Schema{ 3143 Type: TypeString, 3144 Required: true, 3145 }, 3146 }, 3147 false, 3148 }, 3149 3150 "Computed but has default": { 3151 map[string]*Schema{ 3152 "foo": &Schema{ 3153 Type: TypeInt, 3154 Optional: true, 3155 Computed: true, 3156 Default: "foo", 3157 }, 3158 }, 3159 true, 3160 }, 3161 3162 "Required but has default": { 3163 map[string]*Schema{ 3164 "foo": &Schema{ 3165 Type: TypeInt, 3166 Optional: true, 3167 Required: true, 3168 Default: "foo", 3169 }, 3170 }, 3171 true, 3172 }, 3173 3174 "List element not set": { 3175 map[string]*Schema{ 3176 "foo": &Schema{ 3177 Type: TypeList, 3178 }, 3179 }, 3180 true, 3181 }, 3182 3183 "List default": { 3184 map[string]*Schema{ 3185 "foo": &Schema{ 3186 Type: TypeList, 3187 Elem: &Schema{Type: TypeInt}, 3188 Default: "foo", 3189 }, 3190 }, 3191 true, 3192 }, 3193 3194 "List element computed": { 3195 map[string]*Schema{ 3196 "foo": &Schema{ 3197 Type: TypeList, 3198 Optional: true, 3199 Elem: &Schema{ 3200 Type: TypeInt, 3201 Computed: true, 3202 }, 3203 }, 3204 }, 3205 true, 3206 }, 3207 3208 "List element with Set set": { 3209 map[string]*Schema{ 3210 "foo": &Schema{ 3211 Type: TypeList, 3212 Elem: &Schema{Type: TypeInt}, 3213 Set: func(interface{}) int { return 0 }, 3214 Optional: true, 3215 }, 3216 }, 3217 true, 3218 }, 3219 3220 "Set element with no Set set": { 3221 map[string]*Schema{ 3222 "foo": &Schema{ 3223 Type: TypeSet, 3224 Elem: &Schema{Type: TypeInt}, 3225 Optional: true, 3226 }, 3227 }, 3228 false, 3229 }, 3230 3231 "Required but computedWhen": { 3232 map[string]*Schema{ 3233 "foo": &Schema{ 3234 Type: TypeInt, 3235 Required: true, 3236 ComputedWhen: []string{"foo"}, 3237 }, 3238 }, 3239 true, 3240 }, 3241 3242 "Conflicting attributes cannot be required": { 3243 map[string]*Schema{ 3244 "blacklist": &Schema{ 3245 Type: TypeBool, 3246 Required: true, 3247 }, 3248 "whitelist": &Schema{ 3249 Type: TypeBool, 3250 Optional: true, 3251 ConflictsWith: []string{"blacklist"}, 3252 }, 3253 }, 3254 true, 3255 }, 3256 3257 "Attribute with conflicts cannot be required": { 3258 map[string]*Schema{ 3259 "whitelist": &Schema{ 3260 Type: TypeBool, 3261 Required: true, 3262 ConflictsWith: []string{"blacklist"}, 3263 }, 3264 }, 3265 true, 3266 }, 3267 3268 "ConflictsWith cannot be used w/ ComputedWhen": { 3269 map[string]*Schema{ 3270 "blacklist": &Schema{ 3271 Type: TypeBool, 3272 ComputedWhen: []string{"foor"}, 3273 }, 3274 "whitelist": &Schema{ 3275 Type: TypeBool, 3276 Required: true, 3277 ConflictsWith: []string{"blacklist"}, 3278 }, 3279 }, 3280 true, 3281 }, 3282 3283 "Sub-resource invalid": { 3284 map[string]*Schema{ 3285 "foo": &Schema{ 3286 Type: TypeList, 3287 Optional: true, 3288 Elem: &Resource{ 3289 Schema: map[string]*Schema{ 3290 "foo": new(Schema), 3291 }, 3292 }, 3293 }, 3294 }, 3295 true, 3296 }, 3297 3298 "Sub-resource valid": { 3299 map[string]*Schema{ 3300 "foo": &Schema{ 3301 Type: TypeList, 3302 Optional: true, 3303 Elem: &Resource{ 3304 Schema: map[string]*Schema{ 3305 "foo": &Schema{ 3306 Type: TypeInt, 3307 Optional: true, 3308 }, 3309 }, 3310 }, 3311 }, 3312 }, 3313 false, 3314 }, 3315 3316 "ValidateFunc on non-primitive": { 3317 map[string]*Schema{ 3318 "foo": &Schema{ 3319 Type: TypeSet, 3320 Required: true, 3321 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 3322 return 3323 }, 3324 }, 3325 }, 3326 true, 3327 }, 3328 3329 "computed-only field with validateFunc": { 3330 map[string]*Schema{ 3331 "string": &Schema{ 3332 Type: TypeString, 3333 Computed: true, 3334 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 3335 es = append(es, fmt.Errorf("this is not fine")) 3336 return 3337 }, 3338 }, 3339 }, 3340 true, 3341 }, 3342 3343 "computed-only field with diffSuppressFunc": { 3344 map[string]*Schema{ 3345 "string": &Schema{ 3346 Type: TypeString, 3347 Computed: true, 3348 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3349 // Always suppress any diff 3350 return false 3351 }, 3352 }, 3353 }, 3354 true, 3355 }, 3356 } 3357 3358 for tn, tc := range cases { 3359 t.Run(tn, func(t *testing.T) { 3360 err := schemaMap(tc.In).InternalValidate(nil) 3361 if err != nil != tc.Err { 3362 if tc.Err { 3363 t.Fatalf("%q: Expected error did not occur:\n\n%#v", tn, tc.In) 3364 } 3365 t.Fatalf("%q: Unexpected error occurred: %s\n\n%#v", tn, err, tc.In) 3366 } 3367 }) 3368 } 3369 3370 } 3371 3372 func TestSchemaMap_DiffSuppress(t *testing.T) { 3373 cases := map[string]struct { 3374 Schema map[string]*Schema 3375 State *terraform.InstanceState 3376 Config map[string]interface{} 3377 ConfigVariables map[string]ast.Variable 3378 ExpectedDiff *terraform.InstanceDiff 3379 Err bool 3380 }{ 3381 "#0 - Suppress otherwise valid diff by returning true": { 3382 Schema: map[string]*Schema{ 3383 "availability_zone": { 3384 Type: TypeString, 3385 Optional: true, 3386 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3387 // Always suppress any diff 3388 return true 3389 }, 3390 }, 3391 }, 3392 3393 State: nil, 3394 3395 Config: map[string]interface{}{ 3396 "availability_zone": "foo", 3397 }, 3398 3399 ExpectedDiff: nil, 3400 3401 Err: false, 3402 }, 3403 3404 "#1 - Don't suppress diff by returning false": { 3405 Schema: map[string]*Schema{ 3406 "availability_zone": { 3407 Type: TypeString, 3408 Optional: true, 3409 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3410 // Always suppress any diff 3411 return false 3412 }, 3413 }, 3414 }, 3415 3416 State: nil, 3417 3418 Config: map[string]interface{}{ 3419 "availability_zone": "foo", 3420 }, 3421 3422 ExpectedDiff: &terraform.InstanceDiff{ 3423 Attributes: map[string]*terraform.ResourceAttrDiff{ 3424 "availability_zone": { 3425 Old: "", 3426 New: "foo", 3427 }, 3428 }, 3429 }, 3430 3431 Err: false, 3432 }, 3433 3434 "Default with suppress makes no diff": { 3435 Schema: map[string]*Schema{ 3436 "availability_zone": { 3437 Type: TypeString, 3438 Optional: true, 3439 Default: "foo", 3440 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3441 return true 3442 }, 3443 }, 3444 }, 3445 3446 State: nil, 3447 3448 Config: map[string]interface{}{}, 3449 3450 ExpectedDiff: nil, 3451 3452 Err: false, 3453 }, 3454 3455 "Default with false suppress makes diff": { 3456 Schema: map[string]*Schema{ 3457 "availability_zone": { 3458 Type: TypeString, 3459 Optional: true, 3460 Default: "foo", 3461 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3462 return false 3463 }, 3464 }, 3465 }, 3466 3467 State: nil, 3468 3469 Config: map[string]interface{}{}, 3470 3471 ExpectedDiff: &terraform.InstanceDiff{ 3472 Attributes: map[string]*terraform.ResourceAttrDiff{ 3473 "availability_zone": { 3474 Old: "", 3475 New: "foo", 3476 }, 3477 }, 3478 }, 3479 3480 Err: false, 3481 }, 3482 3483 "Complex structure with set of computed string should mark root set as computed": { 3484 Schema: map[string]*Schema{ 3485 "outer": &Schema{ 3486 Type: TypeSet, 3487 Optional: true, 3488 Elem: &Resource{ 3489 Schema: map[string]*Schema{ 3490 "outer_str": &Schema{ 3491 Type: TypeString, 3492 Optional: true, 3493 }, 3494 "inner": &Schema{ 3495 Type: TypeSet, 3496 Optional: true, 3497 Elem: &Resource{ 3498 Schema: map[string]*Schema{ 3499 "inner_str": &Schema{ 3500 Type: TypeString, 3501 Optional: true, 3502 }, 3503 }, 3504 }, 3505 Set: func(v interface{}) int { 3506 return 2 3507 }, 3508 }, 3509 }, 3510 }, 3511 Set: func(v interface{}) int { 3512 return 1 3513 }, 3514 }, 3515 }, 3516 3517 State: nil, 3518 3519 Config: map[string]interface{}{ 3520 "outer": []map[string]interface{}{ 3521 map[string]interface{}{ 3522 "outer_str": "foo", 3523 "inner": []map[string]interface{}{ 3524 map[string]interface{}{ 3525 "inner_str": "${var.bar}", 3526 }, 3527 }, 3528 }, 3529 }, 3530 }, 3531 3532 ConfigVariables: map[string]ast.Variable{ 3533 "var.bar": interfaceToVariableSwallowError(config.UnknownVariableValue), 3534 }, 3535 3536 ExpectedDiff: &terraform.InstanceDiff{ 3537 Attributes: map[string]*terraform.ResourceAttrDiff{ 3538 "outer.#": &terraform.ResourceAttrDiff{ 3539 Old: "0", 3540 New: "1", 3541 }, 3542 "outer.~1.outer_str": &terraform.ResourceAttrDiff{ 3543 Old: "", 3544 New: "foo", 3545 }, 3546 "outer.~1.inner.#": &terraform.ResourceAttrDiff{ 3547 Old: "0", 3548 New: "1", 3549 }, 3550 "outer.~1.inner.~2.inner_str": &terraform.ResourceAttrDiff{ 3551 Old: "", 3552 New: "${var.bar}", 3553 NewComputed: true, 3554 }, 3555 }, 3556 }, 3557 3558 Err: false, 3559 }, 3560 3561 "Complex structure with complex list of computed string should mark root set as computed": { 3562 Schema: map[string]*Schema{ 3563 "outer": &Schema{ 3564 Type: TypeSet, 3565 Optional: true, 3566 Elem: &Resource{ 3567 Schema: map[string]*Schema{ 3568 "outer_str": &Schema{ 3569 Type: TypeString, 3570 Optional: true, 3571 }, 3572 "inner": &Schema{ 3573 Type: TypeList, 3574 Optional: true, 3575 Elem: &Resource{ 3576 Schema: map[string]*Schema{ 3577 "inner_str": &Schema{ 3578 Type: TypeString, 3579 Optional: true, 3580 }, 3581 }, 3582 }, 3583 }, 3584 }, 3585 }, 3586 Set: func(v interface{}) int { 3587 return 1 3588 }, 3589 }, 3590 }, 3591 3592 State: nil, 3593 3594 Config: map[string]interface{}{ 3595 "outer": []map[string]interface{}{ 3596 map[string]interface{}{ 3597 "outer_str": "foo", 3598 "inner": []map[string]interface{}{ 3599 map[string]interface{}{ 3600 "inner_str": "${var.bar}", 3601 }, 3602 }, 3603 }, 3604 }, 3605 }, 3606 3607 ConfigVariables: map[string]ast.Variable{ 3608 "var.bar": interfaceToVariableSwallowError(config.UnknownVariableValue), 3609 }, 3610 3611 ExpectedDiff: &terraform.InstanceDiff{ 3612 Attributes: map[string]*terraform.ResourceAttrDiff{ 3613 "outer.#": &terraform.ResourceAttrDiff{ 3614 Old: "0", 3615 New: "1", 3616 }, 3617 "outer.~1.outer_str": &terraform.ResourceAttrDiff{ 3618 Old: "", 3619 New: "foo", 3620 }, 3621 "outer.~1.inner.#": &terraform.ResourceAttrDiff{ 3622 Old: "0", 3623 New: "1", 3624 }, 3625 "outer.~1.inner.0.inner_str": &terraform.ResourceAttrDiff{ 3626 Old: "", 3627 New: "${var.bar}", 3628 NewComputed: true, 3629 }, 3630 }, 3631 }, 3632 3633 Err: false, 3634 }, 3635 } 3636 3637 for tn, tc := range cases { 3638 t.Run(tn, func(t *testing.T) { 3639 c, err := config.NewRawConfig(tc.Config) 3640 if err != nil { 3641 t.Fatalf("#%q err: %s", tn, err) 3642 } 3643 3644 if len(tc.ConfigVariables) > 0 { 3645 if err := c.Interpolate(tc.ConfigVariables); err != nil { 3646 t.Fatalf("#%q err: %s", tn, err) 3647 } 3648 } 3649 3650 d, err := schemaMap(tc.Schema).Diff( 3651 tc.State, terraform.NewResourceConfig(c)) 3652 if err != nil != tc.Err { 3653 t.Fatalf("#%q err: %s", tn, err) 3654 } 3655 3656 if !reflect.DeepEqual(tc.ExpectedDiff, d) { 3657 t.Fatalf("#%q:\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.ExpectedDiff, d) 3658 } 3659 }) 3660 } 3661 } 3662 3663 func TestSchemaMap_Validate(t *testing.T) { 3664 cases := map[string]struct { 3665 Schema map[string]*Schema 3666 Config map[string]interface{} 3667 Vars map[string]string 3668 Err bool 3669 Errors []error 3670 Warnings []string 3671 }{ 3672 "Good": { 3673 Schema: map[string]*Schema{ 3674 "availability_zone": &Schema{ 3675 Type: TypeString, 3676 Optional: true, 3677 Computed: true, 3678 ForceNew: true, 3679 }, 3680 }, 3681 3682 Config: map[string]interface{}{ 3683 "availability_zone": "foo", 3684 }, 3685 }, 3686 3687 "Good, because the var is not set and that error will come elsewhere": { 3688 Schema: map[string]*Schema{ 3689 "size": &Schema{ 3690 Type: TypeInt, 3691 Required: true, 3692 }, 3693 }, 3694 3695 Config: map[string]interface{}{ 3696 "size": "${var.foo}", 3697 }, 3698 3699 Vars: map[string]string{ 3700 "var.foo": config.UnknownVariableValue, 3701 }, 3702 }, 3703 3704 "Required field not set": { 3705 Schema: map[string]*Schema{ 3706 "availability_zone": &Schema{ 3707 Type: TypeString, 3708 Required: true, 3709 }, 3710 }, 3711 3712 Config: map[string]interface{}{}, 3713 3714 Err: true, 3715 }, 3716 3717 "Invalid basic type": { 3718 Schema: map[string]*Schema{ 3719 "port": &Schema{ 3720 Type: TypeInt, 3721 Required: true, 3722 }, 3723 }, 3724 3725 Config: map[string]interface{}{ 3726 "port": "I am invalid", 3727 }, 3728 3729 Err: true, 3730 }, 3731 3732 "Invalid complex type": { 3733 Schema: map[string]*Schema{ 3734 "user_data": &Schema{ 3735 Type: TypeString, 3736 Optional: true, 3737 }, 3738 }, 3739 3740 Config: map[string]interface{}{ 3741 "user_data": []interface{}{ 3742 map[string]interface{}{ 3743 "foo": "bar", 3744 }, 3745 }, 3746 }, 3747 3748 Err: true, 3749 }, 3750 3751 "Bad type, interpolated": { 3752 Schema: map[string]*Schema{ 3753 "size": &Schema{ 3754 Type: TypeInt, 3755 Required: true, 3756 }, 3757 }, 3758 3759 Config: map[string]interface{}{ 3760 "size": "${var.foo}", 3761 }, 3762 3763 Vars: map[string]string{ 3764 "var.foo": "nope", 3765 }, 3766 3767 Err: true, 3768 }, 3769 3770 "Required but has DefaultFunc": { 3771 Schema: map[string]*Schema{ 3772 "availability_zone": &Schema{ 3773 Type: TypeString, 3774 Required: true, 3775 DefaultFunc: func() (interface{}, error) { 3776 return "foo", nil 3777 }, 3778 }, 3779 }, 3780 3781 Config: nil, 3782 }, 3783 3784 "Required but has DefaultFunc return nil": { 3785 Schema: map[string]*Schema{ 3786 "availability_zone": &Schema{ 3787 Type: TypeString, 3788 Required: true, 3789 DefaultFunc: func() (interface{}, error) { 3790 return nil, nil 3791 }, 3792 }, 3793 }, 3794 3795 Config: nil, 3796 3797 Err: true, 3798 }, 3799 3800 "List with promotion": { 3801 Schema: map[string]*Schema{ 3802 "ingress": &Schema{ 3803 Type: TypeList, 3804 Elem: &Schema{Type: TypeInt}, 3805 PromoteSingle: true, 3806 Optional: true, 3807 }, 3808 }, 3809 3810 Config: map[string]interface{}{ 3811 "ingress": "5", 3812 }, 3813 3814 Err: false, 3815 }, 3816 3817 "List with promotion set as list": { 3818 Schema: map[string]*Schema{ 3819 "ingress": &Schema{ 3820 Type: TypeList, 3821 Elem: &Schema{Type: TypeInt}, 3822 PromoteSingle: true, 3823 Optional: true, 3824 }, 3825 }, 3826 3827 Config: map[string]interface{}{ 3828 "ingress": []interface{}{"5"}, 3829 }, 3830 3831 Err: false, 3832 }, 3833 3834 "Optional sub-resource": { 3835 Schema: map[string]*Schema{ 3836 "ingress": &Schema{ 3837 Type: TypeList, 3838 Elem: &Resource{ 3839 Schema: map[string]*Schema{ 3840 "from": &Schema{ 3841 Type: TypeInt, 3842 Required: true, 3843 }, 3844 }, 3845 }, 3846 }, 3847 }, 3848 3849 Config: map[string]interface{}{}, 3850 3851 Err: false, 3852 }, 3853 3854 "Sub-resource is the wrong type": { 3855 Schema: map[string]*Schema{ 3856 "ingress": &Schema{ 3857 Type: TypeList, 3858 Required: true, 3859 Elem: &Resource{ 3860 Schema: map[string]*Schema{ 3861 "from": &Schema{ 3862 Type: TypeInt, 3863 Required: true, 3864 }, 3865 }, 3866 }, 3867 }, 3868 }, 3869 3870 Config: map[string]interface{}{ 3871 "ingress": []interface{}{"foo"}, 3872 }, 3873 3874 Err: true, 3875 }, 3876 3877 "Not a list": { 3878 Schema: map[string]*Schema{ 3879 "ingress": &Schema{ 3880 Type: TypeList, 3881 Elem: &Resource{ 3882 Schema: map[string]*Schema{ 3883 "from": &Schema{ 3884 Type: TypeInt, 3885 Required: true, 3886 }, 3887 }, 3888 }, 3889 }, 3890 }, 3891 3892 Config: map[string]interface{}{ 3893 "ingress": "foo", 3894 }, 3895 3896 Err: true, 3897 }, 3898 3899 "Required sub-resource field": { 3900 Schema: map[string]*Schema{ 3901 "ingress": &Schema{ 3902 Type: TypeList, 3903 Elem: &Resource{ 3904 Schema: map[string]*Schema{ 3905 "from": &Schema{ 3906 Type: TypeInt, 3907 Required: true, 3908 }, 3909 }, 3910 }, 3911 }, 3912 }, 3913 3914 Config: map[string]interface{}{ 3915 "ingress": []interface{}{ 3916 map[string]interface{}{}, 3917 }, 3918 }, 3919 3920 Err: true, 3921 }, 3922 3923 "Good sub-resource": { 3924 Schema: map[string]*Schema{ 3925 "ingress": &Schema{ 3926 Type: TypeList, 3927 Optional: true, 3928 Elem: &Resource{ 3929 Schema: map[string]*Schema{ 3930 "from": &Schema{ 3931 Type: TypeInt, 3932 Required: true, 3933 }, 3934 }, 3935 }, 3936 }, 3937 }, 3938 3939 Config: map[string]interface{}{ 3940 "ingress": []interface{}{ 3941 map[string]interface{}{ 3942 "from": 80, 3943 }, 3944 }, 3945 }, 3946 3947 Err: false, 3948 }, 3949 3950 "Invalid/unknown field": { 3951 Schema: map[string]*Schema{ 3952 "availability_zone": &Schema{ 3953 Type: TypeString, 3954 Optional: true, 3955 Computed: true, 3956 ForceNew: true, 3957 }, 3958 }, 3959 3960 Config: map[string]interface{}{ 3961 "foo": "bar", 3962 }, 3963 3964 Err: true, 3965 }, 3966 3967 "Invalid/unknown field with computed value": { 3968 Schema: map[string]*Schema{ 3969 "availability_zone": &Schema{ 3970 Type: TypeString, 3971 Optional: true, 3972 Computed: true, 3973 ForceNew: true, 3974 }, 3975 }, 3976 3977 Config: map[string]interface{}{ 3978 "foo": "${var.foo}", 3979 }, 3980 3981 Vars: map[string]string{ 3982 "var.foo": config.UnknownVariableValue, 3983 }, 3984 3985 Err: true, 3986 }, 3987 3988 "Computed field set": { 3989 Schema: map[string]*Schema{ 3990 "availability_zone": &Schema{ 3991 Type: TypeString, 3992 Computed: true, 3993 }, 3994 }, 3995 3996 Config: map[string]interface{}{ 3997 "availability_zone": "bar", 3998 }, 3999 4000 Err: true, 4001 }, 4002 4003 "Not a set": { 4004 Schema: map[string]*Schema{ 4005 "ports": &Schema{ 4006 Type: TypeSet, 4007 Required: true, 4008 Elem: &Schema{Type: TypeInt}, 4009 Set: func(a interface{}) int { 4010 return a.(int) 4011 }, 4012 }, 4013 }, 4014 4015 Config: map[string]interface{}{ 4016 "ports": "foo", 4017 }, 4018 4019 Err: true, 4020 }, 4021 4022 "Maps": { 4023 Schema: map[string]*Schema{ 4024 "user_data": &Schema{ 4025 Type: TypeMap, 4026 Optional: true, 4027 }, 4028 }, 4029 4030 Config: map[string]interface{}{ 4031 "user_data": "foo", 4032 }, 4033 4034 Err: true, 4035 }, 4036 4037 "Good map: data surrounded by extra slice": { 4038 Schema: map[string]*Schema{ 4039 "user_data": &Schema{ 4040 Type: TypeMap, 4041 Optional: true, 4042 }, 4043 }, 4044 4045 Config: map[string]interface{}{ 4046 "user_data": []interface{}{ 4047 map[string]interface{}{ 4048 "foo": "bar", 4049 }, 4050 }, 4051 }, 4052 }, 4053 4054 "Good map": { 4055 Schema: map[string]*Schema{ 4056 "user_data": &Schema{ 4057 Type: TypeMap, 4058 Optional: true, 4059 }, 4060 }, 4061 4062 Config: map[string]interface{}{ 4063 "user_data": map[string]interface{}{ 4064 "foo": "bar", 4065 }, 4066 }, 4067 }, 4068 4069 "Bad map: just a slice": { 4070 Schema: map[string]*Schema{ 4071 "user_data": &Schema{ 4072 Type: TypeMap, 4073 Optional: true, 4074 }, 4075 }, 4076 4077 Config: map[string]interface{}{ 4078 "user_data": []interface{}{ 4079 "foo", 4080 }, 4081 }, 4082 4083 Err: true, 4084 }, 4085 4086 "Good set: config has slice with single interpolated value": { 4087 Schema: map[string]*Schema{ 4088 "security_groups": &Schema{ 4089 Type: TypeSet, 4090 Optional: true, 4091 Computed: true, 4092 ForceNew: true, 4093 Elem: &Schema{Type: TypeString}, 4094 Set: func(v interface{}) int { 4095 return len(v.(string)) 4096 }, 4097 }, 4098 }, 4099 4100 Config: map[string]interface{}{ 4101 "security_groups": []interface{}{"${var.foo}"}, 4102 }, 4103 4104 Err: false, 4105 }, 4106 4107 "Bad set: config has single interpolated value": { 4108 Schema: map[string]*Schema{ 4109 "security_groups": &Schema{ 4110 Type: TypeSet, 4111 Optional: true, 4112 Computed: true, 4113 ForceNew: true, 4114 Elem: &Schema{Type: TypeString}, 4115 }, 4116 }, 4117 4118 Config: map[string]interface{}{ 4119 "security_groups": "${var.foo}", 4120 }, 4121 4122 Err: true, 4123 }, 4124 4125 "Bad, subresource should not allow unknown elements": { 4126 Schema: map[string]*Schema{ 4127 "ingress": &Schema{ 4128 Type: TypeList, 4129 Optional: true, 4130 Elem: &Resource{ 4131 Schema: map[string]*Schema{ 4132 "port": &Schema{ 4133 Type: TypeInt, 4134 Required: true, 4135 }, 4136 }, 4137 }, 4138 }, 4139 }, 4140 4141 Config: map[string]interface{}{ 4142 "ingress": []interface{}{ 4143 map[string]interface{}{ 4144 "port": 80, 4145 "other": "yes", 4146 }, 4147 }, 4148 }, 4149 4150 Err: true, 4151 }, 4152 4153 "Bad, subresource should not allow invalid types": { 4154 Schema: map[string]*Schema{ 4155 "ingress": &Schema{ 4156 Type: TypeList, 4157 Optional: true, 4158 Elem: &Resource{ 4159 Schema: map[string]*Schema{ 4160 "port": &Schema{ 4161 Type: TypeInt, 4162 Required: true, 4163 }, 4164 }, 4165 }, 4166 }, 4167 }, 4168 4169 Config: map[string]interface{}{ 4170 "ingress": []interface{}{ 4171 map[string]interface{}{ 4172 "port": "bad", 4173 }, 4174 }, 4175 }, 4176 4177 Err: true, 4178 }, 4179 4180 "Bad, should not allow lists to be assigned to string attributes": { 4181 Schema: map[string]*Schema{ 4182 "availability_zone": &Schema{ 4183 Type: TypeString, 4184 Required: true, 4185 }, 4186 }, 4187 4188 Config: map[string]interface{}{ 4189 "availability_zone": []interface{}{"foo", "bar", "baz"}, 4190 }, 4191 4192 Err: true, 4193 }, 4194 4195 "Bad, should not allow maps to be assigned to string attributes": { 4196 Schema: map[string]*Schema{ 4197 "availability_zone": &Schema{ 4198 Type: TypeString, 4199 Required: true, 4200 }, 4201 }, 4202 4203 Config: map[string]interface{}{ 4204 "availability_zone": map[string]interface{}{"foo": "bar", "baz": "thing"}, 4205 }, 4206 4207 Err: true, 4208 }, 4209 4210 "Deprecated attribute usage generates warning, but not error": { 4211 Schema: map[string]*Schema{ 4212 "old_news": &Schema{ 4213 Type: TypeString, 4214 Optional: true, 4215 Deprecated: "please use 'new_news' instead", 4216 }, 4217 }, 4218 4219 Config: map[string]interface{}{ 4220 "old_news": "extra extra!", 4221 }, 4222 4223 Err: false, 4224 4225 Warnings: []string{ 4226 "\"old_news\": [DEPRECATED] please use 'new_news' instead", 4227 }, 4228 }, 4229 4230 "Deprecated generates no warnings if attr not used": { 4231 Schema: map[string]*Schema{ 4232 "old_news": &Schema{ 4233 Type: TypeString, 4234 Optional: true, 4235 Deprecated: "please use 'new_news' instead", 4236 }, 4237 }, 4238 4239 Err: false, 4240 4241 Warnings: nil, 4242 }, 4243 4244 "Removed attribute usage generates error": { 4245 Schema: map[string]*Schema{ 4246 "long_gone": &Schema{ 4247 Type: TypeString, 4248 Optional: true, 4249 Removed: "no longer supported by Cloud API", 4250 }, 4251 }, 4252 4253 Config: map[string]interface{}{ 4254 "long_gone": "still here!", 4255 }, 4256 4257 Err: true, 4258 Errors: []error{ 4259 fmt.Errorf("\"long_gone\": [REMOVED] no longer supported by Cloud API"), 4260 }, 4261 }, 4262 4263 "Removed generates no errors if attr not used": { 4264 Schema: map[string]*Schema{ 4265 "long_gone": &Schema{ 4266 Type: TypeString, 4267 Optional: true, 4268 Removed: "no longer supported by Cloud API", 4269 }, 4270 }, 4271 4272 Err: false, 4273 }, 4274 4275 "Conflicting attributes generate error": { 4276 Schema: map[string]*Schema{ 4277 "whitelist": &Schema{ 4278 Type: TypeString, 4279 Optional: true, 4280 }, 4281 "blacklist": &Schema{ 4282 Type: TypeString, 4283 Optional: true, 4284 ConflictsWith: []string{"whitelist"}, 4285 }, 4286 }, 4287 4288 Config: map[string]interface{}{ 4289 "whitelist": "white-val", 4290 "blacklist": "black-val", 4291 }, 4292 4293 Err: true, 4294 Errors: []error{ 4295 fmt.Errorf("\"blacklist\": conflicts with whitelist (\"white-val\")"), 4296 }, 4297 }, 4298 4299 "Required attribute & undefined conflicting optional are good": { 4300 Schema: map[string]*Schema{ 4301 "required_att": &Schema{ 4302 Type: TypeString, 4303 Required: true, 4304 }, 4305 "optional_att": &Schema{ 4306 Type: TypeString, 4307 Optional: true, 4308 ConflictsWith: []string{"required_att"}, 4309 }, 4310 }, 4311 4312 Config: map[string]interface{}{ 4313 "required_att": "required-val", 4314 }, 4315 4316 Err: false, 4317 }, 4318 4319 "Required conflicting attribute & defined optional generate error": { 4320 Schema: map[string]*Schema{ 4321 "required_att": &Schema{ 4322 Type: TypeString, 4323 Required: true, 4324 }, 4325 "optional_att": &Schema{ 4326 Type: TypeString, 4327 Optional: true, 4328 ConflictsWith: []string{"required_att"}, 4329 }, 4330 }, 4331 4332 Config: map[string]interface{}{ 4333 "required_att": "required-val", 4334 "optional_att": "optional-val", 4335 }, 4336 4337 Err: true, 4338 Errors: []error{ 4339 fmt.Errorf(`"optional_att": conflicts with required_att ("required-val")`), 4340 }, 4341 }, 4342 4343 "Computed + Optional fields conflicting with each other": { 4344 Schema: map[string]*Schema{ 4345 "foo_att": &Schema{ 4346 Type: TypeString, 4347 Optional: true, 4348 Computed: true, 4349 ConflictsWith: []string{"bar_att"}, 4350 }, 4351 "bar_att": &Schema{ 4352 Type: TypeString, 4353 Optional: true, 4354 Computed: true, 4355 ConflictsWith: []string{"foo_att"}, 4356 }, 4357 }, 4358 4359 Config: map[string]interface{}{ 4360 "foo_att": "foo-val", 4361 "bar_att": "bar-val", 4362 }, 4363 4364 Err: true, 4365 Errors: []error{ 4366 fmt.Errorf(`"foo_att": conflicts with bar_att ("bar-val")`), 4367 fmt.Errorf(`"bar_att": conflicts with foo_att ("foo-val")`), 4368 }, 4369 }, 4370 4371 "Computed + Optional fields NOT conflicting with each other": { 4372 Schema: map[string]*Schema{ 4373 "foo_att": &Schema{ 4374 Type: TypeString, 4375 Optional: true, 4376 Computed: true, 4377 ConflictsWith: []string{"bar_att"}, 4378 }, 4379 "bar_att": &Schema{ 4380 Type: TypeString, 4381 Optional: true, 4382 Computed: true, 4383 ConflictsWith: []string{"foo_att"}, 4384 }, 4385 }, 4386 4387 Config: map[string]interface{}{ 4388 "foo_att": "foo-val", 4389 }, 4390 4391 Err: false, 4392 }, 4393 4394 "Computed + Optional fields that conflict with none set": { 4395 Schema: map[string]*Schema{ 4396 "foo_att": &Schema{ 4397 Type: TypeString, 4398 Optional: true, 4399 Computed: true, 4400 ConflictsWith: []string{"bar_att"}, 4401 }, 4402 "bar_att": &Schema{ 4403 Type: TypeString, 4404 Optional: true, 4405 Computed: true, 4406 ConflictsWith: []string{"foo_att"}, 4407 }, 4408 }, 4409 4410 Config: map[string]interface{}{}, 4411 4412 Err: false, 4413 }, 4414 4415 "Good with ValidateFunc": { 4416 Schema: map[string]*Schema{ 4417 "validate_me": &Schema{ 4418 Type: TypeString, 4419 Required: true, 4420 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4421 return 4422 }, 4423 }, 4424 }, 4425 Config: map[string]interface{}{ 4426 "validate_me": "valid", 4427 }, 4428 Err: false, 4429 }, 4430 4431 "Bad with ValidateFunc": { 4432 Schema: map[string]*Schema{ 4433 "validate_me": &Schema{ 4434 Type: TypeString, 4435 Required: true, 4436 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4437 es = append(es, fmt.Errorf("something is not right here")) 4438 return 4439 }, 4440 }, 4441 }, 4442 Config: map[string]interface{}{ 4443 "validate_me": "invalid", 4444 }, 4445 Err: true, 4446 Errors: []error{ 4447 fmt.Errorf(`something is not right here`), 4448 }, 4449 }, 4450 4451 "ValidateFunc not called when type does not match": { 4452 Schema: map[string]*Schema{ 4453 "number": &Schema{ 4454 Type: TypeInt, 4455 Required: true, 4456 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4457 t.Fatalf("Should not have gotten validate call") 4458 return 4459 }, 4460 }, 4461 }, 4462 Config: map[string]interface{}{ 4463 "number": "NaN", 4464 }, 4465 Err: true, 4466 }, 4467 4468 "ValidateFunc gets decoded type": { 4469 Schema: map[string]*Schema{ 4470 "maybe": &Schema{ 4471 Type: TypeBool, 4472 Required: true, 4473 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4474 if _, ok := v.(bool); !ok { 4475 t.Fatalf("Expected bool, got: %#v", v) 4476 } 4477 return 4478 }, 4479 }, 4480 }, 4481 Config: map[string]interface{}{ 4482 "maybe": "true", 4483 }, 4484 }, 4485 4486 "ValidateFunc is not called with a computed value": { 4487 Schema: map[string]*Schema{ 4488 "validate_me": &Schema{ 4489 Type: TypeString, 4490 Required: true, 4491 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4492 es = append(es, fmt.Errorf("something is not right here")) 4493 return 4494 }, 4495 }, 4496 }, 4497 Config: map[string]interface{}{ 4498 "validate_me": "${var.foo}", 4499 }, 4500 Vars: map[string]string{ 4501 "var.foo": config.UnknownVariableValue, 4502 }, 4503 4504 Err: false, 4505 }, 4506 4507 "special timeouts field": { 4508 Schema: map[string]*Schema{ 4509 "availability_zone": &Schema{ 4510 Type: TypeString, 4511 Optional: true, 4512 Computed: true, 4513 ForceNew: true, 4514 }, 4515 }, 4516 4517 Config: map[string]interface{}{ 4518 TimeoutsConfigKey: "bar", 4519 }, 4520 4521 Err: false, 4522 }, 4523 4524 "invalid bool field": { 4525 Schema: map[string]*Schema{ 4526 "bool_field": { 4527 Type: TypeBool, 4528 Optional: true, 4529 }, 4530 }, 4531 Config: map[string]interface{}{ 4532 "bool_field": "abcdef", 4533 }, 4534 Err: true, 4535 }, 4536 "invalid integer field": { 4537 Schema: map[string]*Schema{ 4538 "integer_field": { 4539 Type: TypeInt, 4540 Optional: true, 4541 }, 4542 }, 4543 Config: map[string]interface{}{ 4544 "integer_field": "abcdef", 4545 }, 4546 Err: true, 4547 }, 4548 "invalid float field": { 4549 Schema: map[string]*Schema{ 4550 "float_field": { 4551 Type: TypeFloat, 4552 Optional: true, 4553 }, 4554 }, 4555 Config: map[string]interface{}{ 4556 "float_field": "abcdef", 4557 }, 4558 Err: true, 4559 }, 4560 4561 // Invalid map values 4562 "invalid bool map value": { 4563 Schema: map[string]*Schema{ 4564 "boolMap": &Schema{ 4565 Type: TypeMap, 4566 Elem: TypeBool, 4567 Optional: true, 4568 }, 4569 }, 4570 Config: map[string]interface{}{ 4571 "boolMap": map[string]interface{}{ 4572 "boolField": "notbool", 4573 }, 4574 }, 4575 Err: true, 4576 }, 4577 "invalid int map value": { 4578 Schema: map[string]*Schema{ 4579 "intMap": &Schema{ 4580 Type: TypeMap, 4581 Elem: TypeInt, 4582 Optional: true, 4583 }, 4584 }, 4585 Config: map[string]interface{}{ 4586 "intMap": map[string]interface{}{ 4587 "intField": "notInt", 4588 }, 4589 }, 4590 Err: true, 4591 }, 4592 "invalid float map value": { 4593 Schema: map[string]*Schema{ 4594 "floatMap": &Schema{ 4595 Type: TypeMap, 4596 Elem: TypeFloat, 4597 Optional: true, 4598 }, 4599 }, 4600 Config: map[string]interface{}{ 4601 "floatMap": map[string]interface{}{ 4602 "floatField": "notFloat", 4603 }, 4604 }, 4605 Err: true, 4606 }, 4607 4608 "map with positive validate function": { 4609 Schema: map[string]*Schema{ 4610 "floatInt": &Schema{ 4611 Type: TypeMap, 4612 Elem: TypeInt, 4613 Optional: true, 4614 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4615 return 4616 }, 4617 }, 4618 }, 4619 Config: map[string]interface{}{ 4620 "floatInt": map[string]interface{}{ 4621 "rightAnswer": "42", 4622 "tooMuch": "43", 4623 }, 4624 }, 4625 Err: false, 4626 }, 4627 "map with negative validate function": { 4628 Schema: map[string]*Schema{ 4629 "floatInt": &Schema{ 4630 Type: TypeMap, 4631 Elem: TypeInt, 4632 Optional: true, 4633 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4634 es = append(es, fmt.Errorf("this is not fine")) 4635 return 4636 }, 4637 }, 4638 }, 4639 Config: map[string]interface{}{ 4640 "floatInt": map[string]interface{}{ 4641 "rightAnswer": "42", 4642 "tooMuch": "43", 4643 }, 4644 }, 4645 Err: true, 4646 }, 4647 4648 // The Validation function should not see interpolation strings from 4649 // non-computed values. 4650 "set with partially computed list and map": { 4651 Schema: map[string]*Schema{ 4652 "outer": &Schema{ 4653 Type: TypeSet, 4654 Optional: true, 4655 Computed: true, 4656 Elem: &Resource{ 4657 Schema: map[string]*Schema{ 4658 "list": &Schema{ 4659 Type: TypeList, 4660 Optional: true, 4661 Elem: &Schema{ 4662 Type: TypeString, 4663 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4664 if strings.HasPrefix(v.(string), "${") { 4665 es = append(es, fmt.Errorf("should not have interpolations")) 4666 } 4667 return 4668 }, 4669 }, 4670 }, 4671 }, 4672 }, 4673 }, 4674 }, 4675 Config: map[string]interface{}{ 4676 "outer": []map[string]interface{}{ 4677 { 4678 "list": []interface{}{"${var.a}", "${var.b}", "c"}, 4679 }, 4680 }, 4681 }, 4682 Vars: map[string]string{ 4683 "var.a": "A", 4684 "var.b": config.UnknownVariableValue, 4685 }, 4686 Err: false, 4687 }, 4688 } 4689 4690 for tn, tc := range cases { 4691 t.Run(tn, func(t *testing.T) { 4692 c, err := config.NewRawConfig(tc.Config) 4693 if err != nil { 4694 t.Fatalf("err: %s", err) 4695 } 4696 if tc.Vars != nil { 4697 vars := make(map[string]ast.Variable) 4698 for k, v := range tc.Vars { 4699 vars[k] = ast.Variable{Value: v, Type: ast.TypeString} 4700 } 4701 4702 if err := c.Interpolate(vars); err != nil { 4703 t.Fatalf("err: %s", err) 4704 } 4705 } 4706 4707 ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) 4708 if len(es) > 0 != tc.Err { 4709 if len(es) == 0 { 4710 t.Errorf("%q: no errors", tn) 4711 } 4712 4713 for _, e := range es { 4714 t.Errorf("%q: err: %s", tn, e) 4715 } 4716 4717 t.FailNow() 4718 } 4719 4720 if !reflect.DeepEqual(ws, tc.Warnings) { 4721 t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws) 4722 } 4723 4724 if tc.Errors != nil { 4725 sort.Sort(errorSort(es)) 4726 sort.Sort(errorSort(tc.Errors)) 4727 4728 if !reflect.DeepEqual(es, tc.Errors) { 4729 t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es) 4730 } 4731 } 4732 }) 4733 4734 } 4735 } 4736 4737 func TestSchemaSet_ValidateMaxItems(t *testing.T) { 4738 cases := map[string]struct { 4739 Schema map[string]*Schema 4740 State *terraform.InstanceState 4741 Config map[string]interface{} 4742 ConfigVariables map[string]string 4743 Diff *terraform.InstanceDiff 4744 Err bool 4745 Errors []error 4746 }{ 4747 "#0": { 4748 Schema: map[string]*Schema{ 4749 "aliases": &Schema{ 4750 Type: TypeSet, 4751 Optional: true, 4752 MaxItems: 1, 4753 Elem: &Schema{Type: TypeString}, 4754 }, 4755 }, 4756 State: nil, 4757 Config: map[string]interface{}{ 4758 "aliases": []interface{}{"foo", "bar"}, 4759 }, 4760 Diff: nil, 4761 Err: true, 4762 Errors: []error{ 4763 fmt.Errorf("aliases: attribute supports 1 item maximum, config has 2 declared"), 4764 }, 4765 }, 4766 "#1": { 4767 Schema: map[string]*Schema{ 4768 "aliases": &Schema{ 4769 Type: TypeSet, 4770 Optional: true, 4771 Elem: &Schema{Type: TypeString}, 4772 }, 4773 }, 4774 State: nil, 4775 Config: map[string]interface{}{ 4776 "aliases": []interface{}{"foo", "bar"}, 4777 }, 4778 Diff: nil, 4779 Err: false, 4780 Errors: nil, 4781 }, 4782 "#2": { 4783 Schema: map[string]*Schema{ 4784 "aliases": &Schema{ 4785 Type: TypeSet, 4786 Optional: true, 4787 MaxItems: 1, 4788 Elem: &Schema{Type: TypeString}, 4789 }, 4790 }, 4791 State: nil, 4792 Config: map[string]interface{}{ 4793 "aliases": []interface{}{"foo"}, 4794 }, 4795 Diff: nil, 4796 Err: false, 4797 Errors: nil, 4798 }, 4799 } 4800 4801 for tn, tc := range cases { 4802 c, err := config.NewRawConfig(tc.Config) 4803 if err != nil { 4804 t.Fatalf("%q: err: %s", tn, err) 4805 } 4806 _, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) 4807 4808 if len(es) > 0 != tc.Err { 4809 if len(es) == 0 { 4810 t.Errorf("%q: no errors", tn) 4811 } 4812 4813 for _, e := range es { 4814 t.Errorf("%q: err: %s", tn, e) 4815 } 4816 4817 t.FailNow() 4818 } 4819 4820 if tc.Errors != nil { 4821 if !reflect.DeepEqual(es, tc.Errors) { 4822 t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es) 4823 } 4824 } 4825 } 4826 } 4827 4828 func TestSchemaSet_ValidateMinItems(t *testing.T) { 4829 cases := map[string]struct { 4830 Schema map[string]*Schema 4831 State *terraform.InstanceState 4832 Config map[string]interface{} 4833 ConfigVariables map[string]string 4834 Diff *terraform.InstanceDiff 4835 Err bool 4836 Errors []error 4837 }{ 4838 "#0": { 4839 Schema: map[string]*Schema{ 4840 "aliases": &Schema{ 4841 Type: TypeSet, 4842 Optional: true, 4843 MinItems: 2, 4844 Elem: &Schema{Type: TypeString}, 4845 }, 4846 }, 4847 State: nil, 4848 Config: map[string]interface{}{ 4849 "aliases": []interface{}{"foo", "bar"}, 4850 }, 4851 Diff: nil, 4852 Err: false, 4853 Errors: nil, 4854 }, 4855 "#1": { 4856 Schema: map[string]*Schema{ 4857 "aliases": &Schema{ 4858 Type: TypeSet, 4859 Optional: true, 4860 Elem: &Schema{Type: TypeString}, 4861 }, 4862 }, 4863 State: nil, 4864 Config: map[string]interface{}{ 4865 "aliases": []interface{}{"foo", "bar"}, 4866 }, 4867 Diff: nil, 4868 Err: false, 4869 Errors: nil, 4870 }, 4871 "#2": { 4872 Schema: map[string]*Schema{ 4873 "aliases": &Schema{ 4874 Type: TypeSet, 4875 Optional: true, 4876 MinItems: 2, 4877 Elem: &Schema{Type: TypeString}, 4878 }, 4879 }, 4880 State: nil, 4881 Config: map[string]interface{}{ 4882 "aliases": []interface{}{"foo"}, 4883 }, 4884 Diff: nil, 4885 Err: true, 4886 Errors: []error{ 4887 fmt.Errorf("aliases: attribute supports 2 item as a minimum, config has 1 declared"), 4888 }, 4889 }, 4890 } 4891 4892 for tn, tc := range cases { 4893 c, err := config.NewRawConfig(tc.Config) 4894 if err != nil { 4895 t.Fatalf("%q: err: %s", tn, err) 4896 } 4897 _, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) 4898 4899 if len(es) > 0 != tc.Err { 4900 if len(es) == 0 { 4901 t.Errorf("%q: no errors", tn) 4902 } 4903 4904 for _, e := range es { 4905 t.Errorf("%q: err: %s", tn, e) 4906 } 4907 4908 t.FailNow() 4909 } 4910 4911 if tc.Errors != nil { 4912 if !reflect.DeepEqual(es, tc.Errors) { 4913 t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es) 4914 } 4915 } 4916 } 4917 } 4918 4919 // errorSort implements sort.Interface to sort errors by their error message 4920 type errorSort []error 4921 4922 func (e errorSort) Len() int { return len(e) } 4923 func (e errorSort) Swap(i, j int) { e[i], e[j] = e[j], e[i] } 4924 func (e errorSort) Less(i, j int) bool { 4925 return e[i].Error() < e[j].Error() 4926 }