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