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