github.com/tompao/terraform@v0.6.10-0.20180215233341-e41b29d0961b/helper/schema/schema_test.go (about)

     1  package schema
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"reflect"
     9  	"sort"
    10  	"strconv"
    11  	"strings"
    12  	"testing"
    13  
    14  	"github.com/hashicorp/hil"
    15  	"github.com/hashicorp/hil/ast"
    16  	"github.com/hashicorp/terraform/config"
    17  	"github.com/hashicorp/terraform/helper/hashcode"
    18  	"github.com/hashicorp/terraform/terraform"
    19  )
    20  
    21  func TestEnvDefaultFunc(t *testing.T) {
    22  	key := "TF_TEST_ENV_DEFAULT_FUNC"
    23  	defer os.Unsetenv(key)
    24  
    25  	f := EnvDefaultFunc(key, "42")
    26  	if err := os.Setenv(key, "foo"); err != nil {
    27  		t.Fatalf("err: %s", err)
    28  	}
    29  
    30  	actual, err := f()
    31  	if err != nil {
    32  		t.Fatalf("err: %s", err)
    33  	}
    34  	if actual != "foo" {
    35  		t.Fatalf("bad: %#v", actual)
    36  	}
    37  
    38  	if err := os.Unsetenv(key); err != nil {
    39  		t.Fatalf("err: %s", err)
    40  	}
    41  
    42  	actual, err = f()
    43  	if err != nil {
    44  		t.Fatalf("err: %s", err)
    45  	}
    46  	if actual != "42" {
    47  		t.Fatalf("bad: %#v", actual)
    48  	}
    49  }
    50  
    51  func TestMultiEnvDefaultFunc(t *testing.T) {
    52  	keys := []string{
    53  		"TF_TEST_MULTI_ENV_DEFAULT_FUNC1",
    54  		"TF_TEST_MULTI_ENV_DEFAULT_FUNC2",
    55  	}
    56  	defer func() {
    57  		for _, k := range keys {
    58  			os.Unsetenv(k)
    59  		}
    60  	}()
    61  
    62  	// Test that the first key is returned first
    63  	f := MultiEnvDefaultFunc(keys, "42")
    64  	if err := os.Setenv(keys[0], "foo"); err != nil {
    65  		t.Fatalf("err: %s", err)
    66  	}
    67  
    68  	actual, err := f()
    69  	if err != nil {
    70  		t.Fatalf("err: %s", err)
    71  	}
    72  	if actual != "foo" {
    73  		t.Fatalf("bad: %#v", actual)
    74  	}
    75  
    76  	if err := os.Unsetenv(keys[0]); err != nil {
    77  		t.Fatalf("err: %s", err)
    78  	}
    79  
    80  	// Test that the second key is returned if the first one is empty
    81  	f = MultiEnvDefaultFunc(keys, "42")
    82  	if err := os.Setenv(keys[1], "foo"); err != nil {
    83  		t.Fatalf("err: %s", err)
    84  	}
    85  
    86  	actual, err = f()
    87  	if err != nil {
    88  		t.Fatalf("err: %s", err)
    89  	}
    90  	if actual != "foo" {
    91  		t.Fatalf("bad: %#v", actual)
    92  	}
    93  
    94  	if err := os.Unsetenv(keys[1]); err != nil {
    95  		t.Fatalf("err: %s", err)
    96  	}
    97  
    98  	// Test that the default value is returned when no keys are set
    99  	actual, err = f()
   100  	if err != nil {
   101  		t.Fatalf("err: %s", err)
   102  	}
   103  	if actual != "42" {
   104  		t.Fatalf("bad: %#v", actual)
   105  	}
   106  }
   107  
   108  func TestValueType_Zero(t *testing.T) {
   109  	cases := []struct {
   110  		Type  ValueType
   111  		Value interface{}
   112  	}{
   113  		{TypeBool, false},
   114  		{TypeInt, 0},
   115  		{TypeFloat, 0.0},
   116  		{TypeString, ""},
   117  		{TypeList, []interface{}{}},
   118  		{TypeMap, map[string]interface{}{}},
   119  		{TypeSet, new(Set)},
   120  	}
   121  
   122  	for i, tc := range cases {
   123  		actual := tc.Type.Zero()
   124  		if !reflect.DeepEqual(actual, tc.Value) {
   125  			t.Fatalf("%d: %#v != %#v", i, actual, tc.Value)
   126  		}
   127  	}
   128  }
   129  
   130  func interfaceToVariableSwallowError(input interface{}) ast.Variable {
   131  	variable, _ := hil.InterfaceToVariable(input)
   132  	return variable
   133  }
   134  
   135  func TestSchemaMap_Diff(t *testing.T) {
   136  	cases := []struct {
   137  		Name            string
   138  		Schema          map[string]*Schema
   139  		State           *terraform.InstanceState
   140  		Config          map[string]interface{}
   141  		ConfigVariables map[string]ast.Variable
   142  		CustomizeDiff   CustomizeDiffFunc
   143  		Diff            *terraform.InstanceDiff
   144  		Err             bool
   145  	}{
   146  		{
   147  			Schema: map[string]*Schema{
   148  				"availability_zone": &Schema{
   149  					Type:     TypeString,
   150  					Optional: true,
   151  					Computed: true,
   152  					ForceNew: true,
   153  				},
   154  			},
   155  
   156  			State: nil,
   157  
   158  			Config: map[string]interface{}{
   159  				"availability_zone": "foo",
   160  			},
   161  
   162  			Diff: &terraform.InstanceDiff{
   163  				Attributes: map[string]*terraform.ResourceAttrDiff{
   164  					"availability_zone": &terraform.ResourceAttrDiff{
   165  						Old:         "",
   166  						New:         "foo",
   167  						RequiresNew: true,
   168  					},
   169  				},
   170  			},
   171  
   172  			Err: false,
   173  		},
   174  
   175  		{
   176  			Schema: map[string]*Schema{
   177  				"availability_zone": &Schema{
   178  					Type:     TypeString,
   179  					Optional: true,
   180  					Computed: true,
   181  					ForceNew: true,
   182  				},
   183  			},
   184  
   185  			State: nil,
   186  
   187  			Config: map[string]interface{}{},
   188  
   189  			Diff: &terraform.InstanceDiff{
   190  				Attributes: map[string]*terraform.ResourceAttrDiff{
   191  					"availability_zone": &terraform.ResourceAttrDiff{
   192  						Old:         "",
   193  						NewComputed: true,
   194  						RequiresNew: true,
   195  					},
   196  				},
   197  			},
   198  
   199  			Err: false,
   200  		},
   201  
   202  		{
   203  			Schema: map[string]*Schema{
   204  				"availability_zone": &Schema{
   205  					Type:     TypeString,
   206  					Optional: true,
   207  					Computed: true,
   208  					ForceNew: true,
   209  				},
   210  			},
   211  
   212  			State: &terraform.InstanceState{
   213  				ID: "foo",
   214  			},
   215  
   216  			Config: map[string]interface{}{},
   217  
   218  			Diff: nil,
   219  
   220  			Err: false,
   221  		},
   222  
   223  		{
   224  			Name: "Computed, but set in config",
   225  			Schema: map[string]*Schema{
   226  				"availability_zone": &Schema{
   227  					Type:     TypeString,
   228  					Optional: true,
   229  					Computed: true,
   230  				},
   231  			},
   232  
   233  			State: &terraform.InstanceState{
   234  				Attributes: map[string]string{
   235  					"availability_zone": "foo",
   236  				},
   237  			},
   238  
   239  			Config: map[string]interface{}{
   240  				"availability_zone": "bar",
   241  			},
   242  
   243  			Diff: &terraform.InstanceDiff{
   244  				Attributes: map[string]*terraform.ResourceAttrDiff{
   245  					"availability_zone": &terraform.ResourceAttrDiff{
   246  						Old: "foo",
   247  						New: "bar",
   248  					},
   249  				},
   250  			},
   251  
   252  			Err: false,
   253  		},
   254  
   255  		{
   256  			Name: "Default",
   257  			Schema: map[string]*Schema{
   258  				"availability_zone": &Schema{
   259  					Type:     TypeString,
   260  					Optional: true,
   261  					Default:  "foo",
   262  				},
   263  			},
   264  
   265  			State: nil,
   266  
   267  			Config: nil,
   268  
   269  			Diff: &terraform.InstanceDiff{
   270  				Attributes: map[string]*terraform.ResourceAttrDiff{
   271  					"availability_zone": &terraform.ResourceAttrDiff{
   272  						Old: "",
   273  						New: "foo",
   274  					},
   275  				},
   276  			},
   277  
   278  			Err: false,
   279  		},
   280  
   281  		{
   282  			Name: "DefaultFunc, value",
   283  			Schema: map[string]*Schema{
   284  				"availability_zone": &Schema{
   285  					Type:     TypeString,
   286  					Optional: true,
   287  					DefaultFunc: func() (interface{}, error) {
   288  						return "foo", nil
   289  					},
   290  				},
   291  			},
   292  
   293  			State: nil,
   294  
   295  			Config: nil,
   296  
   297  			Diff: &terraform.InstanceDiff{
   298  				Attributes: map[string]*terraform.ResourceAttrDiff{
   299  					"availability_zone": &terraform.ResourceAttrDiff{
   300  						Old: "",
   301  						New: "foo",
   302  					},
   303  				},
   304  			},
   305  
   306  			Err: false,
   307  		},
   308  
   309  		{
   310  			Name: "DefaultFunc, configuration set",
   311  			Schema: map[string]*Schema{
   312  				"availability_zone": &Schema{
   313  					Type:     TypeString,
   314  					Optional: true,
   315  					DefaultFunc: func() (interface{}, error) {
   316  						return "foo", nil
   317  					},
   318  				},
   319  			},
   320  
   321  			State: nil,
   322  
   323  			Config: map[string]interface{}{
   324  				"availability_zone": "bar",
   325  			},
   326  
   327  			Diff: &terraform.InstanceDiff{
   328  				Attributes: map[string]*terraform.ResourceAttrDiff{
   329  					"availability_zone": &terraform.ResourceAttrDiff{
   330  						Old: "",
   331  						New: "bar",
   332  					},
   333  				},
   334  			},
   335  
   336  			Err: false,
   337  		},
   338  
   339  		{
   340  			Name: "String with StateFunc",
   341  			Schema: map[string]*Schema{
   342  				"availability_zone": &Schema{
   343  					Type:     TypeString,
   344  					Optional: true,
   345  					Computed: true,
   346  					StateFunc: func(a interface{}) string {
   347  						return a.(string) + "!"
   348  					},
   349  				},
   350  			},
   351  
   352  			State: nil,
   353  
   354  			Config: map[string]interface{}{
   355  				"availability_zone": "foo",
   356  			},
   357  
   358  			Diff: &terraform.InstanceDiff{
   359  				Attributes: map[string]*terraform.ResourceAttrDiff{
   360  					"availability_zone": &terraform.ResourceAttrDiff{
   361  						Old:      "",
   362  						New:      "foo!",
   363  						NewExtra: "foo",
   364  					},
   365  				},
   366  			},
   367  
   368  			Err: false,
   369  		},
   370  
   371  		{
   372  			Name: "StateFunc not called with nil value",
   373  			Schema: map[string]*Schema{
   374  				"availability_zone": &Schema{
   375  					Type:     TypeString,
   376  					Optional: true,
   377  					Computed: true,
   378  					StateFunc: func(a interface{}) string {
   379  						t.Fatalf("should not get here!")
   380  						return ""
   381  					},
   382  				},
   383  			},
   384  
   385  			State: nil,
   386  
   387  			Config: map[string]interface{}{},
   388  
   389  			Diff: &terraform.InstanceDiff{
   390  				Attributes: map[string]*terraform.ResourceAttrDiff{
   391  					"availability_zone": &terraform.ResourceAttrDiff{
   392  						Old:         "",
   393  						New:         "",
   394  						NewComputed: true,
   395  					},
   396  				},
   397  			},
   398  
   399  			Err: false,
   400  		},
   401  
   402  		{
   403  			Name: "Variable (just checking)",
   404  			Schema: map[string]*Schema{
   405  				"availability_zone": &Schema{
   406  					Type:     TypeString,
   407  					Optional: true,
   408  				},
   409  			},
   410  
   411  			State: nil,
   412  
   413  			Config: map[string]interface{}{
   414  				"availability_zone": "${var.foo}",
   415  			},
   416  
   417  			ConfigVariables: map[string]ast.Variable{
   418  				"var.foo": interfaceToVariableSwallowError("bar"),
   419  			},
   420  
   421  			Diff: &terraform.InstanceDiff{
   422  				Attributes: map[string]*terraform.ResourceAttrDiff{
   423  					"availability_zone": &terraform.ResourceAttrDiff{
   424  						Old: "",
   425  						New: "bar",
   426  					},
   427  				},
   428  			},
   429  
   430  			Err: false,
   431  		},
   432  
   433  		{
   434  			Name: "Variable computed",
   435  			Schema: map[string]*Schema{
   436  				"availability_zone": &Schema{
   437  					Type:     TypeString,
   438  					Optional: true,
   439  				},
   440  			},
   441  
   442  			State: nil,
   443  
   444  			Config: map[string]interface{}{
   445  				"availability_zone": "${var.foo}",
   446  			},
   447  
   448  			ConfigVariables: map[string]ast.Variable{
   449  				"var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue),
   450  			},
   451  
   452  			Diff: &terraform.InstanceDiff{
   453  				Attributes: map[string]*terraform.ResourceAttrDiff{
   454  					"availability_zone": &terraform.ResourceAttrDiff{
   455  						Old:         "",
   456  						New:         "${var.foo}",
   457  						NewComputed: true,
   458  					},
   459  				},
   460  			},
   461  
   462  			Err: false,
   463  		},
   464  
   465  		{
   466  			Name: "Int decode",
   467  			Schema: map[string]*Schema{
   468  				"port": &Schema{
   469  					Type:     TypeInt,
   470  					Optional: true,
   471  					Computed: true,
   472  					ForceNew: true,
   473  				},
   474  			},
   475  
   476  			State: nil,
   477  
   478  			Config: map[string]interface{}{
   479  				"port": 27,
   480  			},
   481  
   482  			Diff: &terraform.InstanceDiff{
   483  				Attributes: map[string]*terraform.ResourceAttrDiff{
   484  					"port": &terraform.ResourceAttrDiff{
   485  						Old:         "",
   486  						New:         "27",
   487  						RequiresNew: true,
   488  					},
   489  				},
   490  			},
   491  
   492  			Err: false,
   493  		},
   494  
   495  		{
   496  			Name: "bool decode",
   497  			Schema: map[string]*Schema{
   498  				"port": &Schema{
   499  					Type:     TypeBool,
   500  					Optional: true,
   501  					Computed: true,
   502  					ForceNew: true,
   503  				},
   504  			},
   505  
   506  			State: nil,
   507  
   508  			Config: map[string]interface{}{
   509  				"port": false,
   510  			},
   511  
   512  			Diff: &terraform.InstanceDiff{
   513  				Attributes: map[string]*terraform.ResourceAttrDiff{
   514  					"port": &terraform.ResourceAttrDiff{
   515  						Old:         "",
   516  						New:         "false",
   517  						RequiresNew: true,
   518  					},
   519  				},
   520  			},
   521  
   522  			Err: false,
   523  		},
   524  
   525  		{
   526  			Name: "Bool",
   527  			Schema: map[string]*Schema{
   528  				"delete": &Schema{
   529  					Type:     TypeBool,
   530  					Optional: true,
   531  					Default:  false,
   532  				},
   533  			},
   534  
   535  			State: &terraform.InstanceState{
   536  				Attributes: map[string]string{
   537  					"delete": "false",
   538  				},
   539  			},
   540  
   541  			Config: nil,
   542  
   543  			Diff: nil,
   544  
   545  			Err: false,
   546  		},
   547  
   548  		{
   549  			Name: "List decode",
   550  			Schema: map[string]*Schema{
   551  				"ports": &Schema{
   552  					Type:     TypeList,
   553  					Required: true,
   554  					Elem:     &Schema{Type: TypeInt},
   555  				},
   556  			},
   557  
   558  			State: nil,
   559  
   560  			Config: map[string]interface{}{
   561  				"ports": []interface{}{1, 2, 5},
   562  			},
   563  
   564  			Diff: &terraform.InstanceDiff{
   565  				Attributes: map[string]*terraform.ResourceAttrDiff{
   566  					"ports.#": &terraform.ResourceAttrDiff{
   567  						Old: "0",
   568  						New: "3",
   569  					},
   570  					"ports.0": &terraform.ResourceAttrDiff{
   571  						Old: "",
   572  						New: "1",
   573  					},
   574  					"ports.1": &terraform.ResourceAttrDiff{
   575  						Old: "",
   576  						New: "2",
   577  					},
   578  					"ports.2": &terraform.ResourceAttrDiff{
   579  						Old: "",
   580  						New: "5",
   581  					},
   582  				},
   583  			},
   584  
   585  			Err: false,
   586  		},
   587  
   588  		{
   589  			Name: "List decode with promotion",
   590  			Schema: map[string]*Schema{
   591  				"ports": &Schema{
   592  					Type:          TypeList,
   593  					Required:      true,
   594  					Elem:          &Schema{Type: TypeInt},
   595  					PromoteSingle: true,
   596  				},
   597  			},
   598  
   599  			State: nil,
   600  
   601  			Config: map[string]interface{}{
   602  				"ports": "5",
   603  			},
   604  
   605  			Diff: &terraform.InstanceDiff{
   606  				Attributes: map[string]*terraform.ResourceAttrDiff{
   607  					"ports.#": &terraform.ResourceAttrDiff{
   608  						Old: "0",
   609  						New: "1",
   610  					},
   611  					"ports.0": &terraform.ResourceAttrDiff{
   612  						Old: "",
   613  						New: "5",
   614  					},
   615  				},
   616  			},
   617  
   618  			Err: false,
   619  		},
   620  
   621  		{
   622  			Name: "List decode with promotion with list",
   623  			Schema: map[string]*Schema{
   624  				"ports": &Schema{
   625  					Type:          TypeList,
   626  					Required:      true,
   627  					Elem:          &Schema{Type: TypeInt},
   628  					PromoteSingle: true,
   629  				},
   630  			},
   631  
   632  			State: nil,
   633  
   634  			Config: map[string]interface{}{
   635  				"ports": []interface{}{"5"},
   636  			},
   637  
   638  			Diff: &terraform.InstanceDiff{
   639  				Attributes: map[string]*terraform.ResourceAttrDiff{
   640  					"ports.#": &terraform.ResourceAttrDiff{
   641  						Old: "0",
   642  						New: "1",
   643  					},
   644  					"ports.0": &terraform.ResourceAttrDiff{
   645  						Old: "",
   646  						New: "5",
   647  					},
   648  				},
   649  			},
   650  
   651  			Err: false,
   652  		},
   653  
   654  		{
   655  			Schema: map[string]*Schema{
   656  				"ports": &Schema{
   657  					Type:     TypeList,
   658  					Required: true,
   659  					Elem:     &Schema{Type: TypeInt},
   660  				},
   661  			},
   662  
   663  			State: nil,
   664  
   665  			Config: map[string]interface{}{
   666  				"ports": []interface{}{1, "${var.foo}"},
   667  			},
   668  
   669  			ConfigVariables: map[string]ast.Variable{
   670  				"var.foo": interfaceToVariableSwallowError([]interface{}{"2", "5"}),
   671  			},
   672  
   673  			Diff: &terraform.InstanceDiff{
   674  				Attributes: map[string]*terraform.ResourceAttrDiff{
   675  					"ports.#": &terraform.ResourceAttrDiff{
   676  						Old: "0",
   677  						New: "3",
   678  					},
   679  					"ports.0": &terraform.ResourceAttrDiff{
   680  						Old: "",
   681  						New: "1",
   682  					},
   683  					"ports.1": &terraform.ResourceAttrDiff{
   684  						Old: "",
   685  						New: "2",
   686  					},
   687  					"ports.2": &terraform.ResourceAttrDiff{
   688  						Old: "",
   689  						New: "5",
   690  					},
   691  				},
   692  			},
   693  
   694  			Err: false,
   695  		},
   696  
   697  		{
   698  			Schema: map[string]*Schema{
   699  				"ports": &Schema{
   700  					Type:     TypeList,
   701  					Required: true,
   702  					Elem:     &Schema{Type: TypeInt},
   703  				},
   704  			},
   705  
   706  			State: nil,
   707  
   708  			Config: map[string]interface{}{
   709  				"ports": []interface{}{1, "${var.foo}"},
   710  			},
   711  
   712  			ConfigVariables: map[string]ast.Variable{
   713  				"var.foo": interfaceToVariableSwallowError([]interface{}{
   714  					config.UnknownVariableValue, "5"}),
   715  			},
   716  
   717  			Diff: &terraform.InstanceDiff{
   718  				Attributes: map[string]*terraform.ResourceAttrDiff{
   719  					"ports.#": &terraform.ResourceAttrDiff{
   720  						Old:         "0",
   721  						New:         "",
   722  						NewComputed: true,
   723  					},
   724  				},
   725  			},
   726  
   727  			Err: false,
   728  		},
   729  
   730  		{
   731  			Schema: map[string]*Schema{
   732  				"ports": &Schema{
   733  					Type:     TypeList,
   734  					Required: true,
   735  					Elem:     &Schema{Type: TypeInt},
   736  				},
   737  			},
   738  
   739  			State: &terraform.InstanceState{
   740  				Attributes: map[string]string{
   741  					"ports.#": "3",
   742  					"ports.0": "1",
   743  					"ports.1": "2",
   744  					"ports.2": "5",
   745  				},
   746  			},
   747  
   748  			Config: map[string]interface{}{
   749  				"ports": []interface{}{1, 2, 5},
   750  			},
   751  
   752  			Diff: nil,
   753  
   754  			Err: false,
   755  		},
   756  
   757  		{
   758  			Name: "",
   759  			Schema: map[string]*Schema{
   760  				"ports": &Schema{
   761  					Type:     TypeList,
   762  					Required: true,
   763  					Elem:     &Schema{Type: TypeInt},
   764  				},
   765  			},
   766  
   767  			State: &terraform.InstanceState{
   768  				Attributes: map[string]string{
   769  					"ports.#": "2",
   770  					"ports.0": "1",
   771  					"ports.1": "2",
   772  				},
   773  			},
   774  
   775  			Config: map[string]interface{}{
   776  				"ports": []interface{}{1, 2, 5},
   777  			},
   778  
   779  			Diff: &terraform.InstanceDiff{
   780  				Attributes: map[string]*terraform.ResourceAttrDiff{
   781  					"ports.#": &terraform.ResourceAttrDiff{
   782  						Old: "2",
   783  						New: "3",
   784  					},
   785  					"ports.2": &terraform.ResourceAttrDiff{
   786  						Old: "",
   787  						New: "5",
   788  					},
   789  				},
   790  			},
   791  
   792  			Err: false,
   793  		},
   794  
   795  		{
   796  			Name: "",
   797  			Schema: map[string]*Schema{
   798  				"ports": &Schema{
   799  					Type:     TypeList,
   800  					Required: true,
   801  					Elem:     &Schema{Type: TypeInt},
   802  					ForceNew: true,
   803  				},
   804  			},
   805  
   806  			State: nil,
   807  
   808  			Config: map[string]interface{}{
   809  				"ports": []interface{}{1, 2, 5},
   810  			},
   811  
   812  			Diff: &terraform.InstanceDiff{
   813  				Attributes: map[string]*terraform.ResourceAttrDiff{
   814  					"ports.#": &terraform.ResourceAttrDiff{
   815  						Old:         "0",
   816  						New:         "3",
   817  						RequiresNew: true,
   818  					},
   819  					"ports.0": &terraform.ResourceAttrDiff{
   820  						Old:         "",
   821  						New:         "1",
   822  						RequiresNew: true,
   823  					},
   824  					"ports.1": &terraform.ResourceAttrDiff{
   825  						Old:         "",
   826  						New:         "2",
   827  						RequiresNew: true,
   828  					},
   829  					"ports.2": &terraform.ResourceAttrDiff{
   830  						Old:         "",
   831  						New:         "5",
   832  						RequiresNew: true,
   833  					},
   834  				},
   835  			},
   836  
   837  			Err: false,
   838  		},
   839  
   840  		{
   841  			Name: "",
   842  			Schema: map[string]*Schema{
   843  				"ports": &Schema{
   844  					Type:     TypeList,
   845  					Optional: true,
   846  					Computed: true,
   847  					Elem:     &Schema{Type: TypeInt},
   848  				},
   849  			},
   850  
   851  			State: nil,
   852  
   853  			Config: map[string]interface{}{},
   854  
   855  			Diff: &terraform.InstanceDiff{
   856  				Attributes: map[string]*terraform.ResourceAttrDiff{
   857  					"ports.#": &terraform.ResourceAttrDiff{
   858  						Old:         "",
   859  						NewComputed: true,
   860  					},
   861  				},
   862  			},
   863  
   864  			Err: false,
   865  		},
   866  
   867  		{
   868  			Name: "List with computed set",
   869  			Schema: map[string]*Schema{
   870  				"config": &Schema{
   871  					Type:     TypeList,
   872  					Optional: true,
   873  					ForceNew: true,
   874  					MinItems: 1,
   875  					Elem: &Resource{
   876  						Schema: map[string]*Schema{
   877  							"name": {
   878  								Type:     TypeString,
   879  								Required: true,
   880  							},
   881  
   882  							"rules": {
   883  								Type:     TypeSet,
   884  								Computed: true,
   885  								Elem:     &Schema{Type: TypeString},
   886  								Set:      HashString,
   887  							},
   888  						},
   889  					},
   890  				},
   891  			},
   892  
   893  			State: nil,
   894  
   895  			Config: map[string]interface{}{
   896  				"config": []interface{}{
   897  					map[string]interface{}{
   898  						"name": "hello",
   899  					},
   900  				},
   901  			},
   902  
   903  			Diff: &terraform.InstanceDiff{
   904  				Attributes: map[string]*terraform.ResourceAttrDiff{
   905  					"config.#": &terraform.ResourceAttrDiff{
   906  						Old:         "0",
   907  						New:         "1",
   908  						RequiresNew: true,
   909  					},
   910  
   911  					"config.0.name": &terraform.ResourceAttrDiff{
   912  						Old: "",
   913  						New: "hello",
   914  					},
   915  
   916  					"config.0.rules.#": &terraform.ResourceAttrDiff{
   917  						Old:         "",
   918  						NewComputed: true,
   919  					},
   920  				},
   921  			},
   922  
   923  			Err: false,
   924  		},
   925  
   926  		{
   927  			Name: "Set",
   928  			Schema: map[string]*Schema{
   929  				"ports": &Schema{
   930  					Type:     TypeSet,
   931  					Required: true,
   932  					Elem:     &Schema{Type: TypeInt},
   933  					Set: func(a interface{}) int {
   934  						return a.(int)
   935  					},
   936  				},
   937  			},
   938  
   939  			State: nil,
   940  
   941  			Config: map[string]interface{}{
   942  				"ports": []interface{}{5, 2, 1},
   943  			},
   944  
   945  			Diff: &terraform.InstanceDiff{
   946  				Attributes: map[string]*terraform.ResourceAttrDiff{
   947  					"ports.#": &terraform.ResourceAttrDiff{
   948  						Old: "0",
   949  						New: "3",
   950  					},
   951  					"ports.1": &terraform.ResourceAttrDiff{
   952  						Old: "",
   953  						New: "1",
   954  					},
   955  					"ports.2": &terraform.ResourceAttrDiff{
   956  						Old: "",
   957  						New: "2",
   958  					},
   959  					"ports.5": &terraform.ResourceAttrDiff{
   960  						Old: "",
   961  						New: "5",
   962  					},
   963  				},
   964  			},
   965  
   966  			Err: false,
   967  		},
   968  
   969  		{
   970  			Name: "Set",
   971  			Schema: map[string]*Schema{
   972  				"ports": &Schema{
   973  					Type:     TypeSet,
   974  					Computed: true,
   975  					Required: true,
   976  					Elem:     &Schema{Type: TypeInt},
   977  					Set: func(a interface{}) int {
   978  						return a.(int)
   979  					},
   980  				},
   981  			},
   982  
   983  			State: &terraform.InstanceState{
   984  				Attributes: map[string]string{
   985  					"ports.#": "0",
   986  				},
   987  			},
   988  
   989  			Config: nil,
   990  
   991  			Diff: nil,
   992  
   993  			Err: false,
   994  		},
   995  
   996  		{
   997  			Name: "Set",
   998  			Schema: map[string]*Schema{
   999  				"ports": &Schema{
  1000  					Type:     TypeSet,
  1001  					Optional: true,
  1002  					Computed: true,
  1003  					Elem:     &Schema{Type: TypeInt},
  1004  					Set: func(a interface{}) int {
  1005  						return a.(int)
  1006  					},
  1007  				},
  1008  			},
  1009  
  1010  			State: nil,
  1011  
  1012  			Config: nil,
  1013  
  1014  			Diff: &terraform.InstanceDiff{
  1015  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1016  					"ports.#": &terraform.ResourceAttrDiff{
  1017  						Old:         "",
  1018  						NewComputed: true,
  1019  					},
  1020  				},
  1021  			},
  1022  
  1023  			Err: false,
  1024  		},
  1025  
  1026  		{
  1027  			Name: "Set",
  1028  			Schema: map[string]*Schema{
  1029  				"ports": &Schema{
  1030  					Type:     TypeSet,
  1031  					Required: true,
  1032  					Elem:     &Schema{Type: TypeInt},
  1033  					Set: func(a interface{}) int {
  1034  						return a.(int)
  1035  					},
  1036  				},
  1037  			},
  1038  
  1039  			State: nil,
  1040  
  1041  			Config: map[string]interface{}{
  1042  				"ports": []interface{}{"${var.foo}", 1},
  1043  			},
  1044  
  1045  			ConfigVariables: map[string]ast.Variable{
  1046  				"var.foo": interfaceToVariableSwallowError([]interface{}{"2", "5"}),
  1047  			},
  1048  
  1049  			Diff: &terraform.InstanceDiff{
  1050  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1051  					"ports.#": &terraform.ResourceAttrDiff{
  1052  						Old: "0",
  1053  						New: "3",
  1054  					},
  1055  					"ports.1": &terraform.ResourceAttrDiff{
  1056  						Old: "",
  1057  						New: "1",
  1058  					},
  1059  					"ports.2": &terraform.ResourceAttrDiff{
  1060  						Old: "",
  1061  						New: "2",
  1062  					},
  1063  					"ports.5": &terraform.ResourceAttrDiff{
  1064  						Old: "",
  1065  						New: "5",
  1066  					},
  1067  				},
  1068  			},
  1069  
  1070  			Err: false,
  1071  		},
  1072  
  1073  		{
  1074  			Name: "Set",
  1075  			Schema: map[string]*Schema{
  1076  				"ports": &Schema{
  1077  					Type:     TypeSet,
  1078  					Required: true,
  1079  					Elem:     &Schema{Type: TypeInt},
  1080  					Set: func(a interface{}) int {
  1081  						return a.(int)
  1082  					},
  1083  				},
  1084  			},
  1085  
  1086  			State: nil,
  1087  
  1088  			Config: map[string]interface{}{
  1089  				"ports": []interface{}{1, "${var.foo}"},
  1090  			},
  1091  
  1092  			ConfigVariables: map[string]ast.Variable{
  1093  				"var.foo": interfaceToVariableSwallowError([]interface{}{
  1094  					config.UnknownVariableValue, "5"}),
  1095  			},
  1096  
  1097  			Diff: &terraform.InstanceDiff{
  1098  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1099  					"ports.#": &terraform.ResourceAttrDiff{
  1100  						Old:         "",
  1101  						New:         "",
  1102  						NewComputed: true,
  1103  					},
  1104  				},
  1105  			},
  1106  
  1107  			Err: false,
  1108  		},
  1109  
  1110  		{
  1111  			Name: "Set",
  1112  			Schema: map[string]*Schema{
  1113  				"ports": &Schema{
  1114  					Type:     TypeSet,
  1115  					Required: true,
  1116  					Elem:     &Schema{Type: TypeInt},
  1117  					Set: func(a interface{}) int {
  1118  						return a.(int)
  1119  					},
  1120  				},
  1121  			},
  1122  
  1123  			State: &terraform.InstanceState{
  1124  				Attributes: map[string]string{
  1125  					"ports.#": "2",
  1126  					"ports.1": "1",
  1127  					"ports.2": "2",
  1128  				},
  1129  			},
  1130  
  1131  			Config: map[string]interface{}{
  1132  				"ports": []interface{}{5, 2, 1},
  1133  			},
  1134  
  1135  			Diff: &terraform.InstanceDiff{
  1136  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1137  					"ports.#": &terraform.ResourceAttrDiff{
  1138  						Old: "2",
  1139  						New: "3",
  1140  					},
  1141  					"ports.1": &terraform.ResourceAttrDiff{
  1142  						Old: "1",
  1143  						New: "1",
  1144  					},
  1145  					"ports.2": &terraform.ResourceAttrDiff{
  1146  						Old: "2",
  1147  						New: "2",
  1148  					},
  1149  					"ports.5": &terraform.ResourceAttrDiff{
  1150  						Old: "",
  1151  						New: "5",
  1152  					},
  1153  				},
  1154  			},
  1155  
  1156  			Err: false,
  1157  		},
  1158  
  1159  		{
  1160  			Name: "Set",
  1161  			Schema: map[string]*Schema{
  1162  				"ports": &Schema{
  1163  					Type:     TypeSet,
  1164  					Required: true,
  1165  					Elem:     &Schema{Type: TypeInt},
  1166  					Set: func(a interface{}) int {
  1167  						return a.(int)
  1168  					},
  1169  				},
  1170  			},
  1171  
  1172  			State: &terraform.InstanceState{
  1173  				Attributes: map[string]string{
  1174  					"ports.#": "2",
  1175  					"ports.1": "1",
  1176  					"ports.2": "2",
  1177  				},
  1178  			},
  1179  
  1180  			Config: map[string]interface{}{},
  1181  
  1182  			Diff: &terraform.InstanceDiff{
  1183  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1184  					"ports.#": &terraform.ResourceAttrDiff{
  1185  						Old: "2",
  1186  						New: "0",
  1187  					},
  1188  					"ports.1": &terraform.ResourceAttrDiff{
  1189  						Old:        "1",
  1190  						New:        "0",
  1191  						NewRemoved: true,
  1192  					},
  1193  					"ports.2": &terraform.ResourceAttrDiff{
  1194  						Old:        "2",
  1195  						New:        "0",
  1196  						NewRemoved: true,
  1197  					},
  1198  				},
  1199  			},
  1200  
  1201  			Err: false,
  1202  		},
  1203  
  1204  		{
  1205  			Name: "Set",
  1206  			Schema: map[string]*Schema{
  1207  				"ports": &Schema{
  1208  					Type:     TypeSet,
  1209  					Optional: true,
  1210  					Computed: true,
  1211  					Elem:     &Schema{Type: TypeInt},
  1212  					Set: func(a interface{}) int {
  1213  						return a.(int)
  1214  					},
  1215  				},
  1216  			},
  1217  
  1218  			State: &terraform.InstanceState{
  1219  				Attributes: map[string]string{
  1220  					"availability_zone": "bar",
  1221  					"ports.#":           "1",
  1222  					"ports.80":          "80",
  1223  				},
  1224  			},
  1225  
  1226  			Config: map[string]interface{}{},
  1227  
  1228  			Diff: nil,
  1229  
  1230  			Err: false,
  1231  		},
  1232  
  1233  		{
  1234  			Name: "Set",
  1235  			Schema: map[string]*Schema{
  1236  				"ingress": &Schema{
  1237  					Type:     TypeSet,
  1238  					Required: true,
  1239  					Elem: &Resource{
  1240  						Schema: map[string]*Schema{
  1241  							"ports": &Schema{
  1242  								Type:     TypeList,
  1243  								Optional: true,
  1244  								Elem:     &Schema{Type: TypeInt},
  1245  							},
  1246  						},
  1247  					},
  1248  					Set: func(v interface{}) int {
  1249  						m := v.(map[string]interface{})
  1250  						ps := m["ports"].([]interface{})
  1251  						result := 0
  1252  						for _, p := range ps {
  1253  							result += p.(int)
  1254  						}
  1255  						return result
  1256  					},
  1257  				},
  1258  			},
  1259  
  1260  			State: &terraform.InstanceState{
  1261  				Attributes: map[string]string{
  1262  					"ingress.#":           "2",
  1263  					"ingress.80.ports.#":  "1",
  1264  					"ingress.80.ports.0":  "80",
  1265  					"ingress.443.ports.#": "1",
  1266  					"ingress.443.ports.0": "443",
  1267  				},
  1268  			},
  1269  
  1270  			Config: map[string]interface{}{
  1271  				"ingress": []map[string]interface{}{
  1272  					map[string]interface{}{
  1273  						"ports": []interface{}{443},
  1274  					},
  1275  					map[string]interface{}{
  1276  						"ports": []interface{}{80},
  1277  					},
  1278  				},
  1279  			},
  1280  
  1281  			Diff: nil,
  1282  
  1283  			Err: false,
  1284  		},
  1285  
  1286  		{
  1287  			Name: "List of structure decode",
  1288  			Schema: map[string]*Schema{
  1289  				"ingress": &Schema{
  1290  					Type:     TypeList,
  1291  					Required: true,
  1292  					Elem: &Resource{
  1293  						Schema: map[string]*Schema{
  1294  							"from": &Schema{
  1295  								Type:     TypeInt,
  1296  								Required: true,
  1297  							},
  1298  						},
  1299  					},
  1300  				},
  1301  			},
  1302  
  1303  			State: nil,
  1304  
  1305  			Config: map[string]interface{}{
  1306  				"ingress": []interface{}{
  1307  					map[string]interface{}{
  1308  						"from": 8080,
  1309  					},
  1310  				},
  1311  			},
  1312  
  1313  			Diff: &terraform.InstanceDiff{
  1314  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1315  					"ingress.#": &terraform.ResourceAttrDiff{
  1316  						Old: "0",
  1317  						New: "1",
  1318  					},
  1319  					"ingress.0.from": &terraform.ResourceAttrDiff{
  1320  						Old: "",
  1321  						New: "8080",
  1322  					},
  1323  				},
  1324  			},
  1325  
  1326  			Err: false,
  1327  		},
  1328  
  1329  		{
  1330  			Name: "ComputedWhen",
  1331  			Schema: map[string]*Schema{
  1332  				"availability_zone": &Schema{
  1333  					Type:         TypeString,
  1334  					Computed:     true,
  1335  					ComputedWhen: []string{"port"},
  1336  				},
  1337  
  1338  				"port": &Schema{
  1339  					Type:     TypeInt,
  1340  					Optional: true,
  1341  				},
  1342  			},
  1343  
  1344  			State: &terraform.InstanceState{
  1345  				Attributes: map[string]string{
  1346  					"availability_zone": "foo",
  1347  					"port":              "80",
  1348  				},
  1349  			},
  1350  
  1351  			Config: map[string]interface{}{
  1352  				"port": 80,
  1353  			},
  1354  
  1355  			Diff: nil,
  1356  
  1357  			Err: false,
  1358  		},
  1359  
  1360  		{
  1361  			Name: "",
  1362  			Schema: map[string]*Schema{
  1363  				"availability_zone": &Schema{
  1364  					Type:         TypeString,
  1365  					Computed:     true,
  1366  					ComputedWhen: []string{"port"},
  1367  				},
  1368  
  1369  				"port": &Schema{
  1370  					Type:     TypeInt,
  1371  					Optional: true,
  1372  				},
  1373  			},
  1374  
  1375  			State: &terraform.InstanceState{
  1376  				Attributes: map[string]string{
  1377  					"port": "80",
  1378  				},
  1379  			},
  1380  
  1381  			Config: map[string]interface{}{
  1382  				"port": 80,
  1383  			},
  1384  
  1385  			Diff: &terraform.InstanceDiff{
  1386  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1387  					"availability_zone": &terraform.ResourceAttrDiff{
  1388  						NewComputed: true,
  1389  					},
  1390  				},
  1391  			},
  1392  
  1393  			Err: false,
  1394  		},
  1395  
  1396  		/* TODO
  1397  		{
  1398  			Schema: map[string]*Schema{
  1399  				"availability_zone": &Schema{
  1400  					Type:         TypeString,
  1401  					Computed:     true,
  1402  					ComputedWhen: []string{"port"},
  1403  				},
  1404  
  1405  				"port": &Schema{
  1406  					Type:     TypeInt,
  1407  					Optional: true,
  1408  				},
  1409  			},
  1410  
  1411  			State: &terraform.InstanceState{
  1412  				Attributes: map[string]string{
  1413  					"availability_zone": "foo",
  1414  					"port":              "80",
  1415  				},
  1416  			},
  1417  
  1418  			Config: map[string]interface{}{
  1419  				"port": 8080,
  1420  			},
  1421  
  1422  			Diff: &terraform.ResourceDiff{
  1423  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1424  					"availability_zone": &terraform.ResourceAttrDiff{
  1425  						Old:         "foo",
  1426  						NewComputed: true,
  1427  					},
  1428  					"port": &terraform.ResourceAttrDiff{
  1429  						Old: "80",
  1430  						New: "8080",
  1431  					},
  1432  				},
  1433  			},
  1434  
  1435  			Err: false,
  1436  		},
  1437  		*/
  1438  
  1439  		{
  1440  			Name: "Maps",
  1441  			Schema: map[string]*Schema{
  1442  				"config_vars": &Schema{
  1443  					Type: TypeMap,
  1444  				},
  1445  			},
  1446  
  1447  			State: nil,
  1448  
  1449  			Config: map[string]interface{}{
  1450  				"config_vars": []interface{}{
  1451  					map[string]interface{}{
  1452  						"bar": "baz",
  1453  					},
  1454  				},
  1455  			},
  1456  
  1457  			Diff: &terraform.InstanceDiff{
  1458  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1459  					"config_vars.%": &terraform.ResourceAttrDiff{
  1460  						Old: "0",
  1461  						New: "1",
  1462  					},
  1463  
  1464  					"config_vars.bar": &terraform.ResourceAttrDiff{
  1465  						Old: "",
  1466  						New: "baz",
  1467  					},
  1468  				},
  1469  			},
  1470  
  1471  			Err: false,
  1472  		},
  1473  
  1474  		{
  1475  			Name: "Maps",
  1476  			Schema: map[string]*Schema{
  1477  				"config_vars": &Schema{
  1478  					Type: TypeMap,
  1479  				},
  1480  			},
  1481  
  1482  			State: &terraform.InstanceState{
  1483  				Attributes: map[string]string{
  1484  					"config_vars.foo": "bar",
  1485  				},
  1486  			},
  1487  
  1488  			Config: map[string]interface{}{
  1489  				"config_vars": []interface{}{
  1490  					map[string]interface{}{
  1491  						"bar": "baz",
  1492  					},
  1493  				},
  1494  			},
  1495  
  1496  			Diff: &terraform.InstanceDiff{
  1497  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1498  					"config_vars.foo": &terraform.ResourceAttrDiff{
  1499  						Old:        "bar",
  1500  						NewRemoved: true,
  1501  					},
  1502  					"config_vars.bar": &terraform.ResourceAttrDiff{
  1503  						Old: "",
  1504  						New: "baz",
  1505  					},
  1506  				},
  1507  			},
  1508  
  1509  			Err: false,
  1510  		},
  1511  
  1512  		{
  1513  			Name: "Maps",
  1514  			Schema: map[string]*Schema{
  1515  				"vars": &Schema{
  1516  					Type:     TypeMap,
  1517  					Optional: true,
  1518  					Computed: true,
  1519  				},
  1520  			},
  1521  
  1522  			State: &terraform.InstanceState{
  1523  				Attributes: map[string]string{
  1524  					"vars.foo": "bar",
  1525  				},
  1526  			},
  1527  
  1528  			Config: map[string]interface{}{
  1529  				"vars": []interface{}{
  1530  					map[string]interface{}{
  1531  						"bar": "baz",
  1532  					},
  1533  				},
  1534  			},
  1535  
  1536  			Diff: &terraform.InstanceDiff{
  1537  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1538  					"vars.foo": &terraform.ResourceAttrDiff{
  1539  						Old:        "bar",
  1540  						New:        "",
  1541  						NewRemoved: true,
  1542  					},
  1543  					"vars.bar": &terraform.ResourceAttrDiff{
  1544  						Old: "",
  1545  						New: "baz",
  1546  					},
  1547  				},
  1548  			},
  1549  
  1550  			Err: false,
  1551  		},
  1552  
  1553  		{
  1554  			Name: "Maps",
  1555  			Schema: map[string]*Schema{
  1556  				"vars": &Schema{
  1557  					Type:     TypeMap,
  1558  					Computed: true,
  1559  				},
  1560  			},
  1561  
  1562  			State: &terraform.InstanceState{
  1563  				Attributes: map[string]string{
  1564  					"vars.foo": "bar",
  1565  				},
  1566  			},
  1567  
  1568  			Config: nil,
  1569  
  1570  			Diff: nil,
  1571  
  1572  			Err: false,
  1573  		},
  1574  
  1575  		{
  1576  			Name: "Maps",
  1577  			Schema: map[string]*Schema{
  1578  				"config_vars": &Schema{
  1579  					Type: TypeList,
  1580  					Elem: &Schema{Type: TypeMap},
  1581  				},
  1582  			},
  1583  
  1584  			State: &terraform.InstanceState{
  1585  				Attributes: map[string]string{
  1586  					"config_vars.#":     "1",
  1587  					"config_vars.0.foo": "bar",
  1588  				},
  1589  			},
  1590  
  1591  			Config: map[string]interface{}{
  1592  				"config_vars": []interface{}{
  1593  					map[string]interface{}{
  1594  						"bar": "baz",
  1595  					},
  1596  				},
  1597  			},
  1598  
  1599  			Diff: &terraform.InstanceDiff{
  1600  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1601  					"config_vars.0.foo": &terraform.ResourceAttrDiff{
  1602  						Old:        "bar",
  1603  						NewRemoved: true,
  1604  					},
  1605  					"config_vars.0.bar": &terraform.ResourceAttrDiff{
  1606  						Old: "",
  1607  						New: "baz",
  1608  					},
  1609  				},
  1610  			},
  1611  
  1612  			Err: false,
  1613  		},
  1614  
  1615  		{
  1616  			Name: "Maps",
  1617  			Schema: map[string]*Schema{
  1618  				"config_vars": &Schema{
  1619  					Type: TypeList,
  1620  					Elem: &Schema{Type: TypeMap},
  1621  				},
  1622  			},
  1623  
  1624  			State: &terraform.InstanceState{
  1625  				Attributes: map[string]string{
  1626  					"config_vars.#":     "1",
  1627  					"config_vars.0.foo": "bar",
  1628  					"config_vars.0.bar": "baz",
  1629  				},
  1630  			},
  1631  
  1632  			Config: map[string]interface{}{},
  1633  
  1634  			Diff: &terraform.InstanceDiff{
  1635  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1636  					"config_vars.#": &terraform.ResourceAttrDiff{
  1637  						Old: "1",
  1638  						New: "0",
  1639  					},
  1640  					"config_vars.0.%": &terraform.ResourceAttrDiff{
  1641  						Old: "2",
  1642  						New: "0",
  1643  					},
  1644  					"config_vars.0.foo": &terraform.ResourceAttrDiff{
  1645  						Old:        "bar",
  1646  						NewRemoved: true,
  1647  					},
  1648  					"config_vars.0.bar": &terraform.ResourceAttrDiff{
  1649  						Old:        "baz",
  1650  						NewRemoved: true,
  1651  					},
  1652  				},
  1653  			},
  1654  
  1655  			Err: false,
  1656  		},
  1657  
  1658  		{
  1659  			Name: "ForceNews",
  1660  			Schema: map[string]*Schema{
  1661  				"availability_zone": &Schema{
  1662  					Type:     TypeString,
  1663  					Optional: true,
  1664  					ForceNew: true,
  1665  				},
  1666  
  1667  				"address": &Schema{
  1668  					Type:     TypeString,
  1669  					Optional: true,
  1670  					Computed: true,
  1671  				},
  1672  			},
  1673  
  1674  			State: &terraform.InstanceState{
  1675  				Attributes: map[string]string{
  1676  					"availability_zone": "bar",
  1677  					"address":           "foo",
  1678  				},
  1679  			},
  1680  
  1681  			Config: map[string]interface{}{
  1682  				"availability_zone": "foo",
  1683  			},
  1684  
  1685  			Diff: &terraform.InstanceDiff{
  1686  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1687  					"availability_zone": &terraform.ResourceAttrDiff{
  1688  						Old:         "bar",
  1689  						New:         "foo",
  1690  						RequiresNew: true,
  1691  					},
  1692  
  1693  					"address": &terraform.ResourceAttrDiff{
  1694  						Old:         "foo",
  1695  						New:         "",
  1696  						NewComputed: true,
  1697  					},
  1698  				},
  1699  			},
  1700  
  1701  			Err: false,
  1702  		},
  1703  
  1704  		{
  1705  			Name: "Set",
  1706  			Schema: map[string]*Schema{
  1707  				"availability_zone": &Schema{
  1708  					Type:     TypeString,
  1709  					Optional: true,
  1710  					ForceNew: true,
  1711  				},
  1712  
  1713  				"ports": &Schema{
  1714  					Type:     TypeSet,
  1715  					Optional: true,
  1716  					Computed: true,
  1717  					Elem:     &Schema{Type: TypeInt},
  1718  					Set: func(a interface{}) int {
  1719  						return a.(int)
  1720  					},
  1721  				},
  1722  			},
  1723  
  1724  			State: &terraform.InstanceState{
  1725  				Attributes: map[string]string{
  1726  					"availability_zone": "bar",
  1727  					"ports.#":           "1",
  1728  					"ports.80":          "80",
  1729  				},
  1730  			},
  1731  
  1732  			Config: map[string]interface{}{
  1733  				"availability_zone": "foo",
  1734  			},
  1735  
  1736  			Diff: &terraform.InstanceDiff{
  1737  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1738  					"availability_zone": &terraform.ResourceAttrDiff{
  1739  						Old:         "bar",
  1740  						New:         "foo",
  1741  						RequiresNew: true,
  1742  					},
  1743  
  1744  					"ports.#": &terraform.ResourceAttrDiff{
  1745  						Old:         "1",
  1746  						New:         "",
  1747  						NewComputed: true,
  1748  					},
  1749  				},
  1750  			},
  1751  
  1752  			Err: false,
  1753  		},
  1754  
  1755  		{
  1756  			Name: "Set",
  1757  			Schema: map[string]*Schema{
  1758  				"instances": &Schema{
  1759  					Type:     TypeSet,
  1760  					Elem:     &Schema{Type: TypeString},
  1761  					Optional: true,
  1762  					Computed: true,
  1763  					Set: func(v interface{}) int {
  1764  						return len(v.(string))
  1765  					},
  1766  				},
  1767  			},
  1768  
  1769  			State: &terraform.InstanceState{
  1770  				Attributes: map[string]string{
  1771  					"instances.#": "0",
  1772  				},
  1773  			},
  1774  
  1775  			Config: map[string]interface{}{
  1776  				"instances": []interface{}{"${var.foo}"},
  1777  			},
  1778  
  1779  			ConfigVariables: map[string]ast.Variable{
  1780  				"var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue),
  1781  			},
  1782  
  1783  			Diff: &terraform.InstanceDiff{
  1784  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1785  					"instances.#": &terraform.ResourceAttrDiff{
  1786  						NewComputed: true,
  1787  					},
  1788  				},
  1789  			},
  1790  
  1791  			Err: false,
  1792  		},
  1793  
  1794  		{
  1795  			Name: "Set",
  1796  			Schema: map[string]*Schema{
  1797  				"route": &Schema{
  1798  					Type:     TypeSet,
  1799  					Optional: true,
  1800  					Elem: &Resource{
  1801  						Schema: map[string]*Schema{
  1802  							"index": &Schema{
  1803  								Type:     TypeInt,
  1804  								Required: true,
  1805  							},
  1806  
  1807  							"gateway": &Schema{
  1808  								Type:     TypeString,
  1809  								Optional: true,
  1810  							},
  1811  						},
  1812  					},
  1813  					Set: func(v interface{}) int {
  1814  						m := v.(map[string]interface{})
  1815  						return m["index"].(int)
  1816  					},
  1817  				},
  1818  			},
  1819  
  1820  			State: nil,
  1821  
  1822  			Config: map[string]interface{}{
  1823  				"route": []map[string]interface{}{
  1824  					map[string]interface{}{
  1825  						"index":   "1",
  1826  						"gateway": "${var.foo}",
  1827  					},
  1828  				},
  1829  			},
  1830  
  1831  			ConfigVariables: map[string]ast.Variable{
  1832  				"var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue),
  1833  			},
  1834  
  1835  			Diff: &terraform.InstanceDiff{
  1836  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1837  					"route.#": &terraform.ResourceAttrDiff{
  1838  						Old: "0",
  1839  						New: "1",
  1840  					},
  1841  					"route.~1.index": &terraform.ResourceAttrDiff{
  1842  						Old: "",
  1843  						New: "1",
  1844  					},
  1845  					"route.~1.gateway": &terraform.ResourceAttrDiff{
  1846  						Old:         "",
  1847  						New:         "${var.foo}",
  1848  						NewComputed: true,
  1849  					},
  1850  				},
  1851  			},
  1852  
  1853  			Err: false,
  1854  		},
  1855  
  1856  		{
  1857  			Name: "Set",
  1858  			Schema: map[string]*Schema{
  1859  				"route": &Schema{
  1860  					Type:     TypeSet,
  1861  					Optional: true,
  1862  					Elem: &Resource{
  1863  						Schema: map[string]*Schema{
  1864  							"index": &Schema{
  1865  								Type:     TypeInt,
  1866  								Required: true,
  1867  							},
  1868  
  1869  							"gateway": &Schema{
  1870  								Type:     TypeSet,
  1871  								Optional: true,
  1872  								Elem:     &Schema{Type: TypeInt},
  1873  								Set: func(a interface{}) int {
  1874  									return a.(int)
  1875  								},
  1876  							},
  1877  						},
  1878  					},
  1879  					Set: func(v interface{}) int {
  1880  						m := v.(map[string]interface{})
  1881  						return m["index"].(int)
  1882  					},
  1883  				},
  1884  			},
  1885  
  1886  			State: nil,
  1887  
  1888  			Config: map[string]interface{}{
  1889  				"route": []map[string]interface{}{
  1890  					map[string]interface{}{
  1891  						"index": "1",
  1892  						"gateway": []interface{}{
  1893  							"${var.foo}",
  1894  						},
  1895  					},
  1896  				},
  1897  			},
  1898  
  1899  			ConfigVariables: map[string]ast.Variable{
  1900  				"var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue),
  1901  			},
  1902  
  1903  			Diff: &terraform.InstanceDiff{
  1904  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1905  					"route.#": &terraform.ResourceAttrDiff{
  1906  						Old: "0",
  1907  						New: "1",
  1908  					},
  1909  					"route.~1.index": &terraform.ResourceAttrDiff{
  1910  						Old: "",
  1911  						New: "1",
  1912  					},
  1913  					"route.~1.gateway.#": &terraform.ResourceAttrDiff{
  1914  						NewComputed: true,
  1915  					},
  1916  				},
  1917  			},
  1918  
  1919  			Err: false,
  1920  		},
  1921  
  1922  		{
  1923  			Name: "Computed maps",
  1924  			Schema: map[string]*Schema{
  1925  				"vars": &Schema{
  1926  					Type:     TypeMap,
  1927  					Computed: true,
  1928  				},
  1929  			},
  1930  
  1931  			State: nil,
  1932  
  1933  			Config: nil,
  1934  
  1935  			Diff: &terraform.InstanceDiff{
  1936  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1937  					"vars.%": &terraform.ResourceAttrDiff{
  1938  						Old:         "",
  1939  						NewComputed: true,
  1940  					},
  1941  				},
  1942  			},
  1943  
  1944  			Err: false,
  1945  		},
  1946  
  1947  		{
  1948  			Name: "Computed maps",
  1949  			Schema: map[string]*Schema{
  1950  				"vars": &Schema{
  1951  					Type:     TypeMap,
  1952  					Computed: true,
  1953  				},
  1954  			},
  1955  
  1956  			State: &terraform.InstanceState{
  1957  				Attributes: map[string]string{
  1958  					"vars.%": "0",
  1959  				},
  1960  			},
  1961  
  1962  			Config: map[string]interface{}{
  1963  				"vars": map[string]interface{}{
  1964  					"bar": "${var.foo}",
  1965  				},
  1966  			},
  1967  
  1968  			ConfigVariables: map[string]ast.Variable{
  1969  				"var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue),
  1970  			},
  1971  
  1972  			Diff: &terraform.InstanceDiff{
  1973  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1974  					"vars.%": &terraform.ResourceAttrDiff{
  1975  						Old:         "",
  1976  						NewComputed: true,
  1977  					},
  1978  				},
  1979  			},
  1980  
  1981  			Err: false,
  1982  		},
  1983  
  1984  		{
  1985  			Name:   " - Empty",
  1986  			Schema: map[string]*Schema{},
  1987  
  1988  			State: &terraform.InstanceState{},
  1989  
  1990  			Config: map[string]interface{}{},
  1991  
  1992  			Diff: nil,
  1993  
  1994  			Err: false,
  1995  		},
  1996  
  1997  		{
  1998  			Name: "Float",
  1999  			Schema: map[string]*Schema{
  2000  				"some_threshold": &Schema{
  2001  					Type: TypeFloat,
  2002  				},
  2003  			},
  2004  
  2005  			State: &terraform.InstanceState{
  2006  				Attributes: map[string]string{
  2007  					"some_threshold": "567.8",
  2008  				},
  2009  			},
  2010  
  2011  			Config: map[string]interface{}{
  2012  				"some_threshold": 12.34,
  2013  			},
  2014  
  2015  			Diff: &terraform.InstanceDiff{
  2016  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2017  					"some_threshold": &terraform.ResourceAttrDiff{
  2018  						Old: "567.8",
  2019  						New: "12.34",
  2020  					},
  2021  				},
  2022  			},
  2023  
  2024  			Err: false,
  2025  		},
  2026  
  2027  		{
  2028  			Name: "https://github.com/hashicorp/terraform/issues/824",
  2029  			Schema: map[string]*Schema{
  2030  				"block_device": &Schema{
  2031  					Type:     TypeSet,
  2032  					Optional: true,
  2033  					Computed: true,
  2034  					Elem: &Resource{
  2035  						Schema: map[string]*Schema{
  2036  							"device_name": &Schema{
  2037  								Type:     TypeString,
  2038  								Required: true,
  2039  							},
  2040  							"delete_on_termination": &Schema{
  2041  								Type:     TypeBool,
  2042  								Optional: true,
  2043  								Default:  true,
  2044  							},
  2045  						},
  2046  					},
  2047  					Set: func(v interface{}) int {
  2048  						var buf bytes.Buffer
  2049  						m := v.(map[string]interface{})
  2050  						buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
  2051  						buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool)))
  2052  						return hashcode.String(buf.String())
  2053  					},
  2054  				},
  2055  			},
  2056  
  2057  			State: &terraform.InstanceState{
  2058  				Attributes: map[string]string{
  2059  					"block_device.#":                                "2",
  2060  					"block_device.616397234.delete_on_termination":  "true",
  2061  					"block_device.616397234.device_name":            "/dev/sda1",
  2062  					"block_device.2801811477.delete_on_termination": "true",
  2063  					"block_device.2801811477.device_name":           "/dev/sdx",
  2064  				},
  2065  			},
  2066  
  2067  			Config: map[string]interface{}{
  2068  				"block_device": []map[string]interface{}{
  2069  					map[string]interface{}{
  2070  						"device_name": "/dev/sda1",
  2071  					},
  2072  					map[string]interface{}{
  2073  						"device_name": "/dev/sdx",
  2074  					},
  2075  				},
  2076  			},
  2077  			Diff: nil,
  2078  			Err:  false,
  2079  		},
  2080  
  2081  		{
  2082  			Name: "Zero value in state shouldn't result in diff",
  2083  			Schema: map[string]*Schema{
  2084  				"port": &Schema{
  2085  					Type:     TypeBool,
  2086  					Optional: true,
  2087  					ForceNew: true,
  2088  				},
  2089  			},
  2090  
  2091  			State: &terraform.InstanceState{
  2092  				Attributes: map[string]string{
  2093  					"port": "false",
  2094  				},
  2095  			},
  2096  
  2097  			Config: map[string]interface{}{},
  2098  
  2099  			Diff: nil,
  2100  
  2101  			Err: false,
  2102  		},
  2103  
  2104  		{
  2105  			Name: "Same as prev, but for sets",
  2106  			Schema: map[string]*Schema{
  2107  				"route": &Schema{
  2108  					Type:     TypeSet,
  2109  					Optional: true,
  2110  					Elem: &Resource{
  2111  						Schema: map[string]*Schema{
  2112  							"index": &Schema{
  2113  								Type:     TypeInt,
  2114  								Required: true,
  2115  							},
  2116  
  2117  							"gateway": &Schema{
  2118  								Type:     TypeSet,
  2119  								Optional: true,
  2120  								Elem:     &Schema{Type: TypeInt},
  2121  								Set: func(a interface{}) int {
  2122  									return a.(int)
  2123  								},
  2124  							},
  2125  						},
  2126  					},
  2127  					Set: func(v interface{}) int {
  2128  						m := v.(map[string]interface{})
  2129  						return m["index"].(int)
  2130  					},
  2131  				},
  2132  			},
  2133  
  2134  			State: &terraform.InstanceState{
  2135  				Attributes: map[string]string{
  2136  					"route.#": "0",
  2137  				},
  2138  			},
  2139  
  2140  			Config: map[string]interface{}{},
  2141  
  2142  			Diff: nil,
  2143  
  2144  			Err: false,
  2145  		},
  2146  
  2147  		{
  2148  			Name: "A set computed element shouldn't cause a diff",
  2149  			Schema: map[string]*Schema{
  2150  				"active": &Schema{
  2151  					Type:     TypeBool,
  2152  					Computed: true,
  2153  					ForceNew: true,
  2154  				},
  2155  			},
  2156  
  2157  			State: &terraform.InstanceState{
  2158  				Attributes: map[string]string{
  2159  					"active": "true",
  2160  				},
  2161  			},
  2162  
  2163  			Config: map[string]interface{}{},
  2164  
  2165  			Diff: nil,
  2166  
  2167  			Err: false,
  2168  		},
  2169  
  2170  		{
  2171  			Name: "An empty set should show up in the diff",
  2172  			Schema: map[string]*Schema{
  2173  				"instances": &Schema{
  2174  					Type:     TypeSet,
  2175  					Elem:     &Schema{Type: TypeString},
  2176  					Optional: true,
  2177  					ForceNew: true,
  2178  					Set: func(v interface{}) int {
  2179  						return len(v.(string))
  2180  					},
  2181  				},
  2182  			},
  2183  
  2184  			State: &terraform.InstanceState{
  2185  				Attributes: map[string]string{
  2186  					"instances.#": "1",
  2187  					"instances.3": "foo",
  2188  				},
  2189  			},
  2190  
  2191  			Config: map[string]interface{}{},
  2192  
  2193  			Diff: &terraform.InstanceDiff{
  2194  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2195  					"instances.#": &terraform.ResourceAttrDiff{
  2196  						Old:         "1",
  2197  						New:         "0",
  2198  						RequiresNew: true,
  2199  					},
  2200  					"instances.3": &terraform.ResourceAttrDiff{
  2201  						Old:         "foo",
  2202  						New:         "",
  2203  						NewRemoved:  true,
  2204  						RequiresNew: true,
  2205  					},
  2206  				},
  2207  			},
  2208  
  2209  			Err: false,
  2210  		},
  2211  
  2212  		{
  2213  			Name: "Map with empty value",
  2214  			Schema: map[string]*Schema{
  2215  				"vars": &Schema{
  2216  					Type: TypeMap,
  2217  				},
  2218  			},
  2219  
  2220  			State: nil,
  2221  
  2222  			Config: map[string]interface{}{
  2223  				"vars": map[string]interface{}{
  2224  					"foo": "",
  2225  				},
  2226  			},
  2227  
  2228  			Diff: &terraform.InstanceDiff{
  2229  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2230  					"vars.%": &terraform.ResourceAttrDiff{
  2231  						Old: "0",
  2232  						New: "1",
  2233  					},
  2234  					"vars.foo": &terraform.ResourceAttrDiff{
  2235  						Old: "",
  2236  						New: "",
  2237  					},
  2238  				},
  2239  			},
  2240  
  2241  			Err: false,
  2242  		},
  2243  
  2244  		{
  2245  			Name: "Unset bool, not in state",
  2246  			Schema: map[string]*Schema{
  2247  				"force": &Schema{
  2248  					Type:     TypeBool,
  2249  					Optional: true,
  2250  					ForceNew: true,
  2251  				},
  2252  			},
  2253  
  2254  			State: nil,
  2255  
  2256  			Config: map[string]interface{}{},
  2257  
  2258  			Diff: nil,
  2259  
  2260  			Err: false,
  2261  		},
  2262  
  2263  		{
  2264  			Name: "Unset set, not in state",
  2265  			Schema: map[string]*Schema{
  2266  				"metadata_keys": &Schema{
  2267  					Type:     TypeSet,
  2268  					Optional: true,
  2269  					ForceNew: true,
  2270  					Elem:     &Schema{Type: TypeInt},
  2271  					Set:      func(interface{}) int { return 0 },
  2272  				},
  2273  			},
  2274  
  2275  			State: nil,
  2276  
  2277  			Config: map[string]interface{}{},
  2278  
  2279  			Diff: nil,
  2280  
  2281  			Err: false,
  2282  		},
  2283  
  2284  		{
  2285  			Name: "Unset list in state, should not show up computed",
  2286  			Schema: map[string]*Schema{
  2287  				"metadata_keys": &Schema{
  2288  					Type:     TypeList,
  2289  					Optional: true,
  2290  					Computed: true,
  2291  					ForceNew: true,
  2292  					Elem:     &Schema{Type: TypeInt},
  2293  				},
  2294  			},
  2295  
  2296  			State: &terraform.InstanceState{
  2297  				Attributes: map[string]string{
  2298  					"metadata_keys.#": "0",
  2299  				},
  2300  			},
  2301  
  2302  			Config: map[string]interface{}{},
  2303  
  2304  			Diff: nil,
  2305  
  2306  			Err: false,
  2307  		},
  2308  
  2309  		{
  2310  			Name: "Set element computed substring",
  2311  			Schema: map[string]*Schema{
  2312  				"ports": &Schema{
  2313  					Type:     TypeSet,
  2314  					Required: true,
  2315  					Elem:     &Schema{Type: TypeInt},
  2316  					Set: func(a interface{}) int {
  2317  						return a.(int)
  2318  					},
  2319  				},
  2320  			},
  2321  
  2322  			State: nil,
  2323  
  2324  			Config: map[string]interface{}{
  2325  				"ports": []interface{}{1, "${var.foo}32"},
  2326  			},
  2327  
  2328  			ConfigVariables: map[string]ast.Variable{
  2329  				"var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue),
  2330  			},
  2331  
  2332  			Diff: &terraform.InstanceDiff{
  2333  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2334  					"ports.#": &terraform.ResourceAttrDiff{
  2335  						Old:         "",
  2336  						New:         "",
  2337  						NewComputed: true,
  2338  					},
  2339  				},
  2340  			},
  2341  
  2342  			Err: false,
  2343  		},
  2344  
  2345  		{
  2346  			Name: "Computed map without config that's known to be empty does not generate diff",
  2347  			Schema: map[string]*Schema{
  2348  				"tags": &Schema{
  2349  					Type:     TypeMap,
  2350  					Computed: true,
  2351  				},
  2352  			},
  2353  
  2354  			Config: nil,
  2355  
  2356  			State: &terraform.InstanceState{
  2357  				Attributes: map[string]string{
  2358  					"tags.%": "0",
  2359  				},
  2360  			},
  2361  
  2362  			Diff: nil,
  2363  
  2364  			Err: false,
  2365  		},
  2366  
  2367  		{
  2368  			Name: "Set with hyphen keys",
  2369  			Schema: map[string]*Schema{
  2370  				"route": &Schema{
  2371  					Type:     TypeSet,
  2372  					Optional: true,
  2373  					Elem: &Resource{
  2374  						Schema: map[string]*Schema{
  2375  							"index": &Schema{
  2376  								Type:     TypeInt,
  2377  								Required: true,
  2378  							},
  2379  
  2380  							"gateway-name": &Schema{
  2381  								Type:     TypeString,
  2382  								Optional: true,
  2383  							},
  2384  						},
  2385  					},
  2386  					Set: func(v interface{}) int {
  2387  						m := v.(map[string]interface{})
  2388  						return m["index"].(int)
  2389  					},
  2390  				},
  2391  			},
  2392  
  2393  			State: nil,
  2394  
  2395  			Config: map[string]interface{}{
  2396  				"route": []map[string]interface{}{
  2397  					map[string]interface{}{
  2398  						"index":        "1",
  2399  						"gateway-name": "hello",
  2400  					},
  2401  				},
  2402  			},
  2403  
  2404  			Diff: &terraform.InstanceDiff{
  2405  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2406  					"route.#": &terraform.ResourceAttrDiff{
  2407  						Old: "0",
  2408  						New: "1",
  2409  					},
  2410  					"route.1.index": &terraform.ResourceAttrDiff{
  2411  						Old: "",
  2412  						New: "1",
  2413  					},
  2414  					"route.1.gateway-name": &terraform.ResourceAttrDiff{
  2415  						Old: "",
  2416  						New: "hello",
  2417  					},
  2418  				},
  2419  			},
  2420  
  2421  			Err: false,
  2422  		},
  2423  
  2424  		{
  2425  			Name: ": StateFunc in nested set (#1759)",
  2426  			Schema: map[string]*Schema{
  2427  				"service_account": &Schema{
  2428  					Type:     TypeList,
  2429  					Optional: true,
  2430  					ForceNew: true,
  2431  					Elem: &Resource{
  2432  						Schema: map[string]*Schema{
  2433  							"scopes": &Schema{
  2434  								Type:     TypeSet,
  2435  								Required: true,
  2436  								ForceNew: true,
  2437  								Elem: &Schema{
  2438  									Type: TypeString,
  2439  									StateFunc: func(v interface{}) string {
  2440  										return v.(string) + "!"
  2441  									},
  2442  								},
  2443  								Set: func(v interface{}) int {
  2444  									i, err := strconv.Atoi(v.(string))
  2445  									if err != nil {
  2446  										t.Fatalf("err: %s", err)
  2447  									}
  2448  									return i
  2449  								},
  2450  							},
  2451  						},
  2452  					},
  2453  				},
  2454  			},
  2455  
  2456  			State: nil,
  2457  
  2458  			Config: map[string]interface{}{
  2459  				"service_account": []map[string]interface{}{
  2460  					{
  2461  						"scopes": []interface{}{"123"},
  2462  					},
  2463  				},
  2464  			},
  2465  
  2466  			Diff: &terraform.InstanceDiff{
  2467  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2468  					"service_account.#": &terraform.ResourceAttrDiff{
  2469  						Old:         "0",
  2470  						New:         "1",
  2471  						RequiresNew: true,
  2472  					},
  2473  					"service_account.0.scopes.#": &terraform.ResourceAttrDiff{
  2474  						Old:         "0",
  2475  						New:         "1",
  2476  						RequiresNew: true,
  2477  					},
  2478  					"service_account.0.scopes.123": &terraform.ResourceAttrDiff{
  2479  						Old:         "",
  2480  						New:         "123!",
  2481  						NewExtra:    "123",
  2482  						RequiresNew: true,
  2483  					},
  2484  				},
  2485  			},
  2486  
  2487  			Err: false,
  2488  		},
  2489  
  2490  		{
  2491  			Name: "Removing set elements",
  2492  			Schema: map[string]*Schema{
  2493  				"instances": &Schema{
  2494  					Type:     TypeSet,
  2495  					Elem:     &Schema{Type: TypeString},
  2496  					Optional: true,
  2497  					ForceNew: true,
  2498  					Set: func(v interface{}) int {
  2499  						return len(v.(string))
  2500  					},
  2501  				},
  2502  			},
  2503  
  2504  			State: &terraform.InstanceState{
  2505  				Attributes: map[string]string{
  2506  					"instances.#": "2",
  2507  					"instances.3": "333",
  2508  					"instances.2": "22",
  2509  				},
  2510  			},
  2511  
  2512  			Config: map[string]interface{}{
  2513  				"instances": []interface{}{"333", "4444"},
  2514  			},
  2515  
  2516  			Diff: &terraform.InstanceDiff{
  2517  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2518  					"instances.#": &terraform.ResourceAttrDiff{
  2519  						Old: "2",
  2520  						New: "2",
  2521  					},
  2522  					"instances.2": &terraform.ResourceAttrDiff{
  2523  						Old:         "22",
  2524  						New:         "",
  2525  						NewRemoved:  true,
  2526  						RequiresNew: true,
  2527  					},
  2528  					"instances.3": &terraform.ResourceAttrDiff{
  2529  						Old: "333",
  2530  						New: "333",
  2531  					},
  2532  					"instances.4": &terraform.ResourceAttrDiff{
  2533  						Old:         "",
  2534  						New:         "4444",
  2535  						RequiresNew: true,
  2536  					},
  2537  				},
  2538  			},
  2539  
  2540  			Err: false,
  2541  		},
  2542  
  2543  		{
  2544  			Name: "Bools can be set with 0/1 in config, still get true/false",
  2545  			Schema: map[string]*Schema{
  2546  				"one": &Schema{
  2547  					Type:     TypeBool,
  2548  					Optional: true,
  2549  				},
  2550  				"two": &Schema{
  2551  					Type:     TypeBool,
  2552  					Optional: true,
  2553  				},
  2554  				"three": &Schema{
  2555  					Type:     TypeBool,
  2556  					Optional: true,
  2557  				},
  2558  			},
  2559  
  2560  			State: &terraform.InstanceState{
  2561  				Attributes: map[string]string{
  2562  					"one":   "false",
  2563  					"two":   "true",
  2564  					"three": "true",
  2565  				},
  2566  			},
  2567  
  2568  			Config: map[string]interface{}{
  2569  				"one": "1",
  2570  				"two": "0",
  2571  			},
  2572  
  2573  			Diff: &terraform.InstanceDiff{
  2574  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2575  					"one": &terraform.ResourceAttrDiff{
  2576  						Old: "false",
  2577  						New: "true",
  2578  					},
  2579  					"two": &terraform.ResourceAttrDiff{
  2580  						Old: "true",
  2581  						New: "false",
  2582  					},
  2583  					"three": &terraform.ResourceAttrDiff{
  2584  						Old:        "true",
  2585  						New:        "false",
  2586  						NewRemoved: true,
  2587  					},
  2588  				},
  2589  			},
  2590  
  2591  			Err: false,
  2592  		},
  2593  
  2594  		{
  2595  			Name:   "tainted in state w/ no attr changes is still a replacement",
  2596  			Schema: map[string]*Schema{},
  2597  
  2598  			State: &terraform.InstanceState{
  2599  				Attributes: map[string]string{
  2600  					"id": "someid",
  2601  				},
  2602  				Tainted: true,
  2603  			},
  2604  
  2605  			Config: map[string]interface{}{},
  2606  
  2607  			Diff: &terraform.InstanceDiff{
  2608  				Attributes:     map[string]*terraform.ResourceAttrDiff{},
  2609  				DestroyTainted: true,
  2610  			},
  2611  
  2612  			Err: false,
  2613  		},
  2614  
  2615  		{
  2616  			Name: "Set ForceNew only marks the changing element as ForceNew",
  2617  			Schema: map[string]*Schema{
  2618  				"ports": &Schema{
  2619  					Type:     TypeSet,
  2620  					Required: true,
  2621  					ForceNew: true,
  2622  					Elem:     &Schema{Type: TypeInt},
  2623  					Set: func(a interface{}) int {
  2624  						return a.(int)
  2625  					},
  2626  				},
  2627  			},
  2628  
  2629  			State: &terraform.InstanceState{
  2630  				Attributes: map[string]string{
  2631  					"ports.#": "3",
  2632  					"ports.1": "1",
  2633  					"ports.2": "2",
  2634  					"ports.4": "4",
  2635  				},
  2636  			},
  2637  
  2638  			Config: map[string]interface{}{
  2639  				"ports": []interface{}{5, 2, 1},
  2640  			},
  2641  
  2642  			Diff: &terraform.InstanceDiff{
  2643  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2644  					"ports.#": &terraform.ResourceAttrDiff{
  2645  						Old: "3",
  2646  						New: "3",
  2647  					},
  2648  					"ports.1": &terraform.ResourceAttrDiff{
  2649  						Old: "1",
  2650  						New: "1",
  2651  					},
  2652  					"ports.2": &terraform.ResourceAttrDiff{
  2653  						Old: "2",
  2654  						New: "2",
  2655  					},
  2656  					"ports.5": &terraform.ResourceAttrDiff{
  2657  						Old:         "",
  2658  						New:         "5",
  2659  						RequiresNew: true,
  2660  					},
  2661  					"ports.4": &terraform.ResourceAttrDiff{
  2662  						Old:         "4",
  2663  						New:         "0",
  2664  						NewRemoved:  true,
  2665  						RequiresNew: true,
  2666  					},
  2667  				},
  2668  			},
  2669  		},
  2670  
  2671  		{
  2672  			Name: "removed optional items should trigger ForceNew",
  2673  			Schema: map[string]*Schema{
  2674  				"description": &Schema{
  2675  					Type:     TypeString,
  2676  					ForceNew: true,
  2677  					Optional: true,
  2678  				},
  2679  			},
  2680  
  2681  			State: &terraform.InstanceState{
  2682  				Attributes: map[string]string{
  2683  					"description": "foo",
  2684  				},
  2685  			},
  2686  
  2687  			Config: map[string]interface{}{},
  2688  
  2689  			Diff: &terraform.InstanceDiff{
  2690  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2691  					"description": &terraform.ResourceAttrDiff{
  2692  						Old:         "foo",
  2693  						New:         "",
  2694  						RequiresNew: true,
  2695  						NewRemoved:  true,
  2696  					},
  2697  				},
  2698  			},
  2699  
  2700  			Err: false,
  2701  		},
  2702  
  2703  		// GH-7715
  2704  		{
  2705  			Name: "computed value for boolean field",
  2706  			Schema: map[string]*Schema{
  2707  				"foo": &Schema{
  2708  					Type:     TypeBool,
  2709  					ForceNew: true,
  2710  					Computed: true,
  2711  					Optional: true,
  2712  				},
  2713  			},
  2714  
  2715  			State: &terraform.InstanceState{},
  2716  
  2717  			Config: map[string]interface{}{
  2718  				"foo": "${var.foo}",
  2719  			},
  2720  
  2721  			ConfigVariables: map[string]ast.Variable{
  2722  				"var.foo": interfaceToVariableSwallowError(
  2723  					config.UnknownVariableValue),
  2724  			},
  2725  
  2726  			Diff: &terraform.InstanceDiff{
  2727  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2728  					"foo": &terraform.ResourceAttrDiff{
  2729  						Old:         "",
  2730  						New:         "false",
  2731  						NewComputed: true,
  2732  						RequiresNew: true,
  2733  					},
  2734  				},
  2735  			},
  2736  
  2737  			Err: false,
  2738  		},
  2739  
  2740  		{
  2741  			Name: "Set ForceNew marks count as ForceNew if computed",
  2742  			Schema: map[string]*Schema{
  2743  				"ports": &Schema{
  2744  					Type:     TypeSet,
  2745  					Required: true,
  2746  					ForceNew: true,
  2747  					Elem:     &Schema{Type: TypeInt},
  2748  					Set: func(a interface{}) int {
  2749  						return a.(int)
  2750  					},
  2751  				},
  2752  			},
  2753  
  2754  			State: &terraform.InstanceState{
  2755  				Attributes: map[string]string{
  2756  					"ports.#": "3",
  2757  					"ports.1": "1",
  2758  					"ports.2": "2",
  2759  					"ports.4": "4",
  2760  				},
  2761  			},
  2762  
  2763  			Config: map[string]interface{}{
  2764  				"ports": []interface{}{"${var.foo}", 2, 1},
  2765  			},
  2766  
  2767  			ConfigVariables: map[string]ast.Variable{
  2768  				"var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue),
  2769  			},
  2770  
  2771  			Diff: &terraform.InstanceDiff{
  2772  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2773  					"ports.#": &terraform.ResourceAttrDiff{
  2774  						Old:         "3",
  2775  						New:         "",
  2776  						NewComputed: true,
  2777  						RequiresNew: true,
  2778  					},
  2779  				},
  2780  			},
  2781  		},
  2782  
  2783  		{
  2784  			Name: "List with computed schema and ForceNew",
  2785  			Schema: map[string]*Schema{
  2786  				"config": &Schema{
  2787  					Type:     TypeList,
  2788  					Optional: true,
  2789  					ForceNew: true,
  2790  					Elem: &Schema{
  2791  						Type: TypeString,
  2792  					},
  2793  				},
  2794  			},
  2795  
  2796  			State: &terraform.InstanceState{
  2797  				Attributes: map[string]string{
  2798  					"config.#": "2",
  2799  					"config.0": "a",
  2800  					"config.1": "b",
  2801  				},
  2802  			},
  2803  
  2804  			Config: map[string]interface{}{
  2805  				"config": []interface{}{"${var.a}", "${var.b}"},
  2806  			},
  2807  
  2808  			ConfigVariables: map[string]ast.Variable{
  2809  				"var.a": interfaceToVariableSwallowError(
  2810  					config.UnknownVariableValue),
  2811  				"var.b": interfaceToVariableSwallowError(
  2812  					config.UnknownVariableValue),
  2813  			},
  2814  
  2815  			Diff: &terraform.InstanceDiff{
  2816  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2817  					"config.#": &terraform.ResourceAttrDiff{
  2818  						Old:         "2",
  2819  						New:         "",
  2820  						RequiresNew: true,
  2821  						NewComputed: true,
  2822  					},
  2823  				},
  2824  			},
  2825  
  2826  			Err: false,
  2827  		},
  2828  
  2829  		{
  2830  			Name: "overridden diff with a CustomizeDiff function, ForceNew not in schema",
  2831  			Schema: map[string]*Schema{
  2832  				"availability_zone": &Schema{
  2833  					Type:     TypeString,
  2834  					Optional: true,
  2835  					Computed: true,
  2836  				},
  2837  			},
  2838  
  2839  			State: nil,
  2840  
  2841  			Config: map[string]interface{}{
  2842  				"availability_zone": "foo",
  2843  			},
  2844  
  2845  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  2846  				if err := d.SetNew("availability_zone", "bar"); err != nil {
  2847  					return err
  2848  				}
  2849  				if err := d.ForceNew("availability_zone"); err != nil {
  2850  					return err
  2851  				}
  2852  				return nil
  2853  			},
  2854  
  2855  			Diff: &terraform.InstanceDiff{
  2856  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2857  					"availability_zone": &terraform.ResourceAttrDiff{
  2858  						Old:         "",
  2859  						New:         "bar",
  2860  						RequiresNew: true,
  2861  					},
  2862  				},
  2863  			},
  2864  
  2865  			Err: false,
  2866  		},
  2867  
  2868  		{
  2869  			Name: "overridden diff with a CustomizeDiff function, ForceNew in schema",
  2870  			Schema: map[string]*Schema{
  2871  				"availability_zone": &Schema{
  2872  					Type:     TypeString,
  2873  					Optional: true,
  2874  					Computed: true,
  2875  					ForceNew: true,
  2876  				},
  2877  			},
  2878  
  2879  			State: nil,
  2880  
  2881  			Config: map[string]interface{}{
  2882  				"availability_zone": "foo",
  2883  			},
  2884  
  2885  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  2886  				if err := d.SetNew("availability_zone", "bar"); err != nil {
  2887  					return err
  2888  				}
  2889  				return nil
  2890  			},
  2891  
  2892  			Diff: &terraform.InstanceDiff{
  2893  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2894  					"availability_zone": &terraform.ResourceAttrDiff{
  2895  						Old:         "",
  2896  						New:         "bar",
  2897  						RequiresNew: true,
  2898  					},
  2899  				},
  2900  			},
  2901  
  2902  			Err: false,
  2903  		},
  2904  
  2905  		{
  2906  			Name: "required field with computed diff added with CustomizeDiff function",
  2907  			Schema: map[string]*Schema{
  2908  				"ami_id": &Schema{
  2909  					Type:     TypeString,
  2910  					Required: true,
  2911  				},
  2912  				"instance_id": &Schema{
  2913  					Type:     TypeString,
  2914  					Computed: true,
  2915  				},
  2916  			},
  2917  
  2918  			State: nil,
  2919  
  2920  			Config: map[string]interface{}{
  2921  				"ami_id": "foo",
  2922  			},
  2923  
  2924  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  2925  				if err := d.SetNew("instance_id", "bar"); err != nil {
  2926  					return err
  2927  				}
  2928  				return nil
  2929  			},
  2930  
  2931  			Diff: &terraform.InstanceDiff{
  2932  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2933  					"ami_id": &terraform.ResourceAttrDiff{
  2934  						Old: "",
  2935  						New: "foo",
  2936  					},
  2937  					"instance_id": &terraform.ResourceAttrDiff{
  2938  						Old: "",
  2939  						New: "bar",
  2940  					},
  2941  				},
  2942  			},
  2943  
  2944  			Err: false,
  2945  		},
  2946  
  2947  		{
  2948  			Name: "Set ForceNew only marks the changing element as ForceNew - CustomizeDiffFunc edition",
  2949  			Schema: map[string]*Schema{
  2950  				"ports": &Schema{
  2951  					Type:     TypeSet,
  2952  					Optional: true,
  2953  					Computed: true,
  2954  					Elem:     &Schema{Type: TypeInt},
  2955  					Set: func(a interface{}) int {
  2956  						return a.(int)
  2957  					},
  2958  				},
  2959  			},
  2960  
  2961  			State: &terraform.InstanceState{
  2962  				Attributes: map[string]string{
  2963  					"ports.#": "3",
  2964  					"ports.1": "1",
  2965  					"ports.2": "2",
  2966  					"ports.4": "4",
  2967  				},
  2968  			},
  2969  
  2970  			Config: map[string]interface{}{
  2971  				"ports": []interface{}{5, 2, 6},
  2972  			},
  2973  
  2974  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  2975  				if err := d.SetNew("ports", []interface{}{5, 2, 1}); err != nil {
  2976  					return err
  2977  				}
  2978  				if err := d.ForceNew("ports"); err != nil {
  2979  					return err
  2980  				}
  2981  				return nil
  2982  			},
  2983  
  2984  			Diff: &terraform.InstanceDiff{
  2985  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2986  					"ports.#": &terraform.ResourceAttrDiff{
  2987  						Old: "3",
  2988  						New: "3",
  2989  					},
  2990  					"ports.1": &terraform.ResourceAttrDiff{
  2991  						Old: "1",
  2992  						New: "1",
  2993  					},
  2994  					"ports.2": &terraform.ResourceAttrDiff{
  2995  						Old: "2",
  2996  						New: "2",
  2997  					},
  2998  					"ports.5": &terraform.ResourceAttrDiff{
  2999  						Old:         "",
  3000  						New:         "5",
  3001  						RequiresNew: true,
  3002  					},
  3003  					"ports.4": &terraform.ResourceAttrDiff{
  3004  						Old:         "4",
  3005  						New:         "0",
  3006  						NewRemoved:  true,
  3007  						RequiresNew: true,
  3008  					},
  3009  				},
  3010  			},
  3011  		},
  3012  
  3013  		{
  3014  			Name:   "tainted resource does not run CustomizeDiffFunc",
  3015  			Schema: map[string]*Schema{},
  3016  
  3017  			State: &terraform.InstanceState{
  3018  				Attributes: map[string]string{
  3019  					"id": "someid",
  3020  				},
  3021  				Tainted: true,
  3022  			},
  3023  
  3024  			Config: map[string]interface{}{},
  3025  
  3026  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3027  				return errors.New("diff customization should not have run")
  3028  			},
  3029  
  3030  			Diff: &terraform.InstanceDiff{
  3031  				Attributes:     map[string]*terraform.ResourceAttrDiff{},
  3032  				DestroyTainted: true,
  3033  			},
  3034  
  3035  			Err: false,
  3036  		},
  3037  
  3038  		{
  3039  			Name: "NewComputed based on a conditional with CustomizeDiffFunc",
  3040  			Schema: map[string]*Schema{
  3041  				"etag": &Schema{
  3042  					Type:     TypeString,
  3043  					Optional: true,
  3044  					Computed: true,
  3045  				},
  3046  				"version_id": &Schema{
  3047  					Type:     TypeString,
  3048  					Computed: true,
  3049  				},
  3050  			},
  3051  
  3052  			State: &terraform.InstanceState{
  3053  				Attributes: map[string]string{
  3054  					"etag":       "foo",
  3055  					"version_id": "1",
  3056  				},
  3057  			},
  3058  
  3059  			Config: map[string]interface{}{
  3060  				"etag": "bar",
  3061  			},
  3062  
  3063  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3064  				if d.HasChange("etag") {
  3065  					d.SetNewComputed("version_id")
  3066  				}
  3067  				return nil
  3068  			},
  3069  
  3070  			Diff: &terraform.InstanceDiff{
  3071  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3072  					"etag": &terraform.ResourceAttrDiff{
  3073  						Old: "foo",
  3074  						New: "bar",
  3075  					},
  3076  					"version_id": &terraform.ResourceAttrDiff{
  3077  						Old:         "1",
  3078  						New:         "",
  3079  						NewComputed: true,
  3080  					},
  3081  				},
  3082  			},
  3083  
  3084  			Err: false,
  3085  		},
  3086  
  3087  		{
  3088  			Name: "vetoing a diff",
  3089  			Schema: map[string]*Schema{
  3090  				"foo": &Schema{
  3091  					Type:     TypeString,
  3092  					Optional: true,
  3093  					Computed: true,
  3094  				},
  3095  			},
  3096  
  3097  			State: &terraform.InstanceState{
  3098  				Attributes: map[string]string{
  3099  					"foo": "bar",
  3100  				},
  3101  			},
  3102  
  3103  			Config: map[string]interface{}{
  3104  				"foo": "baz",
  3105  			},
  3106  
  3107  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3108  				return fmt.Errorf("diff vetoed")
  3109  			},
  3110  
  3111  			Err: true,
  3112  		},
  3113  
  3114  		// A lot of resources currently depended on using the empty string as a
  3115  		// nil/unset value.
  3116  		// FIXME: We want this to eventually produce a diff, since there
  3117  		// technically is a new value in the config.
  3118  		{
  3119  			Name: "optional, computed, empty string",
  3120  			Schema: map[string]*Schema{
  3121  				"attr": &Schema{
  3122  					Type:     TypeString,
  3123  					Optional: true,
  3124  					Computed: true,
  3125  				},
  3126  			},
  3127  
  3128  			State: &terraform.InstanceState{
  3129  				Attributes: map[string]string{
  3130  					"attr": "bar",
  3131  				},
  3132  			},
  3133  
  3134  			// this does necessarily depend on an interpolated value, but this
  3135  			// is often how it comes about in a configuration, otherwise the
  3136  			// value would be unset.
  3137  			Config: map[string]interface{}{
  3138  				"attr": "${var.foo}",
  3139  			},
  3140  
  3141  			ConfigVariables: map[string]ast.Variable{
  3142  				"var.foo": interfaceToVariableSwallowError(""),
  3143  			},
  3144  		},
  3145  
  3146  		{
  3147  			Name: "optional, computed, empty string should not crash in CustomizeDiff",
  3148  			Schema: map[string]*Schema{
  3149  				"unrelated_set": {
  3150  					Type:     TypeSet,
  3151  					Optional: true,
  3152  					Elem:     &Schema{Type: TypeString},
  3153  				},
  3154  				"stream_enabled": {
  3155  					Type:     TypeBool,
  3156  					Optional: true,
  3157  				},
  3158  				"stream_view_type": {
  3159  					Type:     TypeString,
  3160  					Optional: true,
  3161  					Computed: true,
  3162  				},
  3163  			},
  3164  
  3165  			State: &terraform.InstanceState{
  3166  				Attributes: map[string]string{
  3167  					"unrelated_set.#":  "0",
  3168  					"stream_enabled":   "true",
  3169  					"stream_view_type": "KEYS_ONLY",
  3170  				},
  3171  			},
  3172  			Config: map[string]interface{}{
  3173  				"stream_enabled":   false,
  3174  				"stream_view_type": "",
  3175  			},
  3176  			CustomizeDiff: func(diff *ResourceDiff, v interface{}) error {
  3177  				v, ok := diff.GetOk("unrelated_set")
  3178  				if ok {
  3179  					return fmt.Errorf("Didn't expect unrelated_set: %#v", v)
  3180  				}
  3181  				return nil
  3182  			},
  3183  			Diff: &terraform.InstanceDiff{
  3184  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3185  					"stream_enabled": {
  3186  						Old: "true",
  3187  						New: "false",
  3188  					},
  3189  				},
  3190  			},
  3191  		},
  3192  	}
  3193  
  3194  	for i, tc := range cases {
  3195  		t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
  3196  			c, err := config.NewRawConfig(tc.Config)
  3197  			if err != nil {
  3198  				t.Fatalf("err: %s", err)
  3199  			}
  3200  
  3201  			if len(tc.ConfigVariables) > 0 {
  3202  				if err := c.Interpolate(tc.ConfigVariables); err != nil {
  3203  					t.Fatalf("err: %s", err)
  3204  				}
  3205  			}
  3206  
  3207  			d, err := schemaMap(tc.Schema).Diff(tc.State, terraform.NewResourceConfig(c), tc.CustomizeDiff, nil)
  3208  			if err != nil != tc.Err {
  3209  				t.Fatalf("err: %s", err)
  3210  			}
  3211  
  3212  			if !reflect.DeepEqual(tc.Diff, d) {
  3213  				t.Fatalf("expected:\n%#v\n\ngot:\n%#v", tc.Diff, d)
  3214  			}
  3215  		})
  3216  	}
  3217  }
  3218  
  3219  func TestSchemaMap_Input(t *testing.T) {
  3220  	cases := map[string]struct {
  3221  		Schema map[string]*Schema
  3222  		Config map[string]interface{}
  3223  		Input  map[string]string
  3224  		Result map[string]interface{}
  3225  		Err    bool
  3226  	}{
  3227  		/*
  3228  		 * String decode
  3229  		 */
  3230  
  3231  		"no input on optional field with no config": {
  3232  			Schema: map[string]*Schema{
  3233  				"availability_zone": &Schema{
  3234  					Type:     TypeString,
  3235  					Optional: true,
  3236  				},
  3237  			},
  3238  
  3239  			Input:  map[string]string{},
  3240  			Result: map[string]interface{}{},
  3241  			Err:    false,
  3242  		},
  3243  
  3244  		"input ignored when config has a value": {
  3245  			Schema: map[string]*Schema{
  3246  				"availability_zone": &Schema{
  3247  					Type:     TypeString,
  3248  					Optional: true,
  3249  				},
  3250  			},
  3251  
  3252  			Config: map[string]interface{}{
  3253  				"availability_zone": "bar",
  3254  			},
  3255  
  3256  			Input: map[string]string{
  3257  				"availability_zone": "foo",
  3258  			},
  3259  
  3260  			Result: map[string]interface{}{},
  3261  
  3262  			Err: false,
  3263  		},
  3264  
  3265  		"input ignored when schema has a default": {
  3266  			Schema: map[string]*Schema{
  3267  				"availability_zone": &Schema{
  3268  					Type:     TypeString,
  3269  					Default:  "foo",
  3270  					Optional: true,
  3271  				},
  3272  			},
  3273  
  3274  			Input: map[string]string{
  3275  				"availability_zone": "bar",
  3276  			},
  3277  
  3278  			Result: map[string]interface{}{},
  3279  
  3280  			Err: false,
  3281  		},
  3282  
  3283  		"input ignored when default function returns a value": {
  3284  			Schema: map[string]*Schema{
  3285  				"availability_zone": &Schema{
  3286  					Type: TypeString,
  3287  					DefaultFunc: func() (interface{}, error) {
  3288  						return "foo", nil
  3289  					},
  3290  					Optional: true,
  3291  				},
  3292  			},
  3293  
  3294  			Input: map[string]string{
  3295  				"availability_zone": "bar",
  3296  			},
  3297  
  3298  			Result: map[string]interface{}{},
  3299  
  3300  			Err: false,
  3301  		},
  3302  
  3303  		"input ignored when default function returns an empty string": {
  3304  			Schema: map[string]*Schema{
  3305  				"availability_zone": &Schema{
  3306  					Type:     TypeString,
  3307  					Default:  "",
  3308  					Optional: true,
  3309  				},
  3310  			},
  3311  
  3312  			Input: map[string]string{
  3313  				"availability_zone": "bar",
  3314  			},
  3315  
  3316  			Result: map[string]interface{}{},
  3317  
  3318  			Err: false,
  3319  		},
  3320  
  3321  		"input used when default function returns nil": {
  3322  			Schema: map[string]*Schema{
  3323  				"availability_zone": &Schema{
  3324  					Type: TypeString,
  3325  					DefaultFunc: func() (interface{}, error) {
  3326  						return nil, nil
  3327  					},
  3328  					Required: true,
  3329  				},
  3330  			},
  3331  
  3332  			Input: map[string]string{
  3333  				"availability_zone": "bar",
  3334  			},
  3335  
  3336  			Result: map[string]interface{}{
  3337  				"availability_zone": "bar",
  3338  			},
  3339  
  3340  			Err: false,
  3341  		},
  3342  
  3343  		"input not used when optional default function returns nil": {
  3344  			Schema: map[string]*Schema{
  3345  				"availability_zone": &Schema{
  3346  					Type: TypeString,
  3347  					DefaultFunc: func() (interface{}, error) {
  3348  						return nil, nil
  3349  					},
  3350  					Optional: true,
  3351  				},
  3352  			},
  3353  
  3354  			Input:  map[string]string{},
  3355  			Result: map[string]interface{}{},
  3356  			Err:    false,
  3357  		},
  3358  	}
  3359  
  3360  	for i, tc := range cases {
  3361  		if tc.Config == nil {
  3362  			tc.Config = make(map[string]interface{})
  3363  		}
  3364  
  3365  		c, err := config.NewRawConfig(tc.Config)
  3366  		if err != nil {
  3367  			t.Fatalf("err: %s", err)
  3368  		}
  3369  
  3370  		input := new(terraform.MockUIInput)
  3371  		input.InputReturnMap = tc.Input
  3372  
  3373  		rc := terraform.NewResourceConfig(c)
  3374  		rc.Config = make(map[string]interface{})
  3375  
  3376  		actual, err := schemaMap(tc.Schema).Input(input, rc)
  3377  		if err != nil != tc.Err {
  3378  			t.Fatalf("#%v err: %s", i, err)
  3379  		}
  3380  
  3381  		if !reflect.DeepEqual(tc.Result, actual.Config) {
  3382  			t.Fatalf("#%v: bad:\n\ngot: %#v\nexpected: %#v", i, actual.Config, tc.Result)
  3383  		}
  3384  	}
  3385  }
  3386  
  3387  func TestSchemaMap_InputDefault(t *testing.T) {
  3388  	emptyConfig := make(map[string]interface{})
  3389  	c, err := config.NewRawConfig(emptyConfig)
  3390  	if err != nil {
  3391  		t.Fatalf("err: %s", err)
  3392  	}
  3393  	rc := terraform.NewResourceConfig(c)
  3394  	rc.Config = make(map[string]interface{})
  3395  
  3396  	input := new(terraform.MockUIInput)
  3397  	input.InputFn = func(opts *terraform.InputOpts) (string, error) {
  3398  		t.Fatalf("InputFn should not be called on: %#v", opts)
  3399  		return "", nil
  3400  	}
  3401  
  3402  	schema := map[string]*Schema{
  3403  		"availability_zone": &Schema{
  3404  			Type:     TypeString,
  3405  			Default:  "foo",
  3406  			Optional: true,
  3407  		},
  3408  	}
  3409  	actual, err := schemaMap(schema).Input(input, rc)
  3410  	if err != nil {
  3411  		t.Fatalf("err: %s", err)
  3412  	}
  3413  
  3414  	expected := map[string]interface{}{}
  3415  
  3416  	if !reflect.DeepEqual(expected, actual.Config) {
  3417  		t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected)
  3418  	}
  3419  }
  3420  
  3421  func TestSchemaMap_InputDeprecated(t *testing.T) {
  3422  	emptyConfig := make(map[string]interface{})
  3423  	c, err := config.NewRawConfig(emptyConfig)
  3424  	if err != nil {
  3425  		t.Fatalf("err: %s", err)
  3426  	}
  3427  	rc := terraform.NewResourceConfig(c)
  3428  	rc.Config = make(map[string]interface{})
  3429  
  3430  	input := new(terraform.MockUIInput)
  3431  	input.InputFn = func(opts *terraform.InputOpts) (string, error) {
  3432  		t.Fatalf("InputFn should not be called on: %#v", opts)
  3433  		return "", nil
  3434  	}
  3435  
  3436  	schema := map[string]*Schema{
  3437  		"availability_zone": &Schema{
  3438  			Type:       TypeString,
  3439  			Deprecated: "long gone",
  3440  			Optional:   true,
  3441  		},
  3442  	}
  3443  	actual, err := schemaMap(schema).Input(input, rc)
  3444  	if err != nil {
  3445  		t.Fatalf("err: %s", err)
  3446  	}
  3447  
  3448  	expected := map[string]interface{}{}
  3449  
  3450  	if !reflect.DeepEqual(expected, actual.Config) {
  3451  		t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected)
  3452  	}
  3453  }
  3454  
  3455  func TestSchemaMap_InternalValidate(t *testing.T) {
  3456  	cases := map[string]struct {
  3457  		In  map[string]*Schema
  3458  		Err bool
  3459  	}{
  3460  		"nothing": {
  3461  			nil,
  3462  			false,
  3463  		},
  3464  
  3465  		"Both optional and required": {
  3466  			map[string]*Schema{
  3467  				"foo": &Schema{
  3468  					Type:     TypeInt,
  3469  					Optional: true,
  3470  					Required: true,
  3471  				},
  3472  			},
  3473  			true,
  3474  		},
  3475  
  3476  		"No optional and no required": {
  3477  			map[string]*Schema{
  3478  				"foo": &Schema{
  3479  					Type: TypeInt,
  3480  				},
  3481  			},
  3482  			true,
  3483  		},
  3484  
  3485  		"Missing Type": {
  3486  			map[string]*Schema{
  3487  				"foo": &Schema{
  3488  					Required: true,
  3489  				},
  3490  			},
  3491  			true,
  3492  		},
  3493  
  3494  		"Required but computed": {
  3495  			map[string]*Schema{
  3496  				"foo": &Schema{
  3497  					Type:     TypeInt,
  3498  					Required: true,
  3499  					Computed: true,
  3500  				},
  3501  			},
  3502  			true,
  3503  		},
  3504  
  3505  		"Looks good": {
  3506  			map[string]*Schema{
  3507  				"foo": &Schema{
  3508  					Type:     TypeString,
  3509  					Required: true,
  3510  				},
  3511  			},
  3512  			false,
  3513  		},
  3514  
  3515  		"Computed but has default": {
  3516  			map[string]*Schema{
  3517  				"foo": &Schema{
  3518  					Type:     TypeInt,
  3519  					Optional: true,
  3520  					Computed: true,
  3521  					Default:  "foo",
  3522  				},
  3523  			},
  3524  			true,
  3525  		},
  3526  
  3527  		"Required but has default": {
  3528  			map[string]*Schema{
  3529  				"foo": &Schema{
  3530  					Type:     TypeInt,
  3531  					Optional: true,
  3532  					Required: true,
  3533  					Default:  "foo",
  3534  				},
  3535  			},
  3536  			true,
  3537  		},
  3538  
  3539  		"List element not set": {
  3540  			map[string]*Schema{
  3541  				"foo": &Schema{
  3542  					Type: TypeList,
  3543  				},
  3544  			},
  3545  			true,
  3546  		},
  3547  
  3548  		"List default": {
  3549  			map[string]*Schema{
  3550  				"foo": &Schema{
  3551  					Type:    TypeList,
  3552  					Elem:    &Schema{Type: TypeInt},
  3553  					Default: "foo",
  3554  				},
  3555  			},
  3556  			true,
  3557  		},
  3558  
  3559  		"List element computed": {
  3560  			map[string]*Schema{
  3561  				"foo": &Schema{
  3562  					Type:     TypeList,
  3563  					Optional: true,
  3564  					Elem: &Schema{
  3565  						Type:     TypeInt,
  3566  						Computed: true,
  3567  					},
  3568  				},
  3569  			},
  3570  			true,
  3571  		},
  3572  
  3573  		"List element with Set set": {
  3574  			map[string]*Schema{
  3575  				"foo": &Schema{
  3576  					Type:     TypeList,
  3577  					Elem:     &Schema{Type: TypeInt},
  3578  					Set:      func(interface{}) int { return 0 },
  3579  					Optional: true,
  3580  				},
  3581  			},
  3582  			true,
  3583  		},
  3584  
  3585  		"Set element with no Set set": {
  3586  			map[string]*Schema{
  3587  				"foo": &Schema{
  3588  					Type:     TypeSet,
  3589  					Elem:     &Schema{Type: TypeInt},
  3590  					Optional: true,
  3591  				},
  3592  			},
  3593  			false,
  3594  		},
  3595  
  3596  		"Required but computedWhen": {
  3597  			map[string]*Schema{
  3598  				"foo": &Schema{
  3599  					Type:         TypeInt,
  3600  					Required:     true,
  3601  					ComputedWhen: []string{"foo"},
  3602  				},
  3603  			},
  3604  			true,
  3605  		},
  3606  
  3607  		"Conflicting attributes cannot be required": {
  3608  			map[string]*Schema{
  3609  				"blacklist": &Schema{
  3610  					Type:     TypeBool,
  3611  					Required: true,
  3612  				},
  3613  				"whitelist": &Schema{
  3614  					Type:          TypeBool,
  3615  					Optional:      true,
  3616  					ConflictsWith: []string{"blacklist"},
  3617  				},
  3618  			},
  3619  			true,
  3620  		},
  3621  
  3622  		"Attribute with conflicts cannot be required": {
  3623  			map[string]*Schema{
  3624  				"whitelist": &Schema{
  3625  					Type:          TypeBool,
  3626  					Required:      true,
  3627  					ConflictsWith: []string{"blacklist"},
  3628  				},
  3629  			},
  3630  			true,
  3631  		},
  3632  
  3633  		"ConflictsWith cannot be used w/ ComputedWhen": {
  3634  			map[string]*Schema{
  3635  				"blacklist": &Schema{
  3636  					Type:         TypeBool,
  3637  					ComputedWhen: []string{"foor"},
  3638  				},
  3639  				"whitelist": &Schema{
  3640  					Type:          TypeBool,
  3641  					Required:      true,
  3642  					ConflictsWith: []string{"blacklist"},
  3643  				},
  3644  			},
  3645  			true,
  3646  		},
  3647  
  3648  		"Sub-resource invalid": {
  3649  			map[string]*Schema{
  3650  				"foo": &Schema{
  3651  					Type:     TypeList,
  3652  					Optional: true,
  3653  					Elem: &Resource{
  3654  						Schema: map[string]*Schema{
  3655  							"foo": new(Schema),
  3656  						},
  3657  					},
  3658  				},
  3659  			},
  3660  			true,
  3661  		},
  3662  
  3663  		"Sub-resource valid": {
  3664  			map[string]*Schema{
  3665  				"foo": &Schema{
  3666  					Type:     TypeList,
  3667  					Optional: true,
  3668  					Elem: &Resource{
  3669  						Schema: map[string]*Schema{
  3670  							"foo": &Schema{
  3671  								Type:     TypeInt,
  3672  								Optional: true,
  3673  							},
  3674  						},
  3675  					},
  3676  				},
  3677  			},
  3678  			false,
  3679  		},
  3680  
  3681  		"ValidateFunc on non-primitive": {
  3682  			map[string]*Schema{
  3683  				"foo": &Schema{
  3684  					Type:     TypeSet,
  3685  					Required: true,
  3686  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  3687  						return
  3688  					},
  3689  				},
  3690  			},
  3691  			true,
  3692  		},
  3693  
  3694  		"computed-only field with validateFunc": {
  3695  			map[string]*Schema{
  3696  				"string": &Schema{
  3697  					Type:     TypeString,
  3698  					Computed: true,
  3699  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  3700  						es = append(es, fmt.Errorf("this is not fine"))
  3701  						return
  3702  					},
  3703  				},
  3704  			},
  3705  			true,
  3706  		},
  3707  
  3708  		"computed-only field with diffSuppressFunc": {
  3709  			map[string]*Schema{
  3710  				"string": &Schema{
  3711  					Type:     TypeString,
  3712  					Computed: true,
  3713  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  3714  						// Always suppress any diff
  3715  						return false
  3716  					},
  3717  				},
  3718  			},
  3719  			true,
  3720  		},
  3721  
  3722  		"invalid field name format #1": {
  3723  			map[string]*Schema{
  3724  				"with space": &Schema{
  3725  					Type:     TypeString,
  3726  					Optional: true,
  3727  				},
  3728  			},
  3729  			true,
  3730  		},
  3731  
  3732  		"invalid field name format #2": {
  3733  			map[string]*Schema{
  3734  				"WithCapitals": &Schema{
  3735  					Type:     TypeString,
  3736  					Optional: true,
  3737  				},
  3738  			},
  3739  			true,
  3740  		},
  3741  
  3742  		"invalid field name format of a Deprecated field": {
  3743  			map[string]*Schema{
  3744  				"WithCapitals": &Schema{
  3745  					Type:       TypeString,
  3746  					Optional:   true,
  3747  					Deprecated: "Use with_underscores instead",
  3748  				},
  3749  			},
  3750  			false,
  3751  		},
  3752  
  3753  		"invalid field name format of a Removed field": {
  3754  			map[string]*Schema{
  3755  				"WithCapitals": &Schema{
  3756  					Type:     TypeString,
  3757  					Optional: true,
  3758  					Removed:  "Use with_underscores instead",
  3759  				},
  3760  			},
  3761  			false,
  3762  		},
  3763  	}
  3764  
  3765  	for tn, tc := range cases {
  3766  		t.Run(tn, func(t *testing.T) {
  3767  			err := schemaMap(tc.In).InternalValidate(nil)
  3768  			if err != nil != tc.Err {
  3769  				if tc.Err {
  3770  					t.Fatalf("%q: Expected error did not occur:\n\n%#v", tn, tc.In)
  3771  				}
  3772  				t.Fatalf("%q: Unexpected error occurred: %s\n\n%#v", tn, err, tc.In)
  3773  			}
  3774  		})
  3775  	}
  3776  
  3777  }
  3778  
  3779  func TestSchemaMap_DiffSuppress(t *testing.T) {
  3780  	cases := map[string]struct {
  3781  		Schema          map[string]*Schema
  3782  		State           *terraform.InstanceState
  3783  		Config          map[string]interface{}
  3784  		ConfigVariables map[string]ast.Variable
  3785  		ExpectedDiff    *terraform.InstanceDiff
  3786  		Err             bool
  3787  	}{
  3788  		"#0 - Suppress otherwise valid diff by returning true": {
  3789  			Schema: map[string]*Schema{
  3790  				"availability_zone": {
  3791  					Type:     TypeString,
  3792  					Optional: true,
  3793  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  3794  						// Always suppress any diff
  3795  						return true
  3796  					},
  3797  				},
  3798  			},
  3799  
  3800  			State: nil,
  3801  
  3802  			Config: map[string]interface{}{
  3803  				"availability_zone": "foo",
  3804  			},
  3805  
  3806  			ExpectedDiff: nil,
  3807  
  3808  			Err: false,
  3809  		},
  3810  
  3811  		"#1 - Don't suppress diff by returning false": {
  3812  			Schema: map[string]*Schema{
  3813  				"availability_zone": {
  3814  					Type:     TypeString,
  3815  					Optional: true,
  3816  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  3817  						// Always suppress any diff
  3818  						return false
  3819  					},
  3820  				},
  3821  			},
  3822  
  3823  			State: nil,
  3824  
  3825  			Config: map[string]interface{}{
  3826  				"availability_zone": "foo",
  3827  			},
  3828  
  3829  			ExpectedDiff: &terraform.InstanceDiff{
  3830  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3831  					"availability_zone": {
  3832  						Old: "",
  3833  						New: "foo",
  3834  					},
  3835  				},
  3836  			},
  3837  
  3838  			Err: false,
  3839  		},
  3840  
  3841  		"Default with suppress makes no diff": {
  3842  			Schema: map[string]*Schema{
  3843  				"availability_zone": {
  3844  					Type:     TypeString,
  3845  					Optional: true,
  3846  					Default:  "foo",
  3847  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  3848  						return true
  3849  					},
  3850  				},
  3851  			},
  3852  
  3853  			State: nil,
  3854  
  3855  			Config: map[string]interface{}{},
  3856  
  3857  			ExpectedDiff: nil,
  3858  
  3859  			Err: false,
  3860  		},
  3861  
  3862  		"Default with false suppress makes diff": {
  3863  			Schema: map[string]*Schema{
  3864  				"availability_zone": {
  3865  					Type:     TypeString,
  3866  					Optional: true,
  3867  					Default:  "foo",
  3868  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  3869  						return false
  3870  					},
  3871  				},
  3872  			},
  3873  
  3874  			State: nil,
  3875  
  3876  			Config: map[string]interface{}{},
  3877  
  3878  			ExpectedDiff: &terraform.InstanceDiff{
  3879  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3880  					"availability_zone": {
  3881  						Old: "",
  3882  						New: "foo",
  3883  					},
  3884  				},
  3885  			},
  3886  
  3887  			Err: false,
  3888  		},
  3889  
  3890  		"Complex structure with set of computed string should mark root set as computed": {
  3891  			Schema: map[string]*Schema{
  3892  				"outer": &Schema{
  3893  					Type:     TypeSet,
  3894  					Optional: true,
  3895  					Elem: &Resource{
  3896  						Schema: map[string]*Schema{
  3897  							"outer_str": &Schema{
  3898  								Type:     TypeString,
  3899  								Optional: true,
  3900  							},
  3901  							"inner": &Schema{
  3902  								Type:     TypeSet,
  3903  								Optional: true,
  3904  								Elem: &Resource{
  3905  									Schema: map[string]*Schema{
  3906  										"inner_str": &Schema{
  3907  											Type:     TypeString,
  3908  											Optional: true,
  3909  										},
  3910  									},
  3911  								},
  3912  								Set: func(v interface{}) int {
  3913  									return 2
  3914  								},
  3915  							},
  3916  						},
  3917  					},
  3918  					Set: func(v interface{}) int {
  3919  						return 1
  3920  					},
  3921  				},
  3922  			},
  3923  
  3924  			State: nil,
  3925  
  3926  			Config: map[string]interface{}{
  3927  				"outer": []map[string]interface{}{
  3928  					map[string]interface{}{
  3929  						"outer_str": "foo",
  3930  						"inner": []map[string]interface{}{
  3931  							map[string]interface{}{
  3932  								"inner_str": "${var.bar}",
  3933  							},
  3934  						},
  3935  					},
  3936  				},
  3937  			},
  3938  
  3939  			ConfigVariables: map[string]ast.Variable{
  3940  				"var.bar": interfaceToVariableSwallowError(config.UnknownVariableValue),
  3941  			},
  3942  
  3943  			ExpectedDiff: &terraform.InstanceDiff{
  3944  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3945  					"outer.#": &terraform.ResourceAttrDiff{
  3946  						Old: "0",
  3947  						New: "1",
  3948  					},
  3949  					"outer.~1.outer_str": &terraform.ResourceAttrDiff{
  3950  						Old: "",
  3951  						New: "foo",
  3952  					},
  3953  					"outer.~1.inner.#": &terraform.ResourceAttrDiff{
  3954  						Old: "0",
  3955  						New: "1",
  3956  					},
  3957  					"outer.~1.inner.~2.inner_str": &terraform.ResourceAttrDiff{
  3958  						Old:         "",
  3959  						New:         "${var.bar}",
  3960  						NewComputed: true,
  3961  					},
  3962  				},
  3963  			},
  3964  
  3965  			Err: false,
  3966  		},
  3967  
  3968  		"Complex structure with complex list of computed string should mark root set as computed": {
  3969  			Schema: map[string]*Schema{
  3970  				"outer": &Schema{
  3971  					Type:     TypeSet,
  3972  					Optional: true,
  3973  					Elem: &Resource{
  3974  						Schema: map[string]*Schema{
  3975  							"outer_str": &Schema{
  3976  								Type:     TypeString,
  3977  								Optional: true,
  3978  							},
  3979  							"inner": &Schema{
  3980  								Type:     TypeList,
  3981  								Optional: true,
  3982  								Elem: &Resource{
  3983  									Schema: map[string]*Schema{
  3984  										"inner_str": &Schema{
  3985  											Type:     TypeString,
  3986  											Optional: true,
  3987  										},
  3988  									},
  3989  								},
  3990  							},
  3991  						},
  3992  					},
  3993  					Set: func(v interface{}) int {
  3994  						return 1
  3995  					},
  3996  				},
  3997  			},
  3998  
  3999  			State: nil,
  4000  
  4001  			Config: map[string]interface{}{
  4002  				"outer": []map[string]interface{}{
  4003  					map[string]interface{}{
  4004  						"outer_str": "foo",
  4005  						"inner": []map[string]interface{}{
  4006  							map[string]interface{}{
  4007  								"inner_str": "${var.bar}",
  4008  							},
  4009  						},
  4010  					},
  4011  				},
  4012  			},
  4013  
  4014  			ConfigVariables: map[string]ast.Variable{
  4015  				"var.bar": interfaceToVariableSwallowError(config.UnknownVariableValue),
  4016  			},
  4017  
  4018  			ExpectedDiff: &terraform.InstanceDiff{
  4019  				Attributes: map[string]*terraform.ResourceAttrDiff{
  4020  					"outer.#": &terraform.ResourceAttrDiff{
  4021  						Old: "0",
  4022  						New: "1",
  4023  					},
  4024  					"outer.~1.outer_str": &terraform.ResourceAttrDiff{
  4025  						Old: "",
  4026  						New: "foo",
  4027  					},
  4028  					"outer.~1.inner.#": &terraform.ResourceAttrDiff{
  4029  						Old: "0",
  4030  						New: "1",
  4031  					},
  4032  					"outer.~1.inner.0.inner_str": &terraform.ResourceAttrDiff{
  4033  						Old:         "",
  4034  						New:         "${var.bar}",
  4035  						NewComputed: true,
  4036  					},
  4037  				},
  4038  			},
  4039  
  4040  			Err: false,
  4041  		},
  4042  	}
  4043  
  4044  	for tn, tc := range cases {
  4045  		t.Run(tn, func(t *testing.T) {
  4046  			c, err := config.NewRawConfig(tc.Config)
  4047  			if err != nil {
  4048  				t.Fatalf("#%q err: %s", tn, err)
  4049  			}
  4050  
  4051  			if len(tc.ConfigVariables) > 0 {
  4052  				if err := c.Interpolate(tc.ConfigVariables); err != nil {
  4053  					t.Fatalf("#%q err: %s", tn, err)
  4054  				}
  4055  			}
  4056  
  4057  			d, err := schemaMap(tc.Schema).Diff(tc.State, terraform.NewResourceConfig(c), nil, nil)
  4058  			if err != nil != tc.Err {
  4059  				t.Fatalf("#%q err: %s", tn, err)
  4060  			}
  4061  
  4062  			if !reflect.DeepEqual(tc.ExpectedDiff, d) {
  4063  				t.Fatalf("#%q:\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.ExpectedDiff, d)
  4064  			}
  4065  		})
  4066  	}
  4067  }
  4068  
  4069  func TestSchemaMap_Validate(t *testing.T) {
  4070  	cases := map[string]struct {
  4071  		Schema   map[string]*Schema
  4072  		Config   map[string]interface{}
  4073  		Vars     map[string]string
  4074  		Err      bool
  4075  		Errors   []error
  4076  		Warnings []string
  4077  	}{
  4078  		"Good": {
  4079  			Schema: map[string]*Schema{
  4080  				"availability_zone": &Schema{
  4081  					Type:     TypeString,
  4082  					Optional: true,
  4083  					Computed: true,
  4084  					ForceNew: true,
  4085  				},
  4086  			},
  4087  
  4088  			Config: map[string]interface{}{
  4089  				"availability_zone": "foo",
  4090  			},
  4091  		},
  4092  
  4093  		"Good, because the var is not set and that error will come elsewhere": {
  4094  			Schema: map[string]*Schema{
  4095  				"size": &Schema{
  4096  					Type:     TypeInt,
  4097  					Required: true,
  4098  				},
  4099  			},
  4100  
  4101  			Config: map[string]interface{}{
  4102  				"size": "${var.foo}",
  4103  			},
  4104  
  4105  			Vars: map[string]string{
  4106  				"var.foo": config.UnknownVariableValue,
  4107  			},
  4108  		},
  4109  
  4110  		"Required field not set": {
  4111  			Schema: map[string]*Schema{
  4112  				"availability_zone": &Schema{
  4113  					Type:     TypeString,
  4114  					Required: true,
  4115  				},
  4116  			},
  4117  
  4118  			Config: map[string]interface{}{},
  4119  
  4120  			Err: true,
  4121  		},
  4122  
  4123  		"Invalid basic type": {
  4124  			Schema: map[string]*Schema{
  4125  				"port": &Schema{
  4126  					Type:     TypeInt,
  4127  					Required: true,
  4128  				},
  4129  			},
  4130  
  4131  			Config: map[string]interface{}{
  4132  				"port": "I am invalid",
  4133  			},
  4134  
  4135  			Err: true,
  4136  		},
  4137  
  4138  		"Invalid complex type": {
  4139  			Schema: map[string]*Schema{
  4140  				"user_data": &Schema{
  4141  					Type:     TypeString,
  4142  					Optional: true,
  4143  				},
  4144  			},
  4145  
  4146  			Config: map[string]interface{}{
  4147  				"user_data": []interface{}{
  4148  					map[string]interface{}{
  4149  						"foo": "bar",
  4150  					},
  4151  				},
  4152  			},
  4153  
  4154  			Err: true,
  4155  		},
  4156  
  4157  		"Bad type, interpolated": {
  4158  			Schema: map[string]*Schema{
  4159  				"size": &Schema{
  4160  					Type:     TypeInt,
  4161  					Required: true,
  4162  				},
  4163  			},
  4164  
  4165  			Config: map[string]interface{}{
  4166  				"size": "${var.foo}",
  4167  			},
  4168  
  4169  			Vars: map[string]string{
  4170  				"var.foo": "nope",
  4171  			},
  4172  
  4173  			Err: true,
  4174  		},
  4175  
  4176  		"Required but has DefaultFunc": {
  4177  			Schema: map[string]*Schema{
  4178  				"availability_zone": &Schema{
  4179  					Type:     TypeString,
  4180  					Required: true,
  4181  					DefaultFunc: func() (interface{}, error) {
  4182  						return "foo", nil
  4183  					},
  4184  				},
  4185  			},
  4186  
  4187  			Config: nil,
  4188  		},
  4189  
  4190  		"Required but has DefaultFunc return nil": {
  4191  			Schema: map[string]*Schema{
  4192  				"availability_zone": &Schema{
  4193  					Type:     TypeString,
  4194  					Required: true,
  4195  					DefaultFunc: func() (interface{}, error) {
  4196  						return nil, nil
  4197  					},
  4198  				},
  4199  			},
  4200  
  4201  			Config: nil,
  4202  
  4203  			Err: true,
  4204  		},
  4205  
  4206  		"List with promotion": {
  4207  			Schema: map[string]*Schema{
  4208  				"ingress": &Schema{
  4209  					Type:          TypeList,
  4210  					Elem:          &Schema{Type: TypeInt},
  4211  					PromoteSingle: true,
  4212  					Optional:      true,
  4213  				},
  4214  			},
  4215  
  4216  			Config: map[string]interface{}{
  4217  				"ingress": "5",
  4218  			},
  4219  
  4220  			Err: false,
  4221  		},
  4222  
  4223  		"List with promotion set as list": {
  4224  			Schema: map[string]*Schema{
  4225  				"ingress": &Schema{
  4226  					Type:          TypeList,
  4227  					Elem:          &Schema{Type: TypeInt},
  4228  					PromoteSingle: true,
  4229  					Optional:      true,
  4230  				},
  4231  			},
  4232  
  4233  			Config: map[string]interface{}{
  4234  				"ingress": []interface{}{"5"},
  4235  			},
  4236  
  4237  			Err: false,
  4238  		},
  4239  
  4240  		"Optional sub-resource": {
  4241  			Schema: map[string]*Schema{
  4242  				"ingress": &Schema{
  4243  					Type: TypeList,
  4244  					Elem: &Resource{
  4245  						Schema: map[string]*Schema{
  4246  							"from": &Schema{
  4247  								Type:     TypeInt,
  4248  								Required: true,
  4249  							},
  4250  						},
  4251  					},
  4252  				},
  4253  			},
  4254  
  4255  			Config: map[string]interface{}{},
  4256  
  4257  			Err: false,
  4258  		},
  4259  
  4260  		"Sub-resource is the wrong type": {
  4261  			Schema: map[string]*Schema{
  4262  				"ingress": &Schema{
  4263  					Type:     TypeList,
  4264  					Required: true,
  4265  					Elem: &Resource{
  4266  						Schema: map[string]*Schema{
  4267  							"from": &Schema{
  4268  								Type:     TypeInt,
  4269  								Required: true,
  4270  							},
  4271  						},
  4272  					},
  4273  				},
  4274  			},
  4275  
  4276  			Config: map[string]interface{}{
  4277  				"ingress": []interface{}{"foo"},
  4278  			},
  4279  
  4280  			Err: true,
  4281  		},
  4282  
  4283  		"Not a list": {
  4284  			Schema: map[string]*Schema{
  4285  				"ingress": &Schema{
  4286  					Type: TypeList,
  4287  					Elem: &Resource{
  4288  						Schema: map[string]*Schema{
  4289  							"from": &Schema{
  4290  								Type:     TypeInt,
  4291  								Required: true,
  4292  							},
  4293  						},
  4294  					},
  4295  				},
  4296  			},
  4297  
  4298  			Config: map[string]interface{}{
  4299  				"ingress": "foo",
  4300  			},
  4301  
  4302  			Err: true,
  4303  		},
  4304  
  4305  		"Required sub-resource field": {
  4306  			Schema: map[string]*Schema{
  4307  				"ingress": &Schema{
  4308  					Type: TypeList,
  4309  					Elem: &Resource{
  4310  						Schema: map[string]*Schema{
  4311  							"from": &Schema{
  4312  								Type:     TypeInt,
  4313  								Required: true,
  4314  							},
  4315  						},
  4316  					},
  4317  				},
  4318  			},
  4319  
  4320  			Config: map[string]interface{}{
  4321  				"ingress": []interface{}{
  4322  					map[string]interface{}{},
  4323  				},
  4324  			},
  4325  
  4326  			Err: true,
  4327  		},
  4328  
  4329  		"Good sub-resource": {
  4330  			Schema: map[string]*Schema{
  4331  				"ingress": &Schema{
  4332  					Type:     TypeList,
  4333  					Optional: true,
  4334  					Elem: &Resource{
  4335  						Schema: map[string]*Schema{
  4336  							"from": &Schema{
  4337  								Type:     TypeInt,
  4338  								Required: true,
  4339  							},
  4340  						},
  4341  					},
  4342  				},
  4343  			},
  4344  
  4345  			Config: map[string]interface{}{
  4346  				"ingress": []interface{}{
  4347  					map[string]interface{}{
  4348  						"from": 80,
  4349  					},
  4350  				},
  4351  			},
  4352  
  4353  			Err: false,
  4354  		},
  4355  
  4356  		"Good sub-resource, interpolated value": {
  4357  			Schema: map[string]*Schema{
  4358  				"ingress": &Schema{
  4359  					Type:     TypeList,
  4360  					Optional: true,
  4361  					Elem: &Resource{
  4362  						Schema: map[string]*Schema{
  4363  							"from": &Schema{
  4364  								Type:     TypeInt,
  4365  								Required: true,
  4366  							},
  4367  						},
  4368  					},
  4369  				},
  4370  			},
  4371  
  4372  			Config: map[string]interface{}{
  4373  				"ingress": []interface{}{
  4374  					`${map("from", "80")}`,
  4375  				},
  4376  			},
  4377  
  4378  			Vars: map[string]string{},
  4379  
  4380  			Err: false,
  4381  		},
  4382  
  4383  		"Good sub-resource, computed value": {
  4384  			Schema: map[string]*Schema{
  4385  				"ingress": &Schema{
  4386  					Type:     TypeList,
  4387  					Optional: true,
  4388  					Elem: &Resource{
  4389  						Schema: map[string]*Schema{
  4390  							"from": &Schema{
  4391  								Type:     TypeInt,
  4392  								Optional: true,
  4393  							},
  4394  						},
  4395  					},
  4396  				},
  4397  			},
  4398  
  4399  			Config: map[string]interface{}{
  4400  				"ingress": []interface{}{
  4401  					`${map("from", var.port)}`,
  4402  				},
  4403  			},
  4404  
  4405  			Vars: map[string]string{
  4406  				"var.port": config.UnknownVariableValue,
  4407  			},
  4408  
  4409  			Err: false,
  4410  		},
  4411  
  4412  		"Invalid/unknown field": {
  4413  			Schema: map[string]*Schema{
  4414  				"availability_zone": &Schema{
  4415  					Type:     TypeString,
  4416  					Optional: true,
  4417  					Computed: true,
  4418  					ForceNew: true,
  4419  				},
  4420  			},
  4421  
  4422  			Config: map[string]interface{}{
  4423  				"foo": "bar",
  4424  			},
  4425  
  4426  			Err: true,
  4427  		},
  4428  
  4429  		"Invalid/unknown field with computed value": {
  4430  			Schema: map[string]*Schema{
  4431  				"availability_zone": &Schema{
  4432  					Type:     TypeString,
  4433  					Optional: true,
  4434  					Computed: true,
  4435  					ForceNew: true,
  4436  				},
  4437  			},
  4438  
  4439  			Config: map[string]interface{}{
  4440  				"foo": "${var.foo}",
  4441  			},
  4442  
  4443  			Vars: map[string]string{
  4444  				"var.foo": config.UnknownVariableValue,
  4445  			},
  4446  
  4447  			Err: true,
  4448  		},
  4449  
  4450  		"Computed field set": {
  4451  			Schema: map[string]*Schema{
  4452  				"availability_zone": &Schema{
  4453  					Type:     TypeString,
  4454  					Computed: true,
  4455  				},
  4456  			},
  4457  
  4458  			Config: map[string]interface{}{
  4459  				"availability_zone": "bar",
  4460  			},
  4461  
  4462  			Err: true,
  4463  		},
  4464  
  4465  		"Not a set": {
  4466  			Schema: map[string]*Schema{
  4467  				"ports": &Schema{
  4468  					Type:     TypeSet,
  4469  					Required: true,
  4470  					Elem:     &Schema{Type: TypeInt},
  4471  					Set: func(a interface{}) int {
  4472  						return a.(int)
  4473  					},
  4474  				},
  4475  			},
  4476  
  4477  			Config: map[string]interface{}{
  4478  				"ports": "foo",
  4479  			},
  4480  
  4481  			Err: true,
  4482  		},
  4483  
  4484  		"Maps": {
  4485  			Schema: map[string]*Schema{
  4486  				"user_data": &Schema{
  4487  					Type:     TypeMap,
  4488  					Optional: true,
  4489  				},
  4490  			},
  4491  
  4492  			Config: map[string]interface{}{
  4493  				"user_data": "foo",
  4494  			},
  4495  
  4496  			Err: true,
  4497  		},
  4498  
  4499  		"Good map: data surrounded by extra slice": {
  4500  			Schema: map[string]*Schema{
  4501  				"user_data": &Schema{
  4502  					Type:     TypeMap,
  4503  					Optional: true,
  4504  				},
  4505  			},
  4506  
  4507  			Config: map[string]interface{}{
  4508  				"user_data": []interface{}{
  4509  					map[string]interface{}{
  4510  						"foo": "bar",
  4511  					},
  4512  				},
  4513  			},
  4514  		},
  4515  
  4516  		"Good map": {
  4517  			Schema: map[string]*Schema{
  4518  				"user_data": &Schema{
  4519  					Type:     TypeMap,
  4520  					Optional: true,
  4521  				},
  4522  			},
  4523  
  4524  			Config: map[string]interface{}{
  4525  				"user_data": map[string]interface{}{
  4526  					"foo": "bar",
  4527  				},
  4528  			},
  4529  		},
  4530  
  4531  		"Bad map: just a slice": {
  4532  			Schema: map[string]*Schema{
  4533  				"user_data": &Schema{
  4534  					Type:     TypeMap,
  4535  					Optional: true,
  4536  				},
  4537  			},
  4538  
  4539  			Config: map[string]interface{}{
  4540  				"user_data": []interface{}{
  4541  					"foo",
  4542  				},
  4543  			},
  4544  
  4545  			Err: true,
  4546  		},
  4547  
  4548  		"Good set: config has slice with single interpolated value": {
  4549  			Schema: map[string]*Schema{
  4550  				"security_groups": &Schema{
  4551  					Type:     TypeSet,
  4552  					Optional: true,
  4553  					Computed: true,
  4554  					ForceNew: true,
  4555  					Elem:     &Schema{Type: TypeString},
  4556  					Set: func(v interface{}) int {
  4557  						return len(v.(string))
  4558  					},
  4559  				},
  4560  			},
  4561  
  4562  			Config: map[string]interface{}{
  4563  				"security_groups": []interface{}{"${var.foo}"},
  4564  			},
  4565  
  4566  			Err: false,
  4567  		},
  4568  
  4569  		"Bad set: config has single interpolated value": {
  4570  			Schema: map[string]*Schema{
  4571  				"security_groups": &Schema{
  4572  					Type:     TypeSet,
  4573  					Optional: true,
  4574  					Computed: true,
  4575  					ForceNew: true,
  4576  					Elem:     &Schema{Type: TypeString},
  4577  				},
  4578  			},
  4579  
  4580  			Config: map[string]interface{}{
  4581  				"security_groups": "${var.foo}",
  4582  			},
  4583  
  4584  			Err: true,
  4585  		},
  4586  
  4587  		"Bad, subresource should not allow unknown elements": {
  4588  			Schema: map[string]*Schema{
  4589  				"ingress": &Schema{
  4590  					Type:     TypeList,
  4591  					Optional: true,
  4592  					Elem: &Resource{
  4593  						Schema: map[string]*Schema{
  4594  							"port": &Schema{
  4595  								Type:     TypeInt,
  4596  								Required: true,
  4597  							},
  4598  						},
  4599  					},
  4600  				},
  4601  			},
  4602  
  4603  			Config: map[string]interface{}{
  4604  				"ingress": []interface{}{
  4605  					map[string]interface{}{
  4606  						"port":  80,
  4607  						"other": "yes",
  4608  					},
  4609  				},
  4610  			},
  4611  
  4612  			Err: true,
  4613  		},
  4614  
  4615  		"Bad, subresource should not allow invalid types": {
  4616  			Schema: map[string]*Schema{
  4617  				"ingress": &Schema{
  4618  					Type:     TypeList,
  4619  					Optional: true,
  4620  					Elem: &Resource{
  4621  						Schema: map[string]*Schema{
  4622  							"port": &Schema{
  4623  								Type:     TypeInt,
  4624  								Required: true,
  4625  							},
  4626  						},
  4627  					},
  4628  				},
  4629  			},
  4630  
  4631  			Config: map[string]interface{}{
  4632  				"ingress": []interface{}{
  4633  					map[string]interface{}{
  4634  						"port": "bad",
  4635  					},
  4636  				},
  4637  			},
  4638  
  4639  			Err: true,
  4640  		},
  4641  
  4642  		"Bad, should not allow lists to be assigned to string attributes": {
  4643  			Schema: map[string]*Schema{
  4644  				"availability_zone": &Schema{
  4645  					Type:     TypeString,
  4646  					Required: true,
  4647  				},
  4648  			},
  4649  
  4650  			Config: map[string]interface{}{
  4651  				"availability_zone": []interface{}{"foo", "bar", "baz"},
  4652  			},
  4653  
  4654  			Err: true,
  4655  		},
  4656  
  4657  		"Bad, should not allow maps to be assigned to string attributes": {
  4658  			Schema: map[string]*Schema{
  4659  				"availability_zone": &Schema{
  4660  					Type:     TypeString,
  4661  					Required: true,
  4662  				},
  4663  			},
  4664  
  4665  			Config: map[string]interface{}{
  4666  				"availability_zone": map[string]interface{}{"foo": "bar", "baz": "thing"},
  4667  			},
  4668  
  4669  			Err: true,
  4670  		},
  4671  
  4672  		"Deprecated attribute usage generates warning, but not error": {
  4673  			Schema: map[string]*Schema{
  4674  				"old_news": &Schema{
  4675  					Type:       TypeString,
  4676  					Optional:   true,
  4677  					Deprecated: "please use 'new_news' instead",
  4678  				},
  4679  			},
  4680  
  4681  			Config: map[string]interface{}{
  4682  				"old_news": "extra extra!",
  4683  			},
  4684  
  4685  			Err: false,
  4686  
  4687  			Warnings: []string{
  4688  				"\"old_news\": [DEPRECATED] please use 'new_news' instead",
  4689  			},
  4690  		},
  4691  
  4692  		"Deprecated generates no warnings if attr not used": {
  4693  			Schema: map[string]*Schema{
  4694  				"old_news": &Schema{
  4695  					Type:       TypeString,
  4696  					Optional:   true,
  4697  					Deprecated: "please use 'new_news' instead",
  4698  				},
  4699  			},
  4700  
  4701  			Err: false,
  4702  
  4703  			Warnings: nil,
  4704  		},
  4705  
  4706  		"Removed attribute usage generates error": {
  4707  			Schema: map[string]*Schema{
  4708  				"long_gone": &Schema{
  4709  					Type:     TypeString,
  4710  					Optional: true,
  4711  					Removed:  "no longer supported by Cloud API",
  4712  				},
  4713  			},
  4714  
  4715  			Config: map[string]interface{}{
  4716  				"long_gone": "still here!",
  4717  			},
  4718  
  4719  			Err: true,
  4720  			Errors: []error{
  4721  				fmt.Errorf("\"long_gone\": [REMOVED] no longer supported by Cloud API"),
  4722  			},
  4723  		},
  4724  
  4725  		"Removed generates no errors if attr not used": {
  4726  			Schema: map[string]*Schema{
  4727  				"long_gone": &Schema{
  4728  					Type:     TypeString,
  4729  					Optional: true,
  4730  					Removed:  "no longer supported by Cloud API",
  4731  				},
  4732  			},
  4733  
  4734  			Err: false,
  4735  		},
  4736  
  4737  		"Conflicting attributes generate error": {
  4738  			Schema: map[string]*Schema{
  4739  				"whitelist": &Schema{
  4740  					Type:     TypeString,
  4741  					Optional: true,
  4742  				},
  4743  				"blacklist": &Schema{
  4744  					Type:          TypeString,
  4745  					Optional:      true,
  4746  					ConflictsWith: []string{"whitelist"},
  4747  				},
  4748  			},
  4749  
  4750  			Config: map[string]interface{}{
  4751  				"whitelist": "white-val",
  4752  				"blacklist": "black-val",
  4753  			},
  4754  
  4755  			Err: true,
  4756  			Errors: []error{
  4757  				fmt.Errorf("\"blacklist\": conflicts with whitelist (\"white-val\")"),
  4758  			},
  4759  		},
  4760  
  4761  		"Required attribute & undefined conflicting optional are good": {
  4762  			Schema: map[string]*Schema{
  4763  				"required_att": &Schema{
  4764  					Type:     TypeString,
  4765  					Required: true,
  4766  				},
  4767  				"optional_att": &Schema{
  4768  					Type:          TypeString,
  4769  					Optional:      true,
  4770  					ConflictsWith: []string{"required_att"},
  4771  				},
  4772  			},
  4773  
  4774  			Config: map[string]interface{}{
  4775  				"required_att": "required-val",
  4776  			},
  4777  
  4778  			Err: false,
  4779  		},
  4780  
  4781  		"Required conflicting attribute & defined optional generate error": {
  4782  			Schema: map[string]*Schema{
  4783  				"required_att": &Schema{
  4784  					Type:     TypeString,
  4785  					Required: true,
  4786  				},
  4787  				"optional_att": &Schema{
  4788  					Type:          TypeString,
  4789  					Optional:      true,
  4790  					ConflictsWith: []string{"required_att"},
  4791  				},
  4792  			},
  4793  
  4794  			Config: map[string]interface{}{
  4795  				"required_att": "required-val",
  4796  				"optional_att": "optional-val",
  4797  			},
  4798  
  4799  			Err: true,
  4800  			Errors: []error{
  4801  				fmt.Errorf(`"optional_att": conflicts with required_att ("required-val")`),
  4802  			},
  4803  		},
  4804  
  4805  		"Computed + Optional fields conflicting with each other": {
  4806  			Schema: map[string]*Schema{
  4807  				"foo_att": &Schema{
  4808  					Type:          TypeString,
  4809  					Optional:      true,
  4810  					Computed:      true,
  4811  					ConflictsWith: []string{"bar_att"},
  4812  				},
  4813  				"bar_att": &Schema{
  4814  					Type:          TypeString,
  4815  					Optional:      true,
  4816  					Computed:      true,
  4817  					ConflictsWith: []string{"foo_att"},
  4818  				},
  4819  			},
  4820  
  4821  			Config: map[string]interface{}{
  4822  				"foo_att": "foo-val",
  4823  				"bar_att": "bar-val",
  4824  			},
  4825  
  4826  			Err: true,
  4827  			Errors: []error{
  4828  				fmt.Errorf(`"foo_att": conflicts with bar_att ("bar-val")`),
  4829  				fmt.Errorf(`"bar_att": conflicts with foo_att ("foo-val")`),
  4830  			},
  4831  		},
  4832  
  4833  		"Computed + Optional fields NOT conflicting with each other": {
  4834  			Schema: map[string]*Schema{
  4835  				"foo_att": &Schema{
  4836  					Type:          TypeString,
  4837  					Optional:      true,
  4838  					Computed:      true,
  4839  					ConflictsWith: []string{"bar_att"},
  4840  				},
  4841  				"bar_att": &Schema{
  4842  					Type:          TypeString,
  4843  					Optional:      true,
  4844  					Computed:      true,
  4845  					ConflictsWith: []string{"foo_att"},
  4846  				},
  4847  			},
  4848  
  4849  			Config: map[string]interface{}{
  4850  				"foo_att": "foo-val",
  4851  			},
  4852  
  4853  			Err: false,
  4854  		},
  4855  
  4856  		"Computed + Optional fields that conflict with none set": {
  4857  			Schema: map[string]*Schema{
  4858  				"foo_att": &Schema{
  4859  					Type:          TypeString,
  4860  					Optional:      true,
  4861  					Computed:      true,
  4862  					ConflictsWith: []string{"bar_att"},
  4863  				},
  4864  				"bar_att": &Schema{
  4865  					Type:          TypeString,
  4866  					Optional:      true,
  4867  					Computed:      true,
  4868  					ConflictsWith: []string{"foo_att"},
  4869  				},
  4870  			},
  4871  
  4872  			Config: map[string]interface{}{},
  4873  
  4874  			Err: false,
  4875  		},
  4876  
  4877  		"Good with ValidateFunc": {
  4878  			Schema: map[string]*Schema{
  4879  				"validate_me": &Schema{
  4880  					Type:     TypeString,
  4881  					Required: true,
  4882  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  4883  						return
  4884  					},
  4885  				},
  4886  			},
  4887  			Config: map[string]interface{}{
  4888  				"validate_me": "valid",
  4889  			},
  4890  			Err: false,
  4891  		},
  4892  
  4893  		"Bad with ValidateFunc": {
  4894  			Schema: map[string]*Schema{
  4895  				"validate_me": &Schema{
  4896  					Type:     TypeString,
  4897  					Required: true,
  4898  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  4899  						es = append(es, fmt.Errorf("something is not right here"))
  4900  						return
  4901  					},
  4902  				},
  4903  			},
  4904  			Config: map[string]interface{}{
  4905  				"validate_me": "invalid",
  4906  			},
  4907  			Err: true,
  4908  			Errors: []error{
  4909  				fmt.Errorf(`something is not right here`),
  4910  			},
  4911  		},
  4912  
  4913  		"ValidateFunc not called when type does not match": {
  4914  			Schema: map[string]*Schema{
  4915  				"number": &Schema{
  4916  					Type:     TypeInt,
  4917  					Required: true,
  4918  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  4919  						t.Fatalf("Should not have gotten validate call")
  4920  						return
  4921  					},
  4922  				},
  4923  			},
  4924  			Config: map[string]interface{}{
  4925  				"number": "NaN",
  4926  			},
  4927  			Err: true,
  4928  		},
  4929  
  4930  		"ValidateFunc gets decoded type": {
  4931  			Schema: map[string]*Schema{
  4932  				"maybe": &Schema{
  4933  					Type:     TypeBool,
  4934  					Required: true,
  4935  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  4936  						if _, ok := v.(bool); !ok {
  4937  							t.Fatalf("Expected bool, got: %#v", v)
  4938  						}
  4939  						return
  4940  					},
  4941  				},
  4942  			},
  4943  			Config: map[string]interface{}{
  4944  				"maybe": "true",
  4945  			},
  4946  		},
  4947  
  4948  		"ValidateFunc is not called with a computed value": {
  4949  			Schema: map[string]*Schema{
  4950  				"validate_me": &Schema{
  4951  					Type:     TypeString,
  4952  					Required: true,
  4953  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  4954  						es = append(es, fmt.Errorf("something is not right here"))
  4955  						return
  4956  					},
  4957  				},
  4958  			},
  4959  			Config: map[string]interface{}{
  4960  				"validate_me": "${var.foo}",
  4961  			},
  4962  			Vars: map[string]string{
  4963  				"var.foo": config.UnknownVariableValue,
  4964  			},
  4965  
  4966  			Err: false,
  4967  		},
  4968  
  4969  		"special timeouts field": {
  4970  			Schema: map[string]*Schema{
  4971  				"availability_zone": &Schema{
  4972  					Type:     TypeString,
  4973  					Optional: true,
  4974  					Computed: true,
  4975  					ForceNew: true,
  4976  				},
  4977  			},
  4978  
  4979  			Config: map[string]interface{}{
  4980  				TimeoutsConfigKey: "bar",
  4981  			},
  4982  
  4983  			Err: false,
  4984  		},
  4985  
  4986  		"invalid bool field": {
  4987  			Schema: map[string]*Schema{
  4988  				"bool_field": {
  4989  					Type:     TypeBool,
  4990  					Optional: true,
  4991  				},
  4992  			},
  4993  			Config: map[string]interface{}{
  4994  				"bool_field": "abcdef",
  4995  			},
  4996  			Err: true,
  4997  		},
  4998  		"invalid integer field": {
  4999  			Schema: map[string]*Schema{
  5000  				"integer_field": {
  5001  					Type:     TypeInt,
  5002  					Optional: true,
  5003  				},
  5004  			},
  5005  			Config: map[string]interface{}{
  5006  				"integer_field": "abcdef",
  5007  			},
  5008  			Err: true,
  5009  		},
  5010  		"invalid float field": {
  5011  			Schema: map[string]*Schema{
  5012  				"float_field": {
  5013  					Type:     TypeFloat,
  5014  					Optional: true,
  5015  				},
  5016  			},
  5017  			Config: map[string]interface{}{
  5018  				"float_field": "abcdef",
  5019  			},
  5020  			Err: true,
  5021  		},
  5022  
  5023  		// Invalid map values
  5024  		"invalid bool map value": {
  5025  			Schema: map[string]*Schema{
  5026  				"boolMap": &Schema{
  5027  					Type:     TypeMap,
  5028  					Elem:     TypeBool,
  5029  					Optional: true,
  5030  				},
  5031  			},
  5032  			Config: map[string]interface{}{
  5033  				"boolMap": map[string]interface{}{
  5034  					"boolField": "notbool",
  5035  				},
  5036  			},
  5037  			Err: true,
  5038  		},
  5039  		"invalid int map value": {
  5040  			Schema: map[string]*Schema{
  5041  				"intMap": &Schema{
  5042  					Type:     TypeMap,
  5043  					Elem:     TypeInt,
  5044  					Optional: true,
  5045  				},
  5046  			},
  5047  			Config: map[string]interface{}{
  5048  				"intMap": map[string]interface{}{
  5049  					"intField": "notInt",
  5050  				},
  5051  			},
  5052  			Err: true,
  5053  		},
  5054  		"invalid float map value": {
  5055  			Schema: map[string]*Schema{
  5056  				"floatMap": &Schema{
  5057  					Type:     TypeMap,
  5058  					Elem:     TypeFloat,
  5059  					Optional: true,
  5060  				},
  5061  			},
  5062  			Config: map[string]interface{}{
  5063  				"floatMap": map[string]interface{}{
  5064  					"floatField": "notFloat",
  5065  				},
  5066  			},
  5067  			Err: true,
  5068  		},
  5069  
  5070  		"map with positive validate function": {
  5071  			Schema: map[string]*Schema{
  5072  				"floatInt": &Schema{
  5073  					Type:     TypeMap,
  5074  					Elem:     TypeInt,
  5075  					Optional: true,
  5076  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  5077  						return
  5078  					},
  5079  				},
  5080  			},
  5081  			Config: map[string]interface{}{
  5082  				"floatInt": map[string]interface{}{
  5083  					"rightAnswer": "42",
  5084  					"tooMuch":     "43",
  5085  				},
  5086  			},
  5087  			Err: false,
  5088  		},
  5089  		"map with negative validate function": {
  5090  			Schema: map[string]*Schema{
  5091  				"floatInt": &Schema{
  5092  					Type:     TypeMap,
  5093  					Elem:     TypeInt,
  5094  					Optional: true,
  5095  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  5096  						es = append(es, fmt.Errorf("this is not fine"))
  5097  						return
  5098  					},
  5099  				},
  5100  			},
  5101  			Config: map[string]interface{}{
  5102  				"floatInt": map[string]interface{}{
  5103  					"rightAnswer": "42",
  5104  					"tooMuch":     "43",
  5105  				},
  5106  			},
  5107  			Err: true,
  5108  		},
  5109  
  5110  		// The Validation function should not see interpolation strings from
  5111  		// non-computed values.
  5112  		"set with partially computed list and map": {
  5113  			Schema: map[string]*Schema{
  5114  				"outer": &Schema{
  5115  					Type:     TypeSet,
  5116  					Optional: true,
  5117  					Computed: true,
  5118  					Elem: &Resource{
  5119  						Schema: map[string]*Schema{
  5120  							"list": &Schema{
  5121  								Type:     TypeList,
  5122  								Optional: true,
  5123  								Elem: &Schema{
  5124  									Type: TypeString,
  5125  									ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  5126  										if strings.HasPrefix(v.(string), "${") {
  5127  											es = append(es, fmt.Errorf("should not have interpolations"))
  5128  										}
  5129  										return
  5130  									},
  5131  								},
  5132  							},
  5133  						},
  5134  					},
  5135  				},
  5136  			},
  5137  			Config: map[string]interface{}{
  5138  				"outer": []map[string]interface{}{
  5139  					{
  5140  						"list": []interface{}{"${var.a}", "${var.b}", "c"},
  5141  					},
  5142  				},
  5143  			},
  5144  			Vars: map[string]string{
  5145  				"var.a": "A",
  5146  				"var.b": config.UnknownVariableValue,
  5147  			},
  5148  			Err: false,
  5149  		},
  5150  	}
  5151  
  5152  	for tn, tc := range cases {
  5153  		t.Run(tn, func(t *testing.T) {
  5154  			c, err := config.NewRawConfig(tc.Config)
  5155  			if err != nil {
  5156  				t.Fatalf("err: %s", err)
  5157  			}
  5158  			if tc.Vars != nil {
  5159  				vars := make(map[string]ast.Variable)
  5160  				for k, v := range tc.Vars {
  5161  					vars[k] = ast.Variable{Value: v, Type: ast.TypeString}
  5162  				}
  5163  
  5164  				if err := c.Interpolate(vars); err != nil {
  5165  					t.Fatalf("err: %s", err)
  5166  				}
  5167  			}
  5168  
  5169  			ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c))
  5170  			if len(es) > 0 != tc.Err {
  5171  				if len(es) == 0 {
  5172  					t.Errorf("%q: no errors", tn)
  5173  				}
  5174  
  5175  				for _, e := range es {
  5176  					t.Errorf("%q: err: %s", tn, e)
  5177  				}
  5178  
  5179  				t.FailNow()
  5180  			}
  5181  
  5182  			if !reflect.DeepEqual(ws, tc.Warnings) {
  5183  				t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws)
  5184  			}
  5185  
  5186  			if tc.Errors != nil {
  5187  				sort.Sort(errorSort(es))
  5188  				sort.Sort(errorSort(tc.Errors))
  5189  
  5190  				if !reflect.DeepEqual(es, tc.Errors) {
  5191  					t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es)
  5192  				}
  5193  			}
  5194  		})
  5195  
  5196  	}
  5197  }
  5198  
  5199  func TestSchemaSet_ValidateMaxItems(t *testing.T) {
  5200  	cases := map[string]struct {
  5201  		Schema          map[string]*Schema
  5202  		State           *terraform.InstanceState
  5203  		Config          map[string]interface{}
  5204  		ConfigVariables map[string]string
  5205  		Diff            *terraform.InstanceDiff
  5206  		Err             bool
  5207  		Errors          []error
  5208  	}{
  5209  		"#0": {
  5210  			Schema: map[string]*Schema{
  5211  				"aliases": &Schema{
  5212  					Type:     TypeSet,
  5213  					Optional: true,
  5214  					MaxItems: 1,
  5215  					Elem:     &Schema{Type: TypeString},
  5216  				},
  5217  			},
  5218  			State: nil,
  5219  			Config: map[string]interface{}{
  5220  				"aliases": []interface{}{"foo", "bar"},
  5221  			},
  5222  			Diff: nil,
  5223  			Err:  true,
  5224  			Errors: []error{
  5225  				fmt.Errorf("aliases: attribute supports 1 item maximum, config has 2 declared"),
  5226  			},
  5227  		},
  5228  		"#1": {
  5229  			Schema: map[string]*Schema{
  5230  				"aliases": &Schema{
  5231  					Type:     TypeSet,
  5232  					Optional: true,
  5233  					Elem:     &Schema{Type: TypeString},
  5234  				},
  5235  			},
  5236  			State: nil,
  5237  			Config: map[string]interface{}{
  5238  				"aliases": []interface{}{"foo", "bar"},
  5239  			},
  5240  			Diff:   nil,
  5241  			Err:    false,
  5242  			Errors: nil,
  5243  		},
  5244  		"#2": {
  5245  			Schema: map[string]*Schema{
  5246  				"aliases": &Schema{
  5247  					Type:     TypeSet,
  5248  					Optional: true,
  5249  					MaxItems: 1,
  5250  					Elem:     &Schema{Type: TypeString},
  5251  				},
  5252  			},
  5253  			State: nil,
  5254  			Config: map[string]interface{}{
  5255  				"aliases": []interface{}{"foo"},
  5256  			},
  5257  			Diff:   nil,
  5258  			Err:    false,
  5259  			Errors: nil,
  5260  		},
  5261  	}
  5262  
  5263  	for tn, tc := range cases {
  5264  		c, err := config.NewRawConfig(tc.Config)
  5265  		if err != nil {
  5266  			t.Fatalf("%q: err: %s", tn, err)
  5267  		}
  5268  		_, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c))
  5269  
  5270  		if len(es) > 0 != tc.Err {
  5271  			if len(es) == 0 {
  5272  				t.Errorf("%q: no errors", tn)
  5273  			}
  5274  
  5275  			for _, e := range es {
  5276  				t.Errorf("%q: err: %s", tn, e)
  5277  			}
  5278  
  5279  			t.FailNow()
  5280  		}
  5281  
  5282  		if tc.Errors != nil {
  5283  			if !reflect.DeepEqual(es, tc.Errors) {
  5284  				t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es)
  5285  			}
  5286  		}
  5287  	}
  5288  }
  5289  
  5290  func TestSchemaSet_ValidateMinItems(t *testing.T) {
  5291  	cases := map[string]struct {
  5292  		Schema          map[string]*Schema
  5293  		State           *terraform.InstanceState
  5294  		Config          map[string]interface{}
  5295  		ConfigVariables map[string]string
  5296  		Diff            *terraform.InstanceDiff
  5297  		Err             bool
  5298  		Errors          []error
  5299  	}{
  5300  		"#0": {
  5301  			Schema: map[string]*Schema{
  5302  				"aliases": &Schema{
  5303  					Type:     TypeSet,
  5304  					Optional: true,
  5305  					MinItems: 2,
  5306  					Elem:     &Schema{Type: TypeString},
  5307  				},
  5308  			},
  5309  			State: nil,
  5310  			Config: map[string]interface{}{
  5311  				"aliases": []interface{}{"foo", "bar"},
  5312  			},
  5313  			Diff:   nil,
  5314  			Err:    false,
  5315  			Errors: nil,
  5316  		},
  5317  		"#1": {
  5318  			Schema: map[string]*Schema{
  5319  				"aliases": &Schema{
  5320  					Type:     TypeSet,
  5321  					Optional: true,
  5322  					Elem:     &Schema{Type: TypeString},
  5323  				},
  5324  			},
  5325  			State: nil,
  5326  			Config: map[string]interface{}{
  5327  				"aliases": []interface{}{"foo", "bar"},
  5328  			},
  5329  			Diff:   nil,
  5330  			Err:    false,
  5331  			Errors: nil,
  5332  		},
  5333  		"#2": {
  5334  			Schema: map[string]*Schema{
  5335  				"aliases": &Schema{
  5336  					Type:     TypeSet,
  5337  					Optional: true,
  5338  					MinItems: 2,
  5339  					Elem:     &Schema{Type: TypeString},
  5340  				},
  5341  			},
  5342  			State: nil,
  5343  			Config: map[string]interface{}{
  5344  				"aliases": []interface{}{"foo"},
  5345  			},
  5346  			Diff: nil,
  5347  			Err:  true,
  5348  			Errors: []error{
  5349  				fmt.Errorf("aliases: attribute supports 2 item as a minimum, config has 1 declared"),
  5350  			},
  5351  		},
  5352  	}
  5353  
  5354  	for tn, tc := range cases {
  5355  		c, err := config.NewRawConfig(tc.Config)
  5356  		if err != nil {
  5357  			t.Fatalf("%q: err: %s", tn, err)
  5358  		}
  5359  		_, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c))
  5360  
  5361  		if len(es) > 0 != tc.Err {
  5362  			if len(es) == 0 {
  5363  				t.Errorf("%q: no errors", tn)
  5364  			}
  5365  
  5366  			for _, e := range es {
  5367  				t.Errorf("%q: err: %s", tn, e)
  5368  			}
  5369  
  5370  			t.FailNow()
  5371  		}
  5372  
  5373  		if tc.Errors != nil {
  5374  			if !reflect.DeepEqual(es, tc.Errors) {
  5375  				t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es)
  5376  			}
  5377  		}
  5378  	}
  5379  }
  5380  
  5381  // errorSort implements sort.Interface to sort errors by their error message
  5382  type errorSort []error
  5383  
  5384  func (e errorSort) Len() int      { return len(e) }
  5385  func (e errorSort) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
  5386  func (e errorSort) Less(i, j int) bool {
  5387  	return e[i].Error() < e[j].Error()
  5388  }
  5389  
  5390  func TestSchemaMapDeepCopy(t *testing.T) {
  5391  	schema := map[string]*Schema{
  5392  		"foo": &Schema{
  5393  			Type: TypeString,
  5394  		},
  5395  	}
  5396  	source := schemaMap(schema)
  5397  	dest := source.DeepCopy()
  5398  	dest["foo"].ForceNew = true
  5399  	if reflect.DeepEqual(source, dest) {
  5400  		t.Fatalf("source and dest should not match")
  5401  	}
  5402  }