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