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