github.com/alexissmirnov/terraform@v0.4.3-0.20150423153700-1ef9731a2f14/helper/schema/schema_test.go (about)

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