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