github.com/abhinavdahiya/terraform@v0.11.12-beta1/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  			// NOTE: This case is technically impossible in the current
  2870  			// implementation, because optional+computed values never show up in the
  2871  			// diff. In the event behavior changes this test should ensure that the
  2872  			// intended diff still shows up.
  2873  			Name: "overridden removed attribute diff with a CustomizeDiff function, ForceNew not in schema",
  2874  			Schema: map[string]*Schema{
  2875  				"availability_zone": &Schema{
  2876  					Type:     TypeString,
  2877  					Optional: true,
  2878  					Computed: true,
  2879  				},
  2880  			},
  2881  
  2882  			State: nil,
  2883  
  2884  			Config: map[string]interface{}{},
  2885  
  2886  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  2887  				if err := d.SetNew("availability_zone", "bar"); err != nil {
  2888  					return err
  2889  				}
  2890  				if err := d.ForceNew("availability_zone"); err != nil {
  2891  					return err
  2892  				}
  2893  				return nil
  2894  			},
  2895  
  2896  			Diff: &terraform.InstanceDiff{
  2897  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2898  					"availability_zone": &terraform.ResourceAttrDiff{
  2899  						Old:         "",
  2900  						New:         "bar",
  2901  						RequiresNew: true,
  2902  					},
  2903  				},
  2904  			},
  2905  
  2906  			Err: false,
  2907  		},
  2908  
  2909  		{
  2910  
  2911  			Name: "overridden diff with a CustomizeDiff function, ForceNew in schema",
  2912  			Schema: map[string]*Schema{
  2913  				"availability_zone": &Schema{
  2914  					Type:     TypeString,
  2915  					Optional: true,
  2916  					Computed: true,
  2917  					ForceNew: true,
  2918  				},
  2919  			},
  2920  
  2921  			State: nil,
  2922  
  2923  			Config: map[string]interface{}{
  2924  				"availability_zone": "foo",
  2925  			},
  2926  
  2927  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  2928  				if err := d.SetNew("availability_zone", "bar"); err != nil {
  2929  					return err
  2930  				}
  2931  				return nil
  2932  			},
  2933  
  2934  			Diff: &terraform.InstanceDiff{
  2935  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2936  					"availability_zone": &terraform.ResourceAttrDiff{
  2937  						Old:         "",
  2938  						New:         "bar",
  2939  						RequiresNew: true,
  2940  					},
  2941  				},
  2942  			},
  2943  
  2944  			Err: false,
  2945  		},
  2946  
  2947  		{
  2948  			Name: "required field with computed diff added with CustomizeDiff function",
  2949  			Schema: map[string]*Schema{
  2950  				"ami_id": &Schema{
  2951  					Type:     TypeString,
  2952  					Required: true,
  2953  				},
  2954  				"instance_id": &Schema{
  2955  					Type:     TypeString,
  2956  					Computed: true,
  2957  				},
  2958  			},
  2959  
  2960  			State: nil,
  2961  
  2962  			Config: map[string]interface{}{
  2963  				"ami_id": "foo",
  2964  			},
  2965  
  2966  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  2967  				if err := d.SetNew("instance_id", "bar"); err != nil {
  2968  					return err
  2969  				}
  2970  				return nil
  2971  			},
  2972  
  2973  			Diff: &terraform.InstanceDiff{
  2974  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2975  					"ami_id": &terraform.ResourceAttrDiff{
  2976  						Old: "",
  2977  						New: "foo",
  2978  					},
  2979  					"instance_id": &terraform.ResourceAttrDiff{
  2980  						Old: "",
  2981  						New: "bar",
  2982  					},
  2983  				},
  2984  			},
  2985  
  2986  			Err: false,
  2987  		},
  2988  
  2989  		{
  2990  			Name: "Set ForceNew only marks the changing element as ForceNew - CustomizeDiffFunc edition",
  2991  			Schema: map[string]*Schema{
  2992  				"ports": &Schema{
  2993  					Type:     TypeSet,
  2994  					Optional: true,
  2995  					Computed: true,
  2996  					Elem:     &Schema{Type: TypeInt},
  2997  					Set: func(a interface{}) int {
  2998  						return a.(int)
  2999  					},
  3000  				},
  3001  			},
  3002  
  3003  			State: &terraform.InstanceState{
  3004  				Attributes: map[string]string{
  3005  					"ports.#": "3",
  3006  					"ports.1": "1",
  3007  					"ports.2": "2",
  3008  					"ports.4": "4",
  3009  				},
  3010  			},
  3011  
  3012  			Config: map[string]interface{}{
  3013  				"ports": []interface{}{5, 2, 6},
  3014  			},
  3015  
  3016  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3017  				if err := d.SetNew("ports", []interface{}{5, 2, 1}); err != nil {
  3018  					return err
  3019  				}
  3020  				if err := d.ForceNew("ports"); err != nil {
  3021  					return err
  3022  				}
  3023  				return nil
  3024  			},
  3025  
  3026  			Diff: &terraform.InstanceDiff{
  3027  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3028  					"ports.#": &terraform.ResourceAttrDiff{
  3029  						Old: "3",
  3030  						New: "3",
  3031  					},
  3032  					"ports.1": &terraform.ResourceAttrDiff{
  3033  						Old: "1",
  3034  						New: "1",
  3035  					},
  3036  					"ports.2": &terraform.ResourceAttrDiff{
  3037  						Old: "2",
  3038  						New: "2",
  3039  					},
  3040  					"ports.5": &terraform.ResourceAttrDiff{
  3041  						Old:         "",
  3042  						New:         "5",
  3043  						RequiresNew: true,
  3044  					},
  3045  					"ports.4": &terraform.ResourceAttrDiff{
  3046  						Old:         "4",
  3047  						New:         "0",
  3048  						NewRemoved:  true,
  3049  						RequiresNew: true,
  3050  					},
  3051  				},
  3052  			},
  3053  		},
  3054  
  3055  		{
  3056  			Name:   "tainted resource does not run CustomizeDiffFunc",
  3057  			Schema: map[string]*Schema{},
  3058  
  3059  			State: &terraform.InstanceState{
  3060  				Attributes: map[string]string{
  3061  					"id": "someid",
  3062  				},
  3063  				Tainted: true,
  3064  			},
  3065  
  3066  			Config: map[string]interface{}{},
  3067  
  3068  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3069  				return errors.New("diff customization should not have run")
  3070  			},
  3071  
  3072  			Diff: &terraform.InstanceDiff{
  3073  				Attributes:     map[string]*terraform.ResourceAttrDiff{},
  3074  				DestroyTainted: true,
  3075  			},
  3076  
  3077  			Err: false,
  3078  		},
  3079  
  3080  		{
  3081  			Name: "NewComputed based on a conditional with CustomizeDiffFunc",
  3082  			Schema: map[string]*Schema{
  3083  				"etag": &Schema{
  3084  					Type:     TypeString,
  3085  					Optional: true,
  3086  					Computed: true,
  3087  				},
  3088  				"version_id": &Schema{
  3089  					Type:     TypeString,
  3090  					Computed: true,
  3091  				},
  3092  			},
  3093  
  3094  			State: &terraform.InstanceState{
  3095  				Attributes: map[string]string{
  3096  					"etag":       "foo",
  3097  					"version_id": "1",
  3098  				},
  3099  			},
  3100  
  3101  			Config: map[string]interface{}{
  3102  				"etag": "bar",
  3103  			},
  3104  
  3105  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3106  				if d.HasChange("etag") {
  3107  					d.SetNewComputed("version_id")
  3108  				}
  3109  				return nil
  3110  			},
  3111  
  3112  			Diff: &terraform.InstanceDiff{
  3113  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3114  					"etag": &terraform.ResourceAttrDiff{
  3115  						Old: "foo",
  3116  						New: "bar",
  3117  					},
  3118  					"version_id": &terraform.ResourceAttrDiff{
  3119  						Old:         "1",
  3120  						New:         "",
  3121  						NewComputed: true,
  3122  					},
  3123  				},
  3124  			},
  3125  
  3126  			Err: false,
  3127  		},
  3128  
  3129  		{
  3130  			Name: "vetoing a diff",
  3131  			Schema: map[string]*Schema{
  3132  				"foo": &Schema{
  3133  					Type:     TypeString,
  3134  					Optional: true,
  3135  					Computed: true,
  3136  				},
  3137  			},
  3138  
  3139  			State: &terraform.InstanceState{
  3140  				Attributes: map[string]string{
  3141  					"foo": "bar",
  3142  				},
  3143  			},
  3144  
  3145  			Config: map[string]interface{}{
  3146  				"foo": "baz",
  3147  			},
  3148  
  3149  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3150  				return fmt.Errorf("diff vetoed")
  3151  			},
  3152  
  3153  			Err: true,
  3154  		},
  3155  
  3156  		// A lot of resources currently depended on using the empty string as a
  3157  		// nil/unset value.
  3158  		// FIXME: We want this to eventually produce a diff, since there
  3159  		// technically is a new value in the config.
  3160  		{
  3161  			Name: "optional, computed, empty string",
  3162  			Schema: map[string]*Schema{
  3163  				"attr": &Schema{
  3164  					Type:     TypeString,
  3165  					Optional: true,
  3166  					Computed: true,
  3167  				},
  3168  			},
  3169  
  3170  			State: &terraform.InstanceState{
  3171  				Attributes: map[string]string{
  3172  					"attr": "bar",
  3173  				},
  3174  			},
  3175  
  3176  			// this does necessarily depend on an interpolated value, but this
  3177  			// is often how it comes about in a configuration, otherwise the
  3178  			// value would be unset.
  3179  			Config: map[string]interface{}{
  3180  				"attr": "${var.foo}",
  3181  			},
  3182  
  3183  			ConfigVariables: map[string]ast.Variable{
  3184  				"var.foo": interfaceToVariableSwallowError(""),
  3185  			},
  3186  		},
  3187  
  3188  		{
  3189  			Name: "optional, computed, empty string should not crash in CustomizeDiff",
  3190  			Schema: map[string]*Schema{
  3191  				"unrelated_set": {
  3192  					Type:     TypeSet,
  3193  					Optional: true,
  3194  					Elem:     &Schema{Type: TypeString},
  3195  				},
  3196  				"stream_enabled": {
  3197  					Type:     TypeBool,
  3198  					Optional: true,
  3199  				},
  3200  				"stream_view_type": {
  3201  					Type:     TypeString,
  3202  					Optional: true,
  3203  					Computed: true,
  3204  				},
  3205  			},
  3206  
  3207  			State: &terraform.InstanceState{
  3208  				Attributes: map[string]string{
  3209  					"unrelated_set.#":  "0",
  3210  					"stream_enabled":   "true",
  3211  					"stream_view_type": "KEYS_ONLY",
  3212  				},
  3213  			},
  3214  			Config: map[string]interface{}{
  3215  				"stream_enabled":   false,
  3216  				"stream_view_type": "",
  3217  			},
  3218  			CustomizeDiff: func(diff *ResourceDiff, v interface{}) error {
  3219  				v, ok := diff.GetOk("unrelated_set")
  3220  				if ok {
  3221  					return fmt.Errorf("Didn't expect unrelated_set: %#v", v)
  3222  				}
  3223  				return nil
  3224  			},
  3225  			Diff: &terraform.InstanceDiff{
  3226  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3227  					"stream_enabled": {
  3228  						Old: "true",
  3229  						New: "false",
  3230  					},
  3231  				},
  3232  			},
  3233  		},
  3234  	}
  3235  
  3236  	for i, tc := range cases {
  3237  		t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
  3238  			c, err := config.NewRawConfig(tc.Config)
  3239  			if err != nil {
  3240  				t.Fatalf("err: %s", err)
  3241  			}
  3242  
  3243  			if len(tc.ConfigVariables) > 0 {
  3244  				if err := c.Interpolate(tc.ConfigVariables); err != nil {
  3245  					t.Fatalf("err: %s", err)
  3246  				}
  3247  			}
  3248  
  3249  			d, err := schemaMap(tc.Schema).Diff(tc.State, terraform.NewResourceConfig(c), tc.CustomizeDiff, nil)
  3250  			if err != nil != tc.Err {
  3251  				t.Fatalf("err: %s", err)
  3252  			}
  3253  
  3254  			if !reflect.DeepEqual(tc.Diff, d) {
  3255  				t.Fatalf("expected:\n%#v\n\ngot:\n%#v", tc.Diff, d)
  3256  			}
  3257  		})
  3258  	}
  3259  }
  3260  
  3261  func TestSchemaMap_Input(t *testing.T) {
  3262  	cases := map[string]struct {
  3263  		Schema map[string]*Schema
  3264  		Config map[string]interface{}
  3265  		Input  map[string]string
  3266  		Result map[string]interface{}
  3267  		Err    bool
  3268  	}{
  3269  		/*
  3270  		 * String decode
  3271  		 */
  3272  
  3273  		"no input on optional field with no config": {
  3274  			Schema: map[string]*Schema{
  3275  				"availability_zone": &Schema{
  3276  					Type:     TypeString,
  3277  					Optional: true,
  3278  				},
  3279  			},
  3280  
  3281  			Input:  map[string]string{},
  3282  			Result: map[string]interface{}{},
  3283  			Err:    false,
  3284  		},
  3285  
  3286  		"input ignored when config has a value": {
  3287  			Schema: map[string]*Schema{
  3288  				"availability_zone": &Schema{
  3289  					Type:     TypeString,
  3290  					Optional: true,
  3291  				},
  3292  			},
  3293  
  3294  			Config: map[string]interface{}{
  3295  				"availability_zone": "bar",
  3296  			},
  3297  
  3298  			Input: map[string]string{
  3299  				"availability_zone": "foo",
  3300  			},
  3301  
  3302  			Result: map[string]interface{}{},
  3303  
  3304  			Err: false,
  3305  		},
  3306  
  3307  		"input ignored when schema has a default": {
  3308  			Schema: map[string]*Schema{
  3309  				"availability_zone": &Schema{
  3310  					Type:     TypeString,
  3311  					Default:  "foo",
  3312  					Optional: true,
  3313  				},
  3314  			},
  3315  
  3316  			Input: map[string]string{
  3317  				"availability_zone": "bar",
  3318  			},
  3319  
  3320  			Result: map[string]interface{}{},
  3321  
  3322  			Err: false,
  3323  		},
  3324  
  3325  		"input ignored when default function returns a value": {
  3326  			Schema: map[string]*Schema{
  3327  				"availability_zone": &Schema{
  3328  					Type: TypeString,
  3329  					DefaultFunc: func() (interface{}, error) {
  3330  						return "foo", nil
  3331  					},
  3332  					Optional: true,
  3333  				},
  3334  			},
  3335  
  3336  			Input: map[string]string{
  3337  				"availability_zone": "bar",
  3338  			},
  3339  
  3340  			Result: map[string]interface{}{},
  3341  
  3342  			Err: false,
  3343  		},
  3344  
  3345  		"input ignored when default function returns an empty string": {
  3346  			Schema: map[string]*Schema{
  3347  				"availability_zone": &Schema{
  3348  					Type:     TypeString,
  3349  					Default:  "",
  3350  					Optional: true,
  3351  				},
  3352  			},
  3353  
  3354  			Input: map[string]string{
  3355  				"availability_zone": "bar",
  3356  			},
  3357  
  3358  			Result: map[string]interface{}{},
  3359  
  3360  			Err: false,
  3361  		},
  3362  
  3363  		"input used when default function returns nil": {
  3364  			Schema: map[string]*Schema{
  3365  				"availability_zone": &Schema{
  3366  					Type: TypeString,
  3367  					DefaultFunc: func() (interface{}, error) {
  3368  						return nil, nil
  3369  					},
  3370  					Required: true,
  3371  				},
  3372  			},
  3373  
  3374  			Input: map[string]string{
  3375  				"availability_zone": "bar",
  3376  			},
  3377  
  3378  			Result: map[string]interface{}{
  3379  				"availability_zone": "bar",
  3380  			},
  3381  
  3382  			Err: false,
  3383  		},
  3384  
  3385  		"input not used when optional default function returns nil": {
  3386  			Schema: map[string]*Schema{
  3387  				"availability_zone": &Schema{
  3388  					Type: TypeString,
  3389  					DefaultFunc: func() (interface{}, error) {
  3390  						return nil, nil
  3391  					},
  3392  					Optional: true,
  3393  				},
  3394  			},
  3395  
  3396  			Input:  map[string]string{},
  3397  			Result: map[string]interface{}{},
  3398  			Err:    false,
  3399  		},
  3400  	}
  3401  
  3402  	for i, tc := range cases {
  3403  		if tc.Config == nil {
  3404  			tc.Config = make(map[string]interface{})
  3405  		}
  3406  
  3407  		c, err := config.NewRawConfig(tc.Config)
  3408  		if err != nil {
  3409  			t.Fatalf("err: %s", err)
  3410  		}
  3411  
  3412  		input := new(terraform.MockUIInput)
  3413  		input.InputReturnMap = tc.Input
  3414  
  3415  		rc := terraform.NewResourceConfig(c)
  3416  		rc.Config = make(map[string]interface{})
  3417  
  3418  		actual, err := schemaMap(tc.Schema).Input(input, rc)
  3419  		if err != nil != tc.Err {
  3420  			t.Fatalf("#%v err: %s", i, err)
  3421  		}
  3422  
  3423  		if !reflect.DeepEqual(tc.Result, actual.Config) {
  3424  			t.Fatalf("#%v: bad:\n\ngot: %#v\nexpected: %#v", i, actual.Config, tc.Result)
  3425  		}
  3426  	}
  3427  }
  3428  
  3429  func TestSchemaMap_InputDefault(t *testing.T) {
  3430  	emptyConfig := make(map[string]interface{})
  3431  	c, err := config.NewRawConfig(emptyConfig)
  3432  	if err != nil {
  3433  		t.Fatalf("err: %s", err)
  3434  	}
  3435  	rc := terraform.NewResourceConfig(c)
  3436  	rc.Config = make(map[string]interface{})
  3437  
  3438  	input := new(terraform.MockUIInput)
  3439  	input.InputFn = func(opts *terraform.InputOpts) (string, error) {
  3440  		t.Fatalf("InputFn should not be called on: %#v", opts)
  3441  		return "", nil
  3442  	}
  3443  
  3444  	schema := map[string]*Schema{
  3445  		"availability_zone": &Schema{
  3446  			Type:     TypeString,
  3447  			Default:  "foo",
  3448  			Optional: true,
  3449  		},
  3450  	}
  3451  	actual, err := schemaMap(schema).Input(input, rc)
  3452  	if err != nil {
  3453  		t.Fatalf("err: %s", err)
  3454  	}
  3455  
  3456  	expected := map[string]interface{}{}
  3457  
  3458  	if !reflect.DeepEqual(expected, actual.Config) {
  3459  		t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected)
  3460  	}
  3461  }
  3462  
  3463  func TestSchemaMap_InputDeprecated(t *testing.T) {
  3464  	emptyConfig := make(map[string]interface{})
  3465  	c, err := config.NewRawConfig(emptyConfig)
  3466  	if err != nil {
  3467  		t.Fatalf("err: %s", err)
  3468  	}
  3469  	rc := terraform.NewResourceConfig(c)
  3470  	rc.Config = make(map[string]interface{})
  3471  
  3472  	input := new(terraform.MockUIInput)
  3473  	input.InputFn = func(opts *terraform.InputOpts) (string, error) {
  3474  		t.Fatalf("InputFn should not be called on: %#v", opts)
  3475  		return "", nil
  3476  	}
  3477  
  3478  	schema := map[string]*Schema{
  3479  		"availability_zone": &Schema{
  3480  			Type:       TypeString,
  3481  			Deprecated: "long gone",
  3482  			Optional:   true,
  3483  		},
  3484  	}
  3485  	actual, err := schemaMap(schema).Input(input, rc)
  3486  	if err != nil {
  3487  		t.Fatalf("err: %s", err)
  3488  	}
  3489  
  3490  	expected := map[string]interface{}{}
  3491  
  3492  	if !reflect.DeepEqual(expected, actual.Config) {
  3493  		t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected)
  3494  	}
  3495  }
  3496  
  3497  func TestSchemaMap_InternalValidate(t *testing.T) {
  3498  	cases := map[string]struct {
  3499  		In  map[string]*Schema
  3500  		Err bool
  3501  	}{
  3502  		"nothing": {
  3503  			nil,
  3504  			false,
  3505  		},
  3506  
  3507  		"Both optional and required": {
  3508  			map[string]*Schema{
  3509  				"foo": &Schema{
  3510  					Type:     TypeInt,
  3511  					Optional: true,
  3512  					Required: true,
  3513  				},
  3514  			},
  3515  			true,
  3516  		},
  3517  
  3518  		"No optional and no required": {
  3519  			map[string]*Schema{
  3520  				"foo": &Schema{
  3521  					Type: TypeInt,
  3522  				},
  3523  			},
  3524  			true,
  3525  		},
  3526  
  3527  		"Missing Type": {
  3528  			map[string]*Schema{
  3529  				"foo": &Schema{
  3530  					Required: true,
  3531  				},
  3532  			},
  3533  			true,
  3534  		},
  3535  
  3536  		"Required but computed": {
  3537  			map[string]*Schema{
  3538  				"foo": &Schema{
  3539  					Type:     TypeInt,
  3540  					Required: true,
  3541  					Computed: true,
  3542  				},
  3543  			},
  3544  			true,
  3545  		},
  3546  
  3547  		"Looks good": {
  3548  			map[string]*Schema{
  3549  				"foo": &Schema{
  3550  					Type:     TypeString,
  3551  					Required: true,
  3552  				},
  3553  			},
  3554  			false,
  3555  		},
  3556  
  3557  		"Computed but has default": {
  3558  			map[string]*Schema{
  3559  				"foo": &Schema{
  3560  					Type:     TypeInt,
  3561  					Optional: true,
  3562  					Computed: true,
  3563  					Default:  "foo",
  3564  				},
  3565  			},
  3566  			true,
  3567  		},
  3568  
  3569  		"Required but has default": {
  3570  			map[string]*Schema{
  3571  				"foo": &Schema{
  3572  					Type:     TypeInt,
  3573  					Optional: true,
  3574  					Required: true,
  3575  					Default:  "foo",
  3576  				},
  3577  			},
  3578  			true,
  3579  		},
  3580  
  3581  		"List element not set": {
  3582  			map[string]*Schema{
  3583  				"foo": &Schema{
  3584  					Type: TypeList,
  3585  				},
  3586  			},
  3587  			true,
  3588  		},
  3589  
  3590  		"List default": {
  3591  			map[string]*Schema{
  3592  				"foo": &Schema{
  3593  					Type:    TypeList,
  3594  					Elem:    &Schema{Type: TypeInt},
  3595  					Default: "foo",
  3596  				},
  3597  			},
  3598  			true,
  3599  		},
  3600  
  3601  		"List element computed": {
  3602  			map[string]*Schema{
  3603  				"foo": &Schema{
  3604  					Type:     TypeList,
  3605  					Optional: true,
  3606  					Elem: &Schema{
  3607  						Type:     TypeInt,
  3608  						Computed: true,
  3609  					},
  3610  				},
  3611  			},
  3612  			true,
  3613  		},
  3614  
  3615  		"List element with Set set": {
  3616  			map[string]*Schema{
  3617  				"foo": &Schema{
  3618  					Type:     TypeList,
  3619  					Elem:     &Schema{Type: TypeInt},
  3620  					Set:      func(interface{}) int { return 0 },
  3621  					Optional: true,
  3622  				},
  3623  			},
  3624  			true,
  3625  		},
  3626  
  3627  		"Set element with no Set set": {
  3628  			map[string]*Schema{
  3629  				"foo": &Schema{
  3630  					Type:     TypeSet,
  3631  					Elem:     &Schema{Type: TypeInt},
  3632  					Optional: true,
  3633  				},
  3634  			},
  3635  			false,
  3636  		},
  3637  
  3638  		"Required but computedWhen": {
  3639  			map[string]*Schema{
  3640  				"foo": &Schema{
  3641  					Type:         TypeInt,
  3642  					Required:     true,
  3643  					ComputedWhen: []string{"foo"},
  3644  				},
  3645  			},
  3646  			true,
  3647  		},
  3648  
  3649  		"Conflicting attributes cannot be required": {
  3650  			map[string]*Schema{
  3651  				"blacklist": &Schema{
  3652  					Type:     TypeBool,
  3653  					Required: true,
  3654  				},
  3655  				"whitelist": &Schema{
  3656  					Type:          TypeBool,
  3657  					Optional:      true,
  3658  					ConflictsWith: []string{"blacklist"},
  3659  				},
  3660  			},
  3661  			true,
  3662  		},
  3663  
  3664  		"Attribute with conflicts cannot be required": {
  3665  			map[string]*Schema{
  3666  				"whitelist": &Schema{
  3667  					Type:          TypeBool,
  3668  					Required:      true,
  3669  					ConflictsWith: []string{"blacklist"},
  3670  				},
  3671  			},
  3672  			true,
  3673  		},
  3674  
  3675  		"ConflictsWith cannot be used w/ ComputedWhen": {
  3676  			map[string]*Schema{
  3677  				"blacklist": &Schema{
  3678  					Type:         TypeBool,
  3679  					ComputedWhen: []string{"foor"},
  3680  				},
  3681  				"whitelist": &Schema{
  3682  					Type:          TypeBool,
  3683  					Required:      true,
  3684  					ConflictsWith: []string{"blacklist"},
  3685  				},
  3686  			},
  3687  			true,
  3688  		},
  3689  
  3690  		"Sub-resource invalid": {
  3691  			map[string]*Schema{
  3692  				"foo": &Schema{
  3693  					Type:     TypeList,
  3694  					Optional: true,
  3695  					Elem: &Resource{
  3696  						Schema: map[string]*Schema{
  3697  							"foo": new(Schema),
  3698  						},
  3699  					},
  3700  				},
  3701  			},
  3702  			true,
  3703  		},
  3704  
  3705  		"Sub-resource valid": {
  3706  			map[string]*Schema{
  3707  				"foo": &Schema{
  3708  					Type:     TypeList,
  3709  					Optional: true,
  3710  					Elem: &Resource{
  3711  						Schema: map[string]*Schema{
  3712  							"foo": &Schema{
  3713  								Type:     TypeInt,
  3714  								Optional: true,
  3715  							},
  3716  						},
  3717  					},
  3718  				},
  3719  			},
  3720  			false,
  3721  		},
  3722  
  3723  		"ValidateFunc on non-primitive": {
  3724  			map[string]*Schema{
  3725  				"foo": &Schema{
  3726  					Type:     TypeSet,
  3727  					Required: true,
  3728  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  3729  						return
  3730  					},
  3731  				},
  3732  			},
  3733  			true,
  3734  		},
  3735  
  3736  		"computed-only field with validateFunc": {
  3737  			map[string]*Schema{
  3738  				"string": &Schema{
  3739  					Type:     TypeString,
  3740  					Computed: true,
  3741  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  3742  						es = append(es, fmt.Errorf("this is not fine"))
  3743  						return
  3744  					},
  3745  				},
  3746  			},
  3747  			true,
  3748  		},
  3749  
  3750  		"computed-only field with diffSuppressFunc": {
  3751  			map[string]*Schema{
  3752  				"string": &Schema{
  3753  					Type:     TypeString,
  3754  					Computed: true,
  3755  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  3756  						// Always suppress any diff
  3757  						return false
  3758  					},
  3759  				},
  3760  			},
  3761  			true,
  3762  		},
  3763  
  3764  		"invalid field name format #1": {
  3765  			map[string]*Schema{
  3766  				"with space": &Schema{
  3767  					Type:     TypeString,
  3768  					Optional: true,
  3769  				},
  3770  			},
  3771  			true,
  3772  		},
  3773  
  3774  		"invalid field name format #2": {
  3775  			map[string]*Schema{
  3776  				"WithCapitals": &Schema{
  3777  					Type:     TypeString,
  3778  					Optional: true,
  3779  				},
  3780  			},
  3781  			true,
  3782  		},
  3783  
  3784  		"invalid field name format of a Deprecated field": {
  3785  			map[string]*Schema{
  3786  				"WithCapitals": &Schema{
  3787  					Type:       TypeString,
  3788  					Optional:   true,
  3789  					Deprecated: "Use with_underscores instead",
  3790  				},
  3791  			},
  3792  			false,
  3793  		},
  3794  
  3795  		"invalid field name format of a Removed field": {
  3796  			map[string]*Schema{
  3797  				"WithCapitals": &Schema{
  3798  					Type:     TypeString,
  3799  					Optional: true,
  3800  					Removed:  "Use with_underscores instead",
  3801  				},
  3802  			},
  3803  			false,
  3804  		},
  3805  	}
  3806  
  3807  	for tn, tc := range cases {
  3808  		t.Run(tn, func(t *testing.T) {
  3809  			err := schemaMap(tc.In).InternalValidate(nil)
  3810  			if err != nil != tc.Err {
  3811  				if tc.Err {
  3812  					t.Fatalf("%q: Expected error did not occur:\n\n%#v", tn, tc.In)
  3813  				}
  3814  				t.Fatalf("%q: Unexpected error occurred: %s\n\n%#v", tn, err, tc.In)
  3815  			}
  3816  		})
  3817  	}
  3818  
  3819  }
  3820  
  3821  func TestSchemaMap_DiffSuppress(t *testing.T) {
  3822  	cases := map[string]struct {
  3823  		Schema          map[string]*Schema
  3824  		State           *terraform.InstanceState
  3825  		Config          map[string]interface{}
  3826  		ConfigVariables map[string]ast.Variable
  3827  		ExpectedDiff    *terraform.InstanceDiff
  3828  		Err             bool
  3829  	}{
  3830  		"#0 - Suppress otherwise valid diff by returning true": {
  3831  			Schema: map[string]*Schema{
  3832  				"availability_zone": {
  3833  					Type:     TypeString,
  3834  					Optional: true,
  3835  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  3836  						// Always suppress any diff
  3837  						return true
  3838  					},
  3839  				},
  3840  			},
  3841  
  3842  			State: nil,
  3843  
  3844  			Config: map[string]interface{}{
  3845  				"availability_zone": "foo",
  3846  			},
  3847  
  3848  			ExpectedDiff: nil,
  3849  
  3850  			Err: false,
  3851  		},
  3852  
  3853  		"#1 - Don't suppress diff by returning false": {
  3854  			Schema: map[string]*Schema{
  3855  				"availability_zone": {
  3856  					Type:     TypeString,
  3857  					Optional: true,
  3858  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  3859  						// Always suppress any diff
  3860  						return false
  3861  					},
  3862  				},
  3863  			},
  3864  
  3865  			State: nil,
  3866  
  3867  			Config: map[string]interface{}{
  3868  				"availability_zone": "foo",
  3869  			},
  3870  
  3871  			ExpectedDiff: &terraform.InstanceDiff{
  3872  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3873  					"availability_zone": {
  3874  						Old: "",
  3875  						New: "foo",
  3876  					},
  3877  				},
  3878  			},
  3879  
  3880  			Err: false,
  3881  		},
  3882  
  3883  		"Default with suppress makes no diff": {
  3884  			Schema: map[string]*Schema{
  3885  				"availability_zone": {
  3886  					Type:     TypeString,
  3887  					Optional: true,
  3888  					Default:  "foo",
  3889  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  3890  						return true
  3891  					},
  3892  				},
  3893  			},
  3894  
  3895  			State: nil,
  3896  
  3897  			Config: map[string]interface{}{},
  3898  
  3899  			ExpectedDiff: nil,
  3900  
  3901  			Err: false,
  3902  		},
  3903  
  3904  		"Default with false suppress makes diff": {
  3905  			Schema: map[string]*Schema{
  3906  				"availability_zone": {
  3907  					Type:     TypeString,
  3908  					Optional: true,
  3909  					Default:  "foo",
  3910  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  3911  						return false
  3912  					},
  3913  				},
  3914  			},
  3915  
  3916  			State: nil,
  3917  
  3918  			Config: map[string]interface{}{},
  3919  
  3920  			ExpectedDiff: &terraform.InstanceDiff{
  3921  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3922  					"availability_zone": {
  3923  						Old: "",
  3924  						New: "foo",
  3925  					},
  3926  				},
  3927  			},
  3928  
  3929  			Err: false,
  3930  		},
  3931  
  3932  		"Complex structure with set of computed string should mark root set as computed": {
  3933  			Schema: map[string]*Schema{
  3934  				"outer": &Schema{
  3935  					Type:     TypeSet,
  3936  					Optional: true,
  3937  					Elem: &Resource{
  3938  						Schema: map[string]*Schema{
  3939  							"outer_str": &Schema{
  3940  								Type:     TypeString,
  3941  								Optional: true,
  3942  							},
  3943  							"inner": &Schema{
  3944  								Type:     TypeSet,
  3945  								Optional: true,
  3946  								Elem: &Resource{
  3947  									Schema: map[string]*Schema{
  3948  										"inner_str": &Schema{
  3949  											Type:     TypeString,
  3950  											Optional: true,
  3951  										},
  3952  									},
  3953  								},
  3954  								Set: func(v interface{}) int {
  3955  									return 2
  3956  								},
  3957  							},
  3958  						},
  3959  					},
  3960  					Set: func(v interface{}) int {
  3961  						return 1
  3962  					},
  3963  				},
  3964  			},
  3965  
  3966  			State: nil,
  3967  
  3968  			Config: map[string]interface{}{
  3969  				"outer": []map[string]interface{}{
  3970  					map[string]interface{}{
  3971  						"outer_str": "foo",
  3972  						"inner": []map[string]interface{}{
  3973  							map[string]interface{}{
  3974  								"inner_str": "${var.bar}",
  3975  							},
  3976  						},
  3977  					},
  3978  				},
  3979  			},
  3980  
  3981  			ConfigVariables: map[string]ast.Variable{
  3982  				"var.bar": interfaceToVariableSwallowError(config.UnknownVariableValue),
  3983  			},
  3984  
  3985  			ExpectedDiff: &terraform.InstanceDiff{
  3986  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3987  					"outer.#": &terraform.ResourceAttrDiff{
  3988  						Old: "0",
  3989  						New: "1",
  3990  					},
  3991  					"outer.~1.outer_str": &terraform.ResourceAttrDiff{
  3992  						Old: "",
  3993  						New: "foo",
  3994  					},
  3995  					"outer.~1.inner.#": &terraform.ResourceAttrDiff{
  3996  						Old: "0",
  3997  						New: "1",
  3998  					},
  3999  					"outer.~1.inner.~2.inner_str": &terraform.ResourceAttrDiff{
  4000  						Old:         "",
  4001  						New:         "${var.bar}",
  4002  						NewComputed: true,
  4003  					},
  4004  				},
  4005  			},
  4006  
  4007  			Err: false,
  4008  		},
  4009  
  4010  		"Complex structure with complex list of computed string should mark root set as computed": {
  4011  			Schema: map[string]*Schema{
  4012  				"outer": &Schema{
  4013  					Type:     TypeSet,
  4014  					Optional: true,
  4015  					Elem: &Resource{
  4016  						Schema: map[string]*Schema{
  4017  							"outer_str": &Schema{
  4018  								Type:     TypeString,
  4019  								Optional: true,
  4020  							},
  4021  							"inner": &Schema{
  4022  								Type:     TypeList,
  4023  								Optional: true,
  4024  								Elem: &Resource{
  4025  									Schema: map[string]*Schema{
  4026  										"inner_str": &Schema{
  4027  											Type:     TypeString,
  4028  											Optional: true,
  4029  										},
  4030  									},
  4031  								},
  4032  							},
  4033  						},
  4034  					},
  4035  					Set: func(v interface{}) int {
  4036  						return 1
  4037  					},
  4038  				},
  4039  			},
  4040  
  4041  			State: nil,
  4042  
  4043  			Config: map[string]interface{}{
  4044  				"outer": []map[string]interface{}{
  4045  					map[string]interface{}{
  4046  						"outer_str": "foo",
  4047  						"inner": []map[string]interface{}{
  4048  							map[string]interface{}{
  4049  								"inner_str": "${var.bar}",
  4050  							},
  4051  						},
  4052  					},
  4053  				},
  4054  			},
  4055  
  4056  			ConfigVariables: map[string]ast.Variable{
  4057  				"var.bar": interfaceToVariableSwallowError(config.UnknownVariableValue),
  4058  			},
  4059  
  4060  			ExpectedDiff: &terraform.InstanceDiff{
  4061  				Attributes: map[string]*terraform.ResourceAttrDiff{
  4062  					"outer.#": &terraform.ResourceAttrDiff{
  4063  						Old: "0",
  4064  						New: "1",
  4065  					},
  4066  					"outer.~1.outer_str": &terraform.ResourceAttrDiff{
  4067  						Old: "",
  4068  						New: "foo",
  4069  					},
  4070  					"outer.~1.inner.#": &terraform.ResourceAttrDiff{
  4071  						Old: "0",
  4072  						New: "1",
  4073  					},
  4074  					"outer.~1.inner.0.inner_str": &terraform.ResourceAttrDiff{
  4075  						Old:         "",
  4076  						New:         "${var.bar}",
  4077  						NewComputed: true,
  4078  					},
  4079  				},
  4080  			},
  4081  
  4082  			Err: false,
  4083  		},
  4084  	}
  4085  
  4086  	for tn, tc := range cases {
  4087  		t.Run(tn, func(t *testing.T) {
  4088  			c, err := config.NewRawConfig(tc.Config)
  4089  			if err != nil {
  4090  				t.Fatalf("#%q err: %s", tn, err)
  4091  			}
  4092  
  4093  			if len(tc.ConfigVariables) > 0 {
  4094  				if err := c.Interpolate(tc.ConfigVariables); err != nil {
  4095  					t.Fatalf("#%q err: %s", tn, err)
  4096  				}
  4097  			}
  4098  
  4099  			d, err := schemaMap(tc.Schema).Diff(tc.State, terraform.NewResourceConfig(c), nil, nil)
  4100  			if err != nil != tc.Err {
  4101  				t.Fatalf("#%q err: %s", tn, err)
  4102  			}
  4103  
  4104  			if !reflect.DeepEqual(tc.ExpectedDiff, d) {
  4105  				t.Fatalf("#%q:\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.ExpectedDiff, d)
  4106  			}
  4107  		})
  4108  	}
  4109  }
  4110  
  4111  func TestSchemaMap_Validate(t *testing.T) {
  4112  	cases := map[string]struct {
  4113  		Schema   map[string]*Schema
  4114  		Config   map[string]interface{}
  4115  		Vars     map[string]string
  4116  		Err      bool
  4117  		Errors   []error
  4118  		Warnings []string
  4119  	}{
  4120  		"Good": {
  4121  			Schema: map[string]*Schema{
  4122  				"availability_zone": &Schema{
  4123  					Type:     TypeString,
  4124  					Optional: true,
  4125  					Computed: true,
  4126  					ForceNew: true,
  4127  				},
  4128  			},
  4129  
  4130  			Config: map[string]interface{}{
  4131  				"availability_zone": "foo",
  4132  			},
  4133  		},
  4134  
  4135  		"Good, because the var is not set and that error will come elsewhere": {
  4136  			Schema: map[string]*Schema{
  4137  				"size": &Schema{
  4138  					Type:     TypeInt,
  4139  					Required: true,
  4140  				},
  4141  			},
  4142  
  4143  			Config: map[string]interface{}{
  4144  				"size": "${var.foo}",
  4145  			},
  4146  
  4147  			Vars: map[string]string{
  4148  				"var.foo": config.UnknownVariableValue,
  4149  			},
  4150  		},
  4151  
  4152  		"Required field not set": {
  4153  			Schema: map[string]*Schema{
  4154  				"availability_zone": &Schema{
  4155  					Type:     TypeString,
  4156  					Required: true,
  4157  				},
  4158  			},
  4159  
  4160  			Config: map[string]interface{}{},
  4161  
  4162  			Err: true,
  4163  		},
  4164  
  4165  		"Invalid basic type": {
  4166  			Schema: map[string]*Schema{
  4167  				"port": &Schema{
  4168  					Type:     TypeInt,
  4169  					Required: true,
  4170  				},
  4171  			},
  4172  
  4173  			Config: map[string]interface{}{
  4174  				"port": "I am invalid",
  4175  			},
  4176  
  4177  			Err: true,
  4178  		},
  4179  
  4180  		"Invalid complex type": {
  4181  			Schema: map[string]*Schema{
  4182  				"user_data": &Schema{
  4183  					Type:     TypeString,
  4184  					Optional: true,
  4185  				},
  4186  			},
  4187  
  4188  			Config: map[string]interface{}{
  4189  				"user_data": []interface{}{
  4190  					map[string]interface{}{
  4191  						"foo": "bar",
  4192  					},
  4193  				},
  4194  			},
  4195  
  4196  			Err: true,
  4197  		},
  4198  
  4199  		"Bad type, interpolated": {
  4200  			Schema: map[string]*Schema{
  4201  				"size": &Schema{
  4202  					Type:     TypeInt,
  4203  					Required: true,
  4204  				},
  4205  			},
  4206  
  4207  			Config: map[string]interface{}{
  4208  				"size": "${var.foo}",
  4209  			},
  4210  
  4211  			Vars: map[string]string{
  4212  				"var.foo": "nope",
  4213  			},
  4214  
  4215  			Err: true,
  4216  		},
  4217  
  4218  		"Required but has DefaultFunc": {
  4219  			Schema: map[string]*Schema{
  4220  				"availability_zone": &Schema{
  4221  					Type:     TypeString,
  4222  					Required: true,
  4223  					DefaultFunc: func() (interface{}, error) {
  4224  						return "foo", nil
  4225  					},
  4226  				},
  4227  			},
  4228  
  4229  			Config: nil,
  4230  		},
  4231  
  4232  		"Required but has DefaultFunc return nil": {
  4233  			Schema: map[string]*Schema{
  4234  				"availability_zone": &Schema{
  4235  					Type:     TypeString,
  4236  					Required: true,
  4237  					DefaultFunc: func() (interface{}, error) {
  4238  						return nil, nil
  4239  					},
  4240  				},
  4241  			},
  4242  
  4243  			Config: nil,
  4244  
  4245  			Err: true,
  4246  		},
  4247  
  4248  		"List with promotion": {
  4249  			Schema: map[string]*Schema{
  4250  				"ingress": &Schema{
  4251  					Type:          TypeList,
  4252  					Elem:          &Schema{Type: TypeInt},
  4253  					PromoteSingle: true,
  4254  					Optional:      true,
  4255  				},
  4256  			},
  4257  
  4258  			Config: map[string]interface{}{
  4259  				"ingress": "5",
  4260  			},
  4261  
  4262  			Err: false,
  4263  		},
  4264  
  4265  		"List with promotion set as list": {
  4266  			Schema: map[string]*Schema{
  4267  				"ingress": &Schema{
  4268  					Type:          TypeList,
  4269  					Elem:          &Schema{Type: TypeInt},
  4270  					PromoteSingle: true,
  4271  					Optional:      true,
  4272  				},
  4273  			},
  4274  
  4275  			Config: map[string]interface{}{
  4276  				"ingress": []interface{}{"5"},
  4277  			},
  4278  
  4279  			Err: false,
  4280  		},
  4281  
  4282  		"Optional sub-resource": {
  4283  			Schema: map[string]*Schema{
  4284  				"ingress": &Schema{
  4285  					Type: TypeList,
  4286  					Elem: &Resource{
  4287  						Schema: map[string]*Schema{
  4288  							"from": &Schema{
  4289  								Type:     TypeInt,
  4290  								Required: true,
  4291  							},
  4292  						},
  4293  					},
  4294  				},
  4295  			},
  4296  
  4297  			Config: map[string]interface{}{},
  4298  
  4299  			Err: false,
  4300  		},
  4301  
  4302  		"Sub-resource is the wrong type": {
  4303  			Schema: map[string]*Schema{
  4304  				"ingress": &Schema{
  4305  					Type:     TypeList,
  4306  					Required: true,
  4307  					Elem: &Resource{
  4308  						Schema: map[string]*Schema{
  4309  							"from": &Schema{
  4310  								Type:     TypeInt,
  4311  								Required: true,
  4312  							},
  4313  						},
  4314  					},
  4315  				},
  4316  			},
  4317  
  4318  			Config: map[string]interface{}{
  4319  				"ingress": []interface{}{"foo"},
  4320  			},
  4321  
  4322  			Err: true,
  4323  		},
  4324  
  4325  		"Not a list": {
  4326  			Schema: map[string]*Schema{
  4327  				"ingress": &Schema{
  4328  					Type: TypeList,
  4329  					Elem: &Resource{
  4330  						Schema: map[string]*Schema{
  4331  							"from": &Schema{
  4332  								Type:     TypeInt,
  4333  								Required: true,
  4334  							},
  4335  						},
  4336  					},
  4337  				},
  4338  			},
  4339  
  4340  			Config: map[string]interface{}{
  4341  				"ingress": "foo",
  4342  			},
  4343  
  4344  			Err: true,
  4345  		},
  4346  
  4347  		"Required sub-resource field": {
  4348  			Schema: map[string]*Schema{
  4349  				"ingress": &Schema{
  4350  					Type: TypeList,
  4351  					Elem: &Resource{
  4352  						Schema: map[string]*Schema{
  4353  							"from": &Schema{
  4354  								Type:     TypeInt,
  4355  								Required: true,
  4356  							},
  4357  						},
  4358  					},
  4359  				},
  4360  			},
  4361  
  4362  			Config: map[string]interface{}{
  4363  				"ingress": []interface{}{
  4364  					map[string]interface{}{},
  4365  				},
  4366  			},
  4367  
  4368  			Err: true,
  4369  		},
  4370  
  4371  		"Good sub-resource": {
  4372  			Schema: map[string]*Schema{
  4373  				"ingress": &Schema{
  4374  					Type:     TypeList,
  4375  					Optional: true,
  4376  					Elem: &Resource{
  4377  						Schema: map[string]*Schema{
  4378  							"from": &Schema{
  4379  								Type:     TypeInt,
  4380  								Required: true,
  4381  							},
  4382  						},
  4383  					},
  4384  				},
  4385  			},
  4386  
  4387  			Config: map[string]interface{}{
  4388  				"ingress": []interface{}{
  4389  					map[string]interface{}{
  4390  						"from": 80,
  4391  					},
  4392  				},
  4393  			},
  4394  
  4395  			Err: false,
  4396  		},
  4397  
  4398  		"Good sub-resource, interpolated value": {
  4399  			Schema: map[string]*Schema{
  4400  				"ingress": &Schema{
  4401  					Type:     TypeList,
  4402  					Optional: true,
  4403  					Elem: &Resource{
  4404  						Schema: map[string]*Schema{
  4405  							"from": &Schema{
  4406  								Type:     TypeInt,
  4407  								Required: true,
  4408  							},
  4409  						},
  4410  					},
  4411  				},
  4412  			},
  4413  
  4414  			Config: map[string]interface{}{
  4415  				"ingress": []interface{}{
  4416  					`${map("from", "80")}`,
  4417  				},
  4418  			},
  4419  
  4420  			Vars: map[string]string{},
  4421  
  4422  			Err: false,
  4423  		},
  4424  
  4425  		"Good sub-resource, computed value": {
  4426  			Schema: map[string]*Schema{
  4427  				"ingress": &Schema{
  4428  					Type:     TypeList,
  4429  					Optional: true,
  4430  					Elem: &Resource{
  4431  						Schema: map[string]*Schema{
  4432  							"from": &Schema{
  4433  								Type:     TypeInt,
  4434  								Optional: true,
  4435  							},
  4436  						},
  4437  					},
  4438  				},
  4439  			},
  4440  
  4441  			Config: map[string]interface{}{
  4442  				"ingress": []interface{}{
  4443  					`${map("from", var.port)}`,
  4444  				},
  4445  			},
  4446  
  4447  			Vars: map[string]string{
  4448  				"var.port": config.UnknownVariableValue,
  4449  			},
  4450  
  4451  			Err: false,
  4452  		},
  4453  
  4454  		"Invalid/unknown field": {
  4455  			Schema: map[string]*Schema{
  4456  				"availability_zone": &Schema{
  4457  					Type:     TypeString,
  4458  					Optional: true,
  4459  					Computed: true,
  4460  					ForceNew: true,
  4461  				},
  4462  			},
  4463  
  4464  			Config: map[string]interface{}{
  4465  				"foo": "bar",
  4466  			},
  4467  
  4468  			Err: true,
  4469  		},
  4470  
  4471  		"Invalid/unknown field with computed value": {
  4472  			Schema: map[string]*Schema{
  4473  				"availability_zone": &Schema{
  4474  					Type:     TypeString,
  4475  					Optional: true,
  4476  					Computed: true,
  4477  					ForceNew: true,
  4478  				},
  4479  			},
  4480  
  4481  			Config: map[string]interface{}{
  4482  				"foo": "${var.foo}",
  4483  			},
  4484  
  4485  			Vars: map[string]string{
  4486  				"var.foo": config.UnknownVariableValue,
  4487  			},
  4488  
  4489  			Err: true,
  4490  		},
  4491  
  4492  		"Computed field set": {
  4493  			Schema: map[string]*Schema{
  4494  				"availability_zone": &Schema{
  4495  					Type:     TypeString,
  4496  					Computed: true,
  4497  				},
  4498  			},
  4499  
  4500  			Config: map[string]interface{}{
  4501  				"availability_zone": "bar",
  4502  			},
  4503  
  4504  			Err: true,
  4505  		},
  4506  
  4507  		"Not a set": {
  4508  			Schema: map[string]*Schema{
  4509  				"ports": &Schema{
  4510  					Type:     TypeSet,
  4511  					Required: true,
  4512  					Elem:     &Schema{Type: TypeInt},
  4513  					Set: func(a interface{}) int {
  4514  						return a.(int)
  4515  					},
  4516  				},
  4517  			},
  4518  
  4519  			Config: map[string]interface{}{
  4520  				"ports": "foo",
  4521  			},
  4522  
  4523  			Err: true,
  4524  		},
  4525  
  4526  		"Maps": {
  4527  			Schema: map[string]*Schema{
  4528  				"user_data": &Schema{
  4529  					Type:     TypeMap,
  4530  					Optional: true,
  4531  				},
  4532  			},
  4533  
  4534  			Config: map[string]interface{}{
  4535  				"user_data": "foo",
  4536  			},
  4537  
  4538  			Err: true,
  4539  		},
  4540  
  4541  		"Good map: data surrounded by extra slice": {
  4542  			Schema: map[string]*Schema{
  4543  				"user_data": &Schema{
  4544  					Type:     TypeMap,
  4545  					Optional: true,
  4546  				},
  4547  			},
  4548  
  4549  			Config: map[string]interface{}{
  4550  				"user_data": []interface{}{
  4551  					map[string]interface{}{
  4552  						"foo": "bar",
  4553  					},
  4554  				},
  4555  			},
  4556  		},
  4557  
  4558  		"Good map": {
  4559  			Schema: map[string]*Schema{
  4560  				"user_data": &Schema{
  4561  					Type:     TypeMap,
  4562  					Optional: true,
  4563  				},
  4564  			},
  4565  
  4566  			Config: map[string]interface{}{
  4567  				"user_data": map[string]interface{}{
  4568  					"foo": "bar",
  4569  				},
  4570  			},
  4571  		},
  4572  
  4573  		"Map with type specified as value type": {
  4574  			Schema: map[string]*Schema{
  4575  				"user_data": &Schema{
  4576  					Type:     TypeMap,
  4577  					Optional: true,
  4578  					Elem:     TypeBool,
  4579  				},
  4580  			},
  4581  
  4582  			Config: map[string]interface{}{
  4583  				"user_data": map[string]interface{}{
  4584  					"foo": "not_a_bool",
  4585  				},
  4586  			},
  4587  
  4588  			Err: true,
  4589  		},
  4590  
  4591  		"Map with type specified as nested Schema": {
  4592  			Schema: map[string]*Schema{
  4593  				"user_data": &Schema{
  4594  					Type:     TypeMap,
  4595  					Optional: true,
  4596  					Elem:     &Schema{Type: TypeBool},
  4597  				},
  4598  			},
  4599  
  4600  			Config: map[string]interface{}{
  4601  				"user_data": map[string]interface{}{
  4602  					"foo": "not_a_bool",
  4603  				},
  4604  			},
  4605  
  4606  			Err: true,
  4607  		},
  4608  
  4609  		"Bad map: just a slice": {
  4610  			Schema: map[string]*Schema{
  4611  				"user_data": &Schema{
  4612  					Type:     TypeMap,
  4613  					Optional: true,
  4614  				},
  4615  			},
  4616  
  4617  			Config: map[string]interface{}{
  4618  				"user_data": []interface{}{
  4619  					"foo",
  4620  				},
  4621  			},
  4622  
  4623  			Err: true,
  4624  		},
  4625  
  4626  		"Good set: config has slice with single interpolated value": {
  4627  			Schema: map[string]*Schema{
  4628  				"security_groups": &Schema{
  4629  					Type:     TypeSet,
  4630  					Optional: true,
  4631  					Computed: true,
  4632  					ForceNew: true,
  4633  					Elem:     &Schema{Type: TypeString},
  4634  					Set: func(v interface{}) int {
  4635  						return len(v.(string))
  4636  					},
  4637  				},
  4638  			},
  4639  
  4640  			Config: map[string]interface{}{
  4641  				"security_groups": []interface{}{"${var.foo}"},
  4642  			},
  4643  
  4644  			Err: false,
  4645  		},
  4646  
  4647  		"Bad set: config has single interpolated value": {
  4648  			Schema: map[string]*Schema{
  4649  				"security_groups": &Schema{
  4650  					Type:     TypeSet,
  4651  					Optional: true,
  4652  					Computed: true,
  4653  					ForceNew: true,
  4654  					Elem:     &Schema{Type: TypeString},
  4655  				},
  4656  			},
  4657  
  4658  			Config: map[string]interface{}{
  4659  				"security_groups": "${var.foo}",
  4660  			},
  4661  
  4662  			Err: true,
  4663  		},
  4664  
  4665  		"Bad, subresource should not allow unknown elements": {
  4666  			Schema: map[string]*Schema{
  4667  				"ingress": &Schema{
  4668  					Type:     TypeList,
  4669  					Optional: true,
  4670  					Elem: &Resource{
  4671  						Schema: map[string]*Schema{
  4672  							"port": &Schema{
  4673  								Type:     TypeInt,
  4674  								Required: true,
  4675  							},
  4676  						},
  4677  					},
  4678  				},
  4679  			},
  4680  
  4681  			Config: map[string]interface{}{
  4682  				"ingress": []interface{}{
  4683  					map[string]interface{}{
  4684  						"port":  80,
  4685  						"other": "yes",
  4686  					},
  4687  				},
  4688  			},
  4689  
  4690  			Err: true,
  4691  		},
  4692  
  4693  		"Bad, subresource should not allow invalid types": {
  4694  			Schema: map[string]*Schema{
  4695  				"ingress": &Schema{
  4696  					Type:     TypeList,
  4697  					Optional: true,
  4698  					Elem: &Resource{
  4699  						Schema: map[string]*Schema{
  4700  							"port": &Schema{
  4701  								Type:     TypeInt,
  4702  								Required: true,
  4703  							},
  4704  						},
  4705  					},
  4706  				},
  4707  			},
  4708  
  4709  			Config: map[string]interface{}{
  4710  				"ingress": []interface{}{
  4711  					map[string]interface{}{
  4712  						"port": "bad",
  4713  					},
  4714  				},
  4715  			},
  4716  
  4717  			Err: true,
  4718  		},
  4719  
  4720  		"Bad, should not allow lists to be assigned to string attributes": {
  4721  			Schema: map[string]*Schema{
  4722  				"availability_zone": &Schema{
  4723  					Type:     TypeString,
  4724  					Required: true,
  4725  				},
  4726  			},
  4727  
  4728  			Config: map[string]interface{}{
  4729  				"availability_zone": []interface{}{"foo", "bar", "baz"},
  4730  			},
  4731  
  4732  			Err: true,
  4733  		},
  4734  
  4735  		"Bad, should not allow maps to be assigned to string attributes": {
  4736  			Schema: map[string]*Schema{
  4737  				"availability_zone": &Schema{
  4738  					Type:     TypeString,
  4739  					Required: true,
  4740  				},
  4741  			},
  4742  
  4743  			Config: map[string]interface{}{
  4744  				"availability_zone": map[string]interface{}{"foo": "bar", "baz": "thing"},
  4745  			},
  4746  
  4747  			Err: true,
  4748  		},
  4749  
  4750  		"Deprecated attribute usage generates warning, but not error": {
  4751  			Schema: map[string]*Schema{
  4752  				"old_news": &Schema{
  4753  					Type:       TypeString,
  4754  					Optional:   true,
  4755  					Deprecated: "please use 'new_news' instead",
  4756  				},
  4757  			},
  4758  
  4759  			Config: map[string]interface{}{
  4760  				"old_news": "extra extra!",
  4761  			},
  4762  
  4763  			Err: false,
  4764  
  4765  			Warnings: []string{
  4766  				"\"old_news\": [DEPRECATED] please use 'new_news' instead",
  4767  			},
  4768  		},
  4769  
  4770  		"Deprecated generates no warnings if attr not used": {
  4771  			Schema: map[string]*Schema{
  4772  				"old_news": &Schema{
  4773  					Type:       TypeString,
  4774  					Optional:   true,
  4775  					Deprecated: "please use 'new_news' instead",
  4776  				},
  4777  			},
  4778  
  4779  			Err: false,
  4780  
  4781  			Warnings: nil,
  4782  		},
  4783  
  4784  		"Removed attribute usage generates error": {
  4785  			Schema: map[string]*Schema{
  4786  				"long_gone": &Schema{
  4787  					Type:     TypeString,
  4788  					Optional: true,
  4789  					Removed:  "no longer supported by Cloud API",
  4790  				},
  4791  			},
  4792  
  4793  			Config: map[string]interface{}{
  4794  				"long_gone": "still here!",
  4795  			},
  4796  
  4797  			Err: true,
  4798  			Errors: []error{
  4799  				fmt.Errorf("\"long_gone\": [REMOVED] no longer supported by Cloud API"),
  4800  			},
  4801  		},
  4802  
  4803  		"Removed generates no errors if attr not used": {
  4804  			Schema: map[string]*Schema{
  4805  				"long_gone": &Schema{
  4806  					Type:     TypeString,
  4807  					Optional: true,
  4808  					Removed:  "no longer supported by Cloud API",
  4809  				},
  4810  			},
  4811  
  4812  			Err: false,
  4813  		},
  4814  
  4815  		"Conflicting attributes generate error": {
  4816  			Schema: map[string]*Schema{
  4817  				"whitelist": &Schema{
  4818  					Type:     TypeString,
  4819  					Optional: true,
  4820  				},
  4821  				"blacklist": &Schema{
  4822  					Type:          TypeString,
  4823  					Optional:      true,
  4824  					ConflictsWith: []string{"whitelist"},
  4825  				},
  4826  			},
  4827  
  4828  			Config: map[string]interface{}{
  4829  				"whitelist": "white-val",
  4830  				"blacklist": "black-val",
  4831  			},
  4832  
  4833  			Err: true,
  4834  			Errors: []error{
  4835  				fmt.Errorf("\"blacklist\": conflicts with whitelist"),
  4836  			},
  4837  		},
  4838  
  4839  		"Required attribute & undefined conflicting optional are good": {
  4840  			Schema: map[string]*Schema{
  4841  				"required_att": &Schema{
  4842  					Type:     TypeString,
  4843  					Required: true,
  4844  				},
  4845  				"optional_att": &Schema{
  4846  					Type:          TypeString,
  4847  					Optional:      true,
  4848  					ConflictsWith: []string{"required_att"},
  4849  				},
  4850  			},
  4851  
  4852  			Config: map[string]interface{}{
  4853  				"required_att": "required-val",
  4854  			},
  4855  
  4856  			Err: false,
  4857  		},
  4858  
  4859  		"Required conflicting attribute & defined optional generate error": {
  4860  			Schema: map[string]*Schema{
  4861  				"required_att": &Schema{
  4862  					Type:     TypeString,
  4863  					Required: true,
  4864  				},
  4865  				"optional_att": &Schema{
  4866  					Type:          TypeString,
  4867  					Optional:      true,
  4868  					ConflictsWith: []string{"required_att"},
  4869  				},
  4870  			},
  4871  
  4872  			Config: map[string]interface{}{
  4873  				"required_att": "required-val",
  4874  				"optional_att": "optional-val",
  4875  			},
  4876  
  4877  			Err: true,
  4878  			Errors: []error{
  4879  				fmt.Errorf(`"optional_att": conflicts with required_att`),
  4880  			},
  4881  		},
  4882  
  4883  		"Computed + Optional fields conflicting with each other": {
  4884  			Schema: map[string]*Schema{
  4885  				"foo_att": &Schema{
  4886  					Type:          TypeString,
  4887  					Optional:      true,
  4888  					Computed:      true,
  4889  					ConflictsWith: []string{"bar_att"},
  4890  				},
  4891  				"bar_att": &Schema{
  4892  					Type:          TypeString,
  4893  					Optional:      true,
  4894  					Computed:      true,
  4895  					ConflictsWith: []string{"foo_att"},
  4896  				},
  4897  			},
  4898  
  4899  			Config: map[string]interface{}{
  4900  				"foo_att": "foo-val",
  4901  				"bar_att": "bar-val",
  4902  			},
  4903  
  4904  			Err: true,
  4905  			Errors: []error{
  4906  				fmt.Errorf(`"foo_att": conflicts with bar_att`),
  4907  				fmt.Errorf(`"bar_att": conflicts with foo_att`),
  4908  			},
  4909  		},
  4910  
  4911  		"Computed + Optional fields NOT conflicting with each other": {
  4912  			Schema: map[string]*Schema{
  4913  				"foo_att": &Schema{
  4914  					Type:          TypeString,
  4915  					Optional:      true,
  4916  					Computed:      true,
  4917  					ConflictsWith: []string{"bar_att"},
  4918  				},
  4919  				"bar_att": &Schema{
  4920  					Type:          TypeString,
  4921  					Optional:      true,
  4922  					Computed:      true,
  4923  					ConflictsWith: []string{"foo_att"},
  4924  				},
  4925  			},
  4926  
  4927  			Config: map[string]interface{}{
  4928  				"foo_att": "foo-val",
  4929  			},
  4930  
  4931  			Err: false,
  4932  		},
  4933  
  4934  		"Computed + Optional fields that conflict with none set": {
  4935  			Schema: map[string]*Schema{
  4936  				"foo_att": &Schema{
  4937  					Type:          TypeString,
  4938  					Optional:      true,
  4939  					Computed:      true,
  4940  					ConflictsWith: []string{"bar_att"},
  4941  				},
  4942  				"bar_att": &Schema{
  4943  					Type:          TypeString,
  4944  					Optional:      true,
  4945  					Computed:      true,
  4946  					ConflictsWith: []string{"foo_att"},
  4947  				},
  4948  			},
  4949  
  4950  			Config: map[string]interface{}{},
  4951  
  4952  			Err: false,
  4953  		},
  4954  
  4955  		"Good with ValidateFunc": {
  4956  			Schema: map[string]*Schema{
  4957  				"validate_me": &Schema{
  4958  					Type:     TypeString,
  4959  					Required: true,
  4960  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  4961  						return
  4962  					},
  4963  				},
  4964  			},
  4965  			Config: map[string]interface{}{
  4966  				"validate_me": "valid",
  4967  			},
  4968  			Err: false,
  4969  		},
  4970  
  4971  		"Bad with ValidateFunc": {
  4972  			Schema: map[string]*Schema{
  4973  				"validate_me": &Schema{
  4974  					Type:     TypeString,
  4975  					Required: true,
  4976  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  4977  						es = append(es, fmt.Errorf("something is not right here"))
  4978  						return
  4979  					},
  4980  				},
  4981  			},
  4982  			Config: map[string]interface{}{
  4983  				"validate_me": "invalid",
  4984  			},
  4985  			Err: true,
  4986  			Errors: []error{
  4987  				fmt.Errorf(`something is not right here`),
  4988  			},
  4989  		},
  4990  
  4991  		"ValidateFunc not called when type does not match": {
  4992  			Schema: map[string]*Schema{
  4993  				"number": &Schema{
  4994  					Type:     TypeInt,
  4995  					Required: true,
  4996  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  4997  						t.Fatalf("Should not have gotten validate call")
  4998  						return
  4999  					},
  5000  				},
  5001  			},
  5002  			Config: map[string]interface{}{
  5003  				"number": "NaN",
  5004  			},
  5005  			Err: true,
  5006  		},
  5007  
  5008  		"ValidateFunc gets decoded type": {
  5009  			Schema: map[string]*Schema{
  5010  				"maybe": &Schema{
  5011  					Type:     TypeBool,
  5012  					Required: true,
  5013  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  5014  						if _, ok := v.(bool); !ok {
  5015  							t.Fatalf("Expected bool, got: %#v", v)
  5016  						}
  5017  						return
  5018  					},
  5019  				},
  5020  			},
  5021  			Config: map[string]interface{}{
  5022  				"maybe": "true",
  5023  			},
  5024  		},
  5025  
  5026  		"ValidateFunc is not called with a computed value": {
  5027  			Schema: map[string]*Schema{
  5028  				"validate_me": &Schema{
  5029  					Type:     TypeString,
  5030  					Required: true,
  5031  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  5032  						es = append(es, fmt.Errorf("something is not right here"))
  5033  						return
  5034  					},
  5035  				},
  5036  			},
  5037  			Config: map[string]interface{}{
  5038  				"validate_me": "${var.foo}",
  5039  			},
  5040  			Vars: map[string]string{
  5041  				"var.foo": config.UnknownVariableValue,
  5042  			},
  5043  
  5044  			Err: false,
  5045  		},
  5046  
  5047  		"special timeouts field": {
  5048  			Schema: map[string]*Schema{
  5049  				"availability_zone": &Schema{
  5050  					Type:     TypeString,
  5051  					Optional: true,
  5052  					Computed: true,
  5053  					ForceNew: true,
  5054  				},
  5055  			},
  5056  
  5057  			Config: map[string]interface{}{
  5058  				TimeoutsConfigKey: "bar",
  5059  			},
  5060  
  5061  			Err: false,
  5062  		},
  5063  
  5064  		"invalid bool field": {
  5065  			Schema: map[string]*Schema{
  5066  				"bool_field": {
  5067  					Type:     TypeBool,
  5068  					Optional: true,
  5069  				},
  5070  			},
  5071  			Config: map[string]interface{}{
  5072  				"bool_field": "abcdef",
  5073  			},
  5074  			Err: true,
  5075  		},
  5076  		"invalid integer field": {
  5077  			Schema: map[string]*Schema{
  5078  				"integer_field": {
  5079  					Type:     TypeInt,
  5080  					Optional: true,
  5081  				},
  5082  			},
  5083  			Config: map[string]interface{}{
  5084  				"integer_field": "abcdef",
  5085  			},
  5086  			Err: true,
  5087  		},
  5088  		"invalid float field": {
  5089  			Schema: map[string]*Schema{
  5090  				"float_field": {
  5091  					Type:     TypeFloat,
  5092  					Optional: true,
  5093  				},
  5094  			},
  5095  			Config: map[string]interface{}{
  5096  				"float_field": "abcdef",
  5097  			},
  5098  			Err: true,
  5099  		},
  5100  
  5101  		// Invalid map values
  5102  		"invalid bool map value": {
  5103  			Schema: map[string]*Schema{
  5104  				"boolMap": &Schema{
  5105  					Type:     TypeMap,
  5106  					Elem:     TypeBool,
  5107  					Optional: true,
  5108  				},
  5109  			},
  5110  			Config: map[string]interface{}{
  5111  				"boolMap": map[string]interface{}{
  5112  					"boolField": "notbool",
  5113  				},
  5114  			},
  5115  			Err: true,
  5116  		},
  5117  		"invalid int map value": {
  5118  			Schema: map[string]*Schema{
  5119  				"intMap": &Schema{
  5120  					Type:     TypeMap,
  5121  					Elem:     TypeInt,
  5122  					Optional: true,
  5123  				},
  5124  			},
  5125  			Config: map[string]interface{}{
  5126  				"intMap": map[string]interface{}{
  5127  					"intField": "notInt",
  5128  				},
  5129  			},
  5130  			Err: true,
  5131  		},
  5132  		"invalid float map value": {
  5133  			Schema: map[string]*Schema{
  5134  				"floatMap": &Schema{
  5135  					Type:     TypeMap,
  5136  					Elem:     TypeFloat,
  5137  					Optional: true,
  5138  				},
  5139  			},
  5140  			Config: map[string]interface{}{
  5141  				"floatMap": map[string]interface{}{
  5142  					"floatField": "notFloat",
  5143  				},
  5144  			},
  5145  			Err: true,
  5146  		},
  5147  
  5148  		"map with positive validate function": {
  5149  			Schema: map[string]*Schema{
  5150  				"floatInt": &Schema{
  5151  					Type:     TypeMap,
  5152  					Elem:     TypeInt,
  5153  					Optional: true,
  5154  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  5155  						return
  5156  					},
  5157  				},
  5158  			},
  5159  			Config: map[string]interface{}{
  5160  				"floatInt": map[string]interface{}{
  5161  					"rightAnswer": "42",
  5162  					"tooMuch":     "43",
  5163  				},
  5164  			},
  5165  			Err: false,
  5166  		},
  5167  		"map with negative validate function": {
  5168  			Schema: map[string]*Schema{
  5169  				"floatInt": &Schema{
  5170  					Type:     TypeMap,
  5171  					Elem:     TypeInt,
  5172  					Optional: true,
  5173  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  5174  						es = append(es, fmt.Errorf("this is not fine"))
  5175  						return
  5176  					},
  5177  				},
  5178  			},
  5179  			Config: map[string]interface{}{
  5180  				"floatInt": map[string]interface{}{
  5181  					"rightAnswer": "42",
  5182  					"tooMuch":     "43",
  5183  				},
  5184  			},
  5185  			Err: true,
  5186  		},
  5187  
  5188  		// The Validation function should not see interpolation strings from
  5189  		// non-computed values.
  5190  		"set with partially computed list and map": {
  5191  			Schema: map[string]*Schema{
  5192  				"outer": &Schema{
  5193  					Type:     TypeSet,
  5194  					Optional: true,
  5195  					Computed: true,
  5196  					Elem: &Resource{
  5197  						Schema: map[string]*Schema{
  5198  							"list": &Schema{
  5199  								Type:     TypeList,
  5200  								Optional: true,
  5201  								Elem: &Schema{
  5202  									Type: TypeString,
  5203  									ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  5204  										if strings.HasPrefix(v.(string), "${") {
  5205  											es = append(es, fmt.Errorf("should not have interpolations"))
  5206  										}
  5207  										return
  5208  									},
  5209  								},
  5210  							},
  5211  						},
  5212  					},
  5213  				},
  5214  			},
  5215  			Config: map[string]interface{}{
  5216  				"outer": []map[string]interface{}{
  5217  					{
  5218  						"list": []interface{}{"${var.a}", "${var.b}", "c"},
  5219  					},
  5220  				},
  5221  			},
  5222  			Vars: map[string]string{
  5223  				"var.a": "A",
  5224  				"var.b": config.UnknownVariableValue,
  5225  			},
  5226  			Err: false,
  5227  		},
  5228  	}
  5229  
  5230  	for tn, tc := range cases {
  5231  		t.Run(tn, func(t *testing.T) {
  5232  			c, err := config.NewRawConfig(tc.Config)
  5233  			if err != nil {
  5234  				t.Fatalf("err: %s", err)
  5235  			}
  5236  			if tc.Vars != nil {
  5237  				vars := make(map[string]ast.Variable)
  5238  				for k, v := range tc.Vars {
  5239  					vars[k] = ast.Variable{Value: v, Type: ast.TypeString}
  5240  				}
  5241  
  5242  				if err := c.Interpolate(vars); err != nil {
  5243  					t.Fatalf("err: %s", err)
  5244  				}
  5245  			}
  5246  
  5247  			ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c))
  5248  			if len(es) > 0 != tc.Err {
  5249  				if len(es) == 0 {
  5250  					t.Errorf("%q: no errors", tn)
  5251  				}
  5252  
  5253  				for _, e := range es {
  5254  					t.Errorf("%q: err: %s", tn, e)
  5255  				}
  5256  
  5257  				t.FailNow()
  5258  			}
  5259  
  5260  			if !reflect.DeepEqual(ws, tc.Warnings) {
  5261  				t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws)
  5262  			}
  5263  
  5264  			if tc.Errors != nil {
  5265  				sort.Sort(errorSort(es))
  5266  				sort.Sort(errorSort(tc.Errors))
  5267  
  5268  				if !reflect.DeepEqual(es, tc.Errors) {
  5269  					t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es)
  5270  				}
  5271  			}
  5272  		})
  5273  
  5274  	}
  5275  }
  5276  
  5277  func TestSchemaSet_ValidateMaxItems(t *testing.T) {
  5278  	cases := map[string]struct {
  5279  		Schema          map[string]*Schema
  5280  		State           *terraform.InstanceState
  5281  		Config          map[string]interface{}
  5282  		ConfigVariables map[string]string
  5283  		Diff            *terraform.InstanceDiff
  5284  		Err             bool
  5285  		Errors          []error
  5286  	}{
  5287  		"#0": {
  5288  			Schema: map[string]*Schema{
  5289  				"aliases": &Schema{
  5290  					Type:     TypeSet,
  5291  					Optional: true,
  5292  					MaxItems: 1,
  5293  					Elem:     &Schema{Type: TypeString},
  5294  				},
  5295  			},
  5296  			State: nil,
  5297  			Config: map[string]interface{}{
  5298  				"aliases": []interface{}{"foo", "bar"},
  5299  			},
  5300  			Diff: nil,
  5301  			Err:  true,
  5302  			Errors: []error{
  5303  				fmt.Errorf("aliases: attribute supports 1 item maximum, config has 2 declared"),
  5304  			},
  5305  		},
  5306  		"#1": {
  5307  			Schema: map[string]*Schema{
  5308  				"aliases": &Schema{
  5309  					Type:     TypeSet,
  5310  					Optional: true,
  5311  					Elem:     &Schema{Type: TypeString},
  5312  				},
  5313  			},
  5314  			State: nil,
  5315  			Config: map[string]interface{}{
  5316  				"aliases": []interface{}{"foo", "bar"},
  5317  			},
  5318  			Diff:   nil,
  5319  			Err:    false,
  5320  			Errors: nil,
  5321  		},
  5322  		"#2": {
  5323  			Schema: map[string]*Schema{
  5324  				"aliases": &Schema{
  5325  					Type:     TypeSet,
  5326  					Optional: true,
  5327  					MaxItems: 1,
  5328  					Elem:     &Schema{Type: TypeString},
  5329  				},
  5330  			},
  5331  			State: nil,
  5332  			Config: map[string]interface{}{
  5333  				"aliases": []interface{}{"foo"},
  5334  			},
  5335  			Diff:   nil,
  5336  			Err:    false,
  5337  			Errors: nil,
  5338  		},
  5339  	}
  5340  
  5341  	for tn, tc := range cases {
  5342  		c, err := config.NewRawConfig(tc.Config)
  5343  		if err != nil {
  5344  			t.Fatalf("%q: err: %s", tn, err)
  5345  		}
  5346  		_, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c))
  5347  
  5348  		if len(es) > 0 != tc.Err {
  5349  			if len(es) == 0 {
  5350  				t.Errorf("%q: no errors", tn)
  5351  			}
  5352  
  5353  			for _, e := range es {
  5354  				t.Errorf("%q: err: %s", tn, e)
  5355  			}
  5356  
  5357  			t.FailNow()
  5358  		}
  5359  
  5360  		if tc.Errors != nil {
  5361  			if !reflect.DeepEqual(es, tc.Errors) {
  5362  				t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es)
  5363  			}
  5364  		}
  5365  	}
  5366  }
  5367  
  5368  func TestSchemaSet_ValidateMinItems(t *testing.T) {
  5369  	cases := map[string]struct {
  5370  		Schema          map[string]*Schema
  5371  		State           *terraform.InstanceState
  5372  		Config          map[string]interface{}
  5373  		ConfigVariables map[string]string
  5374  		Diff            *terraform.InstanceDiff
  5375  		Err             bool
  5376  		Errors          []error
  5377  	}{
  5378  		"#0": {
  5379  			Schema: map[string]*Schema{
  5380  				"aliases": &Schema{
  5381  					Type:     TypeSet,
  5382  					Optional: true,
  5383  					MinItems: 2,
  5384  					Elem:     &Schema{Type: TypeString},
  5385  				},
  5386  			},
  5387  			State: nil,
  5388  			Config: map[string]interface{}{
  5389  				"aliases": []interface{}{"foo", "bar"},
  5390  			},
  5391  			Diff:   nil,
  5392  			Err:    false,
  5393  			Errors: nil,
  5394  		},
  5395  		"#1": {
  5396  			Schema: map[string]*Schema{
  5397  				"aliases": &Schema{
  5398  					Type:     TypeSet,
  5399  					Optional: true,
  5400  					Elem:     &Schema{Type: TypeString},
  5401  				},
  5402  			},
  5403  			State: nil,
  5404  			Config: map[string]interface{}{
  5405  				"aliases": []interface{}{"foo", "bar"},
  5406  			},
  5407  			Diff:   nil,
  5408  			Err:    false,
  5409  			Errors: nil,
  5410  		},
  5411  		"#2": {
  5412  			Schema: map[string]*Schema{
  5413  				"aliases": &Schema{
  5414  					Type:     TypeSet,
  5415  					Optional: true,
  5416  					MinItems: 2,
  5417  					Elem:     &Schema{Type: TypeString},
  5418  				},
  5419  			},
  5420  			State: nil,
  5421  			Config: map[string]interface{}{
  5422  				"aliases": []interface{}{"foo"},
  5423  			},
  5424  			Diff: nil,
  5425  			Err:  true,
  5426  			Errors: []error{
  5427  				fmt.Errorf("aliases: attribute supports 2 item as a minimum, config has 1 declared"),
  5428  			},
  5429  		},
  5430  	}
  5431  
  5432  	for tn, tc := range cases {
  5433  		c, err := config.NewRawConfig(tc.Config)
  5434  		if err != nil {
  5435  			t.Fatalf("%q: err: %s", tn, err)
  5436  		}
  5437  		_, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c))
  5438  
  5439  		if len(es) > 0 != tc.Err {
  5440  			if len(es) == 0 {
  5441  				t.Errorf("%q: no errors", tn)
  5442  			}
  5443  
  5444  			for _, e := range es {
  5445  				t.Errorf("%q: err: %s", tn, e)
  5446  			}
  5447  
  5448  			t.FailNow()
  5449  		}
  5450  
  5451  		if tc.Errors != nil {
  5452  			if !reflect.DeepEqual(es, tc.Errors) {
  5453  				t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es)
  5454  			}
  5455  		}
  5456  	}
  5457  }
  5458  
  5459  // errorSort implements sort.Interface to sort errors by their error message
  5460  type errorSort []error
  5461  
  5462  func (e errorSort) Len() int      { return len(e) }
  5463  func (e errorSort) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
  5464  func (e errorSort) Less(i, j int) bool {
  5465  	return e[i].Error() < e[j].Error()
  5466  }
  5467  
  5468  func TestSchemaMapDeepCopy(t *testing.T) {
  5469  	schema := map[string]*Schema{
  5470  		"foo": &Schema{
  5471  			Type: TypeString,
  5472  		},
  5473  	}
  5474  	source := schemaMap(schema)
  5475  	dest := source.DeepCopy()
  5476  	dest["foo"].ForceNew = true
  5477  	if reflect.DeepEqual(source, dest) {
  5478  		t.Fatalf("source and dest should not match")
  5479  	}
  5480  }