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