github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/helper/schema/schema_test.go (about)

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