github.com/opentofu/opentofu@v1.7.1/internal/plans/objchange/plan_valid_test.go (about)

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