github.com/nathanielks/terraform@v0.6.1-0.20170509030759-13e1a62319dc/helper/schema/schema_test.go (about)

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