github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/plans/objchange/compatible_test.go (about)

     1  package objchange
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/apparentlymart/go-dump/dump"
     8  	"github.com/zclconf/go-cty/cty"
     9  
    10  	"github.com/iaas-resource-provision/iaas-rpc/internal/configs/configschema"
    11  	"github.com/iaas-resource-provision/iaas-rpc/internal/tfdiags"
    12  )
    13  
    14  func TestAssertObjectCompatible(t *testing.T) {
    15  	schemaWithFoo := configschema.Block{
    16  		Attributes: map[string]*configschema.Attribute{
    17  			"foo": {Type: cty.String, Optional: true},
    18  		},
    19  	}
    20  	fooBlockValue := cty.ObjectVal(map[string]cty.Value{
    21  		"foo": cty.StringVal("bar"),
    22  	})
    23  	schemaWithFooBar := configschema.Block{
    24  		Attributes: map[string]*configschema.Attribute{
    25  			"foo": {Type: cty.String, Optional: true},
    26  			"bar": {Type: cty.String, Optional: true},
    27  		},
    28  	}
    29  	fooBarBlockValue := cty.ObjectVal(map[string]cty.Value{
    30  		"foo": cty.StringVal("bar"),
    31  		"bar": cty.NullVal(cty.String), // simulating the situation where bar isn't set in the config at all
    32  	})
    33  
    34  	tests := []struct {
    35  		Schema   *configschema.Block
    36  		Planned  cty.Value
    37  		Actual   cty.Value
    38  		WantErrs []string
    39  	}{
    40  		{
    41  			&configschema.Block{},
    42  			cty.EmptyObjectVal,
    43  			cty.EmptyObjectVal,
    44  			nil,
    45  		},
    46  		{
    47  			&configschema.Block{
    48  				Attributes: map[string]*configschema.Attribute{
    49  					"id": {
    50  						Type:     cty.String,
    51  						Computed: true,
    52  					},
    53  					"name": {
    54  						Type:     cty.String,
    55  						Required: true,
    56  					},
    57  				},
    58  			},
    59  			cty.ObjectVal(map[string]cty.Value{
    60  				"id":   cty.UnknownVal(cty.String),
    61  				"name": cty.StringVal("thingy"),
    62  			}),
    63  			cty.ObjectVal(map[string]cty.Value{
    64  				"id":   cty.UnknownVal(cty.String),
    65  				"name": cty.StringVal("thingy"),
    66  			}),
    67  			nil,
    68  		},
    69  		{
    70  			&configschema.Block{
    71  				Attributes: map[string]*configschema.Attribute{
    72  					"id": {
    73  						Type:     cty.String,
    74  						Computed: true,
    75  					},
    76  					"name": {
    77  						Type:     cty.String,
    78  						Required: true,
    79  					},
    80  				},
    81  			},
    82  			cty.ObjectVal(map[string]cty.Value{
    83  				"id":   cty.UnknownVal(cty.String),
    84  				"name": cty.UnknownVal(cty.String),
    85  			}),
    86  			cty.ObjectVal(map[string]cty.Value{
    87  				"id":   cty.UnknownVal(cty.String),
    88  				"name": cty.StringVal("thingy"),
    89  			}),
    90  			nil,
    91  		},
    92  		{
    93  			&configschema.Block{
    94  				Attributes: map[string]*configschema.Attribute{
    95  					"id": {
    96  						Type:     cty.String,
    97  						Computed: true,
    98  					},
    99  					"name": {
   100  						Type:     cty.String,
   101  						Required: true,
   102  					},
   103  				},
   104  			},
   105  			cty.ObjectVal(map[string]cty.Value{
   106  				"id":   cty.UnknownVal(cty.String),
   107  				"name": cty.StringVal("wotsit"),
   108  			}),
   109  			cty.ObjectVal(map[string]cty.Value{
   110  				"id":   cty.UnknownVal(cty.String),
   111  				"name": cty.StringVal("thingy"),
   112  			}),
   113  			[]string{
   114  				`.name: was cty.StringVal("wotsit"), but now cty.StringVal("thingy")`,
   115  			},
   116  		},
   117  		{
   118  			&configschema.Block{
   119  				Attributes: map[string]*configschema.Attribute{
   120  					"id": {
   121  						Type:     cty.String,
   122  						Computed: true,
   123  					},
   124  					"name": {
   125  						Type:      cty.String,
   126  						Required:  true,
   127  						Sensitive: true,
   128  					},
   129  				},
   130  			},
   131  			cty.ObjectVal(map[string]cty.Value{
   132  				"id":   cty.UnknownVal(cty.String),
   133  				"name": cty.StringVal("wotsit"),
   134  			}),
   135  			cty.ObjectVal(map[string]cty.Value{
   136  				"id":   cty.UnknownVal(cty.String),
   137  				"name": cty.StringVal("thingy"),
   138  			}),
   139  			[]string{
   140  				`.name: inconsistent values for sensitive attribute`,
   141  			},
   142  		},
   143  		{
   144  			&configschema.Block{
   145  				Attributes: map[string]*configschema.Attribute{
   146  					"id": {
   147  						Type:     cty.String,
   148  						Computed: true,
   149  					},
   150  					"name": {
   151  						Type:     cty.String,
   152  						Required: true,
   153  					},
   154  				},
   155  			},
   156  			cty.ObjectVal(map[string]cty.Value{
   157  				"id":   cty.UnknownVal(cty.String),
   158  				"name": cty.StringVal("wotsit").Mark("sensitive"),
   159  			}),
   160  			cty.ObjectVal(map[string]cty.Value{
   161  				"id":   cty.UnknownVal(cty.String),
   162  				"name": cty.StringVal("thingy"),
   163  			}),
   164  			[]string{
   165  				`.name: inconsistent values for sensitive attribute`,
   166  			},
   167  		},
   168  		{
   169  			&configschema.Block{
   170  				Attributes: map[string]*configschema.Attribute{
   171  					"id": {
   172  						Type:     cty.String,
   173  						Computed: true,
   174  					},
   175  					"name": {
   176  						Type:     cty.String,
   177  						Required: true,
   178  					},
   179  				},
   180  			},
   181  			cty.ObjectVal(map[string]cty.Value{
   182  				"id":   cty.UnknownVal(cty.String),
   183  				"name": cty.StringVal("wotsit"),
   184  			}),
   185  			cty.ObjectVal(map[string]cty.Value{
   186  				"id":   cty.UnknownVal(cty.String),
   187  				"name": cty.StringVal("thingy").Mark("sensitive"),
   188  			}),
   189  			[]string{
   190  				`.name: inconsistent values for sensitive attribute`,
   191  			},
   192  		},
   193  		{
   194  			// This tests the codepath that leads to couldHaveUnknownBlockPlaceholder,
   195  			// where a set may be sensitive and need to be unmarked before it
   196  			// is iterated upon
   197  			&configschema.Block{
   198  				BlockTypes: map[string]*configschema.NestedBlock{
   199  					"configuration": {
   200  						Nesting: configschema.NestingList,
   201  						Block: configschema.Block{
   202  							BlockTypes: map[string]*configschema.NestedBlock{
   203  								"sensitive_fields": {
   204  									Nesting: configschema.NestingSet,
   205  									Block:   schemaWithFoo,
   206  								},
   207  							},
   208  						},
   209  					},
   210  				},
   211  			},
   212  			cty.ObjectVal(map[string]cty.Value{
   213  				"configuration": cty.TupleVal([]cty.Value{
   214  					cty.ObjectVal(map[string]cty.Value{
   215  						"sensitive_fields": cty.SetVal([]cty.Value{
   216  							cty.ObjectVal(map[string]cty.Value{
   217  								"foo": cty.StringVal("secret"),
   218  							}),
   219  						}).Mark("sensitive"),
   220  					}),
   221  				}),
   222  			}),
   223  			cty.ObjectVal(map[string]cty.Value{
   224  				"configuration": cty.TupleVal([]cty.Value{
   225  					cty.ObjectVal(map[string]cty.Value{
   226  						"sensitive_fields": cty.SetVal([]cty.Value{
   227  							cty.ObjectVal(map[string]cty.Value{
   228  								"foo": cty.StringVal("secret"),
   229  							}),
   230  						}).Mark("sensitive"),
   231  					}),
   232  				}),
   233  			}),
   234  			nil,
   235  		},
   236  		{
   237  			&configschema.Block{
   238  				Attributes: map[string]*configschema.Attribute{
   239  					"id": {
   240  						Type:     cty.String,
   241  						Computed: true,
   242  					},
   243  					"stuff": {
   244  						Type:     cty.DynamicPseudoType,
   245  						Required: true,
   246  					},
   247  				},
   248  			},
   249  			cty.ObjectVal(map[string]cty.Value{
   250  				"id":    cty.UnknownVal(cty.String),
   251  				"stuff": cty.DynamicVal,
   252  			}),
   253  			cty.ObjectVal(map[string]cty.Value{
   254  				"id":    cty.UnknownVal(cty.String),
   255  				"stuff": cty.StringVal("thingy"),
   256  			}),
   257  			[]string{},
   258  		},
   259  		{
   260  			&configschema.Block{
   261  				Attributes: map[string]*configschema.Attribute{
   262  					"obj": {
   263  						Type: cty.Object(map[string]cty.Type{
   264  							"stuff": cty.DynamicPseudoType,
   265  						}),
   266  					},
   267  				},
   268  			},
   269  			cty.ObjectVal(map[string]cty.Value{
   270  				"obj": cty.ObjectVal(map[string]cty.Value{
   271  					"stuff": cty.DynamicVal,
   272  				}),
   273  			}),
   274  			cty.ObjectVal(map[string]cty.Value{
   275  				"obj": cty.ObjectVal(map[string]cty.Value{
   276  					"stuff": cty.NumberIntVal(3),
   277  				}),
   278  			}),
   279  			[]string{},
   280  		},
   281  		{
   282  			&configschema.Block{
   283  				Attributes: map[string]*configschema.Attribute{
   284  					"id": {
   285  						Type:     cty.String,
   286  						Computed: true,
   287  					},
   288  					"stuff": {
   289  						Type:     cty.DynamicPseudoType,
   290  						Required: true,
   291  					},
   292  				},
   293  			},
   294  			cty.ObjectVal(map[string]cty.Value{
   295  				"id":    cty.UnknownVal(cty.String),
   296  				"stuff": cty.StringVal("wotsit"),
   297  			}),
   298  			cty.ObjectVal(map[string]cty.Value{
   299  				"id":    cty.UnknownVal(cty.String),
   300  				"stuff": cty.StringVal("thingy"),
   301  			}),
   302  			[]string{
   303  				`.stuff: was cty.StringVal("wotsit"), but now cty.StringVal("thingy")`,
   304  			},
   305  		},
   306  		{
   307  			&configschema.Block{
   308  				Attributes: map[string]*configschema.Attribute{
   309  					"id": {
   310  						Type:     cty.String,
   311  						Computed: true,
   312  					},
   313  					"stuff": {
   314  						Type:     cty.DynamicPseudoType,
   315  						Required: true,
   316  					},
   317  				},
   318  			},
   319  			cty.ObjectVal(map[string]cty.Value{
   320  				"id":    cty.UnknownVal(cty.String),
   321  				"stuff": cty.StringVal("true"),
   322  			}),
   323  			cty.ObjectVal(map[string]cty.Value{
   324  				"id":    cty.UnknownVal(cty.String),
   325  				"stuff": cty.True,
   326  			}),
   327  			[]string{
   328  				`.stuff: wrong final value type: string required`,
   329  			},
   330  		},
   331  		{
   332  			&configschema.Block{
   333  				Attributes: map[string]*configschema.Attribute{
   334  					"id": {
   335  						Type:     cty.String,
   336  						Computed: true,
   337  					},
   338  					"stuff": {
   339  						Type:     cty.DynamicPseudoType,
   340  						Required: true,
   341  					},
   342  				},
   343  			},
   344  			cty.ObjectVal(map[string]cty.Value{
   345  				"id":    cty.UnknownVal(cty.String),
   346  				"stuff": cty.DynamicVal,
   347  			}),
   348  			cty.ObjectVal(map[string]cty.Value{
   349  				"id":    cty.UnknownVal(cty.String),
   350  				"stuff": cty.EmptyObjectVal,
   351  			}),
   352  			nil,
   353  		},
   354  		{
   355  			&configschema.Block{
   356  				Attributes: map[string]*configschema.Attribute{
   357  					"id": {
   358  						Type:     cty.String,
   359  						Computed: true,
   360  					},
   361  					"stuff": {
   362  						Type:     cty.DynamicPseudoType,
   363  						Required: true,
   364  					},
   365  				},
   366  			},
   367  			cty.ObjectVal(map[string]cty.Value{
   368  				"id": cty.UnknownVal(cty.String),
   369  				"stuff": cty.ObjectVal(map[string]cty.Value{
   370  					"nonsense": cty.StringVal("yup"),
   371  				}),
   372  			}),
   373  			cty.ObjectVal(map[string]cty.Value{
   374  				"id":    cty.UnknownVal(cty.String),
   375  				"stuff": cty.EmptyObjectVal,
   376  			}),
   377  			[]string{
   378  				`.stuff: wrong final value type: attribute "nonsense" is required`,
   379  			},
   380  		},
   381  		{
   382  			&configschema.Block{
   383  				Attributes: map[string]*configschema.Attribute{
   384  					"id": {
   385  						Type:     cty.String,
   386  						Computed: true,
   387  					},
   388  					"tags": {
   389  						Type:     cty.Map(cty.String),
   390  						Optional: true,
   391  					},
   392  				},
   393  			},
   394  			cty.ObjectVal(map[string]cty.Value{
   395  				"id": cty.UnknownVal(cty.String),
   396  				"tags": cty.MapVal(map[string]cty.Value{
   397  					"Name": cty.StringVal("thingy"),
   398  				}),
   399  			}),
   400  			cty.ObjectVal(map[string]cty.Value{
   401  				"id": cty.UnknownVal(cty.String),
   402  				"tags": cty.MapVal(map[string]cty.Value{
   403  					"Name": cty.StringVal("thingy"),
   404  				}),
   405  			}),
   406  			nil,
   407  		},
   408  		{
   409  			&configschema.Block{
   410  				Attributes: map[string]*configschema.Attribute{
   411  					"id": {
   412  						Type:     cty.String,
   413  						Computed: true,
   414  					},
   415  					"tags": {
   416  						Type:     cty.Map(cty.String),
   417  						Optional: true,
   418  					},
   419  				},
   420  			},
   421  			cty.ObjectVal(map[string]cty.Value{
   422  				"id": cty.UnknownVal(cty.String),
   423  				"tags": cty.MapVal(map[string]cty.Value{
   424  					"Name": cty.UnknownVal(cty.String),
   425  				}),
   426  			}),
   427  			cty.ObjectVal(map[string]cty.Value{
   428  				"id": cty.UnknownVal(cty.String),
   429  				"tags": cty.MapVal(map[string]cty.Value{
   430  					"Name": cty.StringVal("thingy"),
   431  				}),
   432  			}),
   433  			nil,
   434  		},
   435  		{
   436  			&configschema.Block{
   437  				Attributes: map[string]*configschema.Attribute{
   438  					"id": {
   439  						Type:     cty.String,
   440  						Computed: true,
   441  					},
   442  					"tags": {
   443  						Type:     cty.Map(cty.String),
   444  						Optional: true,
   445  					},
   446  				},
   447  			},
   448  			cty.ObjectVal(map[string]cty.Value{
   449  				"id": cty.UnknownVal(cty.String),
   450  				"tags": cty.MapVal(map[string]cty.Value{
   451  					"Name": cty.StringVal("wotsit"),
   452  				}),
   453  			}),
   454  			cty.ObjectVal(map[string]cty.Value{
   455  				"id": cty.UnknownVal(cty.String),
   456  				"tags": cty.MapVal(map[string]cty.Value{
   457  					"Name": cty.StringVal("thingy"),
   458  				}),
   459  			}),
   460  			[]string{
   461  				`.tags["Name"]: was cty.StringVal("wotsit"), but now cty.StringVal("thingy")`,
   462  			},
   463  		},
   464  		{
   465  			&configschema.Block{
   466  				Attributes: map[string]*configschema.Attribute{
   467  					"id": {
   468  						Type:     cty.String,
   469  						Computed: true,
   470  					},
   471  					"tags": {
   472  						Type:     cty.Map(cty.String),
   473  						Optional: true,
   474  					},
   475  				},
   476  			},
   477  			cty.ObjectVal(map[string]cty.Value{
   478  				"id": cty.UnknownVal(cty.String),
   479  				"tags": cty.MapVal(map[string]cty.Value{
   480  					"Name": cty.StringVal("thingy"),
   481  				}),
   482  			}),
   483  			cty.ObjectVal(map[string]cty.Value{
   484  				"id": cty.UnknownVal(cty.String),
   485  				"tags": cty.MapVal(map[string]cty.Value{
   486  					"Name": cty.StringVal("thingy"),
   487  					"Env":  cty.StringVal("production"),
   488  				}),
   489  			}),
   490  			[]string{
   491  				`.tags: new element "Env" has appeared`,
   492  			},
   493  		},
   494  		{
   495  			&configschema.Block{
   496  				Attributes: map[string]*configschema.Attribute{
   497  					"id": {
   498  						Type:     cty.String,
   499  						Computed: true,
   500  					},
   501  					"tags": {
   502  						Type:     cty.Map(cty.String),
   503  						Optional: true,
   504  					},
   505  				},
   506  			},
   507  			cty.ObjectVal(map[string]cty.Value{
   508  				"id": cty.UnknownVal(cty.String),
   509  				"tags": cty.MapVal(map[string]cty.Value{
   510  					"Name": cty.StringVal("thingy"),
   511  				}),
   512  			}),
   513  			cty.ObjectVal(map[string]cty.Value{
   514  				"id":   cty.UnknownVal(cty.String),
   515  				"tags": cty.MapValEmpty(cty.String),
   516  			}),
   517  			[]string{
   518  				`.tags: element "Name" has vanished`,
   519  			},
   520  		},
   521  		{
   522  			&configschema.Block{
   523  				Attributes: map[string]*configschema.Attribute{
   524  					"id": {
   525  						Type:     cty.String,
   526  						Computed: true,
   527  					},
   528  					"tags": {
   529  						Type:     cty.Map(cty.String),
   530  						Optional: true,
   531  					},
   532  				},
   533  			},
   534  			cty.ObjectVal(map[string]cty.Value{
   535  				"id": cty.UnknownVal(cty.String),
   536  				"tags": cty.MapVal(map[string]cty.Value{
   537  					"Name": cty.UnknownVal(cty.String),
   538  				}),
   539  			}),
   540  			cty.ObjectVal(map[string]cty.Value{
   541  				"id": cty.UnknownVal(cty.String),
   542  				"tags": cty.MapVal(map[string]cty.Value{
   543  					"Name": cty.NullVal(cty.String),
   544  				}),
   545  			}),
   546  			nil,
   547  		},
   548  		{
   549  			&configschema.Block{
   550  				Attributes: map[string]*configschema.Attribute{
   551  					"id": {
   552  						Type:     cty.String,
   553  						Computed: true,
   554  					},
   555  					"zones": {
   556  						Type:     cty.Set(cty.String),
   557  						Optional: true,
   558  					},
   559  				},
   560  			},
   561  			cty.ObjectVal(map[string]cty.Value{
   562  				"id": cty.UnknownVal(cty.String),
   563  				"zones": cty.SetVal([]cty.Value{
   564  					cty.StringVal("thingy"),
   565  				}),
   566  			}),
   567  			cty.ObjectVal(map[string]cty.Value{
   568  				"id": cty.UnknownVal(cty.String),
   569  				"zones": cty.SetVal([]cty.Value{
   570  					cty.StringVal("thingy"),
   571  				}),
   572  			}),
   573  			nil,
   574  		},
   575  		{
   576  			&configschema.Block{
   577  				Attributes: map[string]*configschema.Attribute{
   578  					"id": {
   579  						Type:     cty.String,
   580  						Computed: true,
   581  					},
   582  					"zones": {
   583  						Type:     cty.Set(cty.String),
   584  						Optional: true,
   585  					},
   586  				},
   587  			},
   588  			cty.ObjectVal(map[string]cty.Value{
   589  				"id": cty.UnknownVal(cty.String),
   590  				"zones": cty.SetVal([]cty.Value{
   591  					cty.StringVal("thingy"),
   592  				}),
   593  			}),
   594  			cty.ObjectVal(map[string]cty.Value{
   595  				"id": cty.UnknownVal(cty.String),
   596  				"zones": cty.SetVal([]cty.Value{
   597  					cty.StringVal("thingy"),
   598  					cty.StringVal("wotsit"),
   599  				}),
   600  			}),
   601  			[]string{
   602  				`.zones: actual set element cty.StringVal("wotsit") does not correlate with any element in plan`,
   603  				`.zones: length changed from 1 to 2`,
   604  			},
   605  		},
   606  		{
   607  			&configschema.Block{
   608  				Attributes: map[string]*configschema.Attribute{
   609  					"id": {
   610  						Type:     cty.String,
   611  						Computed: true,
   612  					},
   613  					"zones": {
   614  						Type:     cty.Set(cty.String),
   615  						Optional: true,
   616  					},
   617  				},
   618  			},
   619  			cty.ObjectVal(map[string]cty.Value{
   620  				"id": cty.UnknownVal(cty.String),
   621  				"zones": cty.SetVal([]cty.Value{
   622  					cty.UnknownVal(cty.String),
   623  					cty.UnknownVal(cty.String),
   624  				}),
   625  			}),
   626  			cty.ObjectVal(map[string]cty.Value{
   627  				"id": cty.UnknownVal(cty.String),
   628  				"zones": cty.SetVal([]cty.Value{
   629  					// Imagine that both of our unknown values ultimately resolved to "thingy",
   630  					// causing them to collapse into a single element. That's valid,
   631  					// even though it's also a little confusing and counter-intuitive.
   632  					cty.StringVal("thingy"),
   633  				}),
   634  			}),
   635  			nil,
   636  		},
   637  		{
   638  			&configschema.Block{
   639  				Attributes: map[string]*configschema.Attribute{
   640  					"id": {
   641  						Type:     cty.String,
   642  						Computed: true,
   643  					},
   644  					"names": {
   645  						Type:     cty.List(cty.String),
   646  						Optional: true,
   647  					},
   648  				},
   649  			},
   650  			cty.ObjectVal(map[string]cty.Value{
   651  				"id": cty.UnknownVal(cty.String),
   652  				"names": cty.ListVal([]cty.Value{
   653  					cty.StringVal("thingy"),
   654  				}),
   655  			}),
   656  			cty.ObjectVal(map[string]cty.Value{
   657  				"id": cty.UnknownVal(cty.String),
   658  				"names": cty.ListVal([]cty.Value{
   659  					cty.StringVal("thingy"),
   660  				}),
   661  			}),
   662  			nil,
   663  		},
   664  		{
   665  			&configschema.Block{
   666  				Attributes: map[string]*configschema.Attribute{
   667  					"id": {
   668  						Type:     cty.String,
   669  						Computed: true,
   670  					},
   671  					"names": {
   672  						Type:     cty.List(cty.String),
   673  						Optional: true,
   674  					},
   675  				},
   676  			},
   677  			cty.ObjectVal(map[string]cty.Value{
   678  				"id":    cty.UnknownVal(cty.String),
   679  				"names": cty.UnknownVal(cty.List(cty.String)),
   680  			}),
   681  			cty.ObjectVal(map[string]cty.Value{
   682  				"id": cty.UnknownVal(cty.String),
   683  				"names": cty.ListVal([]cty.Value{
   684  					cty.StringVal("thingy"),
   685  				}),
   686  			}),
   687  			nil,
   688  		},
   689  		{
   690  			&configschema.Block{
   691  				Attributes: map[string]*configschema.Attribute{
   692  					"id": {
   693  						Type:     cty.String,
   694  						Computed: true,
   695  					},
   696  					"names": {
   697  						Type:     cty.List(cty.String),
   698  						Optional: true,
   699  					},
   700  				},
   701  			},
   702  			cty.ObjectVal(map[string]cty.Value{
   703  				"id": cty.UnknownVal(cty.String),
   704  				"names": cty.ListVal([]cty.Value{
   705  					cty.UnknownVal(cty.String),
   706  				}),
   707  			}),
   708  			cty.ObjectVal(map[string]cty.Value{
   709  				"id": cty.UnknownVal(cty.String),
   710  				"names": cty.ListVal([]cty.Value{
   711  					cty.StringVal("thingy"),
   712  				}),
   713  			}),
   714  			nil,
   715  		},
   716  		{
   717  			&configschema.Block{
   718  				Attributes: map[string]*configschema.Attribute{
   719  					"id": {
   720  						Type:     cty.String,
   721  						Computed: true,
   722  					},
   723  					"names": {
   724  						Type:     cty.List(cty.String),
   725  						Optional: true,
   726  					},
   727  				},
   728  			},
   729  			cty.ObjectVal(map[string]cty.Value{
   730  				"id": cty.UnknownVal(cty.String),
   731  				"names": cty.ListVal([]cty.Value{
   732  					cty.StringVal("thingy"),
   733  					cty.UnknownVal(cty.String),
   734  				}),
   735  			}),
   736  			cty.ObjectVal(map[string]cty.Value{
   737  				"id": cty.UnknownVal(cty.String),
   738  				"names": cty.ListVal([]cty.Value{
   739  					cty.StringVal("thingy"),
   740  					cty.StringVal("wotsit"),
   741  				}),
   742  			}),
   743  			nil,
   744  		},
   745  		{
   746  			&configschema.Block{
   747  				Attributes: map[string]*configschema.Attribute{
   748  					"id": {
   749  						Type:     cty.String,
   750  						Computed: true,
   751  					},
   752  					"names": {
   753  						Type:     cty.List(cty.String),
   754  						Optional: true,
   755  					},
   756  				},
   757  			},
   758  			cty.ObjectVal(map[string]cty.Value{
   759  				"id": cty.UnknownVal(cty.String),
   760  				"names": cty.ListVal([]cty.Value{
   761  					cty.UnknownVal(cty.String),
   762  					cty.StringVal("thingy"),
   763  				}),
   764  			}),
   765  			cty.ObjectVal(map[string]cty.Value{
   766  				"id": cty.UnknownVal(cty.String),
   767  				"names": cty.ListVal([]cty.Value{
   768  					cty.StringVal("thingy"),
   769  					cty.StringVal("wotsit"),
   770  				}),
   771  			}),
   772  			[]string{
   773  				`.names[1]: was cty.StringVal("thingy"), but now cty.StringVal("wotsit")`,
   774  			},
   775  		},
   776  		{
   777  			&configschema.Block{
   778  				Attributes: map[string]*configschema.Attribute{
   779  					"id": {
   780  						Type:     cty.String,
   781  						Computed: true,
   782  					},
   783  					"names": {
   784  						Type:     cty.List(cty.String),
   785  						Optional: true,
   786  					},
   787  				},
   788  			},
   789  			cty.ObjectVal(map[string]cty.Value{
   790  				"id": cty.UnknownVal(cty.String),
   791  				"names": cty.ListVal([]cty.Value{
   792  					cty.UnknownVal(cty.String),
   793  				}),
   794  			}),
   795  			cty.ObjectVal(map[string]cty.Value{
   796  				"id": cty.UnknownVal(cty.String),
   797  				"names": cty.ListVal([]cty.Value{
   798  					cty.StringVal("thingy"),
   799  					cty.StringVal("wotsit"),
   800  				}),
   801  			}),
   802  			[]string{
   803  				`.names: new element 1 has appeared`,
   804  			},
   805  		},
   806  
   807  		// NestingSingle blocks
   808  		{
   809  			&configschema.Block{
   810  				BlockTypes: map[string]*configschema.NestedBlock{
   811  					"key": {
   812  						Nesting: configschema.NestingSingle,
   813  						Block:   configschema.Block{},
   814  					},
   815  				},
   816  			},
   817  			cty.ObjectVal(map[string]cty.Value{
   818  				"key": cty.EmptyObjectVal,
   819  			}),
   820  			cty.ObjectVal(map[string]cty.Value{
   821  				"key": cty.EmptyObjectVal,
   822  			}),
   823  			nil,
   824  		},
   825  		{
   826  			&configschema.Block{
   827  				BlockTypes: map[string]*configschema.NestedBlock{
   828  					"key": {
   829  						Nesting: configschema.NestingSingle,
   830  						Block:   configschema.Block{},
   831  					},
   832  				},
   833  			},
   834  			cty.ObjectVal(map[string]cty.Value{
   835  				"key": cty.UnknownVal(cty.EmptyObject),
   836  			}),
   837  			cty.ObjectVal(map[string]cty.Value{
   838  				"key": cty.EmptyObjectVal,
   839  			}),
   840  			nil,
   841  		},
   842  		{
   843  			&configschema.Block{
   844  				BlockTypes: map[string]*configschema.NestedBlock{
   845  					"key": {
   846  						Nesting: configschema.NestingSingle,
   847  						Block: configschema.Block{
   848  							Attributes: map[string]*configschema.Attribute{
   849  								"foo": {
   850  									Type:     cty.String,
   851  									Optional: true,
   852  								},
   853  							},
   854  						},
   855  					},
   856  				},
   857  			},
   858  			cty.ObjectVal(map[string]cty.Value{
   859  				"key": cty.NullVal(cty.Object(map[string]cty.Type{
   860  					"foo": cty.String,
   861  				})),
   862  			}),
   863  			cty.ObjectVal(map[string]cty.Value{
   864  				"key": cty.ObjectVal(map[string]cty.Value{
   865  					"foo": cty.StringVal("hello"),
   866  				}),
   867  			}),
   868  			[]string{
   869  				`.key: was absent, but now present`,
   870  			},
   871  		},
   872  		{
   873  			&configschema.Block{
   874  				BlockTypes: map[string]*configschema.NestedBlock{
   875  					"key": {
   876  						Nesting: configschema.NestingSingle,
   877  						Block: configschema.Block{
   878  							Attributes: map[string]*configschema.Attribute{
   879  								"foo": {
   880  									Type:     cty.String,
   881  									Optional: true,
   882  								},
   883  							},
   884  						},
   885  					},
   886  				},
   887  			},
   888  			cty.ObjectVal(map[string]cty.Value{
   889  				"key": cty.ObjectVal(map[string]cty.Value{
   890  					"foo": cty.StringVal("hello"),
   891  				}),
   892  			}),
   893  			cty.ObjectVal(map[string]cty.Value{
   894  				"key": cty.NullVal(cty.Object(map[string]cty.Type{
   895  					"foo": cty.String,
   896  				})),
   897  			}),
   898  			[]string{
   899  				`.key: was present, but now absent`,
   900  			},
   901  		},
   902  		{
   903  			&configschema.Block{
   904  				BlockTypes: map[string]*configschema.NestedBlock{
   905  					"key": {
   906  						Nesting: configschema.NestingSingle,
   907  						Block: configschema.Block{
   908  							Attributes: map[string]*configschema.Attribute{
   909  								"foo": {
   910  									Type:     cty.String,
   911  									Optional: true,
   912  								},
   913  							},
   914  						},
   915  					},
   916  				},
   917  			},
   918  			cty.UnknownVal(cty.Object(map[string]cty.Type{
   919  				"key": cty.Object(map[string]cty.Type{
   920  					"foo": cty.String,
   921  				}),
   922  			})),
   923  			cty.ObjectVal(map[string]cty.Value{
   924  				"key": cty.NullVal(cty.Object(map[string]cty.Type{
   925  					"foo": cty.String,
   926  				})),
   927  			}),
   928  			nil,
   929  		},
   930  
   931  		// NestingList blocks
   932  		{
   933  			&configschema.Block{
   934  				BlockTypes: map[string]*configschema.NestedBlock{
   935  					"key": {
   936  						Nesting: configschema.NestingList,
   937  						Block:   schemaWithFoo,
   938  					},
   939  				},
   940  			},
   941  			cty.ObjectVal(map[string]cty.Value{
   942  				"key": cty.ListVal([]cty.Value{
   943  					fooBlockValue,
   944  				}),
   945  			}),
   946  			cty.ObjectVal(map[string]cty.Value{
   947  				"key": cty.ListVal([]cty.Value{
   948  					fooBlockValue,
   949  				}),
   950  			}),
   951  			nil,
   952  		},
   953  		{
   954  			&configschema.Block{
   955  				BlockTypes: map[string]*configschema.NestedBlock{
   956  					"key": {
   957  						Nesting: configschema.NestingList,
   958  						Block:   schemaWithFoo,
   959  					},
   960  				},
   961  			},
   962  			cty.ObjectVal(map[string]cty.Value{
   963  				"key": cty.TupleVal([]cty.Value{
   964  					fooBlockValue,
   965  					fooBlockValue,
   966  				}),
   967  			}),
   968  			cty.ObjectVal(map[string]cty.Value{
   969  				"key": cty.TupleVal([]cty.Value{
   970  					fooBlockValue,
   971  				}),
   972  			}),
   973  			[]string{
   974  				`.key: block count changed from 2 to 1`,
   975  			},
   976  		},
   977  		{
   978  			&configschema.Block{
   979  				BlockTypes: map[string]*configschema.NestedBlock{
   980  					"key": {
   981  						Nesting: configschema.NestingList,
   982  						Block:   schemaWithFoo,
   983  					},
   984  				},
   985  			},
   986  			cty.ObjectVal(map[string]cty.Value{
   987  				"key": cty.TupleVal([]cty.Value{}),
   988  			}),
   989  			cty.ObjectVal(map[string]cty.Value{
   990  				"key": cty.TupleVal([]cty.Value{
   991  					fooBlockValue,
   992  					fooBlockValue,
   993  				}),
   994  			}),
   995  			[]string{
   996  				`.key: block count changed from 0 to 2`,
   997  			},
   998  		},
   999  		{
  1000  			&configschema.Block{
  1001  				BlockTypes: map[string]*configschema.NestedBlock{
  1002  					"key": {
  1003  						Nesting: configschema.NestingList,
  1004  						Block:   schemaWithFooBar,
  1005  					},
  1006  				},
  1007  			},
  1008  			cty.UnknownVal(cty.Object(map[string]cty.Type{
  1009  				"key": cty.List(fooBarBlockValue.Type()),
  1010  			})),
  1011  			cty.ObjectVal(map[string]cty.Value{
  1012  				"key": cty.ListVal([]cty.Value{
  1013  					cty.ObjectVal(map[string]cty.Value{
  1014  						"foo": cty.StringVal("hello"),
  1015  					}),
  1016  					cty.ObjectVal(map[string]cty.Value{
  1017  						"foo": cty.StringVal("world"),
  1018  					}),
  1019  				}),
  1020  			}),
  1021  			nil, // an unknown block is allowed to expand into multiple, because that's how dynamic blocks behave when for_each is unknown
  1022  		},
  1023  		{
  1024  			&configschema.Block{
  1025  				BlockTypes: map[string]*configschema.NestedBlock{
  1026  					"key": {
  1027  						Nesting: configschema.NestingList,
  1028  						Block:   schemaWithFooBar,
  1029  					},
  1030  				},
  1031  			},
  1032  			// While we must make an exception for empty strings in sets due to
  1033  			// the legacy SDK, lists should be compared more strictly.
  1034  			// This does not count as a dynamic block placeholder
  1035  			cty.ObjectVal(map[string]cty.Value{
  1036  				"key": cty.ListVal([]cty.Value{
  1037  					fooBarBlockValue,
  1038  					cty.ObjectVal(map[string]cty.Value{
  1039  						"foo": cty.UnknownVal(cty.String),
  1040  						"bar": cty.StringVal(""),
  1041  					}),
  1042  				}),
  1043  			}),
  1044  			cty.ObjectVal(map[string]cty.Value{
  1045  				"key": cty.ListVal([]cty.Value{
  1046  					fooBlockValue,
  1047  					cty.ObjectVal(map[string]cty.Value{
  1048  						"foo": cty.StringVal("hello"),
  1049  					}),
  1050  					cty.ObjectVal(map[string]cty.Value{
  1051  						"foo": cty.StringVal("world"),
  1052  					}),
  1053  				}),
  1054  			}),
  1055  			[]string{".key: block count changed from 2 to 3"},
  1056  		},
  1057  
  1058  		// NestingSet blocks
  1059  		{
  1060  			&configschema.Block{
  1061  				BlockTypes: map[string]*configschema.NestedBlock{
  1062  					"block": {
  1063  						Nesting: configschema.NestingSet,
  1064  						Block:   schemaWithFoo,
  1065  					},
  1066  				},
  1067  			},
  1068  			cty.ObjectVal(map[string]cty.Value{
  1069  				"block": cty.SetVal([]cty.Value{
  1070  					cty.ObjectVal(map[string]cty.Value{
  1071  						"foo": cty.StringVal("hello"),
  1072  					}),
  1073  					cty.ObjectVal(map[string]cty.Value{
  1074  						"foo": cty.StringVal("world"),
  1075  					}),
  1076  				}),
  1077  			}),
  1078  			cty.ObjectVal(map[string]cty.Value{
  1079  				"block": cty.SetVal([]cty.Value{
  1080  					cty.ObjectVal(map[string]cty.Value{
  1081  						"foo": cty.StringVal("hello"),
  1082  					}),
  1083  					cty.ObjectVal(map[string]cty.Value{
  1084  						"foo": cty.StringVal("world"),
  1085  					}),
  1086  				}),
  1087  			}),
  1088  			nil,
  1089  		},
  1090  		{
  1091  			&configschema.Block{
  1092  				BlockTypes: map[string]*configschema.NestedBlock{
  1093  					"block": {
  1094  						Nesting: configschema.NestingSet,
  1095  						Block:   schemaWithFoo,
  1096  					},
  1097  				},
  1098  			},
  1099  			cty.ObjectVal(map[string]cty.Value{
  1100  				"block": cty.SetVal([]cty.Value{
  1101  					cty.ObjectVal(map[string]cty.Value{
  1102  						"foo": cty.UnknownVal(cty.String),
  1103  					}),
  1104  					cty.ObjectVal(map[string]cty.Value{
  1105  						"foo": cty.UnknownVal(cty.String),
  1106  					}),
  1107  				}),
  1108  			}),
  1109  			cty.ObjectVal(map[string]cty.Value{
  1110  				"block": cty.SetVal([]cty.Value{
  1111  					// This is testing the scenario where the two unknown values
  1112  					// turned out to be equal after we learned their values,
  1113  					// and so they coalesced together into a single element.
  1114  					cty.ObjectVal(map[string]cty.Value{
  1115  						"foo": cty.StringVal("hello"),
  1116  					}),
  1117  				}),
  1118  			}),
  1119  			nil,
  1120  		},
  1121  		{
  1122  			&configschema.Block{
  1123  				BlockTypes: map[string]*configschema.NestedBlock{
  1124  					"block": {
  1125  						Nesting: configschema.NestingSet,
  1126  						Block:   schemaWithFoo,
  1127  					},
  1128  				},
  1129  			},
  1130  			cty.ObjectVal(map[string]cty.Value{
  1131  				"block": cty.SetVal([]cty.Value{
  1132  					cty.ObjectVal(map[string]cty.Value{
  1133  						"foo": cty.UnknownVal(cty.String),
  1134  					}),
  1135  					cty.ObjectVal(map[string]cty.Value{
  1136  						"foo": cty.UnknownVal(cty.String),
  1137  					}),
  1138  				}),
  1139  			}),
  1140  			cty.ObjectVal(map[string]cty.Value{
  1141  				"block": cty.SetVal([]cty.Value{
  1142  					cty.ObjectVal(map[string]cty.Value{
  1143  						"foo": cty.StringVal("hello"),
  1144  					}),
  1145  					cty.ObjectVal(map[string]cty.Value{
  1146  						"foo": cty.StringVal("world"),
  1147  					}),
  1148  				}),
  1149  			}),
  1150  			nil,
  1151  		},
  1152  		{
  1153  			&configschema.Block{
  1154  				BlockTypes: map[string]*configschema.NestedBlock{
  1155  					"block": {
  1156  						Nesting: configschema.NestingSet,
  1157  						Block:   schemaWithFoo,
  1158  					},
  1159  				},
  1160  			},
  1161  			cty.ObjectVal(map[string]cty.Value{
  1162  				"block": cty.UnknownVal(cty.Set(
  1163  					cty.Object(map[string]cty.Type{
  1164  						"foo": cty.String,
  1165  					}),
  1166  				)),
  1167  			}),
  1168  			cty.ObjectVal(map[string]cty.Value{
  1169  				"block": cty.SetVal([]cty.Value{
  1170  					cty.ObjectVal(map[string]cty.Value{
  1171  						"foo": cty.StringVal("hello"),
  1172  					}),
  1173  					cty.ObjectVal(map[string]cty.Value{
  1174  						"foo": cty.StringVal("world"),
  1175  					}),
  1176  					cty.ObjectVal(map[string]cty.Value{
  1177  						"foo": cty.StringVal("nope"),
  1178  					}),
  1179  				}),
  1180  			}),
  1181  			// there is no error here, because the presence of unknowns
  1182  			// indicates this may be a dynamic block, and the length is unknown
  1183  			nil,
  1184  		},
  1185  		{
  1186  			&configschema.Block{
  1187  				BlockTypes: map[string]*configschema.NestedBlock{
  1188  					"block": {
  1189  						Nesting: configschema.NestingSet,
  1190  						Block:   schemaWithFoo,
  1191  					},
  1192  				},
  1193  			},
  1194  			cty.ObjectVal(map[string]cty.Value{
  1195  				"block": cty.SetVal([]cty.Value{
  1196  					cty.ObjectVal(map[string]cty.Value{
  1197  						"foo": cty.StringVal("hello"),
  1198  					}),
  1199  					cty.ObjectVal(map[string]cty.Value{
  1200  						"foo": cty.StringVal("world"),
  1201  					}),
  1202  				}),
  1203  			}),
  1204  			cty.ObjectVal(map[string]cty.Value{
  1205  				"block": cty.SetVal([]cty.Value{
  1206  					cty.ObjectVal(map[string]cty.Value{
  1207  						"foo": cty.StringVal("howdy"),
  1208  					}),
  1209  					cty.ObjectVal(map[string]cty.Value{
  1210  						"foo": cty.StringVal("world"),
  1211  					}),
  1212  				}),
  1213  			}),
  1214  			[]string{
  1215  				`.block: planned set element cty.ObjectVal(map[string]cty.Value{"foo":cty.StringVal("hello")}) does not correlate with any element in actual`,
  1216  			},
  1217  		},
  1218  		{
  1219  			// This one is an odd situation where the value representing the
  1220  			// block itself is unknown. This is never supposed to be true,
  1221  			// but in legacy SDK mode we allow such things to pass through as
  1222  			// a warning, and so we must tolerate them for matching purposes.
  1223  			&configschema.Block{
  1224  				BlockTypes: map[string]*configschema.NestedBlock{
  1225  					"block": {
  1226  						Nesting: configschema.NestingSet,
  1227  						Block:   schemaWithFoo,
  1228  					},
  1229  				},
  1230  			},
  1231  			cty.ObjectVal(map[string]cty.Value{
  1232  				"block": cty.SetVal([]cty.Value{
  1233  					cty.ObjectVal(map[string]cty.Value{
  1234  						"foo": cty.UnknownVal(cty.String),
  1235  					}),
  1236  					cty.ObjectVal(map[string]cty.Value{
  1237  						"foo": cty.UnknownVal(cty.String),
  1238  					}),
  1239  				}),
  1240  			}),
  1241  			cty.ObjectVal(map[string]cty.Value{
  1242  				"block": cty.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{
  1243  					"foo": cty.String,
  1244  				}))),
  1245  			}),
  1246  			nil,
  1247  		},
  1248  		{
  1249  			&configschema.Block{
  1250  				BlockTypes: map[string]*configschema.NestedBlock{
  1251  					"block": {
  1252  						Nesting: configschema.NestingSet,
  1253  						Block:   schemaWithFoo,
  1254  					},
  1255  				},
  1256  			},
  1257  			cty.ObjectVal(map[string]cty.Value{
  1258  				"block": cty.UnknownVal(cty.Set(fooBlockValue.Type())),
  1259  			}),
  1260  			cty.ObjectVal(map[string]cty.Value{
  1261  				"block": cty.SetVal([]cty.Value{
  1262  					cty.ObjectVal(map[string]cty.Value{
  1263  						"foo": cty.StringVal("a"),
  1264  					}),
  1265  					cty.ObjectVal(map[string]cty.Value{
  1266  						"foo": cty.StringVal("b"),
  1267  					}),
  1268  				}),
  1269  			}),
  1270  			nil,
  1271  		},
  1272  		// test a set with an unknown dynamic count going to 0 values
  1273  		{
  1274  			&configschema.Block{
  1275  				BlockTypes: map[string]*configschema.NestedBlock{
  1276  					"block2": {
  1277  						Nesting: configschema.NestingSet,
  1278  						Block:   schemaWithFoo,
  1279  					},
  1280  				},
  1281  			},
  1282  			cty.ObjectVal(map[string]cty.Value{
  1283  				"block2": cty.UnknownVal(cty.Set(fooBlockValue.Type())),
  1284  			}),
  1285  			cty.ObjectVal(map[string]cty.Value{
  1286  				"block2": cty.SetValEmpty(cty.Object(map[string]cty.Type{
  1287  					"foo": cty.String,
  1288  				})),
  1289  			}),
  1290  			nil,
  1291  		},
  1292  		// test a set with a patially known dynamic count reducing it's values
  1293  		{
  1294  			&configschema.Block{
  1295  				BlockTypes: map[string]*configschema.NestedBlock{
  1296  					"block3": {
  1297  						Nesting: configschema.NestingSet,
  1298  						Block:   schemaWithFoo,
  1299  					},
  1300  				},
  1301  			},
  1302  			cty.ObjectVal(map[string]cty.Value{
  1303  				"block3": cty.SetVal([]cty.Value{
  1304  					cty.ObjectVal(map[string]cty.Value{
  1305  						"foo": cty.StringVal("a"),
  1306  					}),
  1307  					cty.ObjectVal(map[string]cty.Value{
  1308  						"foo": cty.UnknownVal(cty.String),
  1309  					}),
  1310  				}),
  1311  			}),
  1312  			cty.ObjectVal(map[string]cty.Value{
  1313  				"block3": cty.SetVal([]cty.Value{
  1314  					cty.ObjectVal(map[string]cty.Value{
  1315  						"foo": cty.StringVal("a"),
  1316  					}),
  1317  				}),
  1318  			}),
  1319  			nil,
  1320  		},
  1321  		{
  1322  			&configschema.Block{
  1323  				BlockTypes: map[string]*configschema.NestedBlock{
  1324  					"block": {
  1325  						Nesting: configschema.NestingList,
  1326  						Block: configschema.Block{
  1327  							Attributes: map[string]*configschema.Attribute{
  1328  								"foo": {
  1329  									Type:     cty.String,
  1330  									Required: true,
  1331  								},
  1332  							},
  1333  						},
  1334  					},
  1335  				},
  1336  			},
  1337  			cty.ObjectVal(map[string]cty.Value{
  1338  				"block": cty.EmptyObjectVal,
  1339  			}),
  1340  			cty.ObjectVal(map[string]cty.Value{
  1341  				"block": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{
  1342  					"foo": cty.String,
  1343  				}))),
  1344  			}),
  1345  			nil,
  1346  		},
  1347  	}
  1348  
  1349  	for i, test := range tests {
  1350  		t.Run(fmt.Sprintf("%02d: %#v and %#v", i, test.Planned, test.Actual), func(t *testing.T) {
  1351  			errs := AssertObjectCompatible(test.Schema, test.Planned, test.Actual)
  1352  
  1353  			wantErrs := make(map[string]struct{})
  1354  			gotErrs := make(map[string]struct{})
  1355  			for _, err := range errs {
  1356  				gotErrs[tfdiags.FormatError(err)] = struct{}{}
  1357  			}
  1358  			for _, msg := range test.WantErrs {
  1359  				wantErrs[msg] = struct{}{}
  1360  			}
  1361  
  1362  			t.Logf("\nplanned: %sactual:  %s", dump.Value(test.Planned), dump.Value(test.Actual))
  1363  			for msg := range wantErrs {
  1364  				if _, ok := gotErrs[msg]; !ok {
  1365  					t.Errorf("missing expected error: %s", msg)
  1366  				}
  1367  			}
  1368  			for msg := range gotErrs {
  1369  				if _, ok := wantErrs[msg]; !ok {
  1370  					t.Errorf("unexpected extra error: %s", msg)
  1371  				}
  1372  			}
  1373  		})
  1374  	}
  1375  }