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