github.com/emc-cmd/terraform@v0.7.8-0.20161101145618-f16309630e7c/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 RequiresNew: true, 2028 }, 2029 }, 2030 }, 2031 2032 Err: false, 2033 }, 2034 2035 "#52 - Map with empty value": { 2036 Schema: map[string]*Schema{ 2037 "vars": &Schema{ 2038 Type: TypeMap, 2039 }, 2040 }, 2041 2042 State: nil, 2043 2044 Config: map[string]interface{}{ 2045 "vars": map[string]interface{}{ 2046 "foo": "", 2047 }, 2048 }, 2049 2050 Diff: &terraform.InstanceDiff{ 2051 Attributes: map[string]*terraform.ResourceAttrDiff{ 2052 "vars.%": &terraform.ResourceAttrDiff{ 2053 Old: "0", 2054 New: "1", 2055 }, 2056 "vars.foo": &terraform.ResourceAttrDiff{ 2057 Old: "", 2058 New: "", 2059 }, 2060 }, 2061 }, 2062 2063 Err: false, 2064 }, 2065 2066 "#53 - Unset bool, not in state": { 2067 Schema: map[string]*Schema{ 2068 "force": &Schema{ 2069 Type: TypeBool, 2070 Optional: true, 2071 ForceNew: true, 2072 }, 2073 }, 2074 2075 State: nil, 2076 2077 Config: map[string]interface{}{}, 2078 2079 Diff: nil, 2080 2081 Err: false, 2082 }, 2083 2084 "#54 - Unset set, not in state": { 2085 Schema: map[string]*Schema{ 2086 "metadata_keys": &Schema{ 2087 Type: TypeSet, 2088 Optional: true, 2089 ForceNew: true, 2090 Elem: &Schema{Type: TypeInt}, 2091 Set: func(interface{}) int { return 0 }, 2092 }, 2093 }, 2094 2095 State: nil, 2096 2097 Config: map[string]interface{}{}, 2098 2099 Diff: nil, 2100 2101 Err: false, 2102 }, 2103 2104 "#55 - Unset list in state, should not show up computed": { 2105 Schema: map[string]*Schema{ 2106 "metadata_keys": &Schema{ 2107 Type: TypeList, 2108 Optional: true, 2109 Computed: true, 2110 ForceNew: true, 2111 Elem: &Schema{Type: TypeInt}, 2112 }, 2113 }, 2114 2115 State: &terraform.InstanceState{ 2116 Attributes: map[string]string{ 2117 "metadata_keys.#": "0", 2118 }, 2119 }, 2120 2121 Config: map[string]interface{}{}, 2122 2123 Diff: nil, 2124 2125 Err: false, 2126 }, 2127 2128 "#56 - Set element computed substring": { 2129 Schema: map[string]*Schema{ 2130 "ports": &Schema{ 2131 Type: TypeSet, 2132 Required: true, 2133 Elem: &Schema{Type: TypeInt}, 2134 Set: func(a interface{}) int { 2135 return a.(int) 2136 }, 2137 }, 2138 }, 2139 2140 State: nil, 2141 2142 Config: map[string]interface{}{ 2143 "ports": []interface{}{1, "${var.foo}32"}, 2144 }, 2145 2146 ConfigVariables: map[string]ast.Variable{ 2147 "var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue), 2148 }, 2149 2150 Diff: &terraform.InstanceDiff{ 2151 Attributes: map[string]*terraform.ResourceAttrDiff{ 2152 "ports.#": &terraform.ResourceAttrDiff{ 2153 Old: "", 2154 New: "", 2155 NewComputed: true, 2156 }, 2157 }, 2158 }, 2159 2160 Err: false, 2161 }, 2162 2163 "#57 Computed map without config that's known to be empty does not generate diff": { 2164 Schema: map[string]*Schema{ 2165 "tags": &Schema{ 2166 Type: TypeMap, 2167 Computed: true, 2168 }, 2169 }, 2170 2171 Config: nil, 2172 2173 State: &terraform.InstanceState{ 2174 Attributes: map[string]string{ 2175 "tags.%": "0", 2176 }, 2177 }, 2178 2179 Diff: nil, 2180 2181 Err: false, 2182 }, 2183 2184 "#58 Set with hyphen keys": { 2185 Schema: map[string]*Schema{ 2186 "route": &Schema{ 2187 Type: TypeSet, 2188 Optional: true, 2189 Elem: &Resource{ 2190 Schema: map[string]*Schema{ 2191 "index": &Schema{ 2192 Type: TypeInt, 2193 Required: true, 2194 }, 2195 2196 "gateway-name": &Schema{ 2197 Type: TypeString, 2198 Optional: true, 2199 }, 2200 }, 2201 }, 2202 Set: func(v interface{}) int { 2203 m := v.(map[string]interface{}) 2204 return m["index"].(int) 2205 }, 2206 }, 2207 }, 2208 2209 State: nil, 2210 2211 Config: map[string]interface{}{ 2212 "route": []map[string]interface{}{ 2213 map[string]interface{}{ 2214 "index": "1", 2215 "gateway-name": "hello", 2216 }, 2217 }, 2218 }, 2219 2220 Diff: &terraform.InstanceDiff{ 2221 Attributes: map[string]*terraform.ResourceAttrDiff{ 2222 "route.#": &terraform.ResourceAttrDiff{ 2223 Old: "0", 2224 New: "1", 2225 }, 2226 "route.1.index": &terraform.ResourceAttrDiff{ 2227 Old: "", 2228 New: "1", 2229 }, 2230 "route.1.gateway-name": &terraform.ResourceAttrDiff{ 2231 Old: "", 2232 New: "hello", 2233 }, 2234 }, 2235 }, 2236 2237 Err: false, 2238 }, 2239 2240 "#59: StateFunc in nested set (#1759)": { 2241 Schema: map[string]*Schema{ 2242 "service_account": &Schema{ 2243 Type: TypeList, 2244 Optional: true, 2245 ForceNew: true, 2246 Elem: &Resource{ 2247 Schema: map[string]*Schema{ 2248 "scopes": &Schema{ 2249 Type: TypeSet, 2250 Required: true, 2251 ForceNew: true, 2252 Elem: &Schema{ 2253 Type: TypeString, 2254 StateFunc: func(v interface{}) string { 2255 return v.(string) + "!" 2256 }, 2257 }, 2258 Set: func(v interface{}) int { 2259 i, err := strconv.Atoi(v.(string)) 2260 if err != nil { 2261 t.Fatalf("err: %s", err) 2262 } 2263 return i 2264 }, 2265 }, 2266 }, 2267 }, 2268 }, 2269 }, 2270 2271 State: nil, 2272 2273 Config: map[string]interface{}{ 2274 "service_account": []map[string]interface{}{ 2275 { 2276 "scopes": []interface{}{"123"}, 2277 }, 2278 }, 2279 }, 2280 2281 Diff: &terraform.InstanceDiff{ 2282 Attributes: map[string]*terraform.ResourceAttrDiff{ 2283 "service_account.#": &terraform.ResourceAttrDiff{ 2284 Old: "0", 2285 New: "1", 2286 RequiresNew: true, 2287 }, 2288 "service_account.0.scopes.#": &terraform.ResourceAttrDiff{ 2289 Old: "0", 2290 New: "1", 2291 RequiresNew: true, 2292 }, 2293 "service_account.0.scopes.123": &terraform.ResourceAttrDiff{ 2294 Old: "", 2295 New: "123!", 2296 NewExtra: "123", 2297 RequiresNew: true, 2298 }, 2299 }, 2300 }, 2301 2302 Err: false, 2303 }, 2304 2305 "#60 - Removing set elements": { 2306 Schema: map[string]*Schema{ 2307 "instances": &Schema{ 2308 Type: TypeSet, 2309 Elem: &Schema{Type: TypeString}, 2310 Optional: true, 2311 ForceNew: true, 2312 Set: func(v interface{}) int { 2313 return len(v.(string)) 2314 }, 2315 }, 2316 }, 2317 2318 State: &terraform.InstanceState{ 2319 Attributes: map[string]string{ 2320 "instances.#": "2", 2321 "instances.3": "333", 2322 "instances.2": "22", 2323 }, 2324 }, 2325 2326 Config: map[string]interface{}{ 2327 "instances": []interface{}{"333", "4444"}, 2328 }, 2329 2330 Diff: &terraform.InstanceDiff{ 2331 Attributes: map[string]*terraform.ResourceAttrDiff{ 2332 "instances.#": &terraform.ResourceAttrDiff{ 2333 Old: "2", 2334 New: "2", 2335 }, 2336 "instances.2": &terraform.ResourceAttrDiff{ 2337 Old: "22", 2338 New: "", 2339 NewRemoved: true, 2340 RequiresNew: true, 2341 }, 2342 "instances.3": &terraform.ResourceAttrDiff{ 2343 Old: "333", 2344 New: "333", 2345 RequiresNew: true, 2346 }, 2347 "instances.4": &terraform.ResourceAttrDiff{ 2348 Old: "", 2349 New: "4444", 2350 RequiresNew: true, 2351 }, 2352 }, 2353 }, 2354 2355 Err: false, 2356 }, 2357 2358 "Bools can be set with 0/1 in config, still get true/false": { 2359 Schema: map[string]*Schema{ 2360 "one": &Schema{ 2361 Type: TypeBool, 2362 Optional: true, 2363 }, 2364 "two": &Schema{ 2365 Type: TypeBool, 2366 Optional: true, 2367 }, 2368 "three": &Schema{ 2369 Type: TypeBool, 2370 Optional: true, 2371 }, 2372 }, 2373 2374 State: &terraform.InstanceState{ 2375 Attributes: map[string]string{ 2376 "one": "false", 2377 "two": "true", 2378 "three": "true", 2379 }, 2380 }, 2381 2382 Config: map[string]interface{}{ 2383 "one": "1", 2384 "two": "0", 2385 }, 2386 2387 Diff: &terraform.InstanceDiff{ 2388 Attributes: map[string]*terraform.ResourceAttrDiff{ 2389 "one": &terraform.ResourceAttrDiff{ 2390 Old: "false", 2391 New: "true", 2392 }, 2393 "two": &terraform.ResourceAttrDiff{ 2394 Old: "true", 2395 New: "false", 2396 }, 2397 "three": &terraform.ResourceAttrDiff{ 2398 Old: "true", 2399 New: "false", 2400 NewRemoved: true, 2401 }, 2402 }, 2403 }, 2404 2405 Err: false, 2406 }, 2407 2408 "tainted in state w/ no attr changes is still a replacement": { 2409 Schema: map[string]*Schema{}, 2410 2411 State: &terraform.InstanceState{ 2412 Attributes: map[string]string{ 2413 "id": "someid", 2414 }, 2415 Tainted: true, 2416 }, 2417 2418 Config: map[string]interface{}{}, 2419 2420 Diff: &terraform.InstanceDiff{ 2421 Attributes: map[string]*terraform.ResourceAttrDiff{}, 2422 DestroyTainted: true, 2423 }, 2424 2425 Err: false, 2426 }, 2427 2428 "removed optional items should trigger ForceNew": { 2429 Schema: map[string]*Schema{ 2430 "description": &Schema{ 2431 Type: TypeString, 2432 ForceNew: true, 2433 Optional: true, 2434 }, 2435 }, 2436 2437 State: &terraform.InstanceState{ 2438 Attributes: map[string]string{ 2439 "description": "foo", 2440 }, 2441 }, 2442 2443 Config: map[string]interface{}{}, 2444 2445 Diff: &terraform.InstanceDiff{ 2446 Attributes: map[string]*terraform.ResourceAttrDiff{ 2447 "description": &terraform.ResourceAttrDiff{ 2448 Old: "foo", 2449 New: "", 2450 RequiresNew: true, 2451 NewRemoved: true, 2452 }, 2453 }, 2454 }, 2455 2456 Err: false, 2457 }, 2458 } 2459 2460 for tn, tc := range cases { 2461 c, err := config.NewRawConfig(tc.Config) 2462 if err != nil { 2463 t.Fatalf("#%q err: %s", tn, err) 2464 } 2465 2466 if len(tc.ConfigVariables) > 0 { 2467 if err := c.Interpolate(tc.ConfigVariables); err != nil { 2468 t.Fatalf("#%q err: %s", tn, err) 2469 } 2470 } 2471 2472 d, err := schemaMap(tc.Schema).Diff( 2473 tc.State, terraform.NewResourceConfig(c)) 2474 if err != nil != tc.Err { 2475 t.Fatalf("#%q err: %s", tn, err) 2476 } 2477 2478 if !reflect.DeepEqual(tc.Diff, d) { 2479 t.Fatalf("#%q:\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.Diff, d) 2480 } 2481 } 2482 } 2483 2484 func TestSchemaMap_Input(t *testing.T) { 2485 cases := map[string]struct { 2486 Schema map[string]*Schema 2487 Config map[string]interface{} 2488 Input map[string]string 2489 Result map[string]interface{} 2490 Err bool 2491 }{ 2492 /* 2493 * String decode 2494 */ 2495 2496 "uses input on optional field with no config": { 2497 Schema: map[string]*Schema{ 2498 "availability_zone": &Schema{ 2499 Type: TypeString, 2500 Optional: true, 2501 }, 2502 }, 2503 2504 Input: map[string]string{ 2505 "availability_zone": "foo", 2506 }, 2507 2508 Result: map[string]interface{}{ 2509 "availability_zone": "foo", 2510 }, 2511 2512 Err: false, 2513 }, 2514 2515 "input ignored when config has a value": { 2516 Schema: map[string]*Schema{ 2517 "availability_zone": &Schema{ 2518 Type: TypeString, 2519 Optional: true, 2520 }, 2521 }, 2522 2523 Config: map[string]interface{}{ 2524 "availability_zone": "bar", 2525 }, 2526 2527 Input: map[string]string{ 2528 "availability_zone": "foo", 2529 }, 2530 2531 Result: map[string]interface{}{}, 2532 2533 Err: false, 2534 }, 2535 2536 "input ignored when schema has a default": { 2537 Schema: map[string]*Schema{ 2538 "availability_zone": &Schema{ 2539 Type: TypeString, 2540 Default: "foo", 2541 Optional: true, 2542 }, 2543 }, 2544 2545 Input: map[string]string{ 2546 "availability_zone": "bar", 2547 }, 2548 2549 Result: map[string]interface{}{}, 2550 2551 Err: false, 2552 }, 2553 2554 "input ignored when default function returns a value": { 2555 Schema: map[string]*Schema{ 2556 "availability_zone": &Schema{ 2557 Type: TypeString, 2558 DefaultFunc: func() (interface{}, error) { 2559 return "foo", nil 2560 }, 2561 Optional: true, 2562 }, 2563 }, 2564 2565 Input: map[string]string{ 2566 "availability_zone": "bar", 2567 }, 2568 2569 Result: map[string]interface{}{}, 2570 2571 Err: false, 2572 }, 2573 2574 "input ignored when default function returns an empty string": { 2575 Schema: map[string]*Schema{ 2576 "availability_zone": &Schema{ 2577 Type: TypeString, 2578 Default: "", 2579 Optional: true, 2580 }, 2581 }, 2582 2583 Input: map[string]string{ 2584 "availability_zone": "bar", 2585 }, 2586 2587 Result: map[string]interface{}{}, 2588 2589 Err: false, 2590 }, 2591 2592 "input used when default function returns nil": { 2593 Schema: map[string]*Schema{ 2594 "availability_zone": &Schema{ 2595 Type: TypeString, 2596 DefaultFunc: func() (interface{}, error) { 2597 return nil, nil 2598 }, 2599 Optional: true, 2600 }, 2601 }, 2602 2603 Input: map[string]string{ 2604 "availability_zone": "bar", 2605 }, 2606 2607 Result: map[string]interface{}{ 2608 "availability_zone": "bar", 2609 }, 2610 2611 Err: false, 2612 }, 2613 } 2614 2615 for i, tc := range cases { 2616 if tc.Config == nil { 2617 tc.Config = make(map[string]interface{}) 2618 } 2619 2620 c, err := config.NewRawConfig(tc.Config) 2621 if err != nil { 2622 t.Fatalf("err: %s", err) 2623 } 2624 2625 input := new(terraform.MockUIInput) 2626 input.InputReturnMap = tc.Input 2627 2628 rc := terraform.NewResourceConfig(c) 2629 rc.Config = make(map[string]interface{}) 2630 2631 actual, err := schemaMap(tc.Schema).Input(input, rc) 2632 if err != nil != tc.Err { 2633 t.Fatalf("#%v err: %s", i, err) 2634 } 2635 2636 if !reflect.DeepEqual(tc.Result, actual.Config) { 2637 t.Fatalf("#%v: bad:\n\ngot: %#v\nexpected: %#v", i, actual.Config, tc.Result) 2638 } 2639 } 2640 } 2641 2642 func TestSchemaMap_InputDefault(t *testing.T) { 2643 emptyConfig := make(map[string]interface{}) 2644 c, err := config.NewRawConfig(emptyConfig) 2645 if err != nil { 2646 t.Fatalf("err: %s", err) 2647 } 2648 rc := terraform.NewResourceConfig(c) 2649 rc.Config = make(map[string]interface{}) 2650 2651 input := new(terraform.MockUIInput) 2652 input.InputFn = func(opts *terraform.InputOpts) (string, error) { 2653 t.Fatalf("InputFn should not be called on: %#v", opts) 2654 return "", nil 2655 } 2656 2657 schema := map[string]*Schema{ 2658 "availability_zone": &Schema{ 2659 Type: TypeString, 2660 Default: "foo", 2661 Optional: true, 2662 }, 2663 } 2664 actual, err := schemaMap(schema).Input(input, rc) 2665 if err != nil { 2666 t.Fatalf("err: %s", err) 2667 } 2668 2669 expected := map[string]interface{}{} 2670 2671 if !reflect.DeepEqual(expected, actual.Config) { 2672 t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected) 2673 } 2674 } 2675 2676 func TestSchemaMap_InputDeprecated(t *testing.T) { 2677 emptyConfig := make(map[string]interface{}) 2678 c, err := config.NewRawConfig(emptyConfig) 2679 if err != nil { 2680 t.Fatalf("err: %s", err) 2681 } 2682 rc := terraform.NewResourceConfig(c) 2683 rc.Config = make(map[string]interface{}) 2684 2685 input := new(terraform.MockUIInput) 2686 input.InputFn = func(opts *terraform.InputOpts) (string, error) { 2687 t.Fatalf("InputFn should not be called on: %#v", opts) 2688 return "", nil 2689 } 2690 2691 schema := map[string]*Schema{ 2692 "availability_zone": &Schema{ 2693 Type: TypeString, 2694 Deprecated: "long gone", 2695 Optional: true, 2696 }, 2697 } 2698 actual, err := schemaMap(schema).Input(input, rc) 2699 if err != nil { 2700 t.Fatalf("err: %s", err) 2701 } 2702 2703 expected := map[string]interface{}{} 2704 2705 if !reflect.DeepEqual(expected, actual.Config) { 2706 t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected) 2707 } 2708 } 2709 2710 func TestSchemaMap_InternalValidate(t *testing.T) { 2711 cases := map[string]struct { 2712 In map[string]*Schema 2713 Err bool 2714 }{ 2715 "nothing": { 2716 nil, 2717 false, 2718 }, 2719 2720 "Both optional and required": { 2721 map[string]*Schema{ 2722 "foo": &Schema{ 2723 Type: TypeInt, 2724 Optional: true, 2725 Required: true, 2726 }, 2727 }, 2728 true, 2729 }, 2730 2731 "No optional and no required": { 2732 map[string]*Schema{ 2733 "foo": &Schema{ 2734 Type: TypeInt, 2735 }, 2736 }, 2737 true, 2738 }, 2739 2740 "Missing Type": { 2741 map[string]*Schema{ 2742 "foo": &Schema{ 2743 Required: true, 2744 }, 2745 }, 2746 true, 2747 }, 2748 2749 "Required but computed": { 2750 map[string]*Schema{ 2751 "foo": &Schema{ 2752 Type: TypeInt, 2753 Required: true, 2754 Computed: true, 2755 }, 2756 }, 2757 true, 2758 }, 2759 2760 "Looks good": { 2761 map[string]*Schema{ 2762 "foo": &Schema{ 2763 Type: TypeString, 2764 Required: true, 2765 }, 2766 }, 2767 false, 2768 }, 2769 2770 "Computed but has default": { 2771 map[string]*Schema{ 2772 "foo": &Schema{ 2773 Type: TypeInt, 2774 Optional: true, 2775 Computed: true, 2776 Default: "foo", 2777 }, 2778 }, 2779 true, 2780 }, 2781 2782 "Required but has default": { 2783 map[string]*Schema{ 2784 "foo": &Schema{ 2785 Type: TypeInt, 2786 Optional: true, 2787 Required: true, 2788 Default: "foo", 2789 }, 2790 }, 2791 true, 2792 }, 2793 2794 "List element not set": { 2795 map[string]*Schema{ 2796 "foo": &Schema{ 2797 Type: TypeList, 2798 }, 2799 }, 2800 true, 2801 }, 2802 2803 "List default": { 2804 map[string]*Schema{ 2805 "foo": &Schema{ 2806 Type: TypeList, 2807 Elem: &Schema{Type: TypeInt}, 2808 Default: "foo", 2809 }, 2810 }, 2811 true, 2812 }, 2813 2814 "List element computed": { 2815 map[string]*Schema{ 2816 "foo": &Schema{ 2817 Type: TypeList, 2818 Optional: true, 2819 Elem: &Schema{ 2820 Type: TypeInt, 2821 Computed: true, 2822 }, 2823 }, 2824 }, 2825 true, 2826 }, 2827 2828 "List element with Set set": { 2829 map[string]*Schema{ 2830 "foo": &Schema{ 2831 Type: TypeList, 2832 Elem: &Schema{Type: TypeInt}, 2833 Set: func(interface{}) int { return 0 }, 2834 Optional: true, 2835 }, 2836 }, 2837 true, 2838 }, 2839 2840 "Set element with no Set set": { 2841 map[string]*Schema{ 2842 "foo": &Schema{ 2843 Type: TypeSet, 2844 Elem: &Schema{Type: TypeInt}, 2845 Optional: true, 2846 }, 2847 }, 2848 false, 2849 }, 2850 2851 "Required but computedWhen": { 2852 map[string]*Schema{ 2853 "foo": &Schema{ 2854 Type: TypeInt, 2855 Required: true, 2856 ComputedWhen: []string{"foo"}, 2857 }, 2858 }, 2859 true, 2860 }, 2861 2862 "Conflicting attributes cannot be required": { 2863 map[string]*Schema{ 2864 "blacklist": &Schema{ 2865 Type: TypeBool, 2866 Required: true, 2867 }, 2868 "whitelist": &Schema{ 2869 Type: TypeBool, 2870 Optional: true, 2871 ConflictsWith: []string{"blacklist"}, 2872 }, 2873 }, 2874 true, 2875 }, 2876 2877 "Attribute with conflicts cannot be required": { 2878 map[string]*Schema{ 2879 "whitelist": &Schema{ 2880 Type: TypeBool, 2881 Required: true, 2882 ConflictsWith: []string{"blacklist"}, 2883 }, 2884 }, 2885 true, 2886 }, 2887 2888 "ConflictsWith cannot be used w/ Computed": { 2889 map[string]*Schema{ 2890 "blacklist": &Schema{ 2891 Type: TypeBool, 2892 Computed: true, 2893 }, 2894 "whitelist": &Schema{ 2895 Type: TypeBool, 2896 Optional: true, 2897 ConflictsWith: []string{"blacklist"}, 2898 }, 2899 }, 2900 true, 2901 }, 2902 2903 "ConflictsWith cannot be used w/ ComputedWhen": { 2904 map[string]*Schema{ 2905 "blacklist": &Schema{ 2906 Type: TypeBool, 2907 ComputedWhen: []string{"foor"}, 2908 }, 2909 "whitelist": &Schema{ 2910 Type: TypeBool, 2911 Required: true, 2912 ConflictsWith: []string{"blacklist"}, 2913 }, 2914 }, 2915 true, 2916 }, 2917 2918 "Sub-resource invalid": { 2919 map[string]*Schema{ 2920 "foo": &Schema{ 2921 Type: TypeList, 2922 Optional: true, 2923 Elem: &Resource{ 2924 Schema: map[string]*Schema{ 2925 "foo": new(Schema), 2926 }, 2927 }, 2928 }, 2929 }, 2930 true, 2931 }, 2932 2933 "Sub-resource valid": { 2934 map[string]*Schema{ 2935 "foo": &Schema{ 2936 Type: TypeList, 2937 Optional: true, 2938 Elem: &Resource{ 2939 Schema: map[string]*Schema{ 2940 "foo": &Schema{ 2941 Type: TypeInt, 2942 Optional: true, 2943 }, 2944 }, 2945 }, 2946 }, 2947 }, 2948 false, 2949 }, 2950 2951 "ValidateFunc on non-primitive": { 2952 map[string]*Schema{ 2953 "foo": &Schema{ 2954 Type: TypeSet, 2955 Required: true, 2956 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 2957 return 2958 }, 2959 }, 2960 }, 2961 true, 2962 }, 2963 } 2964 2965 for tn, tc := range cases { 2966 err := schemaMap(tc.In).InternalValidate(schemaMap{}) 2967 if err != nil != tc.Err { 2968 if tc.Err { 2969 t.Fatalf("%q: Expected error did not occur:\n\n%#v", tn, tc.In) 2970 } 2971 t.Fatalf("%q: Unexpected error occurred:\n\n%#v", tn, tc.In) 2972 } 2973 } 2974 2975 } 2976 2977 func TestSchemaMap_DiffSuppress(t *testing.T) { 2978 cases := map[string]struct { 2979 Schema map[string]*Schema 2980 State *terraform.InstanceState 2981 Config map[string]interface{} 2982 ConfigVariables map[string]ast.Variable 2983 ExpectedDiff *terraform.InstanceDiff 2984 Err bool 2985 }{ 2986 "#0 - Suppress otherwise valid diff by returning true": { 2987 Schema: map[string]*Schema{ 2988 "availability_zone": { 2989 Type: TypeString, 2990 Optional: true, 2991 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 2992 // Always suppress any diff 2993 return true 2994 }, 2995 }, 2996 }, 2997 2998 State: nil, 2999 3000 Config: map[string]interface{}{ 3001 "availability_zone": "foo", 3002 }, 3003 3004 ExpectedDiff: nil, 3005 3006 Err: false, 3007 }, 3008 3009 "#1 - Don't suppress diff by returning false": { 3010 Schema: map[string]*Schema{ 3011 "availability_zone": { 3012 Type: TypeString, 3013 Optional: true, 3014 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3015 // Always suppress any diff 3016 return false 3017 }, 3018 }, 3019 }, 3020 3021 State: nil, 3022 3023 Config: map[string]interface{}{ 3024 "availability_zone": "foo", 3025 }, 3026 3027 ExpectedDiff: &terraform.InstanceDiff{ 3028 Attributes: map[string]*terraform.ResourceAttrDiff{ 3029 "availability_zone": { 3030 Old: "", 3031 New: "foo", 3032 }, 3033 }, 3034 }, 3035 3036 Err: false, 3037 }, 3038 3039 "Default with suppress makes no diff": { 3040 Schema: map[string]*Schema{ 3041 "availability_zone": { 3042 Type: TypeString, 3043 Optional: true, 3044 Default: "foo", 3045 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3046 return true 3047 }, 3048 }, 3049 }, 3050 3051 State: nil, 3052 3053 Config: map[string]interface{}{}, 3054 3055 ExpectedDiff: nil, 3056 3057 Err: false, 3058 }, 3059 3060 "Default with false suppress makes diff": { 3061 Schema: map[string]*Schema{ 3062 "availability_zone": { 3063 Type: TypeString, 3064 Optional: true, 3065 Default: "foo", 3066 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3067 return false 3068 }, 3069 }, 3070 }, 3071 3072 State: nil, 3073 3074 Config: map[string]interface{}{}, 3075 3076 ExpectedDiff: &terraform.InstanceDiff{ 3077 Attributes: map[string]*terraform.ResourceAttrDiff{ 3078 "availability_zone": { 3079 Old: "", 3080 New: "foo", 3081 }, 3082 }, 3083 }, 3084 3085 Err: false, 3086 }, 3087 } 3088 3089 for tn, tc := range cases { 3090 t.Run(tn, func(t *testing.T) { 3091 c, err := config.NewRawConfig(tc.Config) 3092 if err != nil { 3093 t.Fatalf("#%q err: %s", tn, err) 3094 } 3095 3096 if len(tc.ConfigVariables) > 0 { 3097 if err := c.Interpolate(tc.ConfigVariables); err != nil { 3098 t.Fatalf("#%q err: %s", tn, err) 3099 } 3100 } 3101 3102 d, err := schemaMap(tc.Schema).Diff( 3103 tc.State, terraform.NewResourceConfig(c)) 3104 if err != nil != tc.Err { 3105 t.Fatalf("#%q err: %s", tn, err) 3106 } 3107 3108 if !reflect.DeepEqual(tc.ExpectedDiff, d) { 3109 t.Fatalf("#%q:\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.ExpectedDiff, d) 3110 } 3111 }) 3112 } 3113 } 3114 3115 func TestSchemaMap_Validate(t *testing.T) { 3116 cases := map[string]struct { 3117 Schema map[string]*Schema 3118 Config map[string]interface{} 3119 Vars map[string]string 3120 Err bool 3121 Errors []error 3122 Warnings []string 3123 }{ 3124 "Good": { 3125 Schema: map[string]*Schema{ 3126 "availability_zone": &Schema{ 3127 Type: TypeString, 3128 Optional: true, 3129 Computed: true, 3130 ForceNew: true, 3131 }, 3132 }, 3133 3134 Config: map[string]interface{}{ 3135 "availability_zone": "foo", 3136 }, 3137 }, 3138 3139 "Good, because the var is not set and that error will come elsewhere": { 3140 Schema: map[string]*Schema{ 3141 "size": &Schema{ 3142 Type: TypeInt, 3143 Required: true, 3144 }, 3145 }, 3146 3147 Config: map[string]interface{}{ 3148 "size": "${var.foo}", 3149 }, 3150 3151 Vars: map[string]string{ 3152 "var.foo": config.UnknownVariableValue, 3153 }, 3154 }, 3155 3156 "Required field not set": { 3157 Schema: map[string]*Schema{ 3158 "availability_zone": &Schema{ 3159 Type: TypeString, 3160 Required: true, 3161 }, 3162 }, 3163 3164 Config: map[string]interface{}{}, 3165 3166 Err: true, 3167 }, 3168 3169 "Invalid basic type": { 3170 Schema: map[string]*Schema{ 3171 "port": &Schema{ 3172 Type: TypeInt, 3173 Required: true, 3174 }, 3175 }, 3176 3177 Config: map[string]interface{}{ 3178 "port": "I am invalid", 3179 }, 3180 3181 Err: true, 3182 }, 3183 3184 "Invalid complex type": { 3185 Schema: map[string]*Schema{ 3186 "user_data": &Schema{ 3187 Type: TypeString, 3188 Optional: true, 3189 }, 3190 }, 3191 3192 Config: map[string]interface{}{ 3193 "user_data": []interface{}{ 3194 map[string]interface{}{ 3195 "foo": "bar", 3196 }, 3197 }, 3198 }, 3199 3200 Err: true, 3201 }, 3202 3203 "Bad type, interpolated": { 3204 Schema: map[string]*Schema{ 3205 "size": &Schema{ 3206 Type: TypeInt, 3207 Required: true, 3208 }, 3209 }, 3210 3211 Config: map[string]interface{}{ 3212 "size": "${var.foo}", 3213 }, 3214 3215 Vars: map[string]string{ 3216 "var.foo": "nope", 3217 }, 3218 3219 Err: true, 3220 }, 3221 3222 "Required but has DefaultFunc": { 3223 Schema: map[string]*Schema{ 3224 "availability_zone": &Schema{ 3225 Type: TypeString, 3226 Required: true, 3227 DefaultFunc: func() (interface{}, error) { 3228 return "foo", nil 3229 }, 3230 }, 3231 }, 3232 3233 Config: nil, 3234 }, 3235 3236 "Required but has DefaultFunc return nil": { 3237 Schema: map[string]*Schema{ 3238 "availability_zone": &Schema{ 3239 Type: TypeString, 3240 Required: true, 3241 DefaultFunc: func() (interface{}, error) { 3242 return nil, nil 3243 }, 3244 }, 3245 }, 3246 3247 Config: nil, 3248 3249 Err: true, 3250 }, 3251 3252 "Optional sub-resource": { 3253 Schema: map[string]*Schema{ 3254 "ingress": &Schema{ 3255 Type: TypeList, 3256 Elem: &Resource{ 3257 Schema: map[string]*Schema{ 3258 "from": &Schema{ 3259 Type: TypeInt, 3260 Required: true, 3261 }, 3262 }, 3263 }, 3264 }, 3265 }, 3266 3267 Config: map[string]interface{}{}, 3268 3269 Err: false, 3270 }, 3271 3272 "Sub-resource is the wrong type": { 3273 Schema: map[string]*Schema{ 3274 "ingress": &Schema{ 3275 Type: TypeList, 3276 Required: true, 3277 Elem: &Resource{ 3278 Schema: map[string]*Schema{ 3279 "from": &Schema{ 3280 Type: TypeInt, 3281 Required: true, 3282 }, 3283 }, 3284 }, 3285 }, 3286 }, 3287 3288 Config: map[string]interface{}{ 3289 "ingress": []interface{}{"foo"}, 3290 }, 3291 3292 Err: true, 3293 }, 3294 3295 "Not a list": { 3296 Schema: map[string]*Schema{ 3297 "ingress": &Schema{ 3298 Type: TypeList, 3299 Elem: &Resource{ 3300 Schema: map[string]*Schema{ 3301 "from": &Schema{ 3302 Type: TypeInt, 3303 Required: true, 3304 }, 3305 }, 3306 }, 3307 }, 3308 }, 3309 3310 Config: map[string]interface{}{ 3311 "ingress": "foo", 3312 }, 3313 3314 Err: true, 3315 }, 3316 3317 "Required sub-resource field": { 3318 Schema: map[string]*Schema{ 3319 "ingress": &Schema{ 3320 Type: TypeList, 3321 Elem: &Resource{ 3322 Schema: map[string]*Schema{ 3323 "from": &Schema{ 3324 Type: TypeInt, 3325 Required: true, 3326 }, 3327 }, 3328 }, 3329 }, 3330 }, 3331 3332 Config: map[string]interface{}{ 3333 "ingress": []interface{}{ 3334 map[string]interface{}{}, 3335 }, 3336 }, 3337 3338 Err: true, 3339 }, 3340 3341 "Good sub-resource": { 3342 Schema: map[string]*Schema{ 3343 "ingress": &Schema{ 3344 Type: TypeList, 3345 Optional: true, 3346 Elem: &Resource{ 3347 Schema: map[string]*Schema{ 3348 "from": &Schema{ 3349 Type: TypeInt, 3350 Required: true, 3351 }, 3352 }, 3353 }, 3354 }, 3355 }, 3356 3357 Config: map[string]interface{}{ 3358 "ingress": []interface{}{ 3359 map[string]interface{}{ 3360 "from": 80, 3361 }, 3362 }, 3363 }, 3364 3365 Err: false, 3366 }, 3367 3368 "Invalid/unknown field": { 3369 Schema: map[string]*Schema{ 3370 "availability_zone": &Schema{ 3371 Type: TypeString, 3372 Optional: true, 3373 Computed: true, 3374 ForceNew: true, 3375 }, 3376 }, 3377 3378 Config: map[string]interface{}{ 3379 "foo": "bar", 3380 }, 3381 3382 Err: true, 3383 }, 3384 3385 "Invalid/unknown field with computed value": { 3386 Schema: map[string]*Schema{ 3387 "availability_zone": &Schema{ 3388 Type: TypeString, 3389 Optional: true, 3390 Computed: true, 3391 ForceNew: true, 3392 }, 3393 }, 3394 3395 Config: map[string]interface{}{ 3396 "foo": "${var.foo}", 3397 }, 3398 3399 Vars: map[string]string{ 3400 "var.foo": config.UnknownVariableValue, 3401 }, 3402 3403 Err: true, 3404 }, 3405 3406 "Computed field set": { 3407 Schema: map[string]*Schema{ 3408 "availability_zone": &Schema{ 3409 Type: TypeString, 3410 Computed: true, 3411 }, 3412 }, 3413 3414 Config: map[string]interface{}{ 3415 "availability_zone": "bar", 3416 }, 3417 3418 Err: true, 3419 }, 3420 3421 "Not a set": { 3422 Schema: map[string]*Schema{ 3423 "ports": &Schema{ 3424 Type: TypeSet, 3425 Required: true, 3426 Elem: &Schema{Type: TypeInt}, 3427 Set: func(a interface{}) int { 3428 return a.(int) 3429 }, 3430 }, 3431 }, 3432 3433 Config: map[string]interface{}{ 3434 "ports": "foo", 3435 }, 3436 3437 Err: true, 3438 }, 3439 3440 "Maps": { 3441 Schema: map[string]*Schema{ 3442 "user_data": &Schema{ 3443 Type: TypeMap, 3444 Optional: true, 3445 }, 3446 }, 3447 3448 Config: map[string]interface{}{ 3449 "user_data": "foo", 3450 }, 3451 3452 Err: true, 3453 }, 3454 3455 "Good map: data surrounded by extra slice": { 3456 Schema: map[string]*Schema{ 3457 "user_data": &Schema{ 3458 Type: TypeMap, 3459 Optional: true, 3460 }, 3461 }, 3462 3463 Config: map[string]interface{}{ 3464 "user_data": []interface{}{ 3465 map[string]interface{}{ 3466 "foo": "bar", 3467 }, 3468 }, 3469 }, 3470 }, 3471 3472 "Good map": { 3473 Schema: map[string]*Schema{ 3474 "user_data": &Schema{ 3475 Type: TypeMap, 3476 Optional: true, 3477 }, 3478 }, 3479 3480 Config: map[string]interface{}{ 3481 "user_data": map[string]interface{}{ 3482 "foo": "bar", 3483 }, 3484 }, 3485 }, 3486 3487 "Bad map: just a slice": { 3488 Schema: map[string]*Schema{ 3489 "user_data": &Schema{ 3490 Type: TypeMap, 3491 Optional: true, 3492 }, 3493 }, 3494 3495 Config: map[string]interface{}{ 3496 "user_data": []interface{}{ 3497 "foo", 3498 }, 3499 }, 3500 3501 Err: true, 3502 }, 3503 3504 "Good set: config has slice with single interpolated value": { 3505 Schema: map[string]*Schema{ 3506 "security_groups": &Schema{ 3507 Type: TypeSet, 3508 Optional: true, 3509 Computed: true, 3510 ForceNew: true, 3511 Elem: &Schema{Type: TypeString}, 3512 Set: func(v interface{}) int { 3513 return len(v.(string)) 3514 }, 3515 }, 3516 }, 3517 3518 Config: map[string]interface{}{ 3519 "security_groups": []interface{}{"${var.foo}"}, 3520 }, 3521 3522 Err: false, 3523 }, 3524 3525 "Bad set: config has single interpolated value": { 3526 Schema: map[string]*Schema{ 3527 "security_groups": &Schema{ 3528 Type: TypeSet, 3529 Optional: true, 3530 Computed: true, 3531 ForceNew: true, 3532 Elem: &Schema{Type: TypeString}, 3533 }, 3534 }, 3535 3536 Config: map[string]interface{}{ 3537 "security_groups": "${var.foo}", 3538 }, 3539 3540 Err: true, 3541 }, 3542 3543 "Bad, subresource should not allow unknown elements": { 3544 Schema: map[string]*Schema{ 3545 "ingress": &Schema{ 3546 Type: TypeList, 3547 Optional: true, 3548 Elem: &Resource{ 3549 Schema: map[string]*Schema{ 3550 "port": &Schema{ 3551 Type: TypeInt, 3552 Required: true, 3553 }, 3554 }, 3555 }, 3556 }, 3557 }, 3558 3559 Config: map[string]interface{}{ 3560 "ingress": []interface{}{ 3561 map[string]interface{}{ 3562 "port": 80, 3563 "other": "yes", 3564 }, 3565 }, 3566 }, 3567 3568 Err: true, 3569 }, 3570 3571 "Bad, subresource should not allow invalid types": { 3572 Schema: map[string]*Schema{ 3573 "ingress": &Schema{ 3574 Type: TypeList, 3575 Optional: true, 3576 Elem: &Resource{ 3577 Schema: map[string]*Schema{ 3578 "port": &Schema{ 3579 Type: TypeInt, 3580 Required: true, 3581 }, 3582 }, 3583 }, 3584 }, 3585 }, 3586 3587 Config: map[string]interface{}{ 3588 "ingress": []interface{}{ 3589 map[string]interface{}{ 3590 "port": "bad", 3591 }, 3592 }, 3593 }, 3594 3595 Err: true, 3596 }, 3597 3598 "Bad, should not allow lists to be assigned to string attributes": { 3599 Schema: map[string]*Schema{ 3600 "availability_zone": &Schema{ 3601 Type: TypeString, 3602 Required: true, 3603 }, 3604 }, 3605 3606 Config: map[string]interface{}{ 3607 "availability_zone": []interface{}{"foo", "bar", "baz"}, 3608 }, 3609 3610 Err: true, 3611 }, 3612 3613 "Bad, should not allow maps to be assigned to string attributes": { 3614 Schema: map[string]*Schema{ 3615 "availability_zone": &Schema{ 3616 Type: TypeString, 3617 Required: true, 3618 }, 3619 }, 3620 3621 Config: map[string]interface{}{ 3622 "availability_zone": map[string]interface{}{"foo": "bar", "baz": "thing"}, 3623 }, 3624 3625 Err: true, 3626 }, 3627 3628 "Deprecated attribute usage generates warning, but not error": { 3629 Schema: map[string]*Schema{ 3630 "old_news": &Schema{ 3631 Type: TypeString, 3632 Optional: true, 3633 Deprecated: "please use 'new_news' instead", 3634 }, 3635 }, 3636 3637 Config: map[string]interface{}{ 3638 "old_news": "extra extra!", 3639 }, 3640 3641 Err: false, 3642 3643 Warnings: []string{ 3644 "\"old_news\": [DEPRECATED] please use 'new_news' instead", 3645 }, 3646 }, 3647 3648 "Deprecated generates no warnings if attr not used": { 3649 Schema: map[string]*Schema{ 3650 "old_news": &Schema{ 3651 Type: TypeString, 3652 Optional: true, 3653 Deprecated: "please use 'new_news' instead", 3654 }, 3655 }, 3656 3657 Err: false, 3658 3659 Warnings: nil, 3660 }, 3661 3662 "Removed attribute usage generates error": { 3663 Schema: map[string]*Schema{ 3664 "long_gone": &Schema{ 3665 Type: TypeString, 3666 Optional: true, 3667 Removed: "no longer supported by Cloud API", 3668 }, 3669 }, 3670 3671 Config: map[string]interface{}{ 3672 "long_gone": "still here!", 3673 }, 3674 3675 Err: true, 3676 Errors: []error{ 3677 fmt.Errorf("\"long_gone\": [REMOVED] no longer supported by Cloud API"), 3678 }, 3679 }, 3680 3681 "Removed generates no errors if attr not used": { 3682 Schema: map[string]*Schema{ 3683 "long_gone": &Schema{ 3684 Type: TypeString, 3685 Optional: true, 3686 Removed: "no longer supported by Cloud API", 3687 }, 3688 }, 3689 3690 Err: false, 3691 }, 3692 3693 "Conflicting attributes generate error": { 3694 Schema: map[string]*Schema{ 3695 "whitelist": &Schema{ 3696 Type: TypeString, 3697 Optional: true, 3698 }, 3699 "blacklist": &Schema{ 3700 Type: TypeString, 3701 Optional: true, 3702 ConflictsWith: []string{"whitelist"}, 3703 }, 3704 }, 3705 3706 Config: map[string]interface{}{ 3707 "whitelist": "white-val", 3708 "blacklist": "black-val", 3709 }, 3710 3711 Err: true, 3712 Errors: []error{ 3713 fmt.Errorf("\"blacklist\": conflicts with whitelist (\"white-val\")"), 3714 }, 3715 }, 3716 3717 "Required attribute & undefined conflicting optional are good": { 3718 Schema: map[string]*Schema{ 3719 "required_att": &Schema{ 3720 Type: TypeString, 3721 Required: true, 3722 }, 3723 "optional_att": &Schema{ 3724 Type: TypeString, 3725 Optional: true, 3726 ConflictsWith: []string{"required_att"}, 3727 }, 3728 }, 3729 3730 Config: map[string]interface{}{ 3731 "required_att": "required-val", 3732 }, 3733 3734 Err: false, 3735 }, 3736 3737 "Required conflicting attribute & defined optional generate error": { 3738 Schema: map[string]*Schema{ 3739 "required_att": &Schema{ 3740 Type: TypeString, 3741 Required: true, 3742 }, 3743 "optional_att": &Schema{ 3744 Type: TypeString, 3745 Optional: true, 3746 ConflictsWith: []string{"required_att"}, 3747 }, 3748 }, 3749 3750 Config: map[string]interface{}{ 3751 "required_att": "required-val", 3752 "optional_att": "optional-val", 3753 }, 3754 3755 Err: true, 3756 Errors: []error{ 3757 fmt.Errorf(`"optional_att": conflicts with required_att ("required-val")`), 3758 }, 3759 }, 3760 3761 "Good with ValidateFunc": { 3762 Schema: map[string]*Schema{ 3763 "validate_me": &Schema{ 3764 Type: TypeString, 3765 Required: true, 3766 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 3767 return 3768 }, 3769 }, 3770 }, 3771 Config: map[string]interface{}{ 3772 "validate_me": "valid", 3773 }, 3774 Err: false, 3775 }, 3776 3777 "Bad with ValidateFunc": { 3778 Schema: map[string]*Schema{ 3779 "validate_me": &Schema{ 3780 Type: TypeString, 3781 Required: true, 3782 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 3783 es = append(es, fmt.Errorf("something is not right here")) 3784 return 3785 }, 3786 }, 3787 }, 3788 Config: map[string]interface{}{ 3789 "validate_me": "invalid", 3790 }, 3791 Err: true, 3792 Errors: []error{ 3793 fmt.Errorf(`something is not right here`), 3794 }, 3795 }, 3796 3797 "ValidateFunc not called when type does not match": { 3798 Schema: map[string]*Schema{ 3799 "number": &Schema{ 3800 Type: TypeInt, 3801 Required: true, 3802 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 3803 t.Fatalf("Should not have gotten validate call") 3804 return 3805 }, 3806 }, 3807 }, 3808 Config: map[string]interface{}{ 3809 "number": "NaN", 3810 }, 3811 Err: true, 3812 }, 3813 3814 "ValidateFunc gets decoded type": { 3815 Schema: map[string]*Schema{ 3816 "maybe": &Schema{ 3817 Type: TypeBool, 3818 Required: true, 3819 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 3820 if _, ok := v.(bool); !ok { 3821 t.Fatalf("Expected bool, got: %#v", v) 3822 } 3823 return 3824 }, 3825 }, 3826 }, 3827 Config: map[string]interface{}{ 3828 "maybe": "true", 3829 }, 3830 }, 3831 3832 "ValidateFunc is not called with a computed value": { 3833 Schema: map[string]*Schema{ 3834 "validate_me": &Schema{ 3835 Type: TypeString, 3836 Required: true, 3837 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 3838 es = append(es, fmt.Errorf("something is not right here")) 3839 return 3840 }, 3841 }, 3842 }, 3843 Config: map[string]interface{}{ 3844 "validate_me": "${var.foo}", 3845 }, 3846 Vars: map[string]string{ 3847 "var.foo": config.UnknownVariableValue, 3848 }, 3849 3850 Err: false, 3851 }, 3852 } 3853 3854 for tn, tc := range cases { 3855 c, err := config.NewRawConfig(tc.Config) 3856 if err != nil { 3857 t.Fatalf("err: %s", err) 3858 } 3859 if tc.Vars != nil { 3860 vars := make(map[string]ast.Variable) 3861 for k, v := range tc.Vars { 3862 vars[k] = ast.Variable{Value: v, Type: ast.TypeString} 3863 } 3864 3865 if err := c.Interpolate(vars); err != nil { 3866 t.Fatalf("err: %s", err) 3867 } 3868 } 3869 3870 ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) 3871 if len(es) > 0 != tc.Err { 3872 if len(es) == 0 { 3873 t.Errorf("%q: no errors", tn) 3874 } 3875 3876 for _, e := range es { 3877 t.Errorf("%q: err: %s", tn, e) 3878 } 3879 3880 t.FailNow() 3881 } 3882 3883 if !reflect.DeepEqual(ws, tc.Warnings) { 3884 t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws) 3885 } 3886 3887 if tc.Errors != nil { 3888 if !reflect.DeepEqual(es, tc.Errors) { 3889 t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es) 3890 } 3891 } 3892 } 3893 } 3894 3895 func TestSchemaSet_ValidateMaxItems(t *testing.T) { 3896 cases := map[string]struct { 3897 Schema map[string]*Schema 3898 State *terraform.InstanceState 3899 Config map[string]interface{} 3900 ConfigVariables map[string]string 3901 Diff *terraform.InstanceDiff 3902 Err bool 3903 Errors []error 3904 }{ 3905 "#0": { 3906 Schema: map[string]*Schema{ 3907 "aliases": &Schema{ 3908 Type: TypeSet, 3909 Optional: true, 3910 MaxItems: 1, 3911 Elem: &Schema{Type: TypeString}, 3912 }, 3913 }, 3914 State: nil, 3915 Config: map[string]interface{}{ 3916 "aliases": []interface{}{"foo", "bar"}, 3917 }, 3918 Diff: nil, 3919 Err: true, 3920 Errors: []error{ 3921 fmt.Errorf("aliases: attribute supports 1 item maximum, config has 2 declared"), 3922 }, 3923 }, 3924 "#1": { 3925 Schema: map[string]*Schema{ 3926 "aliases": &Schema{ 3927 Type: TypeSet, 3928 Optional: true, 3929 Elem: &Schema{Type: TypeString}, 3930 }, 3931 }, 3932 State: nil, 3933 Config: map[string]interface{}{ 3934 "aliases": []interface{}{"foo", "bar"}, 3935 }, 3936 Diff: nil, 3937 Err: false, 3938 Errors: nil, 3939 }, 3940 "#2": { 3941 Schema: map[string]*Schema{ 3942 "aliases": &Schema{ 3943 Type: TypeSet, 3944 Optional: true, 3945 MaxItems: 1, 3946 Elem: &Schema{Type: TypeString}, 3947 }, 3948 }, 3949 State: nil, 3950 Config: map[string]interface{}{ 3951 "aliases": []interface{}{"foo"}, 3952 }, 3953 Diff: nil, 3954 Err: false, 3955 Errors: nil, 3956 }, 3957 } 3958 3959 for tn, tc := range cases { 3960 c, err := config.NewRawConfig(tc.Config) 3961 if err != nil { 3962 t.Fatalf("%q: err: %s", tn, err) 3963 } 3964 _, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) 3965 3966 if len(es) > 0 != tc.Err { 3967 if len(es) == 0 { 3968 t.Errorf("%q: no errors", tn) 3969 } 3970 3971 for _, e := range es { 3972 t.Errorf("%q: err: %s", tn, e) 3973 } 3974 3975 t.FailNow() 3976 } 3977 3978 if tc.Errors != nil { 3979 if !reflect.DeepEqual(es, tc.Errors) { 3980 t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es) 3981 } 3982 } 3983 } 3984 } 3985 3986 func TestSchemaSet_ValidateMinItems(t *testing.T) { 3987 cases := map[string]struct { 3988 Schema map[string]*Schema 3989 State *terraform.InstanceState 3990 Config map[string]interface{} 3991 ConfigVariables map[string]string 3992 Diff *terraform.InstanceDiff 3993 Err bool 3994 Errors []error 3995 }{ 3996 "#0": { 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", "bar"}, 4008 }, 4009 Diff: nil, 4010 Err: false, 4011 Errors: nil, 4012 }, 4013 "#1": { 4014 Schema: map[string]*Schema{ 4015 "aliases": &Schema{ 4016 Type: TypeSet, 4017 Optional: true, 4018 Elem: &Schema{Type: TypeString}, 4019 }, 4020 }, 4021 State: nil, 4022 Config: map[string]interface{}{ 4023 "aliases": []interface{}{"foo", "bar"}, 4024 }, 4025 Diff: nil, 4026 Err: false, 4027 Errors: nil, 4028 }, 4029 "#2": { 4030 Schema: map[string]*Schema{ 4031 "aliases": &Schema{ 4032 Type: TypeSet, 4033 Optional: true, 4034 MinItems: 2, 4035 Elem: &Schema{Type: TypeString}, 4036 }, 4037 }, 4038 State: nil, 4039 Config: map[string]interface{}{ 4040 "aliases": []interface{}{"foo"}, 4041 }, 4042 Diff: nil, 4043 Err: true, 4044 Errors: []error{ 4045 fmt.Errorf("aliases: attribute supports 2 item as a minimum, config has 1 declared"), 4046 }, 4047 }, 4048 } 4049 4050 for tn, tc := range cases { 4051 c, err := config.NewRawConfig(tc.Config) 4052 if err != nil { 4053 t.Fatalf("%q: err: %s", tn, err) 4054 } 4055 _, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) 4056 4057 if len(es) > 0 != tc.Err { 4058 if len(es) == 0 { 4059 t.Errorf("%q: no errors", tn) 4060 } 4061 4062 for _, e := range es { 4063 t.Errorf("%q: err: %s", tn, e) 4064 } 4065 4066 t.FailNow() 4067 } 4068 4069 if tc.Errors != nil { 4070 if !reflect.DeepEqual(es, tc.Errors) { 4071 t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es) 4072 } 4073 } 4074 } 4075 }