github.com/richardbowden/terraform@v0.6.12-0.20160901200758-30ea22c25211/helper/schema/schema_test.go (about)

     1  package schema
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"os"
     7  	"reflect"
     8  	"strconv"
     9  	"testing"
    10  
    11  	"github.com/hashicorp/hil"
    12  	"github.com/hashicorp/hil/ast"
    13  	"github.com/hashicorp/terraform/config"
    14  	"github.com/hashicorp/terraform/helper/hashcode"
    15  	"github.com/hashicorp/terraform/terraform"
    16  )
    17  
    18  func TestEnvDefaultFunc(t *testing.T) {
    19  	key := "TF_TEST_ENV_DEFAULT_FUNC"
    20  	defer os.Unsetenv(key)
    21  
    22  	f := EnvDefaultFunc(key, "42")
    23  	if err := os.Setenv(key, "foo"); err != nil {
    24  		t.Fatalf("err: %s", err)
    25  	}
    26  
    27  	actual, err := f()
    28  	if err != nil {
    29  		t.Fatalf("err: %s", err)
    30  	}
    31  	if actual != "foo" {
    32  		t.Fatalf("bad: %#v", actual)
    33  	}
    34  
    35  	if err := os.Unsetenv(key); err != nil {
    36  		t.Fatalf("err: %s", err)
    37  	}
    38  
    39  	actual, err = f()
    40  	if err != nil {
    41  		t.Fatalf("err: %s", err)
    42  	}
    43  	if actual != "42" {
    44  		t.Fatalf("bad: %#v", actual)
    45  	}
    46  }
    47  
    48  func TestMultiEnvDefaultFunc(t *testing.T) {
    49  	keys := []string{
    50  		"TF_TEST_MULTI_ENV_DEFAULT_FUNC1",
    51  		"TF_TEST_MULTI_ENV_DEFAULT_FUNC2",
    52  	}
    53  	defer func() {
    54  		for _, k := range keys {
    55  			os.Unsetenv(k)
    56  		}
    57  	}()
    58  
    59  	// Test that the first key is returned first
    60  	f := MultiEnvDefaultFunc(keys, "42")
    61  	if err := os.Setenv(keys[0], "foo"); err != nil {
    62  		t.Fatalf("err: %s", err)
    63  	}
    64  
    65  	actual, err := f()
    66  	if err != nil {
    67  		t.Fatalf("err: %s", err)
    68  	}
    69  	if actual != "foo" {
    70  		t.Fatalf("bad: %#v", actual)
    71  	}
    72  
    73  	if err := os.Unsetenv(keys[0]); err != nil {
    74  		t.Fatalf("err: %s", err)
    75  	}
    76  
    77  	// Test that the second key is returned if the first one is empty
    78  	f = MultiEnvDefaultFunc(keys, "42")
    79  	if err := os.Setenv(keys[1], "foo"); err != nil {
    80  		t.Fatalf("err: %s", err)
    81  	}
    82  
    83  	actual, err = f()
    84  	if err != nil {
    85  		t.Fatalf("err: %s", err)
    86  	}
    87  	if actual != "foo" {
    88  		t.Fatalf("bad: %#v", actual)
    89  	}
    90  
    91  	if err := os.Unsetenv(keys[1]); err != nil {
    92  		t.Fatalf("err: %s", err)
    93  	}
    94  
    95  	// Test that the default value is returned when no keys are set
    96  	actual, err = f()
    97  	if err != nil {
    98  		t.Fatalf("err: %s", err)
    99  	}
   100  	if actual != "42" {
   101  		t.Fatalf("bad: %#v", actual)
   102  	}
   103  }
   104  
   105  func TestValueType_Zero(t *testing.T) {
   106  	cases := []struct {
   107  		Type  ValueType
   108  		Value interface{}
   109  	}{
   110  		{TypeBool, false},
   111  		{TypeInt, 0},
   112  		{TypeFloat, 0.0},
   113  		{TypeString, ""},
   114  		{TypeList, []interface{}{}},
   115  		{TypeMap, map[string]interface{}{}},
   116  		{TypeSet, new(Set)},
   117  	}
   118  
   119  	for i, tc := range cases {
   120  		actual := tc.Type.Zero()
   121  		if !reflect.DeepEqual(actual, tc.Value) {
   122  			t.Fatalf("%d: %#v != %#v", i, actual, tc.Value)
   123  		}
   124  	}
   125  }
   126  
   127  func interfaceToVariableSwallowError(input interface{}) ast.Variable {
   128  	variable, _ := hil.InterfaceToVariable(input)
   129  	return variable
   130  }
   131  
   132  func TestSchemaMap_Diff(t *testing.T) {
   133  	cases := map[string]struct {
   134  		Schema          map[string]*Schema
   135  		State           *terraform.InstanceState
   136  		Config          map[string]interface{}
   137  		ConfigVariables map[string]ast.Variable
   138  		Diff            *terraform.InstanceDiff
   139  		Err             bool
   140  	}{
   141  		"#0": {
   142  			Schema: map[string]*Schema{
   143  				"availability_zone": &Schema{
   144  					Type:     TypeString,
   145  					Optional: true,
   146  					Computed: true,
   147  					ForceNew: true,
   148  				},
   149  			},
   150  
   151  			State: nil,
   152  
   153  			Config: map[string]interface{}{
   154  				"availability_zone": "foo",
   155  			},
   156  
   157  			Diff: &terraform.InstanceDiff{
   158  				Attributes: map[string]*terraform.ResourceAttrDiff{
   159  					"availability_zone": &terraform.ResourceAttrDiff{
   160  						Old:         "",
   161  						New:         "foo",
   162  						RequiresNew: true,
   163  					},
   164  				},
   165  			},
   166  
   167  			Err: false,
   168  		},
   169  
   170  		"#1": {
   171  			Schema: map[string]*Schema{
   172  				"availability_zone": &Schema{
   173  					Type:     TypeString,
   174  					Optional: true,
   175  					Computed: true,
   176  					ForceNew: true,
   177  				},
   178  			},
   179  
   180  			State: nil,
   181  
   182  			Config: map[string]interface{}{},
   183  
   184  			Diff: &terraform.InstanceDiff{
   185  				Attributes: map[string]*terraform.ResourceAttrDiff{
   186  					"availability_zone": &terraform.ResourceAttrDiff{
   187  						Old:         "",
   188  						NewComputed: true,
   189  						RequiresNew: true,
   190  					},
   191  				},
   192  			},
   193  
   194  			Err: false,
   195  		},
   196  
   197  		"#2": {
   198  			Schema: map[string]*Schema{
   199  				"availability_zone": &Schema{
   200  					Type:     TypeString,
   201  					Optional: true,
   202  					Computed: true,
   203  					ForceNew: true,
   204  				},
   205  			},
   206  
   207  			State: &terraform.InstanceState{
   208  				ID: "foo",
   209  			},
   210  
   211  			Config: map[string]interface{}{},
   212  
   213  			Diff: nil,
   214  
   215  			Err: false,
   216  		},
   217  
   218  		"#3 Computed, but set in config": {
   219  			Schema: map[string]*Schema{
   220  				"availability_zone": &Schema{
   221  					Type:     TypeString,
   222  					Optional: true,
   223  					Computed: true,
   224  				},
   225  			},
   226  
   227  			State: &terraform.InstanceState{
   228  				Attributes: map[string]string{
   229  					"availability_zone": "foo",
   230  				},
   231  			},
   232  
   233  			Config: map[string]interface{}{
   234  				"availability_zone": "bar",
   235  			},
   236  
   237  			Diff: &terraform.InstanceDiff{
   238  				Attributes: map[string]*terraform.ResourceAttrDiff{
   239  					"availability_zone": &terraform.ResourceAttrDiff{
   240  						Old: "foo",
   241  						New: "bar",
   242  					},
   243  				},
   244  			},
   245  
   246  			Err: false,
   247  		},
   248  
   249  		"#4 Default": {
   250  			Schema: map[string]*Schema{
   251  				"availability_zone": &Schema{
   252  					Type:     TypeString,
   253  					Optional: true,
   254  					Default:  "foo",
   255  				},
   256  			},
   257  
   258  			State: nil,
   259  
   260  			Config: nil,
   261  
   262  			Diff: &terraform.InstanceDiff{
   263  				Attributes: map[string]*terraform.ResourceAttrDiff{
   264  					"availability_zone": &terraform.ResourceAttrDiff{
   265  						Old: "",
   266  						New: "foo",
   267  					},
   268  				},
   269  			},
   270  
   271  			Err: false,
   272  		},
   273  
   274  		"#5 DefaultFunc, value": {
   275  			Schema: map[string]*Schema{
   276  				"availability_zone": &Schema{
   277  					Type:     TypeString,
   278  					Optional: true,
   279  					DefaultFunc: func() (interface{}, error) {
   280  						return "foo", nil
   281  					},
   282  				},
   283  			},
   284  
   285  			State: nil,
   286  
   287  			Config: nil,
   288  
   289  			Diff: &terraform.InstanceDiff{
   290  				Attributes: map[string]*terraform.ResourceAttrDiff{
   291  					"availability_zone": &terraform.ResourceAttrDiff{
   292  						Old: "",
   293  						New: "foo",
   294  					},
   295  				},
   296  			},
   297  
   298  			Err: false,
   299  		},
   300  
   301  		"#6 DefaultFunc, configuration set": {
   302  			Schema: map[string]*Schema{
   303  				"availability_zone": &Schema{
   304  					Type:     TypeString,
   305  					Optional: true,
   306  					DefaultFunc: func() (interface{}, error) {
   307  						return "foo", nil
   308  					},
   309  				},
   310  			},
   311  
   312  			State: nil,
   313  
   314  			Config: map[string]interface{}{
   315  				"availability_zone": "bar",
   316  			},
   317  
   318  			Diff: &terraform.InstanceDiff{
   319  				Attributes: map[string]*terraform.ResourceAttrDiff{
   320  					"availability_zone": &terraform.ResourceAttrDiff{
   321  						Old: "",
   322  						New: "bar",
   323  					},
   324  				},
   325  			},
   326  
   327  			Err: false,
   328  		},
   329  
   330  		"String with StateFunc": {
   331  			Schema: map[string]*Schema{
   332  				"availability_zone": &Schema{
   333  					Type:     TypeString,
   334  					Optional: true,
   335  					Computed: true,
   336  					StateFunc: func(a interface{}) string {
   337  						return a.(string) + "!"
   338  					},
   339  				},
   340  			},
   341  
   342  			State: nil,
   343  
   344  			Config: map[string]interface{}{
   345  				"availability_zone": "foo",
   346  			},
   347  
   348  			Diff: &terraform.InstanceDiff{
   349  				Attributes: map[string]*terraform.ResourceAttrDiff{
   350  					"availability_zone": &terraform.ResourceAttrDiff{
   351  						Old:      "",
   352  						New:      "foo!",
   353  						NewExtra: "foo",
   354  					},
   355  				},
   356  			},
   357  
   358  			Err: false,
   359  		},
   360  
   361  		"StateFunc not called with nil value": {
   362  			Schema: map[string]*Schema{
   363  				"availability_zone": &Schema{
   364  					Type:     TypeString,
   365  					Optional: true,
   366  					Computed: true,
   367  					StateFunc: func(a interface{}) string {
   368  						t.Fatalf("should not get here!")
   369  						return ""
   370  					},
   371  				},
   372  			},
   373  
   374  			State: nil,
   375  
   376  			Config: map[string]interface{}{},
   377  
   378  			Diff: &terraform.InstanceDiff{
   379  				Attributes: map[string]*terraform.ResourceAttrDiff{
   380  					"availability_zone": &terraform.ResourceAttrDiff{
   381  						Old:         "",
   382  						New:         "",
   383  						NewComputed: true,
   384  					},
   385  				},
   386  			},
   387  
   388  			Err: false,
   389  		},
   390  
   391  		"#8 Variable (just checking)": {
   392  			Schema: map[string]*Schema{
   393  				"availability_zone": &Schema{
   394  					Type:     TypeString,
   395  					Optional: true,
   396  				},
   397  			},
   398  
   399  			State: nil,
   400  
   401  			Config: map[string]interface{}{
   402  				"availability_zone": "${var.foo}",
   403  			},
   404  
   405  			ConfigVariables: map[string]ast.Variable{
   406  				"var.foo": interfaceToVariableSwallowError("bar"),
   407  			},
   408  
   409  			Diff: &terraform.InstanceDiff{
   410  				Attributes: map[string]*terraform.ResourceAttrDiff{
   411  					"availability_zone": &terraform.ResourceAttrDiff{
   412  						Old: "",
   413  						New: "bar",
   414  					},
   415  				},
   416  			},
   417  
   418  			Err: false,
   419  		},
   420  
   421  		"#9 Variable computed": {
   422  			Schema: map[string]*Schema{
   423  				"availability_zone": &Schema{
   424  					Type:     TypeString,
   425  					Optional: true,
   426  				},
   427  			},
   428  
   429  			State: nil,
   430  
   431  			Config: map[string]interface{}{
   432  				"availability_zone": "${var.foo}",
   433  			},
   434  
   435  			ConfigVariables: map[string]ast.Variable{
   436  				"var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue),
   437  			},
   438  
   439  			Diff: &terraform.InstanceDiff{
   440  				Attributes: map[string]*terraform.ResourceAttrDiff{
   441  					"availability_zone": &terraform.ResourceAttrDiff{
   442  						Old: "",
   443  						New: "${var.foo}",
   444  					},
   445  				},
   446  			},
   447  
   448  			Err: false,
   449  		},
   450  
   451  		"#10 Int decode": {
   452  			Schema: map[string]*Schema{
   453  				"port": &Schema{
   454  					Type:     TypeInt,
   455  					Optional: true,
   456  					Computed: true,
   457  					ForceNew: true,
   458  				},
   459  			},
   460  
   461  			State: nil,
   462  
   463  			Config: map[string]interface{}{
   464  				"port": 27,
   465  			},
   466  
   467  			Diff: &terraform.InstanceDiff{
   468  				Attributes: map[string]*terraform.ResourceAttrDiff{
   469  					"port": &terraform.ResourceAttrDiff{
   470  						Old:         "",
   471  						New:         "27",
   472  						RequiresNew: true,
   473  					},
   474  				},
   475  			},
   476  
   477  			Err: false,
   478  		},
   479  
   480  		"#11 bool decode": {
   481  			Schema: map[string]*Schema{
   482  				"port": &Schema{
   483  					Type:     TypeBool,
   484  					Optional: true,
   485  					Computed: true,
   486  					ForceNew: true,
   487  				},
   488  			},
   489  
   490  			State: nil,
   491  
   492  			Config: map[string]interface{}{
   493  				"port": false,
   494  			},
   495  
   496  			Diff: &terraform.InstanceDiff{
   497  				Attributes: map[string]*terraform.ResourceAttrDiff{
   498  					"port": &terraform.ResourceAttrDiff{
   499  						Old:         "",
   500  						New:         "false",
   501  						RequiresNew: true,
   502  					},
   503  				},
   504  			},
   505  
   506  			Err: false,
   507  		},
   508  
   509  		"#12 Bool": {
   510  			Schema: map[string]*Schema{
   511  				"delete": &Schema{
   512  					Type:     TypeBool,
   513  					Optional: true,
   514  					Default:  false,
   515  				},
   516  			},
   517  
   518  			State: &terraform.InstanceState{
   519  				Attributes: map[string]string{
   520  					"delete": "false",
   521  				},
   522  			},
   523  
   524  			Config: nil,
   525  
   526  			Diff: nil,
   527  
   528  			Err: false,
   529  		},
   530  
   531  		"#13 List decode": {
   532  			Schema: map[string]*Schema{
   533  				"ports": &Schema{
   534  					Type:     TypeList,
   535  					Required: true,
   536  					Elem:     &Schema{Type: TypeInt},
   537  				},
   538  			},
   539  
   540  			State: nil,
   541  
   542  			Config: map[string]interface{}{
   543  				"ports": []interface{}{1, 2, 5},
   544  			},
   545  
   546  			Diff: &terraform.InstanceDiff{
   547  				Attributes: map[string]*terraform.ResourceAttrDiff{
   548  					"ports.#": &terraform.ResourceAttrDiff{
   549  						Old: "0",
   550  						New: "3",
   551  					},
   552  					"ports.0": &terraform.ResourceAttrDiff{
   553  						Old: "",
   554  						New: "1",
   555  					},
   556  					"ports.1": &terraform.ResourceAttrDiff{
   557  						Old: "",
   558  						New: "2",
   559  					},
   560  					"ports.2": &terraform.ResourceAttrDiff{
   561  						Old: "",
   562  						New: "5",
   563  					},
   564  				},
   565  			},
   566  
   567  			Err: false,
   568  		},
   569  
   570  		"#14": {
   571  			Schema: map[string]*Schema{
   572  				"ports": &Schema{
   573  					Type:     TypeList,
   574  					Required: true,
   575  					Elem:     &Schema{Type: TypeInt},
   576  				},
   577  			},
   578  
   579  			State: nil,
   580  
   581  			Config: map[string]interface{}{
   582  				"ports": []interface{}{1, "${var.foo}"},
   583  			},
   584  
   585  			ConfigVariables: map[string]ast.Variable{
   586  				"var.foo": interfaceToVariableSwallowError([]interface{}{"2", "5"}),
   587  			},
   588  
   589  			Diff: &terraform.InstanceDiff{
   590  				Attributes: map[string]*terraform.ResourceAttrDiff{
   591  					"ports.#": &terraform.ResourceAttrDiff{
   592  						Old: "0",
   593  						New: "3",
   594  					},
   595  					"ports.0": &terraform.ResourceAttrDiff{
   596  						Old: "",
   597  						New: "1",
   598  					},
   599  					"ports.1": &terraform.ResourceAttrDiff{
   600  						Old: "",
   601  						New: "2",
   602  					},
   603  					"ports.2": &terraform.ResourceAttrDiff{
   604  						Old: "",
   605  						New: "5",
   606  					},
   607  				},
   608  			},
   609  
   610  			Err: false,
   611  		},
   612  
   613  		"#15": {
   614  			Schema: map[string]*Schema{
   615  				"ports": &Schema{
   616  					Type:     TypeList,
   617  					Required: true,
   618  					Elem:     &Schema{Type: TypeInt},
   619  				},
   620  			},
   621  
   622  			State: nil,
   623  
   624  			Config: map[string]interface{}{
   625  				"ports": []interface{}{1, "${var.foo}"},
   626  			},
   627  
   628  			ConfigVariables: map[string]ast.Variable{
   629  				"var.foo": interfaceToVariableSwallowError([]interface{}{
   630  					config.UnknownVariableValue, "5"}),
   631  			},
   632  
   633  			Diff: &terraform.InstanceDiff{
   634  				Attributes: map[string]*terraform.ResourceAttrDiff{
   635  					"ports.#": &terraform.ResourceAttrDiff{
   636  						Old:         "0",
   637  						New:         "",
   638  						NewComputed: true,
   639  					},
   640  				},
   641  			},
   642  
   643  			Err: false,
   644  		},
   645  
   646  		"#16": {
   647  			Schema: map[string]*Schema{
   648  				"ports": &Schema{
   649  					Type:     TypeList,
   650  					Required: true,
   651  					Elem:     &Schema{Type: TypeInt},
   652  				},
   653  			},
   654  
   655  			State: &terraform.InstanceState{
   656  				Attributes: map[string]string{
   657  					"ports.#": "3",
   658  					"ports.0": "1",
   659  					"ports.1": "2",
   660  					"ports.2": "5",
   661  				},
   662  			},
   663  
   664  			Config: map[string]interface{}{
   665  				"ports": []interface{}{1, 2, 5},
   666  			},
   667  
   668  			Diff: nil,
   669  
   670  			Err: false,
   671  		},
   672  
   673  		"#17": {
   674  			Schema: map[string]*Schema{
   675  				"ports": &Schema{
   676  					Type:     TypeList,
   677  					Required: true,
   678  					Elem:     &Schema{Type: TypeInt},
   679  				},
   680  			},
   681  
   682  			State: &terraform.InstanceState{
   683  				Attributes: map[string]string{
   684  					"ports.#": "2",
   685  					"ports.0": "1",
   686  					"ports.1": "2",
   687  				},
   688  			},
   689  
   690  			Config: map[string]interface{}{
   691  				"ports": []interface{}{1, 2, 5},
   692  			},
   693  
   694  			Diff: &terraform.InstanceDiff{
   695  				Attributes: map[string]*terraform.ResourceAttrDiff{
   696  					"ports.#": &terraform.ResourceAttrDiff{
   697  						Old: "2",
   698  						New: "3",
   699  					},
   700  					"ports.2": &terraform.ResourceAttrDiff{
   701  						Old: "",
   702  						New: "5",
   703  					},
   704  				},
   705  			},
   706  
   707  			Err: false,
   708  		},
   709  
   710  		"#18": {
   711  			Schema: map[string]*Schema{
   712  				"ports": &Schema{
   713  					Type:     TypeList,
   714  					Required: true,
   715  					Elem:     &Schema{Type: TypeInt},
   716  					ForceNew: true,
   717  				},
   718  			},
   719  
   720  			State: nil,
   721  
   722  			Config: map[string]interface{}{
   723  				"ports": []interface{}{1, 2, 5},
   724  			},
   725  
   726  			Diff: &terraform.InstanceDiff{
   727  				Attributes: map[string]*terraform.ResourceAttrDiff{
   728  					"ports.#": &terraform.ResourceAttrDiff{
   729  						Old:         "0",
   730  						New:         "3",
   731  						RequiresNew: true,
   732  					},
   733  					"ports.0": &terraform.ResourceAttrDiff{
   734  						Old:         "",
   735  						New:         "1",
   736  						RequiresNew: true,
   737  					},
   738  					"ports.1": &terraform.ResourceAttrDiff{
   739  						Old:         "",
   740  						New:         "2",
   741  						RequiresNew: true,
   742  					},
   743  					"ports.2": &terraform.ResourceAttrDiff{
   744  						Old:         "",
   745  						New:         "5",
   746  						RequiresNew: true,
   747  					},
   748  				},
   749  			},
   750  
   751  			Err: false,
   752  		},
   753  
   754  		"#19": {
   755  			Schema: map[string]*Schema{
   756  				"ports": &Schema{
   757  					Type:     TypeList,
   758  					Optional: true,
   759  					Computed: true,
   760  					Elem:     &Schema{Type: TypeInt},
   761  				},
   762  			},
   763  
   764  			State: nil,
   765  
   766  			Config: map[string]interface{}{},
   767  
   768  			Diff: &terraform.InstanceDiff{
   769  				Attributes: map[string]*terraform.ResourceAttrDiff{
   770  					"ports.#": &terraform.ResourceAttrDiff{
   771  						Old:         "",
   772  						NewComputed: true,
   773  					},
   774  				},
   775  			},
   776  
   777  			Err: false,
   778  		},
   779  
   780  		"#20 Set": {
   781  			Schema: map[string]*Schema{
   782  				"ports": &Schema{
   783  					Type:     TypeSet,
   784  					Required: true,
   785  					Elem:     &Schema{Type: TypeInt},
   786  					Set: func(a interface{}) int {
   787  						return a.(int)
   788  					},
   789  				},
   790  			},
   791  
   792  			State: nil,
   793  
   794  			Config: map[string]interface{}{
   795  				"ports": []interface{}{5, 2, 1},
   796  			},
   797  
   798  			Diff: &terraform.InstanceDiff{
   799  				Attributes: map[string]*terraform.ResourceAttrDiff{
   800  					"ports.#": &terraform.ResourceAttrDiff{
   801  						Old: "0",
   802  						New: "3",
   803  					},
   804  					"ports.1": &terraform.ResourceAttrDiff{
   805  						Old: "",
   806  						New: "1",
   807  					},
   808  					"ports.2": &terraform.ResourceAttrDiff{
   809  						Old: "",
   810  						New: "2",
   811  					},
   812  					"ports.5": &terraform.ResourceAttrDiff{
   813  						Old: "",
   814  						New: "5",
   815  					},
   816  				},
   817  			},
   818  
   819  			Err: false,
   820  		},
   821  
   822  		"#21 Set": {
   823  			Schema: map[string]*Schema{
   824  				"ports": &Schema{
   825  					Type:     TypeSet,
   826  					Computed: true,
   827  					Required: true,
   828  					Elem:     &Schema{Type: TypeInt},
   829  					Set: func(a interface{}) int {
   830  						return a.(int)
   831  					},
   832  				},
   833  			},
   834  
   835  			State: &terraform.InstanceState{
   836  				Attributes: map[string]string{
   837  					"ports.#": "0",
   838  				},
   839  			},
   840  
   841  			Config: nil,
   842  
   843  			Diff: nil,
   844  
   845  			Err: false,
   846  		},
   847  
   848  		"#22 Set": {
   849  			Schema: map[string]*Schema{
   850  				"ports": &Schema{
   851  					Type:     TypeSet,
   852  					Optional: true,
   853  					Computed: true,
   854  					Elem:     &Schema{Type: TypeInt},
   855  					Set: func(a interface{}) int {
   856  						return a.(int)
   857  					},
   858  				},
   859  			},
   860  
   861  			State: nil,
   862  
   863  			Config: nil,
   864  
   865  			Diff: &terraform.InstanceDiff{
   866  				Attributes: map[string]*terraform.ResourceAttrDiff{
   867  					"ports.#": &terraform.ResourceAttrDiff{
   868  						Old:         "",
   869  						NewComputed: true,
   870  					},
   871  				},
   872  			},
   873  
   874  			Err: false,
   875  		},
   876  
   877  		"#23 Set": {
   878  			Schema: map[string]*Schema{
   879  				"ports": &Schema{
   880  					Type:     TypeSet,
   881  					Required: true,
   882  					Elem:     &Schema{Type: TypeInt},
   883  					Set: func(a interface{}) int {
   884  						return a.(int)
   885  					},
   886  				},
   887  			},
   888  
   889  			State: nil,
   890  
   891  			Config: map[string]interface{}{
   892  				"ports": []interface{}{"${var.foo}", 1},
   893  			},
   894  
   895  			ConfigVariables: map[string]ast.Variable{
   896  				"var.foo": interfaceToVariableSwallowError([]interface{}{"2", "5"}),
   897  			},
   898  
   899  			Diff: &terraform.InstanceDiff{
   900  				Attributes: map[string]*terraform.ResourceAttrDiff{
   901  					"ports.#": &terraform.ResourceAttrDiff{
   902  						Old: "0",
   903  						New: "3",
   904  					},
   905  					"ports.1": &terraform.ResourceAttrDiff{
   906  						Old: "",
   907  						New: "1",
   908  					},
   909  					"ports.2": &terraform.ResourceAttrDiff{
   910  						Old: "",
   911  						New: "2",
   912  					},
   913  					"ports.5": &terraform.ResourceAttrDiff{
   914  						Old: "",
   915  						New: "5",
   916  					},
   917  				},
   918  			},
   919  
   920  			Err: false,
   921  		},
   922  
   923  		"#24 Set": {
   924  			Schema: map[string]*Schema{
   925  				"ports": &Schema{
   926  					Type:     TypeSet,
   927  					Required: true,
   928  					Elem:     &Schema{Type: TypeInt},
   929  					Set: func(a interface{}) int {
   930  						return a.(int)
   931  					},
   932  				},
   933  			},
   934  
   935  			State: nil,
   936  
   937  			Config: map[string]interface{}{
   938  				"ports": []interface{}{1, "${var.foo}"},
   939  			},
   940  
   941  			ConfigVariables: map[string]ast.Variable{
   942  				"var.foo": interfaceToVariableSwallowError([]interface{}{
   943  					config.UnknownVariableValue, "5"}),
   944  			},
   945  
   946  			Diff: &terraform.InstanceDiff{
   947  				Attributes: map[string]*terraform.ResourceAttrDiff{
   948  					"ports.#": &terraform.ResourceAttrDiff{
   949  						Old:         "",
   950  						New:         "",
   951  						NewComputed: true,
   952  					},
   953  				},
   954  			},
   955  
   956  			Err: false,
   957  		},
   958  
   959  		"#25 Set": {
   960  			Schema: map[string]*Schema{
   961  				"ports": &Schema{
   962  					Type:     TypeSet,
   963  					Required: true,
   964  					Elem:     &Schema{Type: TypeInt},
   965  					Set: func(a interface{}) int {
   966  						return a.(int)
   967  					},
   968  				},
   969  			},
   970  
   971  			State: &terraform.InstanceState{
   972  				Attributes: map[string]string{
   973  					"ports.#": "2",
   974  					"ports.1": "1",
   975  					"ports.2": "2",
   976  				},
   977  			},
   978  
   979  			Config: map[string]interface{}{
   980  				"ports": []interface{}{5, 2, 1},
   981  			},
   982  
   983  			Diff: &terraform.InstanceDiff{
   984  				Attributes: map[string]*terraform.ResourceAttrDiff{
   985  					"ports.#": &terraform.ResourceAttrDiff{
   986  						Old: "2",
   987  						New: "3",
   988  					},
   989  					"ports.1": &terraform.ResourceAttrDiff{
   990  						Old: "1",
   991  						New: "1",
   992  					},
   993  					"ports.2": &terraform.ResourceAttrDiff{
   994  						Old: "2",
   995  						New: "2",
   996  					},
   997  					"ports.5": &terraform.ResourceAttrDiff{
   998  						Old: "",
   999  						New: "5",
  1000  					},
  1001  				},
  1002  			},
  1003  
  1004  			Err: false,
  1005  		},
  1006  
  1007  		"#26 Set": {
  1008  			Schema: map[string]*Schema{
  1009  				"ports": &Schema{
  1010  					Type:     TypeSet,
  1011  					Required: true,
  1012  					Elem:     &Schema{Type: TypeInt},
  1013  					Set: func(a interface{}) int {
  1014  						return a.(int)
  1015  					},
  1016  				},
  1017  			},
  1018  
  1019  			State: &terraform.InstanceState{
  1020  				Attributes: map[string]string{
  1021  					"ports.#": "2",
  1022  					"ports.1": "1",
  1023  					"ports.2": "2",
  1024  				},
  1025  			},
  1026  
  1027  			Config: map[string]interface{}{},
  1028  
  1029  			Diff: &terraform.InstanceDiff{
  1030  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1031  					"ports.#": &terraform.ResourceAttrDiff{
  1032  						Old: "2",
  1033  						New: "0",
  1034  					},
  1035  					"ports.1": &terraform.ResourceAttrDiff{
  1036  						Old:        "1",
  1037  						New:        "0",
  1038  						NewRemoved: true,
  1039  					},
  1040  					"ports.2": &terraform.ResourceAttrDiff{
  1041  						Old:        "2",
  1042  						New:        "0",
  1043  						NewRemoved: true,
  1044  					},
  1045  				},
  1046  			},
  1047  
  1048  			Err: false,
  1049  		},
  1050  
  1051  		"#27 Set": {
  1052  			Schema: map[string]*Schema{
  1053  				"ports": &Schema{
  1054  					Type:     TypeSet,
  1055  					Optional: true,
  1056  					Computed: true,
  1057  					Elem:     &Schema{Type: TypeInt},
  1058  					Set: func(a interface{}) int {
  1059  						return a.(int)
  1060  					},
  1061  				},
  1062  			},
  1063  
  1064  			State: &terraform.InstanceState{
  1065  				Attributes: map[string]string{
  1066  					"availability_zone": "bar",
  1067  					"ports.#":           "1",
  1068  					"ports.80":          "80",
  1069  				},
  1070  			},
  1071  
  1072  			Config: map[string]interface{}{},
  1073  
  1074  			Diff: nil,
  1075  
  1076  			Err: false,
  1077  		},
  1078  
  1079  		"#28 Set": {
  1080  			Schema: map[string]*Schema{
  1081  				"ingress": &Schema{
  1082  					Type:     TypeSet,
  1083  					Required: true,
  1084  					Elem: &Resource{
  1085  						Schema: map[string]*Schema{
  1086  							"ports": &Schema{
  1087  								Type:     TypeList,
  1088  								Optional: true,
  1089  								Elem:     &Schema{Type: TypeInt},
  1090  							},
  1091  						},
  1092  					},
  1093  					Set: func(v interface{}) int {
  1094  						m := v.(map[string]interface{})
  1095  						ps := m["ports"].([]interface{})
  1096  						result := 0
  1097  						for _, p := range ps {
  1098  							result += p.(int)
  1099  						}
  1100  						return result
  1101  					},
  1102  				},
  1103  			},
  1104  
  1105  			State: &terraform.InstanceState{
  1106  				Attributes: map[string]string{
  1107  					"ingress.#":           "2",
  1108  					"ingress.80.ports.#":  "1",
  1109  					"ingress.80.ports.0":  "80",
  1110  					"ingress.443.ports.#": "1",
  1111  					"ingress.443.ports.0": "443",
  1112  				},
  1113  			},
  1114  
  1115  			Config: map[string]interface{}{
  1116  				"ingress": []map[string]interface{}{
  1117  					map[string]interface{}{
  1118  						"ports": []interface{}{443},
  1119  					},
  1120  					map[string]interface{}{
  1121  						"ports": []interface{}{80},
  1122  					},
  1123  				},
  1124  			},
  1125  
  1126  			Diff: nil,
  1127  
  1128  			Err: false,
  1129  		},
  1130  
  1131  		"#29 List of structure decode": {
  1132  			Schema: map[string]*Schema{
  1133  				"ingress": &Schema{
  1134  					Type:     TypeList,
  1135  					Required: true,
  1136  					Elem: &Resource{
  1137  						Schema: map[string]*Schema{
  1138  							"from": &Schema{
  1139  								Type:     TypeInt,
  1140  								Required: true,
  1141  							},
  1142  						},
  1143  					},
  1144  				},
  1145  			},
  1146  
  1147  			State: nil,
  1148  
  1149  			Config: map[string]interface{}{
  1150  				"ingress": []interface{}{
  1151  					map[string]interface{}{
  1152  						"from": 8080,
  1153  					},
  1154  				},
  1155  			},
  1156  
  1157  			Diff: &terraform.InstanceDiff{
  1158  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1159  					"ingress.#": &terraform.ResourceAttrDiff{
  1160  						Old: "0",
  1161  						New: "1",
  1162  					},
  1163  					"ingress.0.from": &terraform.ResourceAttrDiff{
  1164  						Old: "",
  1165  						New: "8080",
  1166  					},
  1167  				},
  1168  			},
  1169  
  1170  			Err: false,
  1171  		},
  1172  
  1173  		"#30 ComputedWhen": {
  1174  			Schema: map[string]*Schema{
  1175  				"availability_zone": &Schema{
  1176  					Type:         TypeString,
  1177  					Computed:     true,
  1178  					ComputedWhen: []string{"port"},
  1179  				},
  1180  
  1181  				"port": &Schema{
  1182  					Type:     TypeInt,
  1183  					Optional: true,
  1184  				},
  1185  			},
  1186  
  1187  			State: &terraform.InstanceState{
  1188  				Attributes: map[string]string{
  1189  					"availability_zone": "foo",
  1190  					"port":              "80",
  1191  				},
  1192  			},
  1193  
  1194  			Config: map[string]interface{}{
  1195  				"port": 80,
  1196  			},
  1197  
  1198  			Diff: nil,
  1199  
  1200  			Err: false,
  1201  		},
  1202  
  1203  		"#31": {
  1204  			Schema: map[string]*Schema{
  1205  				"availability_zone": &Schema{
  1206  					Type:         TypeString,
  1207  					Computed:     true,
  1208  					ComputedWhen: []string{"port"},
  1209  				},
  1210  
  1211  				"port": &Schema{
  1212  					Type:     TypeInt,
  1213  					Optional: true,
  1214  				},
  1215  			},
  1216  
  1217  			State: &terraform.InstanceState{
  1218  				Attributes: map[string]string{
  1219  					"port": "80",
  1220  				},
  1221  			},
  1222  
  1223  			Config: map[string]interface{}{
  1224  				"port": 80,
  1225  			},
  1226  
  1227  			Diff: &terraform.InstanceDiff{
  1228  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1229  					"availability_zone": &terraform.ResourceAttrDiff{
  1230  						NewComputed: true,
  1231  					},
  1232  				},
  1233  			},
  1234  
  1235  			Err: false,
  1236  		},
  1237  
  1238  		/* TODO
  1239  		{
  1240  			Schema: map[string]*Schema{
  1241  				"availability_zone": &Schema{
  1242  					Type:         TypeString,
  1243  					Computed:     true,
  1244  					ComputedWhen: []string{"port"},
  1245  				},
  1246  
  1247  				"port": &Schema{
  1248  					Type:     TypeInt,
  1249  					Optional: true,
  1250  				},
  1251  			},
  1252  
  1253  			State: &terraform.InstanceState{
  1254  				Attributes: map[string]string{
  1255  					"availability_zone": "foo",
  1256  					"port":              "80",
  1257  				},
  1258  			},
  1259  
  1260  			Config: map[string]interface{}{
  1261  				"port": 8080,
  1262  			},
  1263  
  1264  			Diff: &terraform.ResourceDiff{
  1265  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1266  					"availability_zone": &terraform.ResourceAttrDiff{
  1267  						Old:         "foo",
  1268  						NewComputed: true,
  1269  					},
  1270  					"port": &terraform.ResourceAttrDiff{
  1271  						Old: "80",
  1272  						New: "8080",
  1273  					},
  1274  				},
  1275  			},
  1276  
  1277  			Err: false,
  1278  		},
  1279  		*/
  1280  
  1281  		"#32 Maps": {
  1282  			Schema: map[string]*Schema{
  1283  				"config_vars": &Schema{
  1284  					Type: TypeMap,
  1285  				},
  1286  			},
  1287  
  1288  			State: nil,
  1289  
  1290  			Config: map[string]interface{}{
  1291  				"config_vars": []interface{}{
  1292  					map[string]interface{}{
  1293  						"bar": "baz",
  1294  					},
  1295  				},
  1296  			},
  1297  
  1298  			Diff: &terraform.InstanceDiff{
  1299  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1300  					"config_vars.%": &terraform.ResourceAttrDiff{
  1301  						Old: "0",
  1302  						New: "1",
  1303  					},
  1304  
  1305  					"config_vars.bar": &terraform.ResourceAttrDiff{
  1306  						Old: "",
  1307  						New: "baz",
  1308  					},
  1309  				},
  1310  			},
  1311  
  1312  			Err: false,
  1313  		},
  1314  
  1315  		"#33 Maps": {
  1316  			Schema: map[string]*Schema{
  1317  				"config_vars": &Schema{
  1318  					Type: TypeMap,
  1319  				},
  1320  			},
  1321  
  1322  			State: &terraform.InstanceState{
  1323  				Attributes: map[string]string{
  1324  					"config_vars.foo": "bar",
  1325  				},
  1326  			},
  1327  
  1328  			Config: map[string]interface{}{
  1329  				"config_vars": []interface{}{
  1330  					map[string]interface{}{
  1331  						"bar": "baz",
  1332  					},
  1333  				},
  1334  			},
  1335  
  1336  			Diff: &terraform.InstanceDiff{
  1337  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1338  					"config_vars.foo": &terraform.ResourceAttrDiff{
  1339  						Old:        "bar",
  1340  						NewRemoved: true,
  1341  					},
  1342  					"config_vars.bar": &terraform.ResourceAttrDiff{
  1343  						Old: "",
  1344  						New: "baz",
  1345  					},
  1346  				},
  1347  			},
  1348  
  1349  			Err: false,
  1350  		},
  1351  
  1352  		"#34 Maps": {
  1353  			Schema: map[string]*Schema{
  1354  				"vars": &Schema{
  1355  					Type:     TypeMap,
  1356  					Optional: true,
  1357  					Computed: true,
  1358  				},
  1359  			},
  1360  
  1361  			State: &terraform.InstanceState{
  1362  				Attributes: map[string]string{
  1363  					"vars.foo": "bar",
  1364  				},
  1365  			},
  1366  
  1367  			Config: map[string]interface{}{
  1368  				"vars": []interface{}{
  1369  					map[string]interface{}{
  1370  						"bar": "baz",
  1371  					},
  1372  				},
  1373  			},
  1374  
  1375  			Diff: &terraform.InstanceDiff{
  1376  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1377  					"vars.foo": &terraform.ResourceAttrDiff{
  1378  						Old:        "bar",
  1379  						New:        "",
  1380  						NewRemoved: true,
  1381  					},
  1382  					"vars.bar": &terraform.ResourceAttrDiff{
  1383  						Old: "",
  1384  						New: "baz",
  1385  					},
  1386  				},
  1387  			},
  1388  
  1389  			Err: false,
  1390  		},
  1391  
  1392  		"#35 Maps": {
  1393  			Schema: map[string]*Schema{
  1394  				"vars": &Schema{
  1395  					Type:     TypeMap,
  1396  					Computed: true,
  1397  				},
  1398  			},
  1399  
  1400  			State: &terraform.InstanceState{
  1401  				Attributes: map[string]string{
  1402  					"vars.foo": "bar",
  1403  				},
  1404  			},
  1405  
  1406  			Config: nil,
  1407  
  1408  			Diff: nil,
  1409  
  1410  			Err: false,
  1411  		},
  1412  
  1413  		"#36 Maps": {
  1414  			Schema: map[string]*Schema{
  1415  				"config_vars": &Schema{
  1416  					Type: TypeList,
  1417  					Elem: &Schema{Type: TypeMap},
  1418  				},
  1419  			},
  1420  
  1421  			State: &terraform.InstanceState{
  1422  				Attributes: map[string]string{
  1423  					"config_vars.#":     "1",
  1424  					"config_vars.0.foo": "bar",
  1425  				},
  1426  			},
  1427  
  1428  			Config: map[string]interface{}{
  1429  				"config_vars": []interface{}{
  1430  					map[string]interface{}{
  1431  						"bar": "baz",
  1432  					},
  1433  				},
  1434  			},
  1435  
  1436  			Diff: &terraform.InstanceDiff{
  1437  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1438  					"config_vars.0.foo": &terraform.ResourceAttrDiff{
  1439  						Old:        "bar",
  1440  						NewRemoved: true,
  1441  					},
  1442  					"config_vars.0.bar": &terraform.ResourceAttrDiff{
  1443  						Old: "",
  1444  						New: "baz",
  1445  					},
  1446  				},
  1447  			},
  1448  
  1449  			Err: false,
  1450  		},
  1451  
  1452  		"#37 Maps": {
  1453  			Schema: map[string]*Schema{
  1454  				"config_vars": &Schema{
  1455  					Type: TypeList,
  1456  					Elem: &Schema{Type: TypeMap},
  1457  				},
  1458  			},
  1459  
  1460  			State: &terraform.InstanceState{
  1461  				Attributes: map[string]string{
  1462  					"config_vars.#":     "1",
  1463  					"config_vars.0.foo": "bar",
  1464  					"config_vars.0.bar": "baz",
  1465  				},
  1466  			},
  1467  
  1468  			Config: map[string]interface{}{},
  1469  
  1470  			Diff: &terraform.InstanceDiff{
  1471  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1472  					"config_vars.#": &terraform.ResourceAttrDiff{
  1473  						Old: "1",
  1474  						New: "0",
  1475  					},
  1476  					"config_vars.0.%": &terraform.ResourceAttrDiff{
  1477  						Old: "2",
  1478  						New: "0",
  1479  					},
  1480  					"config_vars.0.foo": &terraform.ResourceAttrDiff{
  1481  						Old:        "bar",
  1482  						NewRemoved: true,
  1483  					},
  1484  					"config_vars.0.bar": &terraform.ResourceAttrDiff{
  1485  						Old:        "baz",
  1486  						NewRemoved: true,
  1487  					},
  1488  				},
  1489  			},
  1490  
  1491  			Err: false,
  1492  		},
  1493  
  1494  		"#38 ForceNews": {
  1495  			Schema: map[string]*Schema{
  1496  				"availability_zone": &Schema{
  1497  					Type:     TypeString,
  1498  					Optional: true,
  1499  					ForceNew: true,
  1500  				},
  1501  
  1502  				"address": &Schema{
  1503  					Type:     TypeString,
  1504  					Optional: true,
  1505  					Computed: true,
  1506  				},
  1507  			},
  1508  
  1509  			State: &terraform.InstanceState{
  1510  				Attributes: map[string]string{
  1511  					"availability_zone": "bar",
  1512  					"address":           "foo",
  1513  				},
  1514  			},
  1515  
  1516  			Config: map[string]interface{}{
  1517  				"availability_zone": "foo",
  1518  			},
  1519  
  1520  			Diff: &terraform.InstanceDiff{
  1521  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1522  					"availability_zone": &terraform.ResourceAttrDiff{
  1523  						Old:         "bar",
  1524  						New:         "foo",
  1525  						RequiresNew: true,
  1526  					},
  1527  
  1528  					"address": &terraform.ResourceAttrDiff{
  1529  						Old:         "foo",
  1530  						New:         "",
  1531  						NewComputed: true,
  1532  					},
  1533  				},
  1534  			},
  1535  
  1536  			Err: false,
  1537  		},
  1538  
  1539  		"#39 Set": {
  1540  			Schema: map[string]*Schema{
  1541  				"availability_zone": &Schema{
  1542  					Type:     TypeString,
  1543  					Optional: true,
  1544  					ForceNew: true,
  1545  				},
  1546  
  1547  				"ports": &Schema{
  1548  					Type:     TypeSet,
  1549  					Optional: true,
  1550  					Computed: true,
  1551  					Elem:     &Schema{Type: TypeInt},
  1552  					Set: func(a interface{}) int {
  1553  						return a.(int)
  1554  					},
  1555  				},
  1556  			},
  1557  
  1558  			State: &terraform.InstanceState{
  1559  				Attributes: map[string]string{
  1560  					"availability_zone": "bar",
  1561  					"ports.#":           "1",
  1562  					"ports.80":          "80",
  1563  				},
  1564  			},
  1565  
  1566  			Config: map[string]interface{}{
  1567  				"availability_zone": "foo",
  1568  			},
  1569  
  1570  			Diff: &terraform.InstanceDiff{
  1571  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1572  					"availability_zone": &terraform.ResourceAttrDiff{
  1573  						Old:         "bar",
  1574  						New:         "foo",
  1575  						RequiresNew: true,
  1576  					},
  1577  
  1578  					"ports.#": &terraform.ResourceAttrDiff{
  1579  						Old:         "1",
  1580  						New:         "",
  1581  						NewComputed: true,
  1582  					},
  1583  				},
  1584  			},
  1585  
  1586  			Err: false,
  1587  		},
  1588  
  1589  		"#40 Set": {
  1590  			Schema: map[string]*Schema{
  1591  				"instances": &Schema{
  1592  					Type:     TypeSet,
  1593  					Elem:     &Schema{Type: TypeString},
  1594  					Optional: true,
  1595  					Computed: true,
  1596  					Set: func(v interface{}) int {
  1597  						return len(v.(string))
  1598  					},
  1599  				},
  1600  			},
  1601  
  1602  			State: &terraform.InstanceState{
  1603  				Attributes: map[string]string{
  1604  					"instances.#": "0",
  1605  				},
  1606  			},
  1607  
  1608  			Config: map[string]interface{}{
  1609  				"instances": []interface{}{"${var.foo}"},
  1610  			},
  1611  
  1612  			ConfigVariables: map[string]ast.Variable{
  1613  				"var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue),
  1614  			},
  1615  
  1616  			Diff: &terraform.InstanceDiff{
  1617  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1618  					"instances.#": &terraform.ResourceAttrDiff{
  1619  						NewComputed: true,
  1620  					},
  1621  				},
  1622  			},
  1623  
  1624  			Err: false,
  1625  		},
  1626  
  1627  		"#41 Set": {
  1628  			Schema: map[string]*Schema{
  1629  				"route": &Schema{
  1630  					Type:     TypeSet,
  1631  					Optional: true,
  1632  					Elem: &Resource{
  1633  						Schema: map[string]*Schema{
  1634  							"index": &Schema{
  1635  								Type:     TypeInt,
  1636  								Required: true,
  1637  							},
  1638  
  1639  							"gateway": &Schema{
  1640  								Type:     TypeString,
  1641  								Optional: true,
  1642  							},
  1643  						},
  1644  					},
  1645  					Set: func(v interface{}) int {
  1646  						m := v.(map[string]interface{})
  1647  						return m["index"].(int)
  1648  					},
  1649  				},
  1650  			},
  1651  
  1652  			State: nil,
  1653  
  1654  			Config: map[string]interface{}{
  1655  				"route": []map[string]interface{}{
  1656  					map[string]interface{}{
  1657  						"index":   "1",
  1658  						"gateway": "${var.foo}",
  1659  					},
  1660  				},
  1661  			},
  1662  
  1663  			ConfigVariables: map[string]ast.Variable{
  1664  				"var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue),
  1665  			},
  1666  
  1667  			Diff: &terraform.InstanceDiff{
  1668  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1669  					"route.#": &terraform.ResourceAttrDiff{
  1670  						Old: "0",
  1671  						New: "1",
  1672  					},
  1673  					"route.~1.index": &terraform.ResourceAttrDiff{
  1674  						Old: "",
  1675  						New: "1",
  1676  					},
  1677  					"route.~1.gateway": &terraform.ResourceAttrDiff{
  1678  						Old: "",
  1679  						New: "${var.foo}",
  1680  					},
  1681  				},
  1682  			},
  1683  
  1684  			Err: false,
  1685  		},
  1686  
  1687  		"#42 Set": {
  1688  			Schema: map[string]*Schema{
  1689  				"route": &Schema{
  1690  					Type:     TypeSet,
  1691  					Optional: true,
  1692  					Elem: &Resource{
  1693  						Schema: map[string]*Schema{
  1694  							"index": &Schema{
  1695  								Type:     TypeInt,
  1696  								Required: true,
  1697  							},
  1698  
  1699  							"gateway": &Schema{
  1700  								Type:     TypeSet,
  1701  								Optional: true,
  1702  								Elem:     &Schema{Type: TypeInt},
  1703  								Set: func(a interface{}) int {
  1704  									return a.(int)
  1705  								},
  1706  							},
  1707  						},
  1708  					},
  1709  					Set: func(v interface{}) int {
  1710  						m := v.(map[string]interface{})
  1711  						return m["index"].(int)
  1712  					},
  1713  				},
  1714  			},
  1715  
  1716  			State: nil,
  1717  
  1718  			Config: map[string]interface{}{
  1719  				"route": []map[string]interface{}{
  1720  					map[string]interface{}{
  1721  						"index": "1",
  1722  						"gateway": []interface{}{
  1723  							"${var.foo}",
  1724  						},
  1725  					},
  1726  				},
  1727  			},
  1728  
  1729  			ConfigVariables: map[string]ast.Variable{
  1730  				"var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue),
  1731  			},
  1732  
  1733  			Diff: &terraform.InstanceDiff{
  1734  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1735  					"route.#": &terraform.ResourceAttrDiff{
  1736  						Old: "0",
  1737  						New: "1",
  1738  					},
  1739  					"route.~1.index": &terraform.ResourceAttrDiff{
  1740  						Old: "",
  1741  						New: "1",
  1742  					},
  1743  					"route.~1.gateway.#": &terraform.ResourceAttrDiff{
  1744  						NewComputed: true,
  1745  					},
  1746  				},
  1747  			},
  1748  
  1749  			Err: false,
  1750  		},
  1751  
  1752  		"#43 - Computed maps": {
  1753  			Schema: map[string]*Schema{
  1754  				"vars": &Schema{
  1755  					Type:     TypeMap,
  1756  					Computed: true,
  1757  				},
  1758  			},
  1759  
  1760  			State: nil,
  1761  
  1762  			Config: nil,
  1763  
  1764  			Diff: &terraform.InstanceDiff{
  1765  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1766  					"vars.%": &terraform.ResourceAttrDiff{
  1767  						Old:         "",
  1768  						NewComputed: true,
  1769  					},
  1770  				},
  1771  			},
  1772  
  1773  			Err: false,
  1774  		},
  1775  
  1776  		"#44 - Computed maps": {
  1777  			Schema: map[string]*Schema{
  1778  				"vars": &Schema{
  1779  					Type:     TypeMap,
  1780  					Computed: true,
  1781  				},
  1782  			},
  1783  
  1784  			State: &terraform.InstanceState{
  1785  				Attributes: map[string]string{
  1786  					"vars.%": "0",
  1787  				},
  1788  			},
  1789  
  1790  			Config: map[string]interface{}{
  1791  				"vars": map[string]interface{}{
  1792  					"bar": "${var.foo}",
  1793  				},
  1794  			},
  1795  
  1796  			ConfigVariables: map[string]ast.Variable{
  1797  				"var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue),
  1798  			},
  1799  
  1800  			Diff: &terraform.InstanceDiff{
  1801  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1802  					"vars.%": &terraform.ResourceAttrDiff{
  1803  						Old:         "",
  1804  						NewComputed: true,
  1805  					},
  1806  				},
  1807  			},
  1808  
  1809  			Err: false,
  1810  		},
  1811  
  1812  		"#45 - Empty": {
  1813  			Schema: map[string]*Schema{},
  1814  
  1815  			State: &terraform.InstanceState{},
  1816  
  1817  			Config: map[string]interface{}{},
  1818  
  1819  			Diff: nil,
  1820  
  1821  			Err: false,
  1822  		},
  1823  
  1824  		"#46 - Float": {
  1825  			Schema: map[string]*Schema{
  1826  				"some_threshold": &Schema{
  1827  					Type: TypeFloat,
  1828  				},
  1829  			},
  1830  
  1831  			State: &terraform.InstanceState{
  1832  				Attributes: map[string]string{
  1833  					"some_threshold": "567.8",
  1834  				},
  1835  			},
  1836  
  1837  			Config: map[string]interface{}{
  1838  				"some_threshold": 12.34,
  1839  			},
  1840  
  1841  			Diff: &terraform.InstanceDiff{
  1842  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1843  					"some_threshold": &terraform.ResourceAttrDiff{
  1844  						Old: "567.8",
  1845  						New: "12.34",
  1846  					},
  1847  				},
  1848  			},
  1849  
  1850  			Err: false,
  1851  		},
  1852  
  1853  		"#47 - https://github.com/hashicorp/terraform/issues/824": {
  1854  			Schema: map[string]*Schema{
  1855  				"block_device": &Schema{
  1856  					Type:     TypeSet,
  1857  					Optional: true,
  1858  					Computed: true,
  1859  					Elem: &Resource{
  1860  						Schema: map[string]*Schema{
  1861  							"device_name": &Schema{
  1862  								Type:     TypeString,
  1863  								Required: true,
  1864  							},
  1865  							"delete_on_termination": &Schema{
  1866  								Type:     TypeBool,
  1867  								Optional: true,
  1868  								Default:  true,
  1869  							},
  1870  						},
  1871  					},
  1872  					Set: func(v interface{}) int {
  1873  						var buf bytes.Buffer
  1874  						m := v.(map[string]interface{})
  1875  						buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
  1876  						buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool)))
  1877  						return hashcode.String(buf.String())
  1878  					},
  1879  				},
  1880  			},
  1881  
  1882  			State: &terraform.InstanceState{
  1883  				Attributes: map[string]string{
  1884  					"block_device.#":                                "2",
  1885  					"block_device.616397234.delete_on_termination":  "true",
  1886  					"block_device.616397234.device_name":            "/dev/sda1",
  1887  					"block_device.2801811477.delete_on_termination": "true",
  1888  					"block_device.2801811477.device_name":           "/dev/sdx",
  1889  				},
  1890  			},
  1891  
  1892  			Config: map[string]interface{}{
  1893  				"block_device": []map[string]interface{}{
  1894  					map[string]interface{}{
  1895  						"device_name": "/dev/sda1",
  1896  					},
  1897  					map[string]interface{}{
  1898  						"device_name": "/dev/sdx",
  1899  					},
  1900  				},
  1901  			},
  1902  			Diff: nil,
  1903  			Err:  false,
  1904  		},
  1905  
  1906  		"#48 - Zero value in state shouldn't result in diff": {
  1907  			Schema: map[string]*Schema{
  1908  				"port": &Schema{
  1909  					Type:     TypeBool,
  1910  					Optional: true,
  1911  					ForceNew: true,
  1912  				},
  1913  			},
  1914  
  1915  			State: &terraform.InstanceState{
  1916  				Attributes: map[string]string{
  1917  					"port": "false",
  1918  				},
  1919  			},
  1920  
  1921  			Config: map[string]interface{}{},
  1922  
  1923  			Diff: nil,
  1924  
  1925  			Err: false,
  1926  		},
  1927  
  1928  		"#49 Set - Same as #48 but for sets": {
  1929  			Schema: map[string]*Schema{
  1930  				"route": &Schema{
  1931  					Type:     TypeSet,
  1932  					Optional: true,
  1933  					Elem: &Resource{
  1934  						Schema: map[string]*Schema{
  1935  							"index": &Schema{
  1936  								Type:     TypeInt,
  1937  								Required: true,
  1938  							},
  1939  
  1940  							"gateway": &Schema{
  1941  								Type:     TypeSet,
  1942  								Optional: true,
  1943  								Elem:     &Schema{Type: TypeInt},
  1944  								Set: func(a interface{}) int {
  1945  									return a.(int)
  1946  								},
  1947  							},
  1948  						},
  1949  					},
  1950  					Set: func(v interface{}) int {
  1951  						m := v.(map[string]interface{})
  1952  						return m["index"].(int)
  1953  					},
  1954  				},
  1955  			},
  1956  
  1957  			State: &terraform.InstanceState{
  1958  				Attributes: map[string]string{
  1959  					"route.#": "0",
  1960  				},
  1961  			},
  1962  
  1963  			Config: map[string]interface{}{},
  1964  
  1965  			Diff: nil,
  1966  
  1967  			Err: false,
  1968  		},
  1969  
  1970  		"#50 - A set computed element shouldn't cause a diff": {
  1971  			Schema: map[string]*Schema{
  1972  				"active": &Schema{
  1973  					Type:     TypeBool,
  1974  					Computed: true,
  1975  					ForceNew: true,
  1976  				},
  1977  			},
  1978  
  1979  			State: &terraform.InstanceState{
  1980  				Attributes: map[string]string{
  1981  					"active": "true",
  1982  				},
  1983  			},
  1984  
  1985  			Config: map[string]interface{}{},
  1986  
  1987  			Diff: nil,
  1988  
  1989  			Err: false,
  1990  		},
  1991  
  1992  		"#51 - An empty set should show up in the diff": {
  1993  			Schema: map[string]*Schema{
  1994  				"instances": &Schema{
  1995  					Type:     TypeSet,
  1996  					Elem:     &Schema{Type: TypeString},
  1997  					Optional: true,
  1998  					ForceNew: true,
  1999  					Set: func(v interface{}) int {
  2000  						return len(v.(string))
  2001  					},
  2002  				},
  2003  			},
  2004  
  2005  			State: &terraform.InstanceState{
  2006  				Attributes: map[string]string{
  2007  					"instances.#": "1",
  2008  					"instances.3": "foo",
  2009  				},
  2010  			},
  2011  
  2012  			Config: map[string]interface{}{},
  2013  
  2014  			Diff: &terraform.InstanceDiff{
  2015  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2016  					"instances.#": &terraform.ResourceAttrDiff{
  2017  						Old:         "1",
  2018  						New:         "0",
  2019  						RequiresNew: true,
  2020  					},
  2021  					"instances.3": &terraform.ResourceAttrDiff{
  2022  						Old:        "foo",
  2023  						New:        "",
  2024  						NewRemoved: true,
  2025  					},
  2026  				},
  2027  			},
  2028  
  2029  			Err: false,
  2030  		},
  2031  
  2032  		"#52 - Map with empty value": {
  2033  			Schema: map[string]*Schema{
  2034  				"vars": &Schema{
  2035  					Type: TypeMap,
  2036  				},
  2037  			},
  2038  
  2039  			State: nil,
  2040  
  2041  			Config: map[string]interface{}{
  2042  				"vars": map[string]interface{}{
  2043  					"foo": "",
  2044  				},
  2045  			},
  2046  
  2047  			Diff: &terraform.InstanceDiff{
  2048  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2049  					"vars.%": &terraform.ResourceAttrDiff{
  2050  						Old: "0",
  2051  						New: "1",
  2052  					},
  2053  					"vars.foo": &terraform.ResourceAttrDiff{
  2054  						Old: "",
  2055  						New: "",
  2056  					},
  2057  				},
  2058  			},
  2059  
  2060  			Err: false,
  2061  		},
  2062  
  2063  		"#53 - Unset bool, not in state": {
  2064  			Schema: map[string]*Schema{
  2065  				"force": &Schema{
  2066  					Type:     TypeBool,
  2067  					Optional: true,
  2068  					ForceNew: true,
  2069  				},
  2070  			},
  2071  
  2072  			State: nil,
  2073  
  2074  			Config: map[string]interface{}{},
  2075  
  2076  			Diff: nil,
  2077  
  2078  			Err: false,
  2079  		},
  2080  
  2081  		"#54 - Unset set, not in state": {
  2082  			Schema: map[string]*Schema{
  2083  				"metadata_keys": &Schema{
  2084  					Type:     TypeSet,
  2085  					Optional: true,
  2086  					ForceNew: true,
  2087  					Elem:     &Schema{Type: TypeInt},
  2088  					Set:      func(interface{}) int { return 0 },
  2089  				},
  2090  			},
  2091  
  2092  			State: nil,
  2093  
  2094  			Config: map[string]interface{}{},
  2095  
  2096  			Diff: nil,
  2097  
  2098  			Err: false,
  2099  		},
  2100  
  2101  		"#55 - Unset list in state, should not show up computed": {
  2102  			Schema: map[string]*Schema{
  2103  				"metadata_keys": &Schema{
  2104  					Type:     TypeList,
  2105  					Optional: true,
  2106  					Computed: true,
  2107  					ForceNew: true,
  2108  					Elem:     &Schema{Type: TypeInt},
  2109  				},
  2110  			},
  2111  
  2112  			State: &terraform.InstanceState{
  2113  				Attributes: map[string]string{
  2114  					"metadata_keys.#": "0",
  2115  				},
  2116  			},
  2117  
  2118  			Config: map[string]interface{}{},
  2119  
  2120  			Diff: nil,
  2121  
  2122  			Err: false,
  2123  		},
  2124  
  2125  		"#56 - Set element computed substring": {
  2126  			Schema: map[string]*Schema{
  2127  				"ports": &Schema{
  2128  					Type:     TypeSet,
  2129  					Required: true,
  2130  					Elem:     &Schema{Type: TypeInt},
  2131  					Set: func(a interface{}) int {
  2132  						return a.(int)
  2133  					},
  2134  				},
  2135  			},
  2136  
  2137  			State: nil,
  2138  
  2139  			Config: map[string]interface{}{
  2140  				"ports": []interface{}{1, "${var.foo}32"},
  2141  			},
  2142  
  2143  			ConfigVariables: map[string]ast.Variable{
  2144  				"var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue),
  2145  			},
  2146  
  2147  			Diff: &terraform.InstanceDiff{
  2148  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2149  					"ports.#": &terraform.ResourceAttrDiff{
  2150  						Old:         "",
  2151  						New:         "",
  2152  						NewComputed: true,
  2153  					},
  2154  				},
  2155  			},
  2156  
  2157  			Err: false,
  2158  		},
  2159  
  2160  		"#57 Computed map without config that's known to be empty does not generate diff": {
  2161  			Schema: map[string]*Schema{
  2162  				"tags": &Schema{
  2163  					Type:     TypeMap,
  2164  					Computed: true,
  2165  				},
  2166  			},
  2167  
  2168  			Config: nil,
  2169  
  2170  			State: &terraform.InstanceState{
  2171  				Attributes: map[string]string{
  2172  					"tags.%": "0",
  2173  				},
  2174  			},
  2175  
  2176  			Diff: nil,
  2177  
  2178  			Err: false,
  2179  		},
  2180  
  2181  		"#58 Set with hyphen keys": {
  2182  			Schema: map[string]*Schema{
  2183  				"route": &Schema{
  2184  					Type:     TypeSet,
  2185  					Optional: true,
  2186  					Elem: &Resource{
  2187  						Schema: map[string]*Schema{
  2188  							"index": &Schema{
  2189  								Type:     TypeInt,
  2190  								Required: true,
  2191  							},
  2192  
  2193  							"gateway-name": &Schema{
  2194  								Type:     TypeString,
  2195  								Optional: true,
  2196  							},
  2197  						},
  2198  					},
  2199  					Set: func(v interface{}) int {
  2200  						m := v.(map[string]interface{})
  2201  						return m["index"].(int)
  2202  					},
  2203  				},
  2204  			},
  2205  
  2206  			State: nil,
  2207  
  2208  			Config: map[string]interface{}{
  2209  				"route": []map[string]interface{}{
  2210  					map[string]interface{}{
  2211  						"index":        "1",
  2212  						"gateway-name": "hello",
  2213  					},
  2214  				},
  2215  			},
  2216  
  2217  			Diff: &terraform.InstanceDiff{
  2218  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2219  					"route.#": &terraform.ResourceAttrDiff{
  2220  						Old: "0",
  2221  						New: "1",
  2222  					},
  2223  					"route.1.index": &terraform.ResourceAttrDiff{
  2224  						Old: "",
  2225  						New: "1",
  2226  					},
  2227  					"route.1.gateway-name": &terraform.ResourceAttrDiff{
  2228  						Old: "",
  2229  						New: "hello",
  2230  					},
  2231  				},
  2232  			},
  2233  
  2234  			Err: false,
  2235  		},
  2236  
  2237  		"#59: StateFunc in nested set (#1759)": {
  2238  			Schema: map[string]*Schema{
  2239  				"service_account": &Schema{
  2240  					Type:     TypeList,
  2241  					Optional: true,
  2242  					ForceNew: true,
  2243  					Elem: &Resource{
  2244  						Schema: map[string]*Schema{
  2245  							"scopes": &Schema{
  2246  								Type:     TypeSet,
  2247  								Required: true,
  2248  								ForceNew: true,
  2249  								Elem: &Schema{
  2250  									Type: TypeString,
  2251  									StateFunc: func(v interface{}) string {
  2252  										return v.(string) + "!"
  2253  									},
  2254  								},
  2255  								Set: func(v interface{}) int {
  2256  									i, err := strconv.Atoi(v.(string))
  2257  									if err != nil {
  2258  										t.Fatalf("err: %s", err)
  2259  									}
  2260  									return i
  2261  								},
  2262  							},
  2263  						},
  2264  					},
  2265  				},
  2266  			},
  2267  
  2268  			State: nil,
  2269  
  2270  			Config: map[string]interface{}{
  2271  				"service_account": []map[string]interface{}{
  2272  					{
  2273  						"scopes": []interface{}{"123"},
  2274  					},
  2275  				},
  2276  			},
  2277  
  2278  			Diff: &terraform.InstanceDiff{
  2279  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2280  					"service_account.#": &terraform.ResourceAttrDiff{
  2281  						Old:         "0",
  2282  						New:         "1",
  2283  						RequiresNew: true,
  2284  					},
  2285  					"service_account.0.scopes.#": &terraform.ResourceAttrDiff{
  2286  						Old:         "0",
  2287  						New:         "1",
  2288  						RequiresNew: true,
  2289  					},
  2290  					"service_account.0.scopes.123": &terraform.ResourceAttrDiff{
  2291  						Old:         "",
  2292  						New:         "123!",
  2293  						NewExtra:    "123",
  2294  						RequiresNew: true,
  2295  					},
  2296  				},
  2297  			},
  2298  
  2299  			Err: false,
  2300  		},
  2301  
  2302  		"#60 - Removing set elements": {
  2303  			Schema: map[string]*Schema{
  2304  				"instances": &Schema{
  2305  					Type:     TypeSet,
  2306  					Elem:     &Schema{Type: TypeString},
  2307  					Optional: true,
  2308  					ForceNew: true,
  2309  					Set: func(v interface{}) int {
  2310  						return len(v.(string))
  2311  					},
  2312  				},
  2313  			},
  2314  
  2315  			State: &terraform.InstanceState{
  2316  				Attributes: map[string]string{
  2317  					"instances.#": "2",
  2318  					"instances.3": "333",
  2319  					"instances.2": "22",
  2320  				},
  2321  			},
  2322  
  2323  			Config: map[string]interface{}{
  2324  				"instances": []interface{}{"333", "4444"},
  2325  			},
  2326  
  2327  			Diff: &terraform.InstanceDiff{
  2328  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2329  					"instances.#": &terraform.ResourceAttrDiff{
  2330  						Old: "2",
  2331  						New: "2",
  2332  					},
  2333  					"instances.2": &terraform.ResourceAttrDiff{
  2334  						Old:        "22",
  2335  						New:        "",
  2336  						NewRemoved: true,
  2337  					},
  2338  					"instances.3": &terraform.ResourceAttrDiff{
  2339  						Old:         "333",
  2340  						New:         "333",
  2341  						RequiresNew: true,
  2342  					},
  2343  					"instances.4": &terraform.ResourceAttrDiff{
  2344  						Old:         "",
  2345  						New:         "4444",
  2346  						RequiresNew: true,
  2347  					},
  2348  				},
  2349  			},
  2350  
  2351  			Err: false,
  2352  		},
  2353  
  2354  		"Bools can be set with 0/1 in config, still get true/false": {
  2355  			Schema: map[string]*Schema{
  2356  				"one": &Schema{
  2357  					Type:     TypeBool,
  2358  					Optional: true,
  2359  				},
  2360  				"two": &Schema{
  2361  					Type:     TypeBool,
  2362  					Optional: true,
  2363  				},
  2364  				"three": &Schema{
  2365  					Type:     TypeBool,
  2366  					Optional: true,
  2367  				},
  2368  			},
  2369  
  2370  			State: &terraform.InstanceState{
  2371  				Attributes: map[string]string{
  2372  					"one":   "false",
  2373  					"two":   "true",
  2374  					"three": "true",
  2375  				},
  2376  			},
  2377  
  2378  			Config: map[string]interface{}{
  2379  				"one": "1",
  2380  				"two": "0",
  2381  			},
  2382  
  2383  			Diff: &terraform.InstanceDiff{
  2384  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2385  					"one": &terraform.ResourceAttrDiff{
  2386  						Old: "false",
  2387  						New: "true",
  2388  					},
  2389  					"two": &terraform.ResourceAttrDiff{
  2390  						Old: "true",
  2391  						New: "false",
  2392  					},
  2393  					"three": &terraform.ResourceAttrDiff{
  2394  						Old:        "true",
  2395  						New:        "false",
  2396  						NewRemoved: true,
  2397  					},
  2398  				},
  2399  			},
  2400  
  2401  			Err: false,
  2402  		},
  2403  
  2404  		"tainted in state w/ no attr changes is still a replacement": {
  2405  			Schema: map[string]*Schema{},
  2406  
  2407  			State: &terraform.InstanceState{
  2408  				Attributes: map[string]string{
  2409  					"id": "someid",
  2410  				},
  2411  				Tainted: true,
  2412  			},
  2413  
  2414  			Config: map[string]interface{}{},
  2415  
  2416  			Diff: &terraform.InstanceDiff{
  2417  				Attributes:     map[string]*terraform.ResourceAttrDiff{},
  2418  				DestroyTainted: true,
  2419  			},
  2420  
  2421  			Err: false,
  2422  		},
  2423  	}
  2424  
  2425  	for tn, tc := range cases {
  2426  		c, err := config.NewRawConfig(tc.Config)
  2427  		if err != nil {
  2428  			t.Fatalf("#%q err: %s", tn, err)
  2429  		}
  2430  
  2431  		if len(tc.ConfigVariables) > 0 {
  2432  			if err := c.Interpolate(tc.ConfigVariables); err != nil {
  2433  				t.Fatalf("#%q err: %s", tn, err)
  2434  			}
  2435  		}
  2436  
  2437  		d, err := schemaMap(tc.Schema).Diff(
  2438  			tc.State, terraform.NewResourceConfig(c))
  2439  		if err != nil != tc.Err {
  2440  			t.Fatalf("#%q err: %s", tn, err)
  2441  		}
  2442  
  2443  		if !reflect.DeepEqual(tc.Diff, d) {
  2444  			t.Fatalf("#%q:\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.Diff, d)
  2445  		}
  2446  	}
  2447  }
  2448  
  2449  func TestSchemaMap_Input(t *testing.T) {
  2450  	cases := map[string]struct {
  2451  		Schema map[string]*Schema
  2452  		Config map[string]interface{}
  2453  		Input  map[string]string
  2454  		Result map[string]interface{}
  2455  		Err    bool
  2456  	}{
  2457  		/*
  2458  		 * String decode
  2459  		 */
  2460  
  2461  		"uses input on optional field with no config": {
  2462  			Schema: map[string]*Schema{
  2463  				"availability_zone": &Schema{
  2464  					Type:     TypeString,
  2465  					Optional: true,
  2466  				},
  2467  			},
  2468  
  2469  			Input: map[string]string{
  2470  				"availability_zone": "foo",
  2471  			},
  2472  
  2473  			Result: map[string]interface{}{
  2474  				"availability_zone": "foo",
  2475  			},
  2476  
  2477  			Err: false,
  2478  		},
  2479  
  2480  		"input ignored when config has a value": {
  2481  			Schema: map[string]*Schema{
  2482  				"availability_zone": &Schema{
  2483  					Type:     TypeString,
  2484  					Optional: true,
  2485  				},
  2486  			},
  2487  
  2488  			Config: map[string]interface{}{
  2489  				"availability_zone": "bar",
  2490  			},
  2491  
  2492  			Input: map[string]string{
  2493  				"availability_zone": "foo",
  2494  			},
  2495  
  2496  			Result: map[string]interface{}{},
  2497  
  2498  			Err: false,
  2499  		},
  2500  
  2501  		"input ignored when schema has a default": {
  2502  			Schema: map[string]*Schema{
  2503  				"availability_zone": &Schema{
  2504  					Type:     TypeString,
  2505  					Default:  "foo",
  2506  					Optional: true,
  2507  				},
  2508  			},
  2509  
  2510  			Input: map[string]string{
  2511  				"availability_zone": "bar",
  2512  			},
  2513  
  2514  			Result: map[string]interface{}{},
  2515  
  2516  			Err: false,
  2517  		},
  2518  
  2519  		"input ignored when default function returns a value": {
  2520  			Schema: map[string]*Schema{
  2521  				"availability_zone": &Schema{
  2522  					Type: TypeString,
  2523  					DefaultFunc: func() (interface{}, error) {
  2524  						return "foo", nil
  2525  					},
  2526  					Optional: true,
  2527  				},
  2528  			},
  2529  
  2530  			Input: map[string]string{
  2531  				"availability_zone": "bar",
  2532  			},
  2533  
  2534  			Result: map[string]interface{}{},
  2535  
  2536  			Err: false,
  2537  		},
  2538  
  2539  		"input ignored when default function returns an empty string": {
  2540  			Schema: map[string]*Schema{
  2541  				"availability_zone": &Schema{
  2542  					Type:     TypeString,
  2543  					Default:  "",
  2544  					Optional: true,
  2545  				},
  2546  			},
  2547  
  2548  			Input: map[string]string{
  2549  				"availability_zone": "bar",
  2550  			},
  2551  
  2552  			Result: map[string]interface{}{},
  2553  
  2554  			Err: false,
  2555  		},
  2556  
  2557  		"input used when default function returns nil": {
  2558  			Schema: map[string]*Schema{
  2559  				"availability_zone": &Schema{
  2560  					Type: TypeString,
  2561  					DefaultFunc: func() (interface{}, error) {
  2562  						return nil, nil
  2563  					},
  2564  					Optional: true,
  2565  				},
  2566  			},
  2567  
  2568  			Input: map[string]string{
  2569  				"availability_zone": "bar",
  2570  			},
  2571  
  2572  			Result: map[string]interface{}{
  2573  				"availability_zone": "bar",
  2574  			},
  2575  
  2576  			Err: false,
  2577  		},
  2578  	}
  2579  
  2580  	for i, tc := range cases {
  2581  		if tc.Config == nil {
  2582  			tc.Config = make(map[string]interface{})
  2583  		}
  2584  
  2585  		c, err := config.NewRawConfig(tc.Config)
  2586  		if err != nil {
  2587  			t.Fatalf("err: %s", err)
  2588  		}
  2589  
  2590  		input := new(terraform.MockUIInput)
  2591  		input.InputReturnMap = tc.Input
  2592  
  2593  		rc := terraform.NewResourceConfig(c)
  2594  		rc.Config = make(map[string]interface{})
  2595  
  2596  		actual, err := schemaMap(tc.Schema).Input(input, rc)
  2597  		if err != nil != tc.Err {
  2598  			t.Fatalf("#%v err: %s", i, err)
  2599  		}
  2600  
  2601  		if !reflect.DeepEqual(tc.Result, actual.Config) {
  2602  			t.Fatalf("#%v: bad:\n\ngot: %#v\nexpected: %#v", i, actual.Config, tc.Result)
  2603  		}
  2604  	}
  2605  }
  2606  
  2607  func TestSchemaMap_InputDefault(t *testing.T) {
  2608  	emptyConfig := make(map[string]interface{})
  2609  	c, err := config.NewRawConfig(emptyConfig)
  2610  	if err != nil {
  2611  		t.Fatalf("err: %s", err)
  2612  	}
  2613  	rc := terraform.NewResourceConfig(c)
  2614  	rc.Config = make(map[string]interface{})
  2615  
  2616  	input := new(terraform.MockUIInput)
  2617  	input.InputFn = func(opts *terraform.InputOpts) (string, error) {
  2618  		t.Fatalf("InputFn should not be called on: %#v", opts)
  2619  		return "", nil
  2620  	}
  2621  
  2622  	schema := map[string]*Schema{
  2623  		"availability_zone": &Schema{
  2624  			Type:     TypeString,
  2625  			Default:  "foo",
  2626  			Optional: true,
  2627  		},
  2628  	}
  2629  	actual, err := schemaMap(schema).Input(input, rc)
  2630  	if err != nil {
  2631  		t.Fatalf("err: %s", err)
  2632  	}
  2633  
  2634  	expected := map[string]interface{}{}
  2635  
  2636  	if !reflect.DeepEqual(expected, actual.Config) {
  2637  		t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected)
  2638  	}
  2639  }
  2640  
  2641  func TestSchemaMap_InputDeprecated(t *testing.T) {
  2642  	emptyConfig := make(map[string]interface{})
  2643  	c, err := config.NewRawConfig(emptyConfig)
  2644  	if err != nil {
  2645  		t.Fatalf("err: %s", err)
  2646  	}
  2647  	rc := terraform.NewResourceConfig(c)
  2648  	rc.Config = make(map[string]interface{})
  2649  
  2650  	input := new(terraform.MockUIInput)
  2651  	input.InputFn = func(opts *terraform.InputOpts) (string, error) {
  2652  		t.Fatalf("InputFn should not be called on: %#v", opts)
  2653  		return "", nil
  2654  	}
  2655  
  2656  	schema := map[string]*Schema{
  2657  		"availability_zone": &Schema{
  2658  			Type:       TypeString,
  2659  			Deprecated: "long gone",
  2660  			Optional:   true,
  2661  		},
  2662  	}
  2663  	actual, err := schemaMap(schema).Input(input, rc)
  2664  	if err != nil {
  2665  		t.Fatalf("err: %s", err)
  2666  	}
  2667  
  2668  	expected := map[string]interface{}{}
  2669  
  2670  	if !reflect.DeepEqual(expected, actual.Config) {
  2671  		t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected)
  2672  	}
  2673  }
  2674  
  2675  func TestSchemaMap_InternalValidate(t *testing.T) {
  2676  	cases := map[string]struct {
  2677  		In  map[string]*Schema
  2678  		Err bool
  2679  	}{
  2680  		"nothing": {
  2681  			nil,
  2682  			false,
  2683  		},
  2684  
  2685  		"Both optional and required": {
  2686  			map[string]*Schema{
  2687  				"foo": &Schema{
  2688  					Type:     TypeInt,
  2689  					Optional: true,
  2690  					Required: true,
  2691  				},
  2692  			},
  2693  			true,
  2694  		},
  2695  
  2696  		"No optional and no required": {
  2697  			map[string]*Schema{
  2698  				"foo": &Schema{
  2699  					Type: TypeInt,
  2700  				},
  2701  			},
  2702  			true,
  2703  		},
  2704  
  2705  		"Missing Type": {
  2706  			map[string]*Schema{
  2707  				"foo": &Schema{
  2708  					Required: true,
  2709  				},
  2710  			},
  2711  			true,
  2712  		},
  2713  
  2714  		"Required but computed": {
  2715  			map[string]*Schema{
  2716  				"foo": &Schema{
  2717  					Type:     TypeInt,
  2718  					Required: true,
  2719  					Computed: true,
  2720  				},
  2721  			},
  2722  			true,
  2723  		},
  2724  
  2725  		"Looks good": {
  2726  			map[string]*Schema{
  2727  				"foo": &Schema{
  2728  					Type:     TypeString,
  2729  					Required: true,
  2730  				},
  2731  			},
  2732  			false,
  2733  		},
  2734  
  2735  		"Computed but has default": {
  2736  			map[string]*Schema{
  2737  				"foo": &Schema{
  2738  					Type:     TypeInt,
  2739  					Optional: true,
  2740  					Computed: true,
  2741  					Default:  "foo",
  2742  				},
  2743  			},
  2744  			true,
  2745  		},
  2746  
  2747  		"Required but has default": {
  2748  			map[string]*Schema{
  2749  				"foo": &Schema{
  2750  					Type:     TypeInt,
  2751  					Optional: true,
  2752  					Required: true,
  2753  					Default:  "foo",
  2754  				},
  2755  			},
  2756  			true,
  2757  		},
  2758  
  2759  		"List element not set": {
  2760  			map[string]*Schema{
  2761  				"foo": &Schema{
  2762  					Type: TypeList,
  2763  				},
  2764  			},
  2765  			true,
  2766  		},
  2767  
  2768  		"List default": {
  2769  			map[string]*Schema{
  2770  				"foo": &Schema{
  2771  					Type:    TypeList,
  2772  					Elem:    &Schema{Type: TypeInt},
  2773  					Default: "foo",
  2774  				},
  2775  			},
  2776  			true,
  2777  		},
  2778  
  2779  		"List element computed": {
  2780  			map[string]*Schema{
  2781  				"foo": &Schema{
  2782  					Type:     TypeList,
  2783  					Optional: true,
  2784  					Elem: &Schema{
  2785  						Type:     TypeInt,
  2786  						Computed: true,
  2787  					},
  2788  				},
  2789  			},
  2790  			true,
  2791  		},
  2792  
  2793  		"List element with Set set": {
  2794  			map[string]*Schema{
  2795  				"foo": &Schema{
  2796  					Type:     TypeList,
  2797  					Elem:     &Schema{Type: TypeInt},
  2798  					Set:      func(interface{}) int { return 0 },
  2799  					Optional: true,
  2800  				},
  2801  			},
  2802  			true,
  2803  		},
  2804  
  2805  		"Set element with no Set set": {
  2806  			map[string]*Schema{
  2807  				"foo": &Schema{
  2808  					Type:     TypeSet,
  2809  					Elem:     &Schema{Type: TypeInt},
  2810  					Optional: true,
  2811  				},
  2812  			},
  2813  			false,
  2814  		},
  2815  
  2816  		"Required but computedWhen": {
  2817  			map[string]*Schema{
  2818  				"foo": &Schema{
  2819  					Type:         TypeInt,
  2820  					Required:     true,
  2821  					ComputedWhen: []string{"foo"},
  2822  				},
  2823  			},
  2824  			true,
  2825  		},
  2826  
  2827  		"Conflicting attributes cannot be required": {
  2828  			map[string]*Schema{
  2829  				"blacklist": &Schema{
  2830  					Type:     TypeBool,
  2831  					Required: true,
  2832  				},
  2833  				"whitelist": &Schema{
  2834  					Type:          TypeBool,
  2835  					Optional:      true,
  2836  					ConflictsWith: []string{"blacklist"},
  2837  				},
  2838  			},
  2839  			true,
  2840  		},
  2841  
  2842  		"Attribute with conflicts cannot be required": {
  2843  			map[string]*Schema{
  2844  				"whitelist": &Schema{
  2845  					Type:          TypeBool,
  2846  					Required:      true,
  2847  					ConflictsWith: []string{"blacklist"},
  2848  				},
  2849  			},
  2850  			true,
  2851  		},
  2852  
  2853  		"ConflictsWith cannot be used w/ Computed": {
  2854  			map[string]*Schema{
  2855  				"blacklist": &Schema{
  2856  					Type:     TypeBool,
  2857  					Computed: true,
  2858  				},
  2859  				"whitelist": &Schema{
  2860  					Type:          TypeBool,
  2861  					Optional:      true,
  2862  					ConflictsWith: []string{"blacklist"},
  2863  				},
  2864  			},
  2865  			true,
  2866  		},
  2867  
  2868  		"ConflictsWith cannot be used w/ ComputedWhen": {
  2869  			map[string]*Schema{
  2870  				"blacklist": &Schema{
  2871  					Type:         TypeBool,
  2872  					ComputedWhen: []string{"foor"},
  2873  				},
  2874  				"whitelist": &Schema{
  2875  					Type:          TypeBool,
  2876  					Required:      true,
  2877  					ConflictsWith: []string{"blacklist"},
  2878  				},
  2879  			},
  2880  			true,
  2881  		},
  2882  
  2883  		"Sub-resource invalid": {
  2884  			map[string]*Schema{
  2885  				"foo": &Schema{
  2886  					Type:     TypeList,
  2887  					Optional: true,
  2888  					Elem: &Resource{
  2889  						Schema: map[string]*Schema{
  2890  							"foo": new(Schema),
  2891  						},
  2892  					},
  2893  				},
  2894  			},
  2895  			true,
  2896  		},
  2897  
  2898  		"Sub-resource valid": {
  2899  			map[string]*Schema{
  2900  				"foo": &Schema{
  2901  					Type:     TypeList,
  2902  					Optional: true,
  2903  					Elem: &Resource{
  2904  						Schema: map[string]*Schema{
  2905  							"foo": &Schema{
  2906  								Type:     TypeInt,
  2907  								Optional: true,
  2908  							},
  2909  						},
  2910  					},
  2911  				},
  2912  			},
  2913  			false,
  2914  		},
  2915  
  2916  		"ValidateFunc on non-primitive": {
  2917  			map[string]*Schema{
  2918  				"foo": &Schema{
  2919  					Type:     TypeSet,
  2920  					Required: true,
  2921  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  2922  						return
  2923  					},
  2924  				},
  2925  			},
  2926  			true,
  2927  		},
  2928  	}
  2929  
  2930  	for tn, tc := range cases {
  2931  		err := schemaMap(tc.In).InternalValidate(schemaMap{})
  2932  		if err != nil != tc.Err {
  2933  			if tc.Err {
  2934  				t.Fatalf("%q: Expected error did not occur:\n\n%#v", tn, tc.In)
  2935  			}
  2936  			t.Fatalf("%q: Unexpected error occurred:\n\n%#v", tn, tc.In)
  2937  		}
  2938  	}
  2939  
  2940  }
  2941  
  2942  func TestSchemaMap_DiffSuppress(t *testing.T) {
  2943  	cases := map[string]struct {
  2944  		Schema          map[string]*Schema
  2945  		State           *terraform.InstanceState
  2946  		Config          map[string]interface{}
  2947  		ConfigVariables map[string]ast.Variable
  2948  		ExpectedDiff    *terraform.InstanceDiff
  2949  		Err             bool
  2950  	}{
  2951  		"#0 - Suppress otherwise valid diff by returning true": {
  2952  			Schema: map[string]*Schema{
  2953  				"availability_zone": {
  2954  					Type:     TypeString,
  2955  					Optional: true,
  2956  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  2957  						// Always suppress any diff
  2958  						return true
  2959  					},
  2960  				},
  2961  			},
  2962  
  2963  			State: nil,
  2964  
  2965  			Config: map[string]interface{}{
  2966  				"availability_zone": "foo",
  2967  			},
  2968  
  2969  			ExpectedDiff: nil,
  2970  
  2971  			Err: false,
  2972  		},
  2973  		"#1 - Don't suppress diff by returning false": {
  2974  			Schema: map[string]*Schema{
  2975  				"availability_zone": {
  2976  					Type:     TypeString,
  2977  					Optional: true,
  2978  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  2979  						// Always suppress any diff
  2980  						return false
  2981  					},
  2982  				},
  2983  			},
  2984  
  2985  			State: nil,
  2986  
  2987  			Config: map[string]interface{}{
  2988  				"availability_zone": "foo",
  2989  			},
  2990  
  2991  			ExpectedDiff: &terraform.InstanceDiff{
  2992  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2993  					"availability_zone": {
  2994  						Old: "",
  2995  						New: "foo",
  2996  					},
  2997  				},
  2998  			},
  2999  
  3000  			Err: false,
  3001  		},
  3002  	}
  3003  
  3004  	for tn, tc := range cases {
  3005  		c, err := config.NewRawConfig(tc.Config)
  3006  		if err != nil {
  3007  			t.Fatalf("#%q err: %s", tn, err)
  3008  		}
  3009  
  3010  		if len(tc.ConfigVariables) > 0 {
  3011  			if err := c.Interpolate(tc.ConfigVariables); err != nil {
  3012  				t.Fatalf("#%q err: %s", tn, err)
  3013  			}
  3014  		}
  3015  
  3016  		d, err := schemaMap(tc.Schema).Diff(
  3017  			tc.State, terraform.NewResourceConfig(c))
  3018  		if err != nil != tc.Err {
  3019  			t.Fatalf("#%q err: %s", tn, err)
  3020  		}
  3021  
  3022  		if !reflect.DeepEqual(tc.ExpectedDiff, d) {
  3023  			t.Fatalf("#%q:\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.ExpectedDiff, d)
  3024  		}
  3025  	}
  3026  }
  3027  
  3028  func TestSchemaMap_Validate(t *testing.T) {
  3029  	cases := map[string]struct {
  3030  		Schema   map[string]*Schema
  3031  		Config   map[string]interface{}
  3032  		Vars     map[string]string
  3033  		Err      bool
  3034  		Errors   []error
  3035  		Warnings []string
  3036  	}{
  3037  		"Good": {
  3038  			Schema: map[string]*Schema{
  3039  				"availability_zone": &Schema{
  3040  					Type:     TypeString,
  3041  					Optional: true,
  3042  					Computed: true,
  3043  					ForceNew: true,
  3044  				},
  3045  			},
  3046  
  3047  			Config: map[string]interface{}{
  3048  				"availability_zone": "foo",
  3049  			},
  3050  		},
  3051  
  3052  		"Good, because the var is not set and that error will come elsewhere": {
  3053  			Schema: map[string]*Schema{
  3054  				"size": &Schema{
  3055  					Type:     TypeInt,
  3056  					Required: true,
  3057  				},
  3058  			},
  3059  
  3060  			Config: map[string]interface{}{
  3061  				"size": "${var.foo}",
  3062  			},
  3063  
  3064  			Vars: map[string]string{
  3065  				"var.foo": config.UnknownVariableValue,
  3066  			},
  3067  		},
  3068  
  3069  		"Required field not set": {
  3070  			Schema: map[string]*Schema{
  3071  				"availability_zone": &Schema{
  3072  					Type:     TypeString,
  3073  					Required: true,
  3074  				},
  3075  			},
  3076  
  3077  			Config: map[string]interface{}{},
  3078  
  3079  			Err: true,
  3080  		},
  3081  
  3082  		"Invalid basic type": {
  3083  			Schema: map[string]*Schema{
  3084  				"port": &Schema{
  3085  					Type:     TypeInt,
  3086  					Required: true,
  3087  				},
  3088  			},
  3089  
  3090  			Config: map[string]interface{}{
  3091  				"port": "I am invalid",
  3092  			},
  3093  
  3094  			Err: true,
  3095  		},
  3096  
  3097  		"Invalid complex type": {
  3098  			Schema: map[string]*Schema{
  3099  				"user_data": &Schema{
  3100  					Type:     TypeString,
  3101  					Optional: true,
  3102  				},
  3103  			},
  3104  
  3105  			Config: map[string]interface{}{
  3106  				"user_data": []interface{}{
  3107  					map[string]interface{}{
  3108  						"foo": "bar",
  3109  					},
  3110  				},
  3111  			},
  3112  
  3113  			Err: true,
  3114  		},
  3115  
  3116  		"Bad type, interpolated": {
  3117  			Schema: map[string]*Schema{
  3118  				"size": &Schema{
  3119  					Type:     TypeInt,
  3120  					Required: true,
  3121  				},
  3122  			},
  3123  
  3124  			Config: map[string]interface{}{
  3125  				"size": "${var.foo}",
  3126  			},
  3127  
  3128  			Vars: map[string]string{
  3129  				"var.foo": "nope",
  3130  			},
  3131  
  3132  			Err: true,
  3133  		},
  3134  
  3135  		"Required but has DefaultFunc": {
  3136  			Schema: map[string]*Schema{
  3137  				"availability_zone": &Schema{
  3138  					Type:     TypeString,
  3139  					Required: true,
  3140  					DefaultFunc: func() (interface{}, error) {
  3141  						return "foo", nil
  3142  					},
  3143  				},
  3144  			},
  3145  
  3146  			Config: nil,
  3147  		},
  3148  
  3149  		"Required but has DefaultFunc return nil": {
  3150  			Schema: map[string]*Schema{
  3151  				"availability_zone": &Schema{
  3152  					Type:     TypeString,
  3153  					Required: true,
  3154  					DefaultFunc: func() (interface{}, error) {
  3155  						return nil, nil
  3156  					},
  3157  				},
  3158  			},
  3159  
  3160  			Config: nil,
  3161  
  3162  			Err: true,
  3163  		},
  3164  
  3165  		"Optional sub-resource": {
  3166  			Schema: map[string]*Schema{
  3167  				"ingress": &Schema{
  3168  					Type: TypeList,
  3169  					Elem: &Resource{
  3170  						Schema: map[string]*Schema{
  3171  							"from": &Schema{
  3172  								Type:     TypeInt,
  3173  								Required: true,
  3174  							},
  3175  						},
  3176  					},
  3177  				},
  3178  			},
  3179  
  3180  			Config: map[string]interface{}{},
  3181  
  3182  			Err: false,
  3183  		},
  3184  
  3185  		"Sub-resource is the wrong type": {
  3186  			Schema: map[string]*Schema{
  3187  				"ingress": &Schema{
  3188  					Type:     TypeList,
  3189  					Required: true,
  3190  					Elem: &Resource{
  3191  						Schema: map[string]*Schema{
  3192  							"from": &Schema{
  3193  								Type:     TypeInt,
  3194  								Required: true,
  3195  							},
  3196  						},
  3197  					},
  3198  				},
  3199  			},
  3200  
  3201  			Config: map[string]interface{}{
  3202  				"ingress": []interface{}{"foo"},
  3203  			},
  3204  
  3205  			Err: true,
  3206  		},
  3207  
  3208  		"Not a list": {
  3209  			Schema: map[string]*Schema{
  3210  				"ingress": &Schema{
  3211  					Type: TypeList,
  3212  					Elem: &Resource{
  3213  						Schema: map[string]*Schema{
  3214  							"from": &Schema{
  3215  								Type:     TypeInt,
  3216  								Required: true,
  3217  							},
  3218  						},
  3219  					},
  3220  				},
  3221  			},
  3222  
  3223  			Config: map[string]interface{}{
  3224  				"ingress": "foo",
  3225  			},
  3226  
  3227  			Err: true,
  3228  		},
  3229  
  3230  		"Required sub-resource field": {
  3231  			Schema: map[string]*Schema{
  3232  				"ingress": &Schema{
  3233  					Type: TypeList,
  3234  					Elem: &Resource{
  3235  						Schema: map[string]*Schema{
  3236  							"from": &Schema{
  3237  								Type:     TypeInt,
  3238  								Required: true,
  3239  							},
  3240  						},
  3241  					},
  3242  				},
  3243  			},
  3244  
  3245  			Config: map[string]interface{}{
  3246  				"ingress": []interface{}{
  3247  					map[string]interface{}{},
  3248  				},
  3249  			},
  3250  
  3251  			Err: true,
  3252  		},
  3253  
  3254  		"Good sub-resource": {
  3255  			Schema: map[string]*Schema{
  3256  				"ingress": &Schema{
  3257  					Type:     TypeList,
  3258  					Optional: true,
  3259  					Elem: &Resource{
  3260  						Schema: map[string]*Schema{
  3261  							"from": &Schema{
  3262  								Type:     TypeInt,
  3263  								Required: true,
  3264  							},
  3265  						},
  3266  					},
  3267  				},
  3268  			},
  3269  
  3270  			Config: map[string]interface{}{
  3271  				"ingress": []interface{}{
  3272  					map[string]interface{}{
  3273  						"from": 80,
  3274  					},
  3275  				},
  3276  			},
  3277  
  3278  			Err: false,
  3279  		},
  3280  
  3281  		"Invalid/unknown field": {
  3282  			Schema: map[string]*Schema{
  3283  				"availability_zone": &Schema{
  3284  					Type:     TypeString,
  3285  					Optional: true,
  3286  					Computed: true,
  3287  					ForceNew: true,
  3288  				},
  3289  			},
  3290  
  3291  			Config: map[string]interface{}{
  3292  				"foo": "bar",
  3293  			},
  3294  
  3295  			Err: true,
  3296  		},
  3297  
  3298  		"Invalid/unknown field with computed value": {
  3299  			Schema: map[string]*Schema{
  3300  				"availability_zone": &Schema{
  3301  					Type:     TypeString,
  3302  					Optional: true,
  3303  					Computed: true,
  3304  					ForceNew: true,
  3305  				},
  3306  			},
  3307  
  3308  			Config: map[string]interface{}{
  3309  				"foo": "${var.foo}",
  3310  			},
  3311  
  3312  			Vars: map[string]string{
  3313  				"var.foo": config.UnknownVariableValue,
  3314  			},
  3315  
  3316  			Err: true,
  3317  		},
  3318  
  3319  		"Computed field set": {
  3320  			Schema: map[string]*Schema{
  3321  				"availability_zone": &Schema{
  3322  					Type:     TypeString,
  3323  					Computed: true,
  3324  				},
  3325  			},
  3326  
  3327  			Config: map[string]interface{}{
  3328  				"availability_zone": "bar",
  3329  			},
  3330  
  3331  			Err: true,
  3332  		},
  3333  
  3334  		"Not a set": {
  3335  			Schema: map[string]*Schema{
  3336  				"ports": &Schema{
  3337  					Type:     TypeSet,
  3338  					Required: true,
  3339  					Elem:     &Schema{Type: TypeInt},
  3340  					Set: func(a interface{}) int {
  3341  						return a.(int)
  3342  					},
  3343  				},
  3344  			},
  3345  
  3346  			Config: map[string]interface{}{
  3347  				"ports": "foo",
  3348  			},
  3349  
  3350  			Err: true,
  3351  		},
  3352  
  3353  		"Maps": {
  3354  			Schema: map[string]*Schema{
  3355  				"user_data": &Schema{
  3356  					Type:     TypeMap,
  3357  					Optional: true,
  3358  				},
  3359  			},
  3360  
  3361  			Config: map[string]interface{}{
  3362  				"user_data": "foo",
  3363  			},
  3364  
  3365  			Err: true,
  3366  		},
  3367  
  3368  		"Good map: data surrounded by extra slice": {
  3369  			Schema: map[string]*Schema{
  3370  				"user_data": &Schema{
  3371  					Type:     TypeMap,
  3372  					Optional: true,
  3373  				},
  3374  			},
  3375  
  3376  			Config: map[string]interface{}{
  3377  				"user_data": []interface{}{
  3378  					map[string]interface{}{
  3379  						"foo": "bar",
  3380  					},
  3381  				},
  3382  			},
  3383  		},
  3384  
  3385  		"Good map": {
  3386  			Schema: map[string]*Schema{
  3387  				"user_data": &Schema{
  3388  					Type:     TypeMap,
  3389  					Optional: true,
  3390  				},
  3391  			},
  3392  
  3393  			Config: map[string]interface{}{
  3394  				"user_data": map[string]interface{}{
  3395  					"foo": "bar",
  3396  				},
  3397  			},
  3398  		},
  3399  
  3400  		"Bad map: just a slice": {
  3401  			Schema: map[string]*Schema{
  3402  				"user_data": &Schema{
  3403  					Type:     TypeMap,
  3404  					Optional: true,
  3405  				},
  3406  			},
  3407  
  3408  			Config: map[string]interface{}{
  3409  				"user_data": []interface{}{
  3410  					"foo",
  3411  				},
  3412  			},
  3413  
  3414  			Err: true,
  3415  		},
  3416  
  3417  		"Good set: config has slice with single interpolated value": {
  3418  			Schema: map[string]*Schema{
  3419  				"security_groups": &Schema{
  3420  					Type:     TypeSet,
  3421  					Optional: true,
  3422  					Computed: true,
  3423  					ForceNew: true,
  3424  					Elem:     &Schema{Type: TypeString},
  3425  					Set: func(v interface{}) int {
  3426  						return len(v.(string))
  3427  					},
  3428  				},
  3429  			},
  3430  
  3431  			Config: map[string]interface{}{
  3432  				"security_groups": []interface{}{"${var.foo}"},
  3433  			},
  3434  
  3435  			Err: false,
  3436  		},
  3437  
  3438  		"Bad set: config has single interpolated value": {
  3439  			Schema: map[string]*Schema{
  3440  				"security_groups": &Schema{
  3441  					Type:     TypeSet,
  3442  					Optional: true,
  3443  					Computed: true,
  3444  					ForceNew: true,
  3445  					Elem:     &Schema{Type: TypeString},
  3446  				},
  3447  			},
  3448  
  3449  			Config: map[string]interface{}{
  3450  				"security_groups": "${var.foo}",
  3451  			},
  3452  
  3453  			Err: true,
  3454  		},
  3455  
  3456  		"Bad, subresource should not allow unknown elements": {
  3457  			Schema: map[string]*Schema{
  3458  				"ingress": &Schema{
  3459  					Type:     TypeList,
  3460  					Optional: true,
  3461  					Elem: &Resource{
  3462  						Schema: map[string]*Schema{
  3463  							"port": &Schema{
  3464  								Type:     TypeInt,
  3465  								Required: true,
  3466  							},
  3467  						},
  3468  					},
  3469  				},
  3470  			},
  3471  
  3472  			Config: map[string]interface{}{
  3473  				"ingress": []interface{}{
  3474  					map[string]interface{}{
  3475  						"port":  80,
  3476  						"other": "yes",
  3477  					},
  3478  				},
  3479  			},
  3480  
  3481  			Err: true,
  3482  		},
  3483  
  3484  		"Bad, subresource should not allow invalid types": {
  3485  			Schema: map[string]*Schema{
  3486  				"ingress": &Schema{
  3487  					Type:     TypeList,
  3488  					Optional: true,
  3489  					Elem: &Resource{
  3490  						Schema: map[string]*Schema{
  3491  							"port": &Schema{
  3492  								Type:     TypeInt,
  3493  								Required: true,
  3494  							},
  3495  						},
  3496  					},
  3497  				},
  3498  			},
  3499  
  3500  			Config: map[string]interface{}{
  3501  				"ingress": []interface{}{
  3502  					map[string]interface{}{
  3503  						"port": "bad",
  3504  					},
  3505  				},
  3506  			},
  3507  
  3508  			Err: true,
  3509  		},
  3510  
  3511  		"Bad, should not allow lists to be assigned to string attributes": {
  3512  			Schema: map[string]*Schema{
  3513  				"availability_zone": &Schema{
  3514  					Type:     TypeString,
  3515  					Required: true,
  3516  				},
  3517  			},
  3518  
  3519  			Config: map[string]interface{}{
  3520  				"availability_zone": []interface{}{"foo", "bar", "baz"},
  3521  			},
  3522  
  3523  			Err: true,
  3524  		},
  3525  
  3526  		"Bad, should not allow maps to be assigned to string attributes": {
  3527  			Schema: map[string]*Schema{
  3528  				"availability_zone": &Schema{
  3529  					Type:     TypeString,
  3530  					Required: true,
  3531  				},
  3532  			},
  3533  
  3534  			Config: map[string]interface{}{
  3535  				"availability_zone": map[string]interface{}{"foo": "bar", "baz": "thing"},
  3536  			},
  3537  
  3538  			Err: true,
  3539  		},
  3540  
  3541  		"Deprecated attribute usage generates warning, but not error": {
  3542  			Schema: map[string]*Schema{
  3543  				"old_news": &Schema{
  3544  					Type:       TypeString,
  3545  					Optional:   true,
  3546  					Deprecated: "please use 'new_news' instead",
  3547  				},
  3548  			},
  3549  
  3550  			Config: map[string]interface{}{
  3551  				"old_news": "extra extra!",
  3552  			},
  3553  
  3554  			Err: false,
  3555  
  3556  			Warnings: []string{
  3557  				"\"old_news\": [DEPRECATED] please use 'new_news' instead",
  3558  			},
  3559  		},
  3560  
  3561  		"Deprecated generates no warnings if attr not used": {
  3562  			Schema: map[string]*Schema{
  3563  				"old_news": &Schema{
  3564  					Type:       TypeString,
  3565  					Optional:   true,
  3566  					Deprecated: "please use 'new_news' instead",
  3567  				},
  3568  			},
  3569  
  3570  			Err: false,
  3571  
  3572  			Warnings: nil,
  3573  		},
  3574  
  3575  		"Removed attribute usage generates error": {
  3576  			Schema: map[string]*Schema{
  3577  				"long_gone": &Schema{
  3578  					Type:     TypeString,
  3579  					Optional: true,
  3580  					Removed:  "no longer supported by Cloud API",
  3581  				},
  3582  			},
  3583  
  3584  			Config: map[string]interface{}{
  3585  				"long_gone": "still here!",
  3586  			},
  3587  
  3588  			Err: true,
  3589  			Errors: []error{
  3590  				fmt.Errorf("\"long_gone\": [REMOVED] no longer supported by Cloud API"),
  3591  			},
  3592  		},
  3593  
  3594  		"Removed generates no errors if attr not used": {
  3595  			Schema: map[string]*Schema{
  3596  				"long_gone": &Schema{
  3597  					Type:     TypeString,
  3598  					Optional: true,
  3599  					Removed:  "no longer supported by Cloud API",
  3600  				},
  3601  			},
  3602  
  3603  			Err: false,
  3604  		},
  3605  
  3606  		"Conflicting attributes generate error": {
  3607  			Schema: map[string]*Schema{
  3608  				"whitelist": &Schema{
  3609  					Type:     TypeString,
  3610  					Optional: true,
  3611  				},
  3612  				"blacklist": &Schema{
  3613  					Type:          TypeString,
  3614  					Optional:      true,
  3615  					ConflictsWith: []string{"whitelist"},
  3616  				},
  3617  			},
  3618  
  3619  			Config: map[string]interface{}{
  3620  				"whitelist": "white-val",
  3621  				"blacklist": "black-val",
  3622  			},
  3623  
  3624  			Err: true,
  3625  			Errors: []error{
  3626  				fmt.Errorf("\"blacklist\": conflicts with whitelist (\"white-val\")"),
  3627  			},
  3628  		},
  3629  
  3630  		"Required attribute & undefined conflicting optional are good": {
  3631  			Schema: map[string]*Schema{
  3632  				"required_att": &Schema{
  3633  					Type:     TypeString,
  3634  					Required: true,
  3635  				},
  3636  				"optional_att": &Schema{
  3637  					Type:          TypeString,
  3638  					Optional:      true,
  3639  					ConflictsWith: []string{"required_att"},
  3640  				},
  3641  			},
  3642  
  3643  			Config: map[string]interface{}{
  3644  				"required_att": "required-val",
  3645  			},
  3646  
  3647  			Err: false,
  3648  		},
  3649  
  3650  		"Required conflicting attribute & defined optional generate error": {
  3651  			Schema: map[string]*Schema{
  3652  				"required_att": &Schema{
  3653  					Type:     TypeString,
  3654  					Required: true,
  3655  				},
  3656  				"optional_att": &Schema{
  3657  					Type:          TypeString,
  3658  					Optional:      true,
  3659  					ConflictsWith: []string{"required_att"},
  3660  				},
  3661  			},
  3662  
  3663  			Config: map[string]interface{}{
  3664  				"required_att": "required-val",
  3665  				"optional_att": "optional-val",
  3666  			},
  3667  
  3668  			Err: true,
  3669  			Errors: []error{
  3670  				fmt.Errorf(`"optional_att": conflicts with required_att ("required-val")`),
  3671  			},
  3672  		},
  3673  
  3674  		"Good with ValidateFunc": {
  3675  			Schema: map[string]*Schema{
  3676  				"validate_me": &Schema{
  3677  					Type:     TypeString,
  3678  					Required: true,
  3679  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  3680  						return
  3681  					},
  3682  				},
  3683  			},
  3684  			Config: map[string]interface{}{
  3685  				"validate_me": "valid",
  3686  			},
  3687  			Err: false,
  3688  		},
  3689  
  3690  		"Bad with ValidateFunc": {
  3691  			Schema: map[string]*Schema{
  3692  				"validate_me": &Schema{
  3693  					Type:     TypeString,
  3694  					Required: true,
  3695  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  3696  						es = append(es, fmt.Errorf("something is not right here"))
  3697  						return
  3698  					},
  3699  				},
  3700  			},
  3701  			Config: map[string]interface{}{
  3702  				"validate_me": "invalid",
  3703  			},
  3704  			Err: true,
  3705  			Errors: []error{
  3706  				fmt.Errorf(`something is not right here`),
  3707  			},
  3708  		},
  3709  
  3710  		"ValidateFunc not called when type does not match": {
  3711  			Schema: map[string]*Schema{
  3712  				"number": &Schema{
  3713  					Type:     TypeInt,
  3714  					Required: true,
  3715  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  3716  						t.Fatalf("Should not have gotten validate call")
  3717  						return
  3718  					},
  3719  				},
  3720  			},
  3721  			Config: map[string]interface{}{
  3722  				"number": "NaN",
  3723  			},
  3724  			Err: true,
  3725  		},
  3726  
  3727  		"ValidateFunc gets decoded type": {
  3728  			Schema: map[string]*Schema{
  3729  				"maybe": &Schema{
  3730  					Type:     TypeBool,
  3731  					Required: true,
  3732  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  3733  						if _, ok := v.(bool); !ok {
  3734  							t.Fatalf("Expected bool, got: %#v", v)
  3735  						}
  3736  						return
  3737  					},
  3738  				},
  3739  			},
  3740  			Config: map[string]interface{}{
  3741  				"maybe": "true",
  3742  			},
  3743  		},
  3744  
  3745  		"ValidateFunc is not called with a computed value": {
  3746  			Schema: map[string]*Schema{
  3747  				"validate_me": &Schema{
  3748  					Type:     TypeString,
  3749  					Required: true,
  3750  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  3751  						es = append(es, fmt.Errorf("something is not right here"))
  3752  						return
  3753  					},
  3754  				},
  3755  			},
  3756  			Config: map[string]interface{}{
  3757  				"validate_me": "${var.foo}",
  3758  			},
  3759  			Vars: map[string]string{
  3760  				"var.foo": config.UnknownVariableValue,
  3761  			},
  3762  
  3763  			Err: false,
  3764  		},
  3765  	}
  3766  
  3767  	for tn, tc := range cases {
  3768  		c, err := config.NewRawConfig(tc.Config)
  3769  		if err != nil {
  3770  			t.Fatalf("err: %s", err)
  3771  		}
  3772  		if tc.Vars != nil {
  3773  			vars := make(map[string]ast.Variable)
  3774  			for k, v := range tc.Vars {
  3775  				vars[k] = ast.Variable{Value: v, Type: ast.TypeString}
  3776  			}
  3777  
  3778  			if err := c.Interpolate(vars); err != nil {
  3779  				t.Fatalf("err: %s", err)
  3780  			}
  3781  		}
  3782  
  3783  		ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c))
  3784  		if len(es) > 0 != tc.Err {
  3785  			if len(es) == 0 {
  3786  				t.Errorf("%q: no errors", tn)
  3787  			}
  3788  
  3789  			for _, e := range es {
  3790  				t.Errorf("%q: err: %s", tn, e)
  3791  			}
  3792  
  3793  			t.FailNow()
  3794  		}
  3795  
  3796  		if !reflect.DeepEqual(ws, tc.Warnings) {
  3797  			t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws)
  3798  		}
  3799  
  3800  		if tc.Errors != nil {
  3801  			if !reflect.DeepEqual(es, tc.Errors) {
  3802  				t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es)
  3803  			}
  3804  		}
  3805  	}
  3806  }
  3807  
  3808  func TestSchemaSet_ValidateMaxItems(t *testing.T) {
  3809  	cases := map[string]struct {
  3810  		Schema          map[string]*Schema
  3811  		State           *terraform.InstanceState
  3812  		Config          map[string]interface{}
  3813  		ConfigVariables map[string]string
  3814  		Diff            *terraform.InstanceDiff
  3815  		Err             bool
  3816  		Errors          []error
  3817  	}{
  3818  		"#0": {
  3819  			Schema: map[string]*Schema{
  3820  				"aliases": &Schema{
  3821  					Type:     TypeSet,
  3822  					Optional: true,
  3823  					MaxItems: 1,
  3824  					Elem:     &Schema{Type: TypeString},
  3825  				},
  3826  			},
  3827  			State: nil,
  3828  			Config: map[string]interface{}{
  3829  				"aliases": []interface{}{"foo", "bar"},
  3830  			},
  3831  			Diff: nil,
  3832  			Err:  true,
  3833  			Errors: []error{
  3834  				fmt.Errorf("aliases: attribute supports 1 item maximum, config has 2 declared"),
  3835  			},
  3836  		},
  3837  		"#1": {
  3838  			Schema: map[string]*Schema{
  3839  				"aliases": &Schema{
  3840  					Type:     TypeSet,
  3841  					Optional: true,
  3842  					Elem:     &Schema{Type: TypeString},
  3843  				},
  3844  			},
  3845  			State: nil,
  3846  			Config: map[string]interface{}{
  3847  				"aliases": []interface{}{"foo", "bar"},
  3848  			},
  3849  			Diff:   nil,
  3850  			Err:    false,
  3851  			Errors: nil,
  3852  		},
  3853  		"#2": {
  3854  			Schema: map[string]*Schema{
  3855  				"aliases": &Schema{
  3856  					Type:     TypeSet,
  3857  					Optional: true,
  3858  					MaxItems: 1,
  3859  					Elem:     &Schema{Type: TypeString},
  3860  				},
  3861  			},
  3862  			State: nil,
  3863  			Config: map[string]interface{}{
  3864  				"aliases": []interface{}{"foo"},
  3865  			},
  3866  			Diff:   nil,
  3867  			Err:    false,
  3868  			Errors: nil,
  3869  		},
  3870  	}
  3871  
  3872  	for tn, tc := range cases {
  3873  		c, err := config.NewRawConfig(tc.Config)
  3874  		if err != nil {
  3875  			t.Fatalf("%q: err: %s", tn, err)
  3876  		}
  3877  		_, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c))
  3878  
  3879  		if len(es) > 0 != tc.Err {
  3880  			if len(es) == 0 {
  3881  				t.Errorf("%q: no errors", tn)
  3882  			}
  3883  
  3884  			for _, e := range es {
  3885  				t.Errorf("%q: err: %s", tn, e)
  3886  			}
  3887  
  3888  			t.FailNow()
  3889  		}
  3890  
  3891  		if tc.Errors != nil {
  3892  			if !reflect.DeepEqual(es, tc.Errors) {
  3893  				t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es)
  3894  			}
  3895  		}
  3896  	}
  3897  }