github.com/dougneal/terraform@v0.6.15-0.20170330092735-b6a3840768a4/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: "Removal of TypeList should cause nested Bool fields w/ Default to be removed too",
  2783  			Schema: map[string]*Schema{
  2784  				"deployment_group_name": &Schema{
  2785  					Type:     TypeString,
  2786  					Required: true,
  2787  					ForceNew: true,
  2788  				},
  2789  
  2790  				"alarm_configuration": &Schema{
  2791  					Type:     TypeList,
  2792  					Optional: true,
  2793  					MaxItems: 1,
  2794  					Elem: &Resource{
  2795  						Schema: map[string]*Schema{
  2796  							"alarms": &Schema{
  2797  								Type:     TypeSet,
  2798  								Optional: true,
  2799  								Set:      HashString,
  2800  								Elem:     &Schema{Type: TypeString},
  2801  							},
  2802  
  2803  							"enabled": &Schema{
  2804  								Type:     TypeBool,
  2805  								Optional: true,
  2806  							},
  2807  
  2808  							"ignore_poll_alarm_failure": &Schema{
  2809  								Type:     TypeBool,
  2810  								Optional: true,
  2811  								Default:  false,
  2812  							},
  2813  						},
  2814  					},
  2815  				},
  2816  			},
  2817  
  2818  			State: &terraform.InstanceState{
  2819  				Attributes: map[string]string{
  2820  					"alarm_configuration.#":                           "1",
  2821  					"alarm_configuration.0.alarms.#":                  "1",
  2822  					"alarm_configuration.0.alarms.2356372769":         "foo",
  2823  					"alarm_configuration.0.enabled":                   "true",
  2824  					"alarm_configuration.0.ignore_poll_alarm_failure": "false",
  2825  					"deployment_group_name":                           "foo-group-32345345345",
  2826  				},
  2827  			},
  2828  
  2829  			Config: map[string]interface{}{
  2830  				"deployment_group_name": "foo-group-32345345345",
  2831  			},
  2832  
  2833  			Diff: &terraform.InstanceDiff{
  2834  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2835  					"alarm_configuration.#": &terraform.ResourceAttrDiff{
  2836  						Old:        "1",
  2837  						New:        "0",
  2838  						NewRemoved: false,
  2839  					},
  2840  					"alarm_configuration.0.alarms": &terraform.ResourceAttrDiff{
  2841  						Old:        "",
  2842  						New:        "",
  2843  						NewRemoved: true,
  2844  					},
  2845  					"alarm_configuration.0.alarms.#": &terraform.ResourceAttrDiff{
  2846  						Old:        "1",
  2847  						New:        "0",
  2848  						NewRemoved: false,
  2849  					},
  2850  					"alarm_configuration.0.alarms.2356372769": &terraform.ResourceAttrDiff{
  2851  						Old:        "foo",
  2852  						New:        "",
  2853  						NewRemoved: true,
  2854  					},
  2855  					"alarm_configuration.0.enabled": &terraform.ResourceAttrDiff{
  2856  						Old:        "true",
  2857  						New:        "false",
  2858  						NewRemoved: true,
  2859  					},
  2860  					"alarm_configuration.0.ignore_poll_alarm_failure": &terraform.ResourceAttrDiff{
  2861  						Old:        "",
  2862  						New:        "",
  2863  						NewRemoved: true,
  2864  					},
  2865  				},
  2866  			},
  2867  		},
  2868  
  2869  		{
  2870  			Name: "Removal of TypeList should cause all empty nested String fields to be removed too",
  2871  			Schema: map[string]*Schema{
  2872  				"bucket": {
  2873  					Type:     TypeString,
  2874  					Required: true,
  2875  					ForceNew: true,
  2876  				},
  2877  
  2878  				"acl": {
  2879  					Type:     TypeString,
  2880  					Default:  "private",
  2881  					Optional: true,
  2882  				},
  2883  
  2884  				"website": {
  2885  					Type:     TypeList,
  2886  					Optional: true,
  2887  					Elem: &Resource{
  2888  						Schema: map[string]*Schema{
  2889  							"index_document": {
  2890  								Type:     TypeString,
  2891  								Optional: true,
  2892  							},
  2893  
  2894  							"error_document": {
  2895  								Type:     TypeString,
  2896  								Optional: true,
  2897  							},
  2898  
  2899  							"redirect_all_requests_to": {
  2900  								Type:     TypeString,
  2901  								Optional: true,
  2902  							},
  2903  
  2904  							"routing_rules": {
  2905  								Type:     TypeString,
  2906  								Optional: true,
  2907  							},
  2908  						},
  2909  					},
  2910  				},
  2911  			},
  2912  
  2913  			State: &terraform.InstanceState{
  2914  				Attributes: map[string]string{
  2915  					"acl":                                "public-read",
  2916  					"bucket":                             "tf-test-bucket-5011072831090096749",
  2917  					"website.#":                          "1",
  2918  					"website.0.error_document":           "error.html",
  2919  					"website.0.index_document":           "index.html",
  2920  					"website.0.redirect_all_requests_to": "",
  2921  				},
  2922  			},
  2923  
  2924  			Config: map[string]interface{}{
  2925  				"acl":    "public-read",
  2926  				"bucket": "tf-test-bucket-5011072831090096749",
  2927  			},
  2928  
  2929  			Diff: &terraform.InstanceDiff{
  2930  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2931  					"website.#": &terraform.ResourceAttrDiff{
  2932  						Old:        "1",
  2933  						New:        "0",
  2934  						NewRemoved: false,
  2935  					},
  2936  					"website.0.index_document": &terraform.ResourceAttrDiff{
  2937  						Old:        "index.html",
  2938  						New:        "",
  2939  						NewRemoved: true,
  2940  					},
  2941  					"website.0.error_document": &terraform.ResourceAttrDiff{
  2942  						Old:        "error.html",
  2943  						New:        "",
  2944  						NewRemoved: true,
  2945  					},
  2946  					"website.0.redirect_all_requests_to": &terraform.ResourceAttrDiff{
  2947  						Old:        "",
  2948  						New:        "",
  2949  						NewRemoved: true,
  2950  					},
  2951  					"website.0.routing_rules": &terraform.ResourceAttrDiff{
  2952  						Old:        "",
  2953  						New:        "",
  2954  						NewRemoved: true,
  2955  					},
  2956  				},
  2957  			},
  2958  		},
  2959  
  2960  		{
  2961  			Name: "Removal of TypeList should cause nested Int fields w/ Default to be removed too",
  2962  			Schema: map[string]*Schema{
  2963  				"availability_zones": &Schema{
  2964  					Type:     TypeSet,
  2965  					Elem:     &Schema{Type: TypeString},
  2966  					Optional: true,
  2967  					Computed: true,
  2968  					Set:      HashString,
  2969  				},
  2970  
  2971  				"access_logs": &Schema{
  2972  					Type:     TypeList,
  2973  					Optional: true,
  2974  					MaxItems: 1,
  2975  					Elem: &Resource{
  2976  						Schema: map[string]*Schema{
  2977  							"interval": &Schema{
  2978  								Type:     TypeInt,
  2979  								Optional: true,
  2980  								Default:  60,
  2981  							},
  2982  							"bucket": &Schema{
  2983  								Type:     TypeString,
  2984  								Required: true,
  2985  							},
  2986  							"bucket_prefix": &Schema{
  2987  								Type:     TypeString,
  2988  								Optional: true,
  2989  							},
  2990  							"enabled": &Schema{
  2991  								Type:     TypeBool,
  2992  								Optional: true,
  2993  								Default:  true,
  2994  							},
  2995  						},
  2996  					},
  2997  				},
  2998  			},
  2999  
  3000  			State: &terraform.InstanceState{
  3001  				Attributes: map[string]string{
  3002  					"access_logs.#":                 "1",
  3003  					"access_logs.0.bucket":          "terraform-access-logs-bucket-5906065226840117876",
  3004  					"access_logs.0.bucket_prefix":   "",
  3005  					"access_logs.0.enabled":         "true",
  3006  					"access_logs.0.interval":        "5",
  3007  					"availability_zones.#":          "3",
  3008  					"availability_zones.2050015877": "us-west-2c",
  3009  					"availability_zones.221770259":  "us-west-2b",
  3010  					"availability_zones.2487133097": "us-west-2a",
  3011  				},
  3012  			},
  3013  
  3014  			Config: map[string]interface{}{
  3015  				"availability_zones": []interface{}{"us-west-2a", "us-west-2b", "us-west-2c"},
  3016  			},
  3017  
  3018  			Diff: &terraform.InstanceDiff{
  3019  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3020  					"access_logs.#": &terraform.ResourceAttrDiff{
  3021  						Old:        "1",
  3022  						New:        "0",
  3023  						NewRemoved: false,
  3024  					},
  3025  					"access_logs.0.bucket": &terraform.ResourceAttrDiff{
  3026  						Old:        "terraform-access-logs-bucket-5906065226840117876",
  3027  						New:        "",
  3028  						NewRemoved: true,
  3029  					},
  3030  					"access_logs.0.bucket_prefix": &terraform.ResourceAttrDiff{
  3031  						Old:        "",
  3032  						New:        "",
  3033  						NewRemoved: true,
  3034  					},
  3035  					"access_logs.0.enabled": &terraform.ResourceAttrDiff{
  3036  						Old:        "",
  3037  						New:        "",
  3038  						NewRemoved: true,
  3039  					},
  3040  					"access_logs.0.interval": &terraform.ResourceAttrDiff{
  3041  						Old:        "5",
  3042  						New:        "60",
  3043  						NewRemoved: true,
  3044  					},
  3045  				},
  3046  			},
  3047  		},
  3048  
  3049  		{
  3050  			Name: "Removal of TypeSet should cause computed fields to be removed",
  3051  			Schema: map[string]*Schema{
  3052  				"type_set": &Schema{
  3053  					Type:     TypeSet,
  3054  					Optional: true,
  3055  					Elem: &Resource{
  3056  						Schema: map[string]*Schema{
  3057  							"name": &Schema{
  3058  								Type:     TypeString,
  3059  								Optional: true,
  3060  							},
  3061  							"required": &Schema{
  3062  								Type:     TypeString,
  3063  								Required: true,
  3064  							},
  3065  							"value": &Schema{
  3066  								Type:     TypeInt,
  3067  								Optional: true,
  3068  							},
  3069  							"required_value": &Schema{
  3070  								Type:     TypeInt,
  3071  								Required: true,
  3072  							},
  3073  							"computed_value": &Schema{
  3074  								Type:     TypeString,
  3075  								Optional: true,
  3076  								Computed: true,
  3077  							},
  3078  						},
  3079  					},
  3080  					Set: func(i interface{}) int {
  3081  						if i != nil {
  3082  							return 12345
  3083  						}
  3084  						return 0
  3085  					},
  3086  				},
  3087  			},
  3088  
  3089  			State: &terraform.InstanceState{
  3090  				Attributes: map[string]string{
  3091  					"type_set.#":                    "1",
  3092  					"type_set.12345.name":           "Name",
  3093  					"type_set.12345.required":       "Required",
  3094  					"type_set.12345.value":          "0",
  3095  					"type_set.12345.required_value": "5",
  3096  					"type_set.12345.computed_value": "COMPUTED",
  3097  				},
  3098  			},
  3099  
  3100  			Config: map[string]interface{}{
  3101  				"type_set": []interface{}{},
  3102  			},
  3103  
  3104  			Diff: &terraform.InstanceDiff{
  3105  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3106  					"type_set.#": &terraform.ResourceAttrDiff{
  3107  						Old:        "1",
  3108  						New:        "0",
  3109  						NewRemoved: false,
  3110  					},
  3111  					"type_set.12345.name": &terraform.ResourceAttrDiff{
  3112  						Old:        "Name",
  3113  						New:        "",
  3114  						NewRemoved: true,
  3115  					},
  3116  					"type_set.12345.required": &terraform.ResourceAttrDiff{
  3117  						Old:        "Required",
  3118  						New:        "",
  3119  						NewRemoved: true,
  3120  					},
  3121  					"type_set.12345.value": &terraform.ResourceAttrDiff{
  3122  						Old:        "0",
  3123  						New:        "0",
  3124  						NewRemoved: true,
  3125  					},
  3126  					"type_set.12345.required_value": &terraform.ResourceAttrDiff{
  3127  						Old:        "5",
  3128  						New:        "0",
  3129  						NewRemoved: true,
  3130  					},
  3131  					"type_set.12345.computed_value": &terraform.ResourceAttrDiff{
  3132  						NewRemoved: true,
  3133  					},
  3134  				},
  3135  			},
  3136  		},
  3137  	}
  3138  
  3139  	for i, tc := range cases {
  3140  		t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
  3141  			c, err := config.NewRawConfig(tc.Config)
  3142  			if err != nil {
  3143  				t.Fatalf("err: %s", err)
  3144  			}
  3145  
  3146  			if len(tc.ConfigVariables) > 0 {
  3147  				if err := c.Interpolate(tc.ConfigVariables); err != nil {
  3148  					t.Fatalf("err: %s", err)
  3149  				}
  3150  			}
  3151  
  3152  			d, err := schemaMap(tc.Schema).Diff(
  3153  				tc.State, terraform.NewResourceConfig(c))
  3154  			if err != nil != tc.Err {
  3155  				t.Fatalf("err: %s", err)
  3156  			}
  3157  
  3158  			if !reflect.DeepEqual(tc.Diff, d) {
  3159  				t.Fatalf("expected:\n%#v\n\ngot:\n%#v", tc.Diff, d)
  3160  			}
  3161  		})
  3162  	}
  3163  }
  3164  
  3165  func TestSchemaMap_Input(t *testing.T) {
  3166  	cases := map[string]struct {
  3167  		Schema map[string]*Schema
  3168  		Config map[string]interface{}
  3169  		Input  map[string]string
  3170  		Result map[string]interface{}
  3171  		Err    bool
  3172  	}{
  3173  		/*
  3174  		 * String decode
  3175  		 */
  3176  
  3177  		"no input on optional field with no config": {
  3178  			Schema: map[string]*Schema{
  3179  				"availability_zone": &Schema{
  3180  					Type:     TypeString,
  3181  					Optional: true,
  3182  				},
  3183  			},
  3184  
  3185  			Input:  map[string]string{},
  3186  			Result: map[string]interface{}{},
  3187  			Err:    false,
  3188  		},
  3189  
  3190  		"input ignored when config has a value": {
  3191  			Schema: map[string]*Schema{
  3192  				"availability_zone": &Schema{
  3193  					Type:     TypeString,
  3194  					Optional: true,
  3195  				},
  3196  			},
  3197  
  3198  			Config: map[string]interface{}{
  3199  				"availability_zone": "bar",
  3200  			},
  3201  
  3202  			Input: map[string]string{
  3203  				"availability_zone": "foo",
  3204  			},
  3205  
  3206  			Result: map[string]interface{}{},
  3207  
  3208  			Err: false,
  3209  		},
  3210  
  3211  		"input ignored when schema has a default": {
  3212  			Schema: map[string]*Schema{
  3213  				"availability_zone": &Schema{
  3214  					Type:     TypeString,
  3215  					Default:  "foo",
  3216  					Optional: true,
  3217  				},
  3218  			},
  3219  
  3220  			Input: map[string]string{
  3221  				"availability_zone": "bar",
  3222  			},
  3223  
  3224  			Result: map[string]interface{}{},
  3225  
  3226  			Err: false,
  3227  		},
  3228  
  3229  		"input ignored when default function returns a value": {
  3230  			Schema: map[string]*Schema{
  3231  				"availability_zone": &Schema{
  3232  					Type: TypeString,
  3233  					DefaultFunc: func() (interface{}, error) {
  3234  						return "foo", nil
  3235  					},
  3236  					Optional: true,
  3237  				},
  3238  			},
  3239  
  3240  			Input: map[string]string{
  3241  				"availability_zone": "bar",
  3242  			},
  3243  
  3244  			Result: map[string]interface{}{},
  3245  
  3246  			Err: false,
  3247  		},
  3248  
  3249  		"input ignored when default function returns an empty string": {
  3250  			Schema: map[string]*Schema{
  3251  				"availability_zone": &Schema{
  3252  					Type:     TypeString,
  3253  					Default:  "",
  3254  					Optional: true,
  3255  				},
  3256  			},
  3257  
  3258  			Input: map[string]string{
  3259  				"availability_zone": "bar",
  3260  			},
  3261  
  3262  			Result: map[string]interface{}{},
  3263  
  3264  			Err: false,
  3265  		},
  3266  
  3267  		"input used when default function returns nil": {
  3268  			Schema: map[string]*Schema{
  3269  				"availability_zone": &Schema{
  3270  					Type: TypeString,
  3271  					DefaultFunc: func() (interface{}, error) {
  3272  						return nil, nil
  3273  					},
  3274  					Required: true,
  3275  				},
  3276  			},
  3277  
  3278  			Input: map[string]string{
  3279  				"availability_zone": "bar",
  3280  			},
  3281  
  3282  			Result: map[string]interface{}{
  3283  				"availability_zone": "bar",
  3284  			},
  3285  
  3286  			Err: false,
  3287  		},
  3288  
  3289  		"input not used when optional default function returns nil": {
  3290  			Schema: map[string]*Schema{
  3291  				"availability_zone": &Schema{
  3292  					Type: TypeString,
  3293  					DefaultFunc: func() (interface{}, error) {
  3294  						return nil, nil
  3295  					},
  3296  					Optional: true,
  3297  				},
  3298  			},
  3299  
  3300  			Input:  map[string]string{},
  3301  			Result: map[string]interface{}{},
  3302  			Err:    false,
  3303  		},
  3304  	}
  3305  
  3306  	for i, tc := range cases {
  3307  		if tc.Config == nil {
  3308  			tc.Config = make(map[string]interface{})
  3309  		}
  3310  
  3311  		c, err := config.NewRawConfig(tc.Config)
  3312  		if err != nil {
  3313  			t.Fatalf("err: %s", err)
  3314  		}
  3315  
  3316  		input := new(terraform.MockUIInput)
  3317  		input.InputReturnMap = tc.Input
  3318  
  3319  		rc := terraform.NewResourceConfig(c)
  3320  		rc.Config = make(map[string]interface{})
  3321  
  3322  		actual, err := schemaMap(tc.Schema).Input(input, rc)
  3323  		if err != nil != tc.Err {
  3324  			t.Fatalf("#%v err: %s", i, err)
  3325  		}
  3326  
  3327  		if !reflect.DeepEqual(tc.Result, actual.Config) {
  3328  			t.Fatalf("#%v: bad:\n\ngot: %#v\nexpected: %#v", i, actual.Config, tc.Result)
  3329  		}
  3330  	}
  3331  }
  3332  
  3333  func TestSchemaMap_InputDefault(t *testing.T) {
  3334  	emptyConfig := make(map[string]interface{})
  3335  	c, err := config.NewRawConfig(emptyConfig)
  3336  	if err != nil {
  3337  		t.Fatalf("err: %s", err)
  3338  	}
  3339  	rc := terraform.NewResourceConfig(c)
  3340  	rc.Config = make(map[string]interface{})
  3341  
  3342  	input := new(terraform.MockUIInput)
  3343  	input.InputFn = func(opts *terraform.InputOpts) (string, error) {
  3344  		t.Fatalf("InputFn should not be called on: %#v", opts)
  3345  		return "", nil
  3346  	}
  3347  
  3348  	schema := map[string]*Schema{
  3349  		"availability_zone": &Schema{
  3350  			Type:     TypeString,
  3351  			Default:  "foo",
  3352  			Optional: true,
  3353  		},
  3354  	}
  3355  	actual, err := schemaMap(schema).Input(input, rc)
  3356  	if err != nil {
  3357  		t.Fatalf("err: %s", err)
  3358  	}
  3359  
  3360  	expected := map[string]interface{}{}
  3361  
  3362  	if !reflect.DeepEqual(expected, actual.Config) {
  3363  		t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected)
  3364  	}
  3365  }
  3366  
  3367  func TestSchemaMap_InputDeprecated(t *testing.T) {
  3368  	emptyConfig := make(map[string]interface{})
  3369  	c, err := config.NewRawConfig(emptyConfig)
  3370  	if err != nil {
  3371  		t.Fatalf("err: %s", err)
  3372  	}
  3373  	rc := terraform.NewResourceConfig(c)
  3374  	rc.Config = make(map[string]interface{})
  3375  
  3376  	input := new(terraform.MockUIInput)
  3377  	input.InputFn = func(opts *terraform.InputOpts) (string, error) {
  3378  		t.Fatalf("InputFn should not be called on: %#v", opts)
  3379  		return "", nil
  3380  	}
  3381  
  3382  	schema := map[string]*Schema{
  3383  		"availability_zone": &Schema{
  3384  			Type:       TypeString,
  3385  			Deprecated: "long gone",
  3386  			Optional:   true,
  3387  		},
  3388  	}
  3389  	actual, err := schemaMap(schema).Input(input, rc)
  3390  	if err != nil {
  3391  		t.Fatalf("err: %s", err)
  3392  	}
  3393  
  3394  	expected := map[string]interface{}{}
  3395  
  3396  	if !reflect.DeepEqual(expected, actual.Config) {
  3397  		t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected)
  3398  	}
  3399  }
  3400  
  3401  func TestSchemaMap_InternalValidate(t *testing.T) {
  3402  	cases := map[string]struct {
  3403  		In  map[string]*Schema
  3404  		Err bool
  3405  	}{
  3406  		"nothing": {
  3407  			nil,
  3408  			false,
  3409  		},
  3410  
  3411  		"Both optional and required": {
  3412  			map[string]*Schema{
  3413  				"foo": &Schema{
  3414  					Type:     TypeInt,
  3415  					Optional: true,
  3416  					Required: true,
  3417  				},
  3418  			},
  3419  			true,
  3420  		},
  3421  
  3422  		"No optional and no required": {
  3423  			map[string]*Schema{
  3424  				"foo": &Schema{
  3425  					Type: TypeInt,
  3426  				},
  3427  			},
  3428  			true,
  3429  		},
  3430  
  3431  		"Missing Type": {
  3432  			map[string]*Schema{
  3433  				"foo": &Schema{
  3434  					Required: true,
  3435  				},
  3436  			},
  3437  			true,
  3438  		},
  3439  
  3440  		"Required but computed": {
  3441  			map[string]*Schema{
  3442  				"foo": &Schema{
  3443  					Type:     TypeInt,
  3444  					Required: true,
  3445  					Computed: true,
  3446  				},
  3447  			},
  3448  			true,
  3449  		},
  3450  
  3451  		"Looks good": {
  3452  			map[string]*Schema{
  3453  				"foo": &Schema{
  3454  					Type:     TypeString,
  3455  					Required: true,
  3456  				},
  3457  			},
  3458  			false,
  3459  		},
  3460  
  3461  		"Computed but has default": {
  3462  			map[string]*Schema{
  3463  				"foo": &Schema{
  3464  					Type:     TypeInt,
  3465  					Optional: true,
  3466  					Computed: true,
  3467  					Default:  "foo",
  3468  				},
  3469  			},
  3470  			true,
  3471  		},
  3472  
  3473  		"Required but has default": {
  3474  			map[string]*Schema{
  3475  				"foo": &Schema{
  3476  					Type:     TypeInt,
  3477  					Optional: true,
  3478  					Required: true,
  3479  					Default:  "foo",
  3480  				},
  3481  			},
  3482  			true,
  3483  		},
  3484  
  3485  		"List element not set": {
  3486  			map[string]*Schema{
  3487  				"foo": &Schema{
  3488  					Type: TypeList,
  3489  				},
  3490  			},
  3491  			true,
  3492  		},
  3493  
  3494  		"List default": {
  3495  			map[string]*Schema{
  3496  				"foo": &Schema{
  3497  					Type:    TypeList,
  3498  					Elem:    &Schema{Type: TypeInt},
  3499  					Default: "foo",
  3500  				},
  3501  			},
  3502  			true,
  3503  		},
  3504  
  3505  		"List element computed": {
  3506  			map[string]*Schema{
  3507  				"foo": &Schema{
  3508  					Type:     TypeList,
  3509  					Optional: true,
  3510  					Elem: &Schema{
  3511  						Type:     TypeInt,
  3512  						Computed: true,
  3513  					},
  3514  				},
  3515  			},
  3516  			true,
  3517  		},
  3518  
  3519  		"List element with Set set": {
  3520  			map[string]*Schema{
  3521  				"foo": &Schema{
  3522  					Type:     TypeList,
  3523  					Elem:     &Schema{Type: TypeInt},
  3524  					Set:      func(interface{}) int { return 0 },
  3525  					Optional: true,
  3526  				},
  3527  			},
  3528  			true,
  3529  		},
  3530  
  3531  		"Set element with no Set set": {
  3532  			map[string]*Schema{
  3533  				"foo": &Schema{
  3534  					Type:     TypeSet,
  3535  					Elem:     &Schema{Type: TypeInt},
  3536  					Optional: true,
  3537  				},
  3538  			},
  3539  			false,
  3540  		},
  3541  
  3542  		"Required but computedWhen": {
  3543  			map[string]*Schema{
  3544  				"foo": &Schema{
  3545  					Type:         TypeInt,
  3546  					Required:     true,
  3547  					ComputedWhen: []string{"foo"},
  3548  				},
  3549  			},
  3550  			true,
  3551  		},
  3552  
  3553  		"Conflicting attributes cannot be required": {
  3554  			map[string]*Schema{
  3555  				"blacklist": &Schema{
  3556  					Type:     TypeBool,
  3557  					Required: true,
  3558  				},
  3559  				"whitelist": &Schema{
  3560  					Type:          TypeBool,
  3561  					Optional:      true,
  3562  					ConflictsWith: []string{"blacklist"},
  3563  				},
  3564  			},
  3565  			true,
  3566  		},
  3567  
  3568  		"Attribute with conflicts cannot be required": {
  3569  			map[string]*Schema{
  3570  				"whitelist": &Schema{
  3571  					Type:          TypeBool,
  3572  					Required:      true,
  3573  					ConflictsWith: []string{"blacklist"},
  3574  				},
  3575  			},
  3576  			true,
  3577  		},
  3578  
  3579  		"ConflictsWith cannot be used w/ ComputedWhen": {
  3580  			map[string]*Schema{
  3581  				"blacklist": &Schema{
  3582  					Type:         TypeBool,
  3583  					ComputedWhen: []string{"foor"},
  3584  				},
  3585  				"whitelist": &Schema{
  3586  					Type:          TypeBool,
  3587  					Required:      true,
  3588  					ConflictsWith: []string{"blacklist"},
  3589  				},
  3590  			},
  3591  			true,
  3592  		},
  3593  
  3594  		"Sub-resource invalid": {
  3595  			map[string]*Schema{
  3596  				"foo": &Schema{
  3597  					Type:     TypeList,
  3598  					Optional: true,
  3599  					Elem: &Resource{
  3600  						Schema: map[string]*Schema{
  3601  							"foo": new(Schema),
  3602  						},
  3603  					},
  3604  				},
  3605  			},
  3606  			true,
  3607  		},
  3608  
  3609  		"Sub-resource valid": {
  3610  			map[string]*Schema{
  3611  				"foo": &Schema{
  3612  					Type:     TypeList,
  3613  					Optional: true,
  3614  					Elem: &Resource{
  3615  						Schema: map[string]*Schema{
  3616  							"foo": &Schema{
  3617  								Type:     TypeInt,
  3618  								Optional: true,
  3619  							},
  3620  						},
  3621  					},
  3622  				},
  3623  			},
  3624  			false,
  3625  		},
  3626  
  3627  		"ValidateFunc on non-primitive": {
  3628  			map[string]*Schema{
  3629  				"foo": &Schema{
  3630  					Type:     TypeSet,
  3631  					Required: true,
  3632  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  3633  						return
  3634  					},
  3635  				},
  3636  			},
  3637  			true,
  3638  		},
  3639  	}
  3640  
  3641  	for tn, tc := range cases {
  3642  		err := schemaMap(tc.In).InternalValidate(nil)
  3643  		if err != nil != tc.Err {
  3644  			if tc.Err {
  3645  				t.Fatalf("%q: Expected error did not occur:\n\n%#v", tn, tc.In)
  3646  			}
  3647  			t.Fatalf("%q: Unexpected error occurred:\n\n%#v", tn, tc.In)
  3648  		}
  3649  	}
  3650  
  3651  }
  3652  
  3653  func TestSchemaMap_DiffSuppress(t *testing.T) {
  3654  	cases := map[string]struct {
  3655  		Schema          map[string]*Schema
  3656  		State           *terraform.InstanceState
  3657  		Config          map[string]interface{}
  3658  		ConfigVariables map[string]ast.Variable
  3659  		ExpectedDiff    *terraform.InstanceDiff
  3660  		Err             bool
  3661  	}{
  3662  		"#0 - Suppress otherwise valid diff by returning true": {
  3663  			Schema: map[string]*Schema{
  3664  				"availability_zone": {
  3665  					Type:     TypeString,
  3666  					Optional: true,
  3667  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  3668  						// Always suppress any diff
  3669  						return true
  3670  					},
  3671  				},
  3672  			},
  3673  
  3674  			State: nil,
  3675  
  3676  			Config: map[string]interface{}{
  3677  				"availability_zone": "foo",
  3678  			},
  3679  
  3680  			ExpectedDiff: nil,
  3681  
  3682  			Err: false,
  3683  		},
  3684  
  3685  		"#1 - Don't suppress diff by returning false": {
  3686  			Schema: map[string]*Schema{
  3687  				"availability_zone": {
  3688  					Type:     TypeString,
  3689  					Optional: true,
  3690  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  3691  						// Always suppress any diff
  3692  						return false
  3693  					},
  3694  				},
  3695  			},
  3696  
  3697  			State: nil,
  3698  
  3699  			Config: map[string]interface{}{
  3700  				"availability_zone": "foo",
  3701  			},
  3702  
  3703  			ExpectedDiff: &terraform.InstanceDiff{
  3704  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3705  					"availability_zone": {
  3706  						Old: "",
  3707  						New: "foo",
  3708  					},
  3709  				},
  3710  			},
  3711  
  3712  			Err: false,
  3713  		},
  3714  
  3715  		"Default with suppress makes no diff": {
  3716  			Schema: map[string]*Schema{
  3717  				"availability_zone": {
  3718  					Type:     TypeString,
  3719  					Optional: true,
  3720  					Default:  "foo",
  3721  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  3722  						return true
  3723  					},
  3724  				},
  3725  			},
  3726  
  3727  			State: nil,
  3728  
  3729  			Config: map[string]interface{}{},
  3730  
  3731  			ExpectedDiff: nil,
  3732  
  3733  			Err: false,
  3734  		},
  3735  
  3736  		"Default with false suppress makes diff": {
  3737  			Schema: map[string]*Schema{
  3738  				"availability_zone": {
  3739  					Type:     TypeString,
  3740  					Optional: true,
  3741  					Default:  "foo",
  3742  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  3743  						return false
  3744  					},
  3745  				},
  3746  			},
  3747  
  3748  			State: nil,
  3749  
  3750  			Config: map[string]interface{}{},
  3751  
  3752  			ExpectedDiff: &terraform.InstanceDiff{
  3753  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3754  					"availability_zone": {
  3755  						Old: "",
  3756  						New: "foo",
  3757  					},
  3758  				},
  3759  			},
  3760  
  3761  			Err: false,
  3762  		},
  3763  
  3764  		"Complex structure with set of computed string should mark root set as computed": {
  3765  			Schema: map[string]*Schema{
  3766  				"outer": &Schema{
  3767  					Type:     TypeSet,
  3768  					Optional: true,
  3769  					Elem: &Resource{
  3770  						Schema: map[string]*Schema{
  3771  							"outer_str": &Schema{
  3772  								Type:     TypeString,
  3773  								Optional: true,
  3774  							},
  3775  							"inner": &Schema{
  3776  								Type:     TypeSet,
  3777  								Optional: true,
  3778  								Elem: &Resource{
  3779  									Schema: map[string]*Schema{
  3780  										"inner_str": &Schema{
  3781  											Type:     TypeString,
  3782  											Optional: true,
  3783  										},
  3784  									},
  3785  								},
  3786  								Set: func(v interface{}) int {
  3787  									return 2
  3788  								},
  3789  							},
  3790  						},
  3791  					},
  3792  					Set: func(v interface{}) int {
  3793  						return 1
  3794  					},
  3795  				},
  3796  			},
  3797  
  3798  			State: nil,
  3799  
  3800  			Config: map[string]interface{}{
  3801  				"outer": []map[string]interface{}{
  3802  					map[string]interface{}{
  3803  						"outer_str": "foo",
  3804  						"inner": []map[string]interface{}{
  3805  							map[string]interface{}{
  3806  								"inner_str": "${var.bar}",
  3807  							},
  3808  						},
  3809  					},
  3810  				},
  3811  			},
  3812  
  3813  			ConfigVariables: map[string]ast.Variable{
  3814  				"var.bar": interfaceToVariableSwallowError(config.UnknownVariableValue),
  3815  			},
  3816  
  3817  			ExpectedDiff: &terraform.InstanceDiff{
  3818  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3819  					"outer.#": &terraform.ResourceAttrDiff{
  3820  						Old: "0",
  3821  						New: "1",
  3822  					},
  3823  					"outer.~1.outer_str": &terraform.ResourceAttrDiff{
  3824  						Old: "",
  3825  						New: "foo",
  3826  					},
  3827  					"outer.~1.inner.#": &terraform.ResourceAttrDiff{
  3828  						Old: "0",
  3829  						New: "1",
  3830  					},
  3831  					"outer.~1.inner.~2.inner_str": &terraform.ResourceAttrDiff{
  3832  						Old:         "",
  3833  						New:         "${var.bar}",
  3834  						NewComputed: true,
  3835  					},
  3836  				},
  3837  			},
  3838  
  3839  			Err: false,
  3840  		},
  3841  
  3842  		"Complex structure with complex list of computed string should mark root set as computed": {
  3843  			Schema: map[string]*Schema{
  3844  				"outer": &Schema{
  3845  					Type:     TypeSet,
  3846  					Optional: true,
  3847  					Elem: &Resource{
  3848  						Schema: map[string]*Schema{
  3849  							"outer_str": &Schema{
  3850  								Type:     TypeString,
  3851  								Optional: true,
  3852  							},
  3853  							"inner": &Schema{
  3854  								Type:     TypeList,
  3855  								Optional: true,
  3856  								Elem: &Resource{
  3857  									Schema: map[string]*Schema{
  3858  										"inner_str": &Schema{
  3859  											Type:     TypeString,
  3860  											Optional: true,
  3861  										},
  3862  									},
  3863  								},
  3864  							},
  3865  						},
  3866  					},
  3867  					Set: func(v interface{}) int {
  3868  						return 1
  3869  					},
  3870  				},
  3871  			},
  3872  
  3873  			State: nil,
  3874  
  3875  			Config: map[string]interface{}{
  3876  				"outer": []map[string]interface{}{
  3877  					map[string]interface{}{
  3878  						"outer_str": "foo",
  3879  						"inner": []map[string]interface{}{
  3880  							map[string]interface{}{
  3881  								"inner_str": "${var.bar}",
  3882  							},
  3883  						},
  3884  					},
  3885  				},
  3886  			},
  3887  
  3888  			ConfigVariables: map[string]ast.Variable{
  3889  				"var.bar": interfaceToVariableSwallowError(config.UnknownVariableValue),
  3890  			},
  3891  
  3892  			ExpectedDiff: &terraform.InstanceDiff{
  3893  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3894  					"outer.#": &terraform.ResourceAttrDiff{
  3895  						Old: "0",
  3896  						New: "1",
  3897  					},
  3898  					"outer.~1.outer_str": &terraform.ResourceAttrDiff{
  3899  						Old: "",
  3900  						New: "foo",
  3901  					},
  3902  					"outer.~1.inner.#": &terraform.ResourceAttrDiff{
  3903  						Old: "0",
  3904  						New: "1",
  3905  					},
  3906  					"outer.~1.inner.0.inner_str": &terraform.ResourceAttrDiff{
  3907  						Old:         "",
  3908  						New:         "${var.bar}",
  3909  						NewComputed: true,
  3910  					},
  3911  				},
  3912  			},
  3913  
  3914  			Err: false,
  3915  		},
  3916  	}
  3917  
  3918  	for tn, tc := range cases {
  3919  		t.Run(tn, func(t *testing.T) {
  3920  			c, err := config.NewRawConfig(tc.Config)
  3921  			if err != nil {
  3922  				t.Fatalf("#%q err: %s", tn, err)
  3923  			}
  3924  
  3925  			if len(tc.ConfigVariables) > 0 {
  3926  				if err := c.Interpolate(tc.ConfigVariables); err != nil {
  3927  					t.Fatalf("#%q err: %s", tn, err)
  3928  				}
  3929  			}
  3930  
  3931  			d, err := schemaMap(tc.Schema).Diff(
  3932  				tc.State, terraform.NewResourceConfig(c))
  3933  			if err != nil != tc.Err {
  3934  				t.Fatalf("#%q err: %s", tn, err)
  3935  			}
  3936  
  3937  			if !reflect.DeepEqual(tc.ExpectedDiff, d) {
  3938  				t.Fatalf("#%q:\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.ExpectedDiff, d)
  3939  			}
  3940  		})
  3941  	}
  3942  }
  3943  
  3944  func TestSchemaMap_Validate(t *testing.T) {
  3945  	cases := map[string]struct {
  3946  		Schema   map[string]*Schema
  3947  		Config   map[string]interface{}
  3948  		Vars     map[string]string
  3949  		Err      bool
  3950  		Errors   []error
  3951  		Warnings []string
  3952  	}{
  3953  		"Good": {
  3954  			Schema: map[string]*Schema{
  3955  				"availability_zone": &Schema{
  3956  					Type:     TypeString,
  3957  					Optional: true,
  3958  					Computed: true,
  3959  					ForceNew: true,
  3960  				},
  3961  			},
  3962  
  3963  			Config: map[string]interface{}{
  3964  				"availability_zone": "foo",
  3965  			},
  3966  		},
  3967  
  3968  		"Good, because the var is not set and that error will come elsewhere": {
  3969  			Schema: map[string]*Schema{
  3970  				"size": &Schema{
  3971  					Type:     TypeInt,
  3972  					Required: true,
  3973  				},
  3974  			},
  3975  
  3976  			Config: map[string]interface{}{
  3977  				"size": "${var.foo}",
  3978  			},
  3979  
  3980  			Vars: map[string]string{
  3981  				"var.foo": config.UnknownVariableValue,
  3982  			},
  3983  		},
  3984  
  3985  		"Required field not set": {
  3986  			Schema: map[string]*Schema{
  3987  				"availability_zone": &Schema{
  3988  					Type:     TypeString,
  3989  					Required: true,
  3990  				},
  3991  			},
  3992  
  3993  			Config: map[string]interface{}{},
  3994  
  3995  			Err: true,
  3996  		},
  3997  
  3998  		"Invalid basic type": {
  3999  			Schema: map[string]*Schema{
  4000  				"port": &Schema{
  4001  					Type:     TypeInt,
  4002  					Required: true,
  4003  				},
  4004  			},
  4005  
  4006  			Config: map[string]interface{}{
  4007  				"port": "I am invalid",
  4008  			},
  4009  
  4010  			Err: true,
  4011  		},
  4012  
  4013  		"Invalid complex type": {
  4014  			Schema: map[string]*Schema{
  4015  				"user_data": &Schema{
  4016  					Type:     TypeString,
  4017  					Optional: true,
  4018  				},
  4019  			},
  4020  
  4021  			Config: map[string]interface{}{
  4022  				"user_data": []interface{}{
  4023  					map[string]interface{}{
  4024  						"foo": "bar",
  4025  					},
  4026  				},
  4027  			},
  4028  
  4029  			Err: true,
  4030  		},
  4031  
  4032  		"Bad type, interpolated": {
  4033  			Schema: map[string]*Schema{
  4034  				"size": &Schema{
  4035  					Type:     TypeInt,
  4036  					Required: true,
  4037  				},
  4038  			},
  4039  
  4040  			Config: map[string]interface{}{
  4041  				"size": "${var.foo}",
  4042  			},
  4043  
  4044  			Vars: map[string]string{
  4045  				"var.foo": "nope",
  4046  			},
  4047  
  4048  			Err: true,
  4049  		},
  4050  
  4051  		"Required but has DefaultFunc": {
  4052  			Schema: map[string]*Schema{
  4053  				"availability_zone": &Schema{
  4054  					Type:     TypeString,
  4055  					Required: true,
  4056  					DefaultFunc: func() (interface{}, error) {
  4057  						return "foo", nil
  4058  					},
  4059  				},
  4060  			},
  4061  
  4062  			Config: nil,
  4063  		},
  4064  
  4065  		"Required but has DefaultFunc return nil": {
  4066  			Schema: map[string]*Schema{
  4067  				"availability_zone": &Schema{
  4068  					Type:     TypeString,
  4069  					Required: true,
  4070  					DefaultFunc: func() (interface{}, error) {
  4071  						return nil, nil
  4072  					},
  4073  				},
  4074  			},
  4075  
  4076  			Config: nil,
  4077  
  4078  			Err: true,
  4079  		},
  4080  
  4081  		"List with promotion": {
  4082  			Schema: map[string]*Schema{
  4083  				"ingress": &Schema{
  4084  					Type:          TypeList,
  4085  					Elem:          &Schema{Type: TypeInt},
  4086  					PromoteSingle: true,
  4087  					Optional:      true,
  4088  				},
  4089  			},
  4090  
  4091  			Config: map[string]interface{}{
  4092  				"ingress": "5",
  4093  			},
  4094  
  4095  			Err: false,
  4096  		},
  4097  
  4098  		"List with promotion set as list": {
  4099  			Schema: map[string]*Schema{
  4100  				"ingress": &Schema{
  4101  					Type:          TypeList,
  4102  					Elem:          &Schema{Type: TypeInt},
  4103  					PromoteSingle: true,
  4104  					Optional:      true,
  4105  				},
  4106  			},
  4107  
  4108  			Config: map[string]interface{}{
  4109  				"ingress": []interface{}{"5"},
  4110  			},
  4111  
  4112  			Err: false,
  4113  		},
  4114  
  4115  		"Optional sub-resource": {
  4116  			Schema: map[string]*Schema{
  4117  				"ingress": &Schema{
  4118  					Type: TypeList,
  4119  					Elem: &Resource{
  4120  						Schema: map[string]*Schema{
  4121  							"from": &Schema{
  4122  								Type:     TypeInt,
  4123  								Required: true,
  4124  							},
  4125  						},
  4126  					},
  4127  				},
  4128  			},
  4129  
  4130  			Config: map[string]interface{}{},
  4131  
  4132  			Err: false,
  4133  		},
  4134  
  4135  		"Sub-resource is the wrong type": {
  4136  			Schema: map[string]*Schema{
  4137  				"ingress": &Schema{
  4138  					Type:     TypeList,
  4139  					Required: true,
  4140  					Elem: &Resource{
  4141  						Schema: map[string]*Schema{
  4142  							"from": &Schema{
  4143  								Type:     TypeInt,
  4144  								Required: true,
  4145  							},
  4146  						},
  4147  					},
  4148  				},
  4149  			},
  4150  
  4151  			Config: map[string]interface{}{
  4152  				"ingress": []interface{}{"foo"},
  4153  			},
  4154  
  4155  			Err: true,
  4156  		},
  4157  
  4158  		"Not a list": {
  4159  			Schema: map[string]*Schema{
  4160  				"ingress": &Schema{
  4161  					Type: TypeList,
  4162  					Elem: &Resource{
  4163  						Schema: map[string]*Schema{
  4164  							"from": &Schema{
  4165  								Type:     TypeInt,
  4166  								Required: true,
  4167  							},
  4168  						},
  4169  					},
  4170  				},
  4171  			},
  4172  
  4173  			Config: map[string]interface{}{
  4174  				"ingress": "foo",
  4175  			},
  4176  
  4177  			Err: true,
  4178  		},
  4179  
  4180  		"Required sub-resource field": {
  4181  			Schema: map[string]*Schema{
  4182  				"ingress": &Schema{
  4183  					Type: TypeList,
  4184  					Elem: &Resource{
  4185  						Schema: map[string]*Schema{
  4186  							"from": &Schema{
  4187  								Type:     TypeInt,
  4188  								Required: true,
  4189  							},
  4190  						},
  4191  					},
  4192  				},
  4193  			},
  4194  
  4195  			Config: map[string]interface{}{
  4196  				"ingress": []interface{}{
  4197  					map[string]interface{}{},
  4198  				},
  4199  			},
  4200  
  4201  			Err: true,
  4202  		},
  4203  
  4204  		"Good sub-resource": {
  4205  			Schema: map[string]*Schema{
  4206  				"ingress": &Schema{
  4207  					Type:     TypeList,
  4208  					Optional: true,
  4209  					Elem: &Resource{
  4210  						Schema: map[string]*Schema{
  4211  							"from": &Schema{
  4212  								Type:     TypeInt,
  4213  								Required: true,
  4214  							},
  4215  						},
  4216  					},
  4217  				},
  4218  			},
  4219  
  4220  			Config: map[string]interface{}{
  4221  				"ingress": []interface{}{
  4222  					map[string]interface{}{
  4223  						"from": 80,
  4224  					},
  4225  				},
  4226  			},
  4227  
  4228  			Err: false,
  4229  		},
  4230  
  4231  		"Invalid/unknown field": {
  4232  			Schema: map[string]*Schema{
  4233  				"availability_zone": &Schema{
  4234  					Type:     TypeString,
  4235  					Optional: true,
  4236  					Computed: true,
  4237  					ForceNew: true,
  4238  				},
  4239  			},
  4240  
  4241  			Config: map[string]interface{}{
  4242  				"foo": "bar",
  4243  			},
  4244  
  4245  			Err: true,
  4246  		},
  4247  
  4248  		"Invalid/unknown field with computed value": {
  4249  			Schema: map[string]*Schema{
  4250  				"availability_zone": &Schema{
  4251  					Type:     TypeString,
  4252  					Optional: true,
  4253  					Computed: true,
  4254  					ForceNew: true,
  4255  				},
  4256  			},
  4257  
  4258  			Config: map[string]interface{}{
  4259  				"foo": "${var.foo}",
  4260  			},
  4261  
  4262  			Vars: map[string]string{
  4263  				"var.foo": config.UnknownVariableValue,
  4264  			},
  4265  
  4266  			Err: true,
  4267  		},
  4268  
  4269  		"Computed field set": {
  4270  			Schema: map[string]*Schema{
  4271  				"availability_zone": &Schema{
  4272  					Type:     TypeString,
  4273  					Computed: true,
  4274  				},
  4275  			},
  4276  
  4277  			Config: map[string]interface{}{
  4278  				"availability_zone": "bar",
  4279  			},
  4280  
  4281  			Err: true,
  4282  		},
  4283  
  4284  		"Not a set": {
  4285  			Schema: map[string]*Schema{
  4286  				"ports": &Schema{
  4287  					Type:     TypeSet,
  4288  					Required: true,
  4289  					Elem:     &Schema{Type: TypeInt},
  4290  					Set: func(a interface{}) int {
  4291  						return a.(int)
  4292  					},
  4293  				},
  4294  			},
  4295  
  4296  			Config: map[string]interface{}{
  4297  				"ports": "foo",
  4298  			},
  4299  
  4300  			Err: true,
  4301  		},
  4302  
  4303  		"Maps": {
  4304  			Schema: map[string]*Schema{
  4305  				"user_data": &Schema{
  4306  					Type:     TypeMap,
  4307  					Optional: true,
  4308  				},
  4309  			},
  4310  
  4311  			Config: map[string]interface{}{
  4312  				"user_data": "foo",
  4313  			},
  4314  
  4315  			Err: true,
  4316  		},
  4317  
  4318  		"Good map: data surrounded by extra slice": {
  4319  			Schema: map[string]*Schema{
  4320  				"user_data": &Schema{
  4321  					Type:     TypeMap,
  4322  					Optional: true,
  4323  				},
  4324  			},
  4325  
  4326  			Config: map[string]interface{}{
  4327  				"user_data": []interface{}{
  4328  					map[string]interface{}{
  4329  						"foo": "bar",
  4330  					},
  4331  				},
  4332  			},
  4333  		},
  4334  
  4335  		"Good map": {
  4336  			Schema: map[string]*Schema{
  4337  				"user_data": &Schema{
  4338  					Type:     TypeMap,
  4339  					Optional: true,
  4340  				},
  4341  			},
  4342  
  4343  			Config: map[string]interface{}{
  4344  				"user_data": map[string]interface{}{
  4345  					"foo": "bar",
  4346  				},
  4347  			},
  4348  		},
  4349  
  4350  		"Bad map: just a slice": {
  4351  			Schema: map[string]*Schema{
  4352  				"user_data": &Schema{
  4353  					Type:     TypeMap,
  4354  					Optional: true,
  4355  				},
  4356  			},
  4357  
  4358  			Config: map[string]interface{}{
  4359  				"user_data": []interface{}{
  4360  					"foo",
  4361  				},
  4362  			},
  4363  
  4364  			Err: true,
  4365  		},
  4366  
  4367  		"Good set: config has slice with single interpolated value": {
  4368  			Schema: map[string]*Schema{
  4369  				"security_groups": &Schema{
  4370  					Type:     TypeSet,
  4371  					Optional: true,
  4372  					Computed: true,
  4373  					ForceNew: true,
  4374  					Elem:     &Schema{Type: TypeString},
  4375  					Set: func(v interface{}) int {
  4376  						return len(v.(string))
  4377  					},
  4378  				},
  4379  			},
  4380  
  4381  			Config: map[string]interface{}{
  4382  				"security_groups": []interface{}{"${var.foo}"},
  4383  			},
  4384  
  4385  			Err: false,
  4386  		},
  4387  
  4388  		"Bad set: config has single interpolated value": {
  4389  			Schema: map[string]*Schema{
  4390  				"security_groups": &Schema{
  4391  					Type:     TypeSet,
  4392  					Optional: true,
  4393  					Computed: true,
  4394  					ForceNew: true,
  4395  					Elem:     &Schema{Type: TypeString},
  4396  				},
  4397  			},
  4398  
  4399  			Config: map[string]interface{}{
  4400  				"security_groups": "${var.foo}",
  4401  			},
  4402  
  4403  			Err: true,
  4404  		},
  4405  
  4406  		"Bad, subresource should not allow unknown elements": {
  4407  			Schema: map[string]*Schema{
  4408  				"ingress": &Schema{
  4409  					Type:     TypeList,
  4410  					Optional: true,
  4411  					Elem: &Resource{
  4412  						Schema: map[string]*Schema{
  4413  							"port": &Schema{
  4414  								Type:     TypeInt,
  4415  								Required: true,
  4416  							},
  4417  						},
  4418  					},
  4419  				},
  4420  			},
  4421  
  4422  			Config: map[string]interface{}{
  4423  				"ingress": []interface{}{
  4424  					map[string]interface{}{
  4425  						"port":  80,
  4426  						"other": "yes",
  4427  					},
  4428  				},
  4429  			},
  4430  
  4431  			Err: true,
  4432  		},
  4433  
  4434  		"Bad, subresource should not allow invalid types": {
  4435  			Schema: map[string]*Schema{
  4436  				"ingress": &Schema{
  4437  					Type:     TypeList,
  4438  					Optional: true,
  4439  					Elem: &Resource{
  4440  						Schema: map[string]*Schema{
  4441  							"port": &Schema{
  4442  								Type:     TypeInt,
  4443  								Required: true,
  4444  							},
  4445  						},
  4446  					},
  4447  				},
  4448  			},
  4449  
  4450  			Config: map[string]interface{}{
  4451  				"ingress": []interface{}{
  4452  					map[string]interface{}{
  4453  						"port": "bad",
  4454  					},
  4455  				},
  4456  			},
  4457  
  4458  			Err: true,
  4459  		},
  4460  
  4461  		"Bad, should not allow lists to be assigned to string attributes": {
  4462  			Schema: map[string]*Schema{
  4463  				"availability_zone": &Schema{
  4464  					Type:     TypeString,
  4465  					Required: true,
  4466  				},
  4467  			},
  4468  
  4469  			Config: map[string]interface{}{
  4470  				"availability_zone": []interface{}{"foo", "bar", "baz"},
  4471  			},
  4472  
  4473  			Err: true,
  4474  		},
  4475  
  4476  		"Bad, should not allow maps to be assigned to string attributes": {
  4477  			Schema: map[string]*Schema{
  4478  				"availability_zone": &Schema{
  4479  					Type:     TypeString,
  4480  					Required: true,
  4481  				},
  4482  			},
  4483  
  4484  			Config: map[string]interface{}{
  4485  				"availability_zone": map[string]interface{}{"foo": "bar", "baz": "thing"},
  4486  			},
  4487  
  4488  			Err: true,
  4489  		},
  4490  
  4491  		"Deprecated attribute usage generates warning, but not error": {
  4492  			Schema: map[string]*Schema{
  4493  				"old_news": &Schema{
  4494  					Type:       TypeString,
  4495  					Optional:   true,
  4496  					Deprecated: "please use 'new_news' instead",
  4497  				},
  4498  			},
  4499  
  4500  			Config: map[string]interface{}{
  4501  				"old_news": "extra extra!",
  4502  			},
  4503  
  4504  			Err: false,
  4505  
  4506  			Warnings: []string{
  4507  				"\"old_news\": [DEPRECATED] please use 'new_news' instead",
  4508  			},
  4509  		},
  4510  
  4511  		"Deprecated generates no warnings if attr not used": {
  4512  			Schema: map[string]*Schema{
  4513  				"old_news": &Schema{
  4514  					Type:       TypeString,
  4515  					Optional:   true,
  4516  					Deprecated: "please use 'new_news' instead",
  4517  				},
  4518  			},
  4519  
  4520  			Err: false,
  4521  
  4522  			Warnings: nil,
  4523  		},
  4524  
  4525  		"Removed attribute usage generates error": {
  4526  			Schema: map[string]*Schema{
  4527  				"long_gone": &Schema{
  4528  					Type:     TypeString,
  4529  					Optional: true,
  4530  					Removed:  "no longer supported by Cloud API",
  4531  				},
  4532  			},
  4533  
  4534  			Config: map[string]interface{}{
  4535  				"long_gone": "still here!",
  4536  			},
  4537  
  4538  			Err: true,
  4539  			Errors: []error{
  4540  				fmt.Errorf("\"long_gone\": [REMOVED] no longer supported by Cloud API"),
  4541  			},
  4542  		},
  4543  
  4544  		"Removed generates no errors if attr not used": {
  4545  			Schema: map[string]*Schema{
  4546  				"long_gone": &Schema{
  4547  					Type:     TypeString,
  4548  					Optional: true,
  4549  					Removed:  "no longer supported by Cloud API",
  4550  				},
  4551  			},
  4552  
  4553  			Err: false,
  4554  		},
  4555  
  4556  		"Conflicting attributes generate error": {
  4557  			Schema: map[string]*Schema{
  4558  				"whitelist": &Schema{
  4559  					Type:     TypeString,
  4560  					Optional: true,
  4561  				},
  4562  				"blacklist": &Schema{
  4563  					Type:          TypeString,
  4564  					Optional:      true,
  4565  					ConflictsWith: []string{"whitelist"},
  4566  				},
  4567  			},
  4568  
  4569  			Config: map[string]interface{}{
  4570  				"whitelist": "white-val",
  4571  				"blacklist": "black-val",
  4572  			},
  4573  
  4574  			Err: true,
  4575  			Errors: []error{
  4576  				fmt.Errorf("\"blacklist\": conflicts with whitelist (\"white-val\")"),
  4577  			},
  4578  		},
  4579  
  4580  		"Required attribute & undefined conflicting optional are good": {
  4581  			Schema: map[string]*Schema{
  4582  				"required_att": &Schema{
  4583  					Type:     TypeString,
  4584  					Required: true,
  4585  				},
  4586  				"optional_att": &Schema{
  4587  					Type:          TypeString,
  4588  					Optional:      true,
  4589  					ConflictsWith: []string{"required_att"},
  4590  				},
  4591  			},
  4592  
  4593  			Config: map[string]interface{}{
  4594  				"required_att": "required-val",
  4595  			},
  4596  
  4597  			Err: false,
  4598  		},
  4599  
  4600  		"Required conflicting attribute & defined optional generate error": {
  4601  			Schema: map[string]*Schema{
  4602  				"required_att": &Schema{
  4603  					Type:     TypeString,
  4604  					Required: true,
  4605  				},
  4606  				"optional_att": &Schema{
  4607  					Type:          TypeString,
  4608  					Optional:      true,
  4609  					ConflictsWith: []string{"required_att"},
  4610  				},
  4611  			},
  4612  
  4613  			Config: map[string]interface{}{
  4614  				"required_att": "required-val",
  4615  				"optional_att": "optional-val",
  4616  			},
  4617  
  4618  			Err: true,
  4619  			Errors: []error{
  4620  				fmt.Errorf(`"optional_att": conflicts with required_att ("required-val")`),
  4621  			},
  4622  		},
  4623  
  4624  		"Computed + Optional fields conflicting with each other": {
  4625  			Schema: map[string]*Schema{
  4626  				"foo_att": &Schema{
  4627  					Type:          TypeString,
  4628  					Optional:      true,
  4629  					Computed:      true,
  4630  					ConflictsWith: []string{"bar_att"},
  4631  				},
  4632  				"bar_att": &Schema{
  4633  					Type:          TypeString,
  4634  					Optional:      true,
  4635  					Computed:      true,
  4636  					ConflictsWith: []string{"foo_att"},
  4637  				},
  4638  			},
  4639  
  4640  			Config: map[string]interface{}{
  4641  				"foo_att": "foo-val",
  4642  				"bar_att": "bar-val",
  4643  			},
  4644  
  4645  			Err: true,
  4646  			Errors: []error{
  4647  				fmt.Errorf(`"foo_att": conflicts with bar_att ("bar-val")`),
  4648  				fmt.Errorf(`"bar_att": conflicts with foo_att ("foo-val")`),
  4649  			},
  4650  		},
  4651  
  4652  		"Computed + Optional fields NOT conflicting with each other": {
  4653  			Schema: map[string]*Schema{
  4654  				"foo_att": &Schema{
  4655  					Type:          TypeString,
  4656  					Optional:      true,
  4657  					Computed:      true,
  4658  					ConflictsWith: []string{"bar_att"},
  4659  				},
  4660  				"bar_att": &Schema{
  4661  					Type:          TypeString,
  4662  					Optional:      true,
  4663  					Computed:      true,
  4664  					ConflictsWith: []string{"foo_att"},
  4665  				},
  4666  			},
  4667  
  4668  			Config: map[string]interface{}{
  4669  				"foo_att": "foo-val",
  4670  			},
  4671  
  4672  			Err: false,
  4673  		},
  4674  
  4675  		"Computed + Optional fields that conflict with none set": {
  4676  			Schema: map[string]*Schema{
  4677  				"foo_att": &Schema{
  4678  					Type:          TypeString,
  4679  					Optional:      true,
  4680  					Computed:      true,
  4681  					ConflictsWith: []string{"bar_att"},
  4682  				},
  4683  				"bar_att": &Schema{
  4684  					Type:          TypeString,
  4685  					Optional:      true,
  4686  					Computed:      true,
  4687  					ConflictsWith: []string{"foo_att"},
  4688  				},
  4689  			},
  4690  
  4691  			Config: map[string]interface{}{},
  4692  
  4693  			Err: false,
  4694  		},
  4695  
  4696  		"Good with ValidateFunc": {
  4697  			Schema: map[string]*Schema{
  4698  				"validate_me": &Schema{
  4699  					Type:     TypeString,
  4700  					Required: true,
  4701  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  4702  						return
  4703  					},
  4704  				},
  4705  			},
  4706  			Config: map[string]interface{}{
  4707  				"validate_me": "valid",
  4708  			},
  4709  			Err: false,
  4710  		},
  4711  
  4712  		"Bad with ValidateFunc": {
  4713  			Schema: map[string]*Schema{
  4714  				"validate_me": &Schema{
  4715  					Type:     TypeString,
  4716  					Required: true,
  4717  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  4718  						es = append(es, fmt.Errorf("something is not right here"))
  4719  						return
  4720  					},
  4721  				},
  4722  			},
  4723  			Config: map[string]interface{}{
  4724  				"validate_me": "invalid",
  4725  			},
  4726  			Err: true,
  4727  			Errors: []error{
  4728  				fmt.Errorf(`something is not right here`),
  4729  			},
  4730  		},
  4731  
  4732  		"ValidateFunc not called when type does not match": {
  4733  			Schema: map[string]*Schema{
  4734  				"number": &Schema{
  4735  					Type:     TypeInt,
  4736  					Required: true,
  4737  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  4738  						t.Fatalf("Should not have gotten validate call")
  4739  						return
  4740  					},
  4741  				},
  4742  			},
  4743  			Config: map[string]interface{}{
  4744  				"number": "NaN",
  4745  			},
  4746  			Err: true,
  4747  		},
  4748  
  4749  		"ValidateFunc gets decoded type": {
  4750  			Schema: map[string]*Schema{
  4751  				"maybe": &Schema{
  4752  					Type:     TypeBool,
  4753  					Required: true,
  4754  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  4755  						if _, ok := v.(bool); !ok {
  4756  							t.Fatalf("Expected bool, got: %#v", v)
  4757  						}
  4758  						return
  4759  					},
  4760  				},
  4761  			},
  4762  			Config: map[string]interface{}{
  4763  				"maybe": "true",
  4764  			},
  4765  		},
  4766  
  4767  		"ValidateFunc is not called with a computed value": {
  4768  			Schema: map[string]*Schema{
  4769  				"validate_me": &Schema{
  4770  					Type:     TypeString,
  4771  					Required: true,
  4772  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  4773  						es = append(es, fmt.Errorf("something is not right here"))
  4774  						return
  4775  					},
  4776  				},
  4777  			},
  4778  			Config: map[string]interface{}{
  4779  				"validate_me": "${var.foo}",
  4780  			},
  4781  			Vars: map[string]string{
  4782  				"var.foo": config.UnknownVariableValue,
  4783  			},
  4784  
  4785  			Err: false,
  4786  		},
  4787  
  4788  		"special timeouts field": {
  4789  			Schema: map[string]*Schema{
  4790  				"availability_zone": &Schema{
  4791  					Type:     TypeString,
  4792  					Optional: true,
  4793  					Computed: true,
  4794  					ForceNew: true,
  4795  				},
  4796  			},
  4797  
  4798  			Config: map[string]interface{}{
  4799  				TimeoutsConfigKey: "bar",
  4800  			},
  4801  
  4802  			Err: false,
  4803  		},
  4804  
  4805  		"invalid bool field": {
  4806  			Schema: map[string]*Schema{
  4807  				"bool_field": {
  4808  					Type:     TypeBool,
  4809  					Optional: true,
  4810  				},
  4811  			},
  4812  			Config: map[string]interface{}{
  4813  				"bool_field": "abcdef",
  4814  			},
  4815  			Err: true,
  4816  		},
  4817  		"invalid integer field": {
  4818  			Schema: map[string]*Schema{
  4819  				"integer_field": {
  4820  					Type:     TypeInt,
  4821  					Optional: true,
  4822  				},
  4823  			},
  4824  			Config: map[string]interface{}{
  4825  				"integer_field": "abcdef",
  4826  			},
  4827  			Err: true,
  4828  		},
  4829  		"invalid float field": {
  4830  			Schema: map[string]*Schema{
  4831  				"float_field": {
  4832  					Type:     TypeFloat,
  4833  					Optional: true,
  4834  				},
  4835  			},
  4836  			Config: map[string]interface{}{
  4837  				"float_field": "abcdef",
  4838  			},
  4839  			Err: true,
  4840  		},
  4841  
  4842  		// Invalid map values
  4843  		"invalid bool map value": {
  4844  			Schema: map[string]*Schema{
  4845  				"boolMap": &Schema{
  4846  					Type:     TypeMap,
  4847  					Elem:     TypeBool,
  4848  					Optional: true,
  4849  				},
  4850  			},
  4851  			Config: map[string]interface{}{
  4852  				"boolMap": map[string]interface{}{
  4853  					"boolField": "notbool",
  4854  				},
  4855  			},
  4856  			Err: true,
  4857  		},
  4858  		"invalid int map value": {
  4859  			Schema: map[string]*Schema{
  4860  				"intMap": &Schema{
  4861  					Type:     TypeMap,
  4862  					Elem:     TypeInt,
  4863  					Optional: true,
  4864  				},
  4865  			},
  4866  			Config: map[string]interface{}{
  4867  				"intMap": map[string]interface{}{
  4868  					"intField": "notInt",
  4869  				},
  4870  			},
  4871  			Err: true,
  4872  		},
  4873  		"invalid float map value": {
  4874  			Schema: map[string]*Schema{
  4875  				"floatMap": &Schema{
  4876  					Type:     TypeMap,
  4877  					Elem:     TypeFloat,
  4878  					Optional: true,
  4879  				},
  4880  			},
  4881  			Config: map[string]interface{}{
  4882  				"floatMap": map[string]interface{}{
  4883  					"floatField": "notFloat",
  4884  				},
  4885  			},
  4886  			Err: true,
  4887  		},
  4888  
  4889  		"map with positive validate function": {
  4890  			Schema: map[string]*Schema{
  4891  				"floatInt": &Schema{
  4892  					Type:     TypeMap,
  4893  					Elem:     TypeInt,
  4894  					Optional: true,
  4895  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  4896  						return
  4897  					},
  4898  				},
  4899  			},
  4900  			Config: map[string]interface{}{
  4901  				"floatInt": map[string]interface{}{
  4902  					"rightAnswer": "42",
  4903  					"tooMuch":     "43",
  4904  				},
  4905  			},
  4906  			Err: false,
  4907  		},
  4908  		"map with negative validate function": {
  4909  			Schema: map[string]*Schema{
  4910  				"floatInt": &Schema{
  4911  					Type:     TypeMap,
  4912  					Elem:     TypeInt,
  4913  					Optional: true,
  4914  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  4915  						es = append(es, fmt.Errorf("this is not fine"))
  4916  						return
  4917  					},
  4918  				},
  4919  			},
  4920  			Config: map[string]interface{}{
  4921  				"floatInt": map[string]interface{}{
  4922  					"rightAnswer": "42",
  4923  					"tooMuch":     "43",
  4924  				},
  4925  			},
  4926  			Err: true,
  4927  		},
  4928  
  4929  		// The Validation function should not see interpolation strings from
  4930  		// non-computed values.
  4931  		"set with partially computed list and map": {
  4932  			Schema: map[string]*Schema{
  4933  				"outer": &Schema{
  4934  					Type:     TypeSet,
  4935  					Optional: true,
  4936  					Computed: true,
  4937  					Elem: &Resource{
  4938  						Schema: map[string]*Schema{
  4939  							"list": &Schema{
  4940  								Type:     TypeList,
  4941  								Optional: true,
  4942  								Elem: &Schema{
  4943  									Type: TypeString,
  4944  									ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  4945  										if strings.HasPrefix(v.(string), "${") {
  4946  											es = append(es, fmt.Errorf("should not have interpolations"))
  4947  										}
  4948  										return
  4949  									},
  4950  								},
  4951  							},
  4952  						},
  4953  					},
  4954  				},
  4955  			},
  4956  			Config: map[string]interface{}{
  4957  				"outer": []map[string]interface{}{
  4958  					{
  4959  						"list": []interface{}{"${var.a}", "${var.b}", "c"},
  4960  					},
  4961  				},
  4962  			},
  4963  			Vars: map[string]string{
  4964  				"var.a": "A",
  4965  				"var.b": config.UnknownVariableValue,
  4966  			},
  4967  			Err: false,
  4968  		},
  4969  	}
  4970  
  4971  	for tn, tc := range cases {
  4972  		t.Run(tn, func(t *testing.T) {
  4973  			c, err := config.NewRawConfig(tc.Config)
  4974  			if err != nil {
  4975  				t.Fatalf("err: %s", err)
  4976  			}
  4977  			if tc.Vars != nil {
  4978  				vars := make(map[string]ast.Variable)
  4979  				for k, v := range tc.Vars {
  4980  					vars[k] = ast.Variable{Value: v, Type: ast.TypeString}
  4981  				}
  4982  
  4983  				if err := c.Interpolate(vars); err != nil {
  4984  					t.Fatalf("err: %s", err)
  4985  				}
  4986  			}
  4987  
  4988  			ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c))
  4989  			if len(es) > 0 != tc.Err {
  4990  				if len(es) == 0 {
  4991  					t.Errorf("%q: no errors", tn)
  4992  				}
  4993  
  4994  				for _, e := range es {
  4995  					t.Errorf("%q: err: %s", tn, e)
  4996  				}
  4997  
  4998  				t.FailNow()
  4999  			}
  5000  
  5001  			if !reflect.DeepEqual(ws, tc.Warnings) {
  5002  				t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws)
  5003  			}
  5004  
  5005  			if tc.Errors != nil {
  5006  				sort.Sort(errorSort(es))
  5007  				sort.Sort(errorSort(tc.Errors))
  5008  
  5009  				if !reflect.DeepEqual(es, tc.Errors) {
  5010  					t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es)
  5011  				}
  5012  			}
  5013  		})
  5014  
  5015  	}
  5016  }
  5017  
  5018  func TestSchemaSet_ValidateMaxItems(t *testing.T) {
  5019  	cases := map[string]struct {
  5020  		Schema          map[string]*Schema
  5021  		State           *terraform.InstanceState
  5022  		Config          map[string]interface{}
  5023  		ConfigVariables map[string]string
  5024  		Diff            *terraform.InstanceDiff
  5025  		Err             bool
  5026  		Errors          []error
  5027  	}{
  5028  		"#0": {
  5029  			Schema: map[string]*Schema{
  5030  				"aliases": &Schema{
  5031  					Type:     TypeSet,
  5032  					Optional: true,
  5033  					MaxItems: 1,
  5034  					Elem:     &Schema{Type: TypeString},
  5035  				},
  5036  			},
  5037  			State: nil,
  5038  			Config: map[string]interface{}{
  5039  				"aliases": []interface{}{"foo", "bar"},
  5040  			},
  5041  			Diff: nil,
  5042  			Err:  true,
  5043  			Errors: []error{
  5044  				fmt.Errorf("aliases: attribute supports 1 item maximum, config has 2 declared"),
  5045  			},
  5046  		},
  5047  		"#1": {
  5048  			Schema: map[string]*Schema{
  5049  				"aliases": &Schema{
  5050  					Type:     TypeSet,
  5051  					Optional: true,
  5052  					Elem:     &Schema{Type: TypeString},
  5053  				},
  5054  			},
  5055  			State: nil,
  5056  			Config: map[string]interface{}{
  5057  				"aliases": []interface{}{"foo", "bar"},
  5058  			},
  5059  			Diff:   nil,
  5060  			Err:    false,
  5061  			Errors: nil,
  5062  		},
  5063  		"#2": {
  5064  			Schema: map[string]*Schema{
  5065  				"aliases": &Schema{
  5066  					Type:     TypeSet,
  5067  					Optional: true,
  5068  					MaxItems: 1,
  5069  					Elem:     &Schema{Type: TypeString},
  5070  				},
  5071  			},
  5072  			State: nil,
  5073  			Config: map[string]interface{}{
  5074  				"aliases": []interface{}{"foo"},
  5075  			},
  5076  			Diff:   nil,
  5077  			Err:    false,
  5078  			Errors: nil,
  5079  		},
  5080  	}
  5081  
  5082  	for tn, tc := range cases {
  5083  		c, err := config.NewRawConfig(tc.Config)
  5084  		if err != nil {
  5085  			t.Fatalf("%q: err: %s", tn, err)
  5086  		}
  5087  		_, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c))
  5088  
  5089  		if len(es) > 0 != tc.Err {
  5090  			if len(es) == 0 {
  5091  				t.Errorf("%q: no errors", tn)
  5092  			}
  5093  
  5094  			for _, e := range es {
  5095  				t.Errorf("%q: err: %s", tn, e)
  5096  			}
  5097  
  5098  			t.FailNow()
  5099  		}
  5100  
  5101  		if tc.Errors != nil {
  5102  			if !reflect.DeepEqual(es, tc.Errors) {
  5103  				t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es)
  5104  			}
  5105  		}
  5106  	}
  5107  }
  5108  
  5109  func TestSchemaSet_ValidateMinItems(t *testing.T) {
  5110  	cases := map[string]struct {
  5111  		Schema          map[string]*Schema
  5112  		State           *terraform.InstanceState
  5113  		Config          map[string]interface{}
  5114  		ConfigVariables map[string]string
  5115  		Diff            *terraform.InstanceDiff
  5116  		Err             bool
  5117  		Errors          []error
  5118  	}{
  5119  		"#0": {
  5120  			Schema: map[string]*Schema{
  5121  				"aliases": &Schema{
  5122  					Type:     TypeSet,
  5123  					Optional: true,
  5124  					MinItems: 2,
  5125  					Elem:     &Schema{Type: TypeString},
  5126  				},
  5127  			},
  5128  			State: nil,
  5129  			Config: map[string]interface{}{
  5130  				"aliases": []interface{}{"foo", "bar"},
  5131  			},
  5132  			Diff:   nil,
  5133  			Err:    false,
  5134  			Errors: nil,
  5135  		},
  5136  		"#1": {
  5137  			Schema: map[string]*Schema{
  5138  				"aliases": &Schema{
  5139  					Type:     TypeSet,
  5140  					Optional: true,
  5141  					Elem:     &Schema{Type: TypeString},
  5142  				},
  5143  			},
  5144  			State: nil,
  5145  			Config: map[string]interface{}{
  5146  				"aliases": []interface{}{"foo", "bar"},
  5147  			},
  5148  			Diff:   nil,
  5149  			Err:    false,
  5150  			Errors: nil,
  5151  		},
  5152  		"#2": {
  5153  			Schema: map[string]*Schema{
  5154  				"aliases": &Schema{
  5155  					Type:     TypeSet,
  5156  					Optional: true,
  5157  					MinItems: 2,
  5158  					Elem:     &Schema{Type: TypeString},
  5159  				},
  5160  			},
  5161  			State: nil,
  5162  			Config: map[string]interface{}{
  5163  				"aliases": []interface{}{"foo"},
  5164  			},
  5165  			Diff: nil,
  5166  			Err:  true,
  5167  			Errors: []error{
  5168  				fmt.Errorf("aliases: attribute supports 2 item as a minimum, config has 1 declared"),
  5169  			},
  5170  		},
  5171  	}
  5172  
  5173  	for tn, tc := range cases {
  5174  		c, err := config.NewRawConfig(tc.Config)
  5175  		if err != nil {
  5176  			t.Fatalf("%q: err: %s", tn, err)
  5177  		}
  5178  		_, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c))
  5179  
  5180  		if len(es) > 0 != tc.Err {
  5181  			if len(es) == 0 {
  5182  				t.Errorf("%q: no errors", tn)
  5183  			}
  5184  
  5185  			for _, e := range es {
  5186  				t.Errorf("%q: err: %s", tn, e)
  5187  			}
  5188  
  5189  			t.FailNow()
  5190  		}
  5191  
  5192  		if tc.Errors != nil {
  5193  			if !reflect.DeepEqual(es, tc.Errors) {
  5194  				t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es)
  5195  			}
  5196  		}
  5197  	}
  5198  }
  5199  
  5200  // errorSort implements sort.Interface to sort errors by their error message
  5201  type errorSort []error
  5202  
  5203  func (e errorSort) Len() int      { return len(e) }
  5204  func (e errorSort) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
  5205  func (e errorSort) Less(i, j int) bool {
  5206  	return e[i].Error() < e[j].Error()
  5207  }