github.com/pgray/terraform@v0.5.4-0.20170822184730-b6a464c5214d/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 "invalid field name format #1": { 3358 map[string]*Schema{ 3359 "with space": &Schema{ 3360 Type: TypeString, 3361 Optional: true, 3362 }, 3363 }, 3364 true, 3365 }, 3366 3367 "invalid field name format #2": { 3368 map[string]*Schema{ 3369 "WithCapitals": &Schema{ 3370 Type: TypeString, 3371 Optional: true, 3372 }, 3373 }, 3374 true, 3375 }, 3376 3377 "invalid field name format of a Deprecated field": { 3378 map[string]*Schema{ 3379 "WithCapitals": &Schema{ 3380 Type: TypeString, 3381 Optional: true, 3382 Deprecated: "Use with_underscores instead", 3383 }, 3384 }, 3385 false, 3386 }, 3387 3388 "invalid field name format of a Removed field": { 3389 map[string]*Schema{ 3390 "WithCapitals": &Schema{ 3391 Type: TypeString, 3392 Optional: true, 3393 Removed: "Use with_underscores instead", 3394 }, 3395 }, 3396 false, 3397 }, 3398 } 3399 3400 for tn, tc := range cases { 3401 t.Run(tn, func(t *testing.T) { 3402 err := schemaMap(tc.In).InternalValidate(nil) 3403 if err != nil != tc.Err { 3404 if tc.Err { 3405 t.Fatalf("%q: Expected error did not occur:\n\n%#v", tn, tc.In) 3406 } 3407 t.Fatalf("%q: Unexpected error occurred: %s\n\n%#v", tn, err, tc.In) 3408 } 3409 }) 3410 } 3411 3412 } 3413 3414 func TestSchemaMap_DiffSuppress(t *testing.T) { 3415 cases := map[string]struct { 3416 Schema map[string]*Schema 3417 State *terraform.InstanceState 3418 Config map[string]interface{} 3419 ConfigVariables map[string]ast.Variable 3420 ExpectedDiff *terraform.InstanceDiff 3421 Err bool 3422 }{ 3423 "#0 - Suppress otherwise valid diff by returning true": { 3424 Schema: map[string]*Schema{ 3425 "availability_zone": { 3426 Type: TypeString, 3427 Optional: true, 3428 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3429 // Always suppress any diff 3430 return true 3431 }, 3432 }, 3433 }, 3434 3435 State: nil, 3436 3437 Config: map[string]interface{}{ 3438 "availability_zone": "foo", 3439 }, 3440 3441 ExpectedDiff: nil, 3442 3443 Err: false, 3444 }, 3445 3446 "#1 - Don't suppress diff by returning false": { 3447 Schema: map[string]*Schema{ 3448 "availability_zone": { 3449 Type: TypeString, 3450 Optional: true, 3451 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3452 // Always suppress any diff 3453 return false 3454 }, 3455 }, 3456 }, 3457 3458 State: nil, 3459 3460 Config: map[string]interface{}{ 3461 "availability_zone": "foo", 3462 }, 3463 3464 ExpectedDiff: &terraform.InstanceDiff{ 3465 Attributes: map[string]*terraform.ResourceAttrDiff{ 3466 "availability_zone": { 3467 Old: "", 3468 New: "foo", 3469 }, 3470 }, 3471 }, 3472 3473 Err: false, 3474 }, 3475 3476 "Default with suppress makes no diff": { 3477 Schema: map[string]*Schema{ 3478 "availability_zone": { 3479 Type: TypeString, 3480 Optional: true, 3481 Default: "foo", 3482 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3483 return true 3484 }, 3485 }, 3486 }, 3487 3488 State: nil, 3489 3490 Config: map[string]interface{}{}, 3491 3492 ExpectedDiff: nil, 3493 3494 Err: false, 3495 }, 3496 3497 "Default with false suppress makes diff": { 3498 Schema: map[string]*Schema{ 3499 "availability_zone": { 3500 Type: TypeString, 3501 Optional: true, 3502 Default: "foo", 3503 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3504 return false 3505 }, 3506 }, 3507 }, 3508 3509 State: nil, 3510 3511 Config: map[string]interface{}{}, 3512 3513 ExpectedDiff: &terraform.InstanceDiff{ 3514 Attributes: map[string]*terraform.ResourceAttrDiff{ 3515 "availability_zone": { 3516 Old: "", 3517 New: "foo", 3518 }, 3519 }, 3520 }, 3521 3522 Err: false, 3523 }, 3524 3525 "Complex structure with set of computed string should mark root set as computed": { 3526 Schema: map[string]*Schema{ 3527 "outer": &Schema{ 3528 Type: TypeSet, 3529 Optional: true, 3530 Elem: &Resource{ 3531 Schema: map[string]*Schema{ 3532 "outer_str": &Schema{ 3533 Type: TypeString, 3534 Optional: true, 3535 }, 3536 "inner": &Schema{ 3537 Type: TypeSet, 3538 Optional: true, 3539 Elem: &Resource{ 3540 Schema: map[string]*Schema{ 3541 "inner_str": &Schema{ 3542 Type: TypeString, 3543 Optional: true, 3544 }, 3545 }, 3546 }, 3547 Set: func(v interface{}) int { 3548 return 2 3549 }, 3550 }, 3551 }, 3552 }, 3553 Set: func(v interface{}) int { 3554 return 1 3555 }, 3556 }, 3557 }, 3558 3559 State: nil, 3560 3561 Config: map[string]interface{}{ 3562 "outer": []map[string]interface{}{ 3563 map[string]interface{}{ 3564 "outer_str": "foo", 3565 "inner": []map[string]interface{}{ 3566 map[string]interface{}{ 3567 "inner_str": "${var.bar}", 3568 }, 3569 }, 3570 }, 3571 }, 3572 }, 3573 3574 ConfigVariables: map[string]ast.Variable{ 3575 "var.bar": interfaceToVariableSwallowError(config.UnknownVariableValue), 3576 }, 3577 3578 ExpectedDiff: &terraform.InstanceDiff{ 3579 Attributes: map[string]*terraform.ResourceAttrDiff{ 3580 "outer.#": &terraform.ResourceAttrDiff{ 3581 Old: "0", 3582 New: "1", 3583 }, 3584 "outer.~1.outer_str": &terraform.ResourceAttrDiff{ 3585 Old: "", 3586 New: "foo", 3587 }, 3588 "outer.~1.inner.#": &terraform.ResourceAttrDiff{ 3589 Old: "0", 3590 New: "1", 3591 }, 3592 "outer.~1.inner.~2.inner_str": &terraform.ResourceAttrDiff{ 3593 Old: "", 3594 New: "${var.bar}", 3595 NewComputed: true, 3596 }, 3597 }, 3598 }, 3599 3600 Err: false, 3601 }, 3602 3603 "Complex structure with complex list of computed string should mark root set as computed": { 3604 Schema: map[string]*Schema{ 3605 "outer": &Schema{ 3606 Type: TypeSet, 3607 Optional: true, 3608 Elem: &Resource{ 3609 Schema: map[string]*Schema{ 3610 "outer_str": &Schema{ 3611 Type: TypeString, 3612 Optional: true, 3613 }, 3614 "inner": &Schema{ 3615 Type: TypeList, 3616 Optional: true, 3617 Elem: &Resource{ 3618 Schema: map[string]*Schema{ 3619 "inner_str": &Schema{ 3620 Type: TypeString, 3621 Optional: true, 3622 }, 3623 }, 3624 }, 3625 }, 3626 }, 3627 }, 3628 Set: func(v interface{}) int { 3629 return 1 3630 }, 3631 }, 3632 }, 3633 3634 State: nil, 3635 3636 Config: map[string]interface{}{ 3637 "outer": []map[string]interface{}{ 3638 map[string]interface{}{ 3639 "outer_str": "foo", 3640 "inner": []map[string]interface{}{ 3641 map[string]interface{}{ 3642 "inner_str": "${var.bar}", 3643 }, 3644 }, 3645 }, 3646 }, 3647 }, 3648 3649 ConfigVariables: map[string]ast.Variable{ 3650 "var.bar": interfaceToVariableSwallowError(config.UnknownVariableValue), 3651 }, 3652 3653 ExpectedDiff: &terraform.InstanceDiff{ 3654 Attributes: map[string]*terraform.ResourceAttrDiff{ 3655 "outer.#": &terraform.ResourceAttrDiff{ 3656 Old: "0", 3657 New: "1", 3658 }, 3659 "outer.~1.outer_str": &terraform.ResourceAttrDiff{ 3660 Old: "", 3661 New: "foo", 3662 }, 3663 "outer.~1.inner.#": &terraform.ResourceAttrDiff{ 3664 Old: "0", 3665 New: "1", 3666 }, 3667 "outer.~1.inner.0.inner_str": &terraform.ResourceAttrDiff{ 3668 Old: "", 3669 New: "${var.bar}", 3670 NewComputed: true, 3671 }, 3672 }, 3673 }, 3674 3675 Err: false, 3676 }, 3677 } 3678 3679 for tn, tc := range cases { 3680 t.Run(tn, func(t *testing.T) { 3681 c, err := config.NewRawConfig(tc.Config) 3682 if err != nil { 3683 t.Fatalf("#%q err: %s", tn, err) 3684 } 3685 3686 if len(tc.ConfigVariables) > 0 { 3687 if err := c.Interpolate(tc.ConfigVariables); err != nil { 3688 t.Fatalf("#%q err: %s", tn, err) 3689 } 3690 } 3691 3692 d, err := schemaMap(tc.Schema).Diff( 3693 tc.State, terraform.NewResourceConfig(c)) 3694 if err != nil != tc.Err { 3695 t.Fatalf("#%q err: %s", tn, err) 3696 } 3697 3698 if !reflect.DeepEqual(tc.ExpectedDiff, d) { 3699 t.Fatalf("#%q:\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.ExpectedDiff, d) 3700 } 3701 }) 3702 } 3703 } 3704 3705 func TestSchemaMap_Validate(t *testing.T) { 3706 cases := map[string]struct { 3707 Schema map[string]*Schema 3708 Config map[string]interface{} 3709 Vars map[string]string 3710 Err bool 3711 Errors []error 3712 Warnings []string 3713 }{ 3714 "Good": { 3715 Schema: map[string]*Schema{ 3716 "availability_zone": &Schema{ 3717 Type: TypeString, 3718 Optional: true, 3719 Computed: true, 3720 ForceNew: true, 3721 }, 3722 }, 3723 3724 Config: map[string]interface{}{ 3725 "availability_zone": "foo", 3726 }, 3727 }, 3728 3729 "Good, because the var is not set and that error will come elsewhere": { 3730 Schema: map[string]*Schema{ 3731 "size": &Schema{ 3732 Type: TypeInt, 3733 Required: true, 3734 }, 3735 }, 3736 3737 Config: map[string]interface{}{ 3738 "size": "${var.foo}", 3739 }, 3740 3741 Vars: map[string]string{ 3742 "var.foo": config.UnknownVariableValue, 3743 }, 3744 }, 3745 3746 "Required field not set": { 3747 Schema: map[string]*Schema{ 3748 "availability_zone": &Schema{ 3749 Type: TypeString, 3750 Required: true, 3751 }, 3752 }, 3753 3754 Config: map[string]interface{}{}, 3755 3756 Err: true, 3757 }, 3758 3759 "Invalid basic type": { 3760 Schema: map[string]*Schema{ 3761 "port": &Schema{ 3762 Type: TypeInt, 3763 Required: true, 3764 }, 3765 }, 3766 3767 Config: map[string]interface{}{ 3768 "port": "I am invalid", 3769 }, 3770 3771 Err: true, 3772 }, 3773 3774 "Invalid complex type": { 3775 Schema: map[string]*Schema{ 3776 "user_data": &Schema{ 3777 Type: TypeString, 3778 Optional: true, 3779 }, 3780 }, 3781 3782 Config: map[string]interface{}{ 3783 "user_data": []interface{}{ 3784 map[string]interface{}{ 3785 "foo": "bar", 3786 }, 3787 }, 3788 }, 3789 3790 Err: true, 3791 }, 3792 3793 "Bad type, interpolated": { 3794 Schema: map[string]*Schema{ 3795 "size": &Schema{ 3796 Type: TypeInt, 3797 Required: true, 3798 }, 3799 }, 3800 3801 Config: map[string]interface{}{ 3802 "size": "${var.foo}", 3803 }, 3804 3805 Vars: map[string]string{ 3806 "var.foo": "nope", 3807 }, 3808 3809 Err: true, 3810 }, 3811 3812 "Required but has DefaultFunc": { 3813 Schema: map[string]*Schema{ 3814 "availability_zone": &Schema{ 3815 Type: TypeString, 3816 Required: true, 3817 DefaultFunc: func() (interface{}, error) { 3818 return "foo", nil 3819 }, 3820 }, 3821 }, 3822 3823 Config: nil, 3824 }, 3825 3826 "Required but has DefaultFunc return nil": { 3827 Schema: map[string]*Schema{ 3828 "availability_zone": &Schema{ 3829 Type: TypeString, 3830 Required: true, 3831 DefaultFunc: func() (interface{}, error) { 3832 return nil, nil 3833 }, 3834 }, 3835 }, 3836 3837 Config: nil, 3838 3839 Err: true, 3840 }, 3841 3842 "List with promotion": { 3843 Schema: map[string]*Schema{ 3844 "ingress": &Schema{ 3845 Type: TypeList, 3846 Elem: &Schema{Type: TypeInt}, 3847 PromoteSingle: true, 3848 Optional: true, 3849 }, 3850 }, 3851 3852 Config: map[string]interface{}{ 3853 "ingress": "5", 3854 }, 3855 3856 Err: false, 3857 }, 3858 3859 "List with promotion set as list": { 3860 Schema: map[string]*Schema{ 3861 "ingress": &Schema{ 3862 Type: TypeList, 3863 Elem: &Schema{Type: TypeInt}, 3864 PromoteSingle: true, 3865 Optional: true, 3866 }, 3867 }, 3868 3869 Config: map[string]interface{}{ 3870 "ingress": []interface{}{"5"}, 3871 }, 3872 3873 Err: false, 3874 }, 3875 3876 "Optional sub-resource": { 3877 Schema: map[string]*Schema{ 3878 "ingress": &Schema{ 3879 Type: TypeList, 3880 Elem: &Resource{ 3881 Schema: map[string]*Schema{ 3882 "from": &Schema{ 3883 Type: TypeInt, 3884 Required: true, 3885 }, 3886 }, 3887 }, 3888 }, 3889 }, 3890 3891 Config: map[string]interface{}{}, 3892 3893 Err: false, 3894 }, 3895 3896 "Sub-resource is the wrong type": { 3897 Schema: map[string]*Schema{ 3898 "ingress": &Schema{ 3899 Type: TypeList, 3900 Required: true, 3901 Elem: &Resource{ 3902 Schema: map[string]*Schema{ 3903 "from": &Schema{ 3904 Type: TypeInt, 3905 Required: true, 3906 }, 3907 }, 3908 }, 3909 }, 3910 }, 3911 3912 Config: map[string]interface{}{ 3913 "ingress": []interface{}{"foo"}, 3914 }, 3915 3916 Err: true, 3917 }, 3918 3919 "Not a list": { 3920 Schema: map[string]*Schema{ 3921 "ingress": &Schema{ 3922 Type: TypeList, 3923 Elem: &Resource{ 3924 Schema: map[string]*Schema{ 3925 "from": &Schema{ 3926 Type: TypeInt, 3927 Required: true, 3928 }, 3929 }, 3930 }, 3931 }, 3932 }, 3933 3934 Config: map[string]interface{}{ 3935 "ingress": "foo", 3936 }, 3937 3938 Err: true, 3939 }, 3940 3941 "Required sub-resource field": { 3942 Schema: map[string]*Schema{ 3943 "ingress": &Schema{ 3944 Type: TypeList, 3945 Elem: &Resource{ 3946 Schema: map[string]*Schema{ 3947 "from": &Schema{ 3948 Type: TypeInt, 3949 Required: true, 3950 }, 3951 }, 3952 }, 3953 }, 3954 }, 3955 3956 Config: map[string]interface{}{ 3957 "ingress": []interface{}{ 3958 map[string]interface{}{}, 3959 }, 3960 }, 3961 3962 Err: true, 3963 }, 3964 3965 "Good sub-resource": { 3966 Schema: map[string]*Schema{ 3967 "ingress": &Schema{ 3968 Type: TypeList, 3969 Optional: true, 3970 Elem: &Resource{ 3971 Schema: map[string]*Schema{ 3972 "from": &Schema{ 3973 Type: TypeInt, 3974 Required: true, 3975 }, 3976 }, 3977 }, 3978 }, 3979 }, 3980 3981 Config: map[string]interface{}{ 3982 "ingress": []interface{}{ 3983 map[string]interface{}{ 3984 "from": 80, 3985 }, 3986 }, 3987 }, 3988 3989 Err: false, 3990 }, 3991 3992 "Good sub-resource, interpolated value": { 3993 Schema: map[string]*Schema{ 3994 "ingress": &Schema{ 3995 Type: TypeList, 3996 Optional: true, 3997 Elem: &Resource{ 3998 Schema: map[string]*Schema{ 3999 "from": &Schema{ 4000 Type: TypeInt, 4001 Required: true, 4002 }, 4003 }, 4004 }, 4005 }, 4006 }, 4007 4008 Config: map[string]interface{}{ 4009 "ingress": []interface{}{ 4010 `${map("from", "80")}`, 4011 }, 4012 }, 4013 4014 Vars: map[string]string{}, 4015 4016 Err: false, 4017 }, 4018 4019 "Good sub-resource, computed value": { 4020 Schema: map[string]*Schema{ 4021 "ingress": &Schema{ 4022 Type: TypeList, 4023 Optional: true, 4024 Elem: &Resource{ 4025 Schema: map[string]*Schema{ 4026 "from": &Schema{ 4027 Type: TypeInt, 4028 Optional: true, 4029 }, 4030 }, 4031 }, 4032 }, 4033 }, 4034 4035 Config: map[string]interface{}{ 4036 "ingress": []interface{}{ 4037 `${map("from", var.port)}`, 4038 }, 4039 }, 4040 4041 Vars: map[string]string{ 4042 "var.port": config.UnknownVariableValue, 4043 }, 4044 4045 Err: false, 4046 }, 4047 4048 "Invalid/unknown field": { 4049 Schema: map[string]*Schema{ 4050 "availability_zone": &Schema{ 4051 Type: TypeString, 4052 Optional: true, 4053 Computed: true, 4054 ForceNew: true, 4055 }, 4056 }, 4057 4058 Config: map[string]interface{}{ 4059 "foo": "bar", 4060 }, 4061 4062 Err: true, 4063 }, 4064 4065 "Invalid/unknown field with computed value": { 4066 Schema: map[string]*Schema{ 4067 "availability_zone": &Schema{ 4068 Type: TypeString, 4069 Optional: true, 4070 Computed: true, 4071 ForceNew: true, 4072 }, 4073 }, 4074 4075 Config: map[string]interface{}{ 4076 "foo": "${var.foo}", 4077 }, 4078 4079 Vars: map[string]string{ 4080 "var.foo": config.UnknownVariableValue, 4081 }, 4082 4083 Err: true, 4084 }, 4085 4086 "Computed field set": { 4087 Schema: map[string]*Schema{ 4088 "availability_zone": &Schema{ 4089 Type: TypeString, 4090 Computed: true, 4091 }, 4092 }, 4093 4094 Config: map[string]interface{}{ 4095 "availability_zone": "bar", 4096 }, 4097 4098 Err: true, 4099 }, 4100 4101 "Not a set": { 4102 Schema: map[string]*Schema{ 4103 "ports": &Schema{ 4104 Type: TypeSet, 4105 Required: true, 4106 Elem: &Schema{Type: TypeInt}, 4107 Set: func(a interface{}) int { 4108 return a.(int) 4109 }, 4110 }, 4111 }, 4112 4113 Config: map[string]interface{}{ 4114 "ports": "foo", 4115 }, 4116 4117 Err: true, 4118 }, 4119 4120 "Maps": { 4121 Schema: map[string]*Schema{ 4122 "user_data": &Schema{ 4123 Type: TypeMap, 4124 Optional: true, 4125 }, 4126 }, 4127 4128 Config: map[string]interface{}{ 4129 "user_data": "foo", 4130 }, 4131 4132 Err: true, 4133 }, 4134 4135 "Good map: data surrounded by extra slice": { 4136 Schema: map[string]*Schema{ 4137 "user_data": &Schema{ 4138 Type: TypeMap, 4139 Optional: true, 4140 }, 4141 }, 4142 4143 Config: map[string]interface{}{ 4144 "user_data": []interface{}{ 4145 map[string]interface{}{ 4146 "foo": "bar", 4147 }, 4148 }, 4149 }, 4150 }, 4151 4152 "Good map": { 4153 Schema: map[string]*Schema{ 4154 "user_data": &Schema{ 4155 Type: TypeMap, 4156 Optional: true, 4157 }, 4158 }, 4159 4160 Config: map[string]interface{}{ 4161 "user_data": map[string]interface{}{ 4162 "foo": "bar", 4163 }, 4164 }, 4165 }, 4166 4167 "Bad map: just a slice": { 4168 Schema: map[string]*Schema{ 4169 "user_data": &Schema{ 4170 Type: TypeMap, 4171 Optional: true, 4172 }, 4173 }, 4174 4175 Config: map[string]interface{}{ 4176 "user_data": []interface{}{ 4177 "foo", 4178 }, 4179 }, 4180 4181 Err: true, 4182 }, 4183 4184 "Good set: config has slice with single interpolated value": { 4185 Schema: map[string]*Schema{ 4186 "security_groups": &Schema{ 4187 Type: TypeSet, 4188 Optional: true, 4189 Computed: true, 4190 ForceNew: true, 4191 Elem: &Schema{Type: TypeString}, 4192 Set: func(v interface{}) int { 4193 return len(v.(string)) 4194 }, 4195 }, 4196 }, 4197 4198 Config: map[string]interface{}{ 4199 "security_groups": []interface{}{"${var.foo}"}, 4200 }, 4201 4202 Err: false, 4203 }, 4204 4205 "Bad set: config has single interpolated value": { 4206 Schema: map[string]*Schema{ 4207 "security_groups": &Schema{ 4208 Type: TypeSet, 4209 Optional: true, 4210 Computed: true, 4211 ForceNew: true, 4212 Elem: &Schema{Type: TypeString}, 4213 }, 4214 }, 4215 4216 Config: map[string]interface{}{ 4217 "security_groups": "${var.foo}", 4218 }, 4219 4220 Err: true, 4221 }, 4222 4223 "Bad, subresource should not allow unknown elements": { 4224 Schema: map[string]*Schema{ 4225 "ingress": &Schema{ 4226 Type: TypeList, 4227 Optional: true, 4228 Elem: &Resource{ 4229 Schema: map[string]*Schema{ 4230 "port": &Schema{ 4231 Type: TypeInt, 4232 Required: true, 4233 }, 4234 }, 4235 }, 4236 }, 4237 }, 4238 4239 Config: map[string]interface{}{ 4240 "ingress": []interface{}{ 4241 map[string]interface{}{ 4242 "port": 80, 4243 "other": "yes", 4244 }, 4245 }, 4246 }, 4247 4248 Err: true, 4249 }, 4250 4251 "Bad, subresource should not allow invalid types": { 4252 Schema: map[string]*Schema{ 4253 "ingress": &Schema{ 4254 Type: TypeList, 4255 Optional: true, 4256 Elem: &Resource{ 4257 Schema: map[string]*Schema{ 4258 "port": &Schema{ 4259 Type: TypeInt, 4260 Required: true, 4261 }, 4262 }, 4263 }, 4264 }, 4265 }, 4266 4267 Config: map[string]interface{}{ 4268 "ingress": []interface{}{ 4269 map[string]interface{}{ 4270 "port": "bad", 4271 }, 4272 }, 4273 }, 4274 4275 Err: true, 4276 }, 4277 4278 "Bad, should not allow lists to be assigned to string attributes": { 4279 Schema: map[string]*Schema{ 4280 "availability_zone": &Schema{ 4281 Type: TypeString, 4282 Required: true, 4283 }, 4284 }, 4285 4286 Config: map[string]interface{}{ 4287 "availability_zone": []interface{}{"foo", "bar", "baz"}, 4288 }, 4289 4290 Err: true, 4291 }, 4292 4293 "Bad, should not allow maps to be assigned to string attributes": { 4294 Schema: map[string]*Schema{ 4295 "availability_zone": &Schema{ 4296 Type: TypeString, 4297 Required: true, 4298 }, 4299 }, 4300 4301 Config: map[string]interface{}{ 4302 "availability_zone": map[string]interface{}{"foo": "bar", "baz": "thing"}, 4303 }, 4304 4305 Err: true, 4306 }, 4307 4308 "Deprecated attribute usage generates warning, but not error": { 4309 Schema: map[string]*Schema{ 4310 "old_news": &Schema{ 4311 Type: TypeString, 4312 Optional: true, 4313 Deprecated: "please use 'new_news' instead", 4314 }, 4315 }, 4316 4317 Config: map[string]interface{}{ 4318 "old_news": "extra extra!", 4319 }, 4320 4321 Err: false, 4322 4323 Warnings: []string{ 4324 "\"old_news\": [DEPRECATED] please use 'new_news' instead", 4325 }, 4326 }, 4327 4328 "Deprecated generates no warnings if attr not used": { 4329 Schema: map[string]*Schema{ 4330 "old_news": &Schema{ 4331 Type: TypeString, 4332 Optional: true, 4333 Deprecated: "please use 'new_news' instead", 4334 }, 4335 }, 4336 4337 Err: false, 4338 4339 Warnings: nil, 4340 }, 4341 4342 "Removed attribute usage generates error": { 4343 Schema: map[string]*Schema{ 4344 "long_gone": &Schema{ 4345 Type: TypeString, 4346 Optional: true, 4347 Removed: "no longer supported by Cloud API", 4348 }, 4349 }, 4350 4351 Config: map[string]interface{}{ 4352 "long_gone": "still here!", 4353 }, 4354 4355 Err: true, 4356 Errors: []error{ 4357 fmt.Errorf("\"long_gone\": [REMOVED] no longer supported by Cloud API"), 4358 }, 4359 }, 4360 4361 "Removed generates no errors if attr not used": { 4362 Schema: map[string]*Schema{ 4363 "long_gone": &Schema{ 4364 Type: TypeString, 4365 Optional: true, 4366 Removed: "no longer supported by Cloud API", 4367 }, 4368 }, 4369 4370 Err: false, 4371 }, 4372 4373 "Conflicting attributes generate error": { 4374 Schema: map[string]*Schema{ 4375 "whitelist": &Schema{ 4376 Type: TypeString, 4377 Optional: true, 4378 }, 4379 "blacklist": &Schema{ 4380 Type: TypeString, 4381 Optional: true, 4382 ConflictsWith: []string{"whitelist"}, 4383 }, 4384 }, 4385 4386 Config: map[string]interface{}{ 4387 "whitelist": "white-val", 4388 "blacklist": "black-val", 4389 }, 4390 4391 Err: true, 4392 Errors: []error{ 4393 fmt.Errorf("\"blacklist\": conflicts with whitelist (\"white-val\")"), 4394 }, 4395 }, 4396 4397 "Required attribute & undefined conflicting optional are good": { 4398 Schema: map[string]*Schema{ 4399 "required_att": &Schema{ 4400 Type: TypeString, 4401 Required: true, 4402 }, 4403 "optional_att": &Schema{ 4404 Type: TypeString, 4405 Optional: true, 4406 ConflictsWith: []string{"required_att"}, 4407 }, 4408 }, 4409 4410 Config: map[string]interface{}{ 4411 "required_att": "required-val", 4412 }, 4413 4414 Err: false, 4415 }, 4416 4417 "Required conflicting attribute & defined optional generate error": { 4418 Schema: map[string]*Schema{ 4419 "required_att": &Schema{ 4420 Type: TypeString, 4421 Required: true, 4422 }, 4423 "optional_att": &Schema{ 4424 Type: TypeString, 4425 Optional: true, 4426 ConflictsWith: []string{"required_att"}, 4427 }, 4428 }, 4429 4430 Config: map[string]interface{}{ 4431 "required_att": "required-val", 4432 "optional_att": "optional-val", 4433 }, 4434 4435 Err: true, 4436 Errors: []error{ 4437 fmt.Errorf(`"optional_att": conflicts with required_att ("required-val")`), 4438 }, 4439 }, 4440 4441 "Computed + Optional fields conflicting with each other": { 4442 Schema: map[string]*Schema{ 4443 "foo_att": &Schema{ 4444 Type: TypeString, 4445 Optional: true, 4446 Computed: true, 4447 ConflictsWith: []string{"bar_att"}, 4448 }, 4449 "bar_att": &Schema{ 4450 Type: TypeString, 4451 Optional: true, 4452 Computed: true, 4453 ConflictsWith: []string{"foo_att"}, 4454 }, 4455 }, 4456 4457 Config: map[string]interface{}{ 4458 "foo_att": "foo-val", 4459 "bar_att": "bar-val", 4460 }, 4461 4462 Err: true, 4463 Errors: []error{ 4464 fmt.Errorf(`"foo_att": conflicts with bar_att ("bar-val")`), 4465 fmt.Errorf(`"bar_att": conflicts with foo_att ("foo-val")`), 4466 }, 4467 }, 4468 4469 "Computed + Optional fields NOT conflicting with each other": { 4470 Schema: map[string]*Schema{ 4471 "foo_att": &Schema{ 4472 Type: TypeString, 4473 Optional: true, 4474 Computed: true, 4475 ConflictsWith: []string{"bar_att"}, 4476 }, 4477 "bar_att": &Schema{ 4478 Type: TypeString, 4479 Optional: true, 4480 Computed: true, 4481 ConflictsWith: []string{"foo_att"}, 4482 }, 4483 }, 4484 4485 Config: map[string]interface{}{ 4486 "foo_att": "foo-val", 4487 }, 4488 4489 Err: false, 4490 }, 4491 4492 "Computed + Optional fields that conflict with none set": { 4493 Schema: map[string]*Schema{ 4494 "foo_att": &Schema{ 4495 Type: TypeString, 4496 Optional: true, 4497 Computed: true, 4498 ConflictsWith: []string{"bar_att"}, 4499 }, 4500 "bar_att": &Schema{ 4501 Type: TypeString, 4502 Optional: true, 4503 Computed: true, 4504 ConflictsWith: []string{"foo_att"}, 4505 }, 4506 }, 4507 4508 Config: map[string]interface{}{}, 4509 4510 Err: false, 4511 }, 4512 4513 "Good with ValidateFunc": { 4514 Schema: map[string]*Schema{ 4515 "validate_me": &Schema{ 4516 Type: TypeString, 4517 Required: true, 4518 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4519 return 4520 }, 4521 }, 4522 }, 4523 Config: map[string]interface{}{ 4524 "validate_me": "valid", 4525 }, 4526 Err: false, 4527 }, 4528 4529 "Bad with ValidateFunc": { 4530 Schema: map[string]*Schema{ 4531 "validate_me": &Schema{ 4532 Type: TypeString, 4533 Required: true, 4534 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4535 es = append(es, fmt.Errorf("something is not right here")) 4536 return 4537 }, 4538 }, 4539 }, 4540 Config: map[string]interface{}{ 4541 "validate_me": "invalid", 4542 }, 4543 Err: true, 4544 Errors: []error{ 4545 fmt.Errorf(`something is not right here`), 4546 }, 4547 }, 4548 4549 "ValidateFunc not called when type does not match": { 4550 Schema: map[string]*Schema{ 4551 "number": &Schema{ 4552 Type: TypeInt, 4553 Required: true, 4554 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4555 t.Fatalf("Should not have gotten validate call") 4556 return 4557 }, 4558 }, 4559 }, 4560 Config: map[string]interface{}{ 4561 "number": "NaN", 4562 }, 4563 Err: true, 4564 }, 4565 4566 "ValidateFunc gets decoded type": { 4567 Schema: map[string]*Schema{ 4568 "maybe": &Schema{ 4569 Type: TypeBool, 4570 Required: true, 4571 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4572 if _, ok := v.(bool); !ok { 4573 t.Fatalf("Expected bool, got: %#v", v) 4574 } 4575 return 4576 }, 4577 }, 4578 }, 4579 Config: map[string]interface{}{ 4580 "maybe": "true", 4581 }, 4582 }, 4583 4584 "ValidateFunc is not called with a computed value": { 4585 Schema: map[string]*Schema{ 4586 "validate_me": &Schema{ 4587 Type: TypeString, 4588 Required: true, 4589 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4590 es = append(es, fmt.Errorf("something is not right here")) 4591 return 4592 }, 4593 }, 4594 }, 4595 Config: map[string]interface{}{ 4596 "validate_me": "${var.foo}", 4597 }, 4598 Vars: map[string]string{ 4599 "var.foo": config.UnknownVariableValue, 4600 }, 4601 4602 Err: false, 4603 }, 4604 4605 "special timeouts field": { 4606 Schema: map[string]*Schema{ 4607 "availability_zone": &Schema{ 4608 Type: TypeString, 4609 Optional: true, 4610 Computed: true, 4611 ForceNew: true, 4612 }, 4613 }, 4614 4615 Config: map[string]interface{}{ 4616 TimeoutsConfigKey: "bar", 4617 }, 4618 4619 Err: false, 4620 }, 4621 4622 "invalid bool field": { 4623 Schema: map[string]*Schema{ 4624 "bool_field": { 4625 Type: TypeBool, 4626 Optional: true, 4627 }, 4628 }, 4629 Config: map[string]interface{}{ 4630 "bool_field": "abcdef", 4631 }, 4632 Err: true, 4633 }, 4634 "invalid integer field": { 4635 Schema: map[string]*Schema{ 4636 "integer_field": { 4637 Type: TypeInt, 4638 Optional: true, 4639 }, 4640 }, 4641 Config: map[string]interface{}{ 4642 "integer_field": "abcdef", 4643 }, 4644 Err: true, 4645 }, 4646 "invalid float field": { 4647 Schema: map[string]*Schema{ 4648 "float_field": { 4649 Type: TypeFloat, 4650 Optional: true, 4651 }, 4652 }, 4653 Config: map[string]interface{}{ 4654 "float_field": "abcdef", 4655 }, 4656 Err: true, 4657 }, 4658 4659 // Invalid map values 4660 "invalid bool map value": { 4661 Schema: map[string]*Schema{ 4662 "boolMap": &Schema{ 4663 Type: TypeMap, 4664 Elem: TypeBool, 4665 Optional: true, 4666 }, 4667 }, 4668 Config: map[string]interface{}{ 4669 "boolMap": map[string]interface{}{ 4670 "boolField": "notbool", 4671 }, 4672 }, 4673 Err: true, 4674 }, 4675 "invalid int map value": { 4676 Schema: map[string]*Schema{ 4677 "intMap": &Schema{ 4678 Type: TypeMap, 4679 Elem: TypeInt, 4680 Optional: true, 4681 }, 4682 }, 4683 Config: map[string]interface{}{ 4684 "intMap": map[string]interface{}{ 4685 "intField": "notInt", 4686 }, 4687 }, 4688 Err: true, 4689 }, 4690 "invalid float map value": { 4691 Schema: map[string]*Schema{ 4692 "floatMap": &Schema{ 4693 Type: TypeMap, 4694 Elem: TypeFloat, 4695 Optional: true, 4696 }, 4697 }, 4698 Config: map[string]interface{}{ 4699 "floatMap": map[string]interface{}{ 4700 "floatField": "notFloat", 4701 }, 4702 }, 4703 Err: true, 4704 }, 4705 4706 "map with positive validate function": { 4707 Schema: map[string]*Schema{ 4708 "floatInt": &Schema{ 4709 Type: TypeMap, 4710 Elem: TypeInt, 4711 Optional: true, 4712 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4713 return 4714 }, 4715 }, 4716 }, 4717 Config: map[string]interface{}{ 4718 "floatInt": map[string]interface{}{ 4719 "rightAnswer": "42", 4720 "tooMuch": "43", 4721 }, 4722 }, 4723 Err: false, 4724 }, 4725 "map with negative validate function": { 4726 Schema: map[string]*Schema{ 4727 "floatInt": &Schema{ 4728 Type: TypeMap, 4729 Elem: TypeInt, 4730 Optional: true, 4731 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4732 es = append(es, fmt.Errorf("this is not fine")) 4733 return 4734 }, 4735 }, 4736 }, 4737 Config: map[string]interface{}{ 4738 "floatInt": map[string]interface{}{ 4739 "rightAnswer": "42", 4740 "tooMuch": "43", 4741 }, 4742 }, 4743 Err: true, 4744 }, 4745 4746 // The Validation function should not see interpolation strings from 4747 // non-computed values. 4748 "set with partially computed list and map": { 4749 Schema: map[string]*Schema{ 4750 "outer": &Schema{ 4751 Type: TypeSet, 4752 Optional: true, 4753 Computed: true, 4754 Elem: &Resource{ 4755 Schema: map[string]*Schema{ 4756 "list": &Schema{ 4757 Type: TypeList, 4758 Optional: true, 4759 Elem: &Schema{ 4760 Type: TypeString, 4761 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4762 if strings.HasPrefix(v.(string), "${") { 4763 es = append(es, fmt.Errorf("should not have interpolations")) 4764 } 4765 return 4766 }, 4767 }, 4768 }, 4769 }, 4770 }, 4771 }, 4772 }, 4773 Config: map[string]interface{}{ 4774 "outer": []map[string]interface{}{ 4775 { 4776 "list": []interface{}{"${var.a}", "${var.b}", "c"}, 4777 }, 4778 }, 4779 }, 4780 Vars: map[string]string{ 4781 "var.a": "A", 4782 "var.b": config.UnknownVariableValue, 4783 }, 4784 Err: false, 4785 }, 4786 } 4787 4788 for tn, tc := range cases { 4789 t.Run(tn, func(t *testing.T) { 4790 c, err := config.NewRawConfig(tc.Config) 4791 if err != nil { 4792 t.Fatalf("err: %s", err) 4793 } 4794 if tc.Vars != nil { 4795 vars := make(map[string]ast.Variable) 4796 for k, v := range tc.Vars { 4797 vars[k] = ast.Variable{Value: v, Type: ast.TypeString} 4798 } 4799 4800 if err := c.Interpolate(vars); err != nil { 4801 t.Fatalf("err: %s", err) 4802 } 4803 } 4804 4805 ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) 4806 if len(es) > 0 != tc.Err { 4807 if len(es) == 0 { 4808 t.Errorf("%q: no errors", tn) 4809 } 4810 4811 for _, e := range es { 4812 t.Errorf("%q: err: %s", tn, e) 4813 } 4814 4815 t.FailNow() 4816 } 4817 4818 if !reflect.DeepEqual(ws, tc.Warnings) { 4819 t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws) 4820 } 4821 4822 if tc.Errors != nil { 4823 sort.Sort(errorSort(es)) 4824 sort.Sort(errorSort(tc.Errors)) 4825 4826 if !reflect.DeepEqual(es, tc.Errors) { 4827 t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es) 4828 } 4829 } 4830 }) 4831 4832 } 4833 } 4834 4835 func TestSchemaSet_ValidateMaxItems(t *testing.T) { 4836 cases := map[string]struct { 4837 Schema map[string]*Schema 4838 State *terraform.InstanceState 4839 Config map[string]interface{} 4840 ConfigVariables map[string]string 4841 Diff *terraform.InstanceDiff 4842 Err bool 4843 Errors []error 4844 }{ 4845 "#0": { 4846 Schema: map[string]*Schema{ 4847 "aliases": &Schema{ 4848 Type: TypeSet, 4849 Optional: true, 4850 MaxItems: 1, 4851 Elem: &Schema{Type: TypeString}, 4852 }, 4853 }, 4854 State: nil, 4855 Config: map[string]interface{}{ 4856 "aliases": []interface{}{"foo", "bar"}, 4857 }, 4858 Diff: nil, 4859 Err: true, 4860 Errors: []error{ 4861 fmt.Errorf("aliases: attribute supports 1 item maximum, config has 2 declared"), 4862 }, 4863 }, 4864 "#1": { 4865 Schema: map[string]*Schema{ 4866 "aliases": &Schema{ 4867 Type: TypeSet, 4868 Optional: true, 4869 Elem: &Schema{Type: TypeString}, 4870 }, 4871 }, 4872 State: nil, 4873 Config: map[string]interface{}{ 4874 "aliases": []interface{}{"foo", "bar"}, 4875 }, 4876 Diff: nil, 4877 Err: false, 4878 Errors: nil, 4879 }, 4880 "#2": { 4881 Schema: map[string]*Schema{ 4882 "aliases": &Schema{ 4883 Type: TypeSet, 4884 Optional: true, 4885 MaxItems: 1, 4886 Elem: &Schema{Type: TypeString}, 4887 }, 4888 }, 4889 State: nil, 4890 Config: map[string]interface{}{ 4891 "aliases": []interface{}{"foo"}, 4892 }, 4893 Diff: nil, 4894 Err: false, 4895 Errors: nil, 4896 }, 4897 } 4898 4899 for tn, tc := range cases { 4900 c, err := config.NewRawConfig(tc.Config) 4901 if err != nil { 4902 t.Fatalf("%q: err: %s", tn, err) 4903 } 4904 _, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) 4905 4906 if len(es) > 0 != tc.Err { 4907 if len(es) == 0 { 4908 t.Errorf("%q: no errors", tn) 4909 } 4910 4911 for _, e := range es { 4912 t.Errorf("%q: err: %s", tn, e) 4913 } 4914 4915 t.FailNow() 4916 } 4917 4918 if tc.Errors != nil { 4919 if !reflect.DeepEqual(es, tc.Errors) { 4920 t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es) 4921 } 4922 } 4923 } 4924 } 4925 4926 func TestSchemaSet_ValidateMinItems(t *testing.T) { 4927 cases := map[string]struct { 4928 Schema map[string]*Schema 4929 State *terraform.InstanceState 4930 Config map[string]interface{} 4931 ConfigVariables map[string]string 4932 Diff *terraform.InstanceDiff 4933 Err bool 4934 Errors []error 4935 }{ 4936 "#0": { 4937 Schema: map[string]*Schema{ 4938 "aliases": &Schema{ 4939 Type: TypeSet, 4940 Optional: true, 4941 MinItems: 2, 4942 Elem: &Schema{Type: TypeString}, 4943 }, 4944 }, 4945 State: nil, 4946 Config: map[string]interface{}{ 4947 "aliases": []interface{}{"foo", "bar"}, 4948 }, 4949 Diff: nil, 4950 Err: false, 4951 Errors: nil, 4952 }, 4953 "#1": { 4954 Schema: map[string]*Schema{ 4955 "aliases": &Schema{ 4956 Type: TypeSet, 4957 Optional: true, 4958 Elem: &Schema{Type: TypeString}, 4959 }, 4960 }, 4961 State: nil, 4962 Config: map[string]interface{}{ 4963 "aliases": []interface{}{"foo", "bar"}, 4964 }, 4965 Diff: nil, 4966 Err: false, 4967 Errors: nil, 4968 }, 4969 "#2": { 4970 Schema: map[string]*Schema{ 4971 "aliases": &Schema{ 4972 Type: TypeSet, 4973 Optional: true, 4974 MinItems: 2, 4975 Elem: &Schema{Type: TypeString}, 4976 }, 4977 }, 4978 State: nil, 4979 Config: map[string]interface{}{ 4980 "aliases": []interface{}{"foo"}, 4981 }, 4982 Diff: nil, 4983 Err: true, 4984 Errors: []error{ 4985 fmt.Errorf("aliases: attribute supports 2 item as a minimum, config has 1 declared"), 4986 }, 4987 }, 4988 } 4989 4990 for tn, tc := range cases { 4991 c, err := config.NewRawConfig(tc.Config) 4992 if err != nil { 4993 t.Fatalf("%q: err: %s", tn, err) 4994 } 4995 _, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) 4996 4997 if len(es) > 0 != tc.Err { 4998 if len(es) == 0 { 4999 t.Errorf("%q: no errors", tn) 5000 } 5001 5002 for _, e := range es { 5003 t.Errorf("%q: err: %s", tn, e) 5004 } 5005 5006 t.FailNow() 5007 } 5008 5009 if tc.Errors != nil { 5010 if !reflect.DeepEqual(es, tc.Errors) { 5011 t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es) 5012 } 5013 } 5014 } 5015 } 5016 5017 // errorSort implements sort.Interface to sort errors by their error message 5018 type errorSort []error 5019 5020 func (e errorSort) Len() int { return len(e) } 5021 func (e errorSort) Swap(i, j int) { e[i], e[j] = e[j], e[i] } 5022 func (e errorSort) Less(i, j int) bool { 5023 return e[i].Error() < e[j].Error() 5024 }