github.com/jzbruno/terraform@v0.10.3-0.20180104230435-18975d727047/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 // A lot of resources currently depended on using the empty string as a 3115 // nil/unset value. 3116 // FIXME: We want this to eventually produce a diff, since there 3117 // technically is a new value in the config. 3118 { 3119 Name: "optional, computed, empty string", 3120 Schema: map[string]*Schema{ 3121 "attr": &Schema{ 3122 Type: TypeString, 3123 Optional: true, 3124 Computed: true, 3125 }, 3126 }, 3127 3128 State: &terraform.InstanceState{ 3129 Attributes: map[string]string{ 3130 "attr": "bar", 3131 }, 3132 }, 3133 3134 // this does necessarily depend on an interpolated value, but this 3135 // is often how it comes about in a configuration, otherwise the 3136 // value would be unset. 3137 Config: map[string]interface{}{ 3138 "attr": "${var.foo}", 3139 }, 3140 3141 ConfigVariables: map[string]ast.Variable{ 3142 "var.foo": interfaceToVariableSwallowError(""), 3143 }, 3144 }, 3145 } 3146 3147 for i, tc := range cases { 3148 t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { 3149 c, err := config.NewRawConfig(tc.Config) 3150 if err != nil { 3151 t.Fatalf("err: %s", err) 3152 } 3153 3154 if len(tc.ConfigVariables) > 0 { 3155 if err := c.Interpolate(tc.ConfigVariables); err != nil { 3156 t.Fatalf("err: %s", err) 3157 } 3158 } 3159 3160 d, err := schemaMap(tc.Schema).Diff(tc.State, terraform.NewResourceConfig(c), tc.CustomizeDiff, nil) 3161 if err != nil != tc.Err { 3162 t.Fatalf("err: %s", err) 3163 } 3164 3165 if !reflect.DeepEqual(tc.Diff, d) { 3166 t.Fatalf("expected:\n%#v\n\ngot:\n%#v", tc.Diff, d) 3167 } 3168 }) 3169 } 3170 } 3171 3172 func TestSchemaMap_Input(t *testing.T) { 3173 cases := map[string]struct { 3174 Schema map[string]*Schema 3175 Config map[string]interface{} 3176 Input map[string]string 3177 Result map[string]interface{} 3178 Err bool 3179 }{ 3180 /* 3181 * String decode 3182 */ 3183 3184 "no input on optional field with no config": { 3185 Schema: map[string]*Schema{ 3186 "availability_zone": &Schema{ 3187 Type: TypeString, 3188 Optional: true, 3189 }, 3190 }, 3191 3192 Input: map[string]string{}, 3193 Result: map[string]interface{}{}, 3194 Err: false, 3195 }, 3196 3197 "input ignored when config has a value": { 3198 Schema: map[string]*Schema{ 3199 "availability_zone": &Schema{ 3200 Type: TypeString, 3201 Optional: true, 3202 }, 3203 }, 3204 3205 Config: map[string]interface{}{ 3206 "availability_zone": "bar", 3207 }, 3208 3209 Input: map[string]string{ 3210 "availability_zone": "foo", 3211 }, 3212 3213 Result: map[string]interface{}{}, 3214 3215 Err: false, 3216 }, 3217 3218 "input ignored when schema has a default": { 3219 Schema: map[string]*Schema{ 3220 "availability_zone": &Schema{ 3221 Type: TypeString, 3222 Default: "foo", 3223 Optional: true, 3224 }, 3225 }, 3226 3227 Input: map[string]string{ 3228 "availability_zone": "bar", 3229 }, 3230 3231 Result: map[string]interface{}{}, 3232 3233 Err: false, 3234 }, 3235 3236 "input ignored when default function returns a value": { 3237 Schema: map[string]*Schema{ 3238 "availability_zone": &Schema{ 3239 Type: TypeString, 3240 DefaultFunc: func() (interface{}, error) { 3241 return "foo", nil 3242 }, 3243 Optional: true, 3244 }, 3245 }, 3246 3247 Input: map[string]string{ 3248 "availability_zone": "bar", 3249 }, 3250 3251 Result: map[string]interface{}{}, 3252 3253 Err: false, 3254 }, 3255 3256 "input ignored when default function returns an empty string": { 3257 Schema: map[string]*Schema{ 3258 "availability_zone": &Schema{ 3259 Type: TypeString, 3260 Default: "", 3261 Optional: true, 3262 }, 3263 }, 3264 3265 Input: map[string]string{ 3266 "availability_zone": "bar", 3267 }, 3268 3269 Result: map[string]interface{}{}, 3270 3271 Err: false, 3272 }, 3273 3274 "input used when default function returns nil": { 3275 Schema: map[string]*Schema{ 3276 "availability_zone": &Schema{ 3277 Type: TypeString, 3278 DefaultFunc: func() (interface{}, error) { 3279 return nil, nil 3280 }, 3281 Required: true, 3282 }, 3283 }, 3284 3285 Input: map[string]string{ 3286 "availability_zone": "bar", 3287 }, 3288 3289 Result: map[string]interface{}{ 3290 "availability_zone": "bar", 3291 }, 3292 3293 Err: false, 3294 }, 3295 3296 "input not used when optional default function returns nil": { 3297 Schema: map[string]*Schema{ 3298 "availability_zone": &Schema{ 3299 Type: TypeString, 3300 DefaultFunc: func() (interface{}, error) { 3301 return nil, nil 3302 }, 3303 Optional: true, 3304 }, 3305 }, 3306 3307 Input: map[string]string{}, 3308 Result: map[string]interface{}{}, 3309 Err: false, 3310 }, 3311 } 3312 3313 for i, tc := range cases { 3314 if tc.Config == nil { 3315 tc.Config = make(map[string]interface{}) 3316 } 3317 3318 c, err := config.NewRawConfig(tc.Config) 3319 if err != nil { 3320 t.Fatalf("err: %s", err) 3321 } 3322 3323 input := new(terraform.MockUIInput) 3324 input.InputReturnMap = tc.Input 3325 3326 rc := terraform.NewResourceConfig(c) 3327 rc.Config = make(map[string]interface{}) 3328 3329 actual, err := schemaMap(tc.Schema).Input(input, rc) 3330 if err != nil != tc.Err { 3331 t.Fatalf("#%v err: %s", i, err) 3332 } 3333 3334 if !reflect.DeepEqual(tc.Result, actual.Config) { 3335 t.Fatalf("#%v: bad:\n\ngot: %#v\nexpected: %#v", i, actual.Config, tc.Result) 3336 } 3337 } 3338 } 3339 3340 func TestSchemaMap_InputDefault(t *testing.T) { 3341 emptyConfig := make(map[string]interface{}) 3342 c, err := config.NewRawConfig(emptyConfig) 3343 if err != nil { 3344 t.Fatalf("err: %s", err) 3345 } 3346 rc := terraform.NewResourceConfig(c) 3347 rc.Config = make(map[string]interface{}) 3348 3349 input := new(terraform.MockUIInput) 3350 input.InputFn = func(opts *terraform.InputOpts) (string, error) { 3351 t.Fatalf("InputFn should not be called on: %#v", opts) 3352 return "", nil 3353 } 3354 3355 schema := map[string]*Schema{ 3356 "availability_zone": &Schema{ 3357 Type: TypeString, 3358 Default: "foo", 3359 Optional: true, 3360 }, 3361 } 3362 actual, err := schemaMap(schema).Input(input, rc) 3363 if err != nil { 3364 t.Fatalf("err: %s", err) 3365 } 3366 3367 expected := map[string]interface{}{} 3368 3369 if !reflect.DeepEqual(expected, actual.Config) { 3370 t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected) 3371 } 3372 } 3373 3374 func TestSchemaMap_InputDeprecated(t *testing.T) { 3375 emptyConfig := make(map[string]interface{}) 3376 c, err := config.NewRawConfig(emptyConfig) 3377 if err != nil { 3378 t.Fatalf("err: %s", err) 3379 } 3380 rc := terraform.NewResourceConfig(c) 3381 rc.Config = make(map[string]interface{}) 3382 3383 input := new(terraform.MockUIInput) 3384 input.InputFn = func(opts *terraform.InputOpts) (string, error) { 3385 t.Fatalf("InputFn should not be called on: %#v", opts) 3386 return "", nil 3387 } 3388 3389 schema := map[string]*Schema{ 3390 "availability_zone": &Schema{ 3391 Type: TypeString, 3392 Deprecated: "long gone", 3393 Optional: true, 3394 }, 3395 } 3396 actual, err := schemaMap(schema).Input(input, rc) 3397 if err != nil { 3398 t.Fatalf("err: %s", err) 3399 } 3400 3401 expected := map[string]interface{}{} 3402 3403 if !reflect.DeepEqual(expected, actual.Config) { 3404 t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected) 3405 } 3406 } 3407 3408 func TestSchemaMap_InternalValidate(t *testing.T) { 3409 cases := map[string]struct { 3410 In map[string]*Schema 3411 Err bool 3412 }{ 3413 "nothing": { 3414 nil, 3415 false, 3416 }, 3417 3418 "Both optional and required": { 3419 map[string]*Schema{ 3420 "foo": &Schema{ 3421 Type: TypeInt, 3422 Optional: true, 3423 Required: true, 3424 }, 3425 }, 3426 true, 3427 }, 3428 3429 "No optional and no required": { 3430 map[string]*Schema{ 3431 "foo": &Schema{ 3432 Type: TypeInt, 3433 }, 3434 }, 3435 true, 3436 }, 3437 3438 "Missing Type": { 3439 map[string]*Schema{ 3440 "foo": &Schema{ 3441 Required: true, 3442 }, 3443 }, 3444 true, 3445 }, 3446 3447 "Required but computed": { 3448 map[string]*Schema{ 3449 "foo": &Schema{ 3450 Type: TypeInt, 3451 Required: true, 3452 Computed: true, 3453 }, 3454 }, 3455 true, 3456 }, 3457 3458 "Looks good": { 3459 map[string]*Schema{ 3460 "foo": &Schema{ 3461 Type: TypeString, 3462 Required: true, 3463 }, 3464 }, 3465 false, 3466 }, 3467 3468 "Computed but has default": { 3469 map[string]*Schema{ 3470 "foo": &Schema{ 3471 Type: TypeInt, 3472 Optional: true, 3473 Computed: true, 3474 Default: "foo", 3475 }, 3476 }, 3477 true, 3478 }, 3479 3480 "Required but has default": { 3481 map[string]*Schema{ 3482 "foo": &Schema{ 3483 Type: TypeInt, 3484 Optional: true, 3485 Required: true, 3486 Default: "foo", 3487 }, 3488 }, 3489 true, 3490 }, 3491 3492 "List element not set": { 3493 map[string]*Schema{ 3494 "foo": &Schema{ 3495 Type: TypeList, 3496 }, 3497 }, 3498 true, 3499 }, 3500 3501 "List default": { 3502 map[string]*Schema{ 3503 "foo": &Schema{ 3504 Type: TypeList, 3505 Elem: &Schema{Type: TypeInt}, 3506 Default: "foo", 3507 }, 3508 }, 3509 true, 3510 }, 3511 3512 "List element computed": { 3513 map[string]*Schema{ 3514 "foo": &Schema{ 3515 Type: TypeList, 3516 Optional: true, 3517 Elem: &Schema{ 3518 Type: TypeInt, 3519 Computed: true, 3520 }, 3521 }, 3522 }, 3523 true, 3524 }, 3525 3526 "List element with Set set": { 3527 map[string]*Schema{ 3528 "foo": &Schema{ 3529 Type: TypeList, 3530 Elem: &Schema{Type: TypeInt}, 3531 Set: func(interface{}) int { return 0 }, 3532 Optional: true, 3533 }, 3534 }, 3535 true, 3536 }, 3537 3538 "Set element with no Set set": { 3539 map[string]*Schema{ 3540 "foo": &Schema{ 3541 Type: TypeSet, 3542 Elem: &Schema{Type: TypeInt}, 3543 Optional: true, 3544 }, 3545 }, 3546 false, 3547 }, 3548 3549 "Required but computedWhen": { 3550 map[string]*Schema{ 3551 "foo": &Schema{ 3552 Type: TypeInt, 3553 Required: true, 3554 ComputedWhen: []string{"foo"}, 3555 }, 3556 }, 3557 true, 3558 }, 3559 3560 "Conflicting attributes cannot be required": { 3561 map[string]*Schema{ 3562 "blacklist": &Schema{ 3563 Type: TypeBool, 3564 Required: true, 3565 }, 3566 "whitelist": &Schema{ 3567 Type: TypeBool, 3568 Optional: true, 3569 ConflictsWith: []string{"blacklist"}, 3570 }, 3571 }, 3572 true, 3573 }, 3574 3575 "Attribute with conflicts cannot be required": { 3576 map[string]*Schema{ 3577 "whitelist": &Schema{ 3578 Type: TypeBool, 3579 Required: true, 3580 ConflictsWith: []string{"blacklist"}, 3581 }, 3582 }, 3583 true, 3584 }, 3585 3586 "ConflictsWith cannot be used w/ ComputedWhen": { 3587 map[string]*Schema{ 3588 "blacklist": &Schema{ 3589 Type: TypeBool, 3590 ComputedWhen: []string{"foor"}, 3591 }, 3592 "whitelist": &Schema{ 3593 Type: TypeBool, 3594 Required: true, 3595 ConflictsWith: []string{"blacklist"}, 3596 }, 3597 }, 3598 true, 3599 }, 3600 3601 "Sub-resource invalid": { 3602 map[string]*Schema{ 3603 "foo": &Schema{ 3604 Type: TypeList, 3605 Optional: true, 3606 Elem: &Resource{ 3607 Schema: map[string]*Schema{ 3608 "foo": new(Schema), 3609 }, 3610 }, 3611 }, 3612 }, 3613 true, 3614 }, 3615 3616 "Sub-resource valid": { 3617 map[string]*Schema{ 3618 "foo": &Schema{ 3619 Type: TypeList, 3620 Optional: true, 3621 Elem: &Resource{ 3622 Schema: map[string]*Schema{ 3623 "foo": &Schema{ 3624 Type: TypeInt, 3625 Optional: true, 3626 }, 3627 }, 3628 }, 3629 }, 3630 }, 3631 false, 3632 }, 3633 3634 "ValidateFunc on non-primitive": { 3635 map[string]*Schema{ 3636 "foo": &Schema{ 3637 Type: TypeSet, 3638 Required: true, 3639 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 3640 return 3641 }, 3642 }, 3643 }, 3644 true, 3645 }, 3646 3647 "computed-only field with validateFunc": { 3648 map[string]*Schema{ 3649 "string": &Schema{ 3650 Type: TypeString, 3651 Computed: true, 3652 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 3653 es = append(es, fmt.Errorf("this is not fine")) 3654 return 3655 }, 3656 }, 3657 }, 3658 true, 3659 }, 3660 3661 "computed-only field with diffSuppressFunc": { 3662 map[string]*Schema{ 3663 "string": &Schema{ 3664 Type: TypeString, 3665 Computed: true, 3666 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3667 // Always suppress any diff 3668 return false 3669 }, 3670 }, 3671 }, 3672 true, 3673 }, 3674 3675 "invalid field name format #1": { 3676 map[string]*Schema{ 3677 "with space": &Schema{ 3678 Type: TypeString, 3679 Optional: true, 3680 }, 3681 }, 3682 true, 3683 }, 3684 3685 "invalid field name format #2": { 3686 map[string]*Schema{ 3687 "WithCapitals": &Schema{ 3688 Type: TypeString, 3689 Optional: true, 3690 }, 3691 }, 3692 true, 3693 }, 3694 3695 "invalid field name format of a Deprecated field": { 3696 map[string]*Schema{ 3697 "WithCapitals": &Schema{ 3698 Type: TypeString, 3699 Optional: true, 3700 Deprecated: "Use with_underscores instead", 3701 }, 3702 }, 3703 false, 3704 }, 3705 3706 "invalid field name format of a Removed field": { 3707 map[string]*Schema{ 3708 "WithCapitals": &Schema{ 3709 Type: TypeString, 3710 Optional: true, 3711 Removed: "Use with_underscores instead", 3712 }, 3713 }, 3714 false, 3715 }, 3716 } 3717 3718 for tn, tc := range cases { 3719 t.Run(tn, func(t *testing.T) { 3720 err := schemaMap(tc.In).InternalValidate(nil) 3721 if err != nil != tc.Err { 3722 if tc.Err { 3723 t.Fatalf("%q: Expected error did not occur:\n\n%#v", tn, tc.In) 3724 } 3725 t.Fatalf("%q: Unexpected error occurred: %s\n\n%#v", tn, err, tc.In) 3726 } 3727 }) 3728 } 3729 3730 } 3731 3732 func TestSchemaMap_DiffSuppress(t *testing.T) { 3733 cases := map[string]struct { 3734 Schema map[string]*Schema 3735 State *terraform.InstanceState 3736 Config map[string]interface{} 3737 ConfigVariables map[string]ast.Variable 3738 ExpectedDiff *terraform.InstanceDiff 3739 Err bool 3740 }{ 3741 "#0 - Suppress otherwise valid diff by returning true": { 3742 Schema: map[string]*Schema{ 3743 "availability_zone": { 3744 Type: TypeString, 3745 Optional: true, 3746 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3747 // Always suppress any diff 3748 return true 3749 }, 3750 }, 3751 }, 3752 3753 State: nil, 3754 3755 Config: map[string]interface{}{ 3756 "availability_zone": "foo", 3757 }, 3758 3759 ExpectedDiff: nil, 3760 3761 Err: false, 3762 }, 3763 3764 "#1 - Don't suppress diff by returning false": { 3765 Schema: map[string]*Schema{ 3766 "availability_zone": { 3767 Type: TypeString, 3768 Optional: true, 3769 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3770 // Always suppress any diff 3771 return false 3772 }, 3773 }, 3774 }, 3775 3776 State: nil, 3777 3778 Config: map[string]interface{}{ 3779 "availability_zone": "foo", 3780 }, 3781 3782 ExpectedDiff: &terraform.InstanceDiff{ 3783 Attributes: map[string]*terraform.ResourceAttrDiff{ 3784 "availability_zone": { 3785 Old: "", 3786 New: "foo", 3787 }, 3788 }, 3789 }, 3790 3791 Err: false, 3792 }, 3793 3794 "Default with suppress makes no diff": { 3795 Schema: map[string]*Schema{ 3796 "availability_zone": { 3797 Type: TypeString, 3798 Optional: true, 3799 Default: "foo", 3800 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3801 return true 3802 }, 3803 }, 3804 }, 3805 3806 State: nil, 3807 3808 Config: map[string]interface{}{}, 3809 3810 ExpectedDiff: nil, 3811 3812 Err: false, 3813 }, 3814 3815 "Default with false suppress makes diff": { 3816 Schema: map[string]*Schema{ 3817 "availability_zone": { 3818 Type: TypeString, 3819 Optional: true, 3820 Default: "foo", 3821 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3822 return false 3823 }, 3824 }, 3825 }, 3826 3827 State: nil, 3828 3829 Config: map[string]interface{}{}, 3830 3831 ExpectedDiff: &terraform.InstanceDiff{ 3832 Attributes: map[string]*terraform.ResourceAttrDiff{ 3833 "availability_zone": { 3834 Old: "", 3835 New: "foo", 3836 }, 3837 }, 3838 }, 3839 3840 Err: false, 3841 }, 3842 3843 "Complex structure with set of computed string should mark root set as computed": { 3844 Schema: map[string]*Schema{ 3845 "outer": &Schema{ 3846 Type: TypeSet, 3847 Optional: true, 3848 Elem: &Resource{ 3849 Schema: map[string]*Schema{ 3850 "outer_str": &Schema{ 3851 Type: TypeString, 3852 Optional: true, 3853 }, 3854 "inner": &Schema{ 3855 Type: TypeSet, 3856 Optional: true, 3857 Elem: &Resource{ 3858 Schema: map[string]*Schema{ 3859 "inner_str": &Schema{ 3860 Type: TypeString, 3861 Optional: true, 3862 }, 3863 }, 3864 }, 3865 Set: func(v interface{}) int { 3866 return 2 3867 }, 3868 }, 3869 }, 3870 }, 3871 Set: func(v interface{}) int { 3872 return 1 3873 }, 3874 }, 3875 }, 3876 3877 State: nil, 3878 3879 Config: map[string]interface{}{ 3880 "outer": []map[string]interface{}{ 3881 map[string]interface{}{ 3882 "outer_str": "foo", 3883 "inner": []map[string]interface{}{ 3884 map[string]interface{}{ 3885 "inner_str": "${var.bar}", 3886 }, 3887 }, 3888 }, 3889 }, 3890 }, 3891 3892 ConfigVariables: map[string]ast.Variable{ 3893 "var.bar": interfaceToVariableSwallowError(config.UnknownVariableValue), 3894 }, 3895 3896 ExpectedDiff: &terraform.InstanceDiff{ 3897 Attributes: map[string]*terraform.ResourceAttrDiff{ 3898 "outer.#": &terraform.ResourceAttrDiff{ 3899 Old: "0", 3900 New: "1", 3901 }, 3902 "outer.~1.outer_str": &terraform.ResourceAttrDiff{ 3903 Old: "", 3904 New: "foo", 3905 }, 3906 "outer.~1.inner.#": &terraform.ResourceAttrDiff{ 3907 Old: "0", 3908 New: "1", 3909 }, 3910 "outer.~1.inner.~2.inner_str": &terraform.ResourceAttrDiff{ 3911 Old: "", 3912 New: "${var.bar}", 3913 NewComputed: true, 3914 }, 3915 }, 3916 }, 3917 3918 Err: false, 3919 }, 3920 3921 "Complex structure with complex list of computed string should mark root set as computed": { 3922 Schema: map[string]*Schema{ 3923 "outer": &Schema{ 3924 Type: TypeSet, 3925 Optional: true, 3926 Elem: &Resource{ 3927 Schema: map[string]*Schema{ 3928 "outer_str": &Schema{ 3929 Type: TypeString, 3930 Optional: true, 3931 }, 3932 "inner": &Schema{ 3933 Type: TypeList, 3934 Optional: true, 3935 Elem: &Resource{ 3936 Schema: map[string]*Schema{ 3937 "inner_str": &Schema{ 3938 Type: TypeString, 3939 Optional: true, 3940 }, 3941 }, 3942 }, 3943 }, 3944 }, 3945 }, 3946 Set: func(v interface{}) int { 3947 return 1 3948 }, 3949 }, 3950 }, 3951 3952 State: nil, 3953 3954 Config: map[string]interface{}{ 3955 "outer": []map[string]interface{}{ 3956 map[string]interface{}{ 3957 "outer_str": "foo", 3958 "inner": []map[string]interface{}{ 3959 map[string]interface{}{ 3960 "inner_str": "${var.bar}", 3961 }, 3962 }, 3963 }, 3964 }, 3965 }, 3966 3967 ConfigVariables: map[string]ast.Variable{ 3968 "var.bar": interfaceToVariableSwallowError(config.UnknownVariableValue), 3969 }, 3970 3971 ExpectedDiff: &terraform.InstanceDiff{ 3972 Attributes: map[string]*terraform.ResourceAttrDiff{ 3973 "outer.#": &terraform.ResourceAttrDiff{ 3974 Old: "0", 3975 New: "1", 3976 }, 3977 "outer.~1.outer_str": &terraform.ResourceAttrDiff{ 3978 Old: "", 3979 New: "foo", 3980 }, 3981 "outer.~1.inner.#": &terraform.ResourceAttrDiff{ 3982 Old: "0", 3983 New: "1", 3984 }, 3985 "outer.~1.inner.0.inner_str": &terraform.ResourceAttrDiff{ 3986 Old: "", 3987 New: "${var.bar}", 3988 NewComputed: true, 3989 }, 3990 }, 3991 }, 3992 3993 Err: false, 3994 }, 3995 } 3996 3997 for tn, tc := range cases { 3998 t.Run(tn, func(t *testing.T) { 3999 c, err := config.NewRawConfig(tc.Config) 4000 if err != nil { 4001 t.Fatalf("#%q err: %s", tn, err) 4002 } 4003 4004 if len(tc.ConfigVariables) > 0 { 4005 if err := c.Interpolate(tc.ConfigVariables); err != nil { 4006 t.Fatalf("#%q err: %s", tn, err) 4007 } 4008 } 4009 4010 d, err := schemaMap(tc.Schema).Diff(tc.State, terraform.NewResourceConfig(c), nil, nil) 4011 if err != nil != tc.Err { 4012 t.Fatalf("#%q err: %s", tn, err) 4013 } 4014 4015 if !reflect.DeepEqual(tc.ExpectedDiff, d) { 4016 t.Fatalf("#%q:\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.ExpectedDiff, d) 4017 } 4018 }) 4019 } 4020 } 4021 4022 func TestSchemaMap_Validate(t *testing.T) { 4023 cases := map[string]struct { 4024 Schema map[string]*Schema 4025 Config map[string]interface{} 4026 Vars map[string]string 4027 Err bool 4028 Errors []error 4029 Warnings []string 4030 }{ 4031 "Good": { 4032 Schema: map[string]*Schema{ 4033 "availability_zone": &Schema{ 4034 Type: TypeString, 4035 Optional: true, 4036 Computed: true, 4037 ForceNew: true, 4038 }, 4039 }, 4040 4041 Config: map[string]interface{}{ 4042 "availability_zone": "foo", 4043 }, 4044 }, 4045 4046 "Good, because the var is not set and that error will come elsewhere": { 4047 Schema: map[string]*Schema{ 4048 "size": &Schema{ 4049 Type: TypeInt, 4050 Required: true, 4051 }, 4052 }, 4053 4054 Config: map[string]interface{}{ 4055 "size": "${var.foo}", 4056 }, 4057 4058 Vars: map[string]string{ 4059 "var.foo": config.UnknownVariableValue, 4060 }, 4061 }, 4062 4063 "Required field not set": { 4064 Schema: map[string]*Schema{ 4065 "availability_zone": &Schema{ 4066 Type: TypeString, 4067 Required: true, 4068 }, 4069 }, 4070 4071 Config: map[string]interface{}{}, 4072 4073 Err: true, 4074 }, 4075 4076 "Invalid basic type": { 4077 Schema: map[string]*Schema{ 4078 "port": &Schema{ 4079 Type: TypeInt, 4080 Required: true, 4081 }, 4082 }, 4083 4084 Config: map[string]interface{}{ 4085 "port": "I am invalid", 4086 }, 4087 4088 Err: true, 4089 }, 4090 4091 "Invalid complex type": { 4092 Schema: map[string]*Schema{ 4093 "user_data": &Schema{ 4094 Type: TypeString, 4095 Optional: true, 4096 }, 4097 }, 4098 4099 Config: map[string]interface{}{ 4100 "user_data": []interface{}{ 4101 map[string]interface{}{ 4102 "foo": "bar", 4103 }, 4104 }, 4105 }, 4106 4107 Err: true, 4108 }, 4109 4110 "Bad type, interpolated": { 4111 Schema: map[string]*Schema{ 4112 "size": &Schema{ 4113 Type: TypeInt, 4114 Required: true, 4115 }, 4116 }, 4117 4118 Config: map[string]interface{}{ 4119 "size": "${var.foo}", 4120 }, 4121 4122 Vars: map[string]string{ 4123 "var.foo": "nope", 4124 }, 4125 4126 Err: true, 4127 }, 4128 4129 "Required but has DefaultFunc": { 4130 Schema: map[string]*Schema{ 4131 "availability_zone": &Schema{ 4132 Type: TypeString, 4133 Required: true, 4134 DefaultFunc: func() (interface{}, error) { 4135 return "foo", nil 4136 }, 4137 }, 4138 }, 4139 4140 Config: nil, 4141 }, 4142 4143 "Required but has DefaultFunc return nil": { 4144 Schema: map[string]*Schema{ 4145 "availability_zone": &Schema{ 4146 Type: TypeString, 4147 Required: true, 4148 DefaultFunc: func() (interface{}, error) { 4149 return nil, nil 4150 }, 4151 }, 4152 }, 4153 4154 Config: nil, 4155 4156 Err: true, 4157 }, 4158 4159 "List with promotion": { 4160 Schema: map[string]*Schema{ 4161 "ingress": &Schema{ 4162 Type: TypeList, 4163 Elem: &Schema{Type: TypeInt}, 4164 PromoteSingle: true, 4165 Optional: true, 4166 }, 4167 }, 4168 4169 Config: map[string]interface{}{ 4170 "ingress": "5", 4171 }, 4172 4173 Err: false, 4174 }, 4175 4176 "List with promotion set as list": { 4177 Schema: map[string]*Schema{ 4178 "ingress": &Schema{ 4179 Type: TypeList, 4180 Elem: &Schema{Type: TypeInt}, 4181 PromoteSingle: true, 4182 Optional: true, 4183 }, 4184 }, 4185 4186 Config: map[string]interface{}{ 4187 "ingress": []interface{}{"5"}, 4188 }, 4189 4190 Err: false, 4191 }, 4192 4193 "Optional sub-resource": { 4194 Schema: map[string]*Schema{ 4195 "ingress": &Schema{ 4196 Type: TypeList, 4197 Elem: &Resource{ 4198 Schema: map[string]*Schema{ 4199 "from": &Schema{ 4200 Type: TypeInt, 4201 Required: true, 4202 }, 4203 }, 4204 }, 4205 }, 4206 }, 4207 4208 Config: map[string]interface{}{}, 4209 4210 Err: false, 4211 }, 4212 4213 "Sub-resource is the wrong type": { 4214 Schema: map[string]*Schema{ 4215 "ingress": &Schema{ 4216 Type: TypeList, 4217 Required: true, 4218 Elem: &Resource{ 4219 Schema: map[string]*Schema{ 4220 "from": &Schema{ 4221 Type: TypeInt, 4222 Required: true, 4223 }, 4224 }, 4225 }, 4226 }, 4227 }, 4228 4229 Config: map[string]interface{}{ 4230 "ingress": []interface{}{"foo"}, 4231 }, 4232 4233 Err: true, 4234 }, 4235 4236 "Not a list": { 4237 Schema: map[string]*Schema{ 4238 "ingress": &Schema{ 4239 Type: TypeList, 4240 Elem: &Resource{ 4241 Schema: map[string]*Schema{ 4242 "from": &Schema{ 4243 Type: TypeInt, 4244 Required: true, 4245 }, 4246 }, 4247 }, 4248 }, 4249 }, 4250 4251 Config: map[string]interface{}{ 4252 "ingress": "foo", 4253 }, 4254 4255 Err: true, 4256 }, 4257 4258 "Required sub-resource field": { 4259 Schema: map[string]*Schema{ 4260 "ingress": &Schema{ 4261 Type: TypeList, 4262 Elem: &Resource{ 4263 Schema: map[string]*Schema{ 4264 "from": &Schema{ 4265 Type: TypeInt, 4266 Required: true, 4267 }, 4268 }, 4269 }, 4270 }, 4271 }, 4272 4273 Config: map[string]interface{}{ 4274 "ingress": []interface{}{ 4275 map[string]interface{}{}, 4276 }, 4277 }, 4278 4279 Err: true, 4280 }, 4281 4282 "Good sub-resource": { 4283 Schema: map[string]*Schema{ 4284 "ingress": &Schema{ 4285 Type: TypeList, 4286 Optional: true, 4287 Elem: &Resource{ 4288 Schema: map[string]*Schema{ 4289 "from": &Schema{ 4290 Type: TypeInt, 4291 Required: true, 4292 }, 4293 }, 4294 }, 4295 }, 4296 }, 4297 4298 Config: map[string]interface{}{ 4299 "ingress": []interface{}{ 4300 map[string]interface{}{ 4301 "from": 80, 4302 }, 4303 }, 4304 }, 4305 4306 Err: false, 4307 }, 4308 4309 "Good sub-resource, interpolated value": { 4310 Schema: map[string]*Schema{ 4311 "ingress": &Schema{ 4312 Type: TypeList, 4313 Optional: true, 4314 Elem: &Resource{ 4315 Schema: map[string]*Schema{ 4316 "from": &Schema{ 4317 Type: TypeInt, 4318 Required: true, 4319 }, 4320 }, 4321 }, 4322 }, 4323 }, 4324 4325 Config: map[string]interface{}{ 4326 "ingress": []interface{}{ 4327 `${map("from", "80")}`, 4328 }, 4329 }, 4330 4331 Vars: map[string]string{}, 4332 4333 Err: false, 4334 }, 4335 4336 "Good sub-resource, computed value": { 4337 Schema: map[string]*Schema{ 4338 "ingress": &Schema{ 4339 Type: TypeList, 4340 Optional: true, 4341 Elem: &Resource{ 4342 Schema: map[string]*Schema{ 4343 "from": &Schema{ 4344 Type: TypeInt, 4345 Optional: true, 4346 }, 4347 }, 4348 }, 4349 }, 4350 }, 4351 4352 Config: map[string]interface{}{ 4353 "ingress": []interface{}{ 4354 `${map("from", var.port)}`, 4355 }, 4356 }, 4357 4358 Vars: map[string]string{ 4359 "var.port": config.UnknownVariableValue, 4360 }, 4361 4362 Err: false, 4363 }, 4364 4365 "Invalid/unknown field": { 4366 Schema: map[string]*Schema{ 4367 "availability_zone": &Schema{ 4368 Type: TypeString, 4369 Optional: true, 4370 Computed: true, 4371 ForceNew: true, 4372 }, 4373 }, 4374 4375 Config: map[string]interface{}{ 4376 "foo": "bar", 4377 }, 4378 4379 Err: true, 4380 }, 4381 4382 "Invalid/unknown field with computed value": { 4383 Schema: map[string]*Schema{ 4384 "availability_zone": &Schema{ 4385 Type: TypeString, 4386 Optional: true, 4387 Computed: true, 4388 ForceNew: true, 4389 }, 4390 }, 4391 4392 Config: map[string]interface{}{ 4393 "foo": "${var.foo}", 4394 }, 4395 4396 Vars: map[string]string{ 4397 "var.foo": config.UnknownVariableValue, 4398 }, 4399 4400 Err: true, 4401 }, 4402 4403 "Computed field set": { 4404 Schema: map[string]*Schema{ 4405 "availability_zone": &Schema{ 4406 Type: TypeString, 4407 Computed: true, 4408 }, 4409 }, 4410 4411 Config: map[string]interface{}{ 4412 "availability_zone": "bar", 4413 }, 4414 4415 Err: true, 4416 }, 4417 4418 "Not a set": { 4419 Schema: map[string]*Schema{ 4420 "ports": &Schema{ 4421 Type: TypeSet, 4422 Required: true, 4423 Elem: &Schema{Type: TypeInt}, 4424 Set: func(a interface{}) int { 4425 return a.(int) 4426 }, 4427 }, 4428 }, 4429 4430 Config: map[string]interface{}{ 4431 "ports": "foo", 4432 }, 4433 4434 Err: true, 4435 }, 4436 4437 "Maps": { 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": "foo", 4447 }, 4448 4449 Err: true, 4450 }, 4451 4452 "Good map: data surrounded by extra 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 map[string]interface{}{ 4463 "foo": "bar", 4464 }, 4465 }, 4466 }, 4467 }, 4468 4469 "Good map": { 4470 Schema: map[string]*Schema{ 4471 "user_data": &Schema{ 4472 Type: TypeMap, 4473 Optional: true, 4474 }, 4475 }, 4476 4477 Config: map[string]interface{}{ 4478 "user_data": map[string]interface{}{ 4479 "foo": "bar", 4480 }, 4481 }, 4482 }, 4483 4484 "Bad map: just a slice": { 4485 Schema: map[string]*Schema{ 4486 "user_data": &Schema{ 4487 Type: TypeMap, 4488 Optional: true, 4489 }, 4490 }, 4491 4492 Config: map[string]interface{}{ 4493 "user_data": []interface{}{ 4494 "foo", 4495 }, 4496 }, 4497 4498 Err: true, 4499 }, 4500 4501 "Good set: config has slice with single interpolated value": { 4502 Schema: map[string]*Schema{ 4503 "security_groups": &Schema{ 4504 Type: TypeSet, 4505 Optional: true, 4506 Computed: true, 4507 ForceNew: true, 4508 Elem: &Schema{Type: TypeString}, 4509 Set: func(v interface{}) int { 4510 return len(v.(string)) 4511 }, 4512 }, 4513 }, 4514 4515 Config: map[string]interface{}{ 4516 "security_groups": []interface{}{"${var.foo}"}, 4517 }, 4518 4519 Err: false, 4520 }, 4521 4522 "Bad set: config has single interpolated value": { 4523 Schema: map[string]*Schema{ 4524 "security_groups": &Schema{ 4525 Type: TypeSet, 4526 Optional: true, 4527 Computed: true, 4528 ForceNew: true, 4529 Elem: &Schema{Type: TypeString}, 4530 }, 4531 }, 4532 4533 Config: map[string]interface{}{ 4534 "security_groups": "${var.foo}", 4535 }, 4536 4537 Err: true, 4538 }, 4539 4540 "Bad, subresource should not allow unknown elements": { 4541 Schema: map[string]*Schema{ 4542 "ingress": &Schema{ 4543 Type: TypeList, 4544 Optional: true, 4545 Elem: &Resource{ 4546 Schema: map[string]*Schema{ 4547 "port": &Schema{ 4548 Type: TypeInt, 4549 Required: true, 4550 }, 4551 }, 4552 }, 4553 }, 4554 }, 4555 4556 Config: map[string]interface{}{ 4557 "ingress": []interface{}{ 4558 map[string]interface{}{ 4559 "port": 80, 4560 "other": "yes", 4561 }, 4562 }, 4563 }, 4564 4565 Err: true, 4566 }, 4567 4568 "Bad, subresource should not allow invalid types": { 4569 Schema: map[string]*Schema{ 4570 "ingress": &Schema{ 4571 Type: TypeList, 4572 Optional: true, 4573 Elem: &Resource{ 4574 Schema: map[string]*Schema{ 4575 "port": &Schema{ 4576 Type: TypeInt, 4577 Required: true, 4578 }, 4579 }, 4580 }, 4581 }, 4582 }, 4583 4584 Config: map[string]interface{}{ 4585 "ingress": []interface{}{ 4586 map[string]interface{}{ 4587 "port": "bad", 4588 }, 4589 }, 4590 }, 4591 4592 Err: true, 4593 }, 4594 4595 "Bad, should not allow lists to be assigned to string attributes": { 4596 Schema: map[string]*Schema{ 4597 "availability_zone": &Schema{ 4598 Type: TypeString, 4599 Required: true, 4600 }, 4601 }, 4602 4603 Config: map[string]interface{}{ 4604 "availability_zone": []interface{}{"foo", "bar", "baz"}, 4605 }, 4606 4607 Err: true, 4608 }, 4609 4610 "Bad, should not allow maps to be assigned to string attributes": { 4611 Schema: map[string]*Schema{ 4612 "availability_zone": &Schema{ 4613 Type: TypeString, 4614 Required: true, 4615 }, 4616 }, 4617 4618 Config: map[string]interface{}{ 4619 "availability_zone": map[string]interface{}{"foo": "bar", "baz": "thing"}, 4620 }, 4621 4622 Err: true, 4623 }, 4624 4625 "Deprecated attribute usage generates warning, but not error": { 4626 Schema: map[string]*Schema{ 4627 "old_news": &Schema{ 4628 Type: TypeString, 4629 Optional: true, 4630 Deprecated: "please use 'new_news' instead", 4631 }, 4632 }, 4633 4634 Config: map[string]interface{}{ 4635 "old_news": "extra extra!", 4636 }, 4637 4638 Err: false, 4639 4640 Warnings: []string{ 4641 "\"old_news\": [DEPRECATED] please use 'new_news' instead", 4642 }, 4643 }, 4644 4645 "Deprecated generates no warnings if attr not used": { 4646 Schema: map[string]*Schema{ 4647 "old_news": &Schema{ 4648 Type: TypeString, 4649 Optional: true, 4650 Deprecated: "please use 'new_news' instead", 4651 }, 4652 }, 4653 4654 Err: false, 4655 4656 Warnings: nil, 4657 }, 4658 4659 "Removed attribute usage generates error": { 4660 Schema: map[string]*Schema{ 4661 "long_gone": &Schema{ 4662 Type: TypeString, 4663 Optional: true, 4664 Removed: "no longer supported by Cloud API", 4665 }, 4666 }, 4667 4668 Config: map[string]interface{}{ 4669 "long_gone": "still here!", 4670 }, 4671 4672 Err: true, 4673 Errors: []error{ 4674 fmt.Errorf("\"long_gone\": [REMOVED] no longer supported by Cloud API"), 4675 }, 4676 }, 4677 4678 "Removed generates no errors if attr not used": { 4679 Schema: map[string]*Schema{ 4680 "long_gone": &Schema{ 4681 Type: TypeString, 4682 Optional: true, 4683 Removed: "no longer supported by Cloud API", 4684 }, 4685 }, 4686 4687 Err: false, 4688 }, 4689 4690 "Conflicting attributes generate error": { 4691 Schema: map[string]*Schema{ 4692 "whitelist": &Schema{ 4693 Type: TypeString, 4694 Optional: true, 4695 }, 4696 "blacklist": &Schema{ 4697 Type: TypeString, 4698 Optional: true, 4699 ConflictsWith: []string{"whitelist"}, 4700 }, 4701 }, 4702 4703 Config: map[string]interface{}{ 4704 "whitelist": "white-val", 4705 "blacklist": "black-val", 4706 }, 4707 4708 Err: true, 4709 Errors: []error{ 4710 fmt.Errorf("\"blacklist\": conflicts with whitelist (\"white-val\")"), 4711 }, 4712 }, 4713 4714 "Required attribute & undefined conflicting optional are good": { 4715 Schema: map[string]*Schema{ 4716 "required_att": &Schema{ 4717 Type: TypeString, 4718 Required: true, 4719 }, 4720 "optional_att": &Schema{ 4721 Type: TypeString, 4722 Optional: true, 4723 ConflictsWith: []string{"required_att"}, 4724 }, 4725 }, 4726 4727 Config: map[string]interface{}{ 4728 "required_att": "required-val", 4729 }, 4730 4731 Err: false, 4732 }, 4733 4734 "Required conflicting attribute & defined optional generate error": { 4735 Schema: map[string]*Schema{ 4736 "required_att": &Schema{ 4737 Type: TypeString, 4738 Required: true, 4739 }, 4740 "optional_att": &Schema{ 4741 Type: TypeString, 4742 Optional: true, 4743 ConflictsWith: []string{"required_att"}, 4744 }, 4745 }, 4746 4747 Config: map[string]interface{}{ 4748 "required_att": "required-val", 4749 "optional_att": "optional-val", 4750 }, 4751 4752 Err: true, 4753 Errors: []error{ 4754 fmt.Errorf(`"optional_att": conflicts with required_att ("required-val")`), 4755 }, 4756 }, 4757 4758 "Computed + Optional fields conflicting with each other": { 4759 Schema: map[string]*Schema{ 4760 "foo_att": &Schema{ 4761 Type: TypeString, 4762 Optional: true, 4763 Computed: true, 4764 ConflictsWith: []string{"bar_att"}, 4765 }, 4766 "bar_att": &Schema{ 4767 Type: TypeString, 4768 Optional: true, 4769 Computed: true, 4770 ConflictsWith: []string{"foo_att"}, 4771 }, 4772 }, 4773 4774 Config: map[string]interface{}{ 4775 "foo_att": "foo-val", 4776 "bar_att": "bar-val", 4777 }, 4778 4779 Err: true, 4780 Errors: []error{ 4781 fmt.Errorf(`"foo_att": conflicts with bar_att ("bar-val")`), 4782 fmt.Errorf(`"bar_att": conflicts with foo_att ("foo-val")`), 4783 }, 4784 }, 4785 4786 "Computed + Optional fields NOT conflicting with each other": { 4787 Schema: map[string]*Schema{ 4788 "foo_att": &Schema{ 4789 Type: TypeString, 4790 Optional: true, 4791 Computed: true, 4792 ConflictsWith: []string{"bar_att"}, 4793 }, 4794 "bar_att": &Schema{ 4795 Type: TypeString, 4796 Optional: true, 4797 Computed: true, 4798 ConflictsWith: []string{"foo_att"}, 4799 }, 4800 }, 4801 4802 Config: map[string]interface{}{ 4803 "foo_att": "foo-val", 4804 }, 4805 4806 Err: false, 4807 }, 4808 4809 "Computed + Optional fields that conflict with none set": { 4810 Schema: map[string]*Schema{ 4811 "foo_att": &Schema{ 4812 Type: TypeString, 4813 Optional: true, 4814 Computed: true, 4815 ConflictsWith: []string{"bar_att"}, 4816 }, 4817 "bar_att": &Schema{ 4818 Type: TypeString, 4819 Optional: true, 4820 Computed: true, 4821 ConflictsWith: []string{"foo_att"}, 4822 }, 4823 }, 4824 4825 Config: map[string]interface{}{}, 4826 4827 Err: false, 4828 }, 4829 4830 "Good with ValidateFunc": { 4831 Schema: map[string]*Schema{ 4832 "validate_me": &Schema{ 4833 Type: TypeString, 4834 Required: true, 4835 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4836 return 4837 }, 4838 }, 4839 }, 4840 Config: map[string]interface{}{ 4841 "validate_me": "valid", 4842 }, 4843 Err: false, 4844 }, 4845 4846 "Bad with ValidateFunc": { 4847 Schema: map[string]*Schema{ 4848 "validate_me": &Schema{ 4849 Type: TypeString, 4850 Required: true, 4851 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4852 es = append(es, fmt.Errorf("something is not right here")) 4853 return 4854 }, 4855 }, 4856 }, 4857 Config: map[string]interface{}{ 4858 "validate_me": "invalid", 4859 }, 4860 Err: true, 4861 Errors: []error{ 4862 fmt.Errorf(`something is not right here`), 4863 }, 4864 }, 4865 4866 "ValidateFunc not called when type does not match": { 4867 Schema: map[string]*Schema{ 4868 "number": &Schema{ 4869 Type: TypeInt, 4870 Required: true, 4871 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4872 t.Fatalf("Should not have gotten validate call") 4873 return 4874 }, 4875 }, 4876 }, 4877 Config: map[string]interface{}{ 4878 "number": "NaN", 4879 }, 4880 Err: true, 4881 }, 4882 4883 "ValidateFunc gets decoded type": { 4884 Schema: map[string]*Schema{ 4885 "maybe": &Schema{ 4886 Type: TypeBool, 4887 Required: true, 4888 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4889 if _, ok := v.(bool); !ok { 4890 t.Fatalf("Expected bool, got: %#v", v) 4891 } 4892 return 4893 }, 4894 }, 4895 }, 4896 Config: map[string]interface{}{ 4897 "maybe": "true", 4898 }, 4899 }, 4900 4901 "ValidateFunc is not called with a computed value": { 4902 Schema: map[string]*Schema{ 4903 "validate_me": &Schema{ 4904 Type: TypeString, 4905 Required: true, 4906 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 4907 es = append(es, fmt.Errorf("something is not right here")) 4908 return 4909 }, 4910 }, 4911 }, 4912 Config: map[string]interface{}{ 4913 "validate_me": "${var.foo}", 4914 }, 4915 Vars: map[string]string{ 4916 "var.foo": config.UnknownVariableValue, 4917 }, 4918 4919 Err: false, 4920 }, 4921 4922 "special timeouts field": { 4923 Schema: map[string]*Schema{ 4924 "availability_zone": &Schema{ 4925 Type: TypeString, 4926 Optional: true, 4927 Computed: true, 4928 ForceNew: true, 4929 }, 4930 }, 4931 4932 Config: map[string]interface{}{ 4933 TimeoutsConfigKey: "bar", 4934 }, 4935 4936 Err: false, 4937 }, 4938 4939 "invalid bool field": { 4940 Schema: map[string]*Schema{ 4941 "bool_field": { 4942 Type: TypeBool, 4943 Optional: true, 4944 }, 4945 }, 4946 Config: map[string]interface{}{ 4947 "bool_field": "abcdef", 4948 }, 4949 Err: true, 4950 }, 4951 "invalid integer field": { 4952 Schema: map[string]*Schema{ 4953 "integer_field": { 4954 Type: TypeInt, 4955 Optional: true, 4956 }, 4957 }, 4958 Config: map[string]interface{}{ 4959 "integer_field": "abcdef", 4960 }, 4961 Err: true, 4962 }, 4963 "invalid float field": { 4964 Schema: map[string]*Schema{ 4965 "float_field": { 4966 Type: TypeFloat, 4967 Optional: true, 4968 }, 4969 }, 4970 Config: map[string]interface{}{ 4971 "float_field": "abcdef", 4972 }, 4973 Err: true, 4974 }, 4975 4976 // Invalid map values 4977 "invalid bool map value": { 4978 Schema: map[string]*Schema{ 4979 "boolMap": &Schema{ 4980 Type: TypeMap, 4981 Elem: TypeBool, 4982 Optional: true, 4983 }, 4984 }, 4985 Config: map[string]interface{}{ 4986 "boolMap": map[string]interface{}{ 4987 "boolField": "notbool", 4988 }, 4989 }, 4990 Err: true, 4991 }, 4992 "invalid int map value": { 4993 Schema: map[string]*Schema{ 4994 "intMap": &Schema{ 4995 Type: TypeMap, 4996 Elem: TypeInt, 4997 Optional: true, 4998 }, 4999 }, 5000 Config: map[string]interface{}{ 5001 "intMap": map[string]interface{}{ 5002 "intField": "notInt", 5003 }, 5004 }, 5005 Err: true, 5006 }, 5007 "invalid float map value": { 5008 Schema: map[string]*Schema{ 5009 "floatMap": &Schema{ 5010 Type: TypeMap, 5011 Elem: TypeFloat, 5012 Optional: true, 5013 }, 5014 }, 5015 Config: map[string]interface{}{ 5016 "floatMap": map[string]interface{}{ 5017 "floatField": "notFloat", 5018 }, 5019 }, 5020 Err: true, 5021 }, 5022 5023 "map with positive validate function": { 5024 Schema: map[string]*Schema{ 5025 "floatInt": &Schema{ 5026 Type: TypeMap, 5027 Elem: TypeInt, 5028 Optional: true, 5029 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 5030 return 5031 }, 5032 }, 5033 }, 5034 Config: map[string]interface{}{ 5035 "floatInt": map[string]interface{}{ 5036 "rightAnswer": "42", 5037 "tooMuch": "43", 5038 }, 5039 }, 5040 Err: false, 5041 }, 5042 "map with negative validate function": { 5043 Schema: map[string]*Schema{ 5044 "floatInt": &Schema{ 5045 Type: TypeMap, 5046 Elem: TypeInt, 5047 Optional: true, 5048 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 5049 es = append(es, fmt.Errorf("this is not fine")) 5050 return 5051 }, 5052 }, 5053 }, 5054 Config: map[string]interface{}{ 5055 "floatInt": map[string]interface{}{ 5056 "rightAnswer": "42", 5057 "tooMuch": "43", 5058 }, 5059 }, 5060 Err: true, 5061 }, 5062 5063 // The Validation function should not see interpolation strings from 5064 // non-computed values. 5065 "set with partially computed list and map": { 5066 Schema: map[string]*Schema{ 5067 "outer": &Schema{ 5068 Type: TypeSet, 5069 Optional: true, 5070 Computed: true, 5071 Elem: &Resource{ 5072 Schema: map[string]*Schema{ 5073 "list": &Schema{ 5074 Type: TypeList, 5075 Optional: true, 5076 Elem: &Schema{ 5077 Type: TypeString, 5078 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 5079 if strings.HasPrefix(v.(string), "${") { 5080 es = append(es, fmt.Errorf("should not have interpolations")) 5081 } 5082 return 5083 }, 5084 }, 5085 }, 5086 }, 5087 }, 5088 }, 5089 }, 5090 Config: map[string]interface{}{ 5091 "outer": []map[string]interface{}{ 5092 { 5093 "list": []interface{}{"${var.a}", "${var.b}", "c"}, 5094 }, 5095 }, 5096 }, 5097 Vars: map[string]string{ 5098 "var.a": "A", 5099 "var.b": config.UnknownVariableValue, 5100 }, 5101 Err: false, 5102 }, 5103 } 5104 5105 for tn, tc := range cases { 5106 t.Run(tn, func(t *testing.T) { 5107 c, err := config.NewRawConfig(tc.Config) 5108 if err != nil { 5109 t.Fatalf("err: %s", err) 5110 } 5111 if tc.Vars != nil { 5112 vars := make(map[string]ast.Variable) 5113 for k, v := range tc.Vars { 5114 vars[k] = ast.Variable{Value: v, Type: ast.TypeString} 5115 } 5116 5117 if err := c.Interpolate(vars); err != nil { 5118 t.Fatalf("err: %s", err) 5119 } 5120 } 5121 5122 ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) 5123 if len(es) > 0 != tc.Err { 5124 if len(es) == 0 { 5125 t.Errorf("%q: no errors", tn) 5126 } 5127 5128 for _, e := range es { 5129 t.Errorf("%q: err: %s", tn, e) 5130 } 5131 5132 t.FailNow() 5133 } 5134 5135 if !reflect.DeepEqual(ws, tc.Warnings) { 5136 t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws) 5137 } 5138 5139 if tc.Errors != nil { 5140 sort.Sort(errorSort(es)) 5141 sort.Sort(errorSort(tc.Errors)) 5142 5143 if !reflect.DeepEqual(es, tc.Errors) { 5144 t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es) 5145 } 5146 } 5147 }) 5148 5149 } 5150 } 5151 5152 func TestSchemaSet_ValidateMaxItems(t *testing.T) { 5153 cases := map[string]struct { 5154 Schema map[string]*Schema 5155 State *terraform.InstanceState 5156 Config map[string]interface{} 5157 ConfigVariables map[string]string 5158 Diff *terraform.InstanceDiff 5159 Err bool 5160 Errors []error 5161 }{ 5162 "#0": { 5163 Schema: map[string]*Schema{ 5164 "aliases": &Schema{ 5165 Type: TypeSet, 5166 Optional: true, 5167 MaxItems: 1, 5168 Elem: &Schema{Type: TypeString}, 5169 }, 5170 }, 5171 State: nil, 5172 Config: map[string]interface{}{ 5173 "aliases": []interface{}{"foo", "bar"}, 5174 }, 5175 Diff: nil, 5176 Err: true, 5177 Errors: []error{ 5178 fmt.Errorf("aliases: attribute supports 1 item maximum, config has 2 declared"), 5179 }, 5180 }, 5181 "#1": { 5182 Schema: map[string]*Schema{ 5183 "aliases": &Schema{ 5184 Type: TypeSet, 5185 Optional: true, 5186 Elem: &Schema{Type: TypeString}, 5187 }, 5188 }, 5189 State: nil, 5190 Config: map[string]interface{}{ 5191 "aliases": []interface{}{"foo", "bar"}, 5192 }, 5193 Diff: nil, 5194 Err: false, 5195 Errors: nil, 5196 }, 5197 "#2": { 5198 Schema: map[string]*Schema{ 5199 "aliases": &Schema{ 5200 Type: TypeSet, 5201 Optional: true, 5202 MaxItems: 1, 5203 Elem: &Schema{Type: TypeString}, 5204 }, 5205 }, 5206 State: nil, 5207 Config: map[string]interface{}{ 5208 "aliases": []interface{}{"foo"}, 5209 }, 5210 Diff: nil, 5211 Err: false, 5212 Errors: nil, 5213 }, 5214 } 5215 5216 for tn, tc := range cases { 5217 c, err := config.NewRawConfig(tc.Config) 5218 if err != nil { 5219 t.Fatalf("%q: err: %s", tn, err) 5220 } 5221 _, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) 5222 5223 if len(es) > 0 != tc.Err { 5224 if len(es) == 0 { 5225 t.Errorf("%q: no errors", tn) 5226 } 5227 5228 for _, e := range es { 5229 t.Errorf("%q: err: %s", tn, e) 5230 } 5231 5232 t.FailNow() 5233 } 5234 5235 if tc.Errors != nil { 5236 if !reflect.DeepEqual(es, tc.Errors) { 5237 t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es) 5238 } 5239 } 5240 } 5241 } 5242 5243 func TestSchemaSet_ValidateMinItems(t *testing.T) { 5244 cases := map[string]struct { 5245 Schema map[string]*Schema 5246 State *terraform.InstanceState 5247 Config map[string]interface{} 5248 ConfigVariables map[string]string 5249 Diff *terraform.InstanceDiff 5250 Err bool 5251 Errors []error 5252 }{ 5253 "#0": { 5254 Schema: map[string]*Schema{ 5255 "aliases": &Schema{ 5256 Type: TypeSet, 5257 Optional: true, 5258 MinItems: 2, 5259 Elem: &Schema{Type: TypeString}, 5260 }, 5261 }, 5262 State: nil, 5263 Config: map[string]interface{}{ 5264 "aliases": []interface{}{"foo", "bar"}, 5265 }, 5266 Diff: nil, 5267 Err: false, 5268 Errors: nil, 5269 }, 5270 "#1": { 5271 Schema: map[string]*Schema{ 5272 "aliases": &Schema{ 5273 Type: TypeSet, 5274 Optional: true, 5275 Elem: &Schema{Type: TypeString}, 5276 }, 5277 }, 5278 State: nil, 5279 Config: map[string]interface{}{ 5280 "aliases": []interface{}{"foo", "bar"}, 5281 }, 5282 Diff: nil, 5283 Err: false, 5284 Errors: nil, 5285 }, 5286 "#2": { 5287 Schema: map[string]*Schema{ 5288 "aliases": &Schema{ 5289 Type: TypeSet, 5290 Optional: true, 5291 MinItems: 2, 5292 Elem: &Schema{Type: TypeString}, 5293 }, 5294 }, 5295 State: nil, 5296 Config: map[string]interface{}{ 5297 "aliases": []interface{}{"foo"}, 5298 }, 5299 Diff: nil, 5300 Err: true, 5301 Errors: []error{ 5302 fmt.Errorf("aliases: attribute supports 2 item as a minimum, config has 1 declared"), 5303 }, 5304 }, 5305 } 5306 5307 for tn, tc := range cases { 5308 c, err := config.NewRawConfig(tc.Config) 5309 if err != nil { 5310 t.Fatalf("%q: err: %s", tn, err) 5311 } 5312 _, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) 5313 5314 if len(es) > 0 != tc.Err { 5315 if len(es) == 0 { 5316 t.Errorf("%q: no errors", tn) 5317 } 5318 5319 for _, e := range es { 5320 t.Errorf("%q: err: %s", tn, e) 5321 } 5322 5323 t.FailNow() 5324 } 5325 5326 if tc.Errors != nil { 5327 if !reflect.DeepEqual(es, tc.Errors) { 5328 t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es) 5329 } 5330 } 5331 } 5332 } 5333 5334 // errorSort implements sort.Interface to sort errors by their error message 5335 type errorSort []error 5336 5337 func (e errorSort) Len() int { return len(e) } 5338 func (e errorSort) Swap(i, j int) { e[i], e[j] = e[j], e[i] } 5339 func (e errorSort) Less(i, j int) bool { 5340 return e[i].Error() < e[j].Error() 5341 } 5342 5343 func TestSchemaMapDeepCopy(t *testing.T) { 5344 schema := map[string]*Schema{ 5345 "foo": &Schema{ 5346 Type: TypeString, 5347 }, 5348 } 5349 source := schemaMap(schema) 5350 dest := source.DeepCopy() 5351 dest["foo"].ForceNew = true 5352 if reflect.DeepEqual(source, dest) { 5353 t.Fatalf("source and dest should not match") 5354 } 5355 }