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