github.com/nevins-b/terraform@v0.3.8-0.20170215184714-bbae22007d5a/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 Name: "List decode with promotion", 587 Schema: map[string]*Schema{ 588 "ports": &Schema{ 589 Type: TypeList, 590 Required: true, 591 Elem: &Schema{Type: TypeInt}, 592 PromoteSingle: true, 593 }, 594 }, 595 596 State: nil, 597 598 Config: map[string]interface{}{ 599 "ports": "5", 600 }, 601 602 Diff: &terraform.InstanceDiff{ 603 Attributes: map[string]*terraform.ResourceAttrDiff{ 604 "ports.#": &terraform.ResourceAttrDiff{ 605 Old: "0", 606 New: "1", 607 }, 608 "ports.0": &terraform.ResourceAttrDiff{ 609 Old: "", 610 New: "5", 611 }, 612 }, 613 }, 614 615 Err: false, 616 }, 617 618 { 619 Name: "List decode with promotion with list", 620 Schema: map[string]*Schema{ 621 "ports": &Schema{ 622 Type: TypeList, 623 Required: true, 624 Elem: &Schema{Type: TypeInt}, 625 PromoteSingle: true, 626 }, 627 }, 628 629 State: nil, 630 631 Config: map[string]interface{}{ 632 "ports": []interface{}{"5"}, 633 }, 634 635 Diff: &terraform.InstanceDiff{ 636 Attributes: map[string]*terraform.ResourceAttrDiff{ 637 "ports.#": &terraform.ResourceAttrDiff{ 638 Old: "0", 639 New: "1", 640 }, 641 "ports.0": &terraform.ResourceAttrDiff{ 642 Old: "", 643 New: "5", 644 }, 645 }, 646 }, 647 648 Err: false, 649 }, 650 651 { 652 Schema: map[string]*Schema{ 653 "ports": &Schema{ 654 Type: TypeList, 655 Required: true, 656 Elem: &Schema{Type: TypeInt}, 657 }, 658 }, 659 660 State: nil, 661 662 Config: map[string]interface{}{ 663 "ports": []interface{}{1, "${var.foo}"}, 664 }, 665 666 ConfigVariables: map[string]ast.Variable{ 667 "var.foo": interfaceToVariableSwallowError([]interface{}{"2", "5"}), 668 }, 669 670 Diff: &terraform.InstanceDiff{ 671 Attributes: map[string]*terraform.ResourceAttrDiff{ 672 "ports.#": &terraform.ResourceAttrDiff{ 673 Old: "0", 674 New: "3", 675 }, 676 "ports.0": &terraform.ResourceAttrDiff{ 677 Old: "", 678 New: "1", 679 }, 680 "ports.1": &terraform.ResourceAttrDiff{ 681 Old: "", 682 New: "2", 683 }, 684 "ports.2": &terraform.ResourceAttrDiff{ 685 Old: "", 686 New: "5", 687 }, 688 }, 689 }, 690 691 Err: false, 692 }, 693 694 { 695 Schema: map[string]*Schema{ 696 "ports": &Schema{ 697 Type: TypeList, 698 Required: true, 699 Elem: &Schema{Type: TypeInt}, 700 }, 701 }, 702 703 State: nil, 704 705 Config: map[string]interface{}{ 706 "ports": []interface{}{1, "${var.foo}"}, 707 }, 708 709 ConfigVariables: map[string]ast.Variable{ 710 "var.foo": interfaceToVariableSwallowError([]interface{}{ 711 config.UnknownVariableValue, "5"}), 712 }, 713 714 Diff: &terraform.InstanceDiff{ 715 Attributes: map[string]*terraform.ResourceAttrDiff{ 716 "ports.#": &terraform.ResourceAttrDiff{ 717 Old: "0", 718 New: "", 719 NewComputed: true, 720 }, 721 }, 722 }, 723 724 Err: false, 725 }, 726 727 { 728 Schema: map[string]*Schema{ 729 "ports": &Schema{ 730 Type: TypeList, 731 Required: true, 732 Elem: &Schema{Type: TypeInt}, 733 }, 734 }, 735 736 State: &terraform.InstanceState{ 737 Attributes: map[string]string{ 738 "ports.#": "3", 739 "ports.0": "1", 740 "ports.1": "2", 741 "ports.2": "5", 742 }, 743 }, 744 745 Config: map[string]interface{}{ 746 "ports": []interface{}{1, 2, 5}, 747 }, 748 749 Diff: nil, 750 751 Err: false, 752 }, 753 754 { 755 Name: "", 756 Schema: map[string]*Schema{ 757 "ports": &Schema{ 758 Type: TypeList, 759 Required: true, 760 Elem: &Schema{Type: TypeInt}, 761 }, 762 }, 763 764 State: &terraform.InstanceState{ 765 Attributes: map[string]string{ 766 "ports.#": "2", 767 "ports.0": "1", 768 "ports.1": "2", 769 }, 770 }, 771 772 Config: map[string]interface{}{ 773 "ports": []interface{}{1, 2, 5}, 774 }, 775 776 Diff: &terraform.InstanceDiff{ 777 Attributes: map[string]*terraform.ResourceAttrDiff{ 778 "ports.#": &terraform.ResourceAttrDiff{ 779 Old: "2", 780 New: "3", 781 }, 782 "ports.2": &terraform.ResourceAttrDiff{ 783 Old: "", 784 New: "5", 785 }, 786 }, 787 }, 788 789 Err: false, 790 }, 791 792 { 793 Name: "", 794 Schema: map[string]*Schema{ 795 "ports": &Schema{ 796 Type: TypeList, 797 Required: true, 798 Elem: &Schema{Type: TypeInt}, 799 ForceNew: true, 800 }, 801 }, 802 803 State: nil, 804 805 Config: map[string]interface{}{ 806 "ports": []interface{}{1, 2, 5}, 807 }, 808 809 Diff: &terraform.InstanceDiff{ 810 Attributes: map[string]*terraform.ResourceAttrDiff{ 811 "ports.#": &terraform.ResourceAttrDiff{ 812 Old: "0", 813 New: "3", 814 RequiresNew: true, 815 }, 816 "ports.0": &terraform.ResourceAttrDiff{ 817 Old: "", 818 New: "1", 819 RequiresNew: true, 820 }, 821 "ports.1": &terraform.ResourceAttrDiff{ 822 Old: "", 823 New: "2", 824 RequiresNew: true, 825 }, 826 "ports.2": &terraform.ResourceAttrDiff{ 827 Old: "", 828 New: "5", 829 RequiresNew: true, 830 }, 831 }, 832 }, 833 834 Err: false, 835 }, 836 837 { 838 Name: "", 839 Schema: map[string]*Schema{ 840 "ports": &Schema{ 841 Type: TypeList, 842 Optional: true, 843 Computed: true, 844 Elem: &Schema{Type: TypeInt}, 845 }, 846 }, 847 848 State: nil, 849 850 Config: map[string]interface{}{}, 851 852 Diff: &terraform.InstanceDiff{ 853 Attributes: map[string]*terraform.ResourceAttrDiff{ 854 "ports.#": &terraform.ResourceAttrDiff{ 855 Old: "", 856 NewComputed: true, 857 }, 858 }, 859 }, 860 861 Err: false, 862 }, 863 864 { 865 Name: "Set", 866 Schema: map[string]*Schema{ 867 "ports": &Schema{ 868 Type: TypeSet, 869 Required: true, 870 Elem: &Schema{Type: TypeInt}, 871 Set: func(a interface{}) int { 872 return a.(int) 873 }, 874 }, 875 }, 876 877 State: nil, 878 879 Config: map[string]interface{}{ 880 "ports": []interface{}{5, 2, 1}, 881 }, 882 883 Diff: &terraform.InstanceDiff{ 884 Attributes: map[string]*terraform.ResourceAttrDiff{ 885 "ports.#": &terraform.ResourceAttrDiff{ 886 Old: "0", 887 New: "3", 888 }, 889 "ports.1": &terraform.ResourceAttrDiff{ 890 Old: "", 891 New: "1", 892 }, 893 "ports.2": &terraform.ResourceAttrDiff{ 894 Old: "", 895 New: "2", 896 }, 897 "ports.5": &terraform.ResourceAttrDiff{ 898 Old: "", 899 New: "5", 900 }, 901 }, 902 }, 903 904 Err: false, 905 }, 906 907 { 908 Name: "Set", 909 Schema: map[string]*Schema{ 910 "ports": &Schema{ 911 Type: TypeSet, 912 Computed: true, 913 Required: true, 914 Elem: &Schema{Type: TypeInt}, 915 Set: func(a interface{}) int { 916 return a.(int) 917 }, 918 }, 919 }, 920 921 State: &terraform.InstanceState{ 922 Attributes: map[string]string{ 923 "ports.#": "0", 924 }, 925 }, 926 927 Config: nil, 928 929 Diff: nil, 930 931 Err: false, 932 }, 933 934 { 935 Name: "Set", 936 Schema: map[string]*Schema{ 937 "ports": &Schema{ 938 Type: TypeSet, 939 Optional: true, 940 Computed: true, 941 Elem: &Schema{Type: TypeInt}, 942 Set: func(a interface{}) int { 943 return a.(int) 944 }, 945 }, 946 }, 947 948 State: nil, 949 950 Config: nil, 951 952 Diff: &terraform.InstanceDiff{ 953 Attributes: map[string]*terraform.ResourceAttrDiff{ 954 "ports.#": &terraform.ResourceAttrDiff{ 955 Old: "", 956 NewComputed: true, 957 }, 958 }, 959 }, 960 961 Err: false, 962 }, 963 964 { 965 Name: "Set", 966 Schema: map[string]*Schema{ 967 "ports": &Schema{ 968 Type: TypeSet, 969 Required: true, 970 Elem: &Schema{Type: TypeInt}, 971 Set: func(a interface{}) int { 972 return a.(int) 973 }, 974 }, 975 }, 976 977 State: nil, 978 979 Config: map[string]interface{}{ 980 "ports": []interface{}{"${var.foo}", 1}, 981 }, 982 983 ConfigVariables: map[string]ast.Variable{ 984 "var.foo": interfaceToVariableSwallowError([]interface{}{"2", "5"}), 985 }, 986 987 Diff: &terraform.InstanceDiff{ 988 Attributes: map[string]*terraform.ResourceAttrDiff{ 989 "ports.#": &terraform.ResourceAttrDiff{ 990 Old: "0", 991 New: "3", 992 }, 993 "ports.1": &terraform.ResourceAttrDiff{ 994 Old: "", 995 New: "1", 996 }, 997 "ports.2": &terraform.ResourceAttrDiff{ 998 Old: "", 999 New: "2", 1000 }, 1001 "ports.5": &terraform.ResourceAttrDiff{ 1002 Old: "", 1003 New: "5", 1004 }, 1005 }, 1006 }, 1007 1008 Err: false, 1009 }, 1010 1011 { 1012 Name: "Set", 1013 Schema: map[string]*Schema{ 1014 "ports": &Schema{ 1015 Type: TypeSet, 1016 Required: true, 1017 Elem: &Schema{Type: TypeInt}, 1018 Set: func(a interface{}) int { 1019 return a.(int) 1020 }, 1021 }, 1022 }, 1023 1024 State: nil, 1025 1026 Config: map[string]interface{}{ 1027 "ports": []interface{}{1, "${var.foo}"}, 1028 }, 1029 1030 ConfigVariables: map[string]ast.Variable{ 1031 "var.foo": interfaceToVariableSwallowError([]interface{}{ 1032 config.UnknownVariableValue, "5"}), 1033 }, 1034 1035 Diff: &terraform.InstanceDiff{ 1036 Attributes: map[string]*terraform.ResourceAttrDiff{ 1037 "ports.#": &terraform.ResourceAttrDiff{ 1038 Old: "", 1039 New: "", 1040 NewComputed: true, 1041 }, 1042 }, 1043 }, 1044 1045 Err: false, 1046 }, 1047 1048 { 1049 Name: "Set", 1050 Schema: map[string]*Schema{ 1051 "ports": &Schema{ 1052 Type: TypeSet, 1053 Required: true, 1054 Elem: &Schema{Type: TypeInt}, 1055 Set: func(a interface{}) int { 1056 return a.(int) 1057 }, 1058 }, 1059 }, 1060 1061 State: &terraform.InstanceState{ 1062 Attributes: map[string]string{ 1063 "ports.#": "2", 1064 "ports.1": "1", 1065 "ports.2": "2", 1066 }, 1067 }, 1068 1069 Config: map[string]interface{}{ 1070 "ports": []interface{}{5, 2, 1}, 1071 }, 1072 1073 Diff: &terraform.InstanceDiff{ 1074 Attributes: map[string]*terraform.ResourceAttrDiff{ 1075 "ports.#": &terraform.ResourceAttrDiff{ 1076 Old: "2", 1077 New: "3", 1078 }, 1079 "ports.1": &terraform.ResourceAttrDiff{ 1080 Old: "1", 1081 New: "1", 1082 }, 1083 "ports.2": &terraform.ResourceAttrDiff{ 1084 Old: "2", 1085 New: "2", 1086 }, 1087 "ports.5": &terraform.ResourceAttrDiff{ 1088 Old: "", 1089 New: "5", 1090 }, 1091 }, 1092 }, 1093 1094 Err: false, 1095 }, 1096 1097 { 1098 Name: "Set", 1099 Schema: map[string]*Schema{ 1100 "ports": &Schema{ 1101 Type: TypeSet, 1102 Required: true, 1103 Elem: &Schema{Type: TypeInt}, 1104 Set: func(a interface{}) int { 1105 return a.(int) 1106 }, 1107 }, 1108 }, 1109 1110 State: &terraform.InstanceState{ 1111 Attributes: map[string]string{ 1112 "ports.#": "2", 1113 "ports.1": "1", 1114 "ports.2": "2", 1115 }, 1116 }, 1117 1118 Config: map[string]interface{}{}, 1119 1120 Diff: &terraform.InstanceDiff{ 1121 Attributes: map[string]*terraform.ResourceAttrDiff{ 1122 "ports.#": &terraform.ResourceAttrDiff{ 1123 Old: "2", 1124 New: "0", 1125 }, 1126 "ports.1": &terraform.ResourceAttrDiff{ 1127 Old: "1", 1128 New: "0", 1129 NewRemoved: true, 1130 }, 1131 "ports.2": &terraform.ResourceAttrDiff{ 1132 Old: "2", 1133 New: "0", 1134 NewRemoved: true, 1135 }, 1136 }, 1137 }, 1138 1139 Err: false, 1140 }, 1141 1142 { 1143 Name: "Set", 1144 Schema: map[string]*Schema{ 1145 "ports": &Schema{ 1146 Type: TypeSet, 1147 Optional: true, 1148 Computed: true, 1149 Elem: &Schema{Type: TypeInt}, 1150 Set: func(a interface{}) int { 1151 return a.(int) 1152 }, 1153 }, 1154 }, 1155 1156 State: &terraform.InstanceState{ 1157 Attributes: map[string]string{ 1158 "availability_zone": "bar", 1159 "ports.#": "1", 1160 "ports.80": "80", 1161 }, 1162 }, 1163 1164 Config: map[string]interface{}{}, 1165 1166 Diff: nil, 1167 1168 Err: false, 1169 }, 1170 1171 { 1172 Name: "Set", 1173 Schema: map[string]*Schema{ 1174 "ingress": &Schema{ 1175 Type: TypeSet, 1176 Required: true, 1177 Elem: &Resource{ 1178 Schema: map[string]*Schema{ 1179 "ports": &Schema{ 1180 Type: TypeList, 1181 Optional: true, 1182 Elem: &Schema{Type: TypeInt}, 1183 }, 1184 }, 1185 }, 1186 Set: func(v interface{}) int { 1187 m := v.(map[string]interface{}) 1188 ps := m["ports"].([]interface{}) 1189 result := 0 1190 for _, p := range ps { 1191 result += p.(int) 1192 } 1193 return result 1194 }, 1195 }, 1196 }, 1197 1198 State: &terraform.InstanceState{ 1199 Attributes: map[string]string{ 1200 "ingress.#": "2", 1201 "ingress.80.ports.#": "1", 1202 "ingress.80.ports.0": "80", 1203 "ingress.443.ports.#": "1", 1204 "ingress.443.ports.0": "443", 1205 }, 1206 }, 1207 1208 Config: map[string]interface{}{ 1209 "ingress": []map[string]interface{}{ 1210 map[string]interface{}{ 1211 "ports": []interface{}{443}, 1212 }, 1213 map[string]interface{}{ 1214 "ports": []interface{}{80}, 1215 }, 1216 }, 1217 }, 1218 1219 Diff: nil, 1220 1221 Err: false, 1222 }, 1223 1224 { 1225 Name: "List of structure decode", 1226 Schema: map[string]*Schema{ 1227 "ingress": &Schema{ 1228 Type: TypeList, 1229 Required: true, 1230 Elem: &Resource{ 1231 Schema: map[string]*Schema{ 1232 "from": &Schema{ 1233 Type: TypeInt, 1234 Required: true, 1235 }, 1236 }, 1237 }, 1238 }, 1239 }, 1240 1241 State: nil, 1242 1243 Config: map[string]interface{}{ 1244 "ingress": []interface{}{ 1245 map[string]interface{}{ 1246 "from": 8080, 1247 }, 1248 }, 1249 }, 1250 1251 Diff: &terraform.InstanceDiff{ 1252 Attributes: map[string]*terraform.ResourceAttrDiff{ 1253 "ingress.#": &terraform.ResourceAttrDiff{ 1254 Old: "0", 1255 New: "1", 1256 }, 1257 "ingress.0.from": &terraform.ResourceAttrDiff{ 1258 Old: "", 1259 New: "8080", 1260 }, 1261 }, 1262 }, 1263 1264 Err: false, 1265 }, 1266 1267 { 1268 Name: "ComputedWhen", 1269 Schema: map[string]*Schema{ 1270 "availability_zone": &Schema{ 1271 Type: TypeString, 1272 Computed: true, 1273 ComputedWhen: []string{"port"}, 1274 }, 1275 1276 "port": &Schema{ 1277 Type: TypeInt, 1278 Optional: true, 1279 }, 1280 }, 1281 1282 State: &terraform.InstanceState{ 1283 Attributes: map[string]string{ 1284 "availability_zone": "foo", 1285 "port": "80", 1286 }, 1287 }, 1288 1289 Config: map[string]interface{}{ 1290 "port": 80, 1291 }, 1292 1293 Diff: nil, 1294 1295 Err: false, 1296 }, 1297 1298 { 1299 Name: "", 1300 Schema: map[string]*Schema{ 1301 "availability_zone": &Schema{ 1302 Type: TypeString, 1303 Computed: true, 1304 ComputedWhen: []string{"port"}, 1305 }, 1306 1307 "port": &Schema{ 1308 Type: TypeInt, 1309 Optional: true, 1310 }, 1311 }, 1312 1313 State: &terraform.InstanceState{ 1314 Attributes: map[string]string{ 1315 "port": "80", 1316 }, 1317 }, 1318 1319 Config: map[string]interface{}{ 1320 "port": 80, 1321 }, 1322 1323 Diff: &terraform.InstanceDiff{ 1324 Attributes: map[string]*terraform.ResourceAttrDiff{ 1325 "availability_zone": &terraform.ResourceAttrDiff{ 1326 NewComputed: true, 1327 }, 1328 }, 1329 }, 1330 1331 Err: false, 1332 }, 1333 1334 /* TODO 1335 { 1336 Schema: map[string]*Schema{ 1337 "availability_zone": &Schema{ 1338 Type: TypeString, 1339 Computed: true, 1340 ComputedWhen: []string{"port"}, 1341 }, 1342 1343 "port": &Schema{ 1344 Type: TypeInt, 1345 Optional: true, 1346 }, 1347 }, 1348 1349 State: &terraform.InstanceState{ 1350 Attributes: map[string]string{ 1351 "availability_zone": "foo", 1352 "port": "80", 1353 }, 1354 }, 1355 1356 Config: map[string]interface{}{ 1357 "port": 8080, 1358 }, 1359 1360 Diff: &terraform.ResourceDiff{ 1361 Attributes: map[string]*terraform.ResourceAttrDiff{ 1362 "availability_zone": &terraform.ResourceAttrDiff{ 1363 Old: "foo", 1364 NewComputed: true, 1365 }, 1366 "port": &terraform.ResourceAttrDiff{ 1367 Old: "80", 1368 New: "8080", 1369 }, 1370 }, 1371 }, 1372 1373 Err: false, 1374 }, 1375 */ 1376 1377 { 1378 Name: "Maps", 1379 Schema: map[string]*Schema{ 1380 "config_vars": &Schema{ 1381 Type: TypeMap, 1382 }, 1383 }, 1384 1385 State: nil, 1386 1387 Config: map[string]interface{}{ 1388 "config_vars": []interface{}{ 1389 map[string]interface{}{ 1390 "bar": "baz", 1391 }, 1392 }, 1393 }, 1394 1395 Diff: &terraform.InstanceDiff{ 1396 Attributes: map[string]*terraform.ResourceAttrDiff{ 1397 "config_vars.%": &terraform.ResourceAttrDiff{ 1398 Old: "0", 1399 New: "1", 1400 }, 1401 1402 "config_vars.bar": &terraform.ResourceAttrDiff{ 1403 Old: "", 1404 New: "baz", 1405 }, 1406 }, 1407 }, 1408 1409 Err: false, 1410 }, 1411 1412 { 1413 Name: "Maps", 1414 Schema: map[string]*Schema{ 1415 "config_vars": &Schema{ 1416 Type: TypeMap, 1417 }, 1418 }, 1419 1420 State: &terraform.InstanceState{ 1421 Attributes: map[string]string{ 1422 "config_vars.foo": "bar", 1423 }, 1424 }, 1425 1426 Config: map[string]interface{}{ 1427 "config_vars": []interface{}{ 1428 map[string]interface{}{ 1429 "bar": "baz", 1430 }, 1431 }, 1432 }, 1433 1434 Diff: &terraform.InstanceDiff{ 1435 Attributes: map[string]*terraform.ResourceAttrDiff{ 1436 "config_vars.foo": &terraform.ResourceAttrDiff{ 1437 Old: "bar", 1438 NewRemoved: true, 1439 }, 1440 "config_vars.bar": &terraform.ResourceAttrDiff{ 1441 Old: "", 1442 New: "baz", 1443 }, 1444 }, 1445 }, 1446 1447 Err: false, 1448 }, 1449 1450 { 1451 Name: "Maps", 1452 Schema: map[string]*Schema{ 1453 "vars": &Schema{ 1454 Type: TypeMap, 1455 Optional: true, 1456 Computed: true, 1457 }, 1458 }, 1459 1460 State: &terraform.InstanceState{ 1461 Attributes: map[string]string{ 1462 "vars.foo": "bar", 1463 }, 1464 }, 1465 1466 Config: map[string]interface{}{ 1467 "vars": []interface{}{ 1468 map[string]interface{}{ 1469 "bar": "baz", 1470 }, 1471 }, 1472 }, 1473 1474 Diff: &terraform.InstanceDiff{ 1475 Attributes: map[string]*terraform.ResourceAttrDiff{ 1476 "vars.foo": &terraform.ResourceAttrDiff{ 1477 Old: "bar", 1478 New: "", 1479 NewRemoved: true, 1480 }, 1481 "vars.bar": &terraform.ResourceAttrDiff{ 1482 Old: "", 1483 New: "baz", 1484 }, 1485 }, 1486 }, 1487 1488 Err: false, 1489 }, 1490 1491 { 1492 Name: "Maps", 1493 Schema: map[string]*Schema{ 1494 "vars": &Schema{ 1495 Type: TypeMap, 1496 Computed: true, 1497 }, 1498 }, 1499 1500 State: &terraform.InstanceState{ 1501 Attributes: map[string]string{ 1502 "vars.foo": "bar", 1503 }, 1504 }, 1505 1506 Config: nil, 1507 1508 Diff: nil, 1509 1510 Err: false, 1511 }, 1512 1513 { 1514 Name: "Maps", 1515 Schema: map[string]*Schema{ 1516 "config_vars": &Schema{ 1517 Type: TypeList, 1518 Elem: &Schema{Type: TypeMap}, 1519 }, 1520 }, 1521 1522 State: &terraform.InstanceState{ 1523 Attributes: map[string]string{ 1524 "config_vars.#": "1", 1525 "config_vars.0.foo": "bar", 1526 }, 1527 }, 1528 1529 Config: map[string]interface{}{ 1530 "config_vars": []interface{}{ 1531 map[string]interface{}{ 1532 "bar": "baz", 1533 }, 1534 }, 1535 }, 1536 1537 Diff: &terraform.InstanceDiff{ 1538 Attributes: map[string]*terraform.ResourceAttrDiff{ 1539 "config_vars.0.foo": &terraform.ResourceAttrDiff{ 1540 Old: "bar", 1541 NewRemoved: true, 1542 }, 1543 "config_vars.0.bar": &terraform.ResourceAttrDiff{ 1544 Old: "", 1545 New: "baz", 1546 }, 1547 }, 1548 }, 1549 1550 Err: false, 1551 }, 1552 1553 { 1554 Name: "Maps", 1555 Schema: map[string]*Schema{ 1556 "config_vars": &Schema{ 1557 Type: TypeList, 1558 Elem: &Schema{Type: TypeMap}, 1559 }, 1560 }, 1561 1562 State: &terraform.InstanceState{ 1563 Attributes: map[string]string{ 1564 "config_vars.#": "1", 1565 "config_vars.0.foo": "bar", 1566 "config_vars.0.bar": "baz", 1567 }, 1568 }, 1569 1570 Config: map[string]interface{}{}, 1571 1572 Diff: &terraform.InstanceDiff{ 1573 Attributes: map[string]*terraform.ResourceAttrDiff{ 1574 "config_vars.#": &terraform.ResourceAttrDiff{ 1575 Old: "1", 1576 New: "0", 1577 }, 1578 "config_vars.0.%": &terraform.ResourceAttrDiff{ 1579 Old: "2", 1580 New: "0", 1581 }, 1582 "config_vars.0.foo": &terraform.ResourceAttrDiff{ 1583 Old: "bar", 1584 NewRemoved: true, 1585 }, 1586 "config_vars.0.bar": &terraform.ResourceAttrDiff{ 1587 Old: "baz", 1588 NewRemoved: true, 1589 }, 1590 }, 1591 }, 1592 1593 Err: false, 1594 }, 1595 1596 { 1597 Name: "ForceNews", 1598 Schema: map[string]*Schema{ 1599 "availability_zone": &Schema{ 1600 Type: TypeString, 1601 Optional: true, 1602 ForceNew: true, 1603 }, 1604 1605 "address": &Schema{ 1606 Type: TypeString, 1607 Optional: true, 1608 Computed: true, 1609 }, 1610 }, 1611 1612 State: &terraform.InstanceState{ 1613 Attributes: map[string]string{ 1614 "availability_zone": "bar", 1615 "address": "foo", 1616 }, 1617 }, 1618 1619 Config: map[string]interface{}{ 1620 "availability_zone": "foo", 1621 }, 1622 1623 Diff: &terraform.InstanceDiff{ 1624 Attributes: map[string]*terraform.ResourceAttrDiff{ 1625 "availability_zone": &terraform.ResourceAttrDiff{ 1626 Old: "bar", 1627 New: "foo", 1628 RequiresNew: true, 1629 }, 1630 1631 "address": &terraform.ResourceAttrDiff{ 1632 Old: "foo", 1633 New: "", 1634 NewComputed: true, 1635 }, 1636 }, 1637 }, 1638 1639 Err: false, 1640 }, 1641 1642 { 1643 Name: "Set", 1644 Schema: map[string]*Schema{ 1645 "availability_zone": &Schema{ 1646 Type: TypeString, 1647 Optional: true, 1648 ForceNew: true, 1649 }, 1650 1651 "ports": &Schema{ 1652 Type: TypeSet, 1653 Optional: true, 1654 Computed: true, 1655 Elem: &Schema{Type: TypeInt}, 1656 Set: func(a interface{}) int { 1657 return a.(int) 1658 }, 1659 }, 1660 }, 1661 1662 State: &terraform.InstanceState{ 1663 Attributes: map[string]string{ 1664 "availability_zone": "bar", 1665 "ports.#": "1", 1666 "ports.80": "80", 1667 }, 1668 }, 1669 1670 Config: map[string]interface{}{ 1671 "availability_zone": "foo", 1672 }, 1673 1674 Diff: &terraform.InstanceDiff{ 1675 Attributes: map[string]*terraform.ResourceAttrDiff{ 1676 "availability_zone": &terraform.ResourceAttrDiff{ 1677 Old: "bar", 1678 New: "foo", 1679 RequiresNew: true, 1680 }, 1681 1682 "ports.#": &terraform.ResourceAttrDiff{ 1683 Old: "1", 1684 New: "", 1685 NewComputed: true, 1686 }, 1687 }, 1688 }, 1689 1690 Err: false, 1691 }, 1692 1693 { 1694 Name: "Set", 1695 Schema: map[string]*Schema{ 1696 "instances": &Schema{ 1697 Type: TypeSet, 1698 Elem: &Schema{Type: TypeString}, 1699 Optional: true, 1700 Computed: true, 1701 Set: func(v interface{}) int { 1702 return len(v.(string)) 1703 }, 1704 }, 1705 }, 1706 1707 State: &terraform.InstanceState{ 1708 Attributes: map[string]string{ 1709 "instances.#": "0", 1710 }, 1711 }, 1712 1713 Config: map[string]interface{}{ 1714 "instances": []interface{}{"${var.foo}"}, 1715 }, 1716 1717 ConfigVariables: map[string]ast.Variable{ 1718 "var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue), 1719 }, 1720 1721 Diff: &terraform.InstanceDiff{ 1722 Attributes: map[string]*terraform.ResourceAttrDiff{ 1723 "instances.#": &terraform.ResourceAttrDiff{ 1724 NewComputed: true, 1725 }, 1726 }, 1727 }, 1728 1729 Err: false, 1730 }, 1731 1732 { 1733 Name: "Set", 1734 Schema: map[string]*Schema{ 1735 "route": &Schema{ 1736 Type: TypeSet, 1737 Optional: true, 1738 Elem: &Resource{ 1739 Schema: map[string]*Schema{ 1740 "index": &Schema{ 1741 Type: TypeInt, 1742 Required: true, 1743 }, 1744 1745 "gateway": &Schema{ 1746 Type: TypeString, 1747 Optional: true, 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": "${var.foo}", 1765 }, 1766 }, 1767 }, 1768 1769 ConfigVariables: map[string]ast.Variable{ 1770 "var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue), 1771 }, 1772 1773 Diff: &terraform.InstanceDiff{ 1774 Attributes: map[string]*terraform.ResourceAttrDiff{ 1775 "route.#": &terraform.ResourceAttrDiff{ 1776 Old: "0", 1777 New: "1", 1778 }, 1779 "route.~1.index": &terraform.ResourceAttrDiff{ 1780 Old: "", 1781 New: "1", 1782 }, 1783 "route.~1.gateway": &terraform.ResourceAttrDiff{ 1784 Old: "", 1785 New: "${var.foo}", 1786 NewComputed: true, 1787 }, 1788 }, 1789 }, 1790 1791 Err: false, 1792 }, 1793 1794 { 1795 Name: "Set", 1796 Schema: map[string]*Schema{ 1797 "route": &Schema{ 1798 Type: TypeSet, 1799 Optional: true, 1800 Elem: &Resource{ 1801 Schema: map[string]*Schema{ 1802 "index": &Schema{ 1803 Type: TypeInt, 1804 Required: true, 1805 }, 1806 1807 "gateway": &Schema{ 1808 Type: TypeSet, 1809 Optional: true, 1810 Elem: &Schema{Type: TypeInt}, 1811 Set: func(a interface{}) int { 1812 return a.(int) 1813 }, 1814 }, 1815 }, 1816 }, 1817 Set: func(v interface{}) int { 1818 m := v.(map[string]interface{}) 1819 return m["index"].(int) 1820 }, 1821 }, 1822 }, 1823 1824 State: nil, 1825 1826 Config: map[string]interface{}{ 1827 "route": []map[string]interface{}{ 1828 map[string]interface{}{ 1829 "index": "1", 1830 "gateway": []interface{}{ 1831 "${var.foo}", 1832 }, 1833 }, 1834 }, 1835 }, 1836 1837 ConfigVariables: map[string]ast.Variable{ 1838 "var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue), 1839 }, 1840 1841 Diff: &terraform.InstanceDiff{ 1842 Attributes: map[string]*terraform.ResourceAttrDiff{ 1843 "route.#": &terraform.ResourceAttrDiff{ 1844 Old: "0", 1845 New: "1", 1846 }, 1847 "route.~1.index": &terraform.ResourceAttrDiff{ 1848 Old: "", 1849 New: "1", 1850 }, 1851 "route.~1.gateway.#": &terraform.ResourceAttrDiff{ 1852 NewComputed: true, 1853 }, 1854 }, 1855 }, 1856 1857 Err: false, 1858 }, 1859 1860 { 1861 Name: "Computed maps", 1862 Schema: map[string]*Schema{ 1863 "vars": &Schema{ 1864 Type: TypeMap, 1865 Computed: true, 1866 }, 1867 }, 1868 1869 State: nil, 1870 1871 Config: nil, 1872 1873 Diff: &terraform.InstanceDiff{ 1874 Attributes: map[string]*terraform.ResourceAttrDiff{ 1875 "vars.%": &terraform.ResourceAttrDiff{ 1876 Old: "", 1877 NewComputed: true, 1878 }, 1879 }, 1880 }, 1881 1882 Err: false, 1883 }, 1884 1885 { 1886 Name: "Computed maps", 1887 Schema: map[string]*Schema{ 1888 "vars": &Schema{ 1889 Type: TypeMap, 1890 Computed: true, 1891 }, 1892 }, 1893 1894 State: &terraform.InstanceState{ 1895 Attributes: map[string]string{ 1896 "vars.%": "0", 1897 }, 1898 }, 1899 1900 Config: map[string]interface{}{ 1901 "vars": map[string]interface{}{ 1902 "bar": "${var.foo}", 1903 }, 1904 }, 1905 1906 ConfigVariables: map[string]ast.Variable{ 1907 "var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue), 1908 }, 1909 1910 Diff: &terraform.InstanceDiff{ 1911 Attributes: map[string]*terraform.ResourceAttrDiff{ 1912 "vars.%": &terraform.ResourceAttrDiff{ 1913 Old: "", 1914 NewComputed: true, 1915 }, 1916 }, 1917 }, 1918 1919 Err: false, 1920 }, 1921 1922 { 1923 Name: " - Empty", 1924 Schema: map[string]*Schema{}, 1925 1926 State: &terraform.InstanceState{}, 1927 1928 Config: map[string]interface{}{}, 1929 1930 Diff: nil, 1931 1932 Err: false, 1933 }, 1934 1935 { 1936 Name: "Float", 1937 Schema: map[string]*Schema{ 1938 "some_threshold": &Schema{ 1939 Type: TypeFloat, 1940 }, 1941 }, 1942 1943 State: &terraform.InstanceState{ 1944 Attributes: map[string]string{ 1945 "some_threshold": "567.8", 1946 }, 1947 }, 1948 1949 Config: map[string]interface{}{ 1950 "some_threshold": 12.34, 1951 }, 1952 1953 Diff: &terraform.InstanceDiff{ 1954 Attributes: map[string]*terraform.ResourceAttrDiff{ 1955 "some_threshold": &terraform.ResourceAttrDiff{ 1956 Old: "567.8", 1957 New: "12.34", 1958 }, 1959 }, 1960 }, 1961 1962 Err: false, 1963 }, 1964 1965 { 1966 Name: "https://github.com/hashicorp/terraform/issues/824", 1967 Schema: map[string]*Schema{ 1968 "block_device": &Schema{ 1969 Type: TypeSet, 1970 Optional: true, 1971 Computed: true, 1972 Elem: &Resource{ 1973 Schema: map[string]*Schema{ 1974 "device_name": &Schema{ 1975 Type: TypeString, 1976 Required: true, 1977 }, 1978 "delete_on_termination": &Schema{ 1979 Type: TypeBool, 1980 Optional: true, 1981 Default: true, 1982 }, 1983 }, 1984 }, 1985 Set: func(v interface{}) int { 1986 var buf bytes.Buffer 1987 m := v.(map[string]interface{}) 1988 buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) 1989 buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool))) 1990 return hashcode.String(buf.String()) 1991 }, 1992 }, 1993 }, 1994 1995 State: &terraform.InstanceState{ 1996 Attributes: map[string]string{ 1997 "block_device.#": "2", 1998 "block_device.616397234.delete_on_termination": "true", 1999 "block_device.616397234.device_name": "/dev/sda1", 2000 "block_device.2801811477.delete_on_termination": "true", 2001 "block_device.2801811477.device_name": "/dev/sdx", 2002 }, 2003 }, 2004 2005 Config: map[string]interface{}{ 2006 "block_device": []map[string]interface{}{ 2007 map[string]interface{}{ 2008 "device_name": "/dev/sda1", 2009 }, 2010 map[string]interface{}{ 2011 "device_name": "/dev/sdx", 2012 }, 2013 }, 2014 }, 2015 Diff: nil, 2016 Err: false, 2017 }, 2018 2019 { 2020 Name: "Zero value in state shouldn't result in diff", 2021 Schema: map[string]*Schema{ 2022 "port": &Schema{ 2023 Type: TypeBool, 2024 Optional: true, 2025 ForceNew: true, 2026 }, 2027 }, 2028 2029 State: &terraform.InstanceState{ 2030 Attributes: map[string]string{ 2031 "port": "false", 2032 }, 2033 }, 2034 2035 Config: map[string]interface{}{}, 2036 2037 Diff: nil, 2038 2039 Err: false, 2040 }, 2041 2042 { 2043 Name: "Same as prev, but for sets", 2044 Schema: map[string]*Schema{ 2045 "route": &Schema{ 2046 Type: TypeSet, 2047 Optional: true, 2048 Elem: &Resource{ 2049 Schema: map[string]*Schema{ 2050 "index": &Schema{ 2051 Type: TypeInt, 2052 Required: true, 2053 }, 2054 2055 "gateway": &Schema{ 2056 Type: TypeSet, 2057 Optional: true, 2058 Elem: &Schema{Type: TypeInt}, 2059 Set: func(a interface{}) int { 2060 return a.(int) 2061 }, 2062 }, 2063 }, 2064 }, 2065 Set: func(v interface{}) int { 2066 m := v.(map[string]interface{}) 2067 return m["index"].(int) 2068 }, 2069 }, 2070 }, 2071 2072 State: &terraform.InstanceState{ 2073 Attributes: map[string]string{ 2074 "route.#": "0", 2075 }, 2076 }, 2077 2078 Config: map[string]interface{}{}, 2079 2080 Diff: nil, 2081 2082 Err: false, 2083 }, 2084 2085 { 2086 Name: "A set computed element shouldn't cause a diff", 2087 Schema: map[string]*Schema{ 2088 "active": &Schema{ 2089 Type: TypeBool, 2090 Computed: true, 2091 ForceNew: true, 2092 }, 2093 }, 2094 2095 State: &terraform.InstanceState{ 2096 Attributes: map[string]string{ 2097 "active": "true", 2098 }, 2099 }, 2100 2101 Config: map[string]interface{}{}, 2102 2103 Diff: nil, 2104 2105 Err: false, 2106 }, 2107 2108 { 2109 Name: "An empty set should show up in the diff", 2110 Schema: map[string]*Schema{ 2111 "instances": &Schema{ 2112 Type: TypeSet, 2113 Elem: &Schema{Type: TypeString}, 2114 Optional: true, 2115 ForceNew: true, 2116 Set: func(v interface{}) int { 2117 return len(v.(string)) 2118 }, 2119 }, 2120 }, 2121 2122 State: &terraform.InstanceState{ 2123 Attributes: map[string]string{ 2124 "instances.#": "1", 2125 "instances.3": "foo", 2126 }, 2127 }, 2128 2129 Config: map[string]interface{}{}, 2130 2131 Diff: &terraform.InstanceDiff{ 2132 Attributes: map[string]*terraform.ResourceAttrDiff{ 2133 "instances.#": &terraform.ResourceAttrDiff{ 2134 Old: "1", 2135 New: "0", 2136 RequiresNew: true, 2137 }, 2138 "instances.3": &terraform.ResourceAttrDiff{ 2139 Old: "foo", 2140 New: "", 2141 NewRemoved: true, 2142 RequiresNew: true, 2143 }, 2144 }, 2145 }, 2146 2147 Err: false, 2148 }, 2149 2150 { 2151 Name: "Map with empty value", 2152 Schema: map[string]*Schema{ 2153 "vars": &Schema{ 2154 Type: TypeMap, 2155 }, 2156 }, 2157 2158 State: nil, 2159 2160 Config: map[string]interface{}{ 2161 "vars": map[string]interface{}{ 2162 "foo": "", 2163 }, 2164 }, 2165 2166 Diff: &terraform.InstanceDiff{ 2167 Attributes: map[string]*terraform.ResourceAttrDiff{ 2168 "vars.%": &terraform.ResourceAttrDiff{ 2169 Old: "0", 2170 New: "1", 2171 }, 2172 "vars.foo": &terraform.ResourceAttrDiff{ 2173 Old: "", 2174 New: "", 2175 }, 2176 }, 2177 }, 2178 2179 Err: false, 2180 }, 2181 2182 { 2183 Name: "Unset bool, not in state", 2184 Schema: map[string]*Schema{ 2185 "force": &Schema{ 2186 Type: TypeBool, 2187 Optional: true, 2188 ForceNew: true, 2189 }, 2190 }, 2191 2192 State: nil, 2193 2194 Config: map[string]interface{}{}, 2195 2196 Diff: nil, 2197 2198 Err: false, 2199 }, 2200 2201 { 2202 Name: "Unset set, not in state", 2203 Schema: map[string]*Schema{ 2204 "metadata_keys": &Schema{ 2205 Type: TypeSet, 2206 Optional: true, 2207 ForceNew: true, 2208 Elem: &Schema{Type: TypeInt}, 2209 Set: func(interface{}) int { return 0 }, 2210 }, 2211 }, 2212 2213 State: nil, 2214 2215 Config: map[string]interface{}{}, 2216 2217 Diff: nil, 2218 2219 Err: false, 2220 }, 2221 2222 { 2223 Name: "Unset list in state, should not show up computed", 2224 Schema: map[string]*Schema{ 2225 "metadata_keys": &Schema{ 2226 Type: TypeList, 2227 Optional: true, 2228 Computed: true, 2229 ForceNew: true, 2230 Elem: &Schema{Type: TypeInt}, 2231 }, 2232 }, 2233 2234 State: &terraform.InstanceState{ 2235 Attributes: map[string]string{ 2236 "metadata_keys.#": "0", 2237 }, 2238 }, 2239 2240 Config: map[string]interface{}{}, 2241 2242 Diff: nil, 2243 2244 Err: false, 2245 }, 2246 2247 { 2248 Name: "Set element computed substring", 2249 Schema: map[string]*Schema{ 2250 "ports": &Schema{ 2251 Type: TypeSet, 2252 Required: true, 2253 Elem: &Schema{Type: TypeInt}, 2254 Set: func(a interface{}) int { 2255 return a.(int) 2256 }, 2257 }, 2258 }, 2259 2260 State: nil, 2261 2262 Config: map[string]interface{}{ 2263 "ports": []interface{}{1, "${var.foo}32"}, 2264 }, 2265 2266 ConfigVariables: map[string]ast.Variable{ 2267 "var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue), 2268 }, 2269 2270 Diff: &terraform.InstanceDiff{ 2271 Attributes: map[string]*terraform.ResourceAttrDiff{ 2272 "ports.#": &terraform.ResourceAttrDiff{ 2273 Old: "", 2274 New: "", 2275 NewComputed: true, 2276 }, 2277 }, 2278 }, 2279 2280 Err: false, 2281 }, 2282 2283 { 2284 Name: "Computed map without config that's known to be empty does not generate diff", 2285 Schema: map[string]*Schema{ 2286 "tags": &Schema{ 2287 Type: TypeMap, 2288 Computed: true, 2289 }, 2290 }, 2291 2292 Config: nil, 2293 2294 State: &terraform.InstanceState{ 2295 Attributes: map[string]string{ 2296 "tags.%": "0", 2297 }, 2298 }, 2299 2300 Diff: nil, 2301 2302 Err: false, 2303 }, 2304 2305 { 2306 Name: "Set with hyphen keys", 2307 Schema: map[string]*Schema{ 2308 "route": &Schema{ 2309 Type: TypeSet, 2310 Optional: true, 2311 Elem: &Resource{ 2312 Schema: map[string]*Schema{ 2313 "index": &Schema{ 2314 Type: TypeInt, 2315 Required: true, 2316 }, 2317 2318 "gateway-name": &Schema{ 2319 Type: TypeString, 2320 Optional: true, 2321 }, 2322 }, 2323 }, 2324 Set: func(v interface{}) int { 2325 m := v.(map[string]interface{}) 2326 return m["index"].(int) 2327 }, 2328 }, 2329 }, 2330 2331 State: nil, 2332 2333 Config: map[string]interface{}{ 2334 "route": []map[string]interface{}{ 2335 map[string]interface{}{ 2336 "index": "1", 2337 "gateway-name": "hello", 2338 }, 2339 }, 2340 }, 2341 2342 Diff: &terraform.InstanceDiff{ 2343 Attributes: map[string]*terraform.ResourceAttrDiff{ 2344 "route.#": &terraform.ResourceAttrDiff{ 2345 Old: "0", 2346 New: "1", 2347 }, 2348 "route.1.index": &terraform.ResourceAttrDiff{ 2349 Old: "", 2350 New: "1", 2351 }, 2352 "route.1.gateway-name": &terraform.ResourceAttrDiff{ 2353 Old: "", 2354 New: "hello", 2355 }, 2356 }, 2357 }, 2358 2359 Err: false, 2360 }, 2361 2362 { 2363 Name: ": StateFunc in nested set (#1759)", 2364 Schema: map[string]*Schema{ 2365 "service_account": &Schema{ 2366 Type: TypeList, 2367 Optional: true, 2368 ForceNew: true, 2369 Elem: &Resource{ 2370 Schema: map[string]*Schema{ 2371 "scopes": &Schema{ 2372 Type: TypeSet, 2373 Required: true, 2374 ForceNew: true, 2375 Elem: &Schema{ 2376 Type: TypeString, 2377 StateFunc: func(v interface{}) string { 2378 return v.(string) + "!" 2379 }, 2380 }, 2381 Set: func(v interface{}) int { 2382 i, err := strconv.Atoi(v.(string)) 2383 if err != nil { 2384 t.Fatalf("err: %s", err) 2385 } 2386 return i 2387 }, 2388 }, 2389 }, 2390 }, 2391 }, 2392 }, 2393 2394 State: nil, 2395 2396 Config: map[string]interface{}{ 2397 "service_account": []map[string]interface{}{ 2398 { 2399 "scopes": []interface{}{"123"}, 2400 }, 2401 }, 2402 }, 2403 2404 Diff: &terraform.InstanceDiff{ 2405 Attributes: map[string]*terraform.ResourceAttrDiff{ 2406 "service_account.#": &terraform.ResourceAttrDiff{ 2407 Old: "0", 2408 New: "1", 2409 RequiresNew: true, 2410 }, 2411 "service_account.0.scopes.#": &terraform.ResourceAttrDiff{ 2412 Old: "0", 2413 New: "1", 2414 RequiresNew: true, 2415 }, 2416 "service_account.0.scopes.123": &terraform.ResourceAttrDiff{ 2417 Old: "", 2418 New: "123!", 2419 NewExtra: "123", 2420 RequiresNew: true, 2421 }, 2422 }, 2423 }, 2424 2425 Err: false, 2426 }, 2427 2428 { 2429 Name: "Removing set elements", 2430 Schema: map[string]*Schema{ 2431 "instances": &Schema{ 2432 Type: TypeSet, 2433 Elem: &Schema{Type: TypeString}, 2434 Optional: true, 2435 ForceNew: true, 2436 Set: func(v interface{}) int { 2437 return len(v.(string)) 2438 }, 2439 }, 2440 }, 2441 2442 State: &terraform.InstanceState{ 2443 Attributes: map[string]string{ 2444 "instances.#": "2", 2445 "instances.3": "333", 2446 "instances.2": "22", 2447 }, 2448 }, 2449 2450 Config: map[string]interface{}{ 2451 "instances": []interface{}{"333", "4444"}, 2452 }, 2453 2454 Diff: &terraform.InstanceDiff{ 2455 Attributes: map[string]*terraform.ResourceAttrDiff{ 2456 "instances.#": &terraform.ResourceAttrDiff{ 2457 Old: "2", 2458 New: "2", 2459 }, 2460 "instances.2": &terraform.ResourceAttrDiff{ 2461 Old: "22", 2462 New: "", 2463 NewRemoved: true, 2464 RequiresNew: true, 2465 }, 2466 "instances.3": &terraform.ResourceAttrDiff{ 2467 Old: "333", 2468 New: "333", 2469 }, 2470 "instances.4": &terraform.ResourceAttrDiff{ 2471 Old: "", 2472 New: "4444", 2473 RequiresNew: true, 2474 }, 2475 }, 2476 }, 2477 2478 Err: false, 2479 }, 2480 2481 { 2482 Name: "Bools can be set with 0/1 in config, still get true/false", 2483 Schema: map[string]*Schema{ 2484 "one": &Schema{ 2485 Type: TypeBool, 2486 Optional: true, 2487 }, 2488 "two": &Schema{ 2489 Type: TypeBool, 2490 Optional: true, 2491 }, 2492 "three": &Schema{ 2493 Type: TypeBool, 2494 Optional: true, 2495 }, 2496 }, 2497 2498 State: &terraform.InstanceState{ 2499 Attributes: map[string]string{ 2500 "one": "false", 2501 "two": "true", 2502 "three": "true", 2503 }, 2504 }, 2505 2506 Config: map[string]interface{}{ 2507 "one": "1", 2508 "two": "0", 2509 }, 2510 2511 Diff: &terraform.InstanceDiff{ 2512 Attributes: map[string]*terraform.ResourceAttrDiff{ 2513 "one": &terraform.ResourceAttrDiff{ 2514 Old: "false", 2515 New: "true", 2516 }, 2517 "two": &terraform.ResourceAttrDiff{ 2518 Old: "true", 2519 New: "false", 2520 }, 2521 "three": &terraform.ResourceAttrDiff{ 2522 Old: "true", 2523 New: "false", 2524 NewRemoved: true, 2525 }, 2526 }, 2527 }, 2528 2529 Err: false, 2530 }, 2531 2532 { 2533 Name: "tainted in state w/ no attr changes is still a replacement", 2534 Schema: map[string]*Schema{}, 2535 2536 State: &terraform.InstanceState{ 2537 Attributes: map[string]string{ 2538 "id": "someid", 2539 }, 2540 Tainted: true, 2541 }, 2542 2543 Config: map[string]interface{}{}, 2544 2545 Diff: &terraform.InstanceDiff{ 2546 Attributes: map[string]*terraform.ResourceAttrDiff{}, 2547 DestroyTainted: true, 2548 }, 2549 2550 Err: false, 2551 }, 2552 2553 { 2554 Name: "Set ForceNew only marks the changing element as ForceNew", 2555 Schema: map[string]*Schema{ 2556 "ports": &Schema{ 2557 Type: TypeSet, 2558 Required: true, 2559 ForceNew: true, 2560 Elem: &Schema{Type: TypeInt}, 2561 Set: func(a interface{}) int { 2562 return a.(int) 2563 }, 2564 }, 2565 }, 2566 2567 State: &terraform.InstanceState{ 2568 Attributes: map[string]string{ 2569 "ports.#": "3", 2570 "ports.1": "1", 2571 "ports.2": "2", 2572 "ports.4": "4", 2573 }, 2574 }, 2575 2576 Config: map[string]interface{}{ 2577 "ports": []interface{}{5, 2, 1}, 2578 }, 2579 2580 Diff: &terraform.InstanceDiff{ 2581 Attributes: map[string]*terraform.ResourceAttrDiff{ 2582 "ports.#": &terraform.ResourceAttrDiff{ 2583 Old: "3", 2584 New: "3", 2585 }, 2586 "ports.1": &terraform.ResourceAttrDiff{ 2587 Old: "1", 2588 New: "1", 2589 }, 2590 "ports.2": &terraform.ResourceAttrDiff{ 2591 Old: "2", 2592 New: "2", 2593 }, 2594 "ports.5": &terraform.ResourceAttrDiff{ 2595 Old: "", 2596 New: "5", 2597 RequiresNew: true, 2598 }, 2599 "ports.4": &terraform.ResourceAttrDiff{ 2600 Old: "4", 2601 New: "0", 2602 NewRemoved: true, 2603 RequiresNew: true, 2604 }, 2605 }, 2606 }, 2607 }, 2608 2609 { 2610 Name: "removed optional items should trigger ForceNew", 2611 Schema: map[string]*Schema{ 2612 "description": &Schema{ 2613 Type: TypeString, 2614 ForceNew: true, 2615 Optional: true, 2616 }, 2617 }, 2618 2619 State: &terraform.InstanceState{ 2620 Attributes: map[string]string{ 2621 "description": "foo", 2622 }, 2623 }, 2624 2625 Config: map[string]interface{}{}, 2626 2627 Diff: &terraform.InstanceDiff{ 2628 Attributes: map[string]*terraform.ResourceAttrDiff{ 2629 "description": &terraform.ResourceAttrDiff{ 2630 Old: "foo", 2631 New: "", 2632 RequiresNew: true, 2633 NewRemoved: true, 2634 }, 2635 }, 2636 }, 2637 2638 Err: false, 2639 }, 2640 2641 // GH-7715 2642 { 2643 Name: "computed value for boolean field", 2644 Schema: map[string]*Schema{ 2645 "foo": &Schema{ 2646 Type: TypeBool, 2647 ForceNew: true, 2648 Computed: true, 2649 Optional: true, 2650 }, 2651 }, 2652 2653 State: &terraform.InstanceState{}, 2654 2655 Config: map[string]interface{}{ 2656 "foo": "${var.foo}", 2657 }, 2658 2659 ConfigVariables: map[string]ast.Variable{ 2660 "var.foo": interfaceToVariableSwallowError( 2661 config.UnknownVariableValue), 2662 }, 2663 2664 Diff: &terraform.InstanceDiff{ 2665 Attributes: map[string]*terraform.ResourceAttrDiff{ 2666 "foo": &terraform.ResourceAttrDiff{ 2667 Old: "", 2668 New: "false", 2669 NewComputed: true, 2670 RequiresNew: true, 2671 }, 2672 }, 2673 }, 2674 2675 Err: false, 2676 }, 2677 2678 { 2679 Name: "Set ForceNew marks count as ForceNew if computed", 2680 Schema: map[string]*Schema{ 2681 "ports": &Schema{ 2682 Type: TypeSet, 2683 Required: true, 2684 ForceNew: true, 2685 Elem: &Schema{Type: TypeInt}, 2686 Set: func(a interface{}) int { 2687 return a.(int) 2688 }, 2689 }, 2690 }, 2691 2692 State: &terraform.InstanceState{ 2693 Attributes: map[string]string{ 2694 "ports.#": "3", 2695 "ports.1": "1", 2696 "ports.2": "2", 2697 "ports.4": "4", 2698 }, 2699 }, 2700 2701 Config: map[string]interface{}{ 2702 "ports": []interface{}{"${var.foo}", 2, 1}, 2703 }, 2704 2705 ConfigVariables: map[string]ast.Variable{ 2706 "var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue), 2707 }, 2708 2709 Diff: &terraform.InstanceDiff{ 2710 Attributes: map[string]*terraform.ResourceAttrDiff{ 2711 "ports.#": &terraform.ResourceAttrDiff{ 2712 Old: "3", 2713 New: "", 2714 NewComputed: true, 2715 RequiresNew: true, 2716 }, 2717 }, 2718 }, 2719 }, 2720 2721 { 2722 Name: "Removal of TypeList should cause nested Bool fields w/ Default to be removed too", 2723 Schema: map[string]*Schema{ 2724 "deployment_group_name": &Schema{ 2725 Type: TypeString, 2726 Required: true, 2727 ForceNew: true, 2728 }, 2729 2730 "alarm_configuration": &Schema{ 2731 Type: TypeList, 2732 Optional: true, 2733 MaxItems: 1, 2734 Elem: &Resource{ 2735 Schema: map[string]*Schema{ 2736 "alarms": &Schema{ 2737 Type: TypeSet, 2738 Optional: true, 2739 Set: HashString, 2740 Elem: &Schema{Type: TypeString}, 2741 }, 2742 2743 "enabled": &Schema{ 2744 Type: TypeBool, 2745 Optional: true, 2746 }, 2747 2748 "ignore_poll_alarm_failure": &Schema{ 2749 Type: TypeBool, 2750 Optional: true, 2751 Default: false, 2752 }, 2753 }, 2754 }, 2755 }, 2756 }, 2757 2758 State: &terraform.InstanceState{ 2759 Attributes: map[string]string{ 2760 "alarm_configuration.#": "1", 2761 "alarm_configuration.0.alarms.#": "1", 2762 "alarm_configuration.0.alarms.2356372769": "foo", 2763 "alarm_configuration.0.enabled": "true", 2764 "alarm_configuration.0.ignore_poll_alarm_failure": "false", 2765 "deployment_group_name": "foo-group-32345345345", 2766 }, 2767 }, 2768 2769 Config: map[string]interface{}{ 2770 "deployment_group_name": "foo-group-32345345345", 2771 }, 2772 2773 Diff: &terraform.InstanceDiff{ 2774 Attributes: map[string]*terraform.ResourceAttrDiff{ 2775 "alarm_configuration.#": &terraform.ResourceAttrDiff{ 2776 Old: "1", 2777 New: "0", 2778 NewRemoved: false, 2779 }, 2780 "alarm_configuration.0.alarms": &terraform.ResourceAttrDiff{ 2781 Old: "", 2782 New: "", 2783 NewRemoved: true, 2784 }, 2785 "alarm_configuration.0.alarms.#": &terraform.ResourceAttrDiff{ 2786 Old: "1", 2787 New: "0", 2788 NewRemoved: false, 2789 }, 2790 "alarm_configuration.0.alarms.2356372769": &terraform.ResourceAttrDiff{ 2791 Old: "foo", 2792 New: "", 2793 NewRemoved: true, 2794 }, 2795 "alarm_configuration.0.enabled": &terraform.ResourceAttrDiff{ 2796 Old: "true", 2797 New: "false", 2798 NewRemoved: true, 2799 }, 2800 "alarm_configuration.0.ignore_poll_alarm_failure": &terraform.ResourceAttrDiff{ 2801 Old: "", 2802 New: "", 2803 NewRemoved: true, 2804 }, 2805 }, 2806 }, 2807 }, 2808 2809 { 2810 Name: "Removal of TypeList should cause all empty nested String fields to be removed too", 2811 Schema: map[string]*Schema{ 2812 "bucket": { 2813 Type: TypeString, 2814 Required: true, 2815 ForceNew: true, 2816 }, 2817 2818 "acl": { 2819 Type: TypeString, 2820 Default: "private", 2821 Optional: true, 2822 }, 2823 2824 "website": { 2825 Type: TypeList, 2826 Optional: true, 2827 Elem: &Resource{ 2828 Schema: map[string]*Schema{ 2829 "index_document": { 2830 Type: TypeString, 2831 Optional: true, 2832 }, 2833 2834 "error_document": { 2835 Type: TypeString, 2836 Optional: true, 2837 }, 2838 2839 "redirect_all_requests_to": { 2840 Type: TypeString, 2841 Optional: true, 2842 }, 2843 2844 "routing_rules": { 2845 Type: TypeString, 2846 Optional: true, 2847 }, 2848 }, 2849 }, 2850 }, 2851 }, 2852 2853 State: &terraform.InstanceState{ 2854 Attributes: map[string]string{ 2855 "acl": "public-read", 2856 "bucket": "tf-test-bucket-5011072831090096749", 2857 "website.#": "1", 2858 "website.0.error_document": "error.html", 2859 "website.0.index_document": "index.html", 2860 "website.0.redirect_all_requests_to": "", 2861 }, 2862 }, 2863 2864 Config: map[string]interface{}{ 2865 "acl": "public-read", 2866 "bucket": "tf-test-bucket-5011072831090096749", 2867 }, 2868 2869 Diff: &terraform.InstanceDiff{ 2870 Attributes: map[string]*terraform.ResourceAttrDiff{ 2871 "website.#": &terraform.ResourceAttrDiff{ 2872 Old: "1", 2873 New: "0", 2874 NewRemoved: false, 2875 }, 2876 "website.0.index_document": &terraform.ResourceAttrDiff{ 2877 Old: "index.html", 2878 New: "", 2879 NewRemoved: true, 2880 }, 2881 "website.0.error_document": &terraform.ResourceAttrDiff{ 2882 Old: "error.html", 2883 New: "", 2884 NewRemoved: true, 2885 }, 2886 "website.0.redirect_all_requests_to": &terraform.ResourceAttrDiff{ 2887 Old: "", 2888 New: "", 2889 NewRemoved: true, 2890 }, 2891 "website.0.routing_rules": &terraform.ResourceAttrDiff{ 2892 Old: "", 2893 New: "", 2894 NewRemoved: true, 2895 }, 2896 }, 2897 }, 2898 }, 2899 2900 { 2901 Name: "Removal of TypeList should cause nested Int fields w/ Default to be removed too", 2902 Schema: map[string]*Schema{ 2903 "availability_zones": &Schema{ 2904 Type: TypeSet, 2905 Elem: &Schema{Type: TypeString}, 2906 Optional: true, 2907 Computed: true, 2908 Set: HashString, 2909 }, 2910 2911 "access_logs": &Schema{ 2912 Type: TypeList, 2913 Optional: true, 2914 MaxItems: 1, 2915 Elem: &Resource{ 2916 Schema: map[string]*Schema{ 2917 "interval": &Schema{ 2918 Type: TypeInt, 2919 Optional: true, 2920 Default: 60, 2921 }, 2922 "bucket": &Schema{ 2923 Type: TypeString, 2924 Required: true, 2925 }, 2926 "bucket_prefix": &Schema{ 2927 Type: TypeString, 2928 Optional: true, 2929 }, 2930 "enabled": &Schema{ 2931 Type: TypeBool, 2932 Optional: true, 2933 Default: true, 2934 }, 2935 }, 2936 }, 2937 }, 2938 }, 2939 2940 State: &terraform.InstanceState{ 2941 Attributes: map[string]string{ 2942 "access_logs.#": "1", 2943 "access_logs.0.bucket": "terraform-access-logs-bucket-5906065226840117876", 2944 "access_logs.0.bucket_prefix": "", 2945 "access_logs.0.enabled": "true", 2946 "access_logs.0.interval": "5", 2947 "availability_zones.#": "3", 2948 "availability_zones.2050015877": "us-west-2c", 2949 "availability_zones.221770259": "us-west-2b", 2950 "availability_zones.2487133097": "us-west-2a", 2951 }, 2952 }, 2953 2954 Config: map[string]interface{}{ 2955 "availability_zones": []interface{}{"us-west-2a", "us-west-2b", "us-west-2c"}, 2956 }, 2957 2958 Diff: &terraform.InstanceDiff{ 2959 Attributes: map[string]*terraform.ResourceAttrDiff{ 2960 "access_logs.#": &terraform.ResourceAttrDiff{ 2961 Old: "1", 2962 New: "0", 2963 NewRemoved: false, 2964 }, 2965 "access_logs.0.bucket": &terraform.ResourceAttrDiff{ 2966 Old: "terraform-access-logs-bucket-5906065226840117876", 2967 New: "", 2968 NewRemoved: true, 2969 }, 2970 "access_logs.0.bucket_prefix": &terraform.ResourceAttrDiff{ 2971 Old: "", 2972 New: "", 2973 NewRemoved: true, 2974 }, 2975 "access_logs.0.enabled": &terraform.ResourceAttrDiff{ 2976 Old: "", 2977 New: "", 2978 NewRemoved: true, 2979 }, 2980 "access_logs.0.interval": &terraform.ResourceAttrDiff{ 2981 Old: "5", 2982 New: "60", 2983 NewRemoved: true, 2984 }, 2985 }, 2986 }, 2987 }, 2988 2989 { 2990 Name: "Removal of TypeSet should cause computed fields to be removed", 2991 Schema: map[string]*Schema{ 2992 "type_set": &Schema{ 2993 Type: TypeSet, 2994 Optional: true, 2995 Elem: &Resource{ 2996 Schema: map[string]*Schema{ 2997 "name": &Schema{ 2998 Type: TypeString, 2999 Optional: true, 3000 }, 3001 "required": &Schema{ 3002 Type: TypeString, 3003 Required: true, 3004 }, 3005 "value": &Schema{ 3006 Type: TypeInt, 3007 Optional: true, 3008 }, 3009 "required_value": &Schema{ 3010 Type: TypeInt, 3011 Required: true, 3012 }, 3013 "computed_value": &Schema{ 3014 Type: TypeString, 3015 Optional: true, 3016 Computed: true, 3017 }, 3018 }, 3019 }, 3020 Set: func(i interface{}) int { 3021 if i != nil { 3022 return 12345 3023 } 3024 return 0 3025 }, 3026 }, 3027 }, 3028 3029 State: &terraform.InstanceState{ 3030 Attributes: map[string]string{ 3031 "type_set.#": "1", 3032 "type_set.12345.name": "Name", 3033 "type_set.12345.required": "Required", 3034 "type_set.12345.value": "0", 3035 "type_set.12345.required_value": "5", 3036 "type_set.12345.computed_value": "COMPUTED", 3037 }, 3038 }, 3039 3040 Config: map[string]interface{}{ 3041 "type_set": []interface{}{}, 3042 }, 3043 3044 Diff: &terraform.InstanceDiff{ 3045 Attributes: map[string]*terraform.ResourceAttrDiff{ 3046 "type_set.#": &terraform.ResourceAttrDiff{ 3047 Old: "1", 3048 New: "0", 3049 NewRemoved: false, 3050 }, 3051 "type_set.12345.name": &terraform.ResourceAttrDiff{ 3052 Old: "Name", 3053 New: "", 3054 NewRemoved: true, 3055 }, 3056 "type_set.12345.required": &terraform.ResourceAttrDiff{ 3057 Old: "Required", 3058 New: "", 3059 NewRemoved: true, 3060 }, 3061 "type_set.12345.value": &terraform.ResourceAttrDiff{ 3062 Old: "0", 3063 New: "0", 3064 NewRemoved: true, 3065 }, 3066 "type_set.12345.required_value": &terraform.ResourceAttrDiff{ 3067 Old: "5", 3068 New: "0", 3069 NewRemoved: true, 3070 }, 3071 "type_set.12345.computed_value": &terraform.ResourceAttrDiff{ 3072 NewRemoved: true, 3073 }, 3074 }, 3075 }, 3076 }, 3077 } 3078 3079 for i, tc := range cases { 3080 t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { 3081 c, err := config.NewRawConfig(tc.Config) 3082 if err != nil { 3083 t.Fatalf("err: %s", err) 3084 } 3085 3086 if len(tc.ConfigVariables) > 0 { 3087 if err := c.Interpolate(tc.ConfigVariables); err != nil { 3088 t.Fatalf("err: %s", err) 3089 } 3090 } 3091 3092 d, err := schemaMap(tc.Schema).Diff( 3093 tc.State, terraform.NewResourceConfig(c)) 3094 if err != nil != tc.Err { 3095 t.Fatalf("err: %s", err) 3096 } 3097 3098 if !reflect.DeepEqual(tc.Diff, d) { 3099 t.Fatalf("expected:\n%#v\n\ngot:\n%#v", tc.Diff, d) 3100 } 3101 }) 3102 } 3103 } 3104 3105 func TestSchemaMap_Input(t *testing.T) { 3106 cases := map[string]struct { 3107 Schema map[string]*Schema 3108 Config map[string]interface{} 3109 Input map[string]string 3110 Result map[string]interface{} 3111 Err bool 3112 }{ 3113 /* 3114 * String decode 3115 */ 3116 3117 "uses input on optional field with no config": { 3118 Schema: map[string]*Schema{ 3119 "availability_zone": &Schema{ 3120 Type: TypeString, 3121 Optional: true, 3122 }, 3123 }, 3124 3125 Input: map[string]string{ 3126 "availability_zone": "foo", 3127 }, 3128 3129 Result: map[string]interface{}{ 3130 "availability_zone": "foo", 3131 }, 3132 3133 Err: false, 3134 }, 3135 3136 "input ignored when config has a value": { 3137 Schema: map[string]*Schema{ 3138 "availability_zone": &Schema{ 3139 Type: TypeString, 3140 Optional: true, 3141 }, 3142 }, 3143 3144 Config: map[string]interface{}{ 3145 "availability_zone": "bar", 3146 }, 3147 3148 Input: map[string]string{ 3149 "availability_zone": "foo", 3150 }, 3151 3152 Result: map[string]interface{}{}, 3153 3154 Err: false, 3155 }, 3156 3157 "input ignored when schema has a default": { 3158 Schema: map[string]*Schema{ 3159 "availability_zone": &Schema{ 3160 Type: TypeString, 3161 Default: "foo", 3162 Optional: true, 3163 }, 3164 }, 3165 3166 Input: map[string]string{ 3167 "availability_zone": "bar", 3168 }, 3169 3170 Result: map[string]interface{}{}, 3171 3172 Err: false, 3173 }, 3174 3175 "input ignored when default function returns a value": { 3176 Schema: map[string]*Schema{ 3177 "availability_zone": &Schema{ 3178 Type: TypeString, 3179 DefaultFunc: func() (interface{}, error) { 3180 return "foo", nil 3181 }, 3182 Optional: true, 3183 }, 3184 }, 3185 3186 Input: map[string]string{ 3187 "availability_zone": "bar", 3188 }, 3189 3190 Result: map[string]interface{}{}, 3191 3192 Err: false, 3193 }, 3194 3195 "input ignored when default function returns an empty string": { 3196 Schema: map[string]*Schema{ 3197 "availability_zone": &Schema{ 3198 Type: TypeString, 3199 Default: "", 3200 Optional: true, 3201 }, 3202 }, 3203 3204 Input: map[string]string{ 3205 "availability_zone": "bar", 3206 }, 3207 3208 Result: map[string]interface{}{}, 3209 3210 Err: false, 3211 }, 3212 3213 "input used when default function returns nil": { 3214 Schema: map[string]*Schema{ 3215 "availability_zone": &Schema{ 3216 Type: TypeString, 3217 DefaultFunc: func() (interface{}, error) { 3218 return nil, nil 3219 }, 3220 Optional: true, 3221 }, 3222 }, 3223 3224 Input: map[string]string{ 3225 "availability_zone": "bar", 3226 }, 3227 3228 Result: map[string]interface{}{ 3229 "availability_zone": "bar", 3230 }, 3231 3232 Err: false, 3233 }, 3234 } 3235 3236 for i, tc := range cases { 3237 if tc.Config == nil { 3238 tc.Config = make(map[string]interface{}) 3239 } 3240 3241 c, err := config.NewRawConfig(tc.Config) 3242 if err != nil { 3243 t.Fatalf("err: %s", err) 3244 } 3245 3246 input := new(terraform.MockUIInput) 3247 input.InputReturnMap = tc.Input 3248 3249 rc := terraform.NewResourceConfig(c) 3250 rc.Config = make(map[string]interface{}) 3251 3252 actual, err := schemaMap(tc.Schema).Input(input, rc) 3253 if err != nil != tc.Err { 3254 t.Fatalf("#%v err: %s", i, err) 3255 } 3256 3257 if !reflect.DeepEqual(tc.Result, actual.Config) { 3258 t.Fatalf("#%v: bad:\n\ngot: %#v\nexpected: %#v", i, actual.Config, tc.Result) 3259 } 3260 } 3261 } 3262 3263 func TestSchemaMap_InputDefault(t *testing.T) { 3264 emptyConfig := make(map[string]interface{}) 3265 c, err := config.NewRawConfig(emptyConfig) 3266 if err != nil { 3267 t.Fatalf("err: %s", err) 3268 } 3269 rc := terraform.NewResourceConfig(c) 3270 rc.Config = make(map[string]interface{}) 3271 3272 input := new(terraform.MockUIInput) 3273 input.InputFn = func(opts *terraform.InputOpts) (string, error) { 3274 t.Fatalf("InputFn should not be called on: %#v", opts) 3275 return "", nil 3276 } 3277 3278 schema := map[string]*Schema{ 3279 "availability_zone": &Schema{ 3280 Type: TypeString, 3281 Default: "foo", 3282 Optional: true, 3283 }, 3284 } 3285 actual, err := schemaMap(schema).Input(input, rc) 3286 if err != nil { 3287 t.Fatalf("err: %s", err) 3288 } 3289 3290 expected := map[string]interface{}{} 3291 3292 if !reflect.DeepEqual(expected, actual.Config) { 3293 t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected) 3294 } 3295 } 3296 3297 func TestSchemaMap_InputDeprecated(t *testing.T) { 3298 emptyConfig := make(map[string]interface{}) 3299 c, err := config.NewRawConfig(emptyConfig) 3300 if err != nil { 3301 t.Fatalf("err: %s", err) 3302 } 3303 rc := terraform.NewResourceConfig(c) 3304 rc.Config = make(map[string]interface{}) 3305 3306 input := new(terraform.MockUIInput) 3307 input.InputFn = func(opts *terraform.InputOpts) (string, error) { 3308 t.Fatalf("InputFn should not be called on: %#v", opts) 3309 return "", nil 3310 } 3311 3312 schema := map[string]*Schema{ 3313 "availability_zone": &Schema{ 3314 Type: TypeString, 3315 Deprecated: "long gone", 3316 Optional: true, 3317 }, 3318 } 3319 actual, err := schemaMap(schema).Input(input, rc) 3320 if err != nil { 3321 t.Fatalf("err: %s", err) 3322 } 3323 3324 expected := map[string]interface{}{} 3325 3326 if !reflect.DeepEqual(expected, actual.Config) { 3327 t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected) 3328 } 3329 } 3330 3331 func TestSchemaMap_InternalValidate(t *testing.T) { 3332 cases := map[string]struct { 3333 In map[string]*Schema 3334 Err bool 3335 }{ 3336 "nothing": { 3337 nil, 3338 false, 3339 }, 3340 3341 "Both optional and required": { 3342 map[string]*Schema{ 3343 "foo": &Schema{ 3344 Type: TypeInt, 3345 Optional: true, 3346 Required: true, 3347 }, 3348 }, 3349 true, 3350 }, 3351 3352 "No optional and no required": { 3353 map[string]*Schema{ 3354 "foo": &Schema{ 3355 Type: TypeInt, 3356 }, 3357 }, 3358 true, 3359 }, 3360 3361 "Missing Type": { 3362 map[string]*Schema{ 3363 "foo": &Schema{ 3364 Required: true, 3365 }, 3366 }, 3367 true, 3368 }, 3369 3370 "Required but computed": { 3371 map[string]*Schema{ 3372 "foo": &Schema{ 3373 Type: TypeInt, 3374 Required: true, 3375 Computed: true, 3376 }, 3377 }, 3378 true, 3379 }, 3380 3381 "Looks good": { 3382 map[string]*Schema{ 3383 "foo": &Schema{ 3384 Type: TypeString, 3385 Required: true, 3386 }, 3387 }, 3388 false, 3389 }, 3390 3391 "Computed but has default": { 3392 map[string]*Schema{ 3393 "foo": &Schema{ 3394 Type: TypeInt, 3395 Optional: true, 3396 Computed: true, 3397 Default: "foo", 3398 }, 3399 }, 3400 true, 3401 }, 3402 3403 "Required but has default": { 3404 map[string]*Schema{ 3405 "foo": &Schema{ 3406 Type: TypeInt, 3407 Optional: true, 3408 Required: true, 3409 Default: "foo", 3410 }, 3411 }, 3412 true, 3413 }, 3414 3415 "List element not set": { 3416 map[string]*Schema{ 3417 "foo": &Schema{ 3418 Type: TypeList, 3419 }, 3420 }, 3421 true, 3422 }, 3423 3424 "List default": { 3425 map[string]*Schema{ 3426 "foo": &Schema{ 3427 Type: TypeList, 3428 Elem: &Schema{Type: TypeInt}, 3429 Default: "foo", 3430 }, 3431 }, 3432 true, 3433 }, 3434 3435 "List element computed": { 3436 map[string]*Schema{ 3437 "foo": &Schema{ 3438 Type: TypeList, 3439 Optional: true, 3440 Elem: &Schema{ 3441 Type: TypeInt, 3442 Computed: true, 3443 }, 3444 }, 3445 }, 3446 true, 3447 }, 3448 3449 "List element with Set set": { 3450 map[string]*Schema{ 3451 "foo": &Schema{ 3452 Type: TypeList, 3453 Elem: &Schema{Type: TypeInt}, 3454 Set: func(interface{}) int { return 0 }, 3455 Optional: true, 3456 }, 3457 }, 3458 true, 3459 }, 3460 3461 "Set element with no Set set": { 3462 map[string]*Schema{ 3463 "foo": &Schema{ 3464 Type: TypeSet, 3465 Elem: &Schema{Type: TypeInt}, 3466 Optional: true, 3467 }, 3468 }, 3469 false, 3470 }, 3471 3472 "Required but computedWhen": { 3473 map[string]*Schema{ 3474 "foo": &Schema{ 3475 Type: TypeInt, 3476 Required: true, 3477 ComputedWhen: []string{"foo"}, 3478 }, 3479 }, 3480 true, 3481 }, 3482 3483 "Conflicting attributes cannot be required": { 3484 map[string]*Schema{ 3485 "blacklist": &Schema{ 3486 Type: TypeBool, 3487 Required: true, 3488 }, 3489 "whitelist": &Schema{ 3490 Type: TypeBool, 3491 Optional: true, 3492 ConflictsWith: []string{"blacklist"}, 3493 }, 3494 }, 3495 true, 3496 }, 3497 3498 "Attribute with conflicts cannot be required": { 3499 map[string]*Schema{ 3500 "whitelist": &Schema{ 3501 Type: TypeBool, 3502 Required: true, 3503 ConflictsWith: []string{"blacklist"}, 3504 }, 3505 }, 3506 true, 3507 }, 3508 3509 "ConflictsWith cannot be used w/ ComputedWhen": { 3510 map[string]*Schema{ 3511 "blacklist": &Schema{ 3512 Type: TypeBool, 3513 ComputedWhen: []string{"foor"}, 3514 }, 3515 "whitelist": &Schema{ 3516 Type: TypeBool, 3517 Required: true, 3518 ConflictsWith: []string{"blacklist"}, 3519 }, 3520 }, 3521 true, 3522 }, 3523 3524 "Sub-resource invalid": { 3525 map[string]*Schema{ 3526 "foo": &Schema{ 3527 Type: TypeList, 3528 Optional: true, 3529 Elem: &Resource{ 3530 Schema: map[string]*Schema{ 3531 "foo": new(Schema), 3532 }, 3533 }, 3534 }, 3535 }, 3536 true, 3537 }, 3538 3539 "Sub-resource valid": { 3540 map[string]*Schema{ 3541 "foo": &Schema{ 3542 Type: TypeList, 3543 Optional: true, 3544 Elem: &Resource{ 3545 Schema: map[string]*Schema{ 3546 "foo": &Schema{ 3547 Type: TypeInt, 3548 Optional: true, 3549 }, 3550 }, 3551 }, 3552 }, 3553 }, 3554 false, 3555 }, 3556 3557 "ValidateFunc on non-primitive": { 3558 map[string]*Schema{ 3559 "foo": &Schema{ 3560 Type: TypeSet, 3561 Required: true, 3562 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 3563 return 3564 }, 3565 }, 3566 }, 3567 true, 3568 }, 3569 } 3570 3571 for tn, tc := range cases { 3572 err := schemaMap(tc.In).InternalValidate(nil) 3573 if err != nil != tc.Err { 3574 if tc.Err { 3575 t.Fatalf("%q: Expected error did not occur:\n\n%#v", tn, tc.In) 3576 } 3577 t.Fatalf("%q: Unexpected error occurred:\n\n%#v", tn, tc.In) 3578 } 3579 } 3580 3581 } 3582 3583 func TestSchemaMap_DiffSuppress(t *testing.T) { 3584 cases := map[string]struct { 3585 Schema map[string]*Schema 3586 State *terraform.InstanceState 3587 Config map[string]interface{} 3588 ConfigVariables map[string]ast.Variable 3589 ExpectedDiff *terraform.InstanceDiff 3590 Err bool 3591 }{ 3592 "#0 - Suppress otherwise valid diff by returning true": { 3593 Schema: map[string]*Schema{ 3594 "availability_zone": { 3595 Type: TypeString, 3596 Optional: true, 3597 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3598 // Always suppress any diff 3599 return true 3600 }, 3601 }, 3602 }, 3603 3604 State: nil, 3605 3606 Config: map[string]interface{}{ 3607 "availability_zone": "foo", 3608 }, 3609 3610 ExpectedDiff: nil, 3611 3612 Err: false, 3613 }, 3614 3615 "#1 - Don't suppress diff by returning false": { 3616 Schema: map[string]*Schema{ 3617 "availability_zone": { 3618 Type: TypeString, 3619 Optional: true, 3620 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3621 // Always suppress any diff 3622 return false 3623 }, 3624 }, 3625 }, 3626 3627 State: nil, 3628 3629 Config: map[string]interface{}{ 3630 "availability_zone": "foo", 3631 }, 3632 3633 ExpectedDiff: &terraform.InstanceDiff{ 3634 Attributes: map[string]*terraform.ResourceAttrDiff{ 3635 "availability_zone": { 3636 Old: "", 3637 New: "foo", 3638 }, 3639 }, 3640 }, 3641 3642 Err: false, 3643 }, 3644 3645 "Default with suppress makes no diff": { 3646 Schema: map[string]*Schema{ 3647 "availability_zone": { 3648 Type: TypeString, 3649 Optional: true, 3650 Default: "foo", 3651 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3652 return true 3653 }, 3654 }, 3655 }, 3656 3657 State: nil, 3658 3659 Config: map[string]interface{}{}, 3660 3661 ExpectedDiff: nil, 3662 3663 Err: false, 3664 }, 3665 3666 "Default with false suppress makes diff": { 3667 Schema: map[string]*Schema{ 3668 "availability_zone": { 3669 Type: TypeString, 3670 Optional: true, 3671 Default: "foo", 3672 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3673 return false 3674 }, 3675 }, 3676 }, 3677 3678 State: nil, 3679 3680 Config: map[string]interface{}{}, 3681 3682 ExpectedDiff: &terraform.InstanceDiff{ 3683 Attributes: map[string]*terraform.ResourceAttrDiff{ 3684 "availability_zone": { 3685 Old: "", 3686 New: "foo", 3687 }, 3688 }, 3689 }, 3690 3691 Err: false, 3692 }, 3693 3694 "Complex structure with set of computed string should mark root set as computed": { 3695 Schema: map[string]*Schema{ 3696 "outer": &Schema{ 3697 Type: TypeSet, 3698 Optional: true, 3699 Elem: &Resource{ 3700 Schema: map[string]*Schema{ 3701 "outer_str": &Schema{ 3702 Type: TypeString, 3703 Optional: true, 3704 }, 3705 "inner": &Schema{ 3706 Type: TypeSet, 3707 Optional: true, 3708 Elem: &Resource{ 3709 Schema: map[string]*Schema{ 3710 "inner_str": &Schema{ 3711 Type: TypeString, 3712 Optional: true, 3713 }, 3714 }, 3715 }, 3716 Set: func(v interface{}) int { 3717 return 2 3718 }, 3719 }, 3720 }, 3721 }, 3722 Set: func(v interface{}) int { 3723 return 1 3724 }, 3725 }, 3726 }, 3727 3728 State: nil, 3729 3730 Config: map[string]interface{}{ 3731 "outer": []map[string]interface{}{ 3732 map[string]interface{}{ 3733 "outer_str": "foo", 3734 "inner": []map[string]interface{}{ 3735 map[string]interface{}{ 3736 "inner_str": "${var.bar}", 3737 }, 3738 }, 3739 }, 3740 }, 3741 }, 3742 3743 ConfigVariables: map[string]ast.Variable{ 3744 "var.bar": interfaceToVariableSwallowError(config.UnknownVariableValue), 3745 }, 3746 3747 ExpectedDiff: &terraform.InstanceDiff{ 3748 Attributes: map[string]*terraform.ResourceAttrDiff{ 3749 "outer.#": &terraform.ResourceAttrDiff{ 3750 Old: "0", 3751 New: "1", 3752 }, 3753 "outer.~1.outer_str": &terraform.ResourceAttrDiff{ 3754 Old: "", 3755 New: "foo", 3756 }, 3757 "outer.~1.inner.#": &terraform.ResourceAttrDiff{ 3758 Old: "0", 3759 New: "1", 3760 }, 3761 "outer.~1.inner.~2.inner_str": &terraform.ResourceAttrDiff{ 3762 Old: "", 3763 New: "${var.bar}", 3764 NewComputed: true, 3765 }, 3766 }, 3767 }, 3768 3769 Err: false, 3770 }, 3771 3772 "Complex structure with complex list of computed string should mark root set as computed": { 3773 Schema: map[string]*Schema{ 3774 "outer": &Schema{ 3775 Type: TypeSet, 3776 Optional: true, 3777 Elem: &Resource{ 3778 Schema: map[string]*Schema{ 3779 "outer_str": &Schema{ 3780 Type: TypeString, 3781 Optional: true, 3782 }, 3783 "inner": &Schema{ 3784 Type: TypeList, 3785 Optional: true, 3786 Elem: &Resource{ 3787 Schema: map[string]*Schema{ 3788 "inner_str": &Schema{ 3789 Type: TypeString, 3790 Optional: true, 3791 }, 3792 }, 3793 }, 3794 }, 3795 }, 3796 }, 3797 Set: func(v interface{}) int { 3798 return 1 3799 }, 3800 }, 3801 }, 3802 3803 State: nil, 3804 3805 Config: map[string]interface{}{ 3806 "outer": []map[string]interface{}{ 3807 map[string]interface{}{ 3808 "outer_str": "foo", 3809 "inner": []map[string]interface{}{ 3810 map[string]interface{}{ 3811 "inner_str": "${var.bar}", 3812 }, 3813 }, 3814 }, 3815 }, 3816 }, 3817 3818 ConfigVariables: map[string]ast.Variable{ 3819 "var.bar": interfaceToVariableSwallowError(config.UnknownVariableValue), 3820 }, 3821 3822 ExpectedDiff: &terraform.InstanceDiff{ 3823 Attributes: map[string]*terraform.ResourceAttrDiff{ 3824 "outer.#": &terraform.ResourceAttrDiff{ 3825 Old: "0", 3826 New: "1", 3827 }, 3828 "outer.~1.outer_str": &terraform.ResourceAttrDiff{ 3829 Old: "", 3830 New: "foo", 3831 }, 3832 "outer.~1.inner.#": &terraform.ResourceAttrDiff{ 3833 Old: "0", 3834 New: "1", 3835 }, 3836 "outer.~1.inner.0.inner_str": &terraform.ResourceAttrDiff{ 3837 Old: "", 3838 New: "${var.bar}", 3839 NewComputed: true, 3840 }, 3841 }, 3842 }, 3843 3844 Err: false, 3845 }, 3846 } 3847 3848 for tn, tc := range cases { 3849 t.Run(tn, func(t *testing.T) { 3850 c, err := config.NewRawConfig(tc.Config) 3851 if err != nil { 3852 t.Fatalf("#%q err: %s", tn, err) 3853 } 3854 3855 if len(tc.ConfigVariables) > 0 { 3856 if err := c.Interpolate(tc.ConfigVariables); err != nil { 3857 t.Fatalf("#%q err: %s", tn, err) 3858 } 3859 } 3860 3861 d, err := schemaMap(tc.Schema).Diff( 3862 tc.State, terraform.NewResourceConfig(c)) 3863 if err != nil != tc.Err { 3864 t.Fatalf("#%q err: %s", tn, err) 3865 } 3866 3867 if !reflect.DeepEqual(tc.ExpectedDiff, d) { 3868 t.Fatalf("#%q:\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.ExpectedDiff, d) 3869 } 3870 }) 3871 } 3872 } 3873 3874 func TestSchemaMap_Validate(t *testing.T) { 3875 cases := map[string]struct { 3876 Schema map[string]*Schema 3877 Config map[string]interface{} 3878 Vars map[string]string 3879 Err bool 3880 Errors []error 3881 Warnings []string 3882 }{ 3883 "Good": { 3884 Schema: map[string]*Schema{ 3885 "availability_zone": &Schema{ 3886 Type: TypeString, 3887 Optional: true, 3888 Computed: true, 3889 ForceNew: true, 3890 }, 3891 }, 3892 3893 Config: map[string]interface{}{ 3894 "availability_zone": "foo", 3895 }, 3896 }, 3897 3898 "Good, because the var is not set and that error will come elsewhere": { 3899 Schema: map[string]*Schema{ 3900 "size": &Schema{ 3901 Type: TypeInt, 3902 Required: true, 3903 }, 3904 }, 3905 3906 Config: map[string]interface{}{ 3907 "size": "${var.foo}", 3908 }, 3909 3910 Vars: map[string]string{ 3911 "var.foo": config.UnknownVariableValue, 3912 }, 3913 }, 3914 3915 "Required field not set": { 3916 Schema: map[string]*Schema{ 3917 "availability_zone": &Schema{ 3918 Type: TypeString, 3919 Required: true, 3920 }, 3921 }, 3922 3923 Config: map[string]interface{}{}, 3924 3925 Err: true, 3926 }, 3927 3928 "Invalid basic type": { 3929 Schema: map[string]*Schema{ 3930 "port": &Schema{ 3931 Type: TypeInt, 3932 Required: true, 3933 }, 3934 }, 3935 3936 Config: map[string]interface{}{ 3937 "port": "I am invalid", 3938 }, 3939 3940 Err: true, 3941 }, 3942 3943 "Invalid complex type": { 3944 Schema: map[string]*Schema{ 3945 "user_data": &Schema{ 3946 Type: TypeString, 3947 Optional: true, 3948 }, 3949 }, 3950 3951 Config: map[string]interface{}{ 3952 "user_data": []interface{}{ 3953 map[string]interface{}{ 3954 "foo": "bar", 3955 }, 3956 }, 3957 }, 3958 3959 Err: true, 3960 }, 3961 3962 "Bad type, interpolated": { 3963 Schema: map[string]*Schema{ 3964 "size": &Schema{ 3965 Type: TypeInt, 3966 Required: true, 3967 }, 3968 }, 3969 3970 Config: map[string]interface{}{ 3971 "size": "${var.foo}", 3972 }, 3973 3974 Vars: map[string]string{ 3975 "var.foo": "nope", 3976 }, 3977 3978 Err: true, 3979 }, 3980 3981 "Required but has DefaultFunc": { 3982 Schema: map[string]*Schema{ 3983 "availability_zone": &Schema{ 3984 Type: TypeString, 3985 Required: true, 3986 DefaultFunc: func() (interface{}, error) { 3987 return "foo", nil 3988 }, 3989 }, 3990 }, 3991 3992 Config: nil, 3993 }, 3994 3995 "Required but has DefaultFunc return nil": { 3996 Schema: map[string]*Schema{ 3997 "availability_zone": &Schema{ 3998 Type: TypeString, 3999 Required: true, 4000 DefaultFunc: func() (interface{}, error) { 4001 return nil, nil 4002 }, 4003 }, 4004 }, 4005 4006 Config: nil, 4007 4008 Err: true, 4009 }, 4010 4011 "List with promotion": { 4012 Schema: map[string]*Schema{ 4013 "ingress": &Schema{ 4014 Type: TypeList, 4015 Elem: &Schema{Type: TypeInt}, 4016 PromoteSingle: true, 4017 Optional: true, 4018 }, 4019 }, 4020 4021 Config: map[string]interface{}{ 4022 "ingress": "5", 4023 }, 4024 4025 Err: false, 4026 }, 4027 4028 "List with promotion set as list": { 4029 Schema: map[string]*Schema{ 4030 "ingress": &Schema{ 4031 Type: TypeList, 4032 Elem: &Schema{Type: TypeInt}, 4033 PromoteSingle: true, 4034 Optional: true, 4035 }, 4036 }, 4037 4038 Config: map[string]interface{}{ 4039 "ingress": []interface{}{"5"}, 4040 }, 4041 4042 Err: false, 4043 }, 4044 4045 "Optional sub-resource": { 4046 Schema: map[string]*Schema{ 4047 "ingress": &Schema{ 4048 Type: TypeList, 4049 Elem: &Resource{ 4050 Schema: map[string]*Schema{ 4051 "from": &Schema{ 4052 Type: TypeInt, 4053 Required: true, 4054 }, 4055 }, 4056 }, 4057 }, 4058 }, 4059 4060 Config: map[string]interface{}{}, 4061 4062 Err: false, 4063 }, 4064 4065 "Sub-resource is the wrong type": { 4066 Schema: map[string]*Schema{ 4067 "ingress": &Schema{ 4068 Type: TypeList, 4069 Required: true, 4070 Elem: &Resource{ 4071 Schema: map[string]*Schema{ 4072 "from": &Schema{ 4073 Type: TypeInt, 4074 Required: true, 4075 }, 4076 }, 4077 }, 4078 }, 4079 }, 4080 4081 Config: map[string]interface{}{ 4082 "ingress": []interface{}{"foo"}, 4083 }, 4084 4085 Err: true, 4086 }, 4087 4088 "Not a list": { 4089 Schema: map[string]*Schema{ 4090 "ingress": &Schema{ 4091 Type: TypeList, 4092 Elem: &Resource{ 4093 Schema: map[string]*Schema{ 4094 "from": &Schema{ 4095 Type: TypeInt, 4096 Required: true, 4097 }, 4098 }, 4099 }, 4100 }, 4101 }, 4102 4103 Config: map[string]interface{}{ 4104 "ingress": "foo", 4105 }, 4106 4107 Err: true, 4108 }, 4109 4110 "Required sub-resource field": { 4111 Schema: map[string]*Schema{ 4112 "ingress": &Schema{ 4113 Type: TypeList, 4114 Elem: &Resource{ 4115 Schema: map[string]*Schema{ 4116 "from": &Schema{ 4117 Type: TypeInt, 4118 Required: true, 4119 }, 4120 }, 4121 }, 4122 }, 4123 }, 4124 4125 Config: map[string]interface{}{ 4126 "ingress": []interface{}{ 4127 map[string]interface{}{}, 4128 }, 4129 }, 4130 4131 Err: true, 4132 }, 4133 4134 "Good sub-resource": { 4135 Schema: map[string]*Schema{ 4136 "ingress": &Schema{ 4137 Type: TypeList, 4138 Optional: true, 4139 Elem: &Resource{ 4140 Schema: map[string]*Schema{ 4141 "from": &Schema{ 4142 Type: TypeInt, 4143 Required: true, 4144 }, 4145 }, 4146 }, 4147 }, 4148 }, 4149 4150 Config: map[string]interface{}{ 4151 "ingress": []interface{}{ 4152 map[string]interface{}{ 4153 "from": 80, 4154 }, 4155 }, 4156 }, 4157 4158 Err: false, 4159 }, 4160 4161 "Invalid/unknown field": { 4162 Schema: map[string]*Schema{ 4163 "availability_zone": &Schema{ 4164 Type: TypeString, 4165 Optional: true, 4166 Computed: true, 4167 ForceNew: true, 4168 }, 4169 }, 4170 4171 Config: map[string]interface{}{ 4172 "foo": "bar", 4173 }, 4174 4175 Err: true, 4176 }, 4177 4178 "Invalid/unknown field with computed value": { 4179 Schema: map[string]*Schema{ 4180 "availability_zone": &Schema{ 4181 Type: TypeString, 4182 Optional: true, 4183 Computed: true, 4184 ForceNew: true, 4185 }, 4186 }, 4187 4188 Config: map[string]interface{}{ 4189 "foo": "${var.foo}", 4190 }, 4191 4192 Vars: map[string]string{ 4193 "var.foo": config.UnknownVariableValue, 4194 }, 4195 4196 Err: true, 4197 }, 4198 4199 "Computed field set": { 4200 Schema: map[string]*Schema{ 4201 "availability_zone": &Schema{ 4202 Type: TypeString, 4203 Computed: true, 4204 }, 4205 }, 4206 4207 Config: map[string]interface{}{ 4208 "availability_zone": "bar", 4209 }, 4210 4211 Err: true, 4212 }, 4213 4214 "Not a set": { 4215 Schema: map[string]*Schema{ 4216 "ports": &Schema{ 4217 Type: TypeSet, 4218 Required: true, 4219 Elem: &Schema{Type: TypeInt}, 4220 Set: func(a interface{}) int { 4221 return a.(int) 4222 }, 4223 }, 4224 }, 4225 4226 Config: map[string]interface{}{ 4227 "ports": "foo", 4228 }, 4229 4230 Err: true, 4231 }, 4232 4233 "Maps": { 4234 Schema: map[string]*Schema{ 4235 "user_data": &Schema{ 4236 Type: TypeMap, 4237 Optional: true, 4238 }, 4239 }, 4240 4241 Config: map[string]interface{}{ 4242 "user_data": "foo", 4243 }, 4244 4245 Err: true, 4246 }, 4247 4248 "Good map: data surrounded by extra slice": { 4249 Schema: map[string]*Schema{ 4250 "user_data": &Schema{ 4251 Type: TypeMap, 4252 Optional: true, 4253 }, 4254 }, 4255 4256 Config: map[string]interface{}{ 4257 "user_data": []interface{}{ 4258 map[string]interface{}{ 4259 "foo": "bar", 4260 }, 4261 }, 4262 }, 4263 }, 4264 4265 "Good map": { 4266 Schema: map[string]*Schema{ 4267 "user_data": &Schema{ 4268 Type: TypeMap, 4269 Optional: true, 4270 }, 4271 }, 4272 4273 Config: map[string]interface{}{ 4274 "user_data": map[string]interface{}{ 4275 "foo": "bar", 4276 }, 4277 }, 4278 }, 4279 4280 "Bad map: just a slice": { 4281 Schema: map[string]*Schema{ 4282 "user_data": &Schema{ 4283 Type: TypeMap, 4284 Optional: true, 4285 }, 4286 }, 4287 4288 Config: map[string]interface{}{ 4289 "user_data": []interface{}{ 4290 "foo", 4291 }, 4292 }, 4293 4294 Err: true, 4295 }, 4296 4297 "Good set: config has slice with single interpolated value": { 4298 Schema: map[string]*Schema{ 4299 "security_groups": &Schema{ 4300 Type: TypeSet, 4301 Optional: true, 4302 Computed: true, 4303 ForceNew: true, 4304 Elem: &Schema{Type: TypeString}, 4305 Set: func(v interface{}) int { 4306 return len(v.(string)) 4307 }, 4308 }, 4309 }, 4310 4311 Config: map[string]interface{}{ 4312 "security_groups": []interface{}{"${var.foo}"}, 4313 }, 4314 4315 Err: false, 4316 }, 4317 4318 "Bad set: config has single interpolated value": { 4319 Schema: map[string]*Schema{ 4320 "security_groups": &Schema{ 4321 Type: TypeSet, 4322 Optional: true, 4323 Computed: true, 4324 ForceNew: true, 4325 Elem: &Schema{Type: TypeString}, 4326 }, 4327 }, 4328 4329 Config: map[string]interface{}{ 4330 "security_groups": "${var.foo}", 4331 }, 4332 4333 Err: true, 4334 }, 4335 4336 "Bad, subresource should not allow unknown elements": { 4337 Schema: map[string]*Schema{ 4338 "ingress": &Schema{ 4339 Type: TypeList, 4340 Optional: true, 4341 Elem: &Resource{ 4342 Schema: map[string]*Schema{ 4343 "port": &Schema{ 4344 Type: TypeInt, 4345 Required: true, 4346 }, 4347 }, 4348 }, 4349 }, 4350 }, 4351 4352 Config: map[string]interface{}{ 4353 "ingress": []interface{}{ 4354 map[string]interface{}{ 4355 "port": 80, 4356 "other": "yes", 4357 }, 4358 }, 4359 }, 4360 4361 Err: true, 4362 }, 4363 4364 "Bad, subresource should not allow invalid types": { 4365 Schema: map[string]*Schema{ 4366 "ingress": &Schema{ 4367 Type: TypeList, 4368 Optional: true, 4369 Elem: &Resource{ 4370 Schema: map[string]*Schema{ 4371 "port": &Schema{ 4372 Type: TypeInt, 4373 Required: true, 4374 }, 4375 }, 4376 }, 4377 }, 4378 }, 4379 4380 Config: map[string]interface{}{ 4381 "ingress": []interface{}{ 4382 map[string]interface{}{ 4383 "port": "bad", 4384 }, 4385 }, 4386 }, 4387 4388 Err: true, 4389 }, 4390 4391 "Bad, should not allow lists to be assigned to string attributes": { 4392 Schema: map[string]*Schema{ 4393 "availability_zone": &Schema{ 4394 Type: TypeString, 4395 Required: true, 4396 }, 4397 }, 4398 4399 Config: map[string]interface{}{ 4400 "availability_zone": []interface{}{"foo", "bar", "baz"}, 4401 }, 4402 4403 Err: true, 4404 }, 4405 4406 "Bad, should not allow maps to be assigned to string attributes": { 4407 Schema: map[string]*Schema{ 4408 "availability_zone": &Schema{ 4409 Type: TypeString, 4410 Required: true, 4411 }, 4412 }, 4413 4414 Config: map[string]interface{}{ 4415 "availability_zone": map[string]interface{}{"foo": "bar", "baz": "thing"}, 4416 }, 4417 4418 Err: true, 4419 }, 4420 4421 "Deprecated attribute usage generates warning, but not error": { 4422 Schema: map[string]*Schema{ 4423 "old_news": &Schema{ 4424 Type: TypeString, 4425 Optional: true, 4426 Deprecated: "please use 'new_news' instead", 4427 }, 4428 }, 4429 4430 Config: map[string]interface{}{ 4431 "old_news": "extra extra!", 4432 }, 4433 4434 Err: false, 4435 4436 Warnings: []string{ 4437 "\"old_news\": [DEPRECATED] please use 'new_news' instead", 4438 }, 4439 }, 4440 4441 "Deprecated generates no warnings if attr not used": { 4442 Schema: map[string]*Schema{ 4443 "old_news": &Schema{ 4444 Type: TypeString, 4445 Optional: true, 4446 Deprecated: "please use 'new_news' instead", 4447 }, 4448 }, 4449 4450 Err: false, 4451 4452 Warnings: nil, 4453 }, 4454 4455 "Removed attribute usage generates error": { 4456 Schema: map[string]*Schema{ 4457 "long_gone": &Schema{ 4458 Type: TypeString, 4459 Optional: true, 4460 Removed: "no longer supported by Cloud API", 4461 }, 4462 }, 4463 4464 Config: map[string]interface{}{ 4465 "long_gone": "still here!", 4466 }, 4467 4468 Err: true, 4469 Errors: []error{ 4470 fmt.Errorf("\"long_gone\": [REMOVED] no longer supported by Cloud API"), 4471 }, 4472 }, 4473 4474 "Removed generates no errors if attr not used": { 4475 Schema: map[string]*Schema{ 4476 "long_gone": &Schema{ 4477 Type: TypeString, 4478 Optional: true, 4479 Removed: "no longer supported by Cloud API", 4480 }, 4481 }, 4482 4483 Err: false, 4484 }, 4485 4486 "Conflicting attributes generate error": { 4487 Schema: map[string]*Schema{ 4488 "whitelist": &Schema{ 4489 Type: TypeString, 4490 Optional: true, 4491 }, 4492 "blacklist": &Schema{ 4493 Type: TypeString, 4494 Optional: true, 4495 ConflictsWith: []string{"whitelist"}, 4496 }, 4497 }, 4498 4499 Config: map[string]interface{}{ 4500 "whitelist": "white-val", 4501 "blacklist": "black-val", 4502 }, 4503 4504 Err: true, 4505 Errors: []error{ 4506 fmt.Errorf("\"blacklist\": conflicts with whitelist (\"white-val\")"), 4507 }, 4508 }, 4509 4510 "Required attribute & undefined conflicting optional are good": { 4511 Schema: map[string]*Schema{ 4512 "required_att": &Schema{ 4513 Type: TypeString, 4514 Required: true, 4515 }, 4516 "optional_att": &Schema{ 4517 Type: TypeString, 4518 Optional: true, 4519 ConflictsWith: []string{"required_att"}, 4520 }, 4521 }, 4522 4523 Config: map[string]interface{}{ 4524 "required_att": "required-val", 4525 }, 4526 4527 Err: false, 4528 }, 4529 4530 "Required conflicting attribute & defined optional generate error": { 4531 Schema: map[string]*Schema{ 4532 "required_att": &Schema{ 4533 Type: TypeString, 4534 Required: true, 4535 }, 4536 "optional_att": &Schema{ 4537 Type: TypeString, 4538 Optional: true, 4539 ConflictsWith: []string{"required_att"}, 4540 }, 4541 }, 4542 4543 Config: map[string]interface{}{ 4544 "required_att": "required-val", 4545 "optional_att": "optional-val", 4546 }, 4547 4548 Err: true, 4549 Errors: []error{ 4550 fmt.Errorf(`"optional_att": conflicts with required_att ("required-val")`), 4551 }, 4552 }, 4553 4554 "Computed + Optional fields conflicting with each other": { 4555 Schema: map[string]*Schema{ 4556 "foo_att": &Schema{ 4557 Type: TypeString, 4558 Optional: true, 4559 Computed: true, 4560 ConflictsWith: []string{"bar_att"}, 4561 }, 4562 "bar_att": &Schema{ 4563 Type: TypeString, 4564 Optional: true, 4565 Computed: true, 4566 ConflictsWith: []string{"foo_att"}, 4567 }, 4568 }, 4569 4570 Config: map[string]interface{}{ 4571 "foo_att": "foo-val", 4572 "bar_att": "bar-val", 4573 }, 4574 4575 Err: true, 4576 Errors: []error{ 4577 fmt.Errorf(`"foo_att": conflicts with bar_att ("bar-val")`), 4578 fmt.Errorf(`"bar_att": conflicts with foo_att ("foo-val")`), 4579 }, 4580 }, 4581 4582 "Computed + Optional fields NOT conflicting with each other": { 4583 Schema: map[string]*Schema{ 4584 "foo_att": &Schema{ 4585 Type: TypeString, 4586 Optional: true, 4587 Computed: true, 4588 ConflictsWith: []string{"bar_att"}, 4589 }, 4590 "bar_att": &Schema{ 4591 Type: TypeString, 4592 Optional: true, 4593 Computed: true, 4594 ConflictsWith: []string{"foo_att"}, 4595 }, 4596 }, 4597 4598 Config: map[string]interface{}{ 4599 "foo_att": "foo-val", 4600 }, 4601 4602 Err: false, 4603 }, 4604 4605 "Computed + Optional fields that conflict with none set": { 4606 Schema: map[string]*Schema{ 4607 "foo_att": &Schema{ 4608 Type: TypeString, 4609 Optional: true, 4610 Computed: true, 4611 ConflictsWith: []string{"bar_att"}, 4612 }, 4613 "bar_att": &Schema{ 4614 Type: TypeString, 4615 Optional: true, 4616 Computed: true, 4617 ConflictsWith: []string{"foo_att"}, 4618 }, 4619 }, 4620 4621 Config: map[string]interface{}{}, 4622 4623 Err: false, 4624 }, 4625 4626 "Good with ValidateFunc": { 4627 Schema: map[string]*Schema{ 4628 "validate_me": &Schema{ 4629 Type: TypeString, 4630 Required: true, 4631 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4632 return 4633 }, 4634 }, 4635 }, 4636 Config: map[string]interface{}{ 4637 "validate_me": "valid", 4638 }, 4639 Err: false, 4640 }, 4641 4642 "Bad with ValidateFunc": { 4643 Schema: map[string]*Schema{ 4644 "validate_me": &Schema{ 4645 Type: TypeString, 4646 Required: true, 4647 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4648 es = append(es, fmt.Errorf("something is not right here")) 4649 return 4650 }, 4651 }, 4652 }, 4653 Config: map[string]interface{}{ 4654 "validate_me": "invalid", 4655 }, 4656 Err: true, 4657 Errors: []error{ 4658 fmt.Errorf(`something is not right here`), 4659 }, 4660 }, 4661 4662 "ValidateFunc not called when type does not match": { 4663 Schema: map[string]*Schema{ 4664 "number": &Schema{ 4665 Type: TypeInt, 4666 Required: true, 4667 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4668 t.Fatalf("Should not have gotten validate call") 4669 return 4670 }, 4671 }, 4672 }, 4673 Config: map[string]interface{}{ 4674 "number": "NaN", 4675 }, 4676 Err: true, 4677 }, 4678 4679 "ValidateFunc gets decoded type": { 4680 Schema: map[string]*Schema{ 4681 "maybe": &Schema{ 4682 Type: TypeBool, 4683 Required: true, 4684 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4685 if _, ok := v.(bool); !ok { 4686 t.Fatalf("Expected bool, got: %#v", v) 4687 } 4688 return 4689 }, 4690 }, 4691 }, 4692 Config: map[string]interface{}{ 4693 "maybe": "true", 4694 }, 4695 }, 4696 4697 "ValidateFunc is not called with a computed value": { 4698 Schema: map[string]*Schema{ 4699 "validate_me": &Schema{ 4700 Type: TypeString, 4701 Required: true, 4702 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4703 es = append(es, fmt.Errorf("something is not right here")) 4704 return 4705 }, 4706 }, 4707 }, 4708 Config: map[string]interface{}{ 4709 "validate_me": "${var.foo}", 4710 }, 4711 Vars: map[string]string{ 4712 "var.foo": config.UnknownVariableValue, 4713 }, 4714 4715 Err: false, 4716 }, 4717 } 4718 4719 for tn, tc := range cases { 4720 c, err := config.NewRawConfig(tc.Config) 4721 if err != nil { 4722 t.Fatalf("err: %s", err) 4723 } 4724 if tc.Vars != nil { 4725 vars := make(map[string]ast.Variable) 4726 for k, v := range tc.Vars { 4727 vars[k] = ast.Variable{Value: v, Type: ast.TypeString} 4728 } 4729 4730 if err := c.Interpolate(vars); err != nil { 4731 t.Fatalf("err: %s", err) 4732 } 4733 } 4734 4735 ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) 4736 if len(es) > 0 != tc.Err { 4737 if len(es) == 0 { 4738 t.Errorf("%q: no errors", tn) 4739 } 4740 4741 for _, e := range es { 4742 t.Errorf("%q: err: %s", tn, e) 4743 } 4744 4745 t.FailNow() 4746 } 4747 4748 if !reflect.DeepEqual(ws, tc.Warnings) { 4749 t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws) 4750 } 4751 4752 if tc.Errors != nil { 4753 sort.Sort(errorSort(es)) 4754 sort.Sort(errorSort(tc.Errors)) 4755 4756 if !reflect.DeepEqual(es, tc.Errors) { 4757 t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es) 4758 } 4759 } 4760 } 4761 } 4762 4763 func TestSchemaSet_ValidateMaxItems(t *testing.T) { 4764 cases := map[string]struct { 4765 Schema map[string]*Schema 4766 State *terraform.InstanceState 4767 Config map[string]interface{} 4768 ConfigVariables map[string]string 4769 Diff *terraform.InstanceDiff 4770 Err bool 4771 Errors []error 4772 }{ 4773 "#0": { 4774 Schema: map[string]*Schema{ 4775 "aliases": &Schema{ 4776 Type: TypeSet, 4777 Optional: true, 4778 MaxItems: 1, 4779 Elem: &Schema{Type: TypeString}, 4780 }, 4781 }, 4782 State: nil, 4783 Config: map[string]interface{}{ 4784 "aliases": []interface{}{"foo", "bar"}, 4785 }, 4786 Diff: nil, 4787 Err: true, 4788 Errors: []error{ 4789 fmt.Errorf("aliases: attribute supports 1 item maximum, config has 2 declared"), 4790 }, 4791 }, 4792 "#1": { 4793 Schema: map[string]*Schema{ 4794 "aliases": &Schema{ 4795 Type: TypeSet, 4796 Optional: true, 4797 Elem: &Schema{Type: TypeString}, 4798 }, 4799 }, 4800 State: nil, 4801 Config: map[string]interface{}{ 4802 "aliases": []interface{}{"foo", "bar"}, 4803 }, 4804 Diff: nil, 4805 Err: false, 4806 Errors: nil, 4807 }, 4808 "#2": { 4809 Schema: map[string]*Schema{ 4810 "aliases": &Schema{ 4811 Type: TypeSet, 4812 Optional: true, 4813 MaxItems: 1, 4814 Elem: &Schema{Type: TypeString}, 4815 }, 4816 }, 4817 State: nil, 4818 Config: map[string]interface{}{ 4819 "aliases": []interface{}{"foo"}, 4820 }, 4821 Diff: nil, 4822 Err: false, 4823 Errors: nil, 4824 }, 4825 } 4826 4827 for tn, tc := range cases { 4828 c, err := config.NewRawConfig(tc.Config) 4829 if err != nil { 4830 t.Fatalf("%q: err: %s", tn, err) 4831 } 4832 _, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) 4833 4834 if len(es) > 0 != tc.Err { 4835 if len(es) == 0 { 4836 t.Errorf("%q: no errors", tn) 4837 } 4838 4839 for _, e := range es { 4840 t.Errorf("%q: err: %s", tn, e) 4841 } 4842 4843 t.FailNow() 4844 } 4845 4846 if tc.Errors != nil { 4847 if !reflect.DeepEqual(es, tc.Errors) { 4848 t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es) 4849 } 4850 } 4851 } 4852 } 4853 4854 func TestSchemaSet_ValidateMinItems(t *testing.T) { 4855 cases := map[string]struct { 4856 Schema map[string]*Schema 4857 State *terraform.InstanceState 4858 Config map[string]interface{} 4859 ConfigVariables map[string]string 4860 Diff *terraform.InstanceDiff 4861 Err bool 4862 Errors []error 4863 }{ 4864 "#0": { 4865 Schema: map[string]*Schema{ 4866 "aliases": &Schema{ 4867 Type: TypeSet, 4868 Optional: true, 4869 MinItems: 2, 4870 Elem: &Schema{Type: TypeString}, 4871 }, 4872 }, 4873 State: nil, 4874 Config: map[string]interface{}{ 4875 "aliases": []interface{}{"foo", "bar"}, 4876 }, 4877 Diff: nil, 4878 Err: false, 4879 Errors: nil, 4880 }, 4881 "#1": { 4882 Schema: map[string]*Schema{ 4883 "aliases": &Schema{ 4884 Type: TypeSet, 4885 Optional: true, 4886 Elem: &Schema{Type: TypeString}, 4887 }, 4888 }, 4889 State: nil, 4890 Config: map[string]interface{}{ 4891 "aliases": []interface{}{"foo", "bar"}, 4892 }, 4893 Diff: nil, 4894 Err: false, 4895 Errors: nil, 4896 }, 4897 "#2": { 4898 Schema: map[string]*Schema{ 4899 "aliases": &Schema{ 4900 Type: TypeSet, 4901 Optional: true, 4902 MinItems: 2, 4903 Elem: &Schema{Type: TypeString}, 4904 }, 4905 }, 4906 State: nil, 4907 Config: map[string]interface{}{ 4908 "aliases": []interface{}{"foo"}, 4909 }, 4910 Diff: nil, 4911 Err: true, 4912 Errors: []error{ 4913 fmt.Errorf("aliases: attribute supports 2 item as a minimum, config has 1 declared"), 4914 }, 4915 }, 4916 } 4917 4918 for tn, tc := range cases { 4919 c, err := config.NewRawConfig(tc.Config) 4920 if err != nil { 4921 t.Fatalf("%q: err: %s", tn, err) 4922 } 4923 _, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) 4924 4925 if len(es) > 0 != tc.Err { 4926 if len(es) == 0 { 4927 t.Errorf("%q: no errors", tn) 4928 } 4929 4930 for _, e := range es { 4931 t.Errorf("%q: err: %s", tn, e) 4932 } 4933 4934 t.FailNow() 4935 } 4936 4937 if tc.Errors != nil { 4938 if !reflect.DeepEqual(es, tc.Errors) { 4939 t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es) 4940 } 4941 } 4942 } 4943 } 4944 4945 // errorSort implements sort.Interface to sort errors by their error message 4946 type errorSort []error 4947 4948 func (e errorSort) Len() int { return len(e) } 4949 func (e errorSort) Swap(i, j int) { e[i], e[j] = e[j], e[i] } 4950 func (e errorSort) Less(i, j int) bool { 4951 return e[i].Error() < e[j].Error() 4952 }