github.com/recobe182/terraform@v0.8.5-0.20170117231232-49ab22a935b7/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 "Complex structure with set of computed string should mark root set as computed": { 3272 Schema: map[string]*Schema{ 3273 "outer": &Schema{ 3274 Type: TypeSet, 3275 Optional: true, 3276 Elem: &Resource{ 3277 Schema: map[string]*Schema{ 3278 "outer_str": &Schema{ 3279 Type: TypeString, 3280 Optional: true, 3281 }, 3282 "inner": &Schema{ 3283 Type: TypeSet, 3284 Optional: true, 3285 Elem: &Resource{ 3286 Schema: map[string]*Schema{ 3287 "inner_str": &Schema{ 3288 Type: TypeString, 3289 Optional: true, 3290 }, 3291 }, 3292 }, 3293 Set: func(v interface{}) int { 3294 return 2 3295 }, 3296 }, 3297 }, 3298 }, 3299 Set: func(v interface{}) int { 3300 return 1 3301 }, 3302 }, 3303 }, 3304 3305 State: nil, 3306 3307 Config: map[string]interface{}{ 3308 "outer": []map[string]interface{}{ 3309 map[string]interface{}{ 3310 "outer_str": "foo", 3311 "inner": []map[string]interface{}{ 3312 map[string]interface{}{ 3313 "inner_str": "${var.bar}", 3314 }, 3315 }, 3316 }, 3317 }, 3318 }, 3319 3320 ConfigVariables: map[string]ast.Variable{ 3321 "var.bar": interfaceToVariableSwallowError(config.UnknownVariableValue), 3322 }, 3323 3324 ExpectedDiff: &terraform.InstanceDiff{ 3325 Attributes: map[string]*terraform.ResourceAttrDiff{ 3326 "outer.#": &terraform.ResourceAttrDiff{ 3327 Old: "0", 3328 New: "1", 3329 }, 3330 "outer.~1.outer_str": &terraform.ResourceAttrDiff{ 3331 Old: "", 3332 New: "foo", 3333 }, 3334 "outer.~1.inner.#": &terraform.ResourceAttrDiff{ 3335 Old: "0", 3336 New: "1", 3337 }, 3338 "outer.~1.inner.~2.inner_str": &terraform.ResourceAttrDiff{ 3339 Old: "", 3340 New: "${var.bar}", 3341 NewComputed: true, 3342 }, 3343 }, 3344 }, 3345 3346 Err: false, 3347 }, 3348 3349 "Complex structure with complex list of computed string should mark root set as computed": { 3350 Schema: map[string]*Schema{ 3351 "outer": &Schema{ 3352 Type: TypeSet, 3353 Optional: true, 3354 Elem: &Resource{ 3355 Schema: map[string]*Schema{ 3356 "outer_str": &Schema{ 3357 Type: TypeString, 3358 Optional: true, 3359 }, 3360 "inner": &Schema{ 3361 Type: TypeList, 3362 Optional: true, 3363 Elem: &Resource{ 3364 Schema: map[string]*Schema{ 3365 "inner_str": &Schema{ 3366 Type: TypeString, 3367 Optional: true, 3368 }, 3369 }, 3370 }, 3371 }, 3372 }, 3373 }, 3374 Set: func(v interface{}) int { 3375 return 1 3376 }, 3377 }, 3378 }, 3379 3380 State: nil, 3381 3382 Config: map[string]interface{}{ 3383 "outer": []map[string]interface{}{ 3384 map[string]interface{}{ 3385 "outer_str": "foo", 3386 "inner": []map[string]interface{}{ 3387 map[string]interface{}{ 3388 "inner_str": "${var.bar}", 3389 }, 3390 }, 3391 }, 3392 }, 3393 }, 3394 3395 ConfigVariables: map[string]ast.Variable{ 3396 "var.bar": interfaceToVariableSwallowError(config.UnknownVariableValue), 3397 }, 3398 3399 ExpectedDiff: &terraform.InstanceDiff{ 3400 Attributes: map[string]*terraform.ResourceAttrDiff{ 3401 "outer.#": &terraform.ResourceAttrDiff{ 3402 Old: "0", 3403 New: "1", 3404 }, 3405 "outer.~1.outer_str": &terraform.ResourceAttrDiff{ 3406 Old: "", 3407 New: "foo", 3408 }, 3409 "outer.~1.inner.#": &terraform.ResourceAttrDiff{ 3410 Old: "0", 3411 New: "1", 3412 }, 3413 "outer.~1.inner.0.inner_str": &terraform.ResourceAttrDiff{ 3414 Old: "", 3415 New: "${var.bar}", 3416 NewComputed: true, 3417 }, 3418 }, 3419 }, 3420 3421 Err: false, 3422 }, 3423 } 3424 3425 for tn, tc := range cases { 3426 t.Run(tn, func(t *testing.T) { 3427 c, err := config.NewRawConfig(tc.Config) 3428 if err != nil { 3429 t.Fatalf("#%q err: %s", tn, err) 3430 } 3431 3432 if len(tc.ConfigVariables) > 0 { 3433 if err := c.Interpolate(tc.ConfigVariables); err != nil { 3434 t.Fatalf("#%q err: %s", tn, err) 3435 } 3436 } 3437 3438 d, err := schemaMap(tc.Schema).Diff( 3439 tc.State, terraform.NewResourceConfig(c)) 3440 if err != nil != tc.Err { 3441 t.Fatalf("#%q err: %s", tn, err) 3442 } 3443 3444 if !reflect.DeepEqual(tc.ExpectedDiff, d) { 3445 t.Fatalf("#%q:\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.ExpectedDiff, d) 3446 } 3447 }) 3448 } 3449 } 3450 3451 func TestSchemaMap_Validate(t *testing.T) { 3452 cases := map[string]struct { 3453 Schema map[string]*Schema 3454 Config map[string]interface{} 3455 Vars map[string]string 3456 Err bool 3457 Errors []error 3458 Warnings []string 3459 }{ 3460 "Good": { 3461 Schema: map[string]*Schema{ 3462 "availability_zone": &Schema{ 3463 Type: TypeString, 3464 Optional: true, 3465 Computed: true, 3466 ForceNew: true, 3467 }, 3468 }, 3469 3470 Config: map[string]interface{}{ 3471 "availability_zone": "foo", 3472 }, 3473 }, 3474 3475 "Good, because the var is not set and that error will come elsewhere": { 3476 Schema: map[string]*Schema{ 3477 "size": &Schema{ 3478 Type: TypeInt, 3479 Required: true, 3480 }, 3481 }, 3482 3483 Config: map[string]interface{}{ 3484 "size": "${var.foo}", 3485 }, 3486 3487 Vars: map[string]string{ 3488 "var.foo": config.UnknownVariableValue, 3489 }, 3490 }, 3491 3492 "Required field not set": { 3493 Schema: map[string]*Schema{ 3494 "availability_zone": &Schema{ 3495 Type: TypeString, 3496 Required: true, 3497 }, 3498 }, 3499 3500 Config: map[string]interface{}{}, 3501 3502 Err: true, 3503 }, 3504 3505 "Invalid basic type": { 3506 Schema: map[string]*Schema{ 3507 "port": &Schema{ 3508 Type: TypeInt, 3509 Required: true, 3510 }, 3511 }, 3512 3513 Config: map[string]interface{}{ 3514 "port": "I am invalid", 3515 }, 3516 3517 Err: true, 3518 }, 3519 3520 "Invalid complex type": { 3521 Schema: map[string]*Schema{ 3522 "user_data": &Schema{ 3523 Type: TypeString, 3524 Optional: true, 3525 }, 3526 }, 3527 3528 Config: map[string]interface{}{ 3529 "user_data": []interface{}{ 3530 map[string]interface{}{ 3531 "foo": "bar", 3532 }, 3533 }, 3534 }, 3535 3536 Err: true, 3537 }, 3538 3539 "Bad type, interpolated": { 3540 Schema: map[string]*Schema{ 3541 "size": &Schema{ 3542 Type: TypeInt, 3543 Required: true, 3544 }, 3545 }, 3546 3547 Config: map[string]interface{}{ 3548 "size": "${var.foo}", 3549 }, 3550 3551 Vars: map[string]string{ 3552 "var.foo": "nope", 3553 }, 3554 3555 Err: true, 3556 }, 3557 3558 "Required but has DefaultFunc": { 3559 Schema: map[string]*Schema{ 3560 "availability_zone": &Schema{ 3561 Type: TypeString, 3562 Required: true, 3563 DefaultFunc: func() (interface{}, error) { 3564 return "foo", nil 3565 }, 3566 }, 3567 }, 3568 3569 Config: nil, 3570 }, 3571 3572 "Required but has DefaultFunc return nil": { 3573 Schema: map[string]*Schema{ 3574 "availability_zone": &Schema{ 3575 Type: TypeString, 3576 Required: true, 3577 DefaultFunc: func() (interface{}, error) { 3578 return nil, nil 3579 }, 3580 }, 3581 }, 3582 3583 Config: nil, 3584 3585 Err: true, 3586 }, 3587 3588 "Optional sub-resource": { 3589 Schema: map[string]*Schema{ 3590 "ingress": &Schema{ 3591 Type: TypeList, 3592 Elem: &Resource{ 3593 Schema: map[string]*Schema{ 3594 "from": &Schema{ 3595 Type: TypeInt, 3596 Required: true, 3597 }, 3598 }, 3599 }, 3600 }, 3601 }, 3602 3603 Config: map[string]interface{}{}, 3604 3605 Err: false, 3606 }, 3607 3608 "Sub-resource is the wrong type": { 3609 Schema: map[string]*Schema{ 3610 "ingress": &Schema{ 3611 Type: TypeList, 3612 Required: true, 3613 Elem: &Resource{ 3614 Schema: map[string]*Schema{ 3615 "from": &Schema{ 3616 Type: TypeInt, 3617 Required: true, 3618 }, 3619 }, 3620 }, 3621 }, 3622 }, 3623 3624 Config: map[string]interface{}{ 3625 "ingress": []interface{}{"foo"}, 3626 }, 3627 3628 Err: true, 3629 }, 3630 3631 "Not a list": { 3632 Schema: map[string]*Schema{ 3633 "ingress": &Schema{ 3634 Type: TypeList, 3635 Elem: &Resource{ 3636 Schema: map[string]*Schema{ 3637 "from": &Schema{ 3638 Type: TypeInt, 3639 Required: true, 3640 }, 3641 }, 3642 }, 3643 }, 3644 }, 3645 3646 Config: map[string]interface{}{ 3647 "ingress": "foo", 3648 }, 3649 3650 Err: true, 3651 }, 3652 3653 "Required sub-resource field": { 3654 Schema: map[string]*Schema{ 3655 "ingress": &Schema{ 3656 Type: TypeList, 3657 Elem: &Resource{ 3658 Schema: map[string]*Schema{ 3659 "from": &Schema{ 3660 Type: TypeInt, 3661 Required: true, 3662 }, 3663 }, 3664 }, 3665 }, 3666 }, 3667 3668 Config: map[string]interface{}{ 3669 "ingress": []interface{}{ 3670 map[string]interface{}{}, 3671 }, 3672 }, 3673 3674 Err: true, 3675 }, 3676 3677 "Good sub-resource": { 3678 Schema: map[string]*Schema{ 3679 "ingress": &Schema{ 3680 Type: TypeList, 3681 Optional: true, 3682 Elem: &Resource{ 3683 Schema: map[string]*Schema{ 3684 "from": &Schema{ 3685 Type: TypeInt, 3686 Required: true, 3687 }, 3688 }, 3689 }, 3690 }, 3691 }, 3692 3693 Config: map[string]interface{}{ 3694 "ingress": []interface{}{ 3695 map[string]interface{}{ 3696 "from": 80, 3697 }, 3698 }, 3699 }, 3700 3701 Err: false, 3702 }, 3703 3704 "Invalid/unknown field": { 3705 Schema: map[string]*Schema{ 3706 "availability_zone": &Schema{ 3707 Type: TypeString, 3708 Optional: true, 3709 Computed: true, 3710 ForceNew: true, 3711 }, 3712 }, 3713 3714 Config: map[string]interface{}{ 3715 "foo": "bar", 3716 }, 3717 3718 Err: true, 3719 }, 3720 3721 "Invalid/unknown field with computed value": { 3722 Schema: map[string]*Schema{ 3723 "availability_zone": &Schema{ 3724 Type: TypeString, 3725 Optional: true, 3726 Computed: true, 3727 ForceNew: true, 3728 }, 3729 }, 3730 3731 Config: map[string]interface{}{ 3732 "foo": "${var.foo}", 3733 }, 3734 3735 Vars: map[string]string{ 3736 "var.foo": config.UnknownVariableValue, 3737 }, 3738 3739 Err: true, 3740 }, 3741 3742 "Computed field set": { 3743 Schema: map[string]*Schema{ 3744 "availability_zone": &Schema{ 3745 Type: TypeString, 3746 Computed: true, 3747 }, 3748 }, 3749 3750 Config: map[string]interface{}{ 3751 "availability_zone": "bar", 3752 }, 3753 3754 Err: true, 3755 }, 3756 3757 "Not a set": { 3758 Schema: map[string]*Schema{ 3759 "ports": &Schema{ 3760 Type: TypeSet, 3761 Required: true, 3762 Elem: &Schema{Type: TypeInt}, 3763 Set: func(a interface{}) int { 3764 return a.(int) 3765 }, 3766 }, 3767 }, 3768 3769 Config: map[string]interface{}{ 3770 "ports": "foo", 3771 }, 3772 3773 Err: true, 3774 }, 3775 3776 "Maps": { 3777 Schema: map[string]*Schema{ 3778 "user_data": &Schema{ 3779 Type: TypeMap, 3780 Optional: true, 3781 }, 3782 }, 3783 3784 Config: map[string]interface{}{ 3785 "user_data": "foo", 3786 }, 3787 3788 Err: true, 3789 }, 3790 3791 "Good map: data surrounded by extra slice": { 3792 Schema: map[string]*Schema{ 3793 "user_data": &Schema{ 3794 Type: TypeMap, 3795 Optional: true, 3796 }, 3797 }, 3798 3799 Config: map[string]interface{}{ 3800 "user_data": []interface{}{ 3801 map[string]interface{}{ 3802 "foo": "bar", 3803 }, 3804 }, 3805 }, 3806 }, 3807 3808 "Good map": { 3809 Schema: map[string]*Schema{ 3810 "user_data": &Schema{ 3811 Type: TypeMap, 3812 Optional: true, 3813 }, 3814 }, 3815 3816 Config: map[string]interface{}{ 3817 "user_data": map[string]interface{}{ 3818 "foo": "bar", 3819 }, 3820 }, 3821 }, 3822 3823 "Bad map: just a slice": { 3824 Schema: map[string]*Schema{ 3825 "user_data": &Schema{ 3826 Type: TypeMap, 3827 Optional: true, 3828 }, 3829 }, 3830 3831 Config: map[string]interface{}{ 3832 "user_data": []interface{}{ 3833 "foo", 3834 }, 3835 }, 3836 3837 Err: true, 3838 }, 3839 3840 "Good set: config has slice with single interpolated value": { 3841 Schema: map[string]*Schema{ 3842 "security_groups": &Schema{ 3843 Type: TypeSet, 3844 Optional: true, 3845 Computed: true, 3846 ForceNew: true, 3847 Elem: &Schema{Type: TypeString}, 3848 Set: func(v interface{}) int { 3849 return len(v.(string)) 3850 }, 3851 }, 3852 }, 3853 3854 Config: map[string]interface{}{ 3855 "security_groups": []interface{}{"${var.foo}"}, 3856 }, 3857 3858 Err: false, 3859 }, 3860 3861 "Bad set: config has single interpolated value": { 3862 Schema: map[string]*Schema{ 3863 "security_groups": &Schema{ 3864 Type: TypeSet, 3865 Optional: true, 3866 Computed: true, 3867 ForceNew: true, 3868 Elem: &Schema{Type: TypeString}, 3869 }, 3870 }, 3871 3872 Config: map[string]interface{}{ 3873 "security_groups": "${var.foo}", 3874 }, 3875 3876 Err: true, 3877 }, 3878 3879 "Bad, subresource should not allow unknown elements": { 3880 Schema: map[string]*Schema{ 3881 "ingress": &Schema{ 3882 Type: TypeList, 3883 Optional: true, 3884 Elem: &Resource{ 3885 Schema: map[string]*Schema{ 3886 "port": &Schema{ 3887 Type: TypeInt, 3888 Required: true, 3889 }, 3890 }, 3891 }, 3892 }, 3893 }, 3894 3895 Config: map[string]interface{}{ 3896 "ingress": []interface{}{ 3897 map[string]interface{}{ 3898 "port": 80, 3899 "other": "yes", 3900 }, 3901 }, 3902 }, 3903 3904 Err: true, 3905 }, 3906 3907 "Bad, subresource should not allow invalid types": { 3908 Schema: map[string]*Schema{ 3909 "ingress": &Schema{ 3910 Type: TypeList, 3911 Optional: true, 3912 Elem: &Resource{ 3913 Schema: map[string]*Schema{ 3914 "port": &Schema{ 3915 Type: TypeInt, 3916 Required: true, 3917 }, 3918 }, 3919 }, 3920 }, 3921 }, 3922 3923 Config: map[string]interface{}{ 3924 "ingress": []interface{}{ 3925 map[string]interface{}{ 3926 "port": "bad", 3927 }, 3928 }, 3929 }, 3930 3931 Err: true, 3932 }, 3933 3934 "Bad, should not allow lists to be assigned to string attributes": { 3935 Schema: map[string]*Schema{ 3936 "availability_zone": &Schema{ 3937 Type: TypeString, 3938 Required: true, 3939 }, 3940 }, 3941 3942 Config: map[string]interface{}{ 3943 "availability_zone": []interface{}{"foo", "bar", "baz"}, 3944 }, 3945 3946 Err: true, 3947 }, 3948 3949 "Bad, should not allow maps to be assigned to string attributes": { 3950 Schema: map[string]*Schema{ 3951 "availability_zone": &Schema{ 3952 Type: TypeString, 3953 Required: true, 3954 }, 3955 }, 3956 3957 Config: map[string]interface{}{ 3958 "availability_zone": map[string]interface{}{"foo": "bar", "baz": "thing"}, 3959 }, 3960 3961 Err: true, 3962 }, 3963 3964 "Deprecated attribute usage generates warning, but not error": { 3965 Schema: map[string]*Schema{ 3966 "old_news": &Schema{ 3967 Type: TypeString, 3968 Optional: true, 3969 Deprecated: "please use 'new_news' instead", 3970 }, 3971 }, 3972 3973 Config: map[string]interface{}{ 3974 "old_news": "extra extra!", 3975 }, 3976 3977 Err: false, 3978 3979 Warnings: []string{ 3980 "\"old_news\": [DEPRECATED] please use 'new_news' instead", 3981 }, 3982 }, 3983 3984 "Deprecated generates no warnings if attr not used": { 3985 Schema: map[string]*Schema{ 3986 "old_news": &Schema{ 3987 Type: TypeString, 3988 Optional: true, 3989 Deprecated: "please use 'new_news' instead", 3990 }, 3991 }, 3992 3993 Err: false, 3994 3995 Warnings: nil, 3996 }, 3997 3998 "Removed attribute usage generates error": { 3999 Schema: map[string]*Schema{ 4000 "long_gone": &Schema{ 4001 Type: TypeString, 4002 Optional: true, 4003 Removed: "no longer supported by Cloud API", 4004 }, 4005 }, 4006 4007 Config: map[string]interface{}{ 4008 "long_gone": "still here!", 4009 }, 4010 4011 Err: true, 4012 Errors: []error{ 4013 fmt.Errorf("\"long_gone\": [REMOVED] no longer supported by Cloud API"), 4014 }, 4015 }, 4016 4017 "Removed generates no errors if attr not used": { 4018 Schema: map[string]*Schema{ 4019 "long_gone": &Schema{ 4020 Type: TypeString, 4021 Optional: true, 4022 Removed: "no longer supported by Cloud API", 4023 }, 4024 }, 4025 4026 Err: false, 4027 }, 4028 4029 "Conflicting attributes generate error": { 4030 Schema: map[string]*Schema{ 4031 "whitelist": &Schema{ 4032 Type: TypeString, 4033 Optional: true, 4034 }, 4035 "blacklist": &Schema{ 4036 Type: TypeString, 4037 Optional: true, 4038 ConflictsWith: []string{"whitelist"}, 4039 }, 4040 }, 4041 4042 Config: map[string]interface{}{ 4043 "whitelist": "white-val", 4044 "blacklist": "black-val", 4045 }, 4046 4047 Err: true, 4048 Errors: []error{ 4049 fmt.Errorf("\"blacklist\": conflicts with whitelist (\"white-val\")"), 4050 }, 4051 }, 4052 4053 "Required attribute & undefined conflicting optional are good": { 4054 Schema: map[string]*Schema{ 4055 "required_att": &Schema{ 4056 Type: TypeString, 4057 Required: true, 4058 }, 4059 "optional_att": &Schema{ 4060 Type: TypeString, 4061 Optional: true, 4062 ConflictsWith: []string{"required_att"}, 4063 }, 4064 }, 4065 4066 Config: map[string]interface{}{ 4067 "required_att": "required-val", 4068 }, 4069 4070 Err: false, 4071 }, 4072 4073 "Required conflicting attribute & defined optional generate error": { 4074 Schema: map[string]*Schema{ 4075 "required_att": &Schema{ 4076 Type: TypeString, 4077 Required: true, 4078 }, 4079 "optional_att": &Schema{ 4080 Type: TypeString, 4081 Optional: true, 4082 ConflictsWith: []string{"required_att"}, 4083 }, 4084 }, 4085 4086 Config: map[string]interface{}{ 4087 "required_att": "required-val", 4088 "optional_att": "optional-val", 4089 }, 4090 4091 Err: true, 4092 Errors: []error{ 4093 fmt.Errorf(`"optional_att": conflicts with required_att ("required-val")`), 4094 }, 4095 }, 4096 4097 "Computed + Optional fields conflicting with each other": { 4098 Schema: map[string]*Schema{ 4099 "foo_att": &Schema{ 4100 Type: TypeString, 4101 Optional: true, 4102 Computed: true, 4103 ConflictsWith: []string{"bar_att"}, 4104 }, 4105 "bar_att": &Schema{ 4106 Type: TypeString, 4107 Optional: true, 4108 Computed: true, 4109 ConflictsWith: []string{"foo_att"}, 4110 }, 4111 }, 4112 4113 Config: map[string]interface{}{ 4114 "foo_att": "foo-val", 4115 "bar_att": "bar-val", 4116 }, 4117 4118 Err: true, 4119 Errors: []error{ 4120 fmt.Errorf(`"foo_att": conflicts with bar_att ("bar-val")`), 4121 fmt.Errorf(`"bar_att": conflicts with foo_att ("foo-val")`), 4122 }, 4123 }, 4124 4125 "Computed + Optional fields NOT conflicting with each other": { 4126 Schema: map[string]*Schema{ 4127 "foo_att": &Schema{ 4128 Type: TypeString, 4129 Optional: true, 4130 Computed: true, 4131 ConflictsWith: []string{"bar_att"}, 4132 }, 4133 "bar_att": &Schema{ 4134 Type: TypeString, 4135 Optional: true, 4136 Computed: true, 4137 ConflictsWith: []string{"foo_att"}, 4138 }, 4139 }, 4140 4141 Config: map[string]interface{}{ 4142 "foo_att": "foo-val", 4143 }, 4144 4145 Err: false, 4146 }, 4147 4148 "Computed + Optional fields that conflict with none set": { 4149 Schema: map[string]*Schema{ 4150 "foo_att": &Schema{ 4151 Type: TypeString, 4152 Optional: true, 4153 Computed: true, 4154 ConflictsWith: []string{"bar_att"}, 4155 }, 4156 "bar_att": &Schema{ 4157 Type: TypeString, 4158 Optional: true, 4159 Computed: true, 4160 ConflictsWith: []string{"foo_att"}, 4161 }, 4162 }, 4163 4164 Config: map[string]interface{}{}, 4165 4166 Err: false, 4167 }, 4168 4169 "Good with ValidateFunc": { 4170 Schema: map[string]*Schema{ 4171 "validate_me": &Schema{ 4172 Type: TypeString, 4173 Required: true, 4174 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4175 return 4176 }, 4177 }, 4178 }, 4179 Config: map[string]interface{}{ 4180 "validate_me": "valid", 4181 }, 4182 Err: false, 4183 }, 4184 4185 "Bad with ValidateFunc": { 4186 Schema: map[string]*Schema{ 4187 "validate_me": &Schema{ 4188 Type: TypeString, 4189 Required: true, 4190 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4191 es = append(es, fmt.Errorf("something is not right here")) 4192 return 4193 }, 4194 }, 4195 }, 4196 Config: map[string]interface{}{ 4197 "validate_me": "invalid", 4198 }, 4199 Err: true, 4200 Errors: []error{ 4201 fmt.Errorf(`something is not right here`), 4202 }, 4203 }, 4204 4205 "ValidateFunc not called when type does not match": { 4206 Schema: map[string]*Schema{ 4207 "number": &Schema{ 4208 Type: TypeInt, 4209 Required: true, 4210 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4211 t.Fatalf("Should not have gotten validate call") 4212 return 4213 }, 4214 }, 4215 }, 4216 Config: map[string]interface{}{ 4217 "number": "NaN", 4218 }, 4219 Err: true, 4220 }, 4221 4222 "ValidateFunc gets decoded type": { 4223 Schema: map[string]*Schema{ 4224 "maybe": &Schema{ 4225 Type: TypeBool, 4226 Required: true, 4227 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4228 if _, ok := v.(bool); !ok { 4229 t.Fatalf("Expected bool, got: %#v", v) 4230 } 4231 return 4232 }, 4233 }, 4234 }, 4235 Config: map[string]interface{}{ 4236 "maybe": "true", 4237 }, 4238 }, 4239 4240 "ValidateFunc is not called with a computed value": { 4241 Schema: map[string]*Schema{ 4242 "validate_me": &Schema{ 4243 Type: TypeString, 4244 Required: true, 4245 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4246 es = append(es, fmt.Errorf("something is not right here")) 4247 return 4248 }, 4249 }, 4250 }, 4251 Config: map[string]interface{}{ 4252 "validate_me": "${var.foo}", 4253 }, 4254 Vars: map[string]string{ 4255 "var.foo": config.UnknownVariableValue, 4256 }, 4257 4258 Err: false, 4259 }, 4260 } 4261 4262 for tn, tc := range cases { 4263 c, err := config.NewRawConfig(tc.Config) 4264 if err != nil { 4265 t.Fatalf("err: %s", err) 4266 } 4267 if tc.Vars != nil { 4268 vars := make(map[string]ast.Variable) 4269 for k, v := range tc.Vars { 4270 vars[k] = ast.Variable{Value: v, Type: ast.TypeString} 4271 } 4272 4273 if err := c.Interpolate(vars); err != nil { 4274 t.Fatalf("err: %s", err) 4275 } 4276 } 4277 4278 ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) 4279 if len(es) > 0 != tc.Err { 4280 if len(es) == 0 { 4281 t.Errorf("%q: no errors", tn) 4282 } 4283 4284 for _, e := range es { 4285 t.Errorf("%q: err: %s", tn, e) 4286 } 4287 4288 t.FailNow() 4289 } 4290 4291 if !reflect.DeepEqual(ws, tc.Warnings) { 4292 t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws) 4293 } 4294 4295 if tc.Errors != nil { 4296 sort.Sort(errorSort(es)) 4297 sort.Sort(errorSort(tc.Errors)) 4298 4299 if !reflect.DeepEqual(es, tc.Errors) { 4300 t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es) 4301 } 4302 } 4303 } 4304 } 4305 4306 func TestSchemaSet_ValidateMaxItems(t *testing.T) { 4307 cases := map[string]struct { 4308 Schema map[string]*Schema 4309 State *terraform.InstanceState 4310 Config map[string]interface{} 4311 ConfigVariables map[string]string 4312 Diff *terraform.InstanceDiff 4313 Err bool 4314 Errors []error 4315 }{ 4316 "#0": { 4317 Schema: map[string]*Schema{ 4318 "aliases": &Schema{ 4319 Type: TypeSet, 4320 Optional: true, 4321 MaxItems: 1, 4322 Elem: &Schema{Type: TypeString}, 4323 }, 4324 }, 4325 State: nil, 4326 Config: map[string]interface{}{ 4327 "aliases": []interface{}{"foo", "bar"}, 4328 }, 4329 Diff: nil, 4330 Err: true, 4331 Errors: []error{ 4332 fmt.Errorf("aliases: attribute supports 1 item maximum, config has 2 declared"), 4333 }, 4334 }, 4335 "#1": { 4336 Schema: map[string]*Schema{ 4337 "aliases": &Schema{ 4338 Type: TypeSet, 4339 Optional: true, 4340 Elem: &Schema{Type: TypeString}, 4341 }, 4342 }, 4343 State: nil, 4344 Config: map[string]interface{}{ 4345 "aliases": []interface{}{"foo", "bar"}, 4346 }, 4347 Diff: nil, 4348 Err: false, 4349 Errors: nil, 4350 }, 4351 "#2": { 4352 Schema: map[string]*Schema{ 4353 "aliases": &Schema{ 4354 Type: TypeSet, 4355 Optional: true, 4356 MaxItems: 1, 4357 Elem: &Schema{Type: TypeString}, 4358 }, 4359 }, 4360 State: nil, 4361 Config: map[string]interface{}{ 4362 "aliases": []interface{}{"foo"}, 4363 }, 4364 Diff: nil, 4365 Err: false, 4366 Errors: nil, 4367 }, 4368 } 4369 4370 for tn, tc := range cases { 4371 c, err := config.NewRawConfig(tc.Config) 4372 if err != nil { 4373 t.Fatalf("%q: err: %s", tn, err) 4374 } 4375 _, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) 4376 4377 if len(es) > 0 != tc.Err { 4378 if len(es) == 0 { 4379 t.Errorf("%q: no errors", tn) 4380 } 4381 4382 for _, e := range es { 4383 t.Errorf("%q: err: %s", tn, e) 4384 } 4385 4386 t.FailNow() 4387 } 4388 4389 if tc.Errors != nil { 4390 if !reflect.DeepEqual(es, tc.Errors) { 4391 t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es) 4392 } 4393 } 4394 } 4395 } 4396 4397 func TestSchemaSet_ValidateMinItems(t *testing.T) { 4398 cases := map[string]struct { 4399 Schema map[string]*Schema 4400 State *terraform.InstanceState 4401 Config map[string]interface{} 4402 ConfigVariables map[string]string 4403 Diff *terraform.InstanceDiff 4404 Err bool 4405 Errors []error 4406 }{ 4407 "#0": { 4408 Schema: map[string]*Schema{ 4409 "aliases": &Schema{ 4410 Type: TypeSet, 4411 Optional: true, 4412 MinItems: 2, 4413 Elem: &Schema{Type: TypeString}, 4414 }, 4415 }, 4416 State: nil, 4417 Config: map[string]interface{}{ 4418 "aliases": []interface{}{"foo", "bar"}, 4419 }, 4420 Diff: nil, 4421 Err: false, 4422 Errors: nil, 4423 }, 4424 "#1": { 4425 Schema: map[string]*Schema{ 4426 "aliases": &Schema{ 4427 Type: TypeSet, 4428 Optional: true, 4429 Elem: &Schema{Type: TypeString}, 4430 }, 4431 }, 4432 State: nil, 4433 Config: map[string]interface{}{ 4434 "aliases": []interface{}{"foo", "bar"}, 4435 }, 4436 Diff: nil, 4437 Err: false, 4438 Errors: nil, 4439 }, 4440 "#2": { 4441 Schema: map[string]*Schema{ 4442 "aliases": &Schema{ 4443 Type: TypeSet, 4444 Optional: true, 4445 MinItems: 2, 4446 Elem: &Schema{Type: TypeString}, 4447 }, 4448 }, 4449 State: nil, 4450 Config: map[string]interface{}{ 4451 "aliases": []interface{}{"foo"}, 4452 }, 4453 Diff: nil, 4454 Err: true, 4455 Errors: []error{ 4456 fmt.Errorf("aliases: attribute supports 2 item as a minimum, config has 1 declared"), 4457 }, 4458 }, 4459 } 4460 4461 for tn, tc := range cases { 4462 c, err := config.NewRawConfig(tc.Config) 4463 if err != nil { 4464 t.Fatalf("%q: err: %s", tn, err) 4465 } 4466 _, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) 4467 4468 if len(es) > 0 != tc.Err { 4469 if len(es) == 0 { 4470 t.Errorf("%q: no errors", tn) 4471 } 4472 4473 for _, e := range es { 4474 t.Errorf("%q: err: %s", tn, e) 4475 } 4476 4477 t.FailNow() 4478 } 4479 4480 if tc.Errors != nil { 4481 if !reflect.DeepEqual(es, tc.Errors) { 4482 t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es) 4483 } 4484 } 4485 } 4486 } 4487 4488 // errorSort implements sort.Interface to sort errors by their error message 4489 type errorSort []error 4490 4491 func (e errorSort) Len() int { return len(e) } 4492 func (e errorSort) Swap(i, j int) { e[i], e[j] = e[j], e[i] } 4493 func (e errorSort) Less(i, j int) bool { 4494 return e[i].Error() < e[j].Error() 4495 }