github.com/pmcatominey/terraform@v0.7.0-rc2.0.20160708105029-1401a52a5cc5/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  
  2405  	for tn, tc := range cases {
  2406  		c, err := config.NewRawConfig(tc.Config)
  2407  		if err != nil {
  2408  			t.Fatalf("#%q err: %s", tn, err)
  2409  		}
  2410  
  2411  		if len(tc.ConfigVariables) > 0 {
  2412  			if err := c.Interpolate(tc.ConfigVariables); err != nil {
  2413  				t.Fatalf("#%q err: %s", tn, err)
  2414  			}
  2415  		}
  2416  
  2417  		d, err := schemaMap(tc.Schema).Diff(
  2418  			tc.State, terraform.NewResourceConfig(c))
  2419  		if err != nil != tc.Err {
  2420  			t.Fatalf("#%q err: %s", tn, err)
  2421  		}
  2422  
  2423  		if !reflect.DeepEqual(tc.Diff, d) {
  2424  			t.Fatalf("#%q:\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.Diff, d)
  2425  		}
  2426  	}
  2427  }
  2428  
  2429  func TestSchemaMap_Input(t *testing.T) {
  2430  	cases := map[string]struct {
  2431  		Schema map[string]*Schema
  2432  		Config map[string]interface{}
  2433  		Input  map[string]string
  2434  		Result map[string]interface{}
  2435  		Err    bool
  2436  	}{
  2437  		/*
  2438  		 * String decode
  2439  		 */
  2440  
  2441  		"uses input on optional field with no config": {
  2442  			Schema: map[string]*Schema{
  2443  				"availability_zone": &Schema{
  2444  					Type:     TypeString,
  2445  					Optional: true,
  2446  				},
  2447  			},
  2448  
  2449  			Input: map[string]string{
  2450  				"availability_zone": "foo",
  2451  			},
  2452  
  2453  			Result: map[string]interface{}{
  2454  				"availability_zone": "foo",
  2455  			},
  2456  
  2457  			Err: false,
  2458  		},
  2459  
  2460  		"input ignored when config has a value": {
  2461  			Schema: map[string]*Schema{
  2462  				"availability_zone": &Schema{
  2463  					Type:     TypeString,
  2464  					Optional: true,
  2465  				},
  2466  			},
  2467  
  2468  			Config: map[string]interface{}{
  2469  				"availability_zone": "bar",
  2470  			},
  2471  
  2472  			Input: map[string]string{
  2473  				"availability_zone": "foo",
  2474  			},
  2475  
  2476  			Result: map[string]interface{}{},
  2477  
  2478  			Err: false,
  2479  		},
  2480  
  2481  		"input ignored when schema has a default": {
  2482  			Schema: map[string]*Schema{
  2483  				"availability_zone": &Schema{
  2484  					Type:     TypeString,
  2485  					Default:  "foo",
  2486  					Optional: true,
  2487  				},
  2488  			},
  2489  
  2490  			Input: map[string]string{
  2491  				"availability_zone": "bar",
  2492  			},
  2493  
  2494  			Result: map[string]interface{}{},
  2495  
  2496  			Err: false,
  2497  		},
  2498  
  2499  		"input ignored when default function returns a value": {
  2500  			Schema: map[string]*Schema{
  2501  				"availability_zone": &Schema{
  2502  					Type: TypeString,
  2503  					DefaultFunc: func() (interface{}, error) {
  2504  						return "foo", nil
  2505  					},
  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 an empty string": {
  2520  			Schema: map[string]*Schema{
  2521  				"availability_zone": &Schema{
  2522  					Type:     TypeString,
  2523  					Default:  "",
  2524  					Optional: true,
  2525  				},
  2526  			},
  2527  
  2528  			Input: map[string]string{
  2529  				"availability_zone": "bar",
  2530  			},
  2531  
  2532  			Result: map[string]interface{}{},
  2533  
  2534  			Err: false,
  2535  		},
  2536  
  2537  		"input used when default function returns nil": {
  2538  			Schema: map[string]*Schema{
  2539  				"availability_zone": &Schema{
  2540  					Type: TypeString,
  2541  					DefaultFunc: func() (interface{}, error) {
  2542  						return nil, nil
  2543  					},
  2544  					Optional: true,
  2545  				},
  2546  			},
  2547  
  2548  			Input: map[string]string{
  2549  				"availability_zone": "bar",
  2550  			},
  2551  
  2552  			Result: map[string]interface{}{
  2553  				"availability_zone": "bar",
  2554  			},
  2555  
  2556  			Err: false,
  2557  		},
  2558  	}
  2559  
  2560  	for i, tc := range cases {
  2561  		if tc.Config == nil {
  2562  			tc.Config = make(map[string]interface{})
  2563  		}
  2564  
  2565  		c, err := config.NewRawConfig(tc.Config)
  2566  		if err != nil {
  2567  			t.Fatalf("err: %s", err)
  2568  		}
  2569  
  2570  		input := new(terraform.MockUIInput)
  2571  		input.InputReturnMap = tc.Input
  2572  
  2573  		rc := terraform.NewResourceConfig(c)
  2574  		rc.Config = make(map[string]interface{})
  2575  
  2576  		actual, err := schemaMap(tc.Schema).Input(input, rc)
  2577  		if err != nil != tc.Err {
  2578  			t.Fatalf("#%v err: %s", i, err)
  2579  		}
  2580  
  2581  		if !reflect.DeepEqual(tc.Result, actual.Config) {
  2582  			t.Fatalf("#%v: bad:\n\ngot: %#v\nexpected: %#v", i, actual.Config, tc.Result)
  2583  		}
  2584  	}
  2585  }
  2586  
  2587  func TestSchemaMap_InputDefault(t *testing.T) {
  2588  	emptyConfig := make(map[string]interface{})
  2589  	c, err := config.NewRawConfig(emptyConfig)
  2590  	if err != nil {
  2591  		t.Fatalf("err: %s", err)
  2592  	}
  2593  	rc := terraform.NewResourceConfig(c)
  2594  	rc.Config = make(map[string]interface{})
  2595  
  2596  	input := new(terraform.MockUIInput)
  2597  	input.InputFn = func(opts *terraform.InputOpts) (string, error) {
  2598  		t.Fatalf("InputFn should not be called on: %#v", opts)
  2599  		return "", nil
  2600  	}
  2601  
  2602  	schema := map[string]*Schema{
  2603  		"availability_zone": &Schema{
  2604  			Type:     TypeString,
  2605  			Default:  "foo",
  2606  			Optional: true,
  2607  		},
  2608  	}
  2609  	actual, err := schemaMap(schema).Input(input, rc)
  2610  	if err != nil {
  2611  		t.Fatalf("err: %s", err)
  2612  	}
  2613  
  2614  	expected := map[string]interface{}{}
  2615  
  2616  	if !reflect.DeepEqual(expected, actual.Config) {
  2617  		t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected)
  2618  	}
  2619  }
  2620  
  2621  func TestSchemaMap_InputDeprecated(t *testing.T) {
  2622  	emptyConfig := make(map[string]interface{})
  2623  	c, err := config.NewRawConfig(emptyConfig)
  2624  	if err != nil {
  2625  		t.Fatalf("err: %s", err)
  2626  	}
  2627  	rc := terraform.NewResourceConfig(c)
  2628  	rc.Config = make(map[string]interface{})
  2629  
  2630  	input := new(terraform.MockUIInput)
  2631  	input.InputFn = func(opts *terraform.InputOpts) (string, error) {
  2632  		t.Fatalf("InputFn should not be called on: %#v", opts)
  2633  		return "", nil
  2634  	}
  2635  
  2636  	schema := map[string]*Schema{
  2637  		"availability_zone": &Schema{
  2638  			Type:       TypeString,
  2639  			Deprecated: "long gone",
  2640  			Optional:   true,
  2641  		},
  2642  	}
  2643  	actual, err := schemaMap(schema).Input(input, rc)
  2644  	if err != nil {
  2645  		t.Fatalf("err: %s", err)
  2646  	}
  2647  
  2648  	expected := map[string]interface{}{}
  2649  
  2650  	if !reflect.DeepEqual(expected, actual.Config) {
  2651  		t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected)
  2652  	}
  2653  }
  2654  
  2655  func TestSchemaMap_InternalValidate(t *testing.T) {
  2656  	cases := map[string]struct {
  2657  		In  map[string]*Schema
  2658  		Err bool
  2659  	}{
  2660  		"nothing": {
  2661  			nil,
  2662  			false,
  2663  		},
  2664  
  2665  		"Both optional and required": {
  2666  			map[string]*Schema{
  2667  				"foo": &Schema{
  2668  					Type:     TypeInt,
  2669  					Optional: true,
  2670  					Required: true,
  2671  				},
  2672  			},
  2673  			true,
  2674  		},
  2675  
  2676  		"No optional and no required": {
  2677  			map[string]*Schema{
  2678  				"foo": &Schema{
  2679  					Type: TypeInt,
  2680  				},
  2681  			},
  2682  			true,
  2683  		},
  2684  
  2685  		"Missing Type": {
  2686  			map[string]*Schema{
  2687  				"foo": &Schema{
  2688  					Required: true,
  2689  				},
  2690  			},
  2691  			true,
  2692  		},
  2693  
  2694  		"Required but computed": {
  2695  			map[string]*Schema{
  2696  				"foo": &Schema{
  2697  					Type:     TypeInt,
  2698  					Required: true,
  2699  					Computed: true,
  2700  				},
  2701  			},
  2702  			true,
  2703  		},
  2704  
  2705  		"Looks good": {
  2706  			map[string]*Schema{
  2707  				"foo": &Schema{
  2708  					Type:     TypeString,
  2709  					Required: true,
  2710  				},
  2711  			},
  2712  			false,
  2713  		},
  2714  
  2715  		"Computed but has default": {
  2716  			map[string]*Schema{
  2717  				"foo": &Schema{
  2718  					Type:     TypeInt,
  2719  					Optional: true,
  2720  					Computed: true,
  2721  					Default:  "foo",
  2722  				},
  2723  			},
  2724  			true,
  2725  		},
  2726  
  2727  		"Required but has default": {
  2728  			map[string]*Schema{
  2729  				"foo": &Schema{
  2730  					Type:     TypeInt,
  2731  					Optional: true,
  2732  					Required: true,
  2733  					Default:  "foo",
  2734  				},
  2735  			},
  2736  			true,
  2737  		},
  2738  
  2739  		"List element not set": {
  2740  			map[string]*Schema{
  2741  				"foo": &Schema{
  2742  					Type: TypeList,
  2743  				},
  2744  			},
  2745  			true,
  2746  		},
  2747  
  2748  		"List default": {
  2749  			map[string]*Schema{
  2750  				"foo": &Schema{
  2751  					Type:    TypeList,
  2752  					Elem:    &Schema{Type: TypeInt},
  2753  					Default: "foo",
  2754  				},
  2755  			},
  2756  			true,
  2757  		},
  2758  
  2759  		"List element computed": {
  2760  			map[string]*Schema{
  2761  				"foo": &Schema{
  2762  					Type:     TypeList,
  2763  					Optional: true,
  2764  					Elem: &Schema{
  2765  						Type:     TypeInt,
  2766  						Computed: true,
  2767  					},
  2768  				},
  2769  			},
  2770  			true,
  2771  		},
  2772  
  2773  		"List element with Set set": {
  2774  			map[string]*Schema{
  2775  				"foo": &Schema{
  2776  					Type:     TypeList,
  2777  					Elem:     &Schema{Type: TypeInt},
  2778  					Set:      func(interface{}) int { return 0 },
  2779  					Optional: true,
  2780  				},
  2781  			},
  2782  			true,
  2783  		},
  2784  
  2785  		"Set element with no Set set": {
  2786  			map[string]*Schema{
  2787  				"foo": &Schema{
  2788  					Type:     TypeSet,
  2789  					Elem:     &Schema{Type: TypeInt},
  2790  					Optional: true,
  2791  				},
  2792  			},
  2793  			false,
  2794  		},
  2795  
  2796  		"Required but computedWhen": {
  2797  			map[string]*Schema{
  2798  				"foo": &Schema{
  2799  					Type:         TypeInt,
  2800  					Required:     true,
  2801  					ComputedWhen: []string{"foo"},
  2802  				},
  2803  			},
  2804  			true,
  2805  		},
  2806  
  2807  		"Conflicting attributes cannot be required": {
  2808  			map[string]*Schema{
  2809  				"blacklist": &Schema{
  2810  					Type:     TypeBool,
  2811  					Required: true,
  2812  				},
  2813  				"whitelist": &Schema{
  2814  					Type:          TypeBool,
  2815  					Optional:      true,
  2816  					ConflictsWith: []string{"blacklist"},
  2817  				},
  2818  			},
  2819  			true,
  2820  		},
  2821  
  2822  		"Attribute with conflicts cannot be required": {
  2823  			map[string]*Schema{
  2824  				"whitelist": &Schema{
  2825  					Type:          TypeBool,
  2826  					Required:      true,
  2827  					ConflictsWith: []string{"blacklist"},
  2828  				},
  2829  			},
  2830  			true,
  2831  		},
  2832  
  2833  		"ConflictsWith cannot be used w/ Computed": {
  2834  			map[string]*Schema{
  2835  				"blacklist": &Schema{
  2836  					Type:     TypeBool,
  2837  					Computed: true,
  2838  				},
  2839  				"whitelist": &Schema{
  2840  					Type:          TypeBool,
  2841  					Optional:      true,
  2842  					ConflictsWith: []string{"blacklist"},
  2843  				},
  2844  			},
  2845  			true,
  2846  		},
  2847  
  2848  		"ConflictsWith cannot be used w/ ComputedWhen": {
  2849  			map[string]*Schema{
  2850  				"blacklist": &Schema{
  2851  					Type:         TypeBool,
  2852  					ComputedWhen: []string{"foor"},
  2853  				},
  2854  				"whitelist": &Schema{
  2855  					Type:          TypeBool,
  2856  					Required:      true,
  2857  					ConflictsWith: []string{"blacklist"},
  2858  				},
  2859  			},
  2860  			true,
  2861  		},
  2862  
  2863  		"Sub-resource invalid": {
  2864  			map[string]*Schema{
  2865  				"foo": &Schema{
  2866  					Type:     TypeList,
  2867  					Optional: true,
  2868  					Elem: &Resource{
  2869  						Schema: map[string]*Schema{
  2870  							"foo": new(Schema),
  2871  						},
  2872  					},
  2873  				},
  2874  			},
  2875  			true,
  2876  		},
  2877  
  2878  		"Sub-resource valid": {
  2879  			map[string]*Schema{
  2880  				"foo": &Schema{
  2881  					Type:     TypeList,
  2882  					Optional: true,
  2883  					Elem: &Resource{
  2884  						Schema: map[string]*Schema{
  2885  							"foo": &Schema{
  2886  								Type:     TypeInt,
  2887  								Optional: true,
  2888  							},
  2889  						},
  2890  					},
  2891  				},
  2892  			},
  2893  			false,
  2894  		},
  2895  
  2896  		"ValidateFunc on non-primitive": {
  2897  			map[string]*Schema{
  2898  				"foo": &Schema{
  2899  					Type:     TypeSet,
  2900  					Required: true,
  2901  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  2902  						return
  2903  					},
  2904  				},
  2905  			},
  2906  			true,
  2907  		},
  2908  	}
  2909  
  2910  	for tn, tc := range cases {
  2911  		err := schemaMap(tc.In).InternalValidate(schemaMap{})
  2912  		if err != nil != tc.Err {
  2913  			if tc.Err {
  2914  				t.Fatalf("%q: Expected error did not occur:\n\n%#v", tn, tc.In)
  2915  			}
  2916  			t.Fatalf("%q: Unexpected error occurred:\n\n%#v", tn, tc.In)
  2917  		}
  2918  	}
  2919  
  2920  }
  2921  
  2922  func TestSchemaMap_Validate(t *testing.T) {
  2923  	cases := map[string]struct {
  2924  		Schema   map[string]*Schema
  2925  		Config   map[string]interface{}
  2926  		Vars     map[string]string
  2927  		Err      bool
  2928  		Errors   []error
  2929  		Warnings []string
  2930  	}{
  2931  		"Good": {
  2932  			Schema: map[string]*Schema{
  2933  				"availability_zone": &Schema{
  2934  					Type:     TypeString,
  2935  					Optional: true,
  2936  					Computed: true,
  2937  					ForceNew: true,
  2938  				},
  2939  			},
  2940  
  2941  			Config: map[string]interface{}{
  2942  				"availability_zone": "foo",
  2943  			},
  2944  		},
  2945  
  2946  		"Good, because the var is not set and that error will come elsewhere": {
  2947  			Schema: map[string]*Schema{
  2948  				"size": &Schema{
  2949  					Type:     TypeInt,
  2950  					Required: true,
  2951  				},
  2952  			},
  2953  
  2954  			Config: map[string]interface{}{
  2955  				"size": "${var.foo}",
  2956  			},
  2957  
  2958  			Vars: map[string]string{
  2959  				"var.foo": config.UnknownVariableValue,
  2960  			},
  2961  		},
  2962  
  2963  		"Required field not set": {
  2964  			Schema: map[string]*Schema{
  2965  				"availability_zone": &Schema{
  2966  					Type:     TypeString,
  2967  					Required: true,
  2968  				},
  2969  			},
  2970  
  2971  			Config: map[string]interface{}{},
  2972  
  2973  			Err: true,
  2974  		},
  2975  
  2976  		"Invalid basic type": {
  2977  			Schema: map[string]*Schema{
  2978  				"port": &Schema{
  2979  					Type:     TypeInt,
  2980  					Required: true,
  2981  				},
  2982  			},
  2983  
  2984  			Config: map[string]interface{}{
  2985  				"port": "I am invalid",
  2986  			},
  2987  
  2988  			Err: true,
  2989  		},
  2990  
  2991  		"Invalid complex type": {
  2992  			Schema: map[string]*Schema{
  2993  				"user_data": &Schema{
  2994  					Type:     TypeString,
  2995  					Optional: true,
  2996  				},
  2997  			},
  2998  
  2999  			Config: map[string]interface{}{
  3000  				"user_data": []interface{}{
  3001  					map[string]interface{}{
  3002  						"foo": "bar",
  3003  					},
  3004  				},
  3005  			},
  3006  
  3007  			Err: true,
  3008  		},
  3009  
  3010  		"Bad type, interpolated": {
  3011  			Schema: map[string]*Schema{
  3012  				"size": &Schema{
  3013  					Type:     TypeInt,
  3014  					Required: true,
  3015  				},
  3016  			},
  3017  
  3018  			Config: map[string]interface{}{
  3019  				"size": "${var.foo}",
  3020  			},
  3021  
  3022  			Vars: map[string]string{
  3023  				"var.foo": "nope",
  3024  			},
  3025  
  3026  			Err: true,
  3027  		},
  3028  
  3029  		"Required but has DefaultFunc": {
  3030  			Schema: map[string]*Schema{
  3031  				"availability_zone": &Schema{
  3032  					Type:     TypeString,
  3033  					Required: true,
  3034  					DefaultFunc: func() (interface{}, error) {
  3035  						return "foo", nil
  3036  					},
  3037  				},
  3038  			},
  3039  
  3040  			Config: nil,
  3041  		},
  3042  
  3043  		"Required but has DefaultFunc return nil": {
  3044  			Schema: map[string]*Schema{
  3045  				"availability_zone": &Schema{
  3046  					Type:     TypeString,
  3047  					Required: true,
  3048  					DefaultFunc: func() (interface{}, error) {
  3049  						return nil, nil
  3050  					},
  3051  				},
  3052  			},
  3053  
  3054  			Config: nil,
  3055  
  3056  			Err: true,
  3057  		},
  3058  
  3059  		"Optional sub-resource": {
  3060  			Schema: map[string]*Schema{
  3061  				"ingress": &Schema{
  3062  					Type: TypeList,
  3063  					Elem: &Resource{
  3064  						Schema: map[string]*Schema{
  3065  							"from": &Schema{
  3066  								Type:     TypeInt,
  3067  								Required: true,
  3068  							},
  3069  						},
  3070  					},
  3071  				},
  3072  			},
  3073  
  3074  			Config: map[string]interface{}{},
  3075  
  3076  			Err: false,
  3077  		},
  3078  
  3079  		"Sub-resource is the wrong type": {
  3080  			Schema: map[string]*Schema{
  3081  				"ingress": &Schema{
  3082  					Type:     TypeList,
  3083  					Required: true,
  3084  					Elem: &Resource{
  3085  						Schema: map[string]*Schema{
  3086  							"from": &Schema{
  3087  								Type:     TypeInt,
  3088  								Required: true,
  3089  							},
  3090  						},
  3091  					},
  3092  				},
  3093  			},
  3094  
  3095  			Config: map[string]interface{}{
  3096  				"ingress": []interface{}{"foo"},
  3097  			},
  3098  
  3099  			Err: true,
  3100  		},
  3101  
  3102  		"Not a list": {
  3103  			Schema: map[string]*Schema{
  3104  				"ingress": &Schema{
  3105  					Type: TypeList,
  3106  					Elem: &Resource{
  3107  						Schema: map[string]*Schema{
  3108  							"from": &Schema{
  3109  								Type:     TypeInt,
  3110  								Required: true,
  3111  							},
  3112  						},
  3113  					},
  3114  				},
  3115  			},
  3116  
  3117  			Config: map[string]interface{}{
  3118  				"ingress": "foo",
  3119  			},
  3120  
  3121  			Err: true,
  3122  		},
  3123  
  3124  		"Required sub-resource field": {
  3125  			Schema: map[string]*Schema{
  3126  				"ingress": &Schema{
  3127  					Type: TypeList,
  3128  					Elem: &Resource{
  3129  						Schema: map[string]*Schema{
  3130  							"from": &Schema{
  3131  								Type:     TypeInt,
  3132  								Required: true,
  3133  							},
  3134  						},
  3135  					},
  3136  				},
  3137  			},
  3138  
  3139  			Config: map[string]interface{}{
  3140  				"ingress": []interface{}{
  3141  					map[string]interface{}{},
  3142  				},
  3143  			},
  3144  
  3145  			Err: true,
  3146  		},
  3147  
  3148  		"Good sub-resource": {
  3149  			Schema: map[string]*Schema{
  3150  				"ingress": &Schema{
  3151  					Type:     TypeList,
  3152  					Optional: true,
  3153  					Elem: &Resource{
  3154  						Schema: map[string]*Schema{
  3155  							"from": &Schema{
  3156  								Type:     TypeInt,
  3157  								Required: true,
  3158  							},
  3159  						},
  3160  					},
  3161  				},
  3162  			},
  3163  
  3164  			Config: map[string]interface{}{
  3165  				"ingress": []interface{}{
  3166  					map[string]interface{}{
  3167  						"from": 80,
  3168  					},
  3169  				},
  3170  			},
  3171  
  3172  			Err: false,
  3173  		},
  3174  
  3175  		"Invalid/unknown field": {
  3176  			Schema: map[string]*Schema{
  3177  				"availability_zone": &Schema{
  3178  					Type:     TypeString,
  3179  					Optional: true,
  3180  					Computed: true,
  3181  					ForceNew: true,
  3182  				},
  3183  			},
  3184  
  3185  			Config: map[string]interface{}{
  3186  				"foo": "bar",
  3187  			},
  3188  
  3189  			Err: true,
  3190  		},
  3191  
  3192  		"Invalid/unknown field with computed value": {
  3193  			Schema: map[string]*Schema{
  3194  				"availability_zone": &Schema{
  3195  					Type:     TypeString,
  3196  					Optional: true,
  3197  					Computed: true,
  3198  					ForceNew: true,
  3199  				},
  3200  			},
  3201  
  3202  			Config: map[string]interface{}{
  3203  				"foo": "${var.foo}",
  3204  			},
  3205  
  3206  			Vars: map[string]string{
  3207  				"var.foo": config.UnknownVariableValue,
  3208  			},
  3209  
  3210  			Err: true,
  3211  		},
  3212  
  3213  		"Computed field set": {
  3214  			Schema: map[string]*Schema{
  3215  				"availability_zone": &Schema{
  3216  					Type:     TypeString,
  3217  					Computed: true,
  3218  				},
  3219  			},
  3220  
  3221  			Config: map[string]interface{}{
  3222  				"availability_zone": "bar",
  3223  			},
  3224  
  3225  			Err: true,
  3226  		},
  3227  
  3228  		"Not a set": {
  3229  			Schema: map[string]*Schema{
  3230  				"ports": &Schema{
  3231  					Type:     TypeSet,
  3232  					Required: true,
  3233  					Elem:     &Schema{Type: TypeInt},
  3234  					Set: func(a interface{}) int {
  3235  						return a.(int)
  3236  					},
  3237  				},
  3238  			},
  3239  
  3240  			Config: map[string]interface{}{
  3241  				"ports": "foo",
  3242  			},
  3243  
  3244  			Err: true,
  3245  		},
  3246  
  3247  		"Maps": {
  3248  			Schema: map[string]*Schema{
  3249  				"user_data": &Schema{
  3250  					Type:     TypeMap,
  3251  					Optional: true,
  3252  				},
  3253  			},
  3254  
  3255  			Config: map[string]interface{}{
  3256  				"user_data": "foo",
  3257  			},
  3258  
  3259  			Err: true,
  3260  		},
  3261  
  3262  		"Good map: data surrounded by extra slice": {
  3263  			Schema: map[string]*Schema{
  3264  				"user_data": &Schema{
  3265  					Type:     TypeMap,
  3266  					Optional: true,
  3267  				},
  3268  			},
  3269  
  3270  			Config: map[string]interface{}{
  3271  				"user_data": []interface{}{
  3272  					map[string]interface{}{
  3273  						"foo": "bar",
  3274  					},
  3275  				},
  3276  			},
  3277  		},
  3278  
  3279  		"Good map": {
  3280  			Schema: map[string]*Schema{
  3281  				"user_data": &Schema{
  3282  					Type:     TypeMap,
  3283  					Optional: true,
  3284  				},
  3285  			},
  3286  
  3287  			Config: map[string]interface{}{
  3288  				"user_data": map[string]interface{}{
  3289  					"foo": "bar",
  3290  				},
  3291  			},
  3292  		},
  3293  
  3294  		"Bad map: just a slice": {
  3295  			Schema: map[string]*Schema{
  3296  				"user_data": &Schema{
  3297  					Type:     TypeMap,
  3298  					Optional: true,
  3299  				},
  3300  			},
  3301  
  3302  			Config: map[string]interface{}{
  3303  				"user_data": []interface{}{
  3304  					"foo",
  3305  				},
  3306  			},
  3307  
  3308  			Err: true,
  3309  		},
  3310  
  3311  		"Good set: config has slice with single interpolated value": {
  3312  			Schema: map[string]*Schema{
  3313  				"security_groups": &Schema{
  3314  					Type:     TypeSet,
  3315  					Optional: true,
  3316  					Computed: true,
  3317  					ForceNew: true,
  3318  					Elem:     &Schema{Type: TypeString},
  3319  					Set: func(v interface{}) int {
  3320  						return len(v.(string))
  3321  					},
  3322  				},
  3323  			},
  3324  
  3325  			Config: map[string]interface{}{
  3326  				"security_groups": []interface{}{"${var.foo}"},
  3327  			},
  3328  
  3329  			Err: false,
  3330  		},
  3331  
  3332  		"Bad set: config has single interpolated value": {
  3333  			Schema: map[string]*Schema{
  3334  				"security_groups": &Schema{
  3335  					Type:     TypeSet,
  3336  					Optional: true,
  3337  					Computed: true,
  3338  					ForceNew: true,
  3339  					Elem:     &Schema{Type: TypeString},
  3340  				},
  3341  			},
  3342  
  3343  			Config: map[string]interface{}{
  3344  				"security_groups": "${var.foo}",
  3345  			},
  3346  
  3347  			Err: true,
  3348  		},
  3349  
  3350  		"Bad, subresource should not allow unknown elements": {
  3351  			Schema: map[string]*Schema{
  3352  				"ingress": &Schema{
  3353  					Type:     TypeList,
  3354  					Optional: true,
  3355  					Elem: &Resource{
  3356  						Schema: map[string]*Schema{
  3357  							"port": &Schema{
  3358  								Type:     TypeInt,
  3359  								Required: true,
  3360  							},
  3361  						},
  3362  					},
  3363  				},
  3364  			},
  3365  
  3366  			Config: map[string]interface{}{
  3367  				"ingress": []interface{}{
  3368  					map[string]interface{}{
  3369  						"port":  80,
  3370  						"other": "yes",
  3371  					},
  3372  				},
  3373  			},
  3374  
  3375  			Err: true,
  3376  		},
  3377  
  3378  		"Bad, subresource should not allow invalid types": {
  3379  			Schema: map[string]*Schema{
  3380  				"ingress": &Schema{
  3381  					Type:     TypeList,
  3382  					Optional: true,
  3383  					Elem: &Resource{
  3384  						Schema: map[string]*Schema{
  3385  							"port": &Schema{
  3386  								Type:     TypeInt,
  3387  								Required: true,
  3388  							},
  3389  						},
  3390  					},
  3391  				},
  3392  			},
  3393  
  3394  			Config: map[string]interface{}{
  3395  				"ingress": []interface{}{
  3396  					map[string]interface{}{
  3397  						"port": "bad",
  3398  					},
  3399  				},
  3400  			},
  3401  
  3402  			Err: true,
  3403  		},
  3404  
  3405  		"Bad, should not allow lists to be assigned to string attributes": {
  3406  			Schema: map[string]*Schema{
  3407  				"availability_zone": &Schema{
  3408  					Type:     TypeString,
  3409  					Required: true,
  3410  				},
  3411  			},
  3412  
  3413  			Config: map[string]interface{}{
  3414  				"availability_zone": []interface{}{"foo", "bar", "baz"},
  3415  			},
  3416  
  3417  			Err: true,
  3418  		},
  3419  
  3420  		"Bad, should not allow maps to be assigned to string attributes": {
  3421  			Schema: map[string]*Schema{
  3422  				"availability_zone": &Schema{
  3423  					Type:     TypeString,
  3424  					Required: true,
  3425  				},
  3426  			},
  3427  
  3428  			Config: map[string]interface{}{
  3429  				"availability_zone": map[string]interface{}{"foo": "bar", "baz": "thing"},
  3430  			},
  3431  
  3432  			Err: true,
  3433  		},
  3434  
  3435  		"Deprecated attribute usage generates warning, but not error": {
  3436  			Schema: map[string]*Schema{
  3437  				"old_news": &Schema{
  3438  					Type:       TypeString,
  3439  					Optional:   true,
  3440  					Deprecated: "please use 'new_news' instead",
  3441  				},
  3442  			},
  3443  
  3444  			Config: map[string]interface{}{
  3445  				"old_news": "extra extra!",
  3446  			},
  3447  
  3448  			Err: false,
  3449  
  3450  			Warnings: []string{
  3451  				"\"old_news\": [DEPRECATED] please use 'new_news' instead",
  3452  			},
  3453  		},
  3454  
  3455  		"Deprecated generates no warnings if attr not used": {
  3456  			Schema: map[string]*Schema{
  3457  				"old_news": &Schema{
  3458  					Type:       TypeString,
  3459  					Optional:   true,
  3460  					Deprecated: "please use 'new_news' instead",
  3461  				},
  3462  			},
  3463  
  3464  			Err: false,
  3465  
  3466  			Warnings: nil,
  3467  		},
  3468  
  3469  		"Removed attribute usage generates error": {
  3470  			Schema: map[string]*Schema{
  3471  				"long_gone": &Schema{
  3472  					Type:     TypeString,
  3473  					Optional: true,
  3474  					Removed:  "no longer supported by Cloud API",
  3475  				},
  3476  			},
  3477  
  3478  			Config: map[string]interface{}{
  3479  				"long_gone": "still here!",
  3480  			},
  3481  
  3482  			Err: true,
  3483  			Errors: []error{
  3484  				fmt.Errorf("\"long_gone\": [REMOVED] no longer supported by Cloud API"),
  3485  			},
  3486  		},
  3487  
  3488  		"Removed generates no errors if attr not used": {
  3489  			Schema: map[string]*Schema{
  3490  				"long_gone": &Schema{
  3491  					Type:     TypeString,
  3492  					Optional: true,
  3493  					Removed:  "no longer supported by Cloud API",
  3494  				},
  3495  			},
  3496  
  3497  			Err: false,
  3498  		},
  3499  
  3500  		"Conflicting attributes generate error": {
  3501  			Schema: map[string]*Schema{
  3502  				"whitelist": &Schema{
  3503  					Type:     TypeString,
  3504  					Optional: true,
  3505  				},
  3506  				"blacklist": &Schema{
  3507  					Type:          TypeString,
  3508  					Optional:      true,
  3509  					ConflictsWith: []string{"whitelist"},
  3510  				},
  3511  			},
  3512  
  3513  			Config: map[string]interface{}{
  3514  				"whitelist": "white-val",
  3515  				"blacklist": "black-val",
  3516  			},
  3517  
  3518  			Err: true,
  3519  			Errors: []error{
  3520  				fmt.Errorf("\"blacklist\": conflicts with whitelist (\"white-val\")"),
  3521  			},
  3522  		},
  3523  
  3524  		"Required attribute & undefined conflicting optional are good": {
  3525  			Schema: map[string]*Schema{
  3526  				"required_att": &Schema{
  3527  					Type:     TypeString,
  3528  					Required: true,
  3529  				},
  3530  				"optional_att": &Schema{
  3531  					Type:          TypeString,
  3532  					Optional:      true,
  3533  					ConflictsWith: []string{"required_att"},
  3534  				},
  3535  			},
  3536  
  3537  			Config: map[string]interface{}{
  3538  				"required_att": "required-val",
  3539  			},
  3540  
  3541  			Err: false,
  3542  		},
  3543  
  3544  		"Required conflicting attribute & defined optional generate error": {
  3545  			Schema: map[string]*Schema{
  3546  				"required_att": &Schema{
  3547  					Type:     TypeString,
  3548  					Required: true,
  3549  				},
  3550  				"optional_att": &Schema{
  3551  					Type:          TypeString,
  3552  					Optional:      true,
  3553  					ConflictsWith: []string{"required_att"},
  3554  				},
  3555  			},
  3556  
  3557  			Config: map[string]interface{}{
  3558  				"required_att": "required-val",
  3559  				"optional_att": "optional-val",
  3560  			},
  3561  
  3562  			Err: true,
  3563  			Errors: []error{
  3564  				fmt.Errorf(`"optional_att": conflicts with required_att ("required-val")`),
  3565  			},
  3566  		},
  3567  
  3568  		"Good with ValidateFunc": {
  3569  			Schema: map[string]*Schema{
  3570  				"validate_me": &Schema{
  3571  					Type:     TypeString,
  3572  					Required: true,
  3573  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  3574  						return
  3575  					},
  3576  				},
  3577  			},
  3578  			Config: map[string]interface{}{
  3579  				"validate_me": "valid",
  3580  			},
  3581  			Err: false,
  3582  		},
  3583  
  3584  		"Bad with ValidateFunc": {
  3585  			Schema: map[string]*Schema{
  3586  				"validate_me": &Schema{
  3587  					Type:     TypeString,
  3588  					Required: true,
  3589  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  3590  						es = append(es, fmt.Errorf("something is not right here"))
  3591  						return
  3592  					},
  3593  				},
  3594  			},
  3595  			Config: map[string]interface{}{
  3596  				"validate_me": "invalid",
  3597  			},
  3598  			Err: true,
  3599  			Errors: []error{
  3600  				fmt.Errorf(`something is not right here`),
  3601  			},
  3602  		},
  3603  
  3604  		"ValidateFunc not called when type does not match": {
  3605  			Schema: map[string]*Schema{
  3606  				"number": &Schema{
  3607  					Type:     TypeInt,
  3608  					Required: true,
  3609  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  3610  						t.Fatalf("Should not have gotten validate call")
  3611  						return
  3612  					},
  3613  				},
  3614  			},
  3615  			Config: map[string]interface{}{
  3616  				"number": "NaN",
  3617  			},
  3618  			Err: true,
  3619  		},
  3620  
  3621  		"ValidateFunc gets decoded type": {
  3622  			Schema: map[string]*Schema{
  3623  				"maybe": &Schema{
  3624  					Type:     TypeBool,
  3625  					Required: true,
  3626  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  3627  						if _, ok := v.(bool); !ok {
  3628  							t.Fatalf("Expected bool, got: %#v", v)
  3629  						}
  3630  						return
  3631  					},
  3632  				},
  3633  			},
  3634  			Config: map[string]interface{}{
  3635  				"maybe": "true",
  3636  			},
  3637  		},
  3638  
  3639  		"ValidateFunc is not called with a computed value": {
  3640  			Schema: map[string]*Schema{
  3641  				"validate_me": &Schema{
  3642  					Type:     TypeString,
  3643  					Required: true,
  3644  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  3645  						es = append(es, fmt.Errorf("something is not right here"))
  3646  						return
  3647  					},
  3648  				},
  3649  			},
  3650  			Config: map[string]interface{}{
  3651  				"validate_me": "${var.foo}",
  3652  			},
  3653  			Vars: map[string]string{
  3654  				"var.foo": config.UnknownVariableValue,
  3655  			},
  3656  
  3657  			Err: false,
  3658  		},
  3659  	}
  3660  
  3661  	for tn, tc := range cases {
  3662  		c, err := config.NewRawConfig(tc.Config)
  3663  		if err != nil {
  3664  			t.Fatalf("err: %s", err)
  3665  		}
  3666  		if tc.Vars != nil {
  3667  			vars := make(map[string]ast.Variable)
  3668  			for k, v := range tc.Vars {
  3669  				vars[k] = ast.Variable{Value: v, Type: ast.TypeString}
  3670  			}
  3671  
  3672  			if err := c.Interpolate(vars); err != nil {
  3673  				t.Fatalf("err: %s", err)
  3674  			}
  3675  		}
  3676  
  3677  		ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c))
  3678  		if len(es) > 0 != tc.Err {
  3679  			if len(es) == 0 {
  3680  				t.Errorf("%q: no errors", tn)
  3681  			}
  3682  
  3683  			for _, e := range es {
  3684  				t.Errorf("%q: err: %s", tn, e)
  3685  			}
  3686  
  3687  			t.FailNow()
  3688  		}
  3689  
  3690  		if !reflect.DeepEqual(ws, tc.Warnings) {
  3691  			t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws)
  3692  		}
  3693  
  3694  		if tc.Errors != nil {
  3695  			if !reflect.DeepEqual(es, tc.Errors) {
  3696  				t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es)
  3697  			}
  3698  		}
  3699  	}
  3700  }
  3701  
  3702  func TestSchemaSet_ValidateMaxItems(t *testing.T) {
  3703  	cases := map[string]struct {
  3704  		Schema          map[string]*Schema
  3705  		State           *terraform.InstanceState
  3706  		Config          map[string]interface{}
  3707  		ConfigVariables map[string]string
  3708  		Diff            *terraform.InstanceDiff
  3709  		Err             bool
  3710  		Errors          []error
  3711  	}{
  3712  		"#0": {
  3713  			Schema: map[string]*Schema{
  3714  				"aliases": &Schema{
  3715  					Type:     TypeSet,
  3716  					Optional: true,
  3717  					MaxItems: 1,
  3718  					Elem:     &Schema{Type: TypeString},
  3719  				},
  3720  			},
  3721  			State: nil,
  3722  			Config: map[string]interface{}{
  3723  				"aliases": []interface{}{"foo", "bar"},
  3724  			},
  3725  			Diff: nil,
  3726  			Err:  true,
  3727  			Errors: []error{
  3728  				fmt.Errorf("aliases: attribute supports 1 item maximum, config has 2 declared"),
  3729  			},
  3730  		},
  3731  		"#1": {
  3732  			Schema: map[string]*Schema{
  3733  				"aliases": &Schema{
  3734  					Type:     TypeSet,
  3735  					Optional: true,
  3736  					Elem:     &Schema{Type: TypeString},
  3737  				},
  3738  			},
  3739  			State: nil,
  3740  			Config: map[string]interface{}{
  3741  				"aliases": []interface{}{"foo", "bar"},
  3742  			},
  3743  			Diff:   nil,
  3744  			Err:    false,
  3745  			Errors: nil,
  3746  		},
  3747  		"#2": {
  3748  			Schema: map[string]*Schema{
  3749  				"aliases": &Schema{
  3750  					Type:     TypeSet,
  3751  					Optional: true,
  3752  					MaxItems: 1,
  3753  					Elem:     &Schema{Type: TypeString},
  3754  				},
  3755  			},
  3756  			State: nil,
  3757  			Config: map[string]interface{}{
  3758  				"aliases": []interface{}{"foo"},
  3759  			},
  3760  			Diff:   nil,
  3761  			Err:    false,
  3762  			Errors: nil,
  3763  		},
  3764  	}
  3765  
  3766  	for tn, tc := range cases {
  3767  		c, err := config.NewRawConfig(tc.Config)
  3768  		if err != nil {
  3769  			t.Fatalf("%q: err: %s", tn, err)
  3770  		}
  3771  		_, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c))
  3772  
  3773  		if len(es) > 0 != tc.Err {
  3774  			if len(es) == 0 {
  3775  				t.Errorf("%q: no errors", tn)
  3776  			}
  3777  
  3778  			for _, e := range es {
  3779  				t.Errorf("%q: err: %s", tn, e)
  3780  			}
  3781  
  3782  			t.FailNow()
  3783  		}
  3784  
  3785  		if tc.Errors != nil {
  3786  			if !reflect.DeepEqual(es, tc.Errors) {
  3787  				t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es)
  3788  			}
  3789  		}
  3790  	}
  3791  }