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