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