github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/format/diff_test.go (about)

     1  package format
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/google/go-cmp/cmp"
     8  	"github.com/hashicorp/terraform/internal/addrs"
     9  	"github.com/hashicorp/terraform/internal/configs/configschema"
    10  	"github.com/hashicorp/terraform/internal/lang/marks"
    11  	"github.com/hashicorp/terraform/internal/plans"
    12  	"github.com/hashicorp/terraform/internal/states"
    13  	"github.com/mitchellh/colorstring"
    14  	"github.com/zclconf/go-cty/cty"
    15  )
    16  
    17  func TestResourceChange_primitiveTypes(t *testing.T) {
    18  	testCases := map[string]testCase{
    19  		"creation": {
    20  			Action: plans.Create,
    21  			Mode:   addrs.ManagedResourceMode,
    22  			Before: cty.NullVal(cty.EmptyObject),
    23  			After: cty.ObjectVal(map[string]cty.Value{
    24  				"id": cty.UnknownVal(cty.String),
    25  			}),
    26  			Schema: &configschema.Block{
    27  				Attributes: map[string]*configschema.Attribute{
    28  					"id": {Type: cty.String, Computed: true},
    29  				},
    30  			},
    31  			RequiredReplace: cty.NewPathSet(),
    32  			ExpectedOutput: `  # test_instance.example will be created
    33    + resource "test_instance" "example" {
    34        + id = (known after apply)
    35      }
    36  `,
    37  		},
    38  		"creation (null string)": {
    39  			Action: plans.Create,
    40  			Mode:   addrs.ManagedResourceMode,
    41  			Before: cty.NullVal(cty.EmptyObject),
    42  			After: cty.ObjectVal(map[string]cty.Value{
    43  				"string": cty.StringVal("null"),
    44  			}),
    45  			Schema: &configschema.Block{
    46  				Attributes: map[string]*configschema.Attribute{
    47  					"string": {Type: cty.String, Optional: true},
    48  				},
    49  			},
    50  			RequiredReplace: cty.NewPathSet(),
    51  			ExpectedOutput: `  # test_instance.example will be created
    52    + resource "test_instance" "example" {
    53        + string = "null"
    54      }
    55  `,
    56  		},
    57  		"creation (null string with extra whitespace)": {
    58  			Action: plans.Create,
    59  			Mode:   addrs.ManagedResourceMode,
    60  			Before: cty.NullVal(cty.EmptyObject),
    61  			After: cty.ObjectVal(map[string]cty.Value{
    62  				"string": cty.StringVal("null "),
    63  			}),
    64  			Schema: &configschema.Block{
    65  				Attributes: map[string]*configschema.Attribute{
    66  					"string": {Type: cty.String, Optional: true},
    67  				},
    68  			},
    69  			RequiredReplace: cty.NewPathSet(),
    70  			ExpectedOutput: `  # test_instance.example will be created
    71    + resource "test_instance" "example" {
    72        + string = "null "
    73      }
    74  `,
    75  		},
    76  		"creation (object with quoted keys)": {
    77  			Action: plans.Create,
    78  			Mode:   addrs.ManagedResourceMode,
    79  			Before: cty.NullVal(cty.EmptyObject),
    80  			After: cty.ObjectVal(map[string]cty.Value{
    81  				"object": cty.ObjectVal(map[string]cty.Value{
    82  					"unquoted":   cty.StringVal("value"),
    83  					"quoted:key": cty.StringVal("some-value"),
    84  				}),
    85  			}),
    86  			Schema: &configschema.Block{
    87  				Attributes: map[string]*configschema.Attribute{
    88  					"object": {Type: cty.Object(map[string]cty.Type{
    89  						"unquoted":   cty.String,
    90  						"quoted:key": cty.String,
    91  					}), Optional: true},
    92  				},
    93  			},
    94  			RequiredReplace: cty.NewPathSet(),
    95  			ExpectedOutput: `  # test_instance.example will be created
    96    + resource "test_instance" "example" {
    97        + object = {
    98            + "quoted:key" = "some-value"
    99            + unquoted     = "value"
   100          }
   101      }
   102  `,
   103  		},
   104  		"deletion": {
   105  			Action: plans.Delete,
   106  			Mode:   addrs.ManagedResourceMode,
   107  			Before: cty.ObjectVal(map[string]cty.Value{
   108  				"id": cty.StringVal("i-02ae66f368e8518a9"),
   109  			}),
   110  			After: cty.NullVal(cty.EmptyObject),
   111  			Schema: &configschema.Block{
   112  				Attributes: map[string]*configschema.Attribute{
   113  					"id": {Type: cty.String, Computed: true},
   114  				},
   115  			},
   116  			RequiredReplace: cty.NewPathSet(),
   117  			ExpectedOutput: `  # test_instance.example will be destroyed
   118    - resource "test_instance" "example" {
   119        - id = "i-02ae66f368e8518a9" -> null
   120      }
   121  `,
   122  		},
   123  		"deletion of deposed object": {
   124  			Action:     plans.Delete,
   125  			Mode:       addrs.ManagedResourceMode,
   126  			DeposedKey: states.DeposedKey("byebye"),
   127  			Before: cty.ObjectVal(map[string]cty.Value{
   128  				"id": cty.StringVal("i-02ae66f368e8518a9"),
   129  			}),
   130  			After: cty.NullVal(cty.EmptyObject),
   131  			Schema: &configschema.Block{
   132  				Attributes: map[string]*configschema.Attribute{
   133  					"id": {Type: cty.String, Computed: true},
   134  				},
   135  			},
   136  			RequiredReplace: cty.NewPathSet(),
   137  			ExpectedOutput: `  # test_instance.example (deposed object byebye) will be destroyed
   138    # (left over from a partially-failed replacement of this instance)
   139    - resource "test_instance" "example" {
   140        - id = "i-02ae66f368e8518a9" -> null
   141      }
   142  `,
   143  		},
   144  		"deletion (empty string)": {
   145  			Action: plans.Delete,
   146  			Mode:   addrs.ManagedResourceMode,
   147  			Before: cty.ObjectVal(map[string]cty.Value{
   148  				"id":                 cty.StringVal("i-02ae66f368e8518a9"),
   149  				"intentionally_long": cty.StringVal(""),
   150  			}),
   151  			After: cty.NullVal(cty.EmptyObject),
   152  			Schema: &configschema.Block{
   153  				Attributes: map[string]*configschema.Attribute{
   154  					"id":                 {Type: cty.String, Computed: true},
   155  					"intentionally_long": {Type: cty.String, Optional: true},
   156  				},
   157  			},
   158  			RequiredReplace: cty.NewPathSet(),
   159  			ExpectedOutput: `  # test_instance.example will be destroyed
   160    - resource "test_instance" "example" {
   161        - id = "i-02ae66f368e8518a9" -> null
   162      }
   163  `,
   164  		},
   165  		"string in-place update": {
   166  			Action: plans.Update,
   167  			Mode:   addrs.ManagedResourceMode,
   168  			Before: cty.ObjectVal(map[string]cty.Value{
   169  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
   170  				"ami": cty.StringVal("ami-BEFORE"),
   171  			}),
   172  			After: cty.ObjectVal(map[string]cty.Value{
   173  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
   174  				"ami": cty.StringVal("ami-AFTER"),
   175  			}),
   176  			Schema: &configschema.Block{
   177  				Attributes: map[string]*configschema.Attribute{
   178  					"id":  {Type: cty.String, Optional: true, Computed: true},
   179  					"ami": {Type: cty.String, Optional: true},
   180  				},
   181  			},
   182  			RequiredReplace: cty.NewPathSet(),
   183  			ExpectedOutput: `  # test_instance.example will be updated in-place
   184    ~ resource "test_instance" "example" {
   185        ~ ami = "ami-BEFORE" -> "ami-AFTER"
   186          id  = "i-02ae66f368e8518a9"
   187      }
   188  `,
   189  		},
   190  		"update with quoted key": {
   191  			Action: plans.Update,
   192  			Mode:   addrs.ManagedResourceMode,
   193  			Before: cty.ObjectVal(map[string]cty.Value{
   194  				"id":       cty.StringVal("i-02ae66f368e8518a9"),
   195  				"saml:aud": cty.StringVal("https://example.com/saml"),
   196  				"zeta":     cty.StringVal("alpha"),
   197  			}),
   198  			After: cty.ObjectVal(map[string]cty.Value{
   199  				"id":       cty.StringVal("i-02ae66f368e8518a9"),
   200  				"saml:aud": cty.StringVal("https://saml.example.com"),
   201  				"zeta":     cty.StringVal("alpha"),
   202  			}),
   203  			Schema: &configschema.Block{
   204  				Attributes: map[string]*configschema.Attribute{
   205  					"id":       {Type: cty.String, Optional: true, Computed: true},
   206  					"saml:aud": {Type: cty.String, Optional: true},
   207  					"zeta":     {Type: cty.String, Optional: true},
   208  				},
   209  			},
   210  			RequiredReplace: cty.NewPathSet(),
   211  			ExpectedOutput: `  # test_instance.example will be updated in-place
   212    ~ resource "test_instance" "example" {
   213          id         = "i-02ae66f368e8518a9"
   214        ~ "saml:aud" = "https://example.com/saml" -> "https://saml.example.com"
   215          # (1 unchanged attribute hidden)
   216      }
   217  `,
   218  		},
   219  		"string force-new update": {
   220  			Action:       plans.DeleteThenCreate,
   221  			ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate,
   222  			Mode:         addrs.ManagedResourceMode,
   223  			Before: cty.ObjectVal(map[string]cty.Value{
   224  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
   225  				"ami": cty.StringVal("ami-BEFORE"),
   226  			}),
   227  			After: cty.ObjectVal(map[string]cty.Value{
   228  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
   229  				"ami": cty.StringVal("ami-AFTER"),
   230  			}),
   231  			Schema: &configschema.Block{
   232  				Attributes: map[string]*configschema.Attribute{
   233  					"id":  {Type: cty.String, Optional: true, Computed: true},
   234  					"ami": {Type: cty.String, Optional: true},
   235  				},
   236  			},
   237  			RequiredReplace: cty.NewPathSet(cty.Path{
   238  				cty.GetAttrStep{Name: "ami"},
   239  			}),
   240  			ExpectedOutput: `  # test_instance.example must be replaced
   241  -/+ resource "test_instance" "example" {
   242        ~ ami = "ami-BEFORE" -> "ami-AFTER" # forces replacement
   243          id  = "i-02ae66f368e8518a9"
   244      }
   245  `,
   246  		},
   247  		"string in-place update (null values)": {
   248  			Action: plans.Update,
   249  			Mode:   addrs.ManagedResourceMode,
   250  			Before: cty.ObjectVal(map[string]cty.Value{
   251  				"id":        cty.StringVal("i-02ae66f368e8518a9"),
   252  				"ami":       cty.StringVal("ami-BEFORE"),
   253  				"unchanged": cty.NullVal(cty.String),
   254  			}),
   255  			After: cty.ObjectVal(map[string]cty.Value{
   256  				"id":        cty.StringVal("i-02ae66f368e8518a9"),
   257  				"ami":       cty.StringVal("ami-AFTER"),
   258  				"unchanged": cty.NullVal(cty.String),
   259  			}),
   260  			Schema: &configschema.Block{
   261  				Attributes: map[string]*configschema.Attribute{
   262  					"id":        {Type: cty.String, Optional: true, Computed: true},
   263  					"ami":       {Type: cty.String, Optional: true},
   264  					"unchanged": {Type: cty.String, Optional: true},
   265  				},
   266  			},
   267  			RequiredReplace: cty.NewPathSet(),
   268  			ExpectedOutput: `  # test_instance.example will be updated in-place
   269    ~ resource "test_instance" "example" {
   270        ~ ami = "ami-BEFORE" -> "ami-AFTER"
   271          id  = "i-02ae66f368e8518a9"
   272      }
   273  `,
   274  		},
   275  		"in-place update of multi-line string field": {
   276  			Action: plans.Update,
   277  			Mode:   addrs.ManagedResourceMode,
   278  			Before: cty.ObjectVal(map[string]cty.Value{
   279  				"id": cty.StringVal("i-02ae66f368e8518a9"),
   280  				"more_lines": cty.StringVal(`original
   281  long
   282  multi-line
   283  string
   284  field
   285  `),
   286  			}),
   287  			After: cty.ObjectVal(map[string]cty.Value{
   288  				"id": cty.UnknownVal(cty.String),
   289  				"more_lines": cty.StringVal(`original
   290  extremely long
   291  multi-line
   292  string
   293  field
   294  `),
   295  			}),
   296  			Schema: &configschema.Block{
   297  				Attributes: map[string]*configschema.Attribute{
   298  					"id":         {Type: cty.String, Optional: true, Computed: true},
   299  					"more_lines": {Type: cty.String, Optional: true},
   300  				},
   301  			},
   302  			RequiredReplace: cty.NewPathSet(),
   303  			ExpectedOutput: `  # test_instance.example will be updated in-place
   304    ~ resource "test_instance" "example" {
   305        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
   306        ~ more_lines = <<-EOT
   307              original
   308            - long
   309            + extremely long
   310              multi-line
   311              string
   312              field
   313          EOT
   314      }
   315  `,
   316  		},
   317  		"addition of multi-line string field": {
   318  			Action: plans.Update,
   319  			Mode:   addrs.ManagedResourceMode,
   320  			Before: cty.ObjectVal(map[string]cty.Value{
   321  				"id":         cty.StringVal("i-02ae66f368e8518a9"),
   322  				"more_lines": cty.NullVal(cty.String),
   323  			}),
   324  			After: cty.ObjectVal(map[string]cty.Value{
   325  				"id": cty.UnknownVal(cty.String),
   326  				"more_lines": cty.StringVal(`original
   327  new line
   328  `),
   329  			}),
   330  			Schema: &configschema.Block{
   331  				Attributes: map[string]*configschema.Attribute{
   332  					"id":         {Type: cty.String, Optional: true, Computed: true},
   333  					"more_lines": {Type: cty.String, Optional: true},
   334  				},
   335  			},
   336  			RequiredReplace: cty.NewPathSet(),
   337  			ExpectedOutput: `  # test_instance.example will be updated in-place
   338    ~ resource "test_instance" "example" {
   339        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
   340        + more_lines = <<-EOT
   341              original
   342              new line
   343          EOT
   344      }
   345  `,
   346  		},
   347  		"force-new update of multi-line string field": {
   348  			Action: plans.DeleteThenCreate,
   349  			Mode:   addrs.ManagedResourceMode,
   350  			Before: cty.ObjectVal(map[string]cty.Value{
   351  				"id": cty.StringVal("i-02ae66f368e8518a9"),
   352  				"more_lines": cty.StringVal(`original
   353  `),
   354  			}),
   355  			After: cty.ObjectVal(map[string]cty.Value{
   356  				"id": cty.UnknownVal(cty.String),
   357  				"more_lines": cty.StringVal(`original
   358  new line
   359  `),
   360  			}),
   361  			Schema: &configschema.Block{
   362  				Attributes: map[string]*configschema.Attribute{
   363  					"id":         {Type: cty.String, Optional: true, Computed: true},
   364  					"more_lines": {Type: cty.String, Optional: true},
   365  				},
   366  			},
   367  			RequiredReplace: cty.NewPathSet(cty.Path{
   368  				cty.GetAttrStep{Name: "more_lines"},
   369  			}),
   370  			ExpectedOutput: `  # test_instance.example must be replaced
   371  -/+ resource "test_instance" "example" {
   372        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
   373        ~ more_lines = <<-EOT # forces replacement
   374              original
   375            + new line
   376          EOT
   377      }
   378  `,
   379  		},
   380  
   381  		// Sensitive
   382  
   383  		"creation with sensitive field": {
   384  			Action: plans.Create,
   385  			Mode:   addrs.ManagedResourceMode,
   386  			Before: cty.NullVal(cty.EmptyObject),
   387  			After: cty.ObjectVal(map[string]cty.Value{
   388  				"id":       cty.UnknownVal(cty.String),
   389  				"password": cty.StringVal("top-secret"),
   390  				"conn_info": cty.ObjectVal(map[string]cty.Value{
   391  					"user":     cty.StringVal("not-secret"),
   392  					"password": cty.StringVal("top-secret"),
   393  				}),
   394  			}),
   395  			Schema: &configschema.Block{
   396  				Attributes: map[string]*configschema.Attribute{
   397  					"id":       {Type: cty.String, Computed: true},
   398  					"password": {Type: cty.String, Optional: true, Sensitive: true},
   399  					"conn_info": {
   400  						NestedType: &configschema.Object{
   401  							Nesting: configschema.NestingSingle,
   402  							Attributes: map[string]*configschema.Attribute{
   403  								"user":     {Type: cty.String, Optional: true},
   404  								"password": {Type: cty.String, Optional: true, Sensitive: true},
   405  							},
   406  						},
   407  					},
   408  				},
   409  			},
   410  			RequiredReplace: cty.NewPathSet(),
   411  			ExpectedOutput: `  # test_instance.example will be created
   412    + resource "test_instance" "example" {
   413        + conn_info = {
   414            + password = (sensitive value)
   415            + user     = "not-secret"
   416          }
   417        + id        = (known after apply)
   418        + password  = (sensitive value)
   419      }
   420  `,
   421  		},
   422  		"update with equal sensitive field": {
   423  			Action: plans.Update,
   424  			Mode:   addrs.ManagedResourceMode,
   425  			Before: cty.ObjectVal(map[string]cty.Value{
   426  				"id":       cty.StringVal("blah"),
   427  				"str":      cty.StringVal("before"),
   428  				"password": cty.StringVal("top-secret"),
   429  			}),
   430  			After: cty.ObjectVal(map[string]cty.Value{
   431  				"id":       cty.UnknownVal(cty.String),
   432  				"str":      cty.StringVal("after"),
   433  				"password": cty.StringVal("top-secret"),
   434  			}),
   435  			Schema: &configschema.Block{
   436  				Attributes: map[string]*configschema.Attribute{
   437  					"id":       {Type: cty.String, Computed: true},
   438  					"str":      {Type: cty.String, Optional: true},
   439  					"password": {Type: cty.String, Optional: true, Sensitive: true},
   440  				},
   441  			},
   442  			RequiredReplace: cty.NewPathSet(),
   443  			ExpectedOutput: `  # test_instance.example will be updated in-place
   444    ~ resource "test_instance" "example" {
   445        ~ id       = "blah" -> (known after apply)
   446        ~ str      = "before" -> "after"
   447          # (1 unchanged attribute hidden)
   448      }
   449  `,
   450  		},
   451  
   452  		// tainted objects
   453  		"replace tainted resource": {
   454  			Action:       plans.DeleteThenCreate,
   455  			ActionReason: plans.ResourceInstanceReplaceBecauseTainted,
   456  			Mode:         addrs.ManagedResourceMode,
   457  			Before: cty.ObjectVal(map[string]cty.Value{
   458  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
   459  				"ami": cty.StringVal("ami-BEFORE"),
   460  			}),
   461  			After: cty.ObjectVal(map[string]cty.Value{
   462  				"id":  cty.UnknownVal(cty.String),
   463  				"ami": cty.StringVal("ami-AFTER"),
   464  			}),
   465  			Schema: &configschema.Block{
   466  				Attributes: map[string]*configschema.Attribute{
   467  					"id":  {Type: cty.String, Optional: true, Computed: true},
   468  					"ami": {Type: cty.String, Optional: true},
   469  				},
   470  			},
   471  			RequiredReplace: cty.NewPathSet(cty.Path{
   472  				cty.GetAttrStep{Name: "ami"},
   473  			}),
   474  			ExpectedOutput: `  # test_instance.example is tainted, so must be replaced
   475  -/+ resource "test_instance" "example" {
   476        ~ ami = "ami-BEFORE" -> "ami-AFTER" # forces replacement
   477        ~ id  = "i-02ae66f368e8518a9" -> (known after apply)
   478      }
   479  `,
   480  		},
   481  		"force replacement with empty before value": {
   482  			Action:       plans.DeleteThenCreate,
   483  			ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate,
   484  			Mode:         addrs.ManagedResourceMode,
   485  			Before: cty.ObjectVal(map[string]cty.Value{
   486  				"name":   cty.StringVal("name"),
   487  				"forced": cty.NullVal(cty.String),
   488  			}),
   489  			After: cty.ObjectVal(map[string]cty.Value{
   490  				"name":   cty.StringVal("name"),
   491  				"forced": cty.StringVal("example"),
   492  			}),
   493  			Schema: &configschema.Block{
   494  				Attributes: map[string]*configschema.Attribute{
   495  					"name":   {Type: cty.String, Optional: true},
   496  					"forced": {Type: cty.String, Optional: true},
   497  				},
   498  			},
   499  			RequiredReplace: cty.NewPathSet(cty.Path{
   500  				cty.GetAttrStep{Name: "forced"},
   501  			}),
   502  			ExpectedOutput: `  # test_instance.example must be replaced
   503  -/+ resource "test_instance" "example" {
   504        + forced = "example" # forces replacement
   505          name   = "name"
   506      }
   507  `,
   508  		},
   509  		"force replacement with empty before value legacy": {
   510  			Action:       plans.DeleteThenCreate,
   511  			ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate,
   512  			Mode:         addrs.ManagedResourceMode,
   513  			Before: cty.ObjectVal(map[string]cty.Value{
   514  				"name":   cty.StringVal("name"),
   515  				"forced": cty.StringVal(""),
   516  			}),
   517  			After: cty.ObjectVal(map[string]cty.Value{
   518  				"name":   cty.StringVal("name"),
   519  				"forced": cty.StringVal("example"),
   520  			}),
   521  			Schema: &configschema.Block{
   522  				Attributes: map[string]*configschema.Attribute{
   523  					"name":   {Type: cty.String, Optional: true},
   524  					"forced": {Type: cty.String, Optional: true},
   525  				},
   526  			},
   527  			RequiredReplace: cty.NewPathSet(cty.Path{
   528  				cty.GetAttrStep{Name: "forced"},
   529  			}),
   530  			ExpectedOutput: `  # test_instance.example must be replaced
   531  -/+ resource "test_instance" "example" {
   532        + forced = "example" # forces replacement
   533          name   = "name"
   534      }
   535  `,
   536  		},
   537  		"read during apply because of unknown configuration": {
   538  			Action:       plans.Read,
   539  			ActionReason: plans.ResourceInstanceReadBecauseConfigUnknown,
   540  			Mode:         addrs.DataResourceMode,
   541  			Before: cty.ObjectVal(map[string]cty.Value{
   542  				"name": cty.StringVal("name"),
   543  			}),
   544  			After: cty.ObjectVal(map[string]cty.Value{
   545  				"name": cty.StringVal("name"),
   546  			}),
   547  			Schema: &configschema.Block{
   548  				Attributes: map[string]*configschema.Attribute{
   549  					"name": {Type: cty.String, Optional: true},
   550  				},
   551  			},
   552  			ExpectedOutput: `  # data.test_instance.example will be read during apply
   553    # (config refers to values not yet known)
   554   <= data "test_instance" "example" {
   555          name = "name"
   556      }
   557  `,
   558  		},
   559  		"read during apply because of pending changes to upstream dependency": {
   560  			Action:       plans.Read,
   561  			ActionReason: plans.ResourceInstanceReadBecauseDependencyPending,
   562  			Mode:         addrs.DataResourceMode,
   563  			Before: cty.ObjectVal(map[string]cty.Value{
   564  				"name": cty.StringVal("name"),
   565  			}),
   566  			After: cty.ObjectVal(map[string]cty.Value{
   567  				"name": cty.StringVal("name"),
   568  			}),
   569  			Schema: &configschema.Block{
   570  				Attributes: map[string]*configschema.Attribute{
   571  					"name": {Type: cty.String, Optional: true},
   572  				},
   573  			},
   574  			ExpectedOutput: `  # data.test_instance.example will be read during apply
   575    # (depends on a resource or a module with changes pending)
   576   <= data "test_instance" "example" {
   577          name = "name"
   578      }
   579  `,
   580  		},
   581  		"read during apply for unspecified reason": {
   582  			Action: plans.Read,
   583  			Mode:   addrs.DataResourceMode,
   584  			Before: cty.ObjectVal(map[string]cty.Value{
   585  				"name": cty.StringVal("name"),
   586  			}),
   587  			After: cty.ObjectVal(map[string]cty.Value{
   588  				"name": cty.StringVal("name"),
   589  			}),
   590  			Schema: &configschema.Block{
   591  				Attributes: map[string]*configschema.Attribute{
   592  					"name": {Type: cty.String, Optional: true},
   593  				},
   594  			},
   595  			ExpectedOutput: `  # data.test_instance.example will be read during apply
   596   <= data "test_instance" "example" {
   597          name = "name"
   598      }
   599  `,
   600  		},
   601  		"show all identifying attributes even if unchanged": {
   602  			Action: plans.Update,
   603  			Mode:   addrs.ManagedResourceMode,
   604  			Before: cty.ObjectVal(map[string]cty.Value{
   605  				"id":   cty.StringVal("i-02ae66f368e8518a9"),
   606  				"ami":  cty.StringVal("ami-BEFORE"),
   607  				"bar":  cty.StringVal("bar"),
   608  				"foo":  cty.StringVal("foo"),
   609  				"name": cty.StringVal("alice"),
   610  				"tags": cty.MapVal(map[string]cty.Value{
   611  					"name": cty.StringVal("bob"),
   612  				}),
   613  			}),
   614  			After: cty.ObjectVal(map[string]cty.Value{
   615  				"id":   cty.StringVal("i-02ae66f368e8518a9"),
   616  				"ami":  cty.StringVal("ami-AFTER"),
   617  				"bar":  cty.StringVal("bar"),
   618  				"foo":  cty.StringVal("foo"),
   619  				"name": cty.StringVal("alice"),
   620  				"tags": cty.MapVal(map[string]cty.Value{
   621  					"name": cty.StringVal("bob"),
   622  				}),
   623  			}),
   624  			Schema: &configschema.Block{
   625  				Attributes: map[string]*configschema.Attribute{
   626  					"id":   {Type: cty.String, Optional: true, Computed: true},
   627  					"ami":  {Type: cty.String, Optional: true},
   628  					"bar":  {Type: cty.String, Optional: true},
   629  					"foo":  {Type: cty.String, Optional: true},
   630  					"name": {Type: cty.String, Optional: true},
   631  					"tags": {Type: cty.Map(cty.String), Optional: true},
   632  				},
   633  			},
   634  			RequiredReplace: cty.NewPathSet(),
   635  			ExpectedOutput: `  # test_instance.example will be updated in-place
   636    ~ resource "test_instance" "example" {
   637        ~ ami  = "ami-BEFORE" -> "ami-AFTER"
   638          id   = "i-02ae66f368e8518a9"
   639          name = "alice"
   640          tags = {
   641              "name" = "bob"
   642          }
   643          # (2 unchanged attributes hidden)
   644      }
   645  `,
   646  		},
   647  	}
   648  
   649  	runTestCases(t, testCases)
   650  }
   651  
   652  func TestResourceChange_JSON(t *testing.T) {
   653  	testCases := map[string]testCase{
   654  		"creation": {
   655  			Action: plans.Create,
   656  			Mode:   addrs.ManagedResourceMode,
   657  			Before: cty.NullVal(cty.EmptyObject),
   658  			After: cty.ObjectVal(map[string]cty.Value{
   659  				"id": cty.UnknownVal(cty.String),
   660  				"json_field": cty.StringVal(`{
   661  					"str": "value",
   662  					"list":["a","b", 234, true],
   663  					"obj": {"key": "val"}
   664  				}`),
   665  			}),
   666  			Schema: &configschema.Block{
   667  				Attributes: map[string]*configschema.Attribute{
   668  					"id":         {Type: cty.String, Optional: true, Computed: true},
   669  					"json_field": {Type: cty.String, Optional: true},
   670  				},
   671  			},
   672  			RequiredReplace: cty.NewPathSet(),
   673  			ExpectedOutput: `  # test_instance.example will be created
   674    + resource "test_instance" "example" {
   675        + id         = (known after apply)
   676        + json_field = jsonencode(
   677              {
   678                + list = [
   679                    + "a",
   680                    + "b",
   681                    + 234,
   682                    + true,
   683                  ]
   684                + obj  = {
   685                    + key = "val"
   686                  }
   687                + str  = "value"
   688              }
   689          )
   690      }
   691  `,
   692  		},
   693  		"in-place update of object": {
   694  			Action: plans.Update,
   695  			Mode:   addrs.ManagedResourceMode,
   696  			Before: cty.ObjectVal(map[string]cty.Value{
   697  				"id":         cty.StringVal("i-02ae66f368e8518a9"),
   698  				"json_field": cty.StringVal(`{"aaa": "value","ccc": 5}`),
   699  			}),
   700  			After: cty.ObjectVal(map[string]cty.Value{
   701  				"id":         cty.UnknownVal(cty.String),
   702  				"json_field": cty.StringVal(`{"aaa": "value", "bbb": "new_value"}`),
   703  			}),
   704  			Schema: &configschema.Block{
   705  				Attributes: map[string]*configschema.Attribute{
   706  					"id":         {Type: cty.String, Optional: true, Computed: true},
   707  					"json_field": {Type: cty.String, Optional: true},
   708  				},
   709  			},
   710  			RequiredReplace: cty.NewPathSet(),
   711  			ExpectedOutput: `  # test_instance.example will be updated in-place
   712    ~ resource "test_instance" "example" {
   713        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
   714        ~ json_field = jsonencode(
   715            ~ {
   716                + bbb = "new_value"
   717                - ccc = 5 -> null
   718                  # (1 unchanged element hidden)
   719              }
   720          )
   721      }
   722  `,
   723  		},
   724  		"in-place update of object with quoted keys": {
   725  			Action: plans.Update,
   726  			Mode:   addrs.ManagedResourceMode,
   727  			Before: cty.ObjectVal(map[string]cty.Value{
   728  				"id":         cty.StringVal("i-02ae66f368e8518a9"),
   729  				"json_field": cty.StringVal(`{"aaa": "value", "c:c": "old_value"}`),
   730  			}),
   731  			After: cty.ObjectVal(map[string]cty.Value{
   732  				"id":         cty.UnknownVal(cty.String),
   733  				"json_field": cty.StringVal(`{"aaa": "value", "b:bb": "new_value"}`),
   734  			}),
   735  			Schema: &configschema.Block{
   736  				Attributes: map[string]*configschema.Attribute{
   737  					"id":         {Type: cty.String, Optional: true, Computed: true},
   738  					"json_field": {Type: cty.String, Optional: true},
   739  				},
   740  			},
   741  			RequiredReplace: cty.NewPathSet(),
   742  			ExpectedOutput: `  # test_instance.example will be updated in-place
   743    ~ resource "test_instance" "example" {
   744        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
   745        ~ json_field = jsonencode(
   746            ~ {
   747                + "b:bb" = "new_value"
   748                - "c:c"  = "old_value" -> null
   749                  # (1 unchanged element hidden)
   750              }
   751          )
   752      }
   753  `,
   754  		},
   755  		"in-place update (from empty tuple)": {
   756  			Action: plans.Update,
   757  			Mode:   addrs.ManagedResourceMode,
   758  			Before: cty.ObjectVal(map[string]cty.Value{
   759  				"id":         cty.StringVal("i-02ae66f368e8518a9"),
   760  				"json_field": cty.StringVal(`{"aaa": []}`),
   761  			}),
   762  			After: cty.ObjectVal(map[string]cty.Value{
   763  				"id":         cty.UnknownVal(cty.String),
   764  				"json_field": cty.StringVal(`{"aaa": ["value"]}`),
   765  			}),
   766  			Schema: &configschema.Block{
   767  				Attributes: map[string]*configschema.Attribute{
   768  					"id":         {Type: cty.String, Optional: true, Computed: true},
   769  					"json_field": {Type: cty.String, Optional: true},
   770  				},
   771  			},
   772  			RequiredReplace: cty.NewPathSet(),
   773  			ExpectedOutput: `  # test_instance.example will be updated in-place
   774    ~ resource "test_instance" "example" {
   775        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
   776        ~ json_field = jsonencode(
   777            ~ {
   778                ~ aaa = [
   779                    + "value",
   780                  ]
   781              }
   782          )
   783      }
   784  `,
   785  		},
   786  		"in-place update (to empty tuple)": {
   787  			Action: plans.Update,
   788  			Mode:   addrs.ManagedResourceMode,
   789  			Before: cty.ObjectVal(map[string]cty.Value{
   790  				"id":         cty.StringVal("i-02ae66f368e8518a9"),
   791  				"json_field": cty.StringVal(`{"aaa": ["value"]}`),
   792  			}),
   793  			After: cty.ObjectVal(map[string]cty.Value{
   794  				"id":         cty.UnknownVal(cty.String),
   795  				"json_field": cty.StringVal(`{"aaa": []}`),
   796  			}),
   797  			Schema: &configschema.Block{
   798  				Attributes: map[string]*configschema.Attribute{
   799  					"id":         {Type: cty.String, Optional: true, Computed: true},
   800  					"json_field": {Type: cty.String, Optional: true},
   801  				},
   802  			},
   803  			RequiredReplace: cty.NewPathSet(),
   804  			ExpectedOutput: `  # test_instance.example will be updated in-place
   805    ~ resource "test_instance" "example" {
   806        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
   807        ~ json_field = jsonencode(
   808            ~ {
   809                ~ aaa = [
   810                    - "value",
   811                  ]
   812              }
   813          )
   814      }
   815  `,
   816  		},
   817  		"in-place update (tuple of different types)": {
   818  			Action: plans.Update,
   819  			Mode:   addrs.ManagedResourceMode,
   820  			Before: cty.ObjectVal(map[string]cty.Value{
   821  				"id":         cty.StringVal("i-02ae66f368e8518a9"),
   822  				"json_field": cty.StringVal(`{"aaa": [42, {"foo":"bar"}, "value"]}`),
   823  			}),
   824  			After: cty.ObjectVal(map[string]cty.Value{
   825  				"id":         cty.UnknownVal(cty.String),
   826  				"json_field": cty.StringVal(`{"aaa": [42, {"foo":"baz"}, "value"]}`),
   827  			}),
   828  			Schema: &configschema.Block{
   829  				Attributes: map[string]*configschema.Attribute{
   830  					"id":         {Type: cty.String, Optional: true, Computed: true},
   831  					"json_field": {Type: cty.String, Optional: true},
   832  				},
   833  			},
   834  			RequiredReplace: cty.NewPathSet(),
   835  			ExpectedOutput: `  # test_instance.example will be updated in-place
   836    ~ resource "test_instance" "example" {
   837        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
   838        ~ json_field = jsonencode(
   839            ~ {
   840                ~ aaa = [
   841                      42,
   842                    ~ {
   843                        ~ foo = "bar" -> "baz"
   844                      },
   845                      "value",
   846                  ]
   847              }
   848          )
   849      }
   850  `,
   851  		},
   852  		"force-new update": {
   853  			Action:       plans.DeleteThenCreate,
   854  			ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate,
   855  			Mode:         addrs.ManagedResourceMode,
   856  			Before: cty.ObjectVal(map[string]cty.Value{
   857  				"id":         cty.StringVal("i-02ae66f368e8518a9"),
   858  				"json_field": cty.StringVal(`{"aaa": "value"}`),
   859  			}),
   860  			After: cty.ObjectVal(map[string]cty.Value{
   861  				"id":         cty.UnknownVal(cty.String),
   862  				"json_field": cty.StringVal(`{"aaa": "value", "bbb": "new_value"}`),
   863  			}),
   864  			Schema: &configschema.Block{
   865  				Attributes: map[string]*configschema.Attribute{
   866  					"id":         {Type: cty.String, Optional: true, Computed: true},
   867  					"json_field": {Type: cty.String, Optional: true},
   868  				},
   869  			},
   870  			RequiredReplace: cty.NewPathSet(cty.Path{
   871  				cty.GetAttrStep{Name: "json_field"},
   872  			}),
   873  			ExpectedOutput: `  # test_instance.example must be replaced
   874  -/+ resource "test_instance" "example" {
   875        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
   876        ~ json_field = jsonencode(
   877            ~ {
   878                + bbb = "new_value"
   879                  # (1 unchanged element hidden)
   880              } # forces replacement
   881          )
   882      }
   883  `,
   884  		},
   885  		"in-place update (whitespace change)": {
   886  			Action: plans.Update,
   887  			Mode:   addrs.ManagedResourceMode,
   888  			Before: cty.ObjectVal(map[string]cty.Value{
   889  				"id":         cty.StringVal("i-02ae66f368e8518a9"),
   890  				"json_field": cty.StringVal(`{"aaa": "value", "bbb": "another"}`),
   891  			}),
   892  			After: cty.ObjectVal(map[string]cty.Value{
   893  				"id": cty.UnknownVal(cty.String),
   894  				"json_field": cty.StringVal(`{"aaa":"value",
   895  					"bbb":"another"}`),
   896  			}),
   897  			Schema: &configschema.Block{
   898  				Attributes: map[string]*configschema.Attribute{
   899  					"id":         {Type: cty.String, Optional: true, Computed: true},
   900  					"json_field": {Type: cty.String, Optional: true},
   901  				},
   902  			},
   903  			RequiredReplace: cty.NewPathSet(),
   904  			ExpectedOutput: `  # test_instance.example will be updated in-place
   905    ~ resource "test_instance" "example" {
   906        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
   907        ~ json_field = jsonencode( # whitespace changes
   908              {
   909                  aaa = "value"
   910                  bbb = "another"
   911              }
   912          )
   913      }
   914  `,
   915  		},
   916  		"force-new update (whitespace change)": {
   917  			Action:       plans.DeleteThenCreate,
   918  			ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate,
   919  			Mode:         addrs.ManagedResourceMode,
   920  			Before: cty.ObjectVal(map[string]cty.Value{
   921  				"id":         cty.StringVal("i-02ae66f368e8518a9"),
   922  				"json_field": cty.StringVal(`{"aaa": "value", "bbb": "another"}`),
   923  			}),
   924  			After: cty.ObjectVal(map[string]cty.Value{
   925  				"id": cty.UnknownVal(cty.String),
   926  				"json_field": cty.StringVal(`{"aaa":"value",
   927  					"bbb":"another"}`),
   928  			}),
   929  			Schema: &configschema.Block{
   930  				Attributes: map[string]*configschema.Attribute{
   931  					"id":         {Type: cty.String, Optional: true, Computed: true},
   932  					"json_field": {Type: cty.String, Optional: true},
   933  				},
   934  			},
   935  			RequiredReplace: cty.NewPathSet(cty.Path{
   936  				cty.GetAttrStep{Name: "json_field"},
   937  			}),
   938  			ExpectedOutput: `  # test_instance.example must be replaced
   939  -/+ resource "test_instance" "example" {
   940        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
   941        ~ json_field = jsonencode( # whitespace changes force replacement
   942              {
   943                  aaa = "value"
   944                  bbb = "another"
   945              }
   946          )
   947      }
   948  `,
   949  		},
   950  		"creation (empty)": {
   951  			Action: plans.Create,
   952  			Mode:   addrs.ManagedResourceMode,
   953  			Before: cty.NullVal(cty.EmptyObject),
   954  			After: cty.ObjectVal(map[string]cty.Value{
   955  				"id":         cty.UnknownVal(cty.String),
   956  				"json_field": cty.StringVal(`{}`),
   957  			}),
   958  			Schema: &configschema.Block{
   959  				Attributes: map[string]*configschema.Attribute{
   960  					"id":         {Type: cty.String, Optional: true, Computed: true},
   961  					"json_field": {Type: cty.String, Optional: true},
   962  				},
   963  			},
   964  			RequiredReplace: cty.NewPathSet(),
   965  			ExpectedOutput: `  # test_instance.example will be created
   966    + resource "test_instance" "example" {
   967        + id         = (known after apply)
   968        + json_field = jsonencode({})
   969      }
   970  `,
   971  		},
   972  		"JSON list item removal": {
   973  			Action: plans.Update,
   974  			Mode:   addrs.ManagedResourceMode,
   975  			Before: cty.ObjectVal(map[string]cty.Value{
   976  				"id":         cty.StringVal("i-02ae66f368e8518a9"),
   977  				"json_field": cty.StringVal(`["first","second","third"]`),
   978  			}),
   979  			After: cty.ObjectVal(map[string]cty.Value{
   980  				"id":         cty.UnknownVal(cty.String),
   981  				"json_field": cty.StringVal(`["first","second"]`),
   982  			}),
   983  			Schema: &configschema.Block{
   984  				Attributes: map[string]*configschema.Attribute{
   985  					"id":         {Type: cty.String, Optional: true, Computed: true},
   986  					"json_field": {Type: cty.String, Optional: true},
   987  				},
   988  			},
   989  			RequiredReplace: cty.NewPathSet(),
   990  			ExpectedOutput: `  # test_instance.example will be updated in-place
   991    ~ resource "test_instance" "example" {
   992        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
   993        ~ json_field = jsonencode(
   994            ~ [
   995                  # (1 unchanged element hidden)
   996                  "second",
   997                - "third",
   998              ]
   999          )
  1000      }
  1001  `,
  1002  		},
  1003  		"JSON list item addition": {
  1004  			Action: plans.Update,
  1005  			Mode:   addrs.ManagedResourceMode,
  1006  			Before: cty.ObjectVal(map[string]cty.Value{
  1007  				"id":         cty.StringVal("i-02ae66f368e8518a9"),
  1008  				"json_field": cty.StringVal(`["first","second"]`),
  1009  			}),
  1010  			After: cty.ObjectVal(map[string]cty.Value{
  1011  				"id":         cty.UnknownVal(cty.String),
  1012  				"json_field": cty.StringVal(`["first","second","third"]`),
  1013  			}),
  1014  			Schema: &configschema.Block{
  1015  				Attributes: map[string]*configschema.Attribute{
  1016  					"id":         {Type: cty.String, Optional: true, Computed: true},
  1017  					"json_field": {Type: cty.String, Optional: true},
  1018  				},
  1019  			},
  1020  			RequiredReplace: cty.NewPathSet(),
  1021  			ExpectedOutput: `  # test_instance.example will be updated in-place
  1022    ~ resource "test_instance" "example" {
  1023        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
  1024        ~ json_field = jsonencode(
  1025            ~ [
  1026                  # (1 unchanged element hidden)
  1027                  "second",
  1028                + "third",
  1029              ]
  1030          )
  1031      }
  1032  `,
  1033  		},
  1034  		"JSON list object addition": {
  1035  			Action: plans.Update,
  1036  			Mode:   addrs.ManagedResourceMode,
  1037  			Before: cty.ObjectVal(map[string]cty.Value{
  1038  				"id":         cty.StringVal("i-02ae66f368e8518a9"),
  1039  				"json_field": cty.StringVal(`{"first":"111"}`),
  1040  			}),
  1041  			After: cty.ObjectVal(map[string]cty.Value{
  1042  				"id":         cty.UnknownVal(cty.String),
  1043  				"json_field": cty.StringVal(`{"first":"111","second":"222"}`),
  1044  			}),
  1045  			Schema: &configschema.Block{
  1046  				Attributes: map[string]*configschema.Attribute{
  1047  					"id":         {Type: cty.String, Optional: true, Computed: true},
  1048  					"json_field": {Type: cty.String, Optional: true},
  1049  				},
  1050  			},
  1051  			RequiredReplace: cty.NewPathSet(),
  1052  			ExpectedOutput: `  # test_instance.example will be updated in-place
  1053    ~ resource "test_instance" "example" {
  1054        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
  1055        ~ json_field = jsonencode(
  1056            ~ {
  1057                + second = "222"
  1058                  # (1 unchanged element hidden)
  1059              }
  1060          )
  1061      }
  1062  `,
  1063  		},
  1064  		"JSON object with nested list": {
  1065  			Action: plans.Update,
  1066  			Mode:   addrs.ManagedResourceMode,
  1067  			Before: cty.ObjectVal(map[string]cty.Value{
  1068  				"id": cty.StringVal("i-02ae66f368e8518a9"),
  1069  				"json_field": cty.StringVal(`{
  1070  		  "Statement": ["first"]
  1071  		}`),
  1072  			}),
  1073  			After: cty.ObjectVal(map[string]cty.Value{
  1074  				"id": cty.UnknownVal(cty.String),
  1075  				"json_field": cty.StringVal(`{
  1076  		  "Statement": ["first", "second"]
  1077  		}`),
  1078  			}),
  1079  			Schema: &configschema.Block{
  1080  				Attributes: map[string]*configschema.Attribute{
  1081  					"id":         {Type: cty.String, Optional: true, Computed: true},
  1082  					"json_field": {Type: cty.String, Optional: true},
  1083  				},
  1084  			},
  1085  			RequiredReplace: cty.NewPathSet(),
  1086  			ExpectedOutput: `  # test_instance.example will be updated in-place
  1087    ~ resource "test_instance" "example" {
  1088        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
  1089        ~ json_field = jsonencode(
  1090            ~ {
  1091                ~ Statement = [
  1092                      "first",
  1093                    + "second",
  1094                  ]
  1095              }
  1096          )
  1097      }
  1098  `,
  1099  		},
  1100  		"JSON list of objects - adding item": {
  1101  			Action: plans.Update,
  1102  			Mode:   addrs.ManagedResourceMode,
  1103  			Before: cty.ObjectVal(map[string]cty.Value{
  1104  				"id":         cty.StringVal("i-02ae66f368e8518a9"),
  1105  				"json_field": cty.StringVal(`[{"one": "111"}]`),
  1106  			}),
  1107  			After: cty.ObjectVal(map[string]cty.Value{
  1108  				"id":         cty.UnknownVal(cty.String),
  1109  				"json_field": cty.StringVal(`[{"one": "111"}, {"two": "222"}]`),
  1110  			}),
  1111  			Schema: &configschema.Block{
  1112  				Attributes: map[string]*configschema.Attribute{
  1113  					"id":         {Type: cty.String, Optional: true, Computed: true},
  1114  					"json_field": {Type: cty.String, Optional: true},
  1115  				},
  1116  			},
  1117  			RequiredReplace: cty.NewPathSet(),
  1118  			ExpectedOutput: `  # test_instance.example will be updated in-place
  1119    ~ resource "test_instance" "example" {
  1120        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
  1121        ~ json_field = jsonencode(
  1122            ~ [
  1123                  {
  1124                      one = "111"
  1125                  },
  1126                + {
  1127                    + two = "222"
  1128                  },
  1129              ]
  1130          )
  1131      }
  1132  `,
  1133  		},
  1134  		"JSON list of objects - removing item": {
  1135  			Action: plans.Update,
  1136  			Mode:   addrs.ManagedResourceMode,
  1137  			Before: cty.ObjectVal(map[string]cty.Value{
  1138  				"id":         cty.StringVal("i-02ae66f368e8518a9"),
  1139  				"json_field": cty.StringVal(`[{"one": "111"}, {"two": "222"}, {"three": "333"}]`),
  1140  			}),
  1141  			After: cty.ObjectVal(map[string]cty.Value{
  1142  				"id":         cty.UnknownVal(cty.String),
  1143  				"json_field": cty.StringVal(`[{"one": "111"}, {"three": "333"}]`),
  1144  			}),
  1145  			Schema: &configschema.Block{
  1146  				Attributes: map[string]*configschema.Attribute{
  1147  					"id":         {Type: cty.String, Optional: true, Computed: true},
  1148  					"json_field": {Type: cty.String, Optional: true},
  1149  				},
  1150  			},
  1151  			RequiredReplace: cty.NewPathSet(),
  1152  			ExpectedOutput: `  # test_instance.example will be updated in-place
  1153    ~ resource "test_instance" "example" {
  1154        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
  1155        ~ json_field = jsonencode(
  1156            ~ [
  1157                  {
  1158                      one = "111"
  1159                  },
  1160                - {
  1161                    - two = "222"
  1162                  },
  1163                  {
  1164                      three = "333"
  1165                  },
  1166              ]
  1167          )
  1168      }
  1169  `,
  1170  		},
  1171  		"JSON object with list of objects": {
  1172  			Action: plans.Update,
  1173  			Mode:   addrs.ManagedResourceMode,
  1174  			Before: cty.ObjectVal(map[string]cty.Value{
  1175  				"id":         cty.StringVal("i-02ae66f368e8518a9"),
  1176  				"json_field": cty.StringVal(`{"parent":[{"one": "111"}]}`),
  1177  			}),
  1178  			After: cty.ObjectVal(map[string]cty.Value{
  1179  				"id":         cty.UnknownVal(cty.String),
  1180  				"json_field": cty.StringVal(`{"parent":[{"one": "111"}, {"two": "222"}]}`),
  1181  			}),
  1182  			Schema: &configschema.Block{
  1183  				Attributes: map[string]*configschema.Attribute{
  1184  					"id":         {Type: cty.String, Optional: true, Computed: true},
  1185  					"json_field": {Type: cty.String, Optional: true},
  1186  				},
  1187  			},
  1188  			RequiredReplace: cty.NewPathSet(),
  1189  			ExpectedOutput: `  # test_instance.example will be updated in-place
  1190    ~ resource "test_instance" "example" {
  1191        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
  1192        ~ json_field = jsonencode(
  1193            ~ {
  1194                ~ parent = [
  1195                      {
  1196                          one = "111"
  1197                      },
  1198                    + {
  1199                        + two = "222"
  1200                      },
  1201                  ]
  1202              }
  1203          )
  1204      }
  1205  `,
  1206  		},
  1207  		"JSON object double nested lists": {
  1208  			Action: plans.Update,
  1209  			Mode:   addrs.ManagedResourceMode,
  1210  			Before: cty.ObjectVal(map[string]cty.Value{
  1211  				"id":         cty.StringVal("i-02ae66f368e8518a9"),
  1212  				"json_field": cty.StringVal(`{"parent":[{"another_list": ["111"]}]}`),
  1213  			}),
  1214  			After: cty.ObjectVal(map[string]cty.Value{
  1215  				"id":         cty.UnknownVal(cty.String),
  1216  				"json_field": cty.StringVal(`{"parent":[{"another_list": ["111", "222"]}]}`),
  1217  			}),
  1218  			Schema: &configschema.Block{
  1219  				Attributes: map[string]*configschema.Attribute{
  1220  					"id":         {Type: cty.String, Optional: true, Computed: true},
  1221  					"json_field": {Type: cty.String, Optional: true},
  1222  				},
  1223  			},
  1224  			RequiredReplace: cty.NewPathSet(),
  1225  			ExpectedOutput: `  # test_instance.example will be updated in-place
  1226    ~ resource "test_instance" "example" {
  1227        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
  1228        ~ json_field = jsonencode(
  1229            ~ {
  1230                ~ parent = [
  1231                    ~ {
  1232                        ~ another_list = [
  1233                              "111",
  1234                            + "222",
  1235                          ]
  1236                      },
  1237                  ]
  1238              }
  1239          )
  1240      }
  1241  `,
  1242  		},
  1243  		"in-place update from object to tuple": {
  1244  			Action: plans.Update,
  1245  			Mode:   addrs.ManagedResourceMode,
  1246  			Before: cty.ObjectVal(map[string]cty.Value{
  1247  				"id":         cty.StringVal("i-02ae66f368e8518a9"),
  1248  				"json_field": cty.StringVal(`{"aaa": [42, {"foo":"bar"}, "value"]}`),
  1249  			}),
  1250  			After: cty.ObjectVal(map[string]cty.Value{
  1251  				"id":         cty.UnknownVal(cty.String),
  1252  				"json_field": cty.StringVal(`["aaa", 42, "something"]`),
  1253  			}),
  1254  			Schema: &configschema.Block{
  1255  				Attributes: map[string]*configschema.Attribute{
  1256  					"id":         {Type: cty.String, Optional: true, Computed: true},
  1257  					"json_field": {Type: cty.String, Optional: true},
  1258  				},
  1259  			},
  1260  			RequiredReplace: cty.NewPathSet(),
  1261  			ExpectedOutput: `  # test_instance.example will be updated in-place
  1262    ~ resource "test_instance" "example" {
  1263        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
  1264        ~ json_field = jsonencode(
  1265            ~ {
  1266                - aaa = [
  1267                    - 42,
  1268                    - {
  1269                        - foo = "bar"
  1270                      },
  1271                    - "value",
  1272                  ]
  1273              } -> [
  1274                + "aaa",
  1275                + 42,
  1276                + "something",
  1277              ]
  1278          )
  1279      }
  1280  `,
  1281  		},
  1282  	}
  1283  	runTestCases(t, testCases)
  1284  }
  1285  
  1286  func TestResourceChange_listObject(t *testing.T) {
  1287  	testCases := map[string]testCase{
  1288  		// https://github.com/hashicorp/terraform/issues/30641
  1289  		"updating non-identifying attribute": {
  1290  			Action: plans.Update,
  1291  			Mode:   addrs.ManagedResourceMode,
  1292  			Before: cty.ObjectVal(map[string]cty.Value{
  1293  				"id": cty.StringVal("i-02ae66f368e8518a9"),
  1294  				"accounts": cty.ListVal([]cty.Value{
  1295  					cty.ObjectVal(map[string]cty.Value{
  1296  						"id":     cty.StringVal("1"),
  1297  						"name":   cty.StringVal("production"),
  1298  						"status": cty.StringVal("ACTIVE"),
  1299  					}),
  1300  					cty.ObjectVal(map[string]cty.Value{
  1301  						"id":     cty.StringVal("2"),
  1302  						"name":   cty.StringVal("staging"),
  1303  						"status": cty.StringVal("ACTIVE"),
  1304  					}),
  1305  					cty.ObjectVal(map[string]cty.Value{
  1306  						"id":     cty.StringVal("3"),
  1307  						"name":   cty.StringVal("disaster-recovery"),
  1308  						"status": cty.StringVal("ACTIVE"),
  1309  					}),
  1310  				}),
  1311  			}),
  1312  			After: cty.ObjectVal(map[string]cty.Value{
  1313  				"id": cty.UnknownVal(cty.String),
  1314  				"accounts": cty.ListVal([]cty.Value{
  1315  					cty.ObjectVal(map[string]cty.Value{
  1316  						"id":     cty.StringVal("1"),
  1317  						"name":   cty.StringVal("production"),
  1318  						"status": cty.StringVal("ACTIVE"),
  1319  					}),
  1320  					cty.ObjectVal(map[string]cty.Value{
  1321  						"id":     cty.StringVal("2"),
  1322  						"name":   cty.StringVal("staging"),
  1323  						"status": cty.StringVal("EXPLODED"),
  1324  					}),
  1325  					cty.ObjectVal(map[string]cty.Value{
  1326  						"id":     cty.StringVal("3"),
  1327  						"name":   cty.StringVal("disaster-recovery"),
  1328  						"status": cty.StringVal("ACTIVE"),
  1329  					}),
  1330  				}),
  1331  			}),
  1332  			Schema: &configschema.Block{
  1333  				Attributes: map[string]*configschema.Attribute{
  1334  					"id": {Type: cty.String, Optional: true, Computed: true},
  1335  					"accounts": {
  1336  						Type: cty.List(cty.Object(map[string]cty.Type{
  1337  							"id":     cty.String,
  1338  							"name":   cty.String,
  1339  							"status": cty.String,
  1340  						})),
  1341  					},
  1342  				},
  1343  			},
  1344  			RequiredReplace: cty.NewPathSet(),
  1345  			ExpectedOutput: `  # test_instance.example will be updated in-place
  1346    ~ resource "test_instance" "example" {
  1347        ~ accounts = [
  1348              {
  1349                  id     = "1"
  1350                  name   = "production"
  1351                  status = "ACTIVE"
  1352              },
  1353            ~ {
  1354                  id     = "2"
  1355                  name   = "staging"
  1356                ~ status = "ACTIVE" -> "EXPLODED"
  1357              },
  1358              {
  1359                  id     = "3"
  1360                  name   = "disaster-recovery"
  1361                  status = "ACTIVE"
  1362              },
  1363          ]
  1364        ~ id       = "i-02ae66f368e8518a9" -> (known after apply)
  1365      }
  1366  `,
  1367  		},
  1368  	}
  1369  	runTestCases(t, testCases)
  1370  }
  1371  
  1372  func TestResourceChange_primitiveList(t *testing.T) {
  1373  	testCases := map[string]testCase{
  1374  		"in-place update - creation": {
  1375  			Action: plans.Update,
  1376  			Mode:   addrs.ManagedResourceMode,
  1377  			Before: cty.ObjectVal(map[string]cty.Value{
  1378  				"id":         cty.StringVal("i-02ae66f368e8518a9"),
  1379  				"ami":        cty.StringVal("ami-STATIC"),
  1380  				"list_field": cty.NullVal(cty.List(cty.String)),
  1381  			}),
  1382  			After: cty.ObjectVal(map[string]cty.Value{
  1383  				"id":  cty.UnknownVal(cty.String),
  1384  				"ami": cty.StringVal("ami-STATIC"),
  1385  				"list_field": cty.ListVal([]cty.Value{
  1386  					cty.StringVal("new-element"),
  1387  				}),
  1388  			}),
  1389  			Schema: &configschema.Block{
  1390  				Attributes: map[string]*configschema.Attribute{
  1391  					"id":         {Type: cty.String, Optional: true, Computed: true},
  1392  					"ami":        {Type: cty.String, Optional: true},
  1393  					"list_field": {Type: cty.List(cty.String), Optional: true},
  1394  				},
  1395  			},
  1396  			RequiredReplace: cty.NewPathSet(),
  1397  			ExpectedOutput: `  # test_instance.example will be updated in-place
  1398    ~ resource "test_instance" "example" {
  1399        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
  1400        + list_field = [
  1401            + "new-element",
  1402          ]
  1403          # (1 unchanged attribute hidden)
  1404      }
  1405  `,
  1406  		},
  1407  		"in-place update - first addition": {
  1408  			Action: plans.Update,
  1409  			Mode:   addrs.ManagedResourceMode,
  1410  			Before: cty.ObjectVal(map[string]cty.Value{
  1411  				"id":         cty.StringVal("i-02ae66f368e8518a9"),
  1412  				"ami":        cty.StringVal("ami-STATIC"),
  1413  				"list_field": cty.ListValEmpty(cty.String),
  1414  			}),
  1415  			After: cty.ObjectVal(map[string]cty.Value{
  1416  				"id":  cty.UnknownVal(cty.String),
  1417  				"ami": cty.StringVal("ami-STATIC"),
  1418  				"list_field": cty.ListVal([]cty.Value{
  1419  					cty.StringVal("new-element"),
  1420  				}),
  1421  			}),
  1422  			Schema: &configschema.Block{
  1423  				Attributes: map[string]*configschema.Attribute{
  1424  					"id":         {Type: cty.String, Optional: true, Computed: true},
  1425  					"ami":        {Type: cty.String, Optional: true},
  1426  					"list_field": {Type: cty.List(cty.String), Optional: true},
  1427  				},
  1428  			},
  1429  			RequiredReplace: cty.NewPathSet(),
  1430  			ExpectedOutput: `  # test_instance.example will be updated in-place
  1431    ~ resource "test_instance" "example" {
  1432        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
  1433        ~ list_field = [
  1434            + "new-element",
  1435          ]
  1436          # (1 unchanged attribute hidden)
  1437      }
  1438  `,
  1439  		},
  1440  		"in-place update - insertion": {
  1441  			Action: plans.Update,
  1442  			Mode:   addrs.ManagedResourceMode,
  1443  			Before: cty.ObjectVal(map[string]cty.Value{
  1444  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  1445  				"ami": cty.StringVal("ami-STATIC"),
  1446  				"list_field": cty.ListVal([]cty.Value{
  1447  					cty.StringVal("aaaa"),
  1448  					cty.StringVal("bbbb"),
  1449  					cty.StringVal("dddd"),
  1450  					cty.StringVal("eeee"),
  1451  					cty.StringVal("ffff"),
  1452  				}),
  1453  			}),
  1454  			After: cty.ObjectVal(map[string]cty.Value{
  1455  				"id":  cty.UnknownVal(cty.String),
  1456  				"ami": cty.StringVal("ami-STATIC"),
  1457  				"list_field": cty.ListVal([]cty.Value{
  1458  					cty.StringVal("aaaa"),
  1459  					cty.StringVal("bbbb"),
  1460  					cty.StringVal("cccc"),
  1461  					cty.StringVal("dddd"),
  1462  					cty.StringVal("eeee"),
  1463  					cty.StringVal("ffff"),
  1464  				}),
  1465  			}),
  1466  			Schema: &configschema.Block{
  1467  				Attributes: map[string]*configschema.Attribute{
  1468  					"id":         {Type: cty.String, Optional: true, Computed: true},
  1469  					"ami":        {Type: cty.String, Optional: true},
  1470  					"list_field": {Type: cty.List(cty.String), Optional: true},
  1471  				},
  1472  			},
  1473  			RequiredReplace: cty.NewPathSet(),
  1474  			ExpectedOutput: `  # test_instance.example will be updated in-place
  1475    ~ resource "test_instance" "example" {
  1476        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
  1477        ~ list_field = [
  1478              # (1 unchanged element hidden)
  1479              "bbbb",
  1480            + "cccc",
  1481              "dddd",
  1482              # (2 unchanged elements hidden)
  1483          ]
  1484          # (1 unchanged attribute hidden)
  1485      }
  1486  `,
  1487  		},
  1488  		"force-new update - insertion": {
  1489  			Action:       plans.DeleteThenCreate,
  1490  			ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate,
  1491  			Mode:         addrs.ManagedResourceMode,
  1492  			Before: cty.ObjectVal(map[string]cty.Value{
  1493  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  1494  				"ami": cty.StringVal("ami-STATIC"),
  1495  				"list_field": cty.ListVal([]cty.Value{
  1496  					cty.StringVal("aaaa"),
  1497  					cty.StringVal("cccc"),
  1498  				}),
  1499  			}),
  1500  			After: cty.ObjectVal(map[string]cty.Value{
  1501  				"id":  cty.UnknownVal(cty.String),
  1502  				"ami": cty.StringVal("ami-STATIC"),
  1503  				"list_field": cty.ListVal([]cty.Value{
  1504  					cty.StringVal("aaaa"),
  1505  					cty.StringVal("bbbb"),
  1506  					cty.StringVal("cccc"),
  1507  				}),
  1508  			}),
  1509  			Schema: &configschema.Block{
  1510  				Attributes: map[string]*configschema.Attribute{
  1511  					"id":         {Type: cty.String, Optional: true, Computed: true},
  1512  					"ami":        {Type: cty.String, Optional: true},
  1513  					"list_field": {Type: cty.List(cty.String), Optional: true},
  1514  				},
  1515  			},
  1516  			RequiredReplace: cty.NewPathSet(cty.Path{
  1517  				cty.GetAttrStep{Name: "list_field"},
  1518  			}),
  1519  			ExpectedOutput: `  # test_instance.example must be replaced
  1520  -/+ resource "test_instance" "example" {
  1521        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
  1522        ~ list_field = [ # forces replacement
  1523              "aaaa",
  1524            + "bbbb",
  1525              "cccc",
  1526          ]
  1527          # (1 unchanged attribute hidden)
  1528      }
  1529  `,
  1530  		},
  1531  		"in-place update - deletion": {
  1532  			Action: plans.Update,
  1533  			Mode:   addrs.ManagedResourceMode,
  1534  			Before: cty.ObjectVal(map[string]cty.Value{
  1535  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  1536  				"ami": cty.StringVal("ami-STATIC"),
  1537  				"list_field": cty.ListVal([]cty.Value{
  1538  					cty.StringVal("aaaa"),
  1539  					cty.StringVal("bbbb"),
  1540  					cty.StringVal("cccc"),
  1541  					cty.StringVal("dddd"),
  1542  					cty.StringVal("eeee"),
  1543  				}),
  1544  			}),
  1545  			After: cty.ObjectVal(map[string]cty.Value{
  1546  				"id":  cty.UnknownVal(cty.String),
  1547  				"ami": cty.StringVal("ami-STATIC"),
  1548  				"list_field": cty.ListVal([]cty.Value{
  1549  					cty.StringVal("bbbb"),
  1550  					cty.StringVal("dddd"),
  1551  					cty.StringVal("eeee"),
  1552  				}),
  1553  			}),
  1554  			Schema: &configschema.Block{
  1555  				Attributes: map[string]*configschema.Attribute{
  1556  					"id":         {Type: cty.String, Optional: true, Computed: true},
  1557  					"ami":        {Type: cty.String, Optional: true},
  1558  					"list_field": {Type: cty.List(cty.String), Optional: true},
  1559  				},
  1560  			},
  1561  			RequiredReplace: cty.NewPathSet(),
  1562  			ExpectedOutput: `  # test_instance.example will be updated in-place
  1563    ~ resource "test_instance" "example" {
  1564        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
  1565        ~ list_field = [
  1566            - "aaaa",
  1567              "bbbb",
  1568            - "cccc",
  1569              "dddd",
  1570              # (1 unchanged element hidden)
  1571          ]
  1572          # (1 unchanged attribute hidden)
  1573      }
  1574  `,
  1575  		},
  1576  		"creation - empty list": {
  1577  			Action: plans.Create,
  1578  			Mode:   addrs.ManagedResourceMode,
  1579  			Before: cty.NullVal(cty.EmptyObject),
  1580  			After: cty.ObjectVal(map[string]cty.Value{
  1581  				"id":         cty.UnknownVal(cty.String),
  1582  				"ami":        cty.StringVal("ami-STATIC"),
  1583  				"list_field": cty.ListValEmpty(cty.String),
  1584  			}),
  1585  			Schema: &configschema.Block{
  1586  				Attributes: map[string]*configschema.Attribute{
  1587  					"id":         {Type: cty.String, Optional: true, Computed: true},
  1588  					"ami":        {Type: cty.String, Optional: true},
  1589  					"list_field": {Type: cty.List(cty.String), Optional: true},
  1590  				},
  1591  			},
  1592  			RequiredReplace: cty.NewPathSet(),
  1593  			ExpectedOutput: `  # test_instance.example will be created
  1594    + resource "test_instance" "example" {
  1595        + ami        = "ami-STATIC"
  1596        + id         = (known after apply)
  1597        + list_field = []
  1598      }
  1599  `,
  1600  		},
  1601  		"in-place update - full to empty": {
  1602  			Action: plans.Update,
  1603  			Mode:   addrs.ManagedResourceMode,
  1604  			Before: cty.ObjectVal(map[string]cty.Value{
  1605  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  1606  				"ami": cty.StringVal("ami-STATIC"),
  1607  				"list_field": cty.ListVal([]cty.Value{
  1608  					cty.StringVal("aaaa"),
  1609  					cty.StringVal("bbbb"),
  1610  					cty.StringVal("cccc"),
  1611  				}),
  1612  			}),
  1613  			After: cty.ObjectVal(map[string]cty.Value{
  1614  				"id":         cty.UnknownVal(cty.String),
  1615  				"ami":        cty.StringVal("ami-STATIC"),
  1616  				"list_field": cty.ListValEmpty(cty.String),
  1617  			}),
  1618  			Schema: &configschema.Block{
  1619  				Attributes: map[string]*configschema.Attribute{
  1620  					"id":         {Type: cty.String, Optional: true, Computed: true},
  1621  					"ami":        {Type: cty.String, Optional: true},
  1622  					"list_field": {Type: cty.List(cty.String), Optional: true},
  1623  				},
  1624  			},
  1625  			RequiredReplace: cty.NewPathSet(),
  1626  			ExpectedOutput: `  # test_instance.example will be updated in-place
  1627    ~ resource "test_instance" "example" {
  1628        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
  1629        ~ list_field = [
  1630            - "aaaa",
  1631            - "bbbb",
  1632            - "cccc",
  1633          ]
  1634          # (1 unchanged attribute hidden)
  1635      }
  1636  `,
  1637  		},
  1638  		"in-place update - null to empty": {
  1639  			Action: plans.Update,
  1640  			Mode:   addrs.ManagedResourceMode,
  1641  			Before: cty.ObjectVal(map[string]cty.Value{
  1642  				"id":         cty.StringVal("i-02ae66f368e8518a9"),
  1643  				"ami":        cty.StringVal("ami-STATIC"),
  1644  				"list_field": cty.NullVal(cty.List(cty.String)),
  1645  			}),
  1646  			After: cty.ObjectVal(map[string]cty.Value{
  1647  				"id":         cty.UnknownVal(cty.String),
  1648  				"ami":        cty.StringVal("ami-STATIC"),
  1649  				"list_field": cty.ListValEmpty(cty.String),
  1650  			}),
  1651  			Schema: &configschema.Block{
  1652  				Attributes: map[string]*configschema.Attribute{
  1653  					"id":         {Type: cty.String, Optional: true, Computed: true},
  1654  					"ami":        {Type: cty.String, Optional: true},
  1655  					"list_field": {Type: cty.List(cty.String), Optional: true},
  1656  				},
  1657  			},
  1658  			RequiredReplace: cty.NewPathSet(),
  1659  			ExpectedOutput: `  # test_instance.example will be updated in-place
  1660    ~ resource "test_instance" "example" {
  1661        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
  1662        + list_field = []
  1663          # (1 unchanged attribute hidden)
  1664      }
  1665  `,
  1666  		},
  1667  		"update to unknown element": {
  1668  			Action: plans.Update,
  1669  			Mode:   addrs.ManagedResourceMode,
  1670  			Before: cty.ObjectVal(map[string]cty.Value{
  1671  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  1672  				"ami": cty.StringVal("ami-STATIC"),
  1673  				"list_field": cty.ListVal([]cty.Value{
  1674  					cty.StringVal("aaaa"),
  1675  					cty.StringVal("bbbb"),
  1676  					cty.StringVal("cccc"),
  1677  				}),
  1678  			}),
  1679  			After: cty.ObjectVal(map[string]cty.Value{
  1680  				"id":  cty.UnknownVal(cty.String),
  1681  				"ami": cty.StringVal("ami-STATIC"),
  1682  				"list_field": cty.ListVal([]cty.Value{
  1683  					cty.StringVal("aaaa"),
  1684  					cty.UnknownVal(cty.String),
  1685  					cty.StringVal("cccc"),
  1686  				}),
  1687  			}),
  1688  			Schema: &configschema.Block{
  1689  				Attributes: map[string]*configschema.Attribute{
  1690  					"id":         {Type: cty.String, Optional: true, Computed: true},
  1691  					"ami":        {Type: cty.String, Optional: true},
  1692  					"list_field": {Type: cty.List(cty.String), Optional: true},
  1693  				},
  1694  			},
  1695  			RequiredReplace: cty.NewPathSet(),
  1696  			ExpectedOutput: `  # test_instance.example will be updated in-place
  1697    ~ resource "test_instance" "example" {
  1698        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
  1699        ~ list_field = [
  1700              "aaaa",
  1701            - "bbbb",
  1702            + (known after apply),
  1703              "cccc",
  1704          ]
  1705          # (1 unchanged attribute hidden)
  1706      }
  1707  `,
  1708  		},
  1709  		"update - two new unknown elements": {
  1710  			Action: plans.Update,
  1711  			Mode:   addrs.ManagedResourceMode,
  1712  			Before: cty.ObjectVal(map[string]cty.Value{
  1713  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  1714  				"ami": cty.StringVal("ami-STATIC"),
  1715  				"list_field": cty.ListVal([]cty.Value{
  1716  					cty.StringVal("aaaa"),
  1717  					cty.StringVal("bbbb"),
  1718  					cty.StringVal("cccc"),
  1719  					cty.StringVal("dddd"),
  1720  					cty.StringVal("eeee"),
  1721  				}),
  1722  			}),
  1723  			After: cty.ObjectVal(map[string]cty.Value{
  1724  				"id":  cty.UnknownVal(cty.String),
  1725  				"ami": cty.StringVal("ami-STATIC"),
  1726  				"list_field": cty.ListVal([]cty.Value{
  1727  					cty.StringVal("aaaa"),
  1728  					cty.UnknownVal(cty.String),
  1729  					cty.UnknownVal(cty.String),
  1730  					cty.StringVal("cccc"),
  1731  					cty.StringVal("dddd"),
  1732  					cty.StringVal("eeee"),
  1733  				}),
  1734  			}),
  1735  			Schema: &configschema.Block{
  1736  				Attributes: map[string]*configschema.Attribute{
  1737  					"id":         {Type: cty.String, Optional: true, Computed: true},
  1738  					"ami":        {Type: cty.String, Optional: true},
  1739  					"list_field": {Type: cty.List(cty.String), Optional: true},
  1740  				},
  1741  			},
  1742  			RequiredReplace: cty.NewPathSet(),
  1743  			ExpectedOutput: `  # test_instance.example will be updated in-place
  1744    ~ resource "test_instance" "example" {
  1745        ~ id         = "i-02ae66f368e8518a9" -> (known after apply)
  1746        ~ list_field = [
  1747              "aaaa",
  1748            - "bbbb",
  1749            + (known after apply),
  1750            + (known after apply),
  1751              "cccc",
  1752              # (2 unchanged elements hidden)
  1753          ]
  1754          # (1 unchanged attribute hidden)
  1755      }
  1756  `,
  1757  		},
  1758  	}
  1759  	runTestCases(t, testCases)
  1760  }
  1761  
  1762  func TestResourceChange_primitiveTuple(t *testing.T) {
  1763  	testCases := map[string]testCase{
  1764  		"in-place update": {
  1765  			Action: plans.Update,
  1766  			Mode:   addrs.ManagedResourceMode,
  1767  			Before: cty.ObjectVal(map[string]cty.Value{
  1768  				"id": cty.StringVal("i-02ae66f368e8518a9"),
  1769  				"tuple_field": cty.TupleVal([]cty.Value{
  1770  					cty.StringVal("aaaa"),
  1771  					cty.StringVal("bbbb"),
  1772  					cty.StringVal("dddd"),
  1773  					cty.StringVal("eeee"),
  1774  					cty.StringVal("ffff"),
  1775  				}),
  1776  			}),
  1777  			After: cty.ObjectVal(map[string]cty.Value{
  1778  				"id": cty.StringVal("i-02ae66f368e8518a9"),
  1779  				"tuple_field": cty.TupleVal([]cty.Value{
  1780  					cty.StringVal("aaaa"),
  1781  					cty.StringVal("bbbb"),
  1782  					cty.StringVal("cccc"),
  1783  					cty.StringVal("eeee"),
  1784  					cty.StringVal("ffff"),
  1785  				}),
  1786  			}),
  1787  			Schema: &configschema.Block{
  1788  				Attributes: map[string]*configschema.Attribute{
  1789  					"id":          {Type: cty.String, Required: true},
  1790  					"tuple_field": {Type: cty.Tuple([]cty.Type{cty.String, cty.String, cty.String, cty.String, cty.String}), Optional: true},
  1791  				},
  1792  			},
  1793  			RequiredReplace: cty.NewPathSet(),
  1794  			ExpectedOutput: `  # test_instance.example will be updated in-place
  1795    ~ resource "test_instance" "example" {
  1796          id          = "i-02ae66f368e8518a9"
  1797        ~ tuple_field = [
  1798              # (1 unchanged element hidden)
  1799              "bbbb",
  1800            - "dddd",
  1801            + "cccc",
  1802              "eeee",
  1803              # (1 unchanged element hidden)
  1804          ]
  1805      }
  1806  `,
  1807  		},
  1808  	}
  1809  	runTestCases(t, testCases)
  1810  }
  1811  
  1812  func TestResourceChange_primitiveSet(t *testing.T) {
  1813  	testCases := map[string]testCase{
  1814  		"in-place update - creation": {
  1815  			Action: plans.Update,
  1816  			Mode:   addrs.ManagedResourceMode,
  1817  			Before: cty.ObjectVal(map[string]cty.Value{
  1818  				"id":        cty.StringVal("i-02ae66f368e8518a9"),
  1819  				"ami":       cty.StringVal("ami-STATIC"),
  1820  				"set_field": cty.NullVal(cty.Set(cty.String)),
  1821  			}),
  1822  			After: cty.ObjectVal(map[string]cty.Value{
  1823  				"id":  cty.UnknownVal(cty.String),
  1824  				"ami": cty.StringVal("ami-STATIC"),
  1825  				"set_field": cty.SetVal([]cty.Value{
  1826  					cty.StringVal("new-element"),
  1827  				}),
  1828  			}),
  1829  			Schema: &configschema.Block{
  1830  				Attributes: map[string]*configschema.Attribute{
  1831  					"id":        {Type: cty.String, Optional: true, Computed: true},
  1832  					"ami":       {Type: cty.String, Optional: true},
  1833  					"set_field": {Type: cty.Set(cty.String), Optional: true},
  1834  				},
  1835  			},
  1836  			RequiredReplace: cty.NewPathSet(),
  1837  			ExpectedOutput: `  # test_instance.example will be updated in-place
  1838    ~ resource "test_instance" "example" {
  1839        ~ id        = "i-02ae66f368e8518a9" -> (known after apply)
  1840        + set_field = [
  1841            + "new-element",
  1842          ]
  1843          # (1 unchanged attribute hidden)
  1844      }
  1845  `,
  1846  		},
  1847  		"in-place update - first insertion": {
  1848  			Action: plans.Update,
  1849  			Mode:   addrs.ManagedResourceMode,
  1850  			Before: cty.ObjectVal(map[string]cty.Value{
  1851  				"id":        cty.StringVal("i-02ae66f368e8518a9"),
  1852  				"ami":       cty.StringVal("ami-STATIC"),
  1853  				"set_field": cty.SetValEmpty(cty.String),
  1854  			}),
  1855  			After: cty.ObjectVal(map[string]cty.Value{
  1856  				"id":  cty.UnknownVal(cty.String),
  1857  				"ami": cty.StringVal("ami-STATIC"),
  1858  				"set_field": cty.SetVal([]cty.Value{
  1859  					cty.StringVal("new-element"),
  1860  				}),
  1861  			}),
  1862  			Schema: &configschema.Block{
  1863  				Attributes: map[string]*configschema.Attribute{
  1864  					"id":        {Type: cty.String, Optional: true, Computed: true},
  1865  					"ami":       {Type: cty.String, Optional: true},
  1866  					"set_field": {Type: cty.Set(cty.String), Optional: true},
  1867  				},
  1868  			},
  1869  			RequiredReplace: cty.NewPathSet(),
  1870  			ExpectedOutput: `  # test_instance.example will be updated in-place
  1871    ~ resource "test_instance" "example" {
  1872        ~ id        = "i-02ae66f368e8518a9" -> (known after apply)
  1873        ~ set_field = [
  1874            + "new-element",
  1875          ]
  1876          # (1 unchanged attribute hidden)
  1877      }
  1878  `,
  1879  		},
  1880  		"in-place update - insertion": {
  1881  			Action: plans.Update,
  1882  			Mode:   addrs.ManagedResourceMode,
  1883  			Before: cty.ObjectVal(map[string]cty.Value{
  1884  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  1885  				"ami": cty.StringVal("ami-STATIC"),
  1886  				"set_field": cty.SetVal([]cty.Value{
  1887  					cty.StringVal("aaaa"),
  1888  					cty.StringVal("cccc"),
  1889  				}),
  1890  			}),
  1891  			After: cty.ObjectVal(map[string]cty.Value{
  1892  				"id":  cty.UnknownVal(cty.String),
  1893  				"ami": cty.StringVal("ami-STATIC"),
  1894  				"set_field": cty.SetVal([]cty.Value{
  1895  					cty.StringVal("aaaa"),
  1896  					cty.StringVal("bbbb"),
  1897  					cty.StringVal("cccc"),
  1898  				}),
  1899  			}),
  1900  			Schema: &configschema.Block{
  1901  				Attributes: map[string]*configschema.Attribute{
  1902  					"id":        {Type: cty.String, Optional: true, Computed: true},
  1903  					"ami":       {Type: cty.String, Optional: true},
  1904  					"set_field": {Type: cty.Set(cty.String), Optional: true},
  1905  				},
  1906  			},
  1907  			RequiredReplace: cty.NewPathSet(),
  1908  			ExpectedOutput: `  # test_instance.example will be updated in-place
  1909    ~ resource "test_instance" "example" {
  1910        ~ id        = "i-02ae66f368e8518a9" -> (known after apply)
  1911        ~ set_field = [
  1912            + "bbbb",
  1913              # (2 unchanged elements hidden)
  1914          ]
  1915          # (1 unchanged attribute hidden)
  1916      }
  1917  `,
  1918  		},
  1919  		"force-new update - insertion": {
  1920  			Action:       plans.DeleteThenCreate,
  1921  			ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate,
  1922  			Mode:         addrs.ManagedResourceMode,
  1923  			Before: cty.ObjectVal(map[string]cty.Value{
  1924  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  1925  				"ami": cty.StringVal("ami-STATIC"),
  1926  				"set_field": cty.SetVal([]cty.Value{
  1927  					cty.StringVal("aaaa"),
  1928  					cty.StringVal("cccc"),
  1929  				}),
  1930  			}),
  1931  			After: cty.ObjectVal(map[string]cty.Value{
  1932  				"id":  cty.UnknownVal(cty.String),
  1933  				"ami": cty.StringVal("ami-STATIC"),
  1934  				"set_field": cty.SetVal([]cty.Value{
  1935  					cty.StringVal("aaaa"),
  1936  					cty.StringVal("bbbb"),
  1937  					cty.StringVal("cccc"),
  1938  				}),
  1939  			}),
  1940  			Schema: &configschema.Block{
  1941  				Attributes: map[string]*configschema.Attribute{
  1942  					"id":        {Type: cty.String, Optional: true, Computed: true},
  1943  					"ami":       {Type: cty.String, Optional: true},
  1944  					"set_field": {Type: cty.Set(cty.String), Optional: true},
  1945  				},
  1946  			},
  1947  			RequiredReplace: cty.NewPathSet(cty.Path{
  1948  				cty.GetAttrStep{Name: "set_field"},
  1949  			}),
  1950  			ExpectedOutput: `  # test_instance.example must be replaced
  1951  -/+ resource "test_instance" "example" {
  1952        ~ id        = "i-02ae66f368e8518a9" -> (known after apply)
  1953        ~ set_field = [ # forces replacement
  1954            + "bbbb",
  1955              # (2 unchanged elements hidden)
  1956          ]
  1957          # (1 unchanged attribute hidden)
  1958      }
  1959  `,
  1960  		},
  1961  		"in-place update - deletion": {
  1962  			Action: plans.Update,
  1963  			Mode:   addrs.ManagedResourceMode,
  1964  			Before: cty.ObjectVal(map[string]cty.Value{
  1965  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  1966  				"ami": cty.StringVal("ami-STATIC"),
  1967  				"set_field": cty.SetVal([]cty.Value{
  1968  					cty.StringVal("aaaa"),
  1969  					cty.StringVal("bbbb"),
  1970  					cty.StringVal("cccc"),
  1971  				}),
  1972  			}),
  1973  			After: cty.ObjectVal(map[string]cty.Value{
  1974  				"id":  cty.UnknownVal(cty.String),
  1975  				"ami": cty.StringVal("ami-STATIC"),
  1976  				"set_field": cty.SetVal([]cty.Value{
  1977  					cty.StringVal("bbbb"),
  1978  				}),
  1979  			}),
  1980  			Schema: &configschema.Block{
  1981  				Attributes: map[string]*configschema.Attribute{
  1982  					"id":        {Type: cty.String, Optional: true, Computed: true},
  1983  					"ami":       {Type: cty.String, Optional: true},
  1984  					"set_field": {Type: cty.Set(cty.String), Optional: true},
  1985  				},
  1986  			},
  1987  			RequiredReplace: cty.NewPathSet(),
  1988  			ExpectedOutput: `  # test_instance.example will be updated in-place
  1989    ~ resource "test_instance" "example" {
  1990        ~ id        = "i-02ae66f368e8518a9" -> (known after apply)
  1991        ~ set_field = [
  1992            - "aaaa",
  1993            - "cccc",
  1994              # (1 unchanged element hidden)
  1995          ]
  1996          # (1 unchanged attribute hidden)
  1997      }
  1998  `,
  1999  		},
  2000  		"creation - empty set": {
  2001  			Action: plans.Create,
  2002  			Mode:   addrs.ManagedResourceMode,
  2003  			Before: cty.NullVal(cty.EmptyObject),
  2004  			After: cty.ObjectVal(map[string]cty.Value{
  2005  				"id":        cty.UnknownVal(cty.String),
  2006  				"ami":       cty.StringVal("ami-STATIC"),
  2007  				"set_field": cty.SetValEmpty(cty.String),
  2008  			}),
  2009  			Schema: &configschema.Block{
  2010  				Attributes: map[string]*configschema.Attribute{
  2011  					"id":        {Type: cty.String, Optional: true, Computed: true},
  2012  					"ami":       {Type: cty.String, Optional: true},
  2013  					"set_field": {Type: cty.Set(cty.String), Optional: true},
  2014  				},
  2015  			},
  2016  			RequiredReplace: cty.NewPathSet(),
  2017  			ExpectedOutput: `  # test_instance.example will be created
  2018    + resource "test_instance" "example" {
  2019        + ami       = "ami-STATIC"
  2020        + id        = (known after apply)
  2021        + set_field = []
  2022      }
  2023  `,
  2024  		},
  2025  		"in-place update - full to empty set": {
  2026  			Action: plans.Update,
  2027  			Mode:   addrs.ManagedResourceMode,
  2028  			Before: cty.ObjectVal(map[string]cty.Value{
  2029  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2030  				"ami": cty.StringVal("ami-STATIC"),
  2031  				"set_field": cty.SetVal([]cty.Value{
  2032  					cty.StringVal("aaaa"),
  2033  					cty.StringVal("bbbb"),
  2034  				}),
  2035  			}),
  2036  			After: cty.ObjectVal(map[string]cty.Value{
  2037  				"id":        cty.UnknownVal(cty.String),
  2038  				"ami":       cty.StringVal("ami-STATIC"),
  2039  				"set_field": cty.SetValEmpty(cty.String),
  2040  			}),
  2041  			Schema: &configschema.Block{
  2042  				Attributes: map[string]*configschema.Attribute{
  2043  					"id":        {Type: cty.String, Optional: true, Computed: true},
  2044  					"ami":       {Type: cty.String, Optional: true},
  2045  					"set_field": {Type: cty.Set(cty.String), Optional: true},
  2046  				},
  2047  			},
  2048  			RequiredReplace: cty.NewPathSet(),
  2049  			ExpectedOutput: `  # test_instance.example will be updated in-place
  2050    ~ resource "test_instance" "example" {
  2051        ~ id        = "i-02ae66f368e8518a9" -> (known after apply)
  2052        ~ set_field = [
  2053            - "aaaa",
  2054            - "bbbb",
  2055          ]
  2056          # (1 unchanged attribute hidden)
  2057      }
  2058  `,
  2059  		},
  2060  		"in-place update - null to empty set": {
  2061  			Action: plans.Update,
  2062  			Mode:   addrs.ManagedResourceMode,
  2063  			Before: cty.ObjectVal(map[string]cty.Value{
  2064  				"id":        cty.StringVal("i-02ae66f368e8518a9"),
  2065  				"ami":       cty.StringVal("ami-STATIC"),
  2066  				"set_field": cty.NullVal(cty.Set(cty.String)),
  2067  			}),
  2068  			After: cty.ObjectVal(map[string]cty.Value{
  2069  				"id":        cty.UnknownVal(cty.String),
  2070  				"ami":       cty.StringVal("ami-STATIC"),
  2071  				"set_field": cty.SetValEmpty(cty.String),
  2072  			}),
  2073  			Schema: &configschema.Block{
  2074  				Attributes: map[string]*configschema.Attribute{
  2075  					"id":        {Type: cty.String, Optional: true, Computed: true},
  2076  					"ami":       {Type: cty.String, Optional: true},
  2077  					"set_field": {Type: cty.Set(cty.String), Optional: true},
  2078  				},
  2079  			},
  2080  			RequiredReplace: cty.NewPathSet(),
  2081  			ExpectedOutput: `  # test_instance.example will be updated in-place
  2082    ~ resource "test_instance" "example" {
  2083        ~ id        = "i-02ae66f368e8518a9" -> (known after apply)
  2084        + set_field = []
  2085          # (1 unchanged attribute hidden)
  2086      }
  2087  `,
  2088  		},
  2089  		"in-place update to unknown": {
  2090  			Action: plans.Update,
  2091  			Mode:   addrs.ManagedResourceMode,
  2092  			Before: cty.ObjectVal(map[string]cty.Value{
  2093  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2094  				"ami": cty.StringVal("ami-STATIC"),
  2095  				"set_field": cty.SetVal([]cty.Value{
  2096  					cty.StringVal("aaaa"),
  2097  					cty.StringVal("bbbb"),
  2098  				}),
  2099  			}),
  2100  			After: cty.ObjectVal(map[string]cty.Value{
  2101  				"id":        cty.UnknownVal(cty.String),
  2102  				"ami":       cty.StringVal("ami-STATIC"),
  2103  				"set_field": cty.UnknownVal(cty.Set(cty.String)),
  2104  			}),
  2105  			Schema: &configschema.Block{
  2106  				Attributes: map[string]*configschema.Attribute{
  2107  					"id":        {Type: cty.String, Optional: true, Computed: true},
  2108  					"ami":       {Type: cty.String, Optional: true},
  2109  					"set_field": {Type: cty.Set(cty.String), Optional: true},
  2110  				},
  2111  			},
  2112  			RequiredReplace: cty.NewPathSet(),
  2113  			ExpectedOutput: `  # test_instance.example will be updated in-place
  2114    ~ resource "test_instance" "example" {
  2115        ~ id        = "i-02ae66f368e8518a9" -> (known after apply)
  2116        ~ set_field = [
  2117            - "aaaa",
  2118            - "bbbb",
  2119          ] -> (known after apply)
  2120          # (1 unchanged attribute hidden)
  2121      }
  2122  `,
  2123  		},
  2124  		"in-place update to unknown element": {
  2125  			Action: plans.Update,
  2126  			Mode:   addrs.ManagedResourceMode,
  2127  			Before: cty.ObjectVal(map[string]cty.Value{
  2128  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2129  				"ami": cty.StringVal("ami-STATIC"),
  2130  				"set_field": cty.SetVal([]cty.Value{
  2131  					cty.StringVal("aaaa"),
  2132  					cty.StringVal("bbbb"),
  2133  				}),
  2134  			}),
  2135  			After: cty.ObjectVal(map[string]cty.Value{
  2136  				"id":  cty.UnknownVal(cty.String),
  2137  				"ami": cty.StringVal("ami-STATIC"),
  2138  				"set_field": cty.SetVal([]cty.Value{
  2139  					cty.StringVal("aaaa"),
  2140  					cty.UnknownVal(cty.String),
  2141  				}),
  2142  			}),
  2143  			Schema: &configschema.Block{
  2144  				Attributes: map[string]*configschema.Attribute{
  2145  					"id":        {Type: cty.String, Optional: true, Computed: true},
  2146  					"ami":       {Type: cty.String, Optional: true},
  2147  					"set_field": {Type: cty.Set(cty.String), Optional: true},
  2148  				},
  2149  			},
  2150  			RequiredReplace: cty.NewPathSet(),
  2151  			ExpectedOutput: `  # test_instance.example will be updated in-place
  2152    ~ resource "test_instance" "example" {
  2153        ~ id        = "i-02ae66f368e8518a9" -> (known after apply)
  2154        ~ set_field = [
  2155            - "bbbb",
  2156            ~ (known after apply),
  2157              # (1 unchanged element hidden)
  2158          ]
  2159          # (1 unchanged attribute hidden)
  2160      }
  2161  `,
  2162  		},
  2163  	}
  2164  	runTestCases(t, testCases)
  2165  }
  2166  
  2167  func TestResourceChange_map(t *testing.T) {
  2168  	testCases := map[string]testCase{
  2169  		"in-place update - creation": {
  2170  			Action: plans.Update,
  2171  			Mode:   addrs.ManagedResourceMode,
  2172  			Before: cty.ObjectVal(map[string]cty.Value{
  2173  				"id":        cty.StringVal("i-02ae66f368e8518a9"),
  2174  				"ami":       cty.StringVal("ami-STATIC"),
  2175  				"map_field": cty.NullVal(cty.Map(cty.String)),
  2176  			}),
  2177  			After: cty.ObjectVal(map[string]cty.Value{
  2178  				"id":  cty.UnknownVal(cty.String),
  2179  				"ami": cty.StringVal("ami-STATIC"),
  2180  				"map_field": cty.MapVal(map[string]cty.Value{
  2181  					"new-key": cty.StringVal("new-element"),
  2182  					"be:ep":   cty.StringVal("boop"),
  2183  				}),
  2184  			}),
  2185  			Schema: &configschema.Block{
  2186  				Attributes: map[string]*configschema.Attribute{
  2187  					"id":        {Type: cty.String, Optional: true, Computed: true},
  2188  					"ami":       {Type: cty.String, Optional: true},
  2189  					"map_field": {Type: cty.Map(cty.String), Optional: true},
  2190  				},
  2191  			},
  2192  			RequiredReplace: cty.NewPathSet(),
  2193  			ExpectedOutput: `  # test_instance.example will be updated in-place
  2194    ~ resource "test_instance" "example" {
  2195        ~ id        = "i-02ae66f368e8518a9" -> (known after apply)
  2196        + map_field = {
  2197            + "be:ep"   = "boop"
  2198            + "new-key" = "new-element"
  2199          }
  2200          # (1 unchanged attribute hidden)
  2201      }
  2202  `,
  2203  		},
  2204  		"in-place update - first insertion": {
  2205  			Action: plans.Update,
  2206  			Mode:   addrs.ManagedResourceMode,
  2207  			Before: cty.ObjectVal(map[string]cty.Value{
  2208  				"id":        cty.StringVal("i-02ae66f368e8518a9"),
  2209  				"ami":       cty.StringVal("ami-STATIC"),
  2210  				"map_field": cty.MapValEmpty(cty.String),
  2211  			}),
  2212  			After: cty.ObjectVal(map[string]cty.Value{
  2213  				"id":  cty.UnknownVal(cty.String),
  2214  				"ami": cty.StringVal("ami-STATIC"),
  2215  				"map_field": cty.MapVal(map[string]cty.Value{
  2216  					"new-key": cty.StringVal("new-element"),
  2217  					"be:ep":   cty.StringVal("boop"),
  2218  				}),
  2219  			}),
  2220  			Schema: &configschema.Block{
  2221  				Attributes: map[string]*configschema.Attribute{
  2222  					"id":        {Type: cty.String, Optional: true, Computed: true},
  2223  					"ami":       {Type: cty.String, Optional: true},
  2224  					"map_field": {Type: cty.Map(cty.String), Optional: true},
  2225  				},
  2226  			},
  2227  			RequiredReplace: cty.NewPathSet(),
  2228  			ExpectedOutput: `  # test_instance.example will be updated in-place
  2229    ~ resource "test_instance" "example" {
  2230        ~ id        = "i-02ae66f368e8518a9" -> (known after apply)
  2231        ~ map_field = {
  2232            + "be:ep"   = "boop"
  2233            + "new-key" = "new-element"
  2234          }
  2235          # (1 unchanged attribute hidden)
  2236      }
  2237  `,
  2238  		},
  2239  		"in-place update - insertion": {
  2240  			Action: plans.Update,
  2241  			Mode:   addrs.ManagedResourceMode,
  2242  			Before: cty.ObjectVal(map[string]cty.Value{
  2243  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2244  				"ami": cty.StringVal("ami-STATIC"),
  2245  				"map_field": cty.MapVal(map[string]cty.Value{
  2246  					"a": cty.StringVal("aaaa"),
  2247  					"c": cty.StringVal("cccc"),
  2248  				}),
  2249  			}),
  2250  			After: cty.ObjectVal(map[string]cty.Value{
  2251  				"id":  cty.UnknownVal(cty.String),
  2252  				"ami": cty.StringVal("ami-STATIC"),
  2253  				"map_field": cty.MapVal(map[string]cty.Value{
  2254  					"a":   cty.StringVal("aaaa"),
  2255  					"b":   cty.StringVal("bbbb"),
  2256  					"b:b": cty.StringVal("bbbb"),
  2257  					"c":   cty.StringVal("cccc"),
  2258  				}),
  2259  			}),
  2260  			Schema: &configschema.Block{
  2261  				Attributes: map[string]*configschema.Attribute{
  2262  					"id":        {Type: cty.String, Optional: true, Computed: true},
  2263  					"ami":       {Type: cty.String, Optional: true},
  2264  					"map_field": {Type: cty.Map(cty.String), Optional: true},
  2265  				},
  2266  			},
  2267  			RequiredReplace: cty.NewPathSet(),
  2268  			ExpectedOutput: `  # test_instance.example will be updated in-place
  2269    ~ resource "test_instance" "example" {
  2270        ~ id        = "i-02ae66f368e8518a9" -> (known after apply)
  2271        ~ map_field = {
  2272            + "b"   = "bbbb"
  2273            + "b:b" = "bbbb"
  2274              # (2 unchanged elements hidden)
  2275          }
  2276          # (1 unchanged attribute hidden)
  2277      }
  2278  `,
  2279  		},
  2280  		"force-new update - insertion": {
  2281  			Action:       plans.DeleteThenCreate,
  2282  			ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate,
  2283  			Mode:         addrs.ManagedResourceMode,
  2284  			Before: cty.ObjectVal(map[string]cty.Value{
  2285  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2286  				"ami": cty.StringVal("ami-STATIC"),
  2287  				"map_field": cty.MapVal(map[string]cty.Value{
  2288  					"a": cty.StringVal("aaaa"),
  2289  					"c": cty.StringVal("cccc"),
  2290  				}),
  2291  			}),
  2292  			After: cty.ObjectVal(map[string]cty.Value{
  2293  				"id":  cty.UnknownVal(cty.String),
  2294  				"ami": cty.StringVal("ami-STATIC"),
  2295  				"map_field": cty.MapVal(map[string]cty.Value{
  2296  					"a": cty.StringVal("aaaa"),
  2297  					"b": cty.StringVal("bbbb"),
  2298  					"c": cty.StringVal("cccc"),
  2299  				}),
  2300  			}),
  2301  			Schema: &configschema.Block{
  2302  				Attributes: map[string]*configschema.Attribute{
  2303  					"id":        {Type: cty.String, Optional: true, Computed: true},
  2304  					"ami":       {Type: cty.String, Optional: true},
  2305  					"map_field": {Type: cty.Map(cty.String), Optional: true},
  2306  				},
  2307  			},
  2308  			RequiredReplace: cty.NewPathSet(cty.Path{
  2309  				cty.GetAttrStep{Name: "map_field"},
  2310  			}),
  2311  			ExpectedOutput: `  # test_instance.example must be replaced
  2312  -/+ resource "test_instance" "example" {
  2313        ~ id        = "i-02ae66f368e8518a9" -> (known after apply)
  2314        ~ map_field = { # forces replacement
  2315            + "b" = "bbbb"
  2316              # (2 unchanged elements hidden)
  2317          }
  2318          # (1 unchanged attribute hidden)
  2319      }
  2320  `,
  2321  		},
  2322  		"in-place update - deletion": {
  2323  			Action: plans.Update,
  2324  			Mode:   addrs.ManagedResourceMode,
  2325  			Before: cty.ObjectVal(map[string]cty.Value{
  2326  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2327  				"ami": cty.StringVal("ami-STATIC"),
  2328  				"map_field": cty.MapVal(map[string]cty.Value{
  2329  					"a": cty.StringVal("aaaa"),
  2330  					"b": cty.StringVal("bbbb"),
  2331  					"c": cty.StringVal("cccc"),
  2332  				}),
  2333  			}),
  2334  			After: cty.ObjectVal(map[string]cty.Value{
  2335  				"id":  cty.UnknownVal(cty.String),
  2336  				"ami": cty.StringVal("ami-STATIC"),
  2337  				"map_field": cty.MapVal(map[string]cty.Value{
  2338  					"b": cty.StringVal("bbbb"),
  2339  				}),
  2340  			}),
  2341  			Schema: &configschema.Block{
  2342  				Attributes: map[string]*configschema.Attribute{
  2343  					"id":        {Type: cty.String, Optional: true, Computed: true},
  2344  					"ami":       {Type: cty.String, Optional: true},
  2345  					"map_field": {Type: cty.Map(cty.String), Optional: true},
  2346  				},
  2347  			},
  2348  			RequiredReplace: cty.NewPathSet(),
  2349  			ExpectedOutput: `  # test_instance.example will be updated in-place
  2350    ~ resource "test_instance" "example" {
  2351        ~ id        = "i-02ae66f368e8518a9" -> (known after apply)
  2352        ~ map_field = {
  2353            - "a" = "aaaa" -> null
  2354            - "c" = "cccc" -> null
  2355              # (1 unchanged element hidden)
  2356          }
  2357          # (1 unchanged attribute hidden)
  2358      }
  2359  `,
  2360  		},
  2361  		"creation - empty": {
  2362  			Action: plans.Create,
  2363  			Mode:   addrs.ManagedResourceMode,
  2364  			Before: cty.NullVal(cty.EmptyObject),
  2365  			After: cty.ObjectVal(map[string]cty.Value{
  2366  				"id":        cty.UnknownVal(cty.String),
  2367  				"ami":       cty.StringVal("ami-STATIC"),
  2368  				"map_field": cty.MapValEmpty(cty.String),
  2369  			}),
  2370  			Schema: &configschema.Block{
  2371  				Attributes: map[string]*configschema.Attribute{
  2372  					"id":        {Type: cty.String, Optional: true, Computed: true},
  2373  					"ami":       {Type: cty.String, Optional: true},
  2374  					"map_field": {Type: cty.Map(cty.String), Optional: true},
  2375  				},
  2376  			},
  2377  			RequiredReplace: cty.NewPathSet(),
  2378  			ExpectedOutput: `  # test_instance.example will be created
  2379    + resource "test_instance" "example" {
  2380        + ami       = "ami-STATIC"
  2381        + id        = (known after apply)
  2382        + map_field = {}
  2383      }
  2384  `,
  2385  		},
  2386  		"update to unknown element": {
  2387  			Action: plans.Update,
  2388  			Mode:   addrs.ManagedResourceMode,
  2389  			Before: cty.ObjectVal(map[string]cty.Value{
  2390  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2391  				"ami": cty.StringVal("ami-STATIC"),
  2392  				"map_field": cty.MapVal(map[string]cty.Value{
  2393  					"a": cty.StringVal("aaaa"),
  2394  					"b": cty.StringVal("bbbb"),
  2395  					"c": cty.StringVal("cccc"),
  2396  				}),
  2397  			}),
  2398  			After: cty.ObjectVal(map[string]cty.Value{
  2399  				"id":  cty.UnknownVal(cty.String),
  2400  				"ami": cty.StringVal("ami-STATIC"),
  2401  				"map_field": cty.MapVal(map[string]cty.Value{
  2402  					"a": cty.StringVal("aaaa"),
  2403  					"b": cty.UnknownVal(cty.String),
  2404  					"c": cty.StringVal("cccc"),
  2405  				}),
  2406  			}),
  2407  			Schema: &configschema.Block{
  2408  				Attributes: map[string]*configschema.Attribute{
  2409  					"id":        {Type: cty.String, Optional: true, Computed: true},
  2410  					"ami":       {Type: cty.String, Optional: true},
  2411  					"map_field": {Type: cty.Map(cty.String), Optional: true},
  2412  				},
  2413  			},
  2414  			RequiredReplace: cty.NewPathSet(),
  2415  			ExpectedOutput: `  # test_instance.example will be updated in-place
  2416    ~ resource "test_instance" "example" {
  2417        ~ id        = "i-02ae66f368e8518a9" -> (known after apply)
  2418        ~ map_field = {
  2419            ~ "b" = "bbbb" -> (known after apply)
  2420              # (2 unchanged elements hidden)
  2421          }
  2422          # (1 unchanged attribute hidden)
  2423      }
  2424  `,
  2425  		},
  2426  	}
  2427  	runTestCases(t, testCases)
  2428  }
  2429  
  2430  func TestResourceChange_nestedList(t *testing.T) {
  2431  	testCases := map[string]testCase{
  2432  		"in-place update - equal": {
  2433  			Action: plans.Update,
  2434  			Mode:   addrs.ManagedResourceMode,
  2435  			Before: cty.ObjectVal(map[string]cty.Value{
  2436  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2437  				"ami": cty.StringVal("ami-BEFORE"),
  2438  				"root_block_device": cty.ListVal([]cty.Value{
  2439  					cty.ObjectVal(map[string]cty.Value{
  2440  						"volume_type": cty.StringVal("gp2"),
  2441  					}),
  2442  				}),
  2443  				"disks": cty.ListVal([]cty.Value{
  2444  					cty.ObjectVal(map[string]cty.Value{
  2445  						"mount_point": cty.StringVal("/var/diska"),
  2446  						"size":        cty.StringVal("50GB"),
  2447  					}),
  2448  				}),
  2449  			}),
  2450  			After: cty.ObjectVal(map[string]cty.Value{
  2451  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2452  				"ami": cty.StringVal("ami-AFTER"),
  2453  				"root_block_device": cty.ListVal([]cty.Value{
  2454  					cty.ObjectVal(map[string]cty.Value{
  2455  						"volume_type": cty.StringVal("gp2"),
  2456  					}),
  2457  				}),
  2458  				"disks": cty.ListVal([]cty.Value{
  2459  					cty.ObjectVal(map[string]cty.Value{
  2460  						"mount_point": cty.StringVal("/var/diska"),
  2461  						"size":        cty.StringVal("50GB"),
  2462  					}),
  2463  				}),
  2464  			}),
  2465  			RequiredReplace: cty.NewPathSet(),
  2466  			Schema:          testSchema(configschema.NestingList),
  2467  			ExpectedOutput: `  # test_instance.example will be updated in-place
  2468    ~ resource "test_instance" "example" {
  2469        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  2470          id    = "i-02ae66f368e8518a9"
  2471          # (1 unchanged attribute hidden)
  2472  
  2473          # (1 unchanged block hidden)
  2474      }
  2475  `,
  2476  		},
  2477  		"in-place update - creation": {
  2478  			Action: plans.Update,
  2479  			Mode:   addrs.ManagedResourceMode,
  2480  			Before: cty.ObjectVal(map[string]cty.Value{
  2481  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2482  				"ami": cty.StringVal("ami-BEFORE"),
  2483  				"root_block_device": cty.ListValEmpty(cty.Object(map[string]cty.Type{
  2484  					"volume_type": cty.String,
  2485  				})),
  2486  				"disks": cty.ListValEmpty(cty.Object(map[string]cty.Type{
  2487  					"mount_point": cty.String,
  2488  					"size":        cty.String,
  2489  				})),
  2490  			}),
  2491  			After: cty.ObjectVal(map[string]cty.Value{
  2492  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2493  				"ami": cty.StringVal("ami-AFTER"),
  2494  				"disks": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
  2495  					"mount_point": cty.StringVal("/var/diska"),
  2496  					"size":        cty.StringVal("50GB"),
  2497  				})}),
  2498  				"root_block_device": cty.ListVal([]cty.Value{
  2499  					cty.ObjectVal(map[string]cty.Value{
  2500  						"volume_type": cty.NullVal(cty.String),
  2501  					}),
  2502  				}),
  2503  			}),
  2504  			RequiredReplace: cty.NewPathSet(),
  2505  			Schema:          testSchema(configschema.NestingList),
  2506  			ExpectedOutput: `  # test_instance.example will be updated in-place
  2507    ~ resource "test_instance" "example" {
  2508        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  2509        ~ disks = [
  2510            + {
  2511                + mount_point = "/var/diska"
  2512                + size        = "50GB"
  2513              },
  2514          ]
  2515          id    = "i-02ae66f368e8518a9"
  2516  
  2517        + root_block_device {}
  2518      }
  2519  `,
  2520  		},
  2521  		"in-place update - first insertion": {
  2522  			Action: plans.Update,
  2523  			Mode:   addrs.ManagedResourceMode,
  2524  			Before: cty.ObjectVal(map[string]cty.Value{
  2525  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2526  				"ami": cty.StringVal("ami-BEFORE"),
  2527  				"root_block_device": cty.ListValEmpty(cty.Object(map[string]cty.Type{
  2528  					"volume_type": cty.String,
  2529  				})),
  2530  				"disks": cty.ListValEmpty(cty.Object(map[string]cty.Type{
  2531  					"mount_point": cty.String,
  2532  					"size":        cty.String,
  2533  				})),
  2534  			}),
  2535  			After: cty.ObjectVal(map[string]cty.Value{
  2536  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2537  				"ami": cty.StringVal("ami-AFTER"),
  2538  				"disks": cty.ListVal([]cty.Value{
  2539  					cty.ObjectVal(map[string]cty.Value{
  2540  						"mount_point": cty.StringVal("/var/diska"),
  2541  						"size":        cty.NullVal(cty.String),
  2542  					}),
  2543  				}),
  2544  				"root_block_device": cty.ListVal([]cty.Value{
  2545  					cty.ObjectVal(map[string]cty.Value{
  2546  						"volume_type": cty.StringVal("gp2"),
  2547  					}),
  2548  				}),
  2549  			}),
  2550  			RequiredReplace: cty.NewPathSet(),
  2551  			Schema:          testSchema(configschema.NestingList),
  2552  			ExpectedOutput: `  # test_instance.example will be updated in-place
  2553    ~ resource "test_instance" "example" {
  2554        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  2555        ~ disks = [
  2556            + {
  2557                + mount_point = "/var/diska"
  2558              },
  2559          ]
  2560          id    = "i-02ae66f368e8518a9"
  2561  
  2562        + root_block_device {
  2563            + volume_type = "gp2"
  2564          }
  2565      }
  2566  `,
  2567  		},
  2568  		"in-place update - insertion": {
  2569  			Action: plans.Update,
  2570  			Mode:   addrs.ManagedResourceMode,
  2571  			Before: cty.ObjectVal(map[string]cty.Value{
  2572  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2573  				"ami": cty.StringVal("ami-BEFORE"),
  2574  				"disks": cty.ListVal([]cty.Value{
  2575  					cty.ObjectVal(map[string]cty.Value{
  2576  						"mount_point": cty.StringVal("/var/diska"),
  2577  						"size":        cty.NullVal(cty.String),
  2578  					}),
  2579  					cty.ObjectVal(map[string]cty.Value{
  2580  						"mount_point": cty.StringVal("/var/diskb"),
  2581  						"size":        cty.StringVal("50GB"),
  2582  					}),
  2583  				}),
  2584  				"root_block_device": cty.ListVal([]cty.Value{
  2585  					cty.ObjectVal(map[string]cty.Value{
  2586  						"volume_type": cty.StringVal("gp2"),
  2587  						"new_field":   cty.NullVal(cty.String),
  2588  					}),
  2589  				}),
  2590  			}),
  2591  			After: cty.ObjectVal(map[string]cty.Value{
  2592  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2593  				"ami": cty.StringVal("ami-AFTER"),
  2594  				"disks": cty.ListVal([]cty.Value{
  2595  					cty.ObjectVal(map[string]cty.Value{
  2596  						"mount_point": cty.StringVal("/var/diska"),
  2597  						"size":        cty.StringVal("50GB"),
  2598  					}),
  2599  					cty.ObjectVal(map[string]cty.Value{
  2600  						"mount_point": cty.StringVal("/var/diskb"),
  2601  						"size":        cty.StringVal("50GB"),
  2602  					}),
  2603  				}),
  2604  				"root_block_device": cty.ListVal([]cty.Value{
  2605  					cty.ObjectVal(map[string]cty.Value{
  2606  						"volume_type": cty.StringVal("gp2"),
  2607  						"new_field":   cty.StringVal("new_value"),
  2608  					}),
  2609  				}),
  2610  			}),
  2611  			RequiredReplace: cty.NewPathSet(),
  2612  			Schema:          testSchemaPlus(configschema.NestingList),
  2613  			ExpectedOutput: `  # test_instance.example will be updated in-place
  2614    ~ resource "test_instance" "example" {
  2615        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  2616        ~ disks = [
  2617            ~ {
  2618                + size        = "50GB"
  2619                  # (1 unchanged attribute hidden)
  2620              },
  2621              # (1 unchanged element hidden)
  2622          ]
  2623          id    = "i-02ae66f368e8518a9"
  2624  
  2625        ~ root_block_device {
  2626            + new_field   = "new_value"
  2627              # (1 unchanged attribute hidden)
  2628          }
  2629      }
  2630  `,
  2631  		},
  2632  		"force-new update (inside blocks)": {
  2633  			Action:       plans.DeleteThenCreate,
  2634  			ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate,
  2635  			Mode:         addrs.ManagedResourceMode,
  2636  			Before: cty.ObjectVal(map[string]cty.Value{
  2637  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2638  				"ami": cty.StringVal("ami-BEFORE"),
  2639  				"disks": cty.ListVal([]cty.Value{
  2640  					cty.ObjectVal(map[string]cty.Value{
  2641  						"mount_point": cty.StringVal("/var/diska"),
  2642  						"size":        cty.StringVal("50GB"),
  2643  					}),
  2644  				}),
  2645  				"root_block_device": cty.ListVal([]cty.Value{
  2646  					cty.ObjectVal(map[string]cty.Value{
  2647  						"volume_type": cty.StringVal("gp2"),
  2648  					}),
  2649  				}),
  2650  			}),
  2651  			After: cty.ObjectVal(map[string]cty.Value{
  2652  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2653  				"ami": cty.StringVal("ami-AFTER"),
  2654  				"disks": cty.ListVal([]cty.Value{
  2655  					cty.ObjectVal(map[string]cty.Value{
  2656  						"mount_point": cty.StringVal("/var/diskb"),
  2657  						"size":        cty.StringVal("50GB"),
  2658  					}),
  2659  				}),
  2660  				"root_block_device": cty.ListVal([]cty.Value{
  2661  					cty.ObjectVal(map[string]cty.Value{
  2662  						"volume_type": cty.StringVal("different"),
  2663  					}),
  2664  				}),
  2665  			}),
  2666  			RequiredReplace: cty.NewPathSet(
  2667  				cty.Path{
  2668  					cty.GetAttrStep{Name: "root_block_device"},
  2669  					cty.IndexStep{Key: cty.NumberIntVal(0)},
  2670  					cty.GetAttrStep{Name: "volume_type"},
  2671  				},
  2672  				cty.Path{
  2673  					cty.GetAttrStep{Name: "disks"},
  2674  					cty.IndexStep{Key: cty.NumberIntVal(0)},
  2675  					cty.GetAttrStep{Name: "mount_point"},
  2676  				},
  2677  			),
  2678  			Schema: testSchema(configschema.NestingList),
  2679  			ExpectedOutput: `  # test_instance.example must be replaced
  2680  -/+ resource "test_instance" "example" {
  2681        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  2682        ~ disks = [
  2683            ~ {
  2684                ~ mount_point = "/var/diska" -> "/var/diskb" # forces replacement
  2685                  # (1 unchanged attribute hidden)
  2686              },
  2687          ]
  2688          id    = "i-02ae66f368e8518a9"
  2689  
  2690        ~ root_block_device {
  2691            ~ volume_type = "gp2" -> "different" # forces replacement
  2692          }
  2693      }
  2694  `,
  2695  		},
  2696  		"force-new update (whole block)": {
  2697  			Action:       plans.DeleteThenCreate,
  2698  			ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate,
  2699  			Mode:         addrs.ManagedResourceMode,
  2700  			Before: cty.ObjectVal(map[string]cty.Value{
  2701  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2702  				"ami": cty.StringVal("ami-BEFORE"),
  2703  				"disks": cty.ListVal([]cty.Value{
  2704  					cty.ObjectVal(map[string]cty.Value{
  2705  						"mount_point": cty.StringVal("/var/diska"),
  2706  						"size":        cty.StringVal("50GB"),
  2707  					}),
  2708  				}),
  2709  				"root_block_device": cty.ListVal([]cty.Value{
  2710  					cty.ObjectVal(map[string]cty.Value{
  2711  						"volume_type": cty.StringVal("gp2"),
  2712  					}),
  2713  				}),
  2714  			}),
  2715  			After: cty.ObjectVal(map[string]cty.Value{
  2716  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2717  				"ami": cty.StringVal("ami-AFTER"),
  2718  				"disks": cty.ListVal([]cty.Value{
  2719  					cty.ObjectVal(map[string]cty.Value{
  2720  						"mount_point": cty.StringVal("/var/diskb"),
  2721  						"size":        cty.StringVal("50GB"),
  2722  					}),
  2723  				}),
  2724  				"root_block_device": cty.ListVal([]cty.Value{
  2725  					cty.ObjectVal(map[string]cty.Value{
  2726  						"volume_type": cty.StringVal("different"),
  2727  					}),
  2728  				}),
  2729  			}),
  2730  			RequiredReplace: cty.NewPathSet(
  2731  				cty.Path{cty.GetAttrStep{Name: "root_block_device"}},
  2732  				cty.Path{cty.GetAttrStep{Name: "disks"}},
  2733  			),
  2734  			Schema: testSchema(configschema.NestingList),
  2735  			ExpectedOutput: `  # test_instance.example must be replaced
  2736  -/+ resource "test_instance" "example" {
  2737        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  2738        ~ disks = [ # forces replacement
  2739            ~ {
  2740                ~ mount_point = "/var/diska" -> "/var/diskb"
  2741                  # (1 unchanged attribute hidden)
  2742              },
  2743          ]
  2744          id    = "i-02ae66f368e8518a9"
  2745  
  2746        ~ root_block_device { # forces replacement
  2747            ~ volume_type = "gp2" -> "different"
  2748          }
  2749      }
  2750  `,
  2751  		},
  2752  		"in-place update - deletion": {
  2753  			Action: plans.Update,
  2754  			Mode:   addrs.ManagedResourceMode,
  2755  			Before: cty.ObjectVal(map[string]cty.Value{
  2756  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2757  				"ami": cty.StringVal("ami-BEFORE"),
  2758  				"disks": cty.ListVal([]cty.Value{
  2759  					cty.ObjectVal(map[string]cty.Value{
  2760  						"mount_point": cty.StringVal("/var/diska"),
  2761  						"size":        cty.StringVal("50GB"),
  2762  					}),
  2763  				}),
  2764  				"root_block_device": cty.ListVal([]cty.Value{
  2765  					cty.ObjectVal(map[string]cty.Value{
  2766  						"volume_type": cty.StringVal("gp2"),
  2767  					}),
  2768  				}),
  2769  			}),
  2770  			After: cty.ObjectVal(map[string]cty.Value{
  2771  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2772  				"ami": cty.StringVal("ami-AFTER"),
  2773  				"disks": cty.ListValEmpty(cty.Object(map[string]cty.Type{
  2774  					"mount_point": cty.String,
  2775  					"size":        cty.String,
  2776  				})),
  2777  				"root_block_device": cty.ListValEmpty(cty.Object(map[string]cty.Type{
  2778  					"volume_type": cty.String,
  2779  				})),
  2780  			}),
  2781  			RequiredReplace: cty.NewPathSet(),
  2782  			Schema:          testSchema(configschema.NestingList),
  2783  			ExpectedOutput: `  # test_instance.example will be updated in-place
  2784    ~ resource "test_instance" "example" {
  2785        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  2786        ~ disks = [
  2787            - {
  2788                - mount_point = "/var/diska" -> null
  2789                - size        = "50GB" -> null
  2790              },
  2791          ]
  2792          id    = "i-02ae66f368e8518a9"
  2793  
  2794        - root_block_device {
  2795            - volume_type = "gp2" -> null
  2796          }
  2797      }
  2798  `,
  2799  		},
  2800  		"with dynamically-typed attribute": {
  2801  			Action: plans.Update,
  2802  			Mode:   addrs.ManagedResourceMode,
  2803  			Before: cty.ObjectVal(map[string]cty.Value{
  2804  				"block": cty.EmptyTupleVal,
  2805  			}),
  2806  			After: cty.ObjectVal(map[string]cty.Value{
  2807  				"block": cty.TupleVal([]cty.Value{
  2808  					cty.ObjectVal(map[string]cty.Value{
  2809  						"attr": cty.StringVal("foo"),
  2810  					}),
  2811  					cty.ObjectVal(map[string]cty.Value{
  2812  						"attr": cty.True,
  2813  					}),
  2814  				}),
  2815  			}),
  2816  			RequiredReplace: cty.NewPathSet(),
  2817  			Schema: &configschema.Block{
  2818  				BlockTypes: map[string]*configschema.NestedBlock{
  2819  					"block": {
  2820  						Block: configschema.Block{
  2821  							Attributes: map[string]*configschema.Attribute{
  2822  								"attr": {Type: cty.DynamicPseudoType, Optional: true},
  2823  							},
  2824  						},
  2825  						Nesting: configschema.NestingList,
  2826  					},
  2827  				},
  2828  			},
  2829  			ExpectedOutput: `  # test_instance.example will be updated in-place
  2830    ~ resource "test_instance" "example" {
  2831        + block {
  2832            + attr = "foo"
  2833          }
  2834        + block {
  2835            + attr = true
  2836          }
  2837      }
  2838  `,
  2839  		},
  2840  		"in-place sequence update - deletion": {
  2841  			Action: plans.Update,
  2842  			Mode:   addrs.ManagedResourceMode,
  2843  			Before: cty.ObjectVal(map[string]cty.Value{
  2844  				"list": cty.ListVal([]cty.Value{
  2845  					cty.ObjectVal(map[string]cty.Value{"attr": cty.StringVal("x")}),
  2846  					cty.ObjectVal(map[string]cty.Value{"attr": cty.StringVal("y")}),
  2847  				}),
  2848  			}),
  2849  			After: cty.ObjectVal(map[string]cty.Value{
  2850  				"list": cty.ListVal([]cty.Value{
  2851  					cty.ObjectVal(map[string]cty.Value{"attr": cty.StringVal("y")}),
  2852  					cty.ObjectVal(map[string]cty.Value{"attr": cty.StringVal("z")}),
  2853  				}),
  2854  			}),
  2855  			RequiredReplace: cty.NewPathSet(),
  2856  			Schema: &configschema.Block{
  2857  				BlockTypes: map[string]*configschema.NestedBlock{
  2858  					"list": {
  2859  						Block: configschema.Block{
  2860  							Attributes: map[string]*configschema.Attribute{
  2861  								"attr": {
  2862  									Type:     cty.String,
  2863  									Required: true,
  2864  								},
  2865  							},
  2866  						},
  2867  						Nesting: configschema.NestingList,
  2868  					},
  2869  				},
  2870  			},
  2871  			ExpectedOutput: `  # test_instance.example will be updated in-place
  2872    ~ resource "test_instance" "example" {
  2873        ~ list {
  2874            ~ attr = "x" -> "y"
  2875          }
  2876        ~ list {
  2877            ~ attr = "y" -> "z"
  2878          }
  2879      }
  2880  `,
  2881  		},
  2882  		"in-place update - unknown": {
  2883  			Action: plans.Update,
  2884  			Mode:   addrs.ManagedResourceMode,
  2885  			Before: cty.ObjectVal(map[string]cty.Value{
  2886  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2887  				"ami": cty.StringVal("ami-BEFORE"),
  2888  				"disks": cty.ListVal([]cty.Value{
  2889  					cty.ObjectVal(map[string]cty.Value{
  2890  						"mount_point": cty.StringVal("/var/diska"),
  2891  						"size":        cty.StringVal("50GB"),
  2892  					}),
  2893  				}),
  2894  				"root_block_device": cty.ListVal([]cty.Value{
  2895  					cty.ObjectVal(map[string]cty.Value{
  2896  						"volume_type": cty.StringVal("gp2"),
  2897  						"new_field":   cty.StringVal("new_value"),
  2898  					}),
  2899  				}),
  2900  			}),
  2901  			After: cty.ObjectVal(map[string]cty.Value{
  2902  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2903  				"ami": cty.StringVal("ami-AFTER"),
  2904  				"disks": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{
  2905  					"mount_point": cty.String,
  2906  					"size":        cty.String,
  2907  				}))),
  2908  				"root_block_device": cty.ListVal([]cty.Value{
  2909  					cty.ObjectVal(map[string]cty.Value{
  2910  						"volume_type": cty.StringVal("gp2"),
  2911  						"new_field":   cty.StringVal("new_value"),
  2912  					}),
  2913  				}),
  2914  			}),
  2915  			RequiredReplace: cty.NewPathSet(),
  2916  			Schema:          testSchemaPlus(configschema.NestingList),
  2917  			ExpectedOutput: `  # test_instance.example will be updated in-place
  2918    ~ resource "test_instance" "example" {
  2919        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  2920        ~ disks = [
  2921            - {
  2922                - mount_point = "/var/diska" -> null
  2923                - size        = "50GB" -> null
  2924              },
  2925          ] -> (known after apply)
  2926          id    = "i-02ae66f368e8518a9"
  2927  
  2928          # (1 unchanged block hidden)
  2929      }
  2930  `,
  2931  		},
  2932  		"in-place update - modification": {
  2933  			Action: plans.Update,
  2934  			Mode:   addrs.ManagedResourceMode,
  2935  			Before: cty.ObjectVal(map[string]cty.Value{
  2936  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2937  				"ami": cty.StringVal("ami-BEFORE"),
  2938  				"disks": cty.ListVal([]cty.Value{
  2939  					cty.ObjectVal(map[string]cty.Value{
  2940  						"mount_point": cty.StringVal("/var/diska"),
  2941  						"size":        cty.StringVal("50GB"),
  2942  					}),
  2943  					cty.ObjectVal(map[string]cty.Value{
  2944  						"mount_point": cty.StringVal("/var/diskb"),
  2945  						"size":        cty.StringVal("50GB"),
  2946  					}),
  2947  					cty.ObjectVal(map[string]cty.Value{
  2948  						"mount_point": cty.StringVal("/var/diskc"),
  2949  						"size":        cty.StringVal("50GB"),
  2950  					}),
  2951  				}),
  2952  				"root_block_device": cty.ListVal([]cty.Value{
  2953  					cty.ObjectVal(map[string]cty.Value{
  2954  						"volume_type": cty.StringVal("gp2"),
  2955  						"new_field":   cty.StringVal("new_value"),
  2956  					}),
  2957  				}),
  2958  			}),
  2959  			After: cty.ObjectVal(map[string]cty.Value{
  2960  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  2961  				"ami": cty.StringVal("ami-AFTER"),
  2962  				"disks": cty.ListVal([]cty.Value{
  2963  					cty.ObjectVal(map[string]cty.Value{
  2964  						"mount_point": cty.StringVal("/var/diska"),
  2965  						"size":        cty.StringVal("50GB"),
  2966  					}),
  2967  					cty.ObjectVal(map[string]cty.Value{
  2968  						"mount_point": cty.StringVal("/var/diskb"),
  2969  						"size":        cty.StringVal("75GB"),
  2970  					}),
  2971  					cty.ObjectVal(map[string]cty.Value{
  2972  						"mount_point": cty.StringVal("/var/diskc"),
  2973  						"size":        cty.StringVal("25GB"),
  2974  					}),
  2975  				}),
  2976  				"root_block_device": cty.ListVal([]cty.Value{
  2977  					cty.ObjectVal(map[string]cty.Value{
  2978  						"volume_type": cty.StringVal("gp2"),
  2979  						"new_field":   cty.StringVal("new_value"),
  2980  					}),
  2981  				}),
  2982  			}),
  2983  			RequiredReplace: cty.NewPathSet(),
  2984  			Schema:          testSchemaPlus(configschema.NestingList),
  2985  			ExpectedOutput: `  # test_instance.example will be updated in-place
  2986    ~ resource "test_instance" "example" {
  2987        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  2988        ~ disks = [
  2989            ~ {
  2990                ~ size        = "50GB" -> "75GB"
  2991                  # (1 unchanged attribute hidden)
  2992              },
  2993            ~ {
  2994                ~ size        = "50GB" -> "25GB"
  2995                  # (1 unchanged attribute hidden)
  2996              },
  2997              # (1 unchanged element hidden)
  2998          ]
  2999          id    = "i-02ae66f368e8518a9"
  3000  
  3001          # (1 unchanged block hidden)
  3002      }
  3003  `,
  3004  		},
  3005  	}
  3006  	runTestCases(t, testCases)
  3007  }
  3008  
  3009  func TestResourceChange_nestedSet(t *testing.T) {
  3010  	testCases := map[string]testCase{
  3011  		"creation from null - sensitive set": {
  3012  			Action: plans.Create,
  3013  			Mode:   addrs.ManagedResourceMode,
  3014  			Before: cty.NullVal(cty.Object(map[string]cty.Type{
  3015  				"id":  cty.String,
  3016  				"ami": cty.String,
  3017  				"disks": cty.Set(cty.Object(map[string]cty.Type{
  3018  					"mount_point": cty.String,
  3019  					"size":        cty.String,
  3020  				})),
  3021  				"root_block_device": cty.Set(cty.Object(map[string]cty.Type{
  3022  					"volume_type": cty.String,
  3023  				})),
  3024  			})),
  3025  			After: cty.ObjectVal(map[string]cty.Value{
  3026  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3027  				"ami": cty.StringVal("ami-AFTER"),
  3028  				"disks": cty.SetVal([]cty.Value{
  3029  					cty.ObjectVal(map[string]cty.Value{
  3030  						"mount_point": cty.StringVal("/var/diska"),
  3031  						"size":        cty.NullVal(cty.String),
  3032  					}),
  3033  				}),
  3034  				"root_block_device": cty.SetVal([]cty.Value{
  3035  					cty.ObjectVal(map[string]cty.Value{
  3036  						"volume_type": cty.StringVal("gp2"),
  3037  					}),
  3038  				}),
  3039  			}),
  3040  			AfterValMarks: []cty.PathValueMarks{
  3041  				{
  3042  					Path:  cty.Path{cty.GetAttrStep{Name: "disks"}},
  3043  					Marks: cty.NewValueMarks(marks.Sensitive),
  3044  				},
  3045  			},
  3046  			RequiredReplace: cty.NewPathSet(),
  3047  			Schema:          testSchema(configschema.NestingSet),
  3048  			ExpectedOutput: `  # test_instance.example will be created
  3049    + resource "test_instance" "example" {
  3050        + ami   = "ami-AFTER"
  3051        + disks = (sensitive value)
  3052        + id    = "i-02ae66f368e8518a9"
  3053  
  3054        + root_block_device {
  3055            + volume_type = "gp2"
  3056          }
  3057      }
  3058  `,
  3059  		},
  3060  		"in-place update - creation": {
  3061  			Action: plans.Update,
  3062  			Mode:   addrs.ManagedResourceMode,
  3063  			Before: cty.ObjectVal(map[string]cty.Value{
  3064  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3065  				"ami": cty.StringVal("ami-BEFORE"),
  3066  				"disks": cty.SetValEmpty(cty.Object(map[string]cty.Type{
  3067  					"mount_point": cty.String,
  3068  					"size":        cty.String,
  3069  				})),
  3070  				"root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{
  3071  					"volume_type": cty.String,
  3072  				})),
  3073  			}),
  3074  			After: cty.ObjectVal(map[string]cty.Value{
  3075  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3076  				"ami": cty.StringVal("ami-AFTER"),
  3077  				"disks": cty.SetVal([]cty.Value{
  3078  					cty.ObjectVal(map[string]cty.Value{
  3079  						"mount_point": cty.StringVal("/var/diska"),
  3080  						"size":        cty.NullVal(cty.String),
  3081  					}),
  3082  				}),
  3083  				"root_block_device": cty.SetVal([]cty.Value{
  3084  					cty.ObjectVal(map[string]cty.Value{
  3085  						"volume_type": cty.StringVal("gp2"),
  3086  					}),
  3087  				}),
  3088  			}),
  3089  			RequiredReplace: cty.NewPathSet(),
  3090  			Schema:          testSchema(configschema.NestingSet),
  3091  			ExpectedOutput: `  # test_instance.example will be updated in-place
  3092    ~ resource "test_instance" "example" {
  3093        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  3094        ~ disks = [
  3095            + {
  3096                + mount_point = "/var/diska"
  3097              },
  3098          ]
  3099          id    = "i-02ae66f368e8518a9"
  3100  
  3101        + root_block_device {
  3102            + volume_type = "gp2"
  3103          }
  3104      }
  3105  `,
  3106  		},
  3107  		"in-place update - creation - sensitive set": {
  3108  			Action: plans.Update,
  3109  			Mode:   addrs.ManagedResourceMode,
  3110  			Before: cty.ObjectVal(map[string]cty.Value{
  3111  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3112  				"ami": cty.StringVal("ami-BEFORE"),
  3113  				"disks": cty.SetValEmpty(cty.Object(map[string]cty.Type{
  3114  					"mount_point": cty.String,
  3115  					"size":        cty.String,
  3116  				})),
  3117  				"root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{
  3118  					"volume_type": cty.String,
  3119  				})),
  3120  			}),
  3121  			After: cty.ObjectVal(map[string]cty.Value{
  3122  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3123  				"ami": cty.StringVal("ami-AFTER"),
  3124  				"disks": cty.SetVal([]cty.Value{
  3125  					cty.ObjectVal(map[string]cty.Value{
  3126  						"mount_point": cty.StringVal("/var/diska"),
  3127  						"size":        cty.NullVal(cty.String),
  3128  					}),
  3129  				}),
  3130  				"root_block_device": cty.SetVal([]cty.Value{
  3131  					cty.ObjectVal(map[string]cty.Value{
  3132  						"volume_type": cty.StringVal("gp2"),
  3133  					}),
  3134  				}),
  3135  			}),
  3136  			AfterValMarks: []cty.PathValueMarks{
  3137  				{
  3138  					Path:  cty.Path{cty.GetAttrStep{Name: "disks"}},
  3139  					Marks: cty.NewValueMarks(marks.Sensitive),
  3140  				},
  3141  			},
  3142  			RequiredReplace: cty.NewPathSet(),
  3143  			Schema:          testSchema(configschema.NestingSet),
  3144  			ExpectedOutput: `  # test_instance.example will be updated in-place
  3145    ~ resource "test_instance" "example" {
  3146        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  3147        # Warning: this attribute value will be marked as sensitive and will not
  3148        # display in UI output after applying this change.
  3149        ~ disks = (sensitive value)
  3150          id    = "i-02ae66f368e8518a9"
  3151  
  3152        + root_block_device {
  3153            + volume_type = "gp2"
  3154          }
  3155      }
  3156  `,
  3157  		},
  3158  		"in-place update - marking set sensitive": {
  3159  			Action: plans.Update,
  3160  			Mode:   addrs.ManagedResourceMode,
  3161  			Before: cty.ObjectVal(map[string]cty.Value{
  3162  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3163  				"ami": cty.StringVal("ami-BEFORE"),
  3164  				"disks": cty.SetVal([]cty.Value{
  3165  					cty.ObjectVal(map[string]cty.Value{
  3166  						"mount_point": cty.StringVal("/var/diska"),
  3167  						"size":        cty.StringVal("50GB"),
  3168  					}),
  3169  				}),
  3170  				"root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{
  3171  					"volume_type": cty.String,
  3172  				})),
  3173  			}),
  3174  			After: cty.ObjectVal(map[string]cty.Value{
  3175  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3176  				"ami": cty.StringVal("ami-AFTER"),
  3177  				"disks": cty.SetVal([]cty.Value{
  3178  					cty.ObjectVal(map[string]cty.Value{
  3179  						"mount_point": cty.StringVal("/var/diska"),
  3180  						"size":        cty.StringVal("50GB"),
  3181  					}),
  3182  				}),
  3183  				"root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{
  3184  					"volume_type": cty.String,
  3185  				})),
  3186  			}),
  3187  			AfterValMarks: []cty.PathValueMarks{
  3188  				{
  3189  					Path:  cty.Path{cty.GetAttrStep{Name: "disks"}},
  3190  					Marks: cty.NewValueMarks(marks.Sensitive),
  3191  				},
  3192  			},
  3193  			RequiredReplace: cty.NewPathSet(),
  3194  			Schema:          testSchema(configschema.NestingSet),
  3195  			ExpectedOutput: `  # test_instance.example will be updated in-place
  3196    ~ resource "test_instance" "example" {
  3197        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  3198        # Warning: this attribute value will be marked as sensitive and will not
  3199        # display in UI output after applying this change. The value is unchanged.
  3200        ~ disks = (sensitive value)
  3201          id    = "i-02ae66f368e8518a9"
  3202      }
  3203  `,
  3204  		},
  3205  		"in-place update - insertion": {
  3206  			Action: plans.Update,
  3207  			Mode:   addrs.ManagedResourceMode,
  3208  			Before: cty.ObjectVal(map[string]cty.Value{
  3209  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3210  				"ami": cty.StringVal("ami-BEFORE"),
  3211  				"disks": cty.SetVal([]cty.Value{
  3212  					cty.ObjectVal(map[string]cty.Value{
  3213  						"mount_point": cty.StringVal("/var/diska"),
  3214  						"size":        cty.NullVal(cty.String),
  3215  					}),
  3216  					cty.ObjectVal(map[string]cty.Value{
  3217  						"mount_point": cty.StringVal("/var/diskb"),
  3218  						"size":        cty.StringVal("100GB"),
  3219  					}),
  3220  				}),
  3221  				"root_block_device": cty.SetVal([]cty.Value{
  3222  					cty.ObjectVal(map[string]cty.Value{
  3223  						"volume_type": cty.StringVal("gp2"),
  3224  						"new_field":   cty.NullVal(cty.String),
  3225  					}),
  3226  				}),
  3227  			}),
  3228  			After: cty.ObjectVal(map[string]cty.Value{
  3229  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3230  				"ami": cty.StringVal("ami-AFTER"),
  3231  				"disks": cty.SetVal([]cty.Value{
  3232  					cty.ObjectVal(map[string]cty.Value{
  3233  						"mount_point": cty.StringVal("/var/diska"),
  3234  						"size":        cty.StringVal("50GB"),
  3235  					}),
  3236  					cty.ObjectVal(map[string]cty.Value{
  3237  						"mount_point": cty.StringVal("/var/diskb"),
  3238  						"size":        cty.StringVal("100GB"),
  3239  					}),
  3240  				}),
  3241  				"root_block_device": cty.SetVal([]cty.Value{
  3242  					cty.ObjectVal(map[string]cty.Value{
  3243  						"volume_type": cty.StringVal("gp2"),
  3244  						"new_field":   cty.StringVal("new_value"),
  3245  					}),
  3246  				}),
  3247  			}),
  3248  			RequiredReplace: cty.NewPathSet(),
  3249  			Schema:          testSchemaPlus(configschema.NestingSet),
  3250  			ExpectedOutput: `  # test_instance.example will be updated in-place
  3251    ~ resource "test_instance" "example" {
  3252        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  3253        ~ disks = [
  3254            + {
  3255                + mount_point = "/var/diska"
  3256                + size        = "50GB"
  3257              },
  3258            - {
  3259                - mount_point = "/var/diska" -> null
  3260              },
  3261              # (1 unchanged element hidden)
  3262          ]
  3263          id    = "i-02ae66f368e8518a9"
  3264  
  3265        + root_block_device {
  3266            + new_field   = "new_value"
  3267            + volume_type = "gp2"
  3268          }
  3269        - root_block_device {
  3270            - volume_type = "gp2" -> null
  3271          }
  3272      }
  3273  `,
  3274  		},
  3275  		"force-new update (whole block)": {
  3276  			Action:       plans.DeleteThenCreate,
  3277  			ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate,
  3278  			Mode:         addrs.ManagedResourceMode,
  3279  			Before: cty.ObjectVal(map[string]cty.Value{
  3280  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3281  				"ami": cty.StringVal("ami-BEFORE"),
  3282  				"root_block_device": cty.SetVal([]cty.Value{
  3283  					cty.ObjectVal(map[string]cty.Value{
  3284  						"volume_type": cty.StringVal("gp2"),
  3285  					}),
  3286  				}),
  3287  				"disks": cty.SetVal([]cty.Value{
  3288  					cty.ObjectVal(map[string]cty.Value{
  3289  						"mount_point": cty.StringVal("/var/diska"),
  3290  						"size":        cty.StringVal("50GB"),
  3291  					}),
  3292  				}),
  3293  			}),
  3294  			After: cty.ObjectVal(map[string]cty.Value{
  3295  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3296  				"ami": cty.StringVal("ami-AFTER"),
  3297  				"root_block_device": cty.SetVal([]cty.Value{
  3298  					cty.ObjectVal(map[string]cty.Value{
  3299  						"volume_type": cty.StringVal("different"),
  3300  					}),
  3301  				}),
  3302  				"disks": cty.SetVal([]cty.Value{
  3303  					cty.ObjectVal(map[string]cty.Value{
  3304  						"mount_point": cty.StringVal("/var/diskb"),
  3305  						"size":        cty.StringVal("50GB"),
  3306  					}),
  3307  				}),
  3308  			}),
  3309  			RequiredReplace: cty.NewPathSet(
  3310  				cty.Path{cty.GetAttrStep{Name: "root_block_device"}},
  3311  				cty.Path{cty.GetAttrStep{Name: "disks"}},
  3312  			),
  3313  			Schema: testSchema(configschema.NestingSet),
  3314  			ExpectedOutput: `  # test_instance.example must be replaced
  3315  -/+ resource "test_instance" "example" {
  3316        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  3317        ~ disks = [
  3318            - { # forces replacement
  3319                - mount_point = "/var/diska" -> null
  3320                - size        = "50GB" -> null
  3321              },
  3322            + { # forces replacement
  3323                + mount_point = "/var/diskb"
  3324                + size        = "50GB"
  3325              },
  3326          ]
  3327          id    = "i-02ae66f368e8518a9"
  3328  
  3329        + root_block_device { # forces replacement
  3330            + volume_type = "different"
  3331          }
  3332        - root_block_device { # forces replacement
  3333            - volume_type = "gp2" -> null
  3334          }
  3335      }
  3336  `,
  3337  		},
  3338  		"in-place update - deletion": {
  3339  			Action: plans.Update,
  3340  			Mode:   addrs.ManagedResourceMode,
  3341  			Before: cty.ObjectVal(map[string]cty.Value{
  3342  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3343  				"ami": cty.StringVal("ami-BEFORE"),
  3344  				"root_block_device": cty.SetVal([]cty.Value{
  3345  					cty.ObjectVal(map[string]cty.Value{
  3346  						"volume_type": cty.StringVal("gp2"),
  3347  						"new_field":   cty.StringVal("new_value"),
  3348  					}),
  3349  				}),
  3350  				"disks": cty.SetVal([]cty.Value{
  3351  					cty.ObjectVal(map[string]cty.Value{
  3352  						"mount_point": cty.StringVal("/var/diska"),
  3353  						"size":        cty.StringVal("50GB"),
  3354  					}),
  3355  				}),
  3356  			}),
  3357  			After: cty.ObjectVal(map[string]cty.Value{
  3358  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3359  				"ami": cty.StringVal("ami-AFTER"),
  3360  				"root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{
  3361  					"volume_type": cty.String,
  3362  					"new_field":   cty.String,
  3363  				})),
  3364  				"disks": cty.SetValEmpty(cty.Object(map[string]cty.Type{
  3365  					"mount_point": cty.String,
  3366  					"size":        cty.String,
  3367  				})),
  3368  			}),
  3369  			RequiredReplace: cty.NewPathSet(),
  3370  			Schema:          testSchemaPlus(configschema.NestingSet),
  3371  			ExpectedOutput: `  # test_instance.example will be updated in-place
  3372    ~ resource "test_instance" "example" {
  3373        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  3374        ~ disks = [
  3375            - {
  3376                - mount_point = "/var/diska" -> null
  3377                - size        = "50GB" -> null
  3378              },
  3379          ]
  3380          id    = "i-02ae66f368e8518a9"
  3381  
  3382        - root_block_device {
  3383            - new_field   = "new_value" -> null
  3384            - volume_type = "gp2" -> null
  3385          }
  3386      }
  3387  `,
  3388  		},
  3389  		"in-place update - empty nested sets": {
  3390  			Action: plans.Update,
  3391  			Mode:   addrs.ManagedResourceMode,
  3392  			Before: cty.ObjectVal(map[string]cty.Value{
  3393  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3394  				"ami": cty.StringVal("ami-BEFORE"),
  3395  				"disks": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
  3396  					"mount_point": cty.String,
  3397  					"size":        cty.String,
  3398  				}))),
  3399  				"root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{
  3400  					"volume_type": cty.String,
  3401  				})),
  3402  			}),
  3403  			After: cty.ObjectVal(map[string]cty.Value{
  3404  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3405  				"ami": cty.StringVal("ami-AFTER"),
  3406  				"disks": cty.SetValEmpty(cty.Object(map[string]cty.Type{
  3407  					"mount_point": cty.String,
  3408  					"size":        cty.String,
  3409  				})),
  3410  				"root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{
  3411  					"volume_type": cty.String,
  3412  				})),
  3413  			}),
  3414  			RequiredReplace: cty.NewPathSet(),
  3415  			Schema:          testSchema(configschema.NestingSet),
  3416  			ExpectedOutput: `  # test_instance.example will be updated in-place
  3417    ~ resource "test_instance" "example" {
  3418        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  3419        + disks = [
  3420          ]
  3421          id    = "i-02ae66f368e8518a9"
  3422      }
  3423  `,
  3424  		},
  3425  		"in-place update - null insertion": {
  3426  			Action: plans.Update,
  3427  			Mode:   addrs.ManagedResourceMode,
  3428  			Before: cty.ObjectVal(map[string]cty.Value{
  3429  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3430  				"ami": cty.StringVal("ami-BEFORE"),
  3431  				"disks": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
  3432  					"mount_point": cty.String,
  3433  					"size":        cty.String,
  3434  				}))),
  3435  				"root_block_device": cty.SetVal([]cty.Value{
  3436  					cty.ObjectVal(map[string]cty.Value{
  3437  						"volume_type": cty.StringVal("gp2"),
  3438  						"new_field":   cty.NullVal(cty.String),
  3439  					}),
  3440  				}),
  3441  			}),
  3442  			After: cty.ObjectVal(map[string]cty.Value{
  3443  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3444  				"ami": cty.StringVal("ami-AFTER"),
  3445  				"disks": cty.SetVal([]cty.Value{
  3446  					cty.ObjectVal(map[string]cty.Value{
  3447  						"mount_point": cty.StringVal("/var/diska"),
  3448  						"size":        cty.StringVal("50GB"),
  3449  					}),
  3450  				}),
  3451  				"root_block_device": cty.SetVal([]cty.Value{
  3452  					cty.ObjectVal(map[string]cty.Value{
  3453  						"volume_type": cty.StringVal("gp2"),
  3454  						"new_field":   cty.StringVal("new_value"),
  3455  					}),
  3456  				}),
  3457  			}),
  3458  			RequiredReplace: cty.NewPathSet(),
  3459  			Schema:          testSchemaPlus(configschema.NestingSet),
  3460  			ExpectedOutput: `  # test_instance.example will be updated in-place
  3461    ~ resource "test_instance" "example" {
  3462        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  3463        + disks = [
  3464            + {
  3465                + mount_point = "/var/diska"
  3466                + size        = "50GB"
  3467              },
  3468          ]
  3469          id    = "i-02ae66f368e8518a9"
  3470  
  3471        + root_block_device {
  3472            + new_field   = "new_value"
  3473            + volume_type = "gp2"
  3474          }
  3475        - root_block_device {
  3476            - volume_type = "gp2" -> null
  3477          }
  3478      }
  3479  `,
  3480  		},
  3481  		"in-place update - unknown": {
  3482  			Action: plans.Update,
  3483  			Mode:   addrs.ManagedResourceMode,
  3484  			Before: cty.ObjectVal(map[string]cty.Value{
  3485  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3486  				"ami": cty.StringVal("ami-BEFORE"),
  3487  				"disks": cty.SetVal([]cty.Value{
  3488  					cty.ObjectVal(map[string]cty.Value{
  3489  						"mount_point": cty.StringVal("/var/diska"),
  3490  						"size":        cty.StringVal("50GB"),
  3491  					}),
  3492  				}),
  3493  				"root_block_device": cty.SetVal([]cty.Value{
  3494  					cty.ObjectVal(map[string]cty.Value{
  3495  						"volume_type": cty.StringVal("gp2"),
  3496  						"new_field":   cty.StringVal("new_value"),
  3497  					}),
  3498  				}),
  3499  			}),
  3500  			After: cty.ObjectVal(map[string]cty.Value{
  3501  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3502  				"ami": cty.StringVal("ami-AFTER"),
  3503  				"disks": cty.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{
  3504  					"mount_point": cty.String,
  3505  					"size":        cty.String,
  3506  				}))),
  3507  				"root_block_device": cty.SetVal([]cty.Value{
  3508  					cty.ObjectVal(map[string]cty.Value{
  3509  						"volume_type": cty.StringVal("gp2"),
  3510  						"new_field":   cty.StringVal("new_value"),
  3511  					}),
  3512  				}),
  3513  			}),
  3514  			RequiredReplace: cty.NewPathSet(),
  3515  			Schema:          testSchemaPlus(configschema.NestingSet),
  3516  			ExpectedOutput: `  # test_instance.example will be updated in-place
  3517    ~ resource "test_instance" "example" {
  3518        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  3519        ~ disks = [
  3520            - {
  3521                - mount_point = "/var/diska" -> null
  3522                - size        = "50GB" -> null
  3523              },
  3524          ] -> (known after apply)
  3525          id    = "i-02ae66f368e8518a9"
  3526  
  3527          # (1 unchanged block hidden)
  3528      }
  3529  `,
  3530  		},
  3531  	}
  3532  	runTestCases(t, testCases)
  3533  }
  3534  
  3535  func TestResourceChange_nestedMap(t *testing.T) {
  3536  	testCases := map[string]testCase{
  3537  		"creation from null": {
  3538  			Action: plans.Update,
  3539  			Mode:   addrs.ManagedResourceMode,
  3540  			Before: cty.ObjectVal(map[string]cty.Value{
  3541  				"id":  cty.NullVal(cty.String),
  3542  				"ami": cty.NullVal(cty.String),
  3543  				"disks": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{
  3544  					"mount_point": cty.String,
  3545  					"size":        cty.String,
  3546  				}))),
  3547  				"root_block_device": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{
  3548  					"volume_type": cty.String,
  3549  				}))),
  3550  			}),
  3551  			After: cty.ObjectVal(map[string]cty.Value{
  3552  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3553  				"ami": cty.StringVal("ami-AFTER"),
  3554  				"disks": cty.MapVal(map[string]cty.Value{
  3555  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  3556  						"mount_point": cty.StringVal("/var/diska"),
  3557  						"size":        cty.NullVal(cty.String),
  3558  					}),
  3559  				}),
  3560  				"root_block_device": cty.MapVal(map[string]cty.Value{
  3561  					"a": cty.ObjectVal(map[string]cty.Value{
  3562  						"volume_type": cty.StringVal("gp2"),
  3563  					}),
  3564  				}),
  3565  			}),
  3566  			RequiredReplace: cty.NewPathSet(),
  3567  			Schema:          testSchema(configschema.NestingMap),
  3568  			ExpectedOutput: `  # test_instance.example will be updated in-place
  3569    ~ resource "test_instance" "example" {
  3570        + ami   = "ami-AFTER"
  3571        + disks = {
  3572            + "disk_a" = {
  3573                + mount_point = "/var/diska"
  3574              },
  3575          }
  3576        + id    = "i-02ae66f368e8518a9"
  3577  
  3578        + root_block_device "a" {
  3579            + volume_type = "gp2"
  3580          }
  3581      }
  3582  `,
  3583  		},
  3584  		"in-place update - creation": {
  3585  			Action: plans.Update,
  3586  			Mode:   addrs.ManagedResourceMode,
  3587  			Before: cty.ObjectVal(map[string]cty.Value{
  3588  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3589  				"ami": cty.StringVal("ami-BEFORE"),
  3590  				"disks": cty.MapValEmpty(cty.Object(map[string]cty.Type{
  3591  					"mount_point": cty.String,
  3592  					"size":        cty.String,
  3593  				})),
  3594  				"root_block_device": cty.MapValEmpty(cty.Object(map[string]cty.Type{
  3595  					"volume_type": cty.String,
  3596  				})),
  3597  			}),
  3598  			After: cty.ObjectVal(map[string]cty.Value{
  3599  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3600  				"ami": cty.StringVal("ami-AFTER"),
  3601  				"disks": cty.MapVal(map[string]cty.Value{
  3602  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  3603  						"mount_point": cty.StringVal("/var/diska"),
  3604  						"size":        cty.NullVal(cty.String),
  3605  					}),
  3606  				}),
  3607  				"root_block_device": cty.MapVal(map[string]cty.Value{
  3608  					"a": cty.ObjectVal(map[string]cty.Value{
  3609  						"volume_type": cty.StringVal("gp2"),
  3610  					}),
  3611  				}),
  3612  			}),
  3613  			RequiredReplace: cty.NewPathSet(),
  3614  			Schema:          testSchema(configschema.NestingMap),
  3615  			ExpectedOutput: `  # test_instance.example will be updated in-place
  3616    ~ resource "test_instance" "example" {
  3617        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  3618        ~ disks = {
  3619            + "disk_a" = {
  3620                + mount_point = "/var/diska"
  3621              },
  3622          }
  3623          id    = "i-02ae66f368e8518a9"
  3624  
  3625        + root_block_device "a" {
  3626            + volume_type = "gp2"
  3627          }
  3628      }
  3629  `,
  3630  		},
  3631  		"in-place update - change attr": {
  3632  			Action: plans.Update,
  3633  			Mode:   addrs.ManagedResourceMode,
  3634  			Before: cty.ObjectVal(map[string]cty.Value{
  3635  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3636  				"ami": cty.StringVal("ami-BEFORE"),
  3637  				"disks": cty.MapVal(map[string]cty.Value{
  3638  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  3639  						"mount_point": cty.StringVal("/var/diska"),
  3640  						"size":        cty.NullVal(cty.String),
  3641  					}),
  3642  				}),
  3643  				"root_block_device": cty.MapVal(map[string]cty.Value{
  3644  					"a": cty.ObjectVal(map[string]cty.Value{
  3645  						"volume_type": cty.StringVal("gp2"),
  3646  						"new_field":   cty.NullVal(cty.String),
  3647  					}),
  3648  				}),
  3649  			}),
  3650  			After: cty.ObjectVal(map[string]cty.Value{
  3651  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3652  				"ami": cty.StringVal("ami-AFTER"),
  3653  				"disks": cty.MapVal(map[string]cty.Value{
  3654  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  3655  						"mount_point": cty.StringVal("/var/diska"),
  3656  						"size":        cty.StringVal("50GB"),
  3657  					}),
  3658  				}),
  3659  				"root_block_device": cty.MapVal(map[string]cty.Value{
  3660  					"a": cty.ObjectVal(map[string]cty.Value{
  3661  						"volume_type": cty.StringVal("gp2"),
  3662  						"new_field":   cty.StringVal("new_value"),
  3663  					}),
  3664  				}),
  3665  			}),
  3666  			RequiredReplace: cty.NewPathSet(),
  3667  			Schema:          testSchemaPlus(configschema.NestingMap),
  3668  			ExpectedOutput: `  # test_instance.example will be updated in-place
  3669    ~ resource "test_instance" "example" {
  3670        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  3671        ~ disks = {
  3672            ~ "disk_a" = {
  3673                + size        = "50GB"
  3674                  # (1 unchanged attribute hidden)
  3675              },
  3676          }
  3677          id    = "i-02ae66f368e8518a9"
  3678  
  3679        ~ root_block_device "a" {
  3680            + new_field   = "new_value"
  3681              # (1 unchanged attribute hidden)
  3682          }
  3683      }
  3684  `,
  3685  		},
  3686  		"in-place update - insertion": {
  3687  			Action: plans.Update,
  3688  			Mode:   addrs.ManagedResourceMode,
  3689  			Before: cty.ObjectVal(map[string]cty.Value{
  3690  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3691  				"ami": cty.StringVal("ami-BEFORE"),
  3692  				"disks": cty.MapVal(map[string]cty.Value{
  3693  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  3694  						"mount_point": cty.StringVal("/var/diska"),
  3695  						"size":        cty.StringVal("50GB"),
  3696  					}),
  3697  				}),
  3698  				"root_block_device": cty.MapVal(map[string]cty.Value{
  3699  					"a": cty.ObjectVal(map[string]cty.Value{
  3700  						"volume_type": cty.StringVal("gp2"),
  3701  						"new_field":   cty.NullVal(cty.String),
  3702  					}),
  3703  				}),
  3704  			}),
  3705  			After: cty.ObjectVal(map[string]cty.Value{
  3706  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3707  				"ami": cty.StringVal("ami-AFTER"),
  3708  				"disks": cty.MapVal(map[string]cty.Value{
  3709  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  3710  						"mount_point": cty.StringVal("/var/diska"),
  3711  						"size":        cty.StringVal("50GB"),
  3712  					}),
  3713  					"disk_2": cty.ObjectVal(map[string]cty.Value{
  3714  						"mount_point": cty.StringVal("/var/disk2"),
  3715  						"size":        cty.StringVal("50GB"),
  3716  					}),
  3717  				}),
  3718  				"root_block_device": cty.MapVal(map[string]cty.Value{
  3719  					"a": cty.ObjectVal(map[string]cty.Value{
  3720  						"volume_type": cty.StringVal("gp2"),
  3721  						"new_field":   cty.NullVal(cty.String),
  3722  					}),
  3723  					"b": cty.ObjectVal(map[string]cty.Value{
  3724  						"volume_type": cty.StringVal("gp2"),
  3725  						"new_field":   cty.StringVal("new_value"),
  3726  					}),
  3727  				}),
  3728  			}),
  3729  			RequiredReplace: cty.NewPathSet(),
  3730  			Schema:          testSchemaPlus(configschema.NestingMap),
  3731  			ExpectedOutput: `  # test_instance.example will be updated in-place
  3732    ~ resource "test_instance" "example" {
  3733        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  3734        ~ disks = {
  3735            + "disk_2" = {
  3736                + mount_point = "/var/disk2"
  3737                + size        = "50GB"
  3738              },
  3739              # (1 unchanged element hidden)
  3740          }
  3741          id    = "i-02ae66f368e8518a9"
  3742  
  3743        + root_block_device "b" {
  3744            + new_field   = "new_value"
  3745            + volume_type = "gp2"
  3746          }
  3747  
  3748          # (1 unchanged block hidden)
  3749      }
  3750  `,
  3751  		},
  3752  		"force-new update (whole block)": {
  3753  			Action:       plans.DeleteThenCreate,
  3754  			ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate,
  3755  			Mode:         addrs.ManagedResourceMode,
  3756  			Before: cty.ObjectVal(map[string]cty.Value{
  3757  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3758  				"ami": cty.StringVal("ami-BEFORE"),
  3759  				"disks": cty.MapVal(map[string]cty.Value{
  3760  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  3761  						"mount_point": cty.StringVal("/var/diska"),
  3762  						"size":        cty.StringVal("50GB"),
  3763  					}),
  3764  				}),
  3765  				"root_block_device": cty.MapVal(map[string]cty.Value{
  3766  					"a": cty.ObjectVal(map[string]cty.Value{
  3767  						"volume_type": cty.StringVal("gp2"),
  3768  					}),
  3769  					"b": cty.ObjectVal(map[string]cty.Value{
  3770  						"volume_type": cty.StringVal("standard"),
  3771  					}),
  3772  				}),
  3773  			}),
  3774  			After: cty.ObjectVal(map[string]cty.Value{
  3775  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3776  				"ami": cty.StringVal("ami-AFTER"),
  3777  				"disks": cty.MapVal(map[string]cty.Value{
  3778  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  3779  						"mount_point": cty.StringVal("/var/diska"),
  3780  						"size":        cty.StringVal("100GB"),
  3781  					}),
  3782  				}),
  3783  				"root_block_device": cty.MapVal(map[string]cty.Value{
  3784  					"a": cty.ObjectVal(map[string]cty.Value{
  3785  						"volume_type": cty.StringVal("different"),
  3786  					}),
  3787  					"b": cty.ObjectVal(map[string]cty.Value{
  3788  						"volume_type": cty.StringVal("standard"),
  3789  					}),
  3790  				}),
  3791  			}),
  3792  			RequiredReplace: cty.NewPathSet(cty.Path{
  3793  				cty.GetAttrStep{Name: "root_block_device"},
  3794  				cty.IndexStep{Key: cty.StringVal("a")},
  3795  			},
  3796  				cty.Path{cty.GetAttrStep{Name: "disks"}},
  3797  			),
  3798  			Schema: testSchema(configschema.NestingMap),
  3799  			ExpectedOutput: `  # test_instance.example must be replaced
  3800  -/+ resource "test_instance" "example" {
  3801        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  3802        ~ disks = {
  3803            ~ "disk_a" = { # forces replacement
  3804                ~ size        = "50GB" -> "100GB"
  3805                  # (1 unchanged attribute hidden)
  3806              },
  3807          }
  3808          id    = "i-02ae66f368e8518a9"
  3809  
  3810        ~ root_block_device "a" { # forces replacement
  3811            ~ volume_type = "gp2" -> "different"
  3812          }
  3813  
  3814          # (1 unchanged block hidden)
  3815      }
  3816  `,
  3817  		},
  3818  		"in-place update - deletion": {
  3819  			Action: plans.Update,
  3820  			Mode:   addrs.ManagedResourceMode,
  3821  			Before: cty.ObjectVal(map[string]cty.Value{
  3822  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3823  				"ami": cty.StringVal("ami-BEFORE"),
  3824  				"disks": cty.MapVal(map[string]cty.Value{
  3825  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  3826  						"mount_point": cty.StringVal("/var/diska"),
  3827  						"size":        cty.StringVal("50GB"),
  3828  					}),
  3829  				}),
  3830  				"root_block_device": cty.MapVal(map[string]cty.Value{
  3831  					"a": cty.ObjectVal(map[string]cty.Value{
  3832  						"volume_type": cty.StringVal("gp2"),
  3833  						"new_field":   cty.StringVal("new_value"),
  3834  					}),
  3835  				}),
  3836  			}),
  3837  			After: cty.ObjectVal(map[string]cty.Value{
  3838  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3839  				"ami": cty.StringVal("ami-AFTER"),
  3840  				"disks": cty.MapValEmpty(cty.Object(map[string]cty.Type{
  3841  					"mount_point": cty.String,
  3842  					"size":        cty.String,
  3843  				})),
  3844  				"root_block_device": cty.MapValEmpty(cty.Object(map[string]cty.Type{
  3845  					"volume_type": cty.String,
  3846  					"new_field":   cty.String,
  3847  				})),
  3848  			}),
  3849  			RequiredReplace: cty.NewPathSet(),
  3850  			Schema:          testSchemaPlus(configschema.NestingMap),
  3851  			ExpectedOutput: `  # test_instance.example will be updated in-place
  3852    ~ resource "test_instance" "example" {
  3853        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  3854        ~ disks = {
  3855            - "disk_a" = {
  3856                - mount_point = "/var/diska" -> null
  3857                - size        = "50GB" -> null
  3858              },
  3859          }
  3860          id    = "i-02ae66f368e8518a9"
  3861  
  3862        - root_block_device "a" {
  3863            - new_field   = "new_value" -> null
  3864            - volume_type = "gp2" -> null
  3865          }
  3866      }
  3867  `,
  3868  		},
  3869  		"in-place update - unknown": {
  3870  			Action: plans.Update,
  3871  			Mode:   addrs.ManagedResourceMode,
  3872  			Before: cty.ObjectVal(map[string]cty.Value{
  3873  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3874  				"ami": cty.StringVal("ami-BEFORE"),
  3875  				"disks": cty.MapVal(map[string]cty.Value{
  3876  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  3877  						"mount_point": cty.StringVal("/var/diska"),
  3878  						"size":        cty.StringVal("50GB"),
  3879  					}),
  3880  				}),
  3881  				"root_block_device": cty.MapVal(map[string]cty.Value{
  3882  					"a": cty.ObjectVal(map[string]cty.Value{
  3883  						"volume_type": cty.StringVal("gp2"),
  3884  						"new_field":   cty.StringVal("new_value"),
  3885  					}),
  3886  				}),
  3887  			}),
  3888  			After: cty.ObjectVal(map[string]cty.Value{
  3889  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3890  				"ami": cty.StringVal("ami-AFTER"),
  3891  				"disks": cty.UnknownVal(cty.Map(cty.Object(map[string]cty.Type{
  3892  					"mount_point": cty.String,
  3893  					"size":        cty.String,
  3894  				}))),
  3895  				"root_block_device": cty.MapVal(map[string]cty.Value{
  3896  					"a": cty.ObjectVal(map[string]cty.Value{
  3897  						"volume_type": cty.StringVal("gp2"),
  3898  						"new_field":   cty.StringVal("new_value"),
  3899  					}),
  3900  				}),
  3901  			}),
  3902  			RequiredReplace: cty.NewPathSet(),
  3903  			Schema:          testSchemaPlus(configschema.NestingMap),
  3904  			ExpectedOutput: `  # test_instance.example will be updated in-place
  3905    ~ resource "test_instance" "example" {
  3906        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  3907        ~ disks = {
  3908            - "disk_a" = {
  3909                - mount_point = "/var/diska" -> null
  3910                - size        = "50GB" -> null
  3911              },
  3912          } -> (known after apply)
  3913          id    = "i-02ae66f368e8518a9"
  3914  
  3915          # (1 unchanged block hidden)
  3916      }
  3917  `,
  3918  		},
  3919  		"in-place update - insertion sensitive": {
  3920  			Action: plans.Update,
  3921  			Mode:   addrs.ManagedResourceMode,
  3922  			Before: cty.ObjectVal(map[string]cty.Value{
  3923  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3924  				"ami": cty.StringVal("ami-BEFORE"),
  3925  				"disks": cty.MapValEmpty(cty.Object(map[string]cty.Type{
  3926  					"mount_point": cty.String,
  3927  					"size":        cty.String,
  3928  				})),
  3929  				"root_block_device": cty.MapVal(map[string]cty.Value{
  3930  					"a": cty.ObjectVal(map[string]cty.Value{
  3931  						"volume_type": cty.StringVal("gp2"),
  3932  						"new_field":   cty.StringVal("new_value"),
  3933  					}),
  3934  				}),
  3935  			}),
  3936  			After: cty.ObjectVal(map[string]cty.Value{
  3937  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3938  				"ami": cty.StringVal("ami-AFTER"),
  3939  				"disks": cty.MapVal(map[string]cty.Value{
  3940  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  3941  						"mount_point": cty.StringVal("/var/diska"),
  3942  						"size":        cty.StringVal("50GB"),
  3943  					}),
  3944  				}),
  3945  				"root_block_device": cty.MapVal(map[string]cty.Value{
  3946  					"a": cty.ObjectVal(map[string]cty.Value{
  3947  						"volume_type": cty.StringVal("gp2"),
  3948  						"new_field":   cty.StringVal("new_value"),
  3949  					}),
  3950  				}),
  3951  			}),
  3952  			AfterValMarks: []cty.PathValueMarks{
  3953  				{
  3954  					Path: cty.Path{cty.GetAttrStep{Name: "disks"},
  3955  						cty.IndexStep{Key: cty.StringVal("disk_a")},
  3956  						cty.GetAttrStep{Name: "mount_point"},
  3957  					},
  3958  					Marks: cty.NewValueMarks(marks.Sensitive),
  3959  				},
  3960  			},
  3961  			RequiredReplace: cty.NewPathSet(),
  3962  			Schema:          testSchemaPlus(configschema.NestingMap),
  3963  			ExpectedOutput: `  # test_instance.example will be updated in-place
  3964    ~ resource "test_instance" "example" {
  3965        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  3966        ~ disks = {
  3967            + "disk_a" = {
  3968                + mount_point = (sensitive value)
  3969                + size        = "50GB"
  3970              },
  3971          }
  3972          id    = "i-02ae66f368e8518a9"
  3973  
  3974          # (1 unchanged block hidden)
  3975      }
  3976  `,
  3977  		},
  3978  		"in-place update - multiple unchanged blocks": {
  3979  			Action: plans.Update,
  3980  			Mode:   addrs.ManagedResourceMode,
  3981  			Before: cty.ObjectVal(map[string]cty.Value{
  3982  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  3983  				"ami": cty.StringVal("ami-BEFORE"),
  3984  				"disks": cty.MapVal(map[string]cty.Value{
  3985  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  3986  						"mount_point": cty.StringVal("/var/diska"),
  3987  						"size":        cty.StringVal("50GB"),
  3988  					}),
  3989  				}),
  3990  				"root_block_device": cty.MapVal(map[string]cty.Value{
  3991  					"a": cty.ObjectVal(map[string]cty.Value{
  3992  						"volume_type": cty.StringVal("gp2"),
  3993  					}),
  3994  					"b": cty.ObjectVal(map[string]cty.Value{
  3995  						"volume_type": cty.StringVal("gp2"),
  3996  					}),
  3997  				}),
  3998  			}),
  3999  			After: cty.ObjectVal(map[string]cty.Value{
  4000  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4001  				"ami": cty.StringVal("ami-AFTER"),
  4002  				"disks": cty.MapVal(map[string]cty.Value{
  4003  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  4004  						"mount_point": cty.StringVal("/var/diska"),
  4005  						"size":        cty.StringVal("50GB"),
  4006  					}),
  4007  				}),
  4008  				"root_block_device": cty.MapVal(map[string]cty.Value{
  4009  					"a": cty.ObjectVal(map[string]cty.Value{
  4010  						"volume_type": cty.StringVal("gp2"),
  4011  					}),
  4012  					"b": cty.ObjectVal(map[string]cty.Value{
  4013  						"volume_type": cty.StringVal("gp2"),
  4014  					}),
  4015  				}),
  4016  			}),
  4017  			RequiredReplace: cty.NewPathSet(),
  4018  			Schema:          testSchema(configschema.NestingMap),
  4019  			ExpectedOutput: `  # test_instance.example will be updated in-place
  4020    ~ resource "test_instance" "example" {
  4021        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  4022          id    = "i-02ae66f368e8518a9"
  4023          # (1 unchanged attribute hidden)
  4024  
  4025          # (2 unchanged blocks hidden)
  4026      }
  4027  `,
  4028  		},
  4029  		"in-place update - multiple blocks first changed": {
  4030  			Action: plans.Update,
  4031  			Mode:   addrs.ManagedResourceMode,
  4032  			Before: cty.ObjectVal(map[string]cty.Value{
  4033  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4034  				"ami": cty.StringVal("ami-BEFORE"),
  4035  				"disks": cty.MapVal(map[string]cty.Value{
  4036  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  4037  						"mount_point": cty.StringVal("/var/diska"),
  4038  						"size":        cty.StringVal("50GB"),
  4039  					}),
  4040  				}),
  4041  				"root_block_device": cty.MapVal(map[string]cty.Value{
  4042  					"a": cty.ObjectVal(map[string]cty.Value{
  4043  						"volume_type": cty.StringVal("gp2"),
  4044  					}),
  4045  					"b": cty.ObjectVal(map[string]cty.Value{
  4046  						"volume_type": cty.StringVal("gp2"),
  4047  					}),
  4048  				}),
  4049  			}),
  4050  			After: cty.ObjectVal(map[string]cty.Value{
  4051  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4052  				"ami": cty.StringVal("ami-AFTER"),
  4053  				"disks": cty.MapVal(map[string]cty.Value{
  4054  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  4055  						"mount_point": cty.StringVal("/var/diska"),
  4056  						"size":        cty.StringVal("50GB"),
  4057  					}),
  4058  				}),
  4059  				"root_block_device": cty.MapVal(map[string]cty.Value{
  4060  					"a": cty.ObjectVal(map[string]cty.Value{
  4061  						"volume_type": cty.StringVal("gp2"),
  4062  					}),
  4063  					"b": cty.ObjectVal(map[string]cty.Value{
  4064  						"volume_type": cty.StringVal("gp3"),
  4065  					}),
  4066  				}),
  4067  			}),
  4068  			RequiredReplace: cty.NewPathSet(),
  4069  			Schema:          testSchema(configschema.NestingMap),
  4070  			ExpectedOutput: `  # test_instance.example will be updated in-place
  4071    ~ resource "test_instance" "example" {
  4072        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  4073          id    = "i-02ae66f368e8518a9"
  4074          # (1 unchanged attribute hidden)
  4075  
  4076        ~ root_block_device "b" {
  4077            ~ volume_type = "gp2" -> "gp3"
  4078          }
  4079  
  4080          # (1 unchanged block hidden)
  4081      }
  4082  `,
  4083  		},
  4084  		"in-place update - multiple blocks second changed": {
  4085  			Action: plans.Update,
  4086  			Mode:   addrs.ManagedResourceMode,
  4087  			Before: cty.ObjectVal(map[string]cty.Value{
  4088  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4089  				"ami": cty.StringVal("ami-BEFORE"),
  4090  				"disks": cty.MapVal(map[string]cty.Value{
  4091  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  4092  						"mount_point": cty.StringVal("/var/diska"),
  4093  						"size":        cty.StringVal("50GB"),
  4094  					}),
  4095  				}),
  4096  				"root_block_device": cty.MapVal(map[string]cty.Value{
  4097  					"a": cty.ObjectVal(map[string]cty.Value{
  4098  						"volume_type": cty.StringVal("gp2"),
  4099  					}),
  4100  					"b": cty.ObjectVal(map[string]cty.Value{
  4101  						"volume_type": cty.StringVal("gp2"),
  4102  					}),
  4103  				}),
  4104  			}),
  4105  			After: cty.ObjectVal(map[string]cty.Value{
  4106  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4107  				"ami": cty.StringVal("ami-AFTER"),
  4108  				"disks": cty.MapVal(map[string]cty.Value{
  4109  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  4110  						"mount_point": cty.StringVal("/var/diska"),
  4111  						"size":        cty.StringVal("50GB"),
  4112  					}),
  4113  				}),
  4114  				"root_block_device": cty.MapVal(map[string]cty.Value{
  4115  					"a": cty.ObjectVal(map[string]cty.Value{
  4116  						"volume_type": cty.StringVal("gp3"),
  4117  					}),
  4118  					"b": cty.ObjectVal(map[string]cty.Value{
  4119  						"volume_type": cty.StringVal("gp2"),
  4120  					}),
  4121  				}),
  4122  			}),
  4123  			RequiredReplace: cty.NewPathSet(),
  4124  			Schema:          testSchema(configschema.NestingMap),
  4125  			ExpectedOutput: `  # test_instance.example will be updated in-place
  4126    ~ resource "test_instance" "example" {
  4127        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  4128          id    = "i-02ae66f368e8518a9"
  4129          # (1 unchanged attribute hidden)
  4130  
  4131        ~ root_block_device "a" {
  4132            ~ volume_type = "gp2" -> "gp3"
  4133          }
  4134  
  4135          # (1 unchanged block hidden)
  4136      }
  4137  `,
  4138  		},
  4139  		"in-place update - multiple blocks changed": {
  4140  			Action: plans.Update,
  4141  			Mode:   addrs.ManagedResourceMode,
  4142  			Before: cty.ObjectVal(map[string]cty.Value{
  4143  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4144  				"ami": cty.StringVal("ami-BEFORE"),
  4145  				"disks": cty.MapVal(map[string]cty.Value{
  4146  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  4147  						"mount_point": cty.StringVal("/var/diska"),
  4148  						"size":        cty.StringVal("50GB"),
  4149  					}),
  4150  				}),
  4151  				"root_block_device": cty.MapVal(map[string]cty.Value{
  4152  					"a": cty.ObjectVal(map[string]cty.Value{
  4153  						"volume_type": cty.StringVal("gp2"),
  4154  					}),
  4155  					"b": cty.ObjectVal(map[string]cty.Value{
  4156  						"volume_type": cty.StringVal("gp2"),
  4157  					}),
  4158  				}),
  4159  			}),
  4160  			After: cty.ObjectVal(map[string]cty.Value{
  4161  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4162  				"ami": cty.StringVal("ami-AFTER"),
  4163  				"disks": cty.MapVal(map[string]cty.Value{
  4164  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  4165  						"mount_point": cty.StringVal("/var/diska"),
  4166  						"size":        cty.StringVal("50GB"),
  4167  					}),
  4168  				}),
  4169  				"root_block_device": cty.MapVal(map[string]cty.Value{
  4170  					"a": cty.ObjectVal(map[string]cty.Value{
  4171  						"volume_type": cty.StringVal("gp3"),
  4172  					}),
  4173  					"b": cty.ObjectVal(map[string]cty.Value{
  4174  						"volume_type": cty.StringVal("gp3"),
  4175  					}),
  4176  				}),
  4177  			}),
  4178  			RequiredReplace: cty.NewPathSet(),
  4179  			Schema:          testSchema(configschema.NestingMap),
  4180  			ExpectedOutput: `  # test_instance.example will be updated in-place
  4181    ~ resource "test_instance" "example" {
  4182        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  4183          id    = "i-02ae66f368e8518a9"
  4184          # (1 unchanged attribute hidden)
  4185  
  4186        ~ root_block_device "a" {
  4187            ~ volume_type = "gp2" -> "gp3"
  4188          }
  4189        ~ root_block_device "b" {
  4190            ~ volume_type = "gp2" -> "gp3"
  4191          }
  4192      }
  4193  `,
  4194  		},
  4195  		"in-place update - multiple different unchanged blocks": {
  4196  			Action: plans.Update,
  4197  			Mode:   addrs.ManagedResourceMode,
  4198  			Before: cty.ObjectVal(map[string]cty.Value{
  4199  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4200  				"ami": cty.StringVal("ami-BEFORE"),
  4201  				"disks": cty.MapVal(map[string]cty.Value{
  4202  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  4203  						"mount_point": cty.StringVal("/var/diska"),
  4204  						"size":        cty.StringVal("50GB"),
  4205  					}),
  4206  				}),
  4207  				"root_block_device": cty.MapVal(map[string]cty.Value{
  4208  					"a": cty.ObjectVal(map[string]cty.Value{
  4209  						"volume_type": cty.StringVal("gp2"),
  4210  					}),
  4211  				}),
  4212  				"leaf_block_device": cty.MapVal(map[string]cty.Value{
  4213  					"b": cty.ObjectVal(map[string]cty.Value{
  4214  						"volume_type": cty.StringVal("gp2"),
  4215  					}),
  4216  				}),
  4217  			}),
  4218  			After: cty.ObjectVal(map[string]cty.Value{
  4219  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4220  				"ami": cty.StringVal("ami-AFTER"),
  4221  				"disks": cty.MapVal(map[string]cty.Value{
  4222  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  4223  						"mount_point": cty.StringVal("/var/diska"),
  4224  						"size":        cty.StringVal("50GB"),
  4225  					}),
  4226  				}),
  4227  				"root_block_device": cty.MapVal(map[string]cty.Value{
  4228  					"a": cty.ObjectVal(map[string]cty.Value{
  4229  						"volume_type": cty.StringVal("gp2"),
  4230  					}),
  4231  				}),
  4232  				"leaf_block_device": cty.MapVal(map[string]cty.Value{
  4233  					"b": cty.ObjectVal(map[string]cty.Value{
  4234  						"volume_type": cty.StringVal("gp2"),
  4235  					}),
  4236  				}),
  4237  			}),
  4238  			RequiredReplace: cty.NewPathSet(),
  4239  			Schema:          testSchemaMultipleBlocks(configschema.NestingMap),
  4240  			ExpectedOutput: `  # test_instance.example will be updated in-place
  4241    ~ resource "test_instance" "example" {
  4242        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  4243          id    = "i-02ae66f368e8518a9"
  4244          # (1 unchanged attribute hidden)
  4245  
  4246          # (2 unchanged blocks hidden)
  4247      }
  4248  `,
  4249  		},
  4250  		"in-place update - multiple different blocks first changed": {
  4251  			Action: plans.Update,
  4252  			Mode:   addrs.ManagedResourceMode,
  4253  			Before: cty.ObjectVal(map[string]cty.Value{
  4254  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4255  				"ami": cty.StringVal("ami-BEFORE"),
  4256  				"disks": cty.MapVal(map[string]cty.Value{
  4257  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  4258  						"mount_point": cty.StringVal("/var/diska"),
  4259  						"size":        cty.StringVal("50GB"),
  4260  					}),
  4261  				}),
  4262  				"root_block_device": cty.MapVal(map[string]cty.Value{
  4263  					"a": cty.ObjectVal(map[string]cty.Value{
  4264  						"volume_type": cty.StringVal("gp2"),
  4265  					}),
  4266  				}),
  4267  				"leaf_block_device": cty.MapVal(map[string]cty.Value{
  4268  					"b": cty.ObjectVal(map[string]cty.Value{
  4269  						"volume_type": cty.StringVal("gp2"),
  4270  					}),
  4271  				}),
  4272  			}),
  4273  			After: cty.ObjectVal(map[string]cty.Value{
  4274  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4275  				"ami": cty.StringVal("ami-AFTER"),
  4276  				"disks": cty.MapVal(map[string]cty.Value{
  4277  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  4278  						"mount_point": cty.StringVal("/var/diska"),
  4279  						"size":        cty.StringVal("50GB"),
  4280  					}),
  4281  				}),
  4282  				"root_block_device": cty.MapVal(map[string]cty.Value{
  4283  					"a": cty.ObjectVal(map[string]cty.Value{
  4284  						"volume_type": cty.StringVal("gp2"),
  4285  					}),
  4286  				}),
  4287  				"leaf_block_device": cty.MapVal(map[string]cty.Value{
  4288  					"b": cty.ObjectVal(map[string]cty.Value{
  4289  						"volume_type": cty.StringVal("gp3"),
  4290  					}),
  4291  				}),
  4292  			}),
  4293  			RequiredReplace: cty.NewPathSet(),
  4294  			Schema:          testSchemaMultipleBlocks(configschema.NestingMap),
  4295  			ExpectedOutput: `  # test_instance.example will be updated in-place
  4296    ~ resource "test_instance" "example" {
  4297        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  4298          id    = "i-02ae66f368e8518a9"
  4299          # (1 unchanged attribute hidden)
  4300  
  4301        ~ leaf_block_device "b" {
  4302            ~ volume_type = "gp2" -> "gp3"
  4303          }
  4304  
  4305          # (1 unchanged block hidden)
  4306      }
  4307  `,
  4308  		},
  4309  		"in-place update - multiple different blocks second changed": {
  4310  			Action: plans.Update,
  4311  			Mode:   addrs.ManagedResourceMode,
  4312  			Before: cty.ObjectVal(map[string]cty.Value{
  4313  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4314  				"ami": cty.StringVal("ami-BEFORE"),
  4315  				"disks": cty.MapVal(map[string]cty.Value{
  4316  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  4317  						"mount_point": cty.StringVal("/var/diska"),
  4318  						"size":        cty.StringVal("50GB"),
  4319  					}),
  4320  				}),
  4321  				"root_block_device": cty.MapVal(map[string]cty.Value{
  4322  					"a": cty.ObjectVal(map[string]cty.Value{
  4323  						"volume_type": cty.StringVal("gp2"),
  4324  					}),
  4325  				}),
  4326  				"leaf_block_device": cty.MapVal(map[string]cty.Value{
  4327  					"b": cty.ObjectVal(map[string]cty.Value{
  4328  						"volume_type": cty.StringVal("gp2"),
  4329  					}),
  4330  				}),
  4331  			}),
  4332  			After: cty.ObjectVal(map[string]cty.Value{
  4333  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4334  				"ami": cty.StringVal("ami-AFTER"),
  4335  				"disks": cty.MapVal(map[string]cty.Value{
  4336  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  4337  						"mount_point": cty.StringVal("/var/diska"),
  4338  						"size":        cty.StringVal("50GB"),
  4339  					}),
  4340  				}),
  4341  				"root_block_device": cty.MapVal(map[string]cty.Value{
  4342  					"a": cty.ObjectVal(map[string]cty.Value{
  4343  						"volume_type": cty.StringVal("gp3"),
  4344  					}),
  4345  				}),
  4346  				"leaf_block_device": cty.MapVal(map[string]cty.Value{
  4347  					"b": cty.ObjectVal(map[string]cty.Value{
  4348  						"volume_type": cty.StringVal("gp2"),
  4349  					}),
  4350  				}),
  4351  			}),
  4352  			RequiredReplace: cty.NewPathSet(),
  4353  			Schema:          testSchemaMultipleBlocks(configschema.NestingMap),
  4354  			ExpectedOutput: `  # test_instance.example will be updated in-place
  4355    ~ resource "test_instance" "example" {
  4356        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  4357          id    = "i-02ae66f368e8518a9"
  4358          # (1 unchanged attribute hidden)
  4359  
  4360        ~ root_block_device "a" {
  4361            ~ volume_type = "gp2" -> "gp3"
  4362          }
  4363  
  4364          # (1 unchanged block hidden)
  4365      }
  4366  `,
  4367  		},
  4368  		"in-place update - multiple different blocks changed": {
  4369  			Action: plans.Update,
  4370  			Mode:   addrs.ManagedResourceMode,
  4371  			Before: cty.ObjectVal(map[string]cty.Value{
  4372  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4373  				"ami": cty.StringVal("ami-BEFORE"),
  4374  				"disks": cty.MapVal(map[string]cty.Value{
  4375  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  4376  						"mount_point": cty.StringVal("/var/diska"),
  4377  						"size":        cty.StringVal("50GB"),
  4378  					}),
  4379  				}),
  4380  				"root_block_device": cty.MapVal(map[string]cty.Value{
  4381  					"a": cty.ObjectVal(map[string]cty.Value{
  4382  						"volume_type": cty.StringVal("gp2"),
  4383  					}),
  4384  				}),
  4385  				"leaf_block_device": cty.MapVal(map[string]cty.Value{
  4386  					"b": cty.ObjectVal(map[string]cty.Value{
  4387  						"volume_type": cty.StringVal("gp2"),
  4388  					}),
  4389  				}),
  4390  			}),
  4391  			After: cty.ObjectVal(map[string]cty.Value{
  4392  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4393  				"ami": cty.StringVal("ami-AFTER"),
  4394  				"disks": cty.MapVal(map[string]cty.Value{
  4395  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  4396  						"mount_point": cty.StringVal("/var/diska"),
  4397  						"size":        cty.StringVal("50GB"),
  4398  					}),
  4399  				}),
  4400  				"root_block_device": cty.MapVal(map[string]cty.Value{
  4401  					"a": cty.ObjectVal(map[string]cty.Value{
  4402  						"volume_type": cty.StringVal("gp3"),
  4403  					}),
  4404  				}),
  4405  				"leaf_block_device": cty.MapVal(map[string]cty.Value{
  4406  					"b": cty.ObjectVal(map[string]cty.Value{
  4407  						"volume_type": cty.StringVal("gp3"),
  4408  					}),
  4409  				}),
  4410  			}),
  4411  			RequiredReplace: cty.NewPathSet(),
  4412  			Schema:          testSchemaMultipleBlocks(configschema.NestingMap),
  4413  			ExpectedOutput: `  # test_instance.example will be updated in-place
  4414    ~ resource "test_instance" "example" {
  4415        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  4416          id    = "i-02ae66f368e8518a9"
  4417          # (1 unchanged attribute hidden)
  4418  
  4419        ~ leaf_block_device "b" {
  4420            ~ volume_type = "gp2" -> "gp3"
  4421          }
  4422  
  4423        ~ root_block_device "a" {
  4424            ~ volume_type = "gp2" -> "gp3"
  4425          }
  4426      }
  4427  `,
  4428  		},
  4429  		"in-place update - mixed blocks unchanged": {
  4430  			Action: plans.Update,
  4431  			Mode:   addrs.ManagedResourceMode,
  4432  			Before: cty.ObjectVal(map[string]cty.Value{
  4433  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4434  				"ami": cty.StringVal("ami-BEFORE"),
  4435  				"disks": cty.MapVal(map[string]cty.Value{
  4436  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  4437  						"mount_point": cty.StringVal("/var/diska"),
  4438  						"size":        cty.StringVal("50GB"),
  4439  					}),
  4440  				}),
  4441  				"root_block_device": cty.MapVal(map[string]cty.Value{
  4442  					"a": cty.ObjectVal(map[string]cty.Value{
  4443  						"volume_type": cty.StringVal("gp2"),
  4444  					}),
  4445  					"b": cty.ObjectVal(map[string]cty.Value{
  4446  						"volume_type": cty.StringVal("gp2"),
  4447  					}),
  4448  				}),
  4449  				"leaf_block_device": cty.MapVal(map[string]cty.Value{
  4450  					"a": cty.ObjectVal(map[string]cty.Value{
  4451  						"volume_type": cty.StringVal("gp2"),
  4452  					}),
  4453  					"b": cty.ObjectVal(map[string]cty.Value{
  4454  						"volume_type": cty.StringVal("gp2"),
  4455  					}),
  4456  				}),
  4457  			}),
  4458  			After: cty.ObjectVal(map[string]cty.Value{
  4459  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4460  				"ami": cty.StringVal("ami-AFTER"),
  4461  				"disks": cty.MapVal(map[string]cty.Value{
  4462  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  4463  						"mount_point": cty.StringVal("/var/diska"),
  4464  						"size":        cty.StringVal("50GB"),
  4465  					}),
  4466  				}),
  4467  				"root_block_device": cty.MapVal(map[string]cty.Value{
  4468  					"a": cty.ObjectVal(map[string]cty.Value{
  4469  						"volume_type": cty.StringVal("gp2"),
  4470  					}),
  4471  					"b": cty.ObjectVal(map[string]cty.Value{
  4472  						"volume_type": cty.StringVal("gp2"),
  4473  					}),
  4474  				}),
  4475  				"leaf_block_device": cty.MapVal(map[string]cty.Value{
  4476  					"a": cty.ObjectVal(map[string]cty.Value{
  4477  						"volume_type": cty.StringVal("gp2"),
  4478  					}),
  4479  					"b": cty.ObjectVal(map[string]cty.Value{
  4480  						"volume_type": cty.StringVal("gp2"),
  4481  					}),
  4482  				}),
  4483  			}),
  4484  			RequiredReplace: cty.NewPathSet(),
  4485  			Schema:          testSchemaMultipleBlocks(configschema.NestingMap),
  4486  			ExpectedOutput: `  # test_instance.example will be updated in-place
  4487    ~ resource "test_instance" "example" {
  4488        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  4489          id    = "i-02ae66f368e8518a9"
  4490          # (1 unchanged attribute hidden)
  4491  
  4492          # (4 unchanged blocks hidden)
  4493      }
  4494  `,
  4495  		},
  4496  		"in-place update - mixed blocks changed": {
  4497  			Action: plans.Update,
  4498  			Mode:   addrs.ManagedResourceMode,
  4499  			Before: cty.ObjectVal(map[string]cty.Value{
  4500  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4501  				"ami": cty.StringVal("ami-BEFORE"),
  4502  				"disks": cty.MapVal(map[string]cty.Value{
  4503  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  4504  						"mount_point": cty.StringVal("/var/diska"),
  4505  						"size":        cty.StringVal("50GB"),
  4506  					}),
  4507  				}),
  4508  				"root_block_device": cty.MapVal(map[string]cty.Value{
  4509  					"a": cty.ObjectVal(map[string]cty.Value{
  4510  						"volume_type": cty.StringVal("gp2"),
  4511  					}),
  4512  					"b": cty.ObjectVal(map[string]cty.Value{
  4513  						"volume_type": cty.StringVal("gp2"),
  4514  					}),
  4515  				}),
  4516  				"leaf_block_device": cty.MapVal(map[string]cty.Value{
  4517  					"a": cty.ObjectVal(map[string]cty.Value{
  4518  						"volume_type": cty.StringVal("gp2"),
  4519  					}),
  4520  					"b": cty.ObjectVal(map[string]cty.Value{
  4521  						"volume_type": cty.StringVal("gp2"),
  4522  					}),
  4523  				}),
  4524  			}),
  4525  			After: cty.ObjectVal(map[string]cty.Value{
  4526  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4527  				"ami": cty.StringVal("ami-AFTER"),
  4528  				"disks": cty.MapVal(map[string]cty.Value{
  4529  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  4530  						"mount_point": cty.StringVal("/var/diska"),
  4531  						"size":        cty.StringVal("50GB"),
  4532  					}),
  4533  				}),
  4534  				"root_block_device": cty.MapVal(map[string]cty.Value{
  4535  					"a": cty.ObjectVal(map[string]cty.Value{
  4536  						"volume_type": cty.StringVal("gp2"),
  4537  					}),
  4538  					"b": cty.ObjectVal(map[string]cty.Value{
  4539  						"volume_type": cty.StringVal("gp3"),
  4540  					}),
  4541  				}),
  4542  				"leaf_block_device": cty.MapVal(map[string]cty.Value{
  4543  					"a": cty.ObjectVal(map[string]cty.Value{
  4544  						"volume_type": cty.StringVal("gp2"),
  4545  					}),
  4546  					"b": cty.ObjectVal(map[string]cty.Value{
  4547  						"volume_type": cty.StringVal("gp3"),
  4548  					}),
  4549  				}),
  4550  			}),
  4551  			RequiredReplace: cty.NewPathSet(),
  4552  			Schema:          testSchemaMultipleBlocks(configschema.NestingMap),
  4553  			ExpectedOutput: `  # test_instance.example will be updated in-place
  4554    ~ resource "test_instance" "example" {
  4555        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  4556          id    = "i-02ae66f368e8518a9"
  4557          # (1 unchanged attribute hidden)
  4558  
  4559        ~ leaf_block_device "b" {
  4560            ~ volume_type = "gp2" -> "gp3"
  4561          }
  4562  
  4563        ~ root_block_device "b" {
  4564            ~ volume_type = "gp2" -> "gp3"
  4565          }
  4566  
  4567          # (2 unchanged blocks hidden)
  4568      }
  4569  `,
  4570  		},
  4571  	}
  4572  	runTestCases(t, testCases)
  4573  }
  4574  
  4575  func TestResourceChange_nestedSingle(t *testing.T) {
  4576  	testCases := map[string]testCase{
  4577  		"in-place update - equal": {
  4578  			Action: plans.Update,
  4579  			Mode:   addrs.ManagedResourceMode,
  4580  			Before: cty.ObjectVal(map[string]cty.Value{
  4581  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4582  				"ami": cty.StringVal("ami-BEFORE"),
  4583  				"root_block_device": cty.ObjectVal(map[string]cty.Value{
  4584  					"volume_type": cty.StringVal("gp2"),
  4585  				}),
  4586  				"disk": cty.ObjectVal(map[string]cty.Value{
  4587  					"mount_point": cty.StringVal("/var/diska"),
  4588  					"size":        cty.StringVal("50GB"),
  4589  				}),
  4590  			}),
  4591  			After: cty.ObjectVal(map[string]cty.Value{
  4592  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4593  				"ami": cty.StringVal("ami-AFTER"),
  4594  				"root_block_device": cty.ObjectVal(map[string]cty.Value{
  4595  					"volume_type": cty.StringVal("gp2"),
  4596  				}),
  4597  				"disk": cty.ObjectVal(map[string]cty.Value{
  4598  					"mount_point": cty.StringVal("/var/diska"),
  4599  					"size":        cty.StringVal("50GB"),
  4600  				}),
  4601  			}),
  4602  			RequiredReplace: cty.NewPathSet(),
  4603  			Schema:          testSchema(configschema.NestingSingle),
  4604  			ExpectedOutput: `  # test_instance.example will be updated in-place
  4605    ~ resource "test_instance" "example" {
  4606        ~ ami  = "ami-BEFORE" -> "ami-AFTER"
  4607          id   = "i-02ae66f368e8518a9"
  4608          # (1 unchanged attribute hidden)
  4609  
  4610          # (1 unchanged block hidden)
  4611      }
  4612  `,
  4613  		},
  4614  		"in-place update - creation": {
  4615  			Action: plans.Update,
  4616  			Mode:   addrs.ManagedResourceMode,
  4617  			Before: cty.ObjectVal(map[string]cty.Value{
  4618  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4619  				"ami": cty.StringVal("ami-BEFORE"),
  4620  				"root_block_device": cty.NullVal(cty.Object(map[string]cty.Type{
  4621  					"volume_type": cty.String,
  4622  				})),
  4623  				"disk": cty.NullVal(cty.Object(map[string]cty.Type{
  4624  					"mount_point": cty.String,
  4625  					"size":        cty.String,
  4626  				})),
  4627  			}),
  4628  			After: cty.ObjectVal(map[string]cty.Value{
  4629  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4630  				"ami": cty.StringVal("ami-AFTER"),
  4631  				"disk": cty.ObjectVal(map[string]cty.Value{
  4632  					"mount_point": cty.StringVal("/var/diska"),
  4633  					"size":        cty.StringVal("50GB"),
  4634  				}),
  4635  				"root_block_device": cty.ObjectVal(map[string]cty.Value{
  4636  					"volume_type": cty.NullVal(cty.String),
  4637  				}),
  4638  			}),
  4639  			RequiredReplace: cty.NewPathSet(),
  4640  			Schema:          testSchema(configschema.NestingSingle),
  4641  			ExpectedOutput: `  # test_instance.example will be updated in-place
  4642    ~ resource "test_instance" "example" {
  4643        ~ ami  = "ami-BEFORE" -> "ami-AFTER"
  4644        + disk = {
  4645            + mount_point = "/var/diska"
  4646            + size        = "50GB"
  4647          }
  4648          id   = "i-02ae66f368e8518a9"
  4649  
  4650        + root_block_device {}
  4651      }
  4652  `,
  4653  		},
  4654  		"force-new update (inside blocks)": {
  4655  			Action:       plans.DeleteThenCreate,
  4656  			ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate,
  4657  			Mode:         addrs.ManagedResourceMode,
  4658  			Before: cty.ObjectVal(map[string]cty.Value{
  4659  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4660  				"ami": cty.StringVal("ami-BEFORE"),
  4661  				"disk": cty.ObjectVal(map[string]cty.Value{
  4662  					"mount_point": cty.StringVal("/var/diska"),
  4663  					"size":        cty.StringVal("50GB"),
  4664  				}),
  4665  				"root_block_device": cty.ObjectVal(map[string]cty.Value{
  4666  					"volume_type": cty.StringVal("gp2"),
  4667  				}),
  4668  			}),
  4669  			After: cty.ObjectVal(map[string]cty.Value{
  4670  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4671  				"ami": cty.StringVal("ami-AFTER"),
  4672  				"disk": cty.ObjectVal(map[string]cty.Value{
  4673  					"mount_point": cty.StringVal("/var/diskb"),
  4674  					"size":        cty.StringVal("50GB"),
  4675  				}),
  4676  				"root_block_device": cty.ObjectVal(map[string]cty.Value{
  4677  					"volume_type": cty.StringVal("different"),
  4678  				}),
  4679  			}),
  4680  			RequiredReplace: cty.NewPathSet(
  4681  				cty.Path{
  4682  					cty.GetAttrStep{Name: "root_block_device"},
  4683  					cty.GetAttrStep{Name: "volume_type"},
  4684  				},
  4685  				cty.Path{
  4686  					cty.GetAttrStep{Name: "disk"},
  4687  					cty.GetAttrStep{Name: "mount_point"},
  4688  				},
  4689  			),
  4690  			Schema: testSchema(configschema.NestingSingle),
  4691  			ExpectedOutput: `  # test_instance.example must be replaced
  4692  -/+ resource "test_instance" "example" {
  4693        ~ ami  = "ami-BEFORE" -> "ami-AFTER"
  4694        ~ disk = {
  4695            ~ mount_point = "/var/diska" -> "/var/diskb" # forces replacement
  4696              # (1 unchanged attribute hidden)
  4697          }
  4698          id   = "i-02ae66f368e8518a9"
  4699  
  4700        ~ root_block_device {
  4701            ~ volume_type = "gp2" -> "different" # forces replacement
  4702          }
  4703      }
  4704  `,
  4705  		},
  4706  		"force-new update (whole block)": {
  4707  			Action:       plans.DeleteThenCreate,
  4708  			ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate,
  4709  			Mode:         addrs.ManagedResourceMode,
  4710  			Before: cty.ObjectVal(map[string]cty.Value{
  4711  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4712  				"ami": cty.StringVal("ami-BEFORE"),
  4713  				"disk": cty.ObjectVal(map[string]cty.Value{
  4714  					"mount_point": cty.StringVal("/var/diska"),
  4715  					"size":        cty.StringVal("50GB"),
  4716  				}),
  4717  				"root_block_device": cty.ObjectVal(map[string]cty.Value{
  4718  					"volume_type": cty.StringVal("gp2"),
  4719  				}),
  4720  			}),
  4721  			After: cty.ObjectVal(map[string]cty.Value{
  4722  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4723  				"ami": cty.StringVal("ami-AFTER"),
  4724  				"disk": cty.ObjectVal(map[string]cty.Value{
  4725  					"mount_point": cty.StringVal("/var/diskb"),
  4726  					"size":        cty.StringVal("50GB"),
  4727  				}),
  4728  				"root_block_device": cty.ObjectVal(map[string]cty.Value{
  4729  					"volume_type": cty.StringVal("different"),
  4730  				}),
  4731  			}),
  4732  			RequiredReplace: cty.NewPathSet(
  4733  				cty.Path{cty.GetAttrStep{Name: "root_block_device"}},
  4734  				cty.Path{cty.GetAttrStep{Name: "disk"}},
  4735  			),
  4736  			Schema: testSchema(configschema.NestingSingle),
  4737  			ExpectedOutput: `  # test_instance.example must be replaced
  4738  -/+ resource "test_instance" "example" {
  4739        ~ ami  = "ami-BEFORE" -> "ami-AFTER"
  4740        ~ disk = { # forces replacement
  4741            ~ mount_point = "/var/diska" -> "/var/diskb"
  4742              # (1 unchanged attribute hidden)
  4743          }
  4744          id   = "i-02ae66f368e8518a9"
  4745  
  4746        ~ root_block_device { # forces replacement
  4747            ~ volume_type = "gp2" -> "different"
  4748          }
  4749      }
  4750  `,
  4751  		},
  4752  		"in-place update - deletion": {
  4753  			Action: plans.Update,
  4754  			Mode:   addrs.ManagedResourceMode,
  4755  			Before: cty.ObjectVal(map[string]cty.Value{
  4756  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4757  				"ami": cty.StringVal("ami-BEFORE"),
  4758  				"disk": cty.ObjectVal(map[string]cty.Value{
  4759  					"mount_point": cty.StringVal("/var/diska"),
  4760  					"size":        cty.StringVal("50GB"),
  4761  				}),
  4762  				"root_block_device": cty.ObjectVal(map[string]cty.Value{
  4763  					"volume_type": cty.StringVal("gp2"),
  4764  				}),
  4765  			}),
  4766  			After: cty.ObjectVal(map[string]cty.Value{
  4767  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4768  				"ami": cty.StringVal("ami-AFTER"),
  4769  				"root_block_device": cty.NullVal(cty.Object(map[string]cty.Type{
  4770  					"volume_type": cty.String,
  4771  				})),
  4772  				"disk": cty.NullVal(cty.Object(map[string]cty.Type{
  4773  					"mount_point": cty.String,
  4774  					"size":        cty.String,
  4775  				})),
  4776  			}),
  4777  			RequiredReplace: cty.NewPathSet(),
  4778  			Schema:          testSchema(configschema.NestingSingle),
  4779  			ExpectedOutput: `  # test_instance.example will be updated in-place
  4780    ~ resource "test_instance" "example" {
  4781        ~ ami  = "ami-BEFORE" -> "ami-AFTER"
  4782        - disk = {
  4783            - mount_point = "/var/diska" -> null
  4784            - size        = "50GB" -> null
  4785          } -> null
  4786          id   = "i-02ae66f368e8518a9"
  4787  
  4788        - root_block_device {
  4789            - volume_type = "gp2" -> null
  4790          }
  4791      }
  4792  `,
  4793  		},
  4794  		"with dynamically-typed attribute": {
  4795  			Action: plans.Update,
  4796  			Mode:   addrs.ManagedResourceMode,
  4797  			Before: cty.ObjectVal(map[string]cty.Value{
  4798  				"block": cty.NullVal(cty.Object(map[string]cty.Type{
  4799  					"attr": cty.String,
  4800  				})),
  4801  			}),
  4802  			After: cty.ObjectVal(map[string]cty.Value{
  4803  				"block": cty.ObjectVal(map[string]cty.Value{
  4804  					"attr": cty.StringVal("foo"),
  4805  				}),
  4806  			}),
  4807  			RequiredReplace: cty.NewPathSet(),
  4808  			Schema: &configschema.Block{
  4809  				BlockTypes: map[string]*configschema.NestedBlock{
  4810  					"block": {
  4811  						Block: configschema.Block{
  4812  							Attributes: map[string]*configschema.Attribute{
  4813  								"attr": {Type: cty.DynamicPseudoType, Optional: true},
  4814  							},
  4815  						},
  4816  						Nesting: configschema.NestingSingle,
  4817  					},
  4818  				},
  4819  			},
  4820  			ExpectedOutput: `  # test_instance.example will be updated in-place
  4821    ~ resource "test_instance" "example" {
  4822        + block {
  4823            + attr = "foo"
  4824          }
  4825      }
  4826  `,
  4827  		},
  4828  		"in-place update - unknown": {
  4829  			Action: plans.Update,
  4830  			Mode:   addrs.ManagedResourceMode,
  4831  			Before: cty.ObjectVal(map[string]cty.Value{
  4832  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4833  				"ami": cty.StringVal("ami-BEFORE"),
  4834  				"disk": cty.ObjectVal(map[string]cty.Value{
  4835  					"mount_point": cty.StringVal("/var/diska"),
  4836  					"size":        cty.StringVal("50GB"),
  4837  				}),
  4838  				"root_block_device": cty.ObjectVal(map[string]cty.Value{
  4839  					"volume_type": cty.StringVal("gp2"),
  4840  					"new_field":   cty.StringVal("new_value"),
  4841  				}),
  4842  			}),
  4843  			After: cty.ObjectVal(map[string]cty.Value{
  4844  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4845  				"ami": cty.StringVal("ami-AFTER"),
  4846  				"disk": cty.UnknownVal(cty.Object(map[string]cty.Type{
  4847  					"mount_point": cty.String,
  4848  					"size":        cty.String,
  4849  				})),
  4850  				"root_block_device": cty.ObjectVal(map[string]cty.Value{
  4851  					"volume_type": cty.StringVal("gp2"),
  4852  					"new_field":   cty.StringVal("new_value"),
  4853  				}),
  4854  			}),
  4855  			RequiredReplace: cty.NewPathSet(),
  4856  			Schema:          testSchemaPlus(configschema.NestingSingle),
  4857  			ExpectedOutput: `  # test_instance.example will be updated in-place
  4858    ~ resource "test_instance" "example" {
  4859        ~ ami  = "ami-BEFORE" -> "ami-AFTER"
  4860        ~ disk = {
  4861            ~ mount_point = "/var/diska" -> (known after apply)
  4862            ~ size        = "50GB" -> (known after apply)
  4863          } -> (known after apply)
  4864          id   = "i-02ae66f368e8518a9"
  4865  
  4866          # (1 unchanged block hidden)
  4867      }
  4868  `,
  4869  		},
  4870  		"in-place update - modification": {
  4871  			Action: plans.Update,
  4872  			Mode:   addrs.ManagedResourceMode,
  4873  			Before: cty.ObjectVal(map[string]cty.Value{
  4874  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4875  				"ami": cty.StringVal("ami-BEFORE"),
  4876  				"disk": cty.ObjectVal(map[string]cty.Value{
  4877  					"mount_point": cty.StringVal("/var/diska"),
  4878  					"size":        cty.StringVal("50GB"),
  4879  				}),
  4880  				"root_block_device": cty.ObjectVal(map[string]cty.Value{
  4881  					"volume_type": cty.StringVal("gp2"),
  4882  					"new_field":   cty.StringVal("new_value"),
  4883  				}),
  4884  			}),
  4885  			After: cty.ObjectVal(map[string]cty.Value{
  4886  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4887  				"ami": cty.StringVal("ami-AFTER"),
  4888  				"disk": cty.ObjectVal(map[string]cty.Value{
  4889  					"mount_point": cty.StringVal("/var/diska"),
  4890  					"size":        cty.StringVal("25GB"),
  4891  				}),
  4892  				"root_block_device": cty.ObjectVal(map[string]cty.Value{
  4893  					"volume_type": cty.StringVal("gp2"),
  4894  					"new_field":   cty.StringVal("new_value"),
  4895  				}),
  4896  			}),
  4897  			RequiredReplace: cty.NewPathSet(),
  4898  			Schema:          testSchemaPlus(configschema.NestingSingle),
  4899  			ExpectedOutput: `  # test_instance.example will be updated in-place
  4900    ~ resource "test_instance" "example" {
  4901        ~ ami  = "ami-BEFORE" -> "ami-AFTER"
  4902        ~ disk = {
  4903            ~ size        = "50GB" -> "25GB"
  4904              # (1 unchanged attribute hidden)
  4905          }
  4906          id   = "i-02ae66f368e8518a9"
  4907  
  4908          # (1 unchanged block hidden)
  4909      }
  4910  `,
  4911  		},
  4912  	}
  4913  	runTestCases(t, testCases)
  4914  }
  4915  
  4916  func TestResourceChange_nestedMapSensitiveSchema(t *testing.T) {
  4917  	testCases := map[string]testCase{
  4918  		"creation from null": {
  4919  			Action: plans.Update,
  4920  			Mode:   addrs.ManagedResourceMode,
  4921  			Before: cty.ObjectVal(map[string]cty.Value{
  4922  				"id":  cty.NullVal(cty.String),
  4923  				"ami": cty.NullVal(cty.String),
  4924  				"disks": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{
  4925  					"mount_point": cty.String,
  4926  					"size":        cty.String,
  4927  				}))),
  4928  			}),
  4929  			After: cty.ObjectVal(map[string]cty.Value{
  4930  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4931  				"ami": cty.StringVal("ami-AFTER"),
  4932  				"disks": cty.MapVal(map[string]cty.Value{
  4933  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  4934  						"mount_point": cty.StringVal("/var/diska"),
  4935  						"size":        cty.NullVal(cty.String),
  4936  					}),
  4937  				}),
  4938  			}),
  4939  			RequiredReplace: cty.NewPathSet(),
  4940  			Schema:          testSchemaSensitive(configschema.NestingMap),
  4941  			ExpectedOutput: `  # test_instance.example will be updated in-place
  4942    ~ resource "test_instance" "example" {
  4943        + ami   = "ami-AFTER"
  4944        + disks = (sensitive value)
  4945        + id    = "i-02ae66f368e8518a9"
  4946      }
  4947  `,
  4948  		},
  4949  		"in-place update": {
  4950  			Action: plans.Update,
  4951  			Mode:   addrs.ManagedResourceMode,
  4952  			Before: cty.ObjectVal(map[string]cty.Value{
  4953  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4954  				"ami": cty.StringVal("ami-BEFORE"),
  4955  				"disks": cty.MapValEmpty(cty.Object(map[string]cty.Type{
  4956  					"mount_point": cty.String,
  4957  					"size":        cty.String,
  4958  				})),
  4959  			}),
  4960  			After: cty.ObjectVal(map[string]cty.Value{
  4961  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4962  				"ami": cty.StringVal("ami-AFTER"),
  4963  				"disks": cty.MapVal(map[string]cty.Value{
  4964  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  4965  						"mount_point": cty.StringVal("/var/diska"),
  4966  						"size":        cty.NullVal(cty.String),
  4967  					}),
  4968  				}),
  4969  			}),
  4970  			RequiredReplace: cty.NewPathSet(),
  4971  			Schema:          testSchemaSensitive(configschema.NestingMap),
  4972  			ExpectedOutput: `  # test_instance.example will be updated in-place
  4973    ~ resource "test_instance" "example" {
  4974        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  4975        ~ disks = (sensitive value)
  4976          id    = "i-02ae66f368e8518a9"
  4977      }
  4978  `,
  4979  		},
  4980  		"force-new update (whole block)": {
  4981  			Action:       plans.DeleteThenCreate,
  4982  			ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate,
  4983  			Mode:         addrs.ManagedResourceMode,
  4984  			Before: cty.ObjectVal(map[string]cty.Value{
  4985  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4986  				"ami": cty.StringVal("ami-BEFORE"),
  4987  				"disks": cty.MapVal(map[string]cty.Value{
  4988  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  4989  						"mount_point": cty.StringVal("/var/diska"),
  4990  						"size":        cty.StringVal("50GB"),
  4991  					}),
  4992  				}),
  4993  			}),
  4994  			After: cty.ObjectVal(map[string]cty.Value{
  4995  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  4996  				"ami": cty.StringVal("ami-AFTER"),
  4997  				"disks": cty.MapVal(map[string]cty.Value{
  4998  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  4999  						"mount_point": cty.StringVal("/var/diska"),
  5000  						"size":        cty.StringVal("100GB"),
  5001  					}),
  5002  				}),
  5003  			}),
  5004  			RequiredReplace: cty.NewPathSet(
  5005  				cty.Path{cty.GetAttrStep{Name: "disks"}},
  5006  			),
  5007  			Schema: testSchemaSensitive(configschema.NestingMap),
  5008  			ExpectedOutput: `  # test_instance.example must be replaced
  5009  -/+ resource "test_instance" "example" {
  5010        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  5011        ~ disks = (sensitive value) # forces replacement
  5012          id    = "i-02ae66f368e8518a9"
  5013      }
  5014  `,
  5015  		},
  5016  		"in-place update - deletion": {
  5017  			Action: plans.Update,
  5018  			Mode:   addrs.ManagedResourceMode,
  5019  			Before: cty.ObjectVal(map[string]cty.Value{
  5020  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  5021  				"ami": cty.StringVal("ami-BEFORE"),
  5022  				"disks": cty.MapVal(map[string]cty.Value{
  5023  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  5024  						"mount_point": cty.StringVal("/var/diska"),
  5025  						"size":        cty.StringVal("50GB"),
  5026  					}),
  5027  				}),
  5028  			}),
  5029  			After: cty.ObjectVal(map[string]cty.Value{
  5030  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  5031  				"ami": cty.StringVal("ami-AFTER"),
  5032  				"disks": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{
  5033  					"mount_point": cty.String,
  5034  					"size":        cty.String,
  5035  				}))),
  5036  			}),
  5037  			RequiredReplace: cty.NewPathSet(),
  5038  			Schema:          testSchemaSensitive(configschema.NestingMap),
  5039  			ExpectedOutput: `  # test_instance.example will be updated in-place
  5040    ~ resource "test_instance" "example" {
  5041        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  5042        - disks = (sensitive value)
  5043          id    = "i-02ae66f368e8518a9"
  5044      }
  5045  `,
  5046  		},
  5047  		"in-place update - unknown": {
  5048  			Action: plans.Update,
  5049  			Mode:   addrs.ManagedResourceMode,
  5050  			Before: cty.ObjectVal(map[string]cty.Value{
  5051  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  5052  				"ami": cty.StringVal("ami-BEFORE"),
  5053  				"disks": cty.MapVal(map[string]cty.Value{
  5054  					"disk_a": cty.ObjectVal(map[string]cty.Value{
  5055  						"mount_point": cty.StringVal("/var/diska"),
  5056  						"size":        cty.StringVal("50GB"),
  5057  					}),
  5058  				}),
  5059  			}),
  5060  			After: cty.ObjectVal(map[string]cty.Value{
  5061  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  5062  				"ami": cty.StringVal("ami-AFTER"),
  5063  				"disks": cty.UnknownVal(cty.Map(cty.Object(map[string]cty.Type{
  5064  					"mount_point": cty.String,
  5065  					"size":        cty.String,
  5066  				}))),
  5067  			}),
  5068  			RequiredReplace: cty.NewPathSet(),
  5069  			Schema:          testSchemaSensitive(configschema.NestingMap),
  5070  			ExpectedOutput: `  # test_instance.example will be updated in-place
  5071    ~ resource "test_instance" "example" {
  5072        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  5073        ~ disks = (sensitive value)
  5074          id    = "i-02ae66f368e8518a9"
  5075      }
  5076  `,
  5077  		},
  5078  	}
  5079  	runTestCases(t, testCases)
  5080  }
  5081  
  5082  func TestResourceChange_nestedListSensitiveSchema(t *testing.T) {
  5083  	testCases := map[string]testCase{
  5084  		"creation from null": {
  5085  			Action: plans.Update,
  5086  			Mode:   addrs.ManagedResourceMode,
  5087  			Before: cty.ObjectVal(map[string]cty.Value{
  5088  				"id":  cty.NullVal(cty.String),
  5089  				"ami": cty.NullVal(cty.String),
  5090  				"disks": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
  5091  					"mount_point": cty.String,
  5092  					"size":        cty.String,
  5093  				}))),
  5094  			}),
  5095  			After: cty.ObjectVal(map[string]cty.Value{
  5096  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  5097  				"ami": cty.StringVal("ami-AFTER"),
  5098  				"disks": cty.ListVal([]cty.Value{
  5099  					cty.ObjectVal(map[string]cty.Value{
  5100  						"mount_point": cty.StringVal("/var/diska"),
  5101  						"size":        cty.NullVal(cty.String),
  5102  					}),
  5103  				}),
  5104  			}),
  5105  			RequiredReplace: cty.NewPathSet(),
  5106  			Schema:          testSchemaSensitive(configschema.NestingList),
  5107  			ExpectedOutput: `  # test_instance.example will be updated in-place
  5108    ~ resource "test_instance" "example" {
  5109        + ami   = "ami-AFTER"
  5110        + disks = (sensitive value)
  5111        + id    = "i-02ae66f368e8518a9"
  5112      }
  5113  `,
  5114  		},
  5115  		"in-place update": {
  5116  			Action: plans.Update,
  5117  			Mode:   addrs.ManagedResourceMode,
  5118  			Before: cty.ObjectVal(map[string]cty.Value{
  5119  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  5120  				"ami": cty.StringVal("ami-BEFORE"),
  5121  				"disks": cty.ListValEmpty(cty.Object(map[string]cty.Type{
  5122  					"mount_point": cty.String,
  5123  					"size":        cty.String,
  5124  				})),
  5125  			}),
  5126  			After: cty.ObjectVal(map[string]cty.Value{
  5127  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  5128  				"ami": cty.StringVal("ami-AFTER"),
  5129  				"disks": cty.ListVal([]cty.Value{
  5130  					cty.ObjectVal(map[string]cty.Value{
  5131  						"mount_point": cty.StringVal("/var/diska"),
  5132  						"size":        cty.NullVal(cty.String),
  5133  					}),
  5134  				}),
  5135  			}),
  5136  			RequiredReplace: cty.NewPathSet(),
  5137  			Schema:          testSchemaSensitive(configschema.NestingList),
  5138  			ExpectedOutput: `  # test_instance.example will be updated in-place
  5139    ~ resource "test_instance" "example" {
  5140        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  5141        ~ disks = (sensitive value)
  5142          id    = "i-02ae66f368e8518a9"
  5143      }
  5144  `,
  5145  		},
  5146  		"force-new update (whole block)": {
  5147  			Action:       plans.DeleteThenCreate,
  5148  			ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate,
  5149  			Mode:         addrs.ManagedResourceMode,
  5150  			Before: cty.ObjectVal(map[string]cty.Value{
  5151  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  5152  				"ami": cty.StringVal("ami-BEFORE"),
  5153  				"disks": cty.ListVal([]cty.Value{
  5154  					cty.ObjectVal(map[string]cty.Value{
  5155  						"mount_point": cty.StringVal("/var/diska"),
  5156  						"size":        cty.StringVal("50GB"),
  5157  					}),
  5158  				}),
  5159  			}),
  5160  			After: cty.ObjectVal(map[string]cty.Value{
  5161  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  5162  				"ami": cty.StringVal("ami-AFTER"),
  5163  				"disks": cty.ListVal([]cty.Value{
  5164  					cty.ObjectVal(map[string]cty.Value{
  5165  						"mount_point": cty.StringVal("/var/diska"),
  5166  						"size":        cty.StringVal("100GB"),
  5167  					}),
  5168  				}),
  5169  			}),
  5170  			RequiredReplace: cty.NewPathSet(
  5171  				cty.Path{cty.GetAttrStep{Name: "disks"}},
  5172  			),
  5173  			Schema: testSchemaSensitive(configschema.NestingList),
  5174  			ExpectedOutput: `  # test_instance.example must be replaced
  5175  -/+ resource "test_instance" "example" {
  5176        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  5177        ~ disks = (sensitive value) # forces replacement
  5178          id    = "i-02ae66f368e8518a9"
  5179      }
  5180  `,
  5181  		},
  5182  		"in-place update - deletion": {
  5183  			Action: plans.Update,
  5184  			Mode:   addrs.ManagedResourceMode,
  5185  			Before: cty.ObjectVal(map[string]cty.Value{
  5186  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  5187  				"ami": cty.StringVal("ami-BEFORE"),
  5188  				"disks": cty.ListVal([]cty.Value{
  5189  					cty.ObjectVal(map[string]cty.Value{
  5190  						"mount_point": cty.StringVal("/var/diska"),
  5191  						"size":        cty.StringVal("50GB"),
  5192  					}),
  5193  				}),
  5194  			}),
  5195  			After: cty.ObjectVal(map[string]cty.Value{
  5196  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  5197  				"ami": cty.StringVal("ami-AFTER"),
  5198  				"disks": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
  5199  					"mount_point": cty.String,
  5200  					"size":        cty.String,
  5201  				}))),
  5202  			}),
  5203  			RequiredReplace: cty.NewPathSet(),
  5204  			Schema:          testSchemaSensitive(configschema.NestingList),
  5205  			ExpectedOutput: `  # test_instance.example will be updated in-place
  5206    ~ resource "test_instance" "example" {
  5207        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  5208        - disks = (sensitive value)
  5209          id    = "i-02ae66f368e8518a9"
  5210      }
  5211  `,
  5212  		},
  5213  		"in-place update - unknown": {
  5214  			Action: plans.Update,
  5215  			Mode:   addrs.ManagedResourceMode,
  5216  			Before: cty.ObjectVal(map[string]cty.Value{
  5217  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  5218  				"ami": cty.StringVal("ami-BEFORE"),
  5219  				"disks": cty.ListVal([]cty.Value{
  5220  					cty.ObjectVal(map[string]cty.Value{
  5221  						"mount_point": cty.StringVal("/var/diska"),
  5222  						"size":        cty.StringVal("50GB"),
  5223  					}),
  5224  				}),
  5225  			}),
  5226  			After: cty.ObjectVal(map[string]cty.Value{
  5227  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  5228  				"ami": cty.StringVal("ami-AFTER"),
  5229  				"disks": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{
  5230  					"mount_point": cty.String,
  5231  					"size":        cty.String,
  5232  				}))),
  5233  			}),
  5234  			RequiredReplace: cty.NewPathSet(),
  5235  			Schema:          testSchemaSensitive(configschema.NestingList),
  5236  			ExpectedOutput: `  # test_instance.example will be updated in-place
  5237    ~ resource "test_instance" "example" {
  5238        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  5239        ~ disks = (sensitive value)
  5240          id    = "i-02ae66f368e8518a9"
  5241      }
  5242  `,
  5243  		},
  5244  	}
  5245  	runTestCases(t, testCases)
  5246  }
  5247  
  5248  func TestResourceChange_nestedSetSensitiveSchema(t *testing.T) {
  5249  	testCases := map[string]testCase{
  5250  		"creation from null": {
  5251  			Action: plans.Update,
  5252  			Mode:   addrs.ManagedResourceMode,
  5253  			Before: cty.ObjectVal(map[string]cty.Value{
  5254  				"id":  cty.NullVal(cty.String),
  5255  				"ami": cty.NullVal(cty.String),
  5256  				"disks": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
  5257  					"mount_point": cty.String,
  5258  					"size":        cty.String,
  5259  				}))),
  5260  			}),
  5261  			After: cty.ObjectVal(map[string]cty.Value{
  5262  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  5263  				"ami": cty.StringVal("ami-AFTER"),
  5264  				"disks": cty.SetVal([]cty.Value{
  5265  					cty.ObjectVal(map[string]cty.Value{
  5266  						"mount_point": cty.StringVal("/var/diska"),
  5267  						"size":        cty.NullVal(cty.String),
  5268  					}),
  5269  				}),
  5270  			}),
  5271  			RequiredReplace: cty.NewPathSet(),
  5272  			Schema:          testSchemaSensitive(configschema.NestingSet),
  5273  			ExpectedOutput: `  # test_instance.example will be updated in-place
  5274    ~ resource "test_instance" "example" {
  5275        + ami   = "ami-AFTER"
  5276        + disks = (sensitive value)
  5277        + id    = "i-02ae66f368e8518a9"
  5278      }
  5279  `,
  5280  		},
  5281  		"in-place update": {
  5282  			Action: plans.Update,
  5283  			Mode:   addrs.ManagedResourceMode,
  5284  			Before: cty.ObjectVal(map[string]cty.Value{
  5285  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  5286  				"ami": cty.StringVal("ami-BEFORE"),
  5287  				"disks": cty.SetValEmpty(cty.Object(map[string]cty.Type{
  5288  					"mount_point": cty.String,
  5289  					"size":        cty.String,
  5290  				})),
  5291  			}),
  5292  			After: cty.ObjectVal(map[string]cty.Value{
  5293  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  5294  				"ami": cty.StringVal("ami-AFTER"),
  5295  				"disks": cty.SetVal([]cty.Value{
  5296  					cty.ObjectVal(map[string]cty.Value{
  5297  						"mount_point": cty.StringVal("/var/diska"),
  5298  						"size":        cty.NullVal(cty.String),
  5299  					}),
  5300  				}),
  5301  			}),
  5302  			RequiredReplace: cty.NewPathSet(),
  5303  			Schema:          testSchemaSensitive(configschema.NestingSet),
  5304  			ExpectedOutput: `  # test_instance.example will be updated in-place
  5305    ~ resource "test_instance" "example" {
  5306        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  5307        ~ disks = (sensitive value)
  5308          id    = "i-02ae66f368e8518a9"
  5309      }
  5310  `,
  5311  		},
  5312  		"force-new update (whole block)": {
  5313  			Action:       plans.DeleteThenCreate,
  5314  			ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate,
  5315  			Mode:         addrs.ManagedResourceMode,
  5316  			Before: cty.ObjectVal(map[string]cty.Value{
  5317  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  5318  				"ami": cty.StringVal("ami-BEFORE"),
  5319  				"disks": cty.SetVal([]cty.Value{
  5320  					cty.ObjectVal(map[string]cty.Value{
  5321  						"mount_point": cty.StringVal("/var/diska"),
  5322  						"size":        cty.StringVal("50GB"),
  5323  					}),
  5324  				}),
  5325  			}),
  5326  			After: cty.ObjectVal(map[string]cty.Value{
  5327  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  5328  				"ami": cty.StringVal("ami-AFTER"),
  5329  				"disks": cty.SetVal([]cty.Value{
  5330  					cty.ObjectVal(map[string]cty.Value{
  5331  						"mount_point": cty.StringVal("/var/diska"),
  5332  						"size":        cty.StringVal("100GB"),
  5333  					}),
  5334  				}),
  5335  			}),
  5336  			RequiredReplace: cty.NewPathSet(
  5337  				cty.Path{cty.GetAttrStep{Name: "disks"}},
  5338  			),
  5339  			Schema: testSchemaSensitive(configschema.NestingSet),
  5340  			ExpectedOutput: `  # test_instance.example must be replaced
  5341  -/+ resource "test_instance" "example" {
  5342        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  5343        ~ disks = (sensitive value) # forces replacement
  5344          id    = "i-02ae66f368e8518a9"
  5345      }
  5346  `,
  5347  		},
  5348  		"in-place update - deletion": {
  5349  			Action: plans.Update,
  5350  			Mode:   addrs.ManagedResourceMode,
  5351  			Before: cty.ObjectVal(map[string]cty.Value{
  5352  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  5353  				"ami": cty.StringVal("ami-BEFORE"),
  5354  				"disks": cty.SetVal([]cty.Value{
  5355  					cty.ObjectVal(map[string]cty.Value{
  5356  						"mount_point": cty.StringVal("/var/diska"),
  5357  						"size":        cty.StringVal("50GB"),
  5358  					}),
  5359  				}),
  5360  			}),
  5361  			After: cty.ObjectVal(map[string]cty.Value{
  5362  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  5363  				"ami": cty.StringVal("ami-AFTER"),
  5364  				"disks": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
  5365  					"mount_point": cty.String,
  5366  					"size":        cty.String,
  5367  				}))),
  5368  			}),
  5369  			RequiredReplace: cty.NewPathSet(),
  5370  			Schema:          testSchemaSensitive(configschema.NestingSet),
  5371  			ExpectedOutput: `  # test_instance.example will be updated in-place
  5372    ~ resource "test_instance" "example" {
  5373        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  5374        - disks = (sensitive value)
  5375          id    = "i-02ae66f368e8518a9"
  5376      }
  5377  `,
  5378  		},
  5379  		"in-place update - unknown": {
  5380  			Action: plans.Update,
  5381  			Mode:   addrs.ManagedResourceMode,
  5382  			Before: cty.ObjectVal(map[string]cty.Value{
  5383  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  5384  				"ami": cty.StringVal("ami-BEFORE"),
  5385  				"disks": cty.SetVal([]cty.Value{
  5386  					cty.ObjectVal(map[string]cty.Value{
  5387  						"mount_point": cty.StringVal("/var/diska"),
  5388  						"size":        cty.StringVal("50GB"),
  5389  					}),
  5390  				}),
  5391  			}),
  5392  			After: cty.ObjectVal(map[string]cty.Value{
  5393  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  5394  				"ami": cty.StringVal("ami-AFTER"),
  5395  				"disks": cty.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{
  5396  					"mount_point": cty.String,
  5397  					"size":        cty.String,
  5398  				}))),
  5399  			}),
  5400  			RequiredReplace: cty.NewPathSet(),
  5401  			Schema:          testSchemaSensitive(configschema.NestingSet),
  5402  			ExpectedOutput: `  # test_instance.example will be updated in-place
  5403    ~ resource "test_instance" "example" {
  5404        ~ ami   = "ami-BEFORE" -> "ami-AFTER"
  5405        ~ disks = (sensitive value)
  5406          id    = "i-02ae66f368e8518a9"
  5407      }
  5408  `,
  5409  		},
  5410  	}
  5411  	runTestCases(t, testCases)
  5412  }
  5413  
  5414  func TestResourceChange_actionReason(t *testing.T) {
  5415  	emptySchema := &configschema.Block{}
  5416  	nullVal := cty.NullVal(cty.EmptyObject)
  5417  	emptyVal := cty.EmptyObjectVal
  5418  
  5419  	testCases := map[string]testCase{
  5420  		"delete for no particular reason": {
  5421  			Action:          plans.Delete,
  5422  			ActionReason:    plans.ResourceInstanceChangeNoReason,
  5423  			Mode:            addrs.ManagedResourceMode,
  5424  			Before:          emptyVal,
  5425  			After:           nullVal,
  5426  			Schema:          emptySchema,
  5427  			RequiredReplace: cty.NewPathSet(),
  5428  			ExpectedOutput: `  # test_instance.example will be destroyed
  5429    - resource "test_instance" "example" {}
  5430  `,
  5431  		},
  5432  		"delete because of wrong repetition mode (NoKey)": {
  5433  			Action:          plans.Delete,
  5434  			ActionReason:    plans.ResourceInstanceDeleteBecauseWrongRepetition,
  5435  			Mode:            addrs.ManagedResourceMode,
  5436  			InstanceKey:     addrs.NoKey,
  5437  			Before:          emptyVal,
  5438  			After:           nullVal,
  5439  			Schema:          emptySchema,
  5440  			RequiredReplace: cty.NewPathSet(),
  5441  			ExpectedOutput: `  # test_instance.example will be destroyed
  5442    # (because resource uses count or for_each)
  5443    - resource "test_instance" "example" {}
  5444  `,
  5445  		},
  5446  		"delete because of wrong repetition mode (IntKey)": {
  5447  			Action:          plans.Delete,
  5448  			ActionReason:    plans.ResourceInstanceDeleteBecauseWrongRepetition,
  5449  			Mode:            addrs.ManagedResourceMode,
  5450  			InstanceKey:     addrs.IntKey(1),
  5451  			Before:          emptyVal,
  5452  			After:           nullVal,
  5453  			Schema:          emptySchema,
  5454  			RequiredReplace: cty.NewPathSet(),
  5455  			ExpectedOutput: `  # test_instance.example[1] will be destroyed
  5456    # (because resource does not use count)
  5457    - resource "test_instance" "example" {}
  5458  `,
  5459  		},
  5460  		"delete because of wrong repetition mode (StringKey)": {
  5461  			Action:          plans.Delete,
  5462  			ActionReason:    plans.ResourceInstanceDeleteBecauseWrongRepetition,
  5463  			Mode:            addrs.ManagedResourceMode,
  5464  			InstanceKey:     addrs.StringKey("a"),
  5465  			Before:          emptyVal,
  5466  			After:           nullVal,
  5467  			Schema:          emptySchema,
  5468  			RequiredReplace: cty.NewPathSet(),
  5469  			ExpectedOutput: `  # test_instance.example["a"] will be destroyed
  5470    # (because resource does not use for_each)
  5471    - resource "test_instance" "example" {}
  5472  `,
  5473  		},
  5474  		"delete because no resource configuration": {
  5475  			Action:          plans.Delete,
  5476  			ActionReason:    plans.ResourceInstanceDeleteBecauseNoResourceConfig,
  5477  			ModuleInst:      addrs.RootModuleInstance.Child("foo", addrs.NoKey),
  5478  			Mode:            addrs.ManagedResourceMode,
  5479  			Before:          emptyVal,
  5480  			After:           nullVal,
  5481  			Schema:          emptySchema,
  5482  			RequiredReplace: cty.NewPathSet(),
  5483  			ExpectedOutput: `  # module.foo.test_instance.example will be destroyed
  5484    # (because test_instance.example is not in configuration)
  5485    - resource "test_instance" "example" {}
  5486  `,
  5487  		},
  5488  		"delete because no module": {
  5489  			Action:          plans.Delete,
  5490  			ActionReason:    plans.ResourceInstanceDeleteBecauseNoModule,
  5491  			ModuleInst:      addrs.RootModuleInstance.Child("foo", addrs.IntKey(1)),
  5492  			Mode:            addrs.ManagedResourceMode,
  5493  			Before:          emptyVal,
  5494  			After:           nullVal,
  5495  			Schema:          emptySchema,
  5496  			RequiredReplace: cty.NewPathSet(),
  5497  			ExpectedOutput: `  # module.foo[1].test_instance.example will be destroyed
  5498    # (because module.foo[1] is not in configuration)
  5499    - resource "test_instance" "example" {}
  5500  `,
  5501  		},
  5502  		"delete because out of range for count": {
  5503  			Action:          plans.Delete,
  5504  			ActionReason:    plans.ResourceInstanceDeleteBecauseCountIndex,
  5505  			Mode:            addrs.ManagedResourceMode,
  5506  			InstanceKey:     addrs.IntKey(1),
  5507  			Before:          emptyVal,
  5508  			After:           nullVal,
  5509  			Schema:          emptySchema,
  5510  			RequiredReplace: cty.NewPathSet(),
  5511  			ExpectedOutput: `  # test_instance.example[1] will be destroyed
  5512    # (because index [1] is out of range for count)
  5513    - resource "test_instance" "example" {}
  5514  `,
  5515  		},
  5516  		"delete because out of range for for_each": {
  5517  			Action:          plans.Delete,
  5518  			ActionReason:    plans.ResourceInstanceDeleteBecauseEachKey,
  5519  			Mode:            addrs.ManagedResourceMode,
  5520  			InstanceKey:     addrs.StringKey("boop"),
  5521  			Before:          emptyVal,
  5522  			After:           nullVal,
  5523  			Schema:          emptySchema,
  5524  			RequiredReplace: cty.NewPathSet(),
  5525  			ExpectedOutput: `  # test_instance.example["boop"] will be destroyed
  5526    # (because key ["boop"] is not in for_each map)
  5527    - resource "test_instance" "example" {}
  5528  `,
  5529  		},
  5530  		"replace for no particular reason (delete first)": {
  5531  			Action:          plans.DeleteThenCreate,
  5532  			ActionReason:    plans.ResourceInstanceChangeNoReason,
  5533  			Mode:            addrs.ManagedResourceMode,
  5534  			Before:          emptyVal,
  5535  			After:           nullVal,
  5536  			Schema:          emptySchema,
  5537  			RequiredReplace: cty.NewPathSet(),
  5538  			ExpectedOutput: `  # test_instance.example must be replaced
  5539  -/+ resource "test_instance" "example" {}
  5540  `,
  5541  		},
  5542  		"replace for no particular reason (create first)": {
  5543  			Action:          plans.CreateThenDelete,
  5544  			ActionReason:    plans.ResourceInstanceChangeNoReason,
  5545  			Mode:            addrs.ManagedResourceMode,
  5546  			Before:          emptyVal,
  5547  			After:           nullVal,
  5548  			Schema:          emptySchema,
  5549  			RequiredReplace: cty.NewPathSet(),
  5550  			ExpectedOutput: `  # test_instance.example must be replaced
  5551  +/- resource "test_instance" "example" {}
  5552  `,
  5553  		},
  5554  		"replace by request (delete first)": {
  5555  			Action:          plans.DeleteThenCreate,
  5556  			ActionReason:    plans.ResourceInstanceReplaceByRequest,
  5557  			Mode:            addrs.ManagedResourceMode,
  5558  			Before:          emptyVal,
  5559  			After:           nullVal,
  5560  			Schema:          emptySchema,
  5561  			RequiredReplace: cty.NewPathSet(),
  5562  			ExpectedOutput: `  # test_instance.example will be replaced, as requested
  5563  -/+ resource "test_instance" "example" {}
  5564  `,
  5565  		},
  5566  		"replace by request (create first)": {
  5567  			Action:          plans.CreateThenDelete,
  5568  			ActionReason:    plans.ResourceInstanceReplaceByRequest,
  5569  			Mode:            addrs.ManagedResourceMode,
  5570  			Before:          emptyVal,
  5571  			After:           nullVal,
  5572  			Schema:          emptySchema,
  5573  			RequiredReplace: cty.NewPathSet(),
  5574  			ExpectedOutput: `  # test_instance.example will be replaced, as requested
  5575  +/- resource "test_instance" "example" {}
  5576  `,
  5577  		},
  5578  		"replace because tainted (delete first)": {
  5579  			Action:          plans.DeleteThenCreate,
  5580  			ActionReason:    plans.ResourceInstanceReplaceBecauseTainted,
  5581  			Mode:            addrs.ManagedResourceMode,
  5582  			Before:          emptyVal,
  5583  			After:           nullVal,
  5584  			Schema:          emptySchema,
  5585  			RequiredReplace: cty.NewPathSet(),
  5586  			ExpectedOutput: `  # test_instance.example is tainted, so must be replaced
  5587  -/+ resource "test_instance" "example" {}
  5588  `,
  5589  		},
  5590  		"replace because tainted (create first)": {
  5591  			Action:          plans.CreateThenDelete,
  5592  			ActionReason:    plans.ResourceInstanceReplaceBecauseTainted,
  5593  			Mode:            addrs.ManagedResourceMode,
  5594  			Before:          emptyVal,
  5595  			After:           nullVal,
  5596  			Schema:          emptySchema,
  5597  			RequiredReplace: cty.NewPathSet(),
  5598  			ExpectedOutput: `  # test_instance.example is tainted, so must be replaced
  5599  +/- resource "test_instance" "example" {}
  5600  `,
  5601  		},
  5602  		"replace because cannot update (delete first)": {
  5603  			Action:          plans.DeleteThenCreate,
  5604  			ActionReason:    plans.ResourceInstanceReplaceBecauseCannotUpdate,
  5605  			Mode:            addrs.ManagedResourceMode,
  5606  			Before:          emptyVal,
  5607  			After:           nullVal,
  5608  			Schema:          emptySchema,
  5609  			RequiredReplace: cty.NewPathSet(),
  5610  			// This one has no special message, because the fuller explanation
  5611  			// typically appears inline as a "# forces replacement" comment.
  5612  			// (not shown here)
  5613  			ExpectedOutput: `  # test_instance.example must be replaced
  5614  -/+ resource "test_instance" "example" {}
  5615  `,
  5616  		},
  5617  		"replace because cannot update (create first)": {
  5618  			Action:          plans.CreateThenDelete,
  5619  			ActionReason:    plans.ResourceInstanceReplaceBecauseCannotUpdate,
  5620  			Mode:            addrs.ManagedResourceMode,
  5621  			Before:          emptyVal,
  5622  			After:           nullVal,
  5623  			Schema:          emptySchema,
  5624  			RequiredReplace: cty.NewPathSet(),
  5625  			// This one has no special message, because the fuller explanation
  5626  			// typically appears inline as a "# forces replacement" comment.
  5627  			// (not shown here)
  5628  			ExpectedOutput: `  # test_instance.example must be replaced
  5629  +/- resource "test_instance" "example" {}
  5630  `,
  5631  		},
  5632  	}
  5633  
  5634  	runTestCases(t, testCases)
  5635  }
  5636  
  5637  func TestResourceChange_sensitiveVariable(t *testing.T) {
  5638  	testCases := map[string]testCase{
  5639  		"creation": {
  5640  			Action: plans.Create,
  5641  			Mode:   addrs.ManagedResourceMode,
  5642  			Before: cty.NullVal(cty.EmptyObject),
  5643  			After: cty.ObjectVal(map[string]cty.Value{
  5644  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  5645  				"ami": cty.StringVal("ami-123"),
  5646  				"map_key": cty.MapVal(map[string]cty.Value{
  5647  					"breakfast": cty.NumberIntVal(800),
  5648  					"dinner":    cty.NumberIntVal(2000),
  5649  				}),
  5650  				"map_whole": cty.MapVal(map[string]cty.Value{
  5651  					"breakfast": cty.StringVal("pizza"),
  5652  					"dinner":    cty.StringVal("pizza"),
  5653  				}),
  5654  				"list_field": cty.ListVal([]cty.Value{
  5655  					cty.StringVal("hello"),
  5656  					cty.StringVal("friends"),
  5657  					cty.StringVal("!"),
  5658  				}),
  5659  				"nested_block_list": cty.ListVal([]cty.Value{
  5660  					cty.ObjectVal(map[string]cty.Value{
  5661  						"an_attr": cty.StringVal("secretval"),
  5662  						"another": cty.StringVal("not secret"),
  5663  					}),
  5664  				}),
  5665  				"nested_block_set": cty.ListVal([]cty.Value{
  5666  					cty.ObjectVal(map[string]cty.Value{
  5667  						"an_attr": cty.StringVal("secretval"),
  5668  						"another": cty.StringVal("not secret"),
  5669  					}),
  5670  				}),
  5671  			}),
  5672  			AfterValMarks: []cty.PathValueMarks{
  5673  				{
  5674  					Path:  cty.Path{cty.GetAttrStep{Name: "ami"}},
  5675  					Marks: cty.NewValueMarks(marks.Sensitive),
  5676  				},
  5677  				{
  5678  					Path:  cty.Path{cty.GetAttrStep{Name: "list_field"}, cty.IndexStep{Key: cty.NumberIntVal(1)}},
  5679  					Marks: cty.NewValueMarks(marks.Sensitive),
  5680  				},
  5681  				{
  5682  					Path:  cty.Path{cty.GetAttrStep{Name: "map_whole"}},
  5683  					Marks: cty.NewValueMarks(marks.Sensitive),
  5684  				},
  5685  				{
  5686  					Path:  cty.Path{cty.GetAttrStep{Name: "map_key"}, cty.IndexStep{Key: cty.StringVal("dinner")}},
  5687  					Marks: cty.NewValueMarks(marks.Sensitive),
  5688  				},
  5689  				{
  5690  					// Nested blocks/sets will mark the whole set/block as sensitive
  5691  					Path:  cty.Path{cty.GetAttrStep{Name: "nested_block_list"}},
  5692  					Marks: cty.NewValueMarks(marks.Sensitive),
  5693  				},
  5694  				{
  5695  					Path:  cty.Path{cty.GetAttrStep{Name: "nested_block_set"}},
  5696  					Marks: cty.NewValueMarks(marks.Sensitive),
  5697  				},
  5698  			},
  5699  			RequiredReplace: cty.NewPathSet(),
  5700  			Schema: &configschema.Block{
  5701  				Attributes: map[string]*configschema.Attribute{
  5702  					"id":         {Type: cty.String, Optional: true, Computed: true},
  5703  					"ami":        {Type: cty.String, Optional: true},
  5704  					"map_whole":  {Type: cty.Map(cty.String), Optional: true},
  5705  					"map_key":    {Type: cty.Map(cty.Number), Optional: true},
  5706  					"list_field": {Type: cty.List(cty.String), Optional: true},
  5707  				},
  5708  				BlockTypes: map[string]*configschema.NestedBlock{
  5709  					"nested_block_list": {
  5710  						Block: configschema.Block{
  5711  							Attributes: map[string]*configschema.Attribute{
  5712  								"an_attr": {Type: cty.String, Optional: true},
  5713  								"another": {Type: cty.String, Optional: true},
  5714  							},
  5715  						},
  5716  						Nesting: configschema.NestingList,
  5717  					},
  5718  					"nested_block_set": {
  5719  						Block: configschema.Block{
  5720  							Attributes: map[string]*configschema.Attribute{
  5721  								"an_attr": {Type: cty.String, Optional: true},
  5722  								"another": {Type: cty.String, Optional: true},
  5723  							},
  5724  						},
  5725  						Nesting: configschema.NestingSet,
  5726  					},
  5727  				},
  5728  			},
  5729  			ExpectedOutput: `  # test_instance.example will be created
  5730    + resource "test_instance" "example" {
  5731        + ami        = (sensitive value)
  5732        + id         = "i-02ae66f368e8518a9"
  5733        + list_field = [
  5734            + "hello",
  5735            + (sensitive value),
  5736            + "!",
  5737          ]
  5738        + map_key    = {
  5739            + "breakfast" = 800
  5740            + "dinner"    = (sensitive value)
  5741          }
  5742        + map_whole  = (sensitive value)
  5743  
  5744        + nested_block_list {
  5745            # At least one attribute in this block is (or was) sensitive,
  5746            # so its contents will not be displayed.
  5747          }
  5748  
  5749        + nested_block_set {
  5750            # At least one attribute in this block is (or was) sensitive,
  5751            # so its contents will not be displayed.
  5752          }
  5753      }
  5754  `,
  5755  		},
  5756  		"in-place update - before sensitive": {
  5757  			Action: plans.Update,
  5758  			Mode:   addrs.ManagedResourceMode,
  5759  			Before: cty.ObjectVal(map[string]cty.Value{
  5760  				"id":          cty.StringVal("i-02ae66f368e8518a9"),
  5761  				"ami":         cty.StringVal("ami-BEFORE"),
  5762  				"special":     cty.BoolVal(true),
  5763  				"some_number": cty.NumberIntVal(1),
  5764  				"list_field": cty.ListVal([]cty.Value{
  5765  					cty.StringVal("hello"),
  5766  					cty.StringVal("friends"),
  5767  					cty.StringVal("!"),
  5768  				}),
  5769  				"map_key": cty.MapVal(map[string]cty.Value{
  5770  					"breakfast": cty.NumberIntVal(800),
  5771  					"dinner":    cty.NumberIntVal(2000), // sensitive key
  5772  				}),
  5773  				"map_whole": cty.MapVal(map[string]cty.Value{
  5774  					"breakfast": cty.StringVal("pizza"),
  5775  					"dinner":    cty.StringVal("pizza"),
  5776  				}),
  5777  				"nested_block": cty.ListVal([]cty.Value{
  5778  					cty.ObjectVal(map[string]cty.Value{
  5779  						"an_attr": cty.StringVal("secretval"),
  5780  					}),
  5781  				}),
  5782  				"nested_block_set": cty.ListVal([]cty.Value{
  5783  					cty.ObjectVal(map[string]cty.Value{
  5784  						"an_attr": cty.StringVal("secretval"),
  5785  					}),
  5786  				}),
  5787  			}),
  5788  			After: cty.ObjectVal(map[string]cty.Value{
  5789  				"id":          cty.StringVal("i-02ae66f368e8518a9"),
  5790  				"ami":         cty.StringVal("ami-AFTER"),
  5791  				"special":     cty.BoolVal(false),
  5792  				"some_number": cty.NumberIntVal(2),
  5793  				"list_field": cty.ListVal([]cty.Value{
  5794  					cty.StringVal("hello"),
  5795  					cty.StringVal("friends"),
  5796  					cty.StringVal("."),
  5797  				}),
  5798  				"map_key": cty.MapVal(map[string]cty.Value{
  5799  					"breakfast": cty.NumberIntVal(800),
  5800  					"dinner":    cty.NumberIntVal(1900),
  5801  				}),
  5802  				"map_whole": cty.MapVal(map[string]cty.Value{
  5803  					"breakfast": cty.StringVal("cereal"),
  5804  					"dinner":    cty.StringVal("pizza"),
  5805  				}),
  5806  				"nested_block": cty.ListVal([]cty.Value{
  5807  					cty.ObjectVal(map[string]cty.Value{
  5808  						"an_attr": cty.StringVal("changed"),
  5809  					}),
  5810  				}),
  5811  				"nested_block_set": cty.ListVal([]cty.Value{
  5812  					cty.ObjectVal(map[string]cty.Value{
  5813  						"an_attr": cty.StringVal("changed"),
  5814  					}),
  5815  				}),
  5816  			}),
  5817  			BeforeValMarks: []cty.PathValueMarks{
  5818  				{
  5819  					Path:  cty.Path{cty.GetAttrStep{Name: "ami"}},
  5820  					Marks: cty.NewValueMarks(marks.Sensitive),
  5821  				},
  5822  				{
  5823  					Path:  cty.Path{cty.GetAttrStep{Name: "special"}},
  5824  					Marks: cty.NewValueMarks(marks.Sensitive),
  5825  				},
  5826  				{
  5827  					Path:  cty.Path{cty.GetAttrStep{Name: "some_number"}},
  5828  					Marks: cty.NewValueMarks(marks.Sensitive),
  5829  				},
  5830  				{
  5831  					Path:  cty.Path{cty.GetAttrStep{Name: "list_field"}, cty.IndexStep{Key: cty.NumberIntVal(2)}},
  5832  					Marks: cty.NewValueMarks(marks.Sensitive),
  5833  				},
  5834  				{
  5835  					Path:  cty.Path{cty.GetAttrStep{Name: "map_key"}, cty.IndexStep{Key: cty.StringVal("dinner")}},
  5836  					Marks: cty.NewValueMarks(marks.Sensitive),
  5837  				},
  5838  				{
  5839  					Path:  cty.Path{cty.GetAttrStep{Name: "map_whole"}},
  5840  					Marks: cty.NewValueMarks(marks.Sensitive),
  5841  				},
  5842  				{
  5843  					Path:  cty.Path{cty.GetAttrStep{Name: "nested_block"}},
  5844  					Marks: cty.NewValueMarks(marks.Sensitive),
  5845  				},
  5846  				{
  5847  					Path:  cty.Path{cty.GetAttrStep{Name: "nested_block_set"}},
  5848  					Marks: cty.NewValueMarks(marks.Sensitive),
  5849  				},
  5850  			},
  5851  			RequiredReplace: cty.NewPathSet(),
  5852  			Schema: &configschema.Block{
  5853  				Attributes: map[string]*configschema.Attribute{
  5854  					"id":          {Type: cty.String, Optional: true, Computed: true},
  5855  					"ami":         {Type: cty.String, Optional: true},
  5856  					"list_field":  {Type: cty.List(cty.String), Optional: true},
  5857  					"special":     {Type: cty.Bool, Optional: true},
  5858  					"some_number": {Type: cty.Number, Optional: true},
  5859  					"map_key":     {Type: cty.Map(cty.Number), Optional: true},
  5860  					"map_whole":   {Type: cty.Map(cty.String), Optional: true},
  5861  				},
  5862  				BlockTypes: map[string]*configschema.NestedBlock{
  5863  					"nested_block": {
  5864  						Block: configschema.Block{
  5865  							Attributes: map[string]*configschema.Attribute{
  5866  								"an_attr": {Type: cty.String, Optional: true},
  5867  							},
  5868  						},
  5869  						Nesting: configschema.NestingList,
  5870  					},
  5871  					"nested_block_set": {
  5872  						Block: configschema.Block{
  5873  							Attributes: map[string]*configschema.Attribute{
  5874  								"an_attr": {Type: cty.String, Optional: true},
  5875  							},
  5876  						},
  5877  						Nesting: configschema.NestingSet,
  5878  					},
  5879  				},
  5880  			},
  5881  			ExpectedOutput: `  # test_instance.example will be updated in-place
  5882    ~ resource "test_instance" "example" {
  5883        # Warning: this attribute value will no longer be marked as sensitive
  5884        # after applying this change.
  5885        ~ ami         = (sensitive value)
  5886          id          = "i-02ae66f368e8518a9"
  5887        ~ list_field  = [
  5888              # (1 unchanged element hidden)
  5889              "friends",
  5890            - (sensitive value),
  5891            + ".",
  5892          ]
  5893        ~ map_key     = {
  5894            # Warning: this attribute value will no longer be marked as sensitive
  5895            # after applying this change.
  5896            ~ "dinner"    = (sensitive value)
  5897              # (1 unchanged element hidden)
  5898          }
  5899        # Warning: this attribute value will no longer be marked as sensitive
  5900        # after applying this change.
  5901        ~ map_whole   = (sensitive value)
  5902        # Warning: this attribute value will no longer be marked as sensitive
  5903        # after applying this change.
  5904        ~ some_number = (sensitive value)
  5905        # Warning: this attribute value will no longer be marked as sensitive
  5906        # after applying this change.
  5907        ~ special     = (sensitive value)
  5908  
  5909        # Warning: this block will no longer be marked as sensitive
  5910        # after applying this change.
  5911        ~ nested_block {
  5912            # At least one attribute in this block is (or was) sensitive,
  5913            # so its contents will not be displayed.
  5914          }
  5915  
  5916        # Warning: this block will no longer be marked as sensitive
  5917        # after applying this change.
  5918        ~ nested_block_set {
  5919            # At least one attribute in this block is (or was) sensitive,
  5920            # so its contents will not be displayed.
  5921          }
  5922      }
  5923  `,
  5924  		},
  5925  		"in-place update - after sensitive": {
  5926  			Action: plans.Update,
  5927  			Mode:   addrs.ManagedResourceMode,
  5928  			Before: cty.ObjectVal(map[string]cty.Value{
  5929  				"id": cty.StringVal("i-02ae66f368e8518a9"),
  5930  				"list_field": cty.ListVal([]cty.Value{
  5931  					cty.StringVal("hello"),
  5932  					cty.StringVal("friends"),
  5933  				}),
  5934  				"map_key": cty.MapVal(map[string]cty.Value{
  5935  					"breakfast": cty.NumberIntVal(800),
  5936  					"dinner":    cty.NumberIntVal(2000), // sensitive key
  5937  				}),
  5938  				"map_whole": cty.MapVal(map[string]cty.Value{
  5939  					"breakfast": cty.StringVal("pizza"),
  5940  					"dinner":    cty.StringVal("pizza"),
  5941  				}),
  5942  				"nested_block_single": cty.ObjectVal(map[string]cty.Value{
  5943  					"an_attr": cty.StringVal("original"),
  5944  				}),
  5945  			}),
  5946  			After: cty.ObjectVal(map[string]cty.Value{
  5947  				"id": cty.StringVal("i-02ae66f368e8518a9"),
  5948  				"list_field": cty.ListVal([]cty.Value{
  5949  					cty.StringVal("goodbye"),
  5950  					cty.StringVal("friends"),
  5951  				}),
  5952  				"map_key": cty.MapVal(map[string]cty.Value{
  5953  					"breakfast": cty.NumberIntVal(700),
  5954  					"dinner":    cty.NumberIntVal(2100), // sensitive key
  5955  				}),
  5956  				"map_whole": cty.MapVal(map[string]cty.Value{
  5957  					"breakfast": cty.StringVal("cereal"),
  5958  					"dinner":    cty.StringVal("pizza"),
  5959  				}),
  5960  				"nested_block_single": cty.ObjectVal(map[string]cty.Value{
  5961  					"an_attr": cty.StringVal("changed"),
  5962  				}),
  5963  			}),
  5964  			AfterValMarks: []cty.PathValueMarks{
  5965  				{
  5966  					Path:  cty.Path{cty.GetAttrStep{Name: "tags"}, cty.IndexStep{Key: cty.StringVal("address")}},
  5967  					Marks: cty.NewValueMarks(marks.Sensitive),
  5968  				},
  5969  				{
  5970  					Path:  cty.Path{cty.GetAttrStep{Name: "list_field"}, cty.IndexStep{Key: cty.NumberIntVal(0)}},
  5971  					Marks: cty.NewValueMarks(marks.Sensitive),
  5972  				},
  5973  				{
  5974  					Path:  cty.Path{cty.GetAttrStep{Name: "map_key"}, cty.IndexStep{Key: cty.StringVal("dinner")}},
  5975  					Marks: cty.NewValueMarks(marks.Sensitive),
  5976  				},
  5977  				{
  5978  					Path:  cty.Path{cty.GetAttrStep{Name: "map_whole"}},
  5979  					Marks: cty.NewValueMarks(marks.Sensitive),
  5980  				},
  5981  				{
  5982  					Path:  cty.Path{cty.GetAttrStep{Name: "nested_block_single"}},
  5983  					Marks: cty.NewValueMarks(marks.Sensitive),
  5984  				},
  5985  			},
  5986  			RequiredReplace: cty.NewPathSet(),
  5987  			Schema: &configschema.Block{
  5988  				Attributes: map[string]*configschema.Attribute{
  5989  					"id":         {Type: cty.String, Optional: true, Computed: true},
  5990  					"list_field": {Type: cty.List(cty.String), Optional: true},
  5991  					"map_key":    {Type: cty.Map(cty.Number), Optional: true},
  5992  					"map_whole":  {Type: cty.Map(cty.String), Optional: true},
  5993  				},
  5994  				BlockTypes: map[string]*configschema.NestedBlock{
  5995  					"nested_block_single": {
  5996  						Block: configschema.Block{
  5997  							Attributes: map[string]*configschema.Attribute{
  5998  								"an_attr": {Type: cty.String, Optional: true},
  5999  							},
  6000  						},
  6001  						Nesting: configschema.NestingSingle,
  6002  					},
  6003  				},
  6004  			},
  6005  			ExpectedOutput: `  # test_instance.example will be updated in-place
  6006    ~ resource "test_instance" "example" {
  6007          id         = "i-02ae66f368e8518a9"
  6008        ~ list_field = [
  6009            - "hello",
  6010            + (sensitive value),
  6011              "friends",
  6012          ]
  6013        ~ map_key    = {
  6014            ~ "breakfast" = 800 -> 700
  6015            # Warning: this attribute value will be marked as sensitive and will not
  6016            # display in UI output after applying this change.
  6017            ~ "dinner"    = (sensitive value)
  6018          }
  6019        # Warning: this attribute value will be marked as sensitive and will not
  6020        # display in UI output after applying this change.
  6021        ~ map_whole  = (sensitive value)
  6022  
  6023        # Warning: this block will be marked as sensitive and will not
  6024        # display in UI output after applying this change.
  6025        ~ nested_block_single {
  6026            # At least one attribute in this block is (or was) sensitive,
  6027            # so its contents will not be displayed.
  6028          }
  6029      }
  6030  `,
  6031  		},
  6032  		"in-place update - both sensitive": {
  6033  			Action: plans.Update,
  6034  			Mode:   addrs.ManagedResourceMode,
  6035  			Before: cty.ObjectVal(map[string]cty.Value{
  6036  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  6037  				"ami": cty.StringVal("ami-BEFORE"),
  6038  				"list_field": cty.ListVal([]cty.Value{
  6039  					cty.StringVal("hello"),
  6040  					cty.StringVal("friends"),
  6041  				}),
  6042  				"map_key": cty.MapVal(map[string]cty.Value{
  6043  					"breakfast": cty.NumberIntVal(800),
  6044  					"dinner":    cty.NumberIntVal(2000), // sensitive key
  6045  				}),
  6046  				"map_whole": cty.MapVal(map[string]cty.Value{
  6047  					"breakfast": cty.StringVal("pizza"),
  6048  					"dinner":    cty.StringVal("pizza"),
  6049  				}),
  6050  				"nested_block_map": cty.MapVal(map[string]cty.Value{
  6051  					"foo": cty.ObjectVal(map[string]cty.Value{
  6052  						"an_attr": cty.StringVal("original"),
  6053  					}),
  6054  				}),
  6055  			}),
  6056  			After: cty.ObjectVal(map[string]cty.Value{
  6057  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  6058  				"ami": cty.StringVal("ami-AFTER"),
  6059  				"list_field": cty.ListVal([]cty.Value{
  6060  					cty.StringVal("goodbye"),
  6061  					cty.StringVal("friends"),
  6062  				}),
  6063  				"map_key": cty.MapVal(map[string]cty.Value{
  6064  					"breakfast": cty.NumberIntVal(800),
  6065  					"dinner":    cty.NumberIntVal(1800), // sensitive key
  6066  				}),
  6067  				"map_whole": cty.MapVal(map[string]cty.Value{
  6068  					"breakfast": cty.StringVal("cereal"),
  6069  					"dinner":    cty.StringVal("pizza"),
  6070  				}),
  6071  				"nested_block_map": cty.MapVal(map[string]cty.Value{
  6072  					"foo": cty.ObjectVal(map[string]cty.Value{
  6073  						"an_attr": cty.UnknownVal(cty.String),
  6074  					}),
  6075  				}),
  6076  			}),
  6077  			BeforeValMarks: []cty.PathValueMarks{
  6078  				{
  6079  					Path:  cty.Path{cty.GetAttrStep{Name: "ami"}},
  6080  					Marks: cty.NewValueMarks(marks.Sensitive),
  6081  				},
  6082  				{
  6083  					Path:  cty.Path{cty.GetAttrStep{Name: "list_field"}, cty.IndexStep{Key: cty.NumberIntVal(0)}},
  6084  					Marks: cty.NewValueMarks(marks.Sensitive),
  6085  				},
  6086  				{
  6087  					Path:  cty.Path{cty.GetAttrStep{Name: "map_key"}, cty.IndexStep{Key: cty.StringVal("dinner")}},
  6088  					Marks: cty.NewValueMarks(marks.Sensitive),
  6089  				},
  6090  				{
  6091  					Path:  cty.Path{cty.GetAttrStep{Name: "map_whole"}},
  6092  					Marks: cty.NewValueMarks(marks.Sensitive),
  6093  				},
  6094  				{
  6095  					Path:  cty.Path{cty.GetAttrStep{Name: "nested_block_map"}},
  6096  					Marks: cty.NewValueMarks(marks.Sensitive),
  6097  				},
  6098  			},
  6099  			AfterValMarks: []cty.PathValueMarks{
  6100  				{
  6101  					Path:  cty.Path{cty.GetAttrStep{Name: "ami"}},
  6102  					Marks: cty.NewValueMarks(marks.Sensitive),
  6103  				},
  6104  				{
  6105  					Path:  cty.Path{cty.GetAttrStep{Name: "list_field"}, cty.IndexStep{Key: cty.NumberIntVal(0)}},
  6106  					Marks: cty.NewValueMarks(marks.Sensitive),
  6107  				},
  6108  				{
  6109  					Path:  cty.Path{cty.GetAttrStep{Name: "map_key"}, cty.IndexStep{Key: cty.StringVal("dinner")}},
  6110  					Marks: cty.NewValueMarks(marks.Sensitive),
  6111  				},
  6112  				{
  6113  					Path:  cty.Path{cty.GetAttrStep{Name: "map_whole"}},
  6114  					Marks: cty.NewValueMarks(marks.Sensitive),
  6115  				},
  6116  				{
  6117  					Path:  cty.Path{cty.GetAttrStep{Name: "nested_block_map"}},
  6118  					Marks: cty.NewValueMarks(marks.Sensitive),
  6119  				},
  6120  			},
  6121  			RequiredReplace: cty.NewPathSet(),
  6122  			Schema: &configschema.Block{
  6123  				Attributes: map[string]*configschema.Attribute{
  6124  					"id":         {Type: cty.String, Optional: true, Computed: true},
  6125  					"ami":        {Type: cty.String, Optional: true},
  6126  					"list_field": {Type: cty.List(cty.String), Optional: true},
  6127  					"map_key":    {Type: cty.Map(cty.Number), Optional: true},
  6128  					"map_whole":  {Type: cty.Map(cty.String), Optional: true},
  6129  				},
  6130  				BlockTypes: map[string]*configschema.NestedBlock{
  6131  					"nested_block_map": {
  6132  						Block: configschema.Block{
  6133  							Attributes: map[string]*configschema.Attribute{
  6134  								"an_attr": {Type: cty.String, Optional: true},
  6135  							},
  6136  						},
  6137  						Nesting: configschema.NestingMap,
  6138  					},
  6139  				},
  6140  			},
  6141  			ExpectedOutput: `  # test_instance.example will be updated in-place
  6142    ~ resource "test_instance" "example" {
  6143        ~ ami        = (sensitive value)
  6144          id         = "i-02ae66f368e8518a9"
  6145        ~ list_field = [
  6146            - (sensitive value),
  6147            + (sensitive value),
  6148              "friends",
  6149          ]
  6150        ~ map_key    = {
  6151            ~ "dinner"    = (sensitive value)
  6152              # (1 unchanged element hidden)
  6153          }
  6154        ~ map_whole  = (sensitive value)
  6155  
  6156        ~ nested_block_map {
  6157            # At least one attribute in this block is (or was) sensitive,
  6158            # so its contents will not be displayed.
  6159          }
  6160      }
  6161  `,
  6162  		},
  6163  		"in-place update - value unchanged, sensitivity changes": {
  6164  			Action: plans.Update,
  6165  			Mode:   addrs.ManagedResourceMode,
  6166  			Before: cty.ObjectVal(map[string]cty.Value{
  6167  				"id":          cty.StringVal("i-02ae66f368e8518a9"),
  6168  				"ami":         cty.StringVal("ami-BEFORE"),
  6169  				"special":     cty.BoolVal(true),
  6170  				"some_number": cty.NumberIntVal(1),
  6171  				"list_field": cty.ListVal([]cty.Value{
  6172  					cty.StringVal("hello"),
  6173  					cty.StringVal("friends"),
  6174  					cty.StringVal("!"),
  6175  				}),
  6176  				"map_key": cty.MapVal(map[string]cty.Value{
  6177  					"breakfast": cty.NumberIntVal(800),
  6178  					"dinner":    cty.NumberIntVal(2000), // sensitive key
  6179  				}),
  6180  				"map_whole": cty.MapVal(map[string]cty.Value{
  6181  					"breakfast": cty.StringVal("pizza"),
  6182  					"dinner":    cty.StringVal("pizza"),
  6183  				}),
  6184  				"nested_block": cty.ListVal([]cty.Value{
  6185  					cty.ObjectVal(map[string]cty.Value{
  6186  						"an_attr": cty.StringVal("secretval"),
  6187  					}),
  6188  				}),
  6189  				"nested_block_set": cty.ListVal([]cty.Value{
  6190  					cty.ObjectVal(map[string]cty.Value{
  6191  						"an_attr": cty.StringVal("secretval"),
  6192  					}),
  6193  				}),
  6194  			}),
  6195  			After: cty.ObjectVal(map[string]cty.Value{
  6196  				"id":          cty.StringVal("i-02ae66f368e8518a9"),
  6197  				"ami":         cty.StringVal("ami-BEFORE"),
  6198  				"special":     cty.BoolVal(true),
  6199  				"some_number": cty.NumberIntVal(1),
  6200  				"list_field": cty.ListVal([]cty.Value{
  6201  					cty.StringVal("hello"),
  6202  					cty.StringVal("friends"),
  6203  					cty.StringVal("!"),
  6204  				}),
  6205  				"map_key": cty.MapVal(map[string]cty.Value{
  6206  					"breakfast": cty.NumberIntVal(800),
  6207  					"dinner":    cty.NumberIntVal(2000), // sensitive key
  6208  				}),
  6209  				"map_whole": cty.MapVal(map[string]cty.Value{
  6210  					"breakfast": cty.StringVal("pizza"),
  6211  					"dinner":    cty.StringVal("pizza"),
  6212  				}),
  6213  				"nested_block": cty.ListVal([]cty.Value{
  6214  					cty.ObjectVal(map[string]cty.Value{
  6215  						"an_attr": cty.StringVal("secretval"),
  6216  					}),
  6217  				}),
  6218  				"nested_block_set": cty.ListVal([]cty.Value{
  6219  					cty.ObjectVal(map[string]cty.Value{
  6220  						"an_attr": cty.StringVal("secretval"),
  6221  					}),
  6222  				}),
  6223  			}),
  6224  			BeforeValMarks: []cty.PathValueMarks{
  6225  				{
  6226  					Path:  cty.Path{cty.GetAttrStep{Name: "ami"}},
  6227  					Marks: cty.NewValueMarks(marks.Sensitive),
  6228  				},
  6229  				{
  6230  					Path:  cty.Path{cty.GetAttrStep{Name: "special"}},
  6231  					Marks: cty.NewValueMarks(marks.Sensitive),
  6232  				},
  6233  				{
  6234  					Path:  cty.Path{cty.GetAttrStep{Name: "some_number"}},
  6235  					Marks: cty.NewValueMarks(marks.Sensitive),
  6236  				},
  6237  				{
  6238  					Path:  cty.Path{cty.GetAttrStep{Name: "list_field"}, cty.IndexStep{Key: cty.NumberIntVal(2)}},
  6239  					Marks: cty.NewValueMarks(marks.Sensitive),
  6240  				},
  6241  				{
  6242  					Path:  cty.Path{cty.GetAttrStep{Name: "map_key"}, cty.IndexStep{Key: cty.StringVal("dinner")}},
  6243  					Marks: cty.NewValueMarks(marks.Sensitive),
  6244  				},
  6245  				{
  6246  					Path:  cty.Path{cty.GetAttrStep{Name: "map_whole"}},
  6247  					Marks: cty.NewValueMarks(marks.Sensitive),
  6248  				},
  6249  				{
  6250  					Path:  cty.Path{cty.GetAttrStep{Name: "nested_block"}},
  6251  					Marks: cty.NewValueMarks(marks.Sensitive),
  6252  				},
  6253  				{
  6254  					Path:  cty.Path{cty.GetAttrStep{Name: "nested_block_set"}},
  6255  					Marks: cty.NewValueMarks(marks.Sensitive),
  6256  				},
  6257  			},
  6258  			RequiredReplace: cty.NewPathSet(),
  6259  			Schema: &configschema.Block{
  6260  				Attributes: map[string]*configschema.Attribute{
  6261  					"id":          {Type: cty.String, Optional: true, Computed: true},
  6262  					"ami":         {Type: cty.String, Optional: true},
  6263  					"list_field":  {Type: cty.List(cty.String), Optional: true},
  6264  					"special":     {Type: cty.Bool, Optional: true},
  6265  					"some_number": {Type: cty.Number, Optional: true},
  6266  					"map_key":     {Type: cty.Map(cty.Number), Optional: true},
  6267  					"map_whole":   {Type: cty.Map(cty.String), Optional: true},
  6268  				},
  6269  				BlockTypes: map[string]*configschema.NestedBlock{
  6270  					"nested_block": {
  6271  						Block: configschema.Block{
  6272  							Attributes: map[string]*configschema.Attribute{
  6273  								"an_attr": {Type: cty.String, Optional: true},
  6274  							},
  6275  						},
  6276  						Nesting: configschema.NestingList,
  6277  					},
  6278  					"nested_block_set": {
  6279  						Block: configschema.Block{
  6280  							Attributes: map[string]*configschema.Attribute{
  6281  								"an_attr": {Type: cty.String, Optional: true},
  6282  							},
  6283  						},
  6284  						Nesting: configschema.NestingSet,
  6285  					},
  6286  				},
  6287  			},
  6288  			ExpectedOutput: `  # test_instance.example will be updated in-place
  6289    ~ resource "test_instance" "example" {
  6290        # Warning: this attribute value will no longer be marked as sensitive
  6291        # after applying this change. The value is unchanged.
  6292        ~ ami         = (sensitive value)
  6293          id          = "i-02ae66f368e8518a9"
  6294        ~ list_field  = [
  6295              # (1 unchanged element hidden)
  6296              "friends",
  6297            - (sensitive value),
  6298            + "!",
  6299          ]
  6300        ~ map_key     = {
  6301            # Warning: this attribute value will no longer be marked as sensitive
  6302            # after applying this change. The value is unchanged.
  6303            ~ "dinner"    = (sensitive value)
  6304              # (1 unchanged element hidden)
  6305          }
  6306        # Warning: this attribute value will no longer be marked as sensitive
  6307        # after applying this change. The value is unchanged.
  6308        ~ map_whole   = (sensitive value)
  6309        # Warning: this attribute value will no longer be marked as sensitive
  6310        # after applying this change. The value is unchanged.
  6311        ~ some_number = (sensitive value)
  6312        # Warning: this attribute value will no longer be marked as sensitive
  6313        # after applying this change. The value is unchanged.
  6314        ~ special     = (sensitive value)
  6315  
  6316        # Warning: this block will no longer be marked as sensitive
  6317        # after applying this change.
  6318        ~ nested_block {
  6319            # At least one attribute in this block is (or was) sensitive,
  6320            # so its contents will not be displayed.
  6321          }
  6322  
  6323        # Warning: this block will no longer be marked as sensitive
  6324        # after applying this change.
  6325        ~ nested_block_set {
  6326            # At least one attribute in this block is (or was) sensitive,
  6327            # so its contents will not be displayed.
  6328          }
  6329      }
  6330  `,
  6331  		},
  6332  		"deletion": {
  6333  			Action: plans.Delete,
  6334  			Mode:   addrs.ManagedResourceMode,
  6335  			Before: cty.ObjectVal(map[string]cty.Value{
  6336  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  6337  				"ami": cty.StringVal("ami-BEFORE"),
  6338  				"list_field": cty.ListVal([]cty.Value{
  6339  					cty.StringVal("hello"),
  6340  					cty.StringVal("friends"),
  6341  				}),
  6342  				"map_key": cty.MapVal(map[string]cty.Value{
  6343  					"breakfast": cty.NumberIntVal(800),
  6344  					"dinner":    cty.NumberIntVal(2000), // sensitive key
  6345  				}),
  6346  				"map_whole": cty.MapVal(map[string]cty.Value{
  6347  					"breakfast": cty.StringVal("pizza"),
  6348  					"dinner":    cty.StringVal("pizza"),
  6349  				}),
  6350  				"nested_block": cty.ListVal([]cty.Value{
  6351  					cty.ObjectVal(map[string]cty.Value{
  6352  						"an_attr": cty.StringVal("secret"),
  6353  						"another": cty.StringVal("not secret"),
  6354  					}),
  6355  				}),
  6356  				"nested_block_set": cty.ListVal([]cty.Value{
  6357  					cty.ObjectVal(map[string]cty.Value{
  6358  						"an_attr": cty.StringVal("secret"),
  6359  						"another": cty.StringVal("not secret"),
  6360  					}),
  6361  				}),
  6362  			}),
  6363  			After: cty.NullVal(cty.EmptyObject),
  6364  			BeforeValMarks: []cty.PathValueMarks{
  6365  				{
  6366  					Path:  cty.Path{cty.GetAttrStep{Name: "ami"}},
  6367  					Marks: cty.NewValueMarks(marks.Sensitive),
  6368  				},
  6369  				{
  6370  					Path:  cty.Path{cty.GetAttrStep{Name: "list_field"}, cty.IndexStep{Key: cty.NumberIntVal(1)}},
  6371  					Marks: cty.NewValueMarks(marks.Sensitive),
  6372  				},
  6373  				{
  6374  					Path:  cty.Path{cty.GetAttrStep{Name: "map_key"}, cty.IndexStep{Key: cty.StringVal("dinner")}},
  6375  					Marks: cty.NewValueMarks(marks.Sensitive),
  6376  				},
  6377  				{
  6378  					Path:  cty.Path{cty.GetAttrStep{Name: "map_whole"}},
  6379  					Marks: cty.NewValueMarks(marks.Sensitive),
  6380  				},
  6381  				{
  6382  					Path:  cty.Path{cty.GetAttrStep{Name: "nested_block"}},
  6383  					Marks: cty.NewValueMarks(marks.Sensitive),
  6384  				},
  6385  				{
  6386  					Path:  cty.Path{cty.GetAttrStep{Name: "nested_block_set"}},
  6387  					Marks: cty.NewValueMarks(marks.Sensitive),
  6388  				},
  6389  			},
  6390  			RequiredReplace: cty.NewPathSet(),
  6391  			Schema: &configschema.Block{
  6392  				Attributes: map[string]*configschema.Attribute{
  6393  					"id":         {Type: cty.String, Optional: true, Computed: true},
  6394  					"ami":        {Type: cty.String, Optional: true},
  6395  					"list_field": {Type: cty.List(cty.String), Optional: true},
  6396  					"map_key":    {Type: cty.Map(cty.Number), Optional: true},
  6397  					"map_whole":  {Type: cty.Map(cty.String), Optional: true},
  6398  				},
  6399  				BlockTypes: map[string]*configschema.NestedBlock{
  6400  					"nested_block_set": {
  6401  						Block: configschema.Block{
  6402  							Attributes: map[string]*configschema.Attribute{
  6403  								"an_attr": {Type: cty.String, Optional: true},
  6404  								"another": {Type: cty.String, Optional: true},
  6405  							},
  6406  						},
  6407  						Nesting: configschema.NestingSet,
  6408  					},
  6409  				},
  6410  			},
  6411  			ExpectedOutput: `  # test_instance.example will be destroyed
  6412    - resource "test_instance" "example" {
  6413        - ami        = (sensitive value) -> null
  6414        - id         = "i-02ae66f368e8518a9" -> null
  6415        - list_field = [
  6416            - "hello",
  6417            - (sensitive value),
  6418          ] -> null
  6419        - map_key    = {
  6420            - "breakfast" = 800
  6421            - "dinner"    = (sensitive value)
  6422          } -> null
  6423        - map_whole  = (sensitive value) -> null
  6424  
  6425        - nested_block_set {
  6426            # At least one attribute in this block is (or was) sensitive,
  6427            # so its contents will not be displayed.
  6428          }
  6429      }
  6430  `,
  6431  		},
  6432  		"update with sensitive value forcing replacement": {
  6433  			Action: plans.DeleteThenCreate,
  6434  			Mode:   addrs.ManagedResourceMode,
  6435  			Before: cty.ObjectVal(map[string]cty.Value{
  6436  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  6437  				"ami": cty.StringVal("ami-BEFORE"),
  6438  				"nested_block_set": cty.SetVal([]cty.Value{
  6439  					cty.ObjectVal(map[string]cty.Value{
  6440  						"an_attr": cty.StringVal("secret"),
  6441  					}),
  6442  				}),
  6443  			}),
  6444  			After: cty.ObjectVal(map[string]cty.Value{
  6445  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  6446  				"ami": cty.StringVal("ami-AFTER"),
  6447  				"nested_block_set": cty.SetVal([]cty.Value{
  6448  					cty.ObjectVal(map[string]cty.Value{
  6449  						"an_attr": cty.StringVal("changed"),
  6450  					}),
  6451  				}),
  6452  			}),
  6453  			BeforeValMarks: []cty.PathValueMarks{
  6454  				{
  6455  					Path:  cty.GetAttrPath("ami"),
  6456  					Marks: cty.NewValueMarks(marks.Sensitive),
  6457  				},
  6458  				{
  6459  					Path:  cty.GetAttrPath("nested_block_set"),
  6460  					Marks: cty.NewValueMarks(marks.Sensitive),
  6461  				},
  6462  			},
  6463  			AfterValMarks: []cty.PathValueMarks{
  6464  				{
  6465  					Path:  cty.GetAttrPath("ami"),
  6466  					Marks: cty.NewValueMarks(marks.Sensitive),
  6467  				},
  6468  				{
  6469  					Path:  cty.GetAttrPath("nested_block_set"),
  6470  					Marks: cty.NewValueMarks(marks.Sensitive),
  6471  				},
  6472  			},
  6473  			Schema: &configschema.Block{
  6474  				Attributes: map[string]*configschema.Attribute{
  6475  					"id":  {Type: cty.String, Optional: true, Computed: true},
  6476  					"ami": {Type: cty.String, Optional: true},
  6477  				},
  6478  				BlockTypes: map[string]*configschema.NestedBlock{
  6479  					"nested_block_set": {
  6480  						Block: configschema.Block{
  6481  							Attributes: map[string]*configschema.Attribute{
  6482  								"an_attr": {Type: cty.String, Required: true},
  6483  							},
  6484  						},
  6485  						Nesting: configschema.NestingSet,
  6486  					},
  6487  				},
  6488  			},
  6489  			RequiredReplace: cty.NewPathSet(
  6490  				cty.GetAttrPath("ami"),
  6491  				cty.GetAttrPath("nested_block_set"),
  6492  			),
  6493  			ExpectedOutput: `  # test_instance.example must be replaced
  6494  -/+ resource "test_instance" "example" {
  6495        ~ ami = (sensitive value) # forces replacement
  6496          id  = "i-02ae66f368e8518a9"
  6497  
  6498        ~ nested_block_set { # forces replacement
  6499            # At least one attribute in this block is (or was) sensitive,
  6500            # so its contents will not be displayed.
  6501          }
  6502      }
  6503  `,
  6504  		},
  6505  		"update with sensitive attribute forcing replacement": {
  6506  			Action: plans.DeleteThenCreate,
  6507  			Mode:   addrs.ManagedResourceMode,
  6508  			Before: cty.ObjectVal(map[string]cty.Value{
  6509  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  6510  				"ami": cty.StringVal("ami-BEFORE"),
  6511  			}),
  6512  			After: cty.ObjectVal(map[string]cty.Value{
  6513  				"id":  cty.StringVal("i-02ae66f368e8518a9"),
  6514  				"ami": cty.StringVal("ami-AFTER"),
  6515  			}),
  6516  			Schema: &configschema.Block{
  6517  				Attributes: map[string]*configschema.Attribute{
  6518  					"id":  {Type: cty.String, Optional: true, Computed: true},
  6519  					"ami": {Type: cty.String, Optional: true, Computed: true, Sensitive: true},
  6520  				},
  6521  			},
  6522  			RequiredReplace: cty.NewPathSet(
  6523  				cty.GetAttrPath("ami"),
  6524  			),
  6525  			ExpectedOutput: `  # test_instance.example must be replaced
  6526  -/+ resource "test_instance" "example" {
  6527        ~ ami = (sensitive value) # forces replacement
  6528          id  = "i-02ae66f368e8518a9"
  6529      }
  6530  `,
  6531  		},
  6532  		"update with sensitive nested type attribute forcing replacement": {
  6533  			Action: plans.DeleteThenCreate,
  6534  			Mode:   addrs.ManagedResourceMode,
  6535  			Before: cty.ObjectVal(map[string]cty.Value{
  6536  				"id": cty.StringVal("i-02ae66f368e8518a9"),
  6537  				"conn_info": cty.ObjectVal(map[string]cty.Value{
  6538  					"user":     cty.StringVal("not-secret"),
  6539  					"password": cty.StringVal("top-secret"),
  6540  				}),
  6541  			}),
  6542  			After: cty.ObjectVal(map[string]cty.Value{
  6543  				"id": cty.StringVal("i-02ae66f368e8518a9"),
  6544  				"conn_info": cty.ObjectVal(map[string]cty.Value{
  6545  					"user":     cty.StringVal("not-secret"),
  6546  					"password": cty.StringVal("new-secret"),
  6547  				}),
  6548  			}),
  6549  			Schema: &configschema.Block{
  6550  				Attributes: map[string]*configschema.Attribute{
  6551  					"id": {Type: cty.String, Optional: true, Computed: true},
  6552  					"conn_info": {
  6553  						NestedType: &configschema.Object{
  6554  							Nesting: configschema.NestingSingle,
  6555  							Attributes: map[string]*configschema.Attribute{
  6556  								"user":     {Type: cty.String, Optional: true},
  6557  								"password": {Type: cty.String, Optional: true, Sensitive: true},
  6558  							},
  6559  						},
  6560  					},
  6561  				},
  6562  			},
  6563  			RequiredReplace: cty.NewPathSet(
  6564  				cty.GetAttrPath("conn_info"),
  6565  				cty.GetAttrPath("password"),
  6566  			),
  6567  			ExpectedOutput: `  # test_instance.example must be replaced
  6568  -/+ resource "test_instance" "example" {
  6569        ~ conn_info = { # forces replacement
  6570            ~ password = (sensitive value)
  6571              # (1 unchanged attribute hidden)
  6572          }
  6573          id        = "i-02ae66f368e8518a9"
  6574      }
  6575  `,
  6576  		},
  6577  	}
  6578  	runTestCases(t, testCases)
  6579  }
  6580  
  6581  func TestResourceChange_moved(t *testing.T) {
  6582  	prevRunAddr := addrs.Resource{
  6583  		Mode: addrs.ManagedResourceMode,
  6584  		Type: "test_instance",
  6585  		Name: "previous",
  6586  	}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
  6587  
  6588  	testCases := map[string]testCase{
  6589  		"moved and updated": {
  6590  			PrevRunAddr: prevRunAddr,
  6591  			Action:      plans.Update,
  6592  			Mode:        addrs.ManagedResourceMode,
  6593  			Before: cty.ObjectVal(map[string]cty.Value{
  6594  				"id":  cty.StringVal("12345"),
  6595  				"foo": cty.StringVal("hello"),
  6596  				"bar": cty.StringVal("baz"),
  6597  			}),
  6598  			After: cty.ObjectVal(map[string]cty.Value{
  6599  				"id":  cty.StringVal("12345"),
  6600  				"foo": cty.StringVal("hello"),
  6601  				"bar": cty.StringVal("boop"),
  6602  			}),
  6603  			Schema: &configschema.Block{
  6604  				Attributes: map[string]*configschema.Attribute{
  6605  					"id":  {Type: cty.String, Computed: true},
  6606  					"foo": {Type: cty.String, Optional: true},
  6607  					"bar": {Type: cty.String, Optional: true},
  6608  				},
  6609  			},
  6610  			RequiredReplace: cty.NewPathSet(),
  6611  			ExpectedOutput: `  # test_instance.example will be updated in-place
  6612    # (moved from test_instance.previous)
  6613    ~ resource "test_instance" "example" {
  6614        ~ bar = "baz" -> "boop"
  6615          id  = "12345"
  6616          # (1 unchanged attribute hidden)
  6617      }
  6618  `,
  6619  		},
  6620  		"moved without changes": {
  6621  			PrevRunAddr: prevRunAddr,
  6622  			Action:      plans.NoOp,
  6623  			Mode:        addrs.ManagedResourceMode,
  6624  			Before: cty.ObjectVal(map[string]cty.Value{
  6625  				"id":  cty.StringVal("12345"),
  6626  				"foo": cty.StringVal("hello"),
  6627  				"bar": cty.StringVal("baz"),
  6628  			}),
  6629  			After: cty.ObjectVal(map[string]cty.Value{
  6630  				"id":  cty.StringVal("12345"),
  6631  				"foo": cty.StringVal("hello"),
  6632  				"bar": cty.StringVal("baz"),
  6633  			}),
  6634  			Schema: &configschema.Block{
  6635  				Attributes: map[string]*configschema.Attribute{
  6636  					"id":  {Type: cty.String, Computed: true},
  6637  					"foo": {Type: cty.String, Optional: true},
  6638  					"bar": {Type: cty.String, Optional: true},
  6639  				},
  6640  			},
  6641  			RequiredReplace: cty.NewPathSet(),
  6642  			ExpectedOutput: `  # test_instance.previous has moved to test_instance.example
  6643      resource "test_instance" "example" {
  6644          id  = "12345"
  6645          # (2 unchanged attributes hidden)
  6646      }
  6647  `,
  6648  		},
  6649  	}
  6650  
  6651  	runTestCases(t, testCases)
  6652  }
  6653  
  6654  type testCase struct {
  6655  	Action          plans.Action
  6656  	ActionReason    plans.ResourceInstanceChangeActionReason
  6657  	ModuleInst      addrs.ModuleInstance
  6658  	Mode            addrs.ResourceMode
  6659  	InstanceKey     addrs.InstanceKey
  6660  	DeposedKey      states.DeposedKey
  6661  	Before          cty.Value
  6662  	BeforeValMarks  []cty.PathValueMarks
  6663  	AfterValMarks   []cty.PathValueMarks
  6664  	After           cty.Value
  6665  	Schema          *configschema.Block
  6666  	RequiredReplace cty.PathSet
  6667  	ExpectedOutput  string
  6668  	PrevRunAddr     addrs.AbsResourceInstance
  6669  }
  6670  
  6671  func runTestCases(t *testing.T, testCases map[string]testCase) {
  6672  	color := &colorstring.Colorize{Colors: colorstring.DefaultColors, Disable: true}
  6673  
  6674  	for name, tc := range testCases {
  6675  		t.Run(name, func(t *testing.T) {
  6676  			ty := tc.Schema.ImpliedType()
  6677  
  6678  			beforeVal := tc.Before
  6679  			switch { // Some fixups to make the test cases a little easier to write
  6680  			case beforeVal.IsNull():
  6681  				beforeVal = cty.NullVal(ty) // allow mistyped nulls
  6682  			case !beforeVal.IsKnown():
  6683  				beforeVal = cty.UnknownVal(ty) // allow mistyped unknowns
  6684  			}
  6685  
  6686  			afterVal := tc.After
  6687  			switch { // Some fixups to make the test cases a little easier to write
  6688  			case afterVal.IsNull():
  6689  				afterVal = cty.NullVal(ty) // allow mistyped nulls
  6690  			case !afterVal.IsKnown():
  6691  				afterVal = cty.UnknownVal(ty) // allow mistyped unknowns
  6692  			}
  6693  
  6694  			addr := addrs.Resource{
  6695  				Mode: tc.Mode,
  6696  				Type: "test_instance",
  6697  				Name: "example",
  6698  			}.Instance(tc.InstanceKey).Absolute(tc.ModuleInst)
  6699  
  6700  			prevRunAddr := tc.PrevRunAddr
  6701  			// If no previous run address is given, reuse the current address
  6702  			// to make initialization easier
  6703  			if prevRunAddr.Resource.Resource.Type == "" {
  6704  				prevRunAddr = addr
  6705  			}
  6706  
  6707  			change := &plans.ResourceInstanceChange{
  6708  				Addr:        addr,
  6709  				PrevRunAddr: prevRunAddr,
  6710  				DeposedKey:  tc.DeposedKey,
  6711  				ProviderAddr: addrs.AbsProviderConfig{
  6712  					Provider: addrs.NewDefaultProvider("test"),
  6713  					Module:   addrs.RootModule,
  6714  				},
  6715  				Change: plans.Change{
  6716  					Action: tc.Action,
  6717  					Before: beforeVal.MarkWithPaths(tc.BeforeValMarks),
  6718  					After:  afterVal.MarkWithPaths(tc.AfterValMarks),
  6719  				},
  6720  				ActionReason:    tc.ActionReason,
  6721  				RequiredReplace: tc.RequiredReplace,
  6722  			}
  6723  
  6724  			output := ResourceChange(change, tc.Schema, color, DiffLanguageProposedChange)
  6725  			if diff := cmp.Diff(output, tc.ExpectedOutput); diff != "" {
  6726  				t.Errorf("wrong output\n%s", diff)
  6727  			}
  6728  		})
  6729  	}
  6730  }
  6731  
  6732  func TestOutputChanges(t *testing.T) {
  6733  	color := &colorstring.Colorize{Colors: colorstring.DefaultColors, Disable: true}
  6734  
  6735  	testCases := map[string]struct {
  6736  		changes []*plans.OutputChangeSrc
  6737  		output  string
  6738  	}{
  6739  		"new output value": {
  6740  			[]*plans.OutputChangeSrc{
  6741  				outputChange(
  6742  					"foo",
  6743  					cty.NullVal(cty.DynamicPseudoType),
  6744  					cty.StringVal("bar"),
  6745  					false,
  6746  				),
  6747  			},
  6748  			`
  6749    + foo = "bar"`,
  6750  		},
  6751  		"removed output": {
  6752  			[]*plans.OutputChangeSrc{
  6753  				outputChange(
  6754  					"foo",
  6755  					cty.StringVal("bar"),
  6756  					cty.NullVal(cty.DynamicPseudoType),
  6757  					false,
  6758  				),
  6759  			},
  6760  			`
  6761    - foo = "bar" -> null`,
  6762  		},
  6763  		"single string change": {
  6764  			[]*plans.OutputChangeSrc{
  6765  				outputChange(
  6766  					"foo",
  6767  					cty.StringVal("bar"),
  6768  					cty.StringVal("baz"),
  6769  					false,
  6770  				),
  6771  			},
  6772  			`
  6773    ~ foo = "bar" -> "baz"`,
  6774  		},
  6775  		"element added to list": {
  6776  			[]*plans.OutputChangeSrc{
  6777  				outputChange(
  6778  					"foo",
  6779  					cty.ListVal([]cty.Value{
  6780  						cty.StringVal("alpha"),
  6781  						cty.StringVal("beta"),
  6782  						cty.StringVal("delta"),
  6783  						cty.StringVal("epsilon"),
  6784  					}),
  6785  					cty.ListVal([]cty.Value{
  6786  						cty.StringVal("alpha"),
  6787  						cty.StringVal("beta"),
  6788  						cty.StringVal("gamma"),
  6789  						cty.StringVal("delta"),
  6790  						cty.StringVal("epsilon"),
  6791  					}),
  6792  					false,
  6793  				),
  6794  			},
  6795  			`
  6796    ~ foo = [
  6797          # (1 unchanged element hidden)
  6798          "beta",
  6799        + "gamma",
  6800          "delta",
  6801          # (1 unchanged element hidden)
  6802      ]`,
  6803  		},
  6804  		"multiple outputs changed, one sensitive": {
  6805  			[]*plans.OutputChangeSrc{
  6806  				outputChange(
  6807  					"a",
  6808  					cty.NumberIntVal(1),
  6809  					cty.NumberIntVal(2),
  6810  					false,
  6811  				),
  6812  				outputChange(
  6813  					"b",
  6814  					cty.StringVal("hunter2"),
  6815  					cty.StringVal("correct-horse-battery-staple"),
  6816  					true,
  6817  				),
  6818  				outputChange(
  6819  					"c",
  6820  					cty.BoolVal(false),
  6821  					cty.BoolVal(true),
  6822  					false,
  6823  				),
  6824  			},
  6825  			`
  6826    ~ a = 1 -> 2
  6827    ~ b = (sensitive value)
  6828    ~ c = false -> true`,
  6829  		},
  6830  	}
  6831  
  6832  	for name, tc := range testCases {
  6833  		t.Run(name, func(t *testing.T) {
  6834  			output := OutputChanges(tc.changes, color)
  6835  			if output != tc.output {
  6836  				t.Errorf("Unexpected diff.\ngot:\n%s\nwant:\n%s\n", output, tc.output)
  6837  			}
  6838  		})
  6839  	}
  6840  }
  6841  
  6842  func outputChange(name string, before, after cty.Value, sensitive bool) *plans.OutputChangeSrc {
  6843  	addr := addrs.AbsOutputValue{
  6844  		OutputValue: addrs.OutputValue{Name: name},
  6845  	}
  6846  
  6847  	change := &plans.OutputChange{
  6848  		Addr: addr, Change: plans.Change{
  6849  			Before: before,
  6850  			After:  after,
  6851  		},
  6852  		Sensitive: sensitive,
  6853  	}
  6854  
  6855  	changeSrc, err := change.Encode()
  6856  	if err != nil {
  6857  		panic(fmt.Sprintf("failed to encode change for %s: %s", addr, err))
  6858  	}
  6859  
  6860  	return changeSrc
  6861  }
  6862  
  6863  // A basic test schema using a configurable NestingMode for one (NestedType) attribute and one block
  6864  func testSchema(nesting configschema.NestingMode) *configschema.Block {
  6865  	var diskKey = "disks"
  6866  	if nesting == configschema.NestingSingle {
  6867  		diskKey = "disk"
  6868  	}
  6869  
  6870  	return &configschema.Block{
  6871  		Attributes: map[string]*configschema.Attribute{
  6872  			"id":  {Type: cty.String, Optional: true, Computed: true},
  6873  			"ami": {Type: cty.String, Optional: true},
  6874  			diskKey: {
  6875  				NestedType: &configschema.Object{
  6876  					Attributes: map[string]*configschema.Attribute{
  6877  						"mount_point": {Type: cty.String, Optional: true},
  6878  						"size":        {Type: cty.String, Optional: true},
  6879  					},
  6880  					Nesting: nesting,
  6881  				},
  6882  			},
  6883  		},
  6884  		BlockTypes: map[string]*configschema.NestedBlock{
  6885  			"root_block_device": {
  6886  				Block: configschema.Block{
  6887  					Attributes: map[string]*configschema.Attribute{
  6888  						"volume_type": {
  6889  							Type:     cty.String,
  6890  							Optional: true,
  6891  							Computed: true,
  6892  						},
  6893  					},
  6894  				},
  6895  				Nesting: nesting,
  6896  			},
  6897  		},
  6898  	}
  6899  }
  6900  
  6901  // A basic test schema using a configurable NestingMode for one (NestedType)
  6902  // attribute marked sensitive.
  6903  func testSchemaSensitive(nesting configschema.NestingMode) *configschema.Block {
  6904  	return &configschema.Block{
  6905  		Attributes: map[string]*configschema.Attribute{
  6906  			"id":  {Type: cty.String, Optional: true, Computed: true},
  6907  			"ami": {Type: cty.String, Optional: true},
  6908  			"disks": {
  6909  				Sensitive: true,
  6910  				NestedType: &configschema.Object{
  6911  					Attributes: map[string]*configschema.Attribute{
  6912  						"mount_point": {Type: cty.String, Optional: true},
  6913  						"size":        {Type: cty.String, Optional: true},
  6914  					},
  6915  					Nesting: nesting,
  6916  				},
  6917  			},
  6918  		},
  6919  	}
  6920  }
  6921  
  6922  func testSchemaMultipleBlocks(nesting configschema.NestingMode) *configschema.Block {
  6923  	return &configschema.Block{
  6924  		Attributes: map[string]*configschema.Attribute{
  6925  			"id":  {Type: cty.String, Optional: true, Computed: true},
  6926  			"ami": {Type: cty.String, Optional: true},
  6927  			"disks": {
  6928  				NestedType: &configschema.Object{
  6929  					Attributes: map[string]*configschema.Attribute{
  6930  						"mount_point": {Type: cty.String, Optional: true},
  6931  						"size":        {Type: cty.String, Optional: true},
  6932  					},
  6933  					Nesting: nesting,
  6934  				},
  6935  			},
  6936  		},
  6937  		BlockTypes: map[string]*configschema.NestedBlock{
  6938  			"root_block_device": {
  6939  				Block: configschema.Block{
  6940  					Attributes: map[string]*configschema.Attribute{
  6941  						"volume_type": {
  6942  							Type:     cty.String,
  6943  							Optional: true,
  6944  							Computed: true,
  6945  						},
  6946  					},
  6947  				},
  6948  				Nesting: nesting,
  6949  			},
  6950  			"leaf_block_device": {
  6951  				Block: configschema.Block{
  6952  					Attributes: map[string]*configschema.Attribute{
  6953  						"volume_type": {
  6954  							Type:     cty.String,
  6955  							Optional: true,
  6956  							Computed: true,
  6957  						},
  6958  					},
  6959  				},
  6960  				Nesting: nesting,
  6961  			},
  6962  		},
  6963  	}
  6964  }
  6965  
  6966  // similar to testSchema with the addition of a "new_field" block
  6967  func testSchemaPlus(nesting configschema.NestingMode) *configschema.Block {
  6968  	var diskKey = "disks"
  6969  	if nesting == configschema.NestingSingle {
  6970  		diskKey = "disk"
  6971  	}
  6972  
  6973  	return &configschema.Block{
  6974  		Attributes: map[string]*configschema.Attribute{
  6975  			"id":  {Type: cty.String, Optional: true, Computed: true},
  6976  			"ami": {Type: cty.String, Optional: true},
  6977  			diskKey: {
  6978  				NestedType: &configschema.Object{
  6979  					Attributes: map[string]*configschema.Attribute{
  6980  						"mount_point": {Type: cty.String, Optional: true},
  6981  						"size":        {Type: cty.String, Optional: true},
  6982  					},
  6983  					Nesting: nesting,
  6984  				},
  6985  			},
  6986  		},
  6987  		BlockTypes: map[string]*configschema.NestedBlock{
  6988  			"root_block_device": {
  6989  				Block: configschema.Block{
  6990  					Attributes: map[string]*configschema.Attribute{
  6991  						"volume_type": {
  6992  							Type:     cty.String,
  6993  							Optional: true,
  6994  							Computed: true,
  6995  						},
  6996  						"new_field": {
  6997  							Type:     cty.String,
  6998  							Optional: true,
  6999  							Computed: true,
  7000  						},
  7001  					},
  7002  				},
  7003  				Nesting: nesting,
  7004  			},
  7005  		},
  7006  	}
  7007  }