github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/plans/objchange/plan_valid_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package objchange
     5  
     6  import (
     7  	"testing"
     8  
     9  	"github.com/apparentlymart/go-dump/dump"
    10  	"github.com/zclconf/go-cty/cty"
    11  
    12  	"github.com/terramate-io/tf/configs/configschema"
    13  	"github.com/terramate-io/tf/tfdiags"
    14  )
    15  
    16  func TestAssertPlanValid(t *testing.T) {
    17  	tests := map[string]struct {
    18  		Schema   *configschema.Block
    19  		Prior    cty.Value
    20  		Config   cty.Value
    21  		Planned  cty.Value
    22  		WantErrs []string
    23  	}{
    24  		"all empty": {
    25  			&configschema.Block{},
    26  			cty.EmptyObjectVal,
    27  			cty.EmptyObjectVal,
    28  			cty.EmptyObjectVal,
    29  			nil,
    30  		},
    31  		"no computed, all match": {
    32  			&configschema.Block{
    33  				Attributes: map[string]*configschema.Attribute{
    34  					"a": {
    35  						Type:     cty.String,
    36  						Optional: true,
    37  					},
    38  				},
    39  				BlockTypes: map[string]*configschema.NestedBlock{
    40  					"b": {
    41  						Nesting: configschema.NestingList,
    42  						Block: configschema.Block{
    43  							Attributes: map[string]*configschema.Attribute{
    44  								"c": {
    45  									Type:     cty.String,
    46  									Optional: true,
    47  								},
    48  							},
    49  						},
    50  					},
    51  				},
    52  			},
    53  			cty.ObjectVal(map[string]cty.Value{
    54  				"a": cty.StringVal("a value"),
    55  				"b": cty.ListVal([]cty.Value{
    56  					cty.ObjectVal(map[string]cty.Value{
    57  						"c": cty.StringVal("c value"),
    58  					}),
    59  				}),
    60  			}),
    61  			cty.ObjectVal(map[string]cty.Value{
    62  				"a": cty.StringVal("a value"),
    63  				"b": cty.ListVal([]cty.Value{
    64  					cty.ObjectVal(map[string]cty.Value{
    65  						"c": cty.StringVal("c value"),
    66  					}),
    67  				}),
    68  			}),
    69  			cty.ObjectVal(map[string]cty.Value{
    70  				"a": cty.StringVal("a value"),
    71  				"b": cty.ListVal([]cty.Value{
    72  					cty.ObjectVal(map[string]cty.Value{
    73  						"c": cty.StringVal("c value"),
    74  					}),
    75  				}),
    76  			}),
    77  			nil,
    78  		},
    79  		"no computed, plan matches, no prior": {
    80  			&configschema.Block{
    81  				Attributes: map[string]*configschema.Attribute{
    82  					"a": {
    83  						Type:     cty.String,
    84  						Optional: true,
    85  					},
    86  				},
    87  				BlockTypes: map[string]*configschema.NestedBlock{
    88  					"b": {
    89  						Nesting: configschema.NestingList,
    90  						Block: configschema.Block{
    91  							Attributes: map[string]*configschema.Attribute{
    92  								"c": {
    93  									Type:     cty.String,
    94  									Optional: true,
    95  								},
    96  							},
    97  						},
    98  					},
    99  				},
   100  			},
   101  			cty.NullVal(cty.Object(map[string]cty.Type{
   102  				"a": cty.String,
   103  				"b": cty.List(cty.Object(map[string]cty.Type{
   104  					"c": cty.String,
   105  				})),
   106  			})),
   107  			cty.ObjectVal(map[string]cty.Value{
   108  				"a": cty.StringVal("a value"),
   109  				"b": cty.ListVal([]cty.Value{
   110  					cty.ObjectVal(map[string]cty.Value{
   111  						"c": cty.StringVal("c value"),
   112  					}),
   113  				}),
   114  			}),
   115  			cty.ObjectVal(map[string]cty.Value{
   116  				"a": cty.StringVal("a value"),
   117  				"b": cty.ListVal([]cty.Value{
   118  					cty.ObjectVal(map[string]cty.Value{
   119  						"c": cty.StringVal("c value"),
   120  					}),
   121  				}),
   122  			}),
   123  			nil,
   124  		},
   125  		"no computed, invalid change in plan": {
   126  			&configschema.Block{
   127  				Attributes: map[string]*configschema.Attribute{
   128  					"a": {
   129  						Type:     cty.String,
   130  						Optional: true,
   131  					},
   132  				},
   133  				BlockTypes: map[string]*configschema.NestedBlock{
   134  					"b": {
   135  						Nesting: configschema.NestingList,
   136  						Block: configschema.Block{
   137  							Attributes: map[string]*configschema.Attribute{
   138  								"c": {
   139  									Type:     cty.String,
   140  									Optional: true,
   141  								},
   142  							},
   143  						},
   144  					},
   145  				},
   146  			},
   147  			cty.NullVal(cty.Object(map[string]cty.Type{
   148  				"a": cty.String,
   149  				"b": cty.List(cty.Object(map[string]cty.Type{
   150  					"c": cty.String,
   151  				})),
   152  			})),
   153  			cty.ObjectVal(map[string]cty.Value{
   154  				"a": cty.StringVal("a value"),
   155  				"b": cty.ListVal([]cty.Value{
   156  					cty.ObjectVal(map[string]cty.Value{
   157  						"c": cty.StringVal("c value"),
   158  					}),
   159  				}),
   160  			}),
   161  			cty.ObjectVal(map[string]cty.Value{
   162  				"a": cty.StringVal("a value"),
   163  				"b": cty.ListVal([]cty.Value{
   164  					cty.ObjectVal(map[string]cty.Value{
   165  						"c": cty.StringVal("new c value"),
   166  					}),
   167  				}),
   168  			}),
   169  			[]string{
   170  				`.b[0].c: planned value cty.StringVal("new c value") does not match config value cty.StringVal("c value")`,
   171  			},
   172  		},
   173  		"no computed, invalid change in plan sensitive": {
   174  			&configschema.Block{
   175  				Attributes: map[string]*configschema.Attribute{
   176  					"a": {
   177  						Type:     cty.String,
   178  						Optional: true,
   179  					},
   180  				},
   181  				BlockTypes: map[string]*configschema.NestedBlock{
   182  					"b": {
   183  						Nesting: configschema.NestingList,
   184  						Block: configschema.Block{
   185  							Attributes: map[string]*configschema.Attribute{
   186  								"c": {
   187  									Type:      cty.String,
   188  									Optional:  true,
   189  									Sensitive: true,
   190  								},
   191  							},
   192  						},
   193  					},
   194  				},
   195  			},
   196  			cty.NullVal(cty.Object(map[string]cty.Type{
   197  				"a": cty.String,
   198  				"b": cty.List(cty.Object(map[string]cty.Type{
   199  					"c": cty.String,
   200  				})),
   201  			})),
   202  			cty.ObjectVal(map[string]cty.Value{
   203  				"a": cty.StringVal("a value"),
   204  				"b": cty.ListVal([]cty.Value{
   205  					cty.ObjectVal(map[string]cty.Value{
   206  						"c": cty.StringVal("c value"),
   207  					}),
   208  				}),
   209  			}),
   210  			cty.ObjectVal(map[string]cty.Value{
   211  				"a": cty.StringVal("a value"),
   212  				"b": cty.ListVal([]cty.Value{
   213  					cty.ObjectVal(map[string]cty.Value{
   214  						"c": cty.StringVal("new c value"),
   215  					}),
   216  				}),
   217  			}),
   218  			[]string{
   219  				`.b[0].c: sensitive planned value does not match config value`,
   220  			},
   221  		},
   222  		"no computed, diff suppression in plan": {
   223  			&configschema.Block{
   224  				Attributes: map[string]*configschema.Attribute{
   225  					"a": {
   226  						Type:     cty.String,
   227  						Optional: true,
   228  					},
   229  				},
   230  				BlockTypes: map[string]*configschema.NestedBlock{
   231  					"b": {
   232  						Nesting: configschema.NestingList,
   233  						Block: configschema.Block{
   234  							Attributes: map[string]*configschema.Attribute{
   235  								"c": {
   236  									Type:     cty.String,
   237  									Optional: true,
   238  								},
   239  							},
   240  						},
   241  					},
   242  				},
   243  			},
   244  			cty.ObjectVal(map[string]cty.Value{
   245  				"a": cty.StringVal("a value"),
   246  				"b": cty.ListVal([]cty.Value{
   247  					cty.ObjectVal(map[string]cty.Value{
   248  						"c": cty.StringVal("c value"),
   249  					}),
   250  				}),
   251  			}),
   252  			cty.ObjectVal(map[string]cty.Value{
   253  				"a": cty.StringVal("a value"),
   254  				"b": cty.ListVal([]cty.Value{
   255  					cty.ObjectVal(map[string]cty.Value{
   256  						"c": cty.StringVal("new c value"),
   257  					}),
   258  				}),
   259  			}),
   260  			cty.ObjectVal(map[string]cty.Value{
   261  				"a": cty.StringVal("a value"),
   262  				"b": cty.ListVal([]cty.Value{
   263  					cty.ObjectVal(map[string]cty.Value{
   264  						"c": cty.StringVal("c value"), // plan uses value from prior object
   265  					}),
   266  				}),
   267  			}),
   268  			nil,
   269  		},
   270  		"no computed, all null": {
   271  			&configschema.Block{
   272  				Attributes: map[string]*configschema.Attribute{
   273  					"a": {
   274  						Type:     cty.String,
   275  						Optional: true,
   276  					},
   277  				},
   278  				BlockTypes: map[string]*configschema.NestedBlock{
   279  					"b": {
   280  						Nesting: configschema.NestingList,
   281  						Block: configschema.Block{
   282  							Attributes: map[string]*configschema.Attribute{
   283  								"c": {
   284  									Type:     cty.String,
   285  									Optional: true,
   286  								},
   287  							},
   288  						},
   289  					},
   290  				},
   291  			},
   292  			cty.ObjectVal(map[string]cty.Value{
   293  				"a": cty.NullVal(cty.String),
   294  				"b": cty.ListVal([]cty.Value{
   295  					cty.ObjectVal(map[string]cty.Value{
   296  						"c": cty.NullVal(cty.String),
   297  					}),
   298  				}),
   299  			}),
   300  			cty.ObjectVal(map[string]cty.Value{
   301  				"a": cty.NullVal(cty.String),
   302  				"b": cty.ListVal([]cty.Value{
   303  					cty.ObjectVal(map[string]cty.Value{
   304  						"c": cty.NullVal(cty.String),
   305  					}),
   306  				}),
   307  			}),
   308  			cty.ObjectVal(map[string]cty.Value{
   309  				"a": cty.NullVal(cty.String),
   310  				"b": cty.ListVal([]cty.Value{
   311  					cty.ObjectVal(map[string]cty.Value{
   312  						"c": cty.NullVal(cty.String),
   313  					}),
   314  				}),
   315  			}),
   316  			nil,
   317  		},
   318  		"nested map, normal update": {
   319  			&configschema.Block{
   320  				BlockTypes: map[string]*configschema.NestedBlock{
   321  					"b": {
   322  						Nesting: configschema.NestingMap,
   323  						Block: configschema.Block{
   324  							Attributes: map[string]*configschema.Attribute{
   325  								"c": {
   326  									Type:     cty.String,
   327  									Optional: true,
   328  								},
   329  							},
   330  						},
   331  					},
   332  				},
   333  			},
   334  			cty.ObjectVal(map[string]cty.Value{
   335  				"b": cty.MapVal(map[string]cty.Value{
   336  					"boop": cty.ObjectVal(map[string]cty.Value{
   337  						"c": cty.StringVal("hello"),
   338  					}),
   339  				}),
   340  			}),
   341  			cty.ObjectVal(map[string]cty.Value{
   342  				"b": cty.MapVal(map[string]cty.Value{
   343  					"boop": cty.ObjectVal(map[string]cty.Value{
   344  						"c": cty.StringVal("howdy"),
   345  					}),
   346  				}),
   347  			}),
   348  			cty.ObjectVal(map[string]cty.Value{
   349  				"b": cty.MapVal(map[string]cty.Value{
   350  					"boop": cty.ObjectVal(map[string]cty.Value{
   351  						"c": cty.StringVal("howdy"),
   352  					}),
   353  				}),
   354  			}),
   355  			nil,
   356  		},
   357  
   358  		// Nested block collections are never null
   359  		"nested list, null in plan": {
   360  			&configschema.Block{
   361  				BlockTypes: map[string]*configschema.NestedBlock{
   362  					"b": {
   363  						Nesting: configschema.NestingList,
   364  						Block: configschema.Block{
   365  							Attributes: map[string]*configschema.Attribute{
   366  								"c": {
   367  									Type:     cty.String,
   368  									Optional: true,
   369  								},
   370  							},
   371  						},
   372  					},
   373  				},
   374  			},
   375  			cty.NullVal(cty.Object(map[string]cty.Type{
   376  				"b": cty.List(cty.Object(map[string]cty.Type{
   377  					"c": cty.String,
   378  				})),
   379  			})),
   380  			cty.ObjectVal(map[string]cty.Value{
   381  				"b": cty.ListValEmpty(cty.Object(map[string]cty.Type{
   382  					"c": cty.String,
   383  				})),
   384  			}),
   385  			cty.ObjectVal(map[string]cty.Value{
   386  				"b": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
   387  					"c": cty.String,
   388  				}))),
   389  			}),
   390  			[]string{
   391  				`.b: attribute representing a list of nested blocks must be empty to indicate no blocks, not null`,
   392  			},
   393  		},
   394  
   395  		// but don't panic on a null list just in case
   396  		"nested list, null in config": {
   397  			&configschema.Block{
   398  				BlockTypes: map[string]*configschema.NestedBlock{
   399  					"b": {
   400  						Nesting: configschema.NestingList,
   401  						Block: configschema.Block{
   402  							Attributes: map[string]*configschema.Attribute{
   403  								"c": {
   404  									Type:     cty.String,
   405  									Optional: true,
   406  								},
   407  							},
   408  						},
   409  					},
   410  				},
   411  			},
   412  			cty.ObjectVal(map[string]cty.Value{
   413  				"b": cty.ListValEmpty(cty.Object(map[string]cty.Type{
   414  					"c": cty.String,
   415  				})),
   416  			}),
   417  			cty.ObjectVal(map[string]cty.Value{
   418  				"b": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
   419  					"c": cty.String,
   420  				}))),
   421  			}),
   422  			cty.ObjectVal(map[string]cty.Value{
   423  				"b": cty.ListValEmpty(cty.Object(map[string]cty.Type{
   424  					"c": cty.String,
   425  				})),
   426  			}),
   427  			nil,
   428  		},
   429  
   430  		// blocks can be unknown when using dynamic
   431  		"nested list, unknown nested dynamic": {
   432  			&configschema.Block{
   433  				BlockTypes: map[string]*configschema.NestedBlock{
   434  					"a": {
   435  						Nesting: configschema.NestingList,
   436  						Block: configschema.Block{
   437  							BlockTypes: map[string]*configschema.NestedBlock{
   438  								"b": {
   439  									Nesting: configschema.NestingList,
   440  									Block: configschema.Block{
   441  										Attributes: map[string]*configschema.Attribute{
   442  											"c": {
   443  												Type:     cty.String,
   444  												Optional: true,
   445  											},
   446  											"computed": {
   447  												Type:     cty.String,
   448  												Computed: true,
   449  											},
   450  										},
   451  									},
   452  								},
   453  							},
   454  						},
   455  					},
   456  				},
   457  			},
   458  
   459  			cty.ObjectVal(map[string]cty.Value{
   460  				"a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
   461  					"computed": cty.NullVal(cty.String),
   462  					"b": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
   463  						"c": cty.StringVal("x"),
   464  					})}),
   465  				})}),
   466  			}),
   467  			cty.ObjectVal(map[string]cty.Value{
   468  				"a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
   469  					"b": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{
   470  						"c":        cty.String,
   471  						"computed": cty.String,
   472  					}))),
   473  				})}),
   474  			}),
   475  			cty.ObjectVal(map[string]cty.Value{
   476  				"a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
   477  					"b": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{
   478  						"c":        cty.String,
   479  						"computed": cty.String,
   480  					}))),
   481  				})}),
   482  			}),
   483  			[]string{},
   484  		},
   485  
   486  		"nested set, unknown dynamic cannot be planned": {
   487  			&configschema.Block{
   488  				Attributes: map[string]*configschema.Attribute{
   489  					"computed": {
   490  						Type:     cty.String,
   491  						Computed: true,
   492  					},
   493  				},
   494  				BlockTypes: map[string]*configschema.NestedBlock{
   495  					"b": {
   496  						Nesting: configschema.NestingSet,
   497  						Block: configschema.Block{
   498  							Attributes: map[string]*configschema.Attribute{
   499  								"c": {
   500  									Type:     cty.String,
   501  									Optional: true,
   502  								},
   503  							},
   504  						},
   505  					},
   506  				},
   507  			},
   508  
   509  			cty.ObjectVal(map[string]cty.Value{
   510  				"computed": cty.NullVal(cty.String),
   511  				"b": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
   512  					"c": cty.StringVal("x"),
   513  				})}),
   514  			}),
   515  			cty.ObjectVal(map[string]cty.Value{
   516  				"computed": cty.NullVal(cty.String),
   517  				"b": cty.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{
   518  					"c": cty.String,
   519  				}))),
   520  			}),
   521  			cty.ObjectVal(map[string]cty.Value{
   522  				"computed": cty.StringVal("default"),
   523  				"b": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
   524  					"c": cty.StringVal("oops"),
   525  				})}),
   526  			}),
   527  
   528  			[]string{
   529  				`.b: planned value cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"c":cty.StringVal("oops")})}) for unknown dynamic block`,
   530  			},
   531  		},
   532  
   533  		"nested set, null in plan": {
   534  			&configschema.Block{
   535  				BlockTypes: map[string]*configschema.NestedBlock{
   536  					"b": {
   537  						Nesting: configschema.NestingSet,
   538  						Block: configschema.Block{
   539  							Attributes: map[string]*configschema.Attribute{
   540  								"c": {
   541  									Type:     cty.String,
   542  									Optional: true,
   543  								},
   544  							},
   545  						},
   546  					},
   547  				},
   548  			},
   549  			cty.NullVal(cty.Object(map[string]cty.Type{
   550  				"b": cty.Set(cty.Object(map[string]cty.Type{
   551  					"c": cty.String,
   552  				})),
   553  			})),
   554  			cty.ObjectVal(map[string]cty.Value{
   555  				"b": cty.SetValEmpty(cty.Object(map[string]cty.Type{
   556  					"c": cty.String,
   557  				})),
   558  			}),
   559  			cty.ObjectVal(map[string]cty.Value{
   560  				"b": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
   561  					"c": cty.String,
   562  				}))),
   563  			}),
   564  			[]string{
   565  				`.b: attribute representing a set of nested blocks must be empty to indicate no blocks, not null`,
   566  			},
   567  		},
   568  		"nested map, null in plan": {
   569  			&configschema.Block{
   570  				BlockTypes: map[string]*configschema.NestedBlock{
   571  					"b": {
   572  						Nesting: configschema.NestingMap,
   573  						Block: configschema.Block{
   574  							Attributes: map[string]*configschema.Attribute{
   575  								"c": {
   576  									Type:     cty.String,
   577  									Optional: true,
   578  								},
   579  							},
   580  						},
   581  					},
   582  				},
   583  			},
   584  			cty.NullVal(cty.Object(map[string]cty.Type{
   585  				"b": cty.Map(cty.Object(map[string]cty.Type{
   586  					"c": cty.String,
   587  				})),
   588  			})),
   589  			cty.ObjectVal(map[string]cty.Value{
   590  				"b": cty.MapValEmpty(cty.Object(map[string]cty.Type{
   591  					"c": cty.String,
   592  				})),
   593  			}),
   594  			cty.ObjectVal(map[string]cty.Value{
   595  				"b": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{
   596  					"c": cty.String,
   597  				}))),
   598  			}),
   599  			[]string{
   600  				`.b: attribute representing a map of nested blocks must be empty to indicate no blocks, not null`,
   601  			},
   602  		},
   603  
   604  		// We don't actually do any validation for nested set blocks, and so
   605  		// the remaining cases here are just intending to ensure we don't
   606  		// inadvertently start generating errors incorrectly in future.
   607  		"nested set, no computed, no changes": {
   608  			&configschema.Block{
   609  				BlockTypes: map[string]*configschema.NestedBlock{
   610  					"b": {
   611  						Nesting: configschema.NestingSet,
   612  						Block: configschema.Block{
   613  							Attributes: map[string]*configschema.Attribute{
   614  								"c": {
   615  									Type:     cty.String,
   616  									Optional: true,
   617  								},
   618  							},
   619  						},
   620  					},
   621  				},
   622  			},
   623  			cty.ObjectVal(map[string]cty.Value{
   624  				"b": cty.SetVal([]cty.Value{
   625  					cty.ObjectVal(map[string]cty.Value{
   626  						"c": cty.StringVal("c value"),
   627  					}),
   628  				}),
   629  			}),
   630  			cty.ObjectVal(map[string]cty.Value{
   631  				"b": cty.SetVal([]cty.Value{
   632  					cty.ObjectVal(map[string]cty.Value{
   633  						"c": cty.StringVal("c value"),
   634  					}),
   635  				}),
   636  			}),
   637  			cty.ObjectVal(map[string]cty.Value{
   638  				"b": cty.SetVal([]cty.Value{
   639  					cty.ObjectVal(map[string]cty.Value{
   640  						"c": cty.StringVal("c value"),
   641  					}),
   642  				}),
   643  			}),
   644  			nil,
   645  		},
   646  		"nested set, no computed, invalid change in plan": {
   647  			&configschema.Block{
   648  				BlockTypes: map[string]*configschema.NestedBlock{
   649  					"b": {
   650  						Nesting: configschema.NestingSet,
   651  						Block: configschema.Block{
   652  							Attributes: map[string]*configschema.Attribute{
   653  								"c": {
   654  									Type:     cty.String,
   655  									Optional: true,
   656  								},
   657  							},
   658  						},
   659  					},
   660  				},
   661  			},
   662  			cty.ObjectVal(map[string]cty.Value{
   663  				"b": cty.SetVal([]cty.Value{
   664  					cty.ObjectVal(map[string]cty.Value{
   665  						"c": cty.StringVal("c value"),
   666  					}),
   667  				}),
   668  			}),
   669  			cty.ObjectVal(map[string]cty.Value{
   670  				"b": cty.SetVal([]cty.Value{
   671  					cty.ObjectVal(map[string]cty.Value{
   672  						"c": cty.StringVal("c value"),
   673  					}),
   674  				}),
   675  			}),
   676  			cty.ObjectVal(map[string]cty.Value{
   677  				"b": cty.SetVal([]cty.Value{
   678  					cty.ObjectVal(map[string]cty.Value{
   679  						"c": cty.StringVal("new c value"), // matches neither prior nor config
   680  					}),
   681  				}),
   682  			}),
   683  			nil,
   684  		},
   685  		"nested set, no computed, diff suppressed": {
   686  			&configschema.Block{
   687  				BlockTypes: map[string]*configschema.NestedBlock{
   688  					"b": {
   689  						Nesting: configschema.NestingSet,
   690  						Block: configschema.Block{
   691  							Attributes: map[string]*configschema.Attribute{
   692  								"c": {
   693  									Type:     cty.String,
   694  									Optional: true,
   695  								},
   696  							},
   697  						},
   698  					},
   699  				},
   700  			},
   701  			cty.ObjectVal(map[string]cty.Value{
   702  				"b": cty.SetVal([]cty.Value{
   703  					cty.ObjectVal(map[string]cty.Value{
   704  						"c": cty.StringVal("c value"),
   705  					}),
   706  				}),
   707  			}),
   708  			cty.ObjectVal(map[string]cty.Value{
   709  				"b": cty.SetVal([]cty.Value{
   710  					cty.ObjectVal(map[string]cty.Value{
   711  						"c": cty.StringVal("new c value"),
   712  					}),
   713  				}),
   714  			}),
   715  			cty.ObjectVal(map[string]cty.Value{
   716  				"b": cty.SetVal([]cty.Value{
   717  					cty.ObjectVal(map[string]cty.Value{
   718  						"c": cty.StringVal("c value"), // plan uses value from prior object
   719  					}),
   720  				}),
   721  			}),
   722  			nil,
   723  		},
   724  
   725  		// Attributes with NestedTypes
   726  		"NestedType attr, no computed, all match": {
   727  			&configschema.Block{
   728  				Attributes: map[string]*configschema.Attribute{
   729  					"a": {
   730  						NestedType: &configschema.Object{
   731  							Nesting: configschema.NestingList,
   732  							Attributes: map[string]*configschema.Attribute{
   733  								"b": {
   734  									Type:     cty.String,
   735  									Optional: true,
   736  								},
   737  							},
   738  						},
   739  						Optional: true,
   740  					},
   741  				},
   742  			},
   743  			cty.ObjectVal(map[string]cty.Value{
   744  				"a": cty.ListVal([]cty.Value{
   745  					cty.ObjectVal(map[string]cty.Value{
   746  						"b": cty.StringVal("b value"),
   747  					}),
   748  				}),
   749  			}),
   750  			cty.ObjectVal(map[string]cty.Value{
   751  				"a": cty.ListVal([]cty.Value{
   752  					cty.ObjectVal(map[string]cty.Value{
   753  						"b": cty.StringVal("b value"),
   754  					}),
   755  				}),
   756  			}),
   757  			cty.ObjectVal(map[string]cty.Value{
   758  				"a": cty.ListVal([]cty.Value{
   759  					cty.ObjectVal(map[string]cty.Value{
   760  						"b": cty.StringVal("b value"),
   761  					}),
   762  				}),
   763  			}),
   764  			nil,
   765  		},
   766  		"NestedType attr, no computed, plan matches, no prior": {
   767  			&configschema.Block{
   768  				Attributes: map[string]*configschema.Attribute{
   769  					"a": {
   770  						NestedType: &configschema.Object{
   771  							Nesting: configschema.NestingList,
   772  							Attributes: map[string]*configschema.Attribute{
   773  								"b": {
   774  									Type:     cty.String,
   775  									Optional: true,
   776  								},
   777  							},
   778  						},
   779  						Optional: true,
   780  					},
   781  				},
   782  			},
   783  			cty.NullVal(cty.Object(map[string]cty.Type{
   784  				"a": cty.List(cty.Object(map[string]cty.Type{
   785  					"b": cty.String,
   786  				})),
   787  			})),
   788  			cty.ObjectVal(map[string]cty.Value{
   789  				"a": cty.ListVal([]cty.Value{
   790  					cty.ObjectVal(map[string]cty.Value{
   791  						"b": cty.StringVal("c value"),
   792  					}),
   793  				}),
   794  			}),
   795  			cty.ObjectVal(map[string]cty.Value{
   796  				"a": cty.ListVal([]cty.Value{
   797  					cty.ObjectVal(map[string]cty.Value{
   798  						"b": cty.StringVal("c value"),
   799  					}),
   800  				}),
   801  			}),
   802  			nil,
   803  		},
   804  		"NestedType, no computed, invalid change in plan": {
   805  			&configschema.Block{
   806  				Attributes: map[string]*configschema.Attribute{
   807  					"a": {
   808  						NestedType: &configschema.Object{
   809  							Nesting: configschema.NestingList,
   810  							Attributes: map[string]*configschema.Attribute{
   811  								"b": {
   812  									Type:     cty.String,
   813  									Optional: true,
   814  								},
   815  							},
   816  						},
   817  						Optional: true,
   818  					},
   819  				},
   820  			},
   821  			cty.NullVal(cty.Object(map[string]cty.Type{
   822  				"a": cty.List(cty.Object(map[string]cty.Type{
   823  					"b": cty.String,
   824  				})),
   825  			})),
   826  			cty.ObjectVal(map[string]cty.Value{
   827  				"a": cty.ListVal([]cty.Value{
   828  					cty.ObjectVal(map[string]cty.Value{
   829  						"b": cty.StringVal("c value"),
   830  					}),
   831  				}),
   832  			}),
   833  			cty.ObjectVal(map[string]cty.Value{
   834  				"a": cty.ListVal([]cty.Value{
   835  					cty.ObjectVal(map[string]cty.Value{
   836  						"b": cty.StringVal("new c value"),
   837  					}),
   838  				}),
   839  			}),
   840  			[]string{
   841  				`.a[0].b: planned value cty.StringVal("new c value") does not match config value cty.StringVal("c value")`,
   842  			},
   843  		},
   844  		"NestedType attr, no computed, invalid change in plan sensitive": {
   845  			&configschema.Block{
   846  				Attributes: map[string]*configschema.Attribute{
   847  					"a": {
   848  						NestedType: &configschema.Object{
   849  							Nesting: configschema.NestingList,
   850  							Attributes: map[string]*configschema.Attribute{
   851  								"b": {
   852  									Type:      cty.String,
   853  									Optional:  true,
   854  									Sensitive: true,
   855  								},
   856  							},
   857  						},
   858  						Optional: true,
   859  					},
   860  				},
   861  			},
   862  			cty.NullVal(cty.Object(map[string]cty.Type{
   863  				"a": cty.List(cty.Object(map[string]cty.Type{
   864  					"b": cty.String,
   865  				})),
   866  			})),
   867  			cty.ObjectVal(map[string]cty.Value{
   868  				"a": cty.ListVal([]cty.Value{
   869  					cty.ObjectVal(map[string]cty.Value{
   870  						"b": cty.StringVal("b value"),
   871  					}),
   872  				}),
   873  			}),
   874  			cty.ObjectVal(map[string]cty.Value{
   875  				"a": cty.ListVal([]cty.Value{
   876  					cty.ObjectVal(map[string]cty.Value{
   877  						"b": cty.StringVal("new b value"),
   878  					}),
   879  				}),
   880  			}),
   881  			[]string{
   882  				`.a[0].b: sensitive planned value does not match config value`,
   883  			},
   884  		},
   885  		"NestedType attr, no computed, diff suppression in plan": {
   886  			&configschema.Block{
   887  				Attributes: map[string]*configschema.Attribute{
   888  					"a": {
   889  						NestedType: &configschema.Object{
   890  							Nesting: configschema.NestingList,
   891  							Attributes: map[string]*configschema.Attribute{
   892  								"b": {
   893  									Type:     cty.String,
   894  									Optional: true,
   895  								},
   896  							},
   897  						},
   898  						Optional: true,
   899  					},
   900  				},
   901  			},
   902  			cty.ObjectVal(map[string]cty.Value{
   903  				"a": cty.ListVal([]cty.Value{
   904  					cty.ObjectVal(map[string]cty.Value{
   905  						"b": cty.StringVal("b value"),
   906  					}),
   907  				}),
   908  			}),
   909  			cty.ObjectVal(map[string]cty.Value{
   910  				"a": cty.ListVal([]cty.Value{
   911  					cty.ObjectVal(map[string]cty.Value{
   912  						"b": cty.StringVal("new b value"),
   913  					}),
   914  				}),
   915  			}),
   916  			cty.ObjectVal(map[string]cty.Value{
   917  				"a": cty.ListVal([]cty.Value{
   918  					cty.ObjectVal(map[string]cty.Value{
   919  						"b": cty.StringVal("b value"), // plan uses value from prior object
   920  					}),
   921  				}),
   922  			}),
   923  			nil,
   924  		},
   925  		"NestedType attr, no computed, all null": {
   926  			&configschema.Block{
   927  				Attributes: map[string]*configschema.Attribute{
   928  					"a": {
   929  						NestedType: &configschema.Object{
   930  							Nesting: configschema.NestingList,
   931  							Attributes: map[string]*configschema.Attribute{
   932  								"b": {
   933  									Type:     cty.String,
   934  									Optional: true,
   935  								},
   936  							},
   937  						},
   938  						Optional: true,
   939  					},
   940  				},
   941  			},
   942  			cty.ObjectVal(map[string]cty.Value{
   943  				"a": cty.NullVal(cty.DynamicPseudoType),
   944  			}),
   945  			cty.ObjectVal(map[string]cty.Value{
   946  				"a": cty.NullVal(cty.DynamicPseudoType),
   947  			}),
   948  			cty.ObjectVal(map[string]cty.Value{
   949  				"a": cty.NullVal(cty.DynamicPseudoType),
   950  			}),
   951  			nil,
   952  		},
   953  		"NestedType attr, no computed, all zero value": {
   954  			&configschema.Block{
   955  				Attributes: map[string]*configschema.Attribute{
   956  					"a": {
   957  						NestedType: &configschema.Object{
   958  							Nesting: configschema.NestingList,
   959  							Attributes: map[string]*configschema.Attribute{
   960  								"b": {
   961  									Type:     cty.String,
   962  									Optional: true,
   963  								},
   964  							},
   965  						},
   966  						Optional: true,
   967  					},
   968  				},
   969  			},
   970  			cty.ObjectVal(map[string]cty.Value{
   971  				"a": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
   972  					"b": cty.String,
   973  				}))),
   974  			}),
   975  			cty.ObjectVal(map[string]cty.Value{
   976  				"a": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
   977  					"b": cty.String,
   978  				}))),
   979  			}),
   980  			cty.ObjectVal(map[string]cty.Value{
   981  				"a": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
   982  					"b": cty.String,
   983  				}))),
   984  			}),
   985  			nil,
   986  		},
   987  		"NestedType NestingSet attribute to null": {
   988  			&configschema.Block{
   989  				Attributes: map[string]*configschema.Attribute{
   990  					"bloop": {
   991  						NestedType: &configschema.Object{
   992  							Nesting: configschema.NestingSet,
   993  							Attributes: map[string]*configschema.Attribute{
   994  								"blop": {
   995  									Type:     cty.String,
   996  									Required: true,
   997  								},
   998  							},
   999  						},
  1000  						Optional: true,
  1001  					},
  1002  				},
  1003  			},
  1004  			cty.ObjectVal(map[string]cty.Value{
  1005  				"bloop": cty.SetVal([]cty.Value{
  1006  					cty.ObjectVal(map[string]cty.Value{
  1007  						"blop": cty.StringVal("ok"),
  1008  					}),
  1009  				}),
  1010  			}),
  1011  			cty.ObjectVal(map[string]cty.Value{
  1012  				"bloop": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
  1013  					"blop": cty.String,
  1014  				}))),
  1015  			}),
  1016  			cty.ObjectVal(map[string]cty.Value{
  1017  				"bloop": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
  1018  					"blop": cty.String,
  1019  				}))),
  1020  			}),
  1021  			nil,
  1022  		},
  1023  		"NestedType deep nested optional set attribute to null": {
  1024  			&configschema.Block{
  1025  				Attributes: map[string]*configschema.Attribute{
  1026  					"bleep": {
  1027  						NestedType: &configschema.Object{
  1028  							Nesting: configschema.NestingList,
  1029  							Attributes: map[string]*configschema.Attribute{
  1030  								"bloop": {
  1031  									NestedType: &configschema.Object{
  1032  										Nesting: configschema.NestingSet,
  1033  										Attributes: map[string]*configschema.Attribute{
  1034  											"blome": {
  1035  												Type:     cty.String,
  1036  												Optional: true,
  1037  											},
  1038  										},
  1039  									},
  1040  									Optional: true,
  1041  								},
  1042  							},
  1043  						},
  1044  						Optional: true,
  1045  					},
  1046  				},
  1047  			},
  1048  			cty.ObjectVal(map[string]cty.Value{
  1049  				"bleep": cty.ListVal([]cty.Value{
  1050  					cty.ObjectVal(map[string]cty.Value{
  1051  						"bloop": cty.SetVal([]cty.Value{
  1052  							cty.ObjectVal(map[string]cty.Value{
  1053  								"blome": cty.StringVal("ok"),
  1054  							}),
  1055  						}),
  1056  					}),
  1057  				}),
  1058  			}),
  1059  			cty.ObjectVal(map[string]cty.Value{
  1060  				"bleep": cty.ListVal([]cty.Value{
  1061  					cty.ObjectVal(map[string]cty.Value{
  1062  						"bloop": cty.NullVal(cty.Set(
  1063  							cty.Object(map[string]cty.Type{
  1064  								"blome": cty.String,
  1065  							}),
  1066  						)),
  1067  					}),
  1068  				}),
  1069  			}),
  1070  			cty.ObjectVal(map[string]cty.Value{
  1071  				"bleep": cty.ListVal([]cty.Value{
  1072  					cty.ObjectVal(map[string]cty.Value{
  1073  						"bloop": cty.NullVal(cty.List(
  1074  							cty.Object(map[string]cty.Type{
  1075  								"blome": cty.String,
  1076  							}),
  1077  						)),
  1078  					}),
  1079  				}),
  1080  			}),
  1081  			nil,
  1082  		},
  1083  		"NestedType deep nested set": {
  1084  			&configschema.Block{
  1085  				Attributes: map[string]*configschema.Attribute{
  1086  					"bleep": {
  1087  						NestedType: &configschema.Object{
  1088  							Nesting: configschema.NestingList,
  1089  							Attributes: map[string]*configschema.Attribute{
  1090  								"bloop": {
  1091  									NestedType: &configschema.Object{
  1092  										Nesting: configschema.NestingSet,
  1093  										Attributes: map[string]*configschema.Attribute{
  1094  											"blome": {
  1095  												Type:     cty.String,
  1096  												Optional: true,
  1097  											},
  1098  										},
  1099  									},
  1100  									Optional: true,
  1101  								},
  1102  							},
  1103  						},
  1104  						Optional: true,
  1105  					},
  1106  				},
  1107  			},
  1108  			cty.ObjectVal(map[string]cty.Value{
  1109  				"bleep": cty.ListVal([]cty.Value{
  1110  					cty.ObjectVal(map[string]cty.Value{
  1111  						"bloop": cty.SetVal([]cty.Value{
  1112  							cty.ObjectVal(map[string]cty.Value{
  1113  								"blome": cty.StringVal("ok"),
  1114  							}),
  1115  						}),
  1116  					}),
  1117  				}),
  1118  			}),
  1119  			// Note: bloop is null in the config
  1120  			cty.ObjectVal(map[string]cty.Value{
  1121  				"bleep": cty.ListVal([]cty.Value{
  1122  					cty.ObjectVal(map[string]cty.Value{
  1123  						"bloop": cty.NullVal(cty.Set(
  1124  							cty.Object(map[string]cty.Type{
  1125  								"blome": cty.String,
  1126  							}),
  1127  						)),
  1128  					}),
  1129  				}),
  1130  			}),
  1131  			// provider sends back the prior value, not matching the config
  1132  			cty.ObjectVal(map[string]cty.Value{
  1133  				"bleep": cty.ListVal([]cty.Value{
  1134  					cty.ObjectVal(map[string]cty.Value{
  1135  						"bloop": cty.SetVal([]cty.Value{
  1136  							cty.ObjectVal(map[string]cty.Value{
  1137  								"blome": cty.StringVal("ok"),
  1138  							}),
  1139  						}),
  1140  					}),
  1141  				}),
  1142  			}),
  1143  			nil, // we cannot validate individual set elements, and trust the provider's response
  1144  		},
  1145  		"NestedType nested computed list attribute": {
  1146  			&configschema.Block{
  1147  				Attributes: map[string]*configschema.Attribute{
  1148  					"bloop": {
  1149  						NestedType: &configschema.Object{
  1150  							Nesting: configschema.NestingList,
  1151  							Attributes: map[string]*configschema.Attribute{
  1152  								"blop": {
  1153  									Type:     cty.String,
  1154  									Optional: true,
  1155  								},
  1156  							},
  1157  						},
  1158  						Computed: true,
  1159  					},
  1160  				},
  1161  			},
  1162  			cty.ObjectVal(map[string]cty.Value{
  1163  				"bloop": cty.ListVal([]cty.Value{
  1164  					cty.ObjectVal(map[string]cty.Value{
  1165  						"blop": cty.StringVal("ok"),
  1166  					}),
  1167  				}),
  1168  			}),
  1169  			cty.ObjectVal(map[string]cty.Value{
  1170  				"bloop": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
  1171  					"blop": cty.String,
  1172  				}))),
  1173  			}),
  1174  
  1175  			cty.ObjectVal(map[string]cty.Value{
  1176  				"bloop": cty.ListVal([]cty.Value{
  1177  					cty.ObjectVal(map[string]cty.Value{
  1178  						"blop": cty.StringVal("ok"),
  1179  					}),
  1180  				}),
  1181  			}),
  1182  			nil,
  1183  		},
  1184  		"NestedType nested list attribute to null": {
  1185  			&configschema.Block{
  1186  				Attributes: map[string]*configschema.Attribute{
  1187  					"bloop": {
  1188  						NestedType: &configschema.Object{
  1189  							Nesting: configschema.NestingList,
  1190  							Attributes: map[string]*configschema.Attribute{
  1191  								"blop": {
  1192  									Type:     cty.String,
  1193  									Optional: true,
  1194  								},
  1195  							},
  1196  						},
  1197  						Optional: true,
  1198  					},
  1199  				},
  1200  			},
  1201  			cty.ObjectVal(map[string]cty.Value{
  1202  				"bloop": cty.ListVal([]cty.Value{
  1203  					cty.ObjectVal(map[string]cty.Value{
  1204  						"blop": cty.StringVal("ok"),
  1205  					}),
  1206  				}),
  1207  			}),
  1208  			cty.ObjectVal(map[string]cty.Value{
  1209  				"bloop": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
  1210  					"blop": cty.String,
  1211  				}))),
  1212  			}),
  1213  
  1214  			// provider returned the old value
  1215  			cty.ObjectVal(map[string]cty.Value{
  1216  				"bloop": cty.ListVal([]cty.Value{
  1217  					cty.ObjectVal(map[string]cty.Value{
  1218  						"blop": cty.StringVal("ok"),
  1219  					}),
  1220  				}),
  1221  			}),
  1222  			[]string{`.bloop: planned value cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"blop":cty.StringVal("ok")})}) for a non-computed attribute`},
  1223  		},
  1224  		"NestedType nested set attribute to null": {
  1225  			&configschema.Block{
  1226  				Attributes: map[string]*configschema.Attribute{
  1227  					"bloop": {
  1228  						NestedType: &configschema.Object{
  1229  							Nesting: configschema.NestingSet,
  1230  							Attributes: map[string]*configschema.Attribute{
  1231  								"blop": {
  1232  									Type:     cty.String,
  1233  									Optional: true,
  1234  								},
  1235  							},
  1236  						},
  1237  						Optional: true,
  1238  					},
  1239  				},
  1240  			},
  1241  			cty.ObjectVal(map[string]cty.Value{
  1242  				"bloop": cty.SetVal([]cty.Value{
  1243  					cty.ObjectVal(map[string]cty.Value{
  1244  						"blop": cty.StringVal("ok"),
  1245  					}),
  1246  				}),
  1247  			}),
  1248  			cty.ObjectVal(map[string]cty.Value{
  1249  				"bloop": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
  1250  					"blop": cty.String,
  1251  				}))),
  1252  			}),
  1253  			// provider returned the old value
  1254  			cty.ObjectVal(map[string]cty.Value{
  1255  				"bloop": cty.ListVal([]cty.Value{
  1256  					cty.ObjectVal(map[string]cty.Value{
  1257  						"blop": cty.StringVal("ok"),
  1258  					}),
  1259  				}),
  1260  			}),
  1261  			[]string{`.bloop: planned value cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"blop":cty.StringVal("ok")})}) for a non-computed attribute`},
  1262  		},
  1263  		"computed within nested objects": {
  1264  			&configschema.Block{
  1265  				Attributes: map[string]*configschema.Attribute{
  1266  					"map": {
  1267  						NestedType: &configschema.Object{
  1268  							Nesting: configschema.NestingMap,
  1269  							Attributes: map[string]*configschema.Attribute{
  1270  								"name": {
  1271  									Type:     cty.String,
  1272  									Computed: true,
  1273  								},
  1274  							},
  1275  						},
  1276  					},
  1277  					// When an object has dynamic attrs, the map may be
  1278  					// handled as an object.
  1279  					"map_as_obj": {
  1280  						NestedType: &configschema.Object{
  1281  							Nesting: configschema.NestingMap,
  1282  							Attributes: map[string]*configschema.Attribute{
  1283  								"name": {
  1284  									Type:     cty.String,
  1285  									Computed: true,
  1286  								},
  1287  							},
  1288  						},
  1289  					},
  1290  					"list": {
  1291  						NestedType: &configschema.Object{
  1292  							Nesting: configschema.NestingList,
  1293  							Attributes: map[string]*configschema.Attribute{
  1294  								"name": {
  1295  									Type:     cty.String,
  1296  									Computed: true,
  1297  								},
  1298  							},
  1299  						},
  1300  					},
  1301  					"set": {
  1302  						NestedType: &configschema.Object{
  1303  							Nesting: configschema.NestingSet,
  1304  							Attributes: map[string]*configschema.Attribute{
  1305  								"name": {
  1306  									Type:     cty.String,
  1307  									Computed: true,
  1308  								},
  1309  							},
  1310  						},
  1311  					},
  1312  					"single": {
  1313  						NestedType: &configschema.Object{
  1314  							Nesting: configschema.NestingSingle,
  1315  							Attributes: map[string]*configschema.Attribute{
  1316  								"name": {
  1317  									Type:     cty.DynamicPseudoType,
  1318  									Computed: true,
  1319  								},
  1320  							},
  1321  						},
  1322  					},
  1323  				},
  1324  			},
  1325  			cty.NullVal(cty.Object(map[string]cty.Type{
  1326  				"map": cty.Map(cty.Object(map[string]cty.Type{
  1327  					"name": cty.String,
  1328  				})),
  1329  				"map_as_obj": cty.Map(cty.Object(map[string]cty.Type{
  1330  					"name": cty.DynamicPseudoType,
  1331  				})),
  1332  				"list": cty.List(cty.Object(map[string]cty.Type{
  1333  					"name": cty.String,
  1334  				})),
  1335  				"set": cty.Set(cty.Object(map[string]cty.Type{
  1336  					"name": cty.String,
  1337  				})),
  1338  				"single": cty.Object(map[string]cty.Type{
  1339  					"name": cty.String,
  1340  				}),
  1341  			})),
  1342  			cty.ObjectVal(map[string]cty.Value{
  1343  				"map": cty.MapVal(map[string]cty.Value{
  1344  					"one": cty.ObjectVal(map[string]cty.Value{
  1345  						"name": cty.NullVal(cty.String),
  1346  					}),
  1347  				}),
  1348  				"map_as_obj": cty.MapVal(map[string]cty.Value{
  1349  					"one": cty.ObjectVal(map[string]cty.Value{
  1350  						"name": cty.NullVal(cty.DynamicPseudoType),
  1351  					}),
  1352  				}),
  1353  				"list": cty.ListVal([]cty.Value{
  1354  					cty.ObjectVal(map[string]cty.Value{
  1355  						"name": cty.NullVal(cty.String),
  1356  					}),
  1357  				}),
  1358  				"set": cty.SetVal([]cty.Value{
  1359  					cty.ObjectVal(map[string]cty.Value{
  1360  						"name": cty.NullVal(cty.String),
  1361  					}),
  1362  				}),
  1363  				"single": cty.ObjectVal(map[string]cty.Value{
  1364  					"name": cty.NullVal(cty.String),
  1365  				}),
  1366  			}),
  1367  			cty.ObjectVal(map[string]cty.Value{
  1368  				"map": cty.MapVal(map[string]cty.Value{
  1369  					"one": cty.ObjectVal(map[string]cty.Value{
  1370  						"name": cty.NullVal(cty.String),
  1371  					}),
  1372  				}),
  1373  				"map_as_obj": cty.ObjectVal(map[string]cty.Value{
  1374  					"one": cty.ObjectVal(map[string]cty.Value{
  1375  						"name": cty.StringVal("computed"),
  1376  					}),
  1377  				}),
  1378  				"list": cty.ListVal([]cty.Value{
  1379  					cty.ObjectVal(map[string]cty.Value{
  1380  						"name": cty.NullVal(cty.String),
  1381  					}),
  1382  				}),
  1383  				"set": cty.SetVal([]cty.Value{
  1384  					cty.ObjectVal(map[string]cty.Value{
  1385  						"name": cty.NullVal(cty.String),
  1386  					}),
  1387  				}),
  1388  				"single": cty.ObjectVal(map[string]cty.Value{
  1389  					"name": cty.NullVal(cty.String),
  1390  				}),
  1391  			}),
  1392  			nil,
  1393  		},
  1394  		"computed nested objects": {
  1395  			&configschema.Block{
  1396  				Attributes: map[string]*configschema.Attribute{
  1397  					"map": {
  1398  						NestedType: &configschema.Object{
  1399  							Nesting: configschema.NestingMap,
  1400  							Attributes: map[string]*configschema.Attribute{
  1401  								"name": {
  1402  									Type: cty.String,
  1403  								},
  1404  							},
  1405  						},
  1406  						Computed: true,
  1407  					},
  1408  					"list": {
  1409  						NestedType: &configschema.Object{
  1410  							Nesting: configschema.NestingList,
  1411  							Attributes: map[string]*configschema.Attribute{
  1412  								"name": {
  1413  									Type: cty.String,
  1414  								},
  1415  							},
  1416  						},
  1417  						Computed: true,
  1418  					},
  1419  					"set": {
  1420  						NestedType: &configschema.Object{
  1421  							Nesting: configschema.NestingSet,
  1422  							Attributes: map[string]*configschema.Attribute{
  1423  								"name": {
  1424  									Type: cty.String,
  1425  								},
  1426  							},
  1427  						},
  1428  						Optional: true,
  1429  						Computed: true,
  1430  					},
  1431  					"single": {
  1432  						NestedType: &configschema.Object{
  1433  							Nesting: configschema.NestingSingle,
  1434  							Attributes: map[string]*configschema.Attribute{
  1435  								"name": {
  1436  									Type: cty.DynamicPseudoType,
  1437  								},
  1438  							},
  1439  						},
  1440  						Computed: true,
  1441  					},
  1442  				},
  1443  			},
  1444  			cty.NullVal(cty.Object(map[string]cty.Type{
  1445  				"map": cty.Map(cty.Object(map[string]cty.Type{
  1446  					"name": cty.String,
  1447  				})),
  1448  				"list": cty.List(cty.Object(map[string]cty.Type{
  1449  					"name": cty.String,
  1450  				})),
  1451  				"set": cty.Set(cty.Object(map[string]cty.Type{
  1452  					"name": cty.String,
  1453  				})),
  1454  				"single": cty.Object(map[string]cty.Type{
  1455  					"name": cty.String,
  1456  				}),
  1457  			})),
  1458  			cty.ObjectVal(map[string]cty.Value{
  1459  				"map": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{
  1460  					"name": cty.String,
  1461  				}))),
  1462  				"list": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
  1463  					"name": cty.String,
  1464  				}))),
  1465  				"set": cty.SetVal([]cty.Value{
  1466  					cty.ObjectVal(map[string]cty.Value{
  1467  						"name": cty.StringVal("from_config"),
  1468  					}),
  1469  				}),
  1470  				"single": cty.NullVal(cty.Object(map[string]cty.Type{
  1471  					"name": cty.String,
  1472  				})),
  1473  			}),
  1474  			cty.ObjectVal(map[string]cty.Value{
  1475  				"map": cty.MapVal(map[string]cty.Value{
  1476  					"one": cty.UnknownVal(cty.Object(map[string]cty.Type{
  1477  						"name": cty.String,
  1478  					})),
  1479  				}),
  1480  				"list": cty.ListVal([]cty.Value{
  1481  					cty.ObjectVal(map[string]cty.Value{
  1482  						"name": cty.StringVal("computed"),
  1483  					}),
  1484  				}),
  1485  				"set": cty.SetVal([]cty.Value{
  1486  					cty.ObjectVal(map[string]cty.Value{
  1487  						"name": cty.StringVal("from_config"),
  1488  					}),
  1489  				}),
  1490  				"single": cty.UnknownVal(cty.Object(map[string]cty.Type{
  1491  					"name": cty.String,
  1492  				})),
  1493  			}),
  1494  			nil,
  1495  		},
  1496  		"optional computed within nested objects": {
  1497  			&configschema.Block{
  1498  				Attributes: map[string]*configschema.Attribute{
  1499  					"map": {
  1500  						NestedType: &configschema.Object{
  1501  							Nesting: configschema.NestingMap,
  1502  							Attributes: map[string]*configschema.Attribute{
  1503  								"name": {
  1504  									Type:     cty.String,
  1505  									Computed: true,
  1506  								},
  1507  							},
  1508  						},
  1509  					},
  1510  					// When an object has dynamic attrs, the map may be
  1511  					// handled as an object.
  1512  					"map_as_obj": {
  1513  						NestedType: &configschema.Object{
  1514  							Nesting: configschema.NestingMap,
  1515  							Attributes: map[string]*configschema.Attribute{
  1516  								"name": {
  1517  									Type:     cty.String,
  1518  									Optional: true,
  1519  									Computed: true,
  1520  								},
  1521  							},
  1522  						},
  1523  					},
  1524  					"list": {
  1525  						NestedType: &configschema.Object{
  1526  							Nesting: configschema.NestingList,
  1527  							Attributes: map[string]*configschema.Attribute{
  1528  								"name": {
  1529  									Type:     cty.String,
  1530  									Optional: true,
  1531  									Computed: true,
  1532  								},
  1533  							},
  1534  						},
  1535  					},
  1536  					"set": {
  1537  						NestedType: &configschema.Object{
  1538  							Nesting: configschema.NestingSet,
  1539  							Attributes: map[string]*configschema.Attribute{
  1540  								"name": {
  1541  									Type:     cty.String,
  1542  									Optional: true,
  1543  									Computed: true,
  1544  								},
  1545  							},
  1546  						},
  1547  					},
  1548  					"single": {
  1549  						NestedType: &configschema.Object{
  1550  							Nesting: configschema.NestingSingle,
  1551  							Attributes: map[string]*configschema.Attribute{
  1552  								"name": {
  1553  									Type:     cty.DynamicPseudoType,
  1554  									Optional: true,
  1555  									Computed: true,
  1556  								},
  1557  							},
  1558  						},
  1559  					},
  1560  				},
  1561  			},
  1562  			cty.NullVal(cty.Object(map[string]cty.Type{
  1563  				"map": cty.Map(cty.Object(map[string]cty.Type{
  1564  					"name": cty.String,
  1565  				})),
  1566  				"map_as_obj": cty.Map(cty.Object(map[string]cty.Type{
  1567  					"name": cty.DynamicPseudoType,
  1568  				})),
  1569  				"list": cty.List(cty.Object(map[string]cty.Type{
  1570  					"name": cty.String,
  1571  				})),
  1572  				"set": cty.Set(cty.Object(map[string]cty.Type{
  1573  					"name": cty.String,
  1574  				})),
  1575  				"single": cty.Object(map[string]cty.Type{
  1576  					"name": cty.String,
  1577  				}),
  1578  			})),
  1579  			cty.ObjectVal(map[string]cty.Value{
  1580  				"map": cty.MapVal(map[string]cty.Value{
  1581  					"one": cty.ObjectVal(map[string]cty.Value{
  1582  						"name": cty.StringVal("from_config"),
  1583  					}),
  1584  				}),
  1585  				"map_as_obj": cty.MapVal(map[string]cty.Value{
  1586  					"one": cty.ObjectVal(map[string]cty.Value{
  1587  						"name": cty.NullVal(cty.DynamicPseudoType),
  1588  					}),
  1589  				}),
  1590  				"list": cty.ListVal([]cty.Value{
  1591  					cty.ObjectVal(map[string]cty.Value{
  1592  						"name": cty.NullVal(cty.String),
  1593  					}),
  1594  				}),
  1595  				"set": cty.SetVal([]cty.Value{
  1596  					cty.ObjectVal(map[string]cty.Value{
  1597  						"name": cty.NullVal(cty.String),
  1598  					}),
  1599  				}),
  1600  				"single": cty.ObjectVal(map[string]cty.Value{
  1601  					"name": cty.StringVal("from_config"),
  1602  				}),
  1603  			}),
  1604  			cty.ObjectVal(map[string]cty.Value{
  1605  				"map": cty.MapVal(map[string]cty.Value{
  1606  					"one": cty.ObjectVal(map[string]cty.Value{
  1607  						"name": cty.StringVal("from_config"),
  1608  					}),
  1609  				}),
  1610  				"map_as_obj": cty.ObjectVal(map[string]cty.Value{
  1611  					"one": cty.ObjectVal(map[string]cty.Value{
  1612  						"name": cty.StringVal("computed"),
  1613  					}),
  1614  				}),
  1615  				"list": cty.ListVal([]cty.Value{
  1616  					cty.ObjectVal(map[string]cty.Value{
  1617  						"name": cty.StringVal("computed"),
  1618  					}),
  1619  				}),
  1620  				"set": cty.SetVal([]cty.Value{
  1621  					cty.ObjectVal(map[string]cty.Value{
  1622  						"name": cty.NullVal(cty.String),
  1623  					}),
  1624  				}),
  1625  				"single": cty.ObjectVal(map[string]cty.Value{
  1626  					"name": cty.StringVal("from_config"),
  1627  				}),
  1628  			}),
  1629  			nil,
  1630  		},
  1631  		"cannot replace config nested attr": {
  1632  			&configschema.Block{
  1633  				Attributes: map[string]*configschema.Attribute{
  1634  					"map": {
  1635  						NestedType: &configschema.Object{
  1636  							Nesting: configschema.NestingMap,
  1637  							Attributes: map[string]*configschema.Attribute{
  1638  								"name": {
  1639  									Type:     cty.String,
  1640  									Computed: true,
  1641  									Optional: true,
  1642  								},
  1643  							},
  1644  						},
  1645  					},
  1646  				},
  1647  			},
  1648  			cty.NullVal(cty.Object(map[string]cty.Type{
  1649  				"map": cty.Map(cty.Object(map[string]cty.Type{
  1650  					"name": cty.String,
  1651  				})),
  1652  			})),
  1653  			cty.ObjectVal(map[string]cty.Value{
  1654  				"map": cty.MapVal(map[string]cty.Value{
  1655  					"one": cty.ObjectVal(map[string]cty.Value{
  1656  						"name": cty.StringVal("from_config"),
  1657  					}),
  1658  				}),
  1659  			}),
  1660  			cty.ObjectVal(map[string]cty.Value{
  1661  				"map": cty.MapVal(map[string]cty.Value{
  1662  					"one": cty.ObjectVal(map[string]cty.Value{
  1663  						"name": cty.StringVal("from_provider"),
  1664  					}),
  1665  				}),
  1666  			}),
  1667  			[]string{`.map.one.name: planned value cty.StringVal("from_provider") does not match config value cty.StringVal("from_config")`},
  1668  		},
  1669  
  1670  		// If a config value ended up in a computed-only attribute it can still
  1671  		// be a valid plan. We either got here because the user ignore warnings
  1672  		// about ignore_changes on computed attributes, or we failed to
  1673  		// validate a config with computed values. Either way, we don't want to
  1674  		// indicate an error with the provider.
  1675  		"computed only value with config": {
  1676  			&configschema.Block{
  1677  				Attributes: map[string]*configschema.Attribute{
  1678  					"a": {
  1679  						Type:     cty.String,
  1680  						Computed: true,
  1681  					},
  1682  				},
  1683  			},
  1684  			cty.ObjectVal(map[string]cty.Value{
  1685  				"a": cty.StringVal("old"),
  1686  			}),
  1687  			cty.ObjectVal(map[string]cty.Value{
  1688  				"a": cty.StringVal("old"),
  1689  			}),
  1690  			cty.ObjectVal(map[string]cty.Value{
  1691  				"a": cty.UnknownVal(cty.String),
  1692  			}),
  1693  			nil,
  1694  		},
  1695  
  1696  		// When validating collections we start by comparing length, which
  1697  		// requires guarding for any unknown values incorrectly returned by the
  1698  		// provider.
  1699  		"nested collection attrs planned unknown": {
  1700  			&configschema.Block{
  1701  				Attributes: map[string]*configschema.Attribute{
  1702  					"set": {
  1703  						Computed: true,
  1704  						Optional: true,
  1705  						NestedType: &configschema.Object{
  1706  							Nesting: configschema.NestingSet,
  1707  							Attributes: map[string]*configschema.Attribute{
  1708  								"name": {
  1709  									Type:     cty.String,
  1710  									Computed: true,
  1711  									Optional: true,
  1712  								},
  1713  							},
  1714  						},
  1715  					},
  1716  					"list": {
  1717  						Computed: true,
  1718  						Optional: true,
  1719  						NestedType: &configschema.Object{
  1720  							Nesting: configschema.NestingList,
  1721  							Attributes: map[string]*configschema.Attribute{
  1722  								"name": {
  1723  									Type:     cty.String,
  1724  									Computed: true,
  1725  									Optional: true,
  1726  								},
  1727  							},
  1728  						},
  1729  					},
  1730  					"map": {
  1731  						Computed: true,
  1732  						Optional: true,
  1733  						NestedType: &configschema.Object{
  1734  							Nesting: configschema.NestingMap,
  1735  							Attributes: map[string]*configschema.Attribute{
  1736  								"name": {
  1737  									Type:     cty.String,
  1738  									Computed: true,
  1739  									Optional: true,
  1740  								},
  1741  							},
  1742  						},
  1743  					},
  1744  				},
  1745  			},
  1746  			cty.ObjectVal(map[string]cty.Value{
  1747  				"set": cty.SetVal([]cty.Value{
  1748  					cty.ObjectVal(map[string]cty.Value{
  1749  						"name": cty.StringVal("from_config"),
  1750  					}),
  1751  				}),
  1752  				"list": cty.SetVal([]cty.Value{
  1753  					cty.ObjectVal(map[string]cty.Value{
  1754  						"name": cty.StringVal("from_config"),
  1755  					}),
  1756  				}),
  1757  				"map": cty.MapVal(map[string]cty.Value{
  1758  					"key": cty.ObjectVal(map[string]cty.Value{
  1759  						"name": cty.StringVal("from_config"),
  1760  					}),
  1761  				}),
  1762  			}),
  1763  			cty.ObjectVal(map[string]cty.Value{
  1764  				"set": cty.SetVal([]cty.Value{
  1765  					cty.ObjectVal(map[string]cty.Value{
  1766  						"name": cty.StringVal("from_config"),
  1767  					}),
  1768  				}),
  1769  				"list": cty.SetVal([]cty.Value{
  1770  					cty.ObjectVal(map[string]cty.Value{
  1771  						"name": cty.StringVal("from_config"),
  1772  					}),
  1773  				}),
  1774  				"map": cty.MapVal(map[string]cty.Value{
  1775  					"key": cty.ObjectVal(map[string]cty.Value{
  1776  						"name": cty.StringVal("from_config"),
  1777  					}),
  1778  				}),
  1779  			}),
  1780  			// provider cannot override the config
  1781  			cty.ObjectVal(map[string]cty.Value{
  1782  				"set": cty.UnknownVal(cty.Set(
  1783  					cty.Object(map[string]cty.Type{
  1784  						"name": cty.String,
  1785  					}),
  1786  				)),
  1787  				"list": cty.UnknownVal(cty.Set(
  1788  					cty.Object(map[string]cty.Type{
  1789  						"name": cty.String,
  1790  					}),
  1791  				)),
  1792  				"map": cty.UnknownVal(cty.Map(
  1793  					cty.Object(map[string]cty.Type{
  1794  						"name": cty.String,
  1795  					}),
  1796  				)),
  1797  			}),
  1798  			[]string{
  1799  				`.set: planned unknown for configured value`,
  1800  				`.list: planned unknown for configured value`,
  1801  				`.map: planned unknown for configured value`,
  1802  			},
  1803  		},
  1804  
  1805  		"refined unknown values can become less refined": {
  1806  			// Providers often can't preserve refinements through the provider
  1807  			// wire protocol: although we do have a defined serialization for
  1808  			// it, most providers were written before there was any such
  1809  			// thing as refinements, and in future there might be new
  1810  			// refinements that even refinement-aware providers don't know
  1811  			// how to preserve, so we allow them to get dropped here as
  1812  			// a concession to backward-compatibility.
  1813  			&configschema.Block{
  1814  				Attributes: map[string]*configschema.Attribute{
  1815  					"a": {
  1816  						Type:     cty.String,
  1817  						Required: true,
  1818  					},
  1819  				},
  1820  			},
  1821  			cty.ObjectVal(map[string]cty.Value{
  1822  				"a": cty.StringVal("old"),
  1823  			}),
  1824  			cty.ObjectVal(map[string]cty.Value{
  1825  				"a": cty.UnknownVal(cty.String).RefineNotNull(),
  1826  			}),
  1827  			cty.ObjectVal(map[string]cty.Value{
  1828  				"a": cty.UnknownVal(cty.String),
  1829  			}),
  1830  			nil,
  1831  		},
  1832  
  1833  		"refined unknown values in collection elements can become less refined": {
  1834  			// Providers often can't preserve refinements through the provider
  1835  			// wire protocol: although we do have a defined serialization for
  1836  			// it, most providers were written before there was any such
  1837  			// thing as refinements, and in future there might be new
  1838  			// refinements that even refinement-aware providers don't know
  1839  			// how to preserve, so we allow them to get dropped here as
  1840  			// a concession to backward-compatibility.
  1841  			//
  1842  			// This is intending to approximate something like this:
  1843  			//
  1844  			//     resource "null_resource" "hello" {
  1845  			//       triggers = {
  1846  			//         key = uuid()
  1847  			//       }
  1848  			//     }
  1849  			//
  1850  			// ...under the assumption that the null_resource implementation
  1851  			// cannot preserve the not-null refinement that the uuid function
  1852  			// generates.
  1853  			//
  1854  			// https://github.com/terramate-io/tf/issues/33385
  1855  			&configschema.Block{
  1856  				Attributes: map[string]*configschema.Attribute{
  1857  					"m": {
  1858  						Type: cty.Map(cty.String),
  1859  					},
  1860  				},
  1861  			},
  1862  			cty.NullVal(cty.Object(map[string]cty.Type{
  1863  				"m": cty.Map(cty.String),
  1864  			})),
  1865  			cty.ObjectVal(map[string]cty.Value{
  1866  				"m": cty.MapVal(map[string]cty.Value{
  1867  					"key": cty.UnknownVal(cty.String).RefineNotNull(),
  1868  				}),
  1869  			}),
  1870  			cty.ObjectVal(map[string]cty.Value{
  1871  				"m": cty.MapVal(map[string]cty.Value{
  1872  					"key": cty.UnknownVal(cty.String),
  1873  				}),
  1874  			}),
  1875  			nil,
  1876  		},
  1877  
  1878  		"nested set values can contain computed unknown": {
  1879  			&configschema.Block{
  1880  				Attributes: map[string]*configschema.Attribute{
  1881  					"set": {
  1882  						Optional: true,
  1883  						NestedType: &configschema.Object{
  1884  							Nesting: configschema.NestingSet,
  1885  							Attributes: map[string]*configschema.Attribute{
  1886  								"input": {
  1887  									Type:     cty.String,
  1888  									Optional: true,
  1889  								},
  1890  								"computed": {
  1891  									Type:     cty.String,
  1892  									Computed: true,
  1893  									Optional: true,
  1894  								},
  1895  							},
  1896  						},
  1897  					},
  1898  				},
  1899  			},
  1900  			cty.ObjectVal(map[string]cty.Value{
  1901  				"set": cty.SetVal([]cty.Value{
  1902  					cty.ObjectVal(map[string]cty.Value{
  1903  						"input":    cty.StringVal("a"),
  1904  						"computed": cty.NullVal(cty.String),
  1905  					}),
  1906  					cty.ObjectVal(map[string]cty.Value{
  1907  						"input":    cty.StringVal("b"),
  1908  						"computed": cty.NullVal(cty.String),
  1909  					}),
  1910  				}),
  1911  			}),
  1912  			cty.ObjectVal(map[string]cty.Value{
  1913  				"set": cty.SetVal([]cty.Value{
  1914  					cty.ObjectVal(map[string]cty.Value{
  1915  						"input":    cty.StringVal("a"),
  1916  						"computed": cty.NullVal(cty.String),
  1917  					}),
  1918  					cty.ObjectVal(map[string]cty.Value{
  1919  						"input":    cty.StringVal("b"),
  1920  						"computed": cty.NullVal(cty.String),
  1921  					}),
  1922  				}),
  1923  			}),
  1924  			// Plan can mark the null computed values as unknown
  1925  			cty.ObjectVal(map[string]cty.Value{
  1926  				"set": cty.SetVal([]cty.Value{
  1927  					cty.ObjectVal(map[string]cty.Value{
  1928  						"input":    cty.StringVal("a"),
  1929  						"computed": cty.UnknownVal(cty.String),
  1930  					}),
  1931  					cty.ObjectVal(map[string]cty.Value{
  1932  						"input":    cty.StringVal("b"),
  1933  						"computed": cty.UnknownVal(cty.String),
  1934  					}),
  1935  				}),
  1936  			}),
  1937  			[]string{},
  1938  		},
  1939  	}
  1940  
  1941  	for name, test := range tests {
  1942  		t.Run(name, func(t *testing.T) {
  1943  			errs := AssertPlanValid(test.Schema, test.Prior, test.Config, test.Planned)
  1944  
  1945  			wantErrs := make(map[string]struct{})
  1946  			gotErrs := make(map[string]struct{})
  1947  			for _, err := range errs {
  1948  				gotErrs[tfdiags.FormatError(err)] = struct{}{}
  1949  			}
  1950  			for _, msg := range test.WantErrs {
  1951  				wantErrs[msg] = struct{}{}
  1952  			}
  1953  
  1954  			t.Logf(
  1955  				"\nprior:  %sconfig:  %splanned: %s",
  1956  				dump.Value(test.Prior),
  1957  				dump.Value(test.Config),
  1958  				dump.Value(test.Planned),
  1959  			)
  1960  			for msg := range wantErrs {
  1961  				if _, ok := gotErrs[msg]; !ok {
  1962  					t.Errorf("missing expected error: %s", msg)
  1963  				}
  1964  			}
  1965  			for msg := range gotErrs {
  1966  				if _, ok := wantErrs[msg]; !ok {
  1967  					t.Errorf("unexpected extra error: %s", msg)
  1968  				}
  1969  			}
  1970  		})
  1971  	}
  1972  }