github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/legacy/helper/schema/schema_test.go (about)

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