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