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