From d1a3ef32520b1c7b44730f2e845701d84625834b Mon Sep 17 00:00:00 2001 From: Brandon Croft Date: Mon, 24 Oct 2022 12:50:04 -0600 Subject: [PATCH] Reorder tests to get a more favorable commit diff --- internal/command/format/diff_test.go | 2412 +++++++++++++------------- 1 file changed, 1206 insertions(+), 1206 deletions(-) diff --git a/internal/command/format/diff_test.go b/internal/command/format/diff_test.go index a687ae3fb9a2..5ab9502c37b1 100644 --- a/internal/command/format/diff_test.go +++ b/internal/command/format/diff_test.go @@ -2427,7 +2427,7 @@ func TestResourceChange_map(t *testing.T) { runTestCases(t, testCases) } -func TestResourceChange_nestedSingle(t *testing.T) { +func TestResourceChange_nestedList(t *testing.T) { testCases := map[string]testCase{ "in-place update - equal": { Action: plans.Update, @@ -2435,31 +2435,39 @@ func TestResourceChange_nestedSingle(t *testing.T) { Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "root_block_device": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), + "root_block_device": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + }), }), - "disk": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), + "disks": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), + }), }), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "root_block_device": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), + "root_block_device": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + }), }), - "disk": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), + "disks": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), + }), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingSingle), + Schema: testSchema(configschema.NestingList), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { - ~ ami = "ami-BEFORE" -> "ami-AFTER" - id = "i-02ae66f368e8518a9" + ~ ami = "ami-BEFORE" -> "ami-AFTER" + id = "i-02ae66f368e8518a9" # (1 unchanged attribute hidden) # (1 unchanged block hidden) @@ -2472,10 +2480,10 @@ func TestResourceChange_nestedSingle(t *testing.T) { Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "root_block_device": cty.NullVal(cty.Object(map[string]cty.Type{ + "root_block_device": cty.ListValEmpty(cty.Object(map[string]cty.Type{ "volume_type": cty.String, })), - "disk": cty.NullVal(cty.Object(map[string]cty.Type{ + "disks": cty.ListValEmpty(cty.Object(map[string]cty.Type{ "mount_point": cty.String, "size": cty.String, })), @@ -2483,26 +2491,141 @@ func TestResourceChange_nestedSingle(t *testing.T) { After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disk": cty.ObjectVal(map[string]cty.Value{ + "disks": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diska"), "size": cty.StringVal("50GB"), + })}), + "root_block_device": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.NullVal(cty.String), + }), }), - "root_block_device": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.NullVal(cty.String), + }), + RequiredReplace: cty.NewPathSet(), + Schema: testSchema(configschema.NestingList), + ExpectedOutput: ` # test_instance.example will be updated in-place + ~ resource "test_instance" "example" { + ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disks = [ + + { + + mount_point = "/var/diska" + + size = "50GB" + }, + ] + id = "i-02ae66f368e8518a9" + + + root_block_device {} + } +`, + }, + "in-place update - first insertion": { + Action: plans.Update, + Mode: addrs.ManagedResourceMode, + Before: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("i-02ae66f368e8518a9"), + "ami": cty.StringVal("ami-BEFORE"), + "root_block_device": cty.ListValEmpty(cty.Object(map[string]cty.Type{ + "volume_type": cty.String, + })), + "disks": cty.ListValEmpty(cty.Object(map[string]cty.Type{ + "mount_point": cty.String, + "size": cty.String, + })), + }), + After: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("i-02ae66f368e8518a9"), + "ami": cty.StringVal("ami-AFTER"), + "disks": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.NullVal(cty.String), + }), + }), + "root_block_device": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + }), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingSingle), + Schema: testSchema(configschema.NestingList), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { - ~ ami = "ami-BEFORE" -> "ami-AFTER" - + disk = { - + mount_point = "/var/diska" - + size = "50GB" + ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disks = [ + + { + + mount_point = "/var/diska" + }, + ] + id = "i-02ae66f368e8518a9" + + + root_block_device { + + volume_type = "gp2" } - id = "i-02ae66f368e8518a9" + } +`, + }, + "in-place update - insertion": { + Action: plans.Update, + Mode: addrs.ManagedResourceMode, + Before: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("i-02ae66f368e8518a9"), + "ami": cty.StringVal("ami-BEFORE"), + "disks": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.NullVal(cty.String), + }), + cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diskb"), + "size": cty.StringVal("50GB"), + }), + }), + "root_block_device": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + "new_field": cty.NullVal(cty.String), + }), + }), + }), + After: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("i-02ae66f368e8518a9"), + "ami": cty.StringVal("ami-AFTER"), + "disks": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), + }), + cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diskb"), + "size": cty.StringVal("50GB"), + }), + }), + "root_block_device": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + "new_field": cty.StringVal("new_value"), + }), + }), + }), + RequiredReplace: cty.NewPathSet(), + Schema: testSchemaPlus(configschema.NestingList), + ExpectedOutput: ` # test_instance.example will be updated in-place + ~ resource "test_instance" "example" { + ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disks = [ + ~ { + + size = "50GB" + # (1 unchanged attribute hidden) + }, + # (1 unchanged element hidden) + ] + id = "i-02ae66f368e8518a9" - + root_block_device {} + ~ root_block_device { + + new_field = "new_value" + # (1 unchanged attribute hidden) + } } `, }, @@ -2513,44 +2636,56 @@ func TestResourceChange_nestedSingle(t *testing.T) { Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disk": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), + "disks": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), + }), }), - "root_block_device": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), + "root_block_device": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + }), }), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disk": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diskb"), - "size": cty.StringVal("50GB"), + "disks": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diskb"), + "size": cty.StringVal("50GB"), + }), }), - "root_block_device": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("different"), + "root_block_device": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("different"), + }), }), }), RequiredReplace: cty.NewPathSet( cty.Path{ cty.GetAttrStep{Name: "root_block_device"}, + cty.IndexStep{Key: cty.NumberIntVal(0)}, cty.GetAttrStep{Name: "volume_type"}, }, cty.Path{ - cty.GetAttrStep{Name: "disk"}, + cty.GetAttrStep{Name: "disks"}, + cty.IndexStep{Key: cty.NumberIntVal(0)}, cty.GetAttrStep{Name: "mount_point"}, }, ), - Schema: testSchema(configschema.NestingSingle), + Schema: testSchema(configschema.NestingList), ExpectedOutput: ` # test_instance.example must be replaced -/+ resource "test_instance" "example" { - ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disk = { - ~ mount_point = "/var/diska" -> "/var/diskb" # forces replacement - # (1 unchanged attribute hidden) - } - id = "i-02ae66f368e8518a9" + ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disks = [ + ~ { + ~ mount_point = "/var/diska" -> "/var/diskb" # forces replacement + # (1 unchanged attribute hidden) + }, + ] + id = "i-02ae66f368e8518a9" ~ root_block_device { ~ volume_type = "gp2" -> "different" # forces replacement @@ -2565,38 +2700,48 @@ func TestResourceChange_nestedSingle(t *testing.T) { Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disk": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), + "disks": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), + }), }), - "root_block_device": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), + "root_block_device": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + }), }), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disk": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diskb"), - "size": cty.StringVal("50GB"), + "disks": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diskb"), + "size": cty.StringVal("50GB"), + }), }), - "root_block_device": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("different"), + "root_block_device": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("different"), + }), }), }), RequiredReplace: cty.NewPathSet( cty.Path{cty.GetAttrStep{Name: "root_block_device"}}, - cty.Path{cty.GetAttrStep{Name: "disk"}}, + cty.Path{cty.GetAttrStep{Name: "disks"}}, ), - Schema: testSchema(configschema.NestingSingle), + Schema: testSchema(configschema.NestingList), ExpectedOutput: ` # test_instance.example must be replaced -/+ resource "test_instance" "example" { - ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disk = { # forces replacement - ~ mount_point = "/var/diska" -> "/var/diskb" - # (1 unchanged attribute hidden) - } - id = "i-02ae66f368e8518a9" + ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disks = [ # forces replacement + ~ { + ~ mount_point = "/var/diska" -> "/var/diskb" + # (1 unchanged attribute hidden) + }, + ] + id = "i-02ae66f368e8518a9" ~ root_block_device { # forces replacement ~ volume_type = "gp2" -> "different" @@ -2610,35 +2755,41 @@ func TestResourceChange_nestedSingle(t *testing.T) { Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disk": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), + "disks": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), + }), }), - "root_block_device": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), + "root_block_device": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + }), }), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "root_block_device": cty.NullVal(cty.Object(map[string]cty.Type{ - "volume_type": cty.String, - })), - "disk": cty.NullVal(cty.Object(map[string]cty.Type{ + "disks": cty.ListValEmpty(cty.Object(map[string]cty.Type{ "mount_point": cty.String, "size": cty.String, })), + "root_block_device": cty.ListValEmpty(cty.Object(map[string]cty.Type{ + "volume_type": cty.String, + })), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingSingle), + Schema: testSchema(configschema.NestingList), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { - ~ ami = "ami-BEFORE" -> "ami-AFTER" - - disk = { - - mount_point = "/var/diska" -> null - - size = "50GB" -> null - } -> null - id = "i-02ae66f368e8518a9" + ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disks = [ + - { + - mount_point = "/var/diska" -> null + - size = "50GB" -> null + }, + ] + id = "i-02ae66f368e8518a9" - root_block_device { - volume_type = "gp2" -> null @@ -2650,13 +2801,16 @@ func TestResourceChange_nestedSingle(t *testing.T) { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ - "block": cty.NullVal(cty.Object(map[string]cty.Type{ - "attr": cty.String, - })), + "block": cty.EmptyTupleVal, }), After: cty.ObjectVal(map[string]cty.Value{ - "block": cty.ObjectVal(map[string]cty.Value{ - "attr": cty.StringVal("foo"), + "block": cty.TupleVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "attr": cty.StringVal("foo"), + }), + cty.ObjectVal(map[string]cty.Value{ + "attr": cty.True, + }), }), }), RequiredReplace: cty.NewPathSet(), @@ -2668,7 +2822,7 @@ func TestResourceChange_nestedSingle(t *testing.T) { "attr": {Type: cty.DynamicPseudoType, Optional: true}, }, }, - Nesting: configschema.NestingSingle, + Nesting: configschema.NestingList, }, }, }, @@ -2677,233 +2831,375 @@ func TestResourceChange_nestedSingle(t *testing.T) { + block { + attr = "foo" } + + block { + + attr = true + } } `, }, - "in-place update - unknown": { + "in-place sequence update - deletion": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ - "id": cty.StringVal("i-02ae66f368e8518a9"), - "ami": cty.StringVal("ami-BEFORE"), - "disk": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), - }), - "root_block_device": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - "new_field": cty.StringVal("new_value"), + "list": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{"attr": cty.StringVal("x")}), + cty.ObjectVal(map[string]cty.Value{"attr": cty.StringVal("y")}), }), }), After: cty.ObjectVal(map[string]cty.Value{ - "id": cty.StringVal("i-02ae66f368e8518a9"), - "ami": cty.StringVal("ami-AFTER"), - "disk": cty.UnknownVal(cty.Object(map[string]cty.Type{ - "mount_point": cty.String, - "size": cty.String, - })), - "root_block_device": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - "new_field": cty.StringVal("new_value"), + "list": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{"attr": cty.StringVal("y")}), + cty.ObjectVal(map[string]cty.Value{"attr": cty.StringVal("z")}), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaPlus(configschema.NestingSingle), + Schema: &configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + "list": { + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "attr": { + Type: cty.String, + Required: true, + }, + }, + }, + Nesting: configschema.NestingList, + }, + }, + }, ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { - ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disk = { - ~ mount_point = "/var/diska" -> (known after apply) - ~ size = "50GB" -> (known after apply) - } -> (known after apply) - id = "i-02ae66f368e8518a9" - - # (1 unchanged block hidden) + ~ list { + ~ attr = "x" -> "y" + } + ~ list { + ~ attr = "y" -> "z" + } } `, }, - "in-place update - modification": { + "in-place update - unknown": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disk": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), + "disks": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), + }), }), - "root_block_device": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - "new_field": cty.StringVal("new_value"), + "root_block_device": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + "new_field": cty.StringVal("new_value"), + }), }), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disk": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("25GB"), - }), - "root_block_device": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - "new_field": cty.StringVal("new_value"), + "disks": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{ + "mount_point": cty.String, + "size": cty.String, + }))), + "root_block_device": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + "new_field": cty.StringVal("new_value"), + }), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaPlus(configschema.NestingSingle), + Schema: testSchemaPlus(configschema.NestingList), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { - ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disk = { - ~ size = "50GB" -> "25GB" - # (1 unchanged attribute hidden) - } - id = "i-02ae66f368e8518a9" + ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disks = [ + - { + - mount_point = "/var/diska" -> null + - size = "50GB" -> null + }, + ] -> (known after apply) + id = "i-02ae66f368e8518a9" # (1 unchanged block hidden) } `, }, - } - runTestCases(t, testCases) -} - -func TestResourceChange_nestedList(t *testing.T) { - testCases := map[string]testCase{ - "in-place update - equal": { + "in-place update - modification": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "root_block_device": cty.ListVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), - }), "disks": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diska"), "size": cty.StringVal("50GB"), }), + cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diskb"), + "size": cty.StringVal("50GB"), + }), + cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diskc"), + "size": cty.StringVal("50GB"), + }), }), - }), - After: cty.ObjectVal(map[string]cty.Value{ - "id": cty.StringVal("i-02ae66f368e8518a9"), - "ami": cty.StringVal("ami-AFTER"), "root_block_device": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), + "new_field": cty.StringVal("new_value"), }), }), + }), + After: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("i-02ae66f368e8518a9"), + "ami": cty.StringVal("ami-AFTER"), "disks": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diska"), "size": cty.StringVal("50GB"), }), + cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diskb"), + "size": cty.StringVal("75GB"), + }), + cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diskc"), + "size": cty.StringVal("25GB"), + }), + }), + "root_block_device": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + "new_field": cty.StringVal("new_value"), + }), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingList), + Schema: testSchemaPlus(configschema.NestingList), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disks = [ + ~ { + ~ size = "50GB" -> "75GB" + # (1 unchanged attribute hidden) + }, + ~ { + ~ size = "50GB" -> "25GB" + # (1 unchanged attribute hidden) + }, + # (1 unchanged element hidden) + ] id = "i-02ae66f368e8518a9" - # (1 unchanged attribute hidden) # (1 unchanged block hidden) } `, }, - "in-place update - creation": { - Action: plans.Update, + } + runTestCases(t, testCases) +} + +func TestResourceChange_nestedSet(t *testing.T) { + testCases := map[string]testCase{ + "creation from null - sensitive set": { + Action: plans.Create, Mode: addrs.ManagedResourceMode, - Before: cty.ObjectVal(map[string]cty.Value{ - "id": cty.StringVal("i-02ae66f368e8518a9"), - "ami": cty.StringVal("ami-BEFORE"), - "root_block_device": cty.ListValEmpty(cty.Object(map[string]cty.Type{ - "volume_type": cty.String, - })), - "disks": cty.ListValEmpty(cty.Object(map[string]cty.Type{ + Before: cty.NullVal(cty.Object(map[string]cty.Type{ + "id": cty.String, + "ami": cty.String, + "disks": cty.Set(cty.Object(map[string]cty.Type{ "mount_point": cty.String, "size": cty.String, })), - }), - After: cty.ObjectVal(map[string]cty.Value{ + "root_block_device": cty.Set(cty.Object(map[string]cty.Type{ + "volume_type": cty.String, + })), + })), + After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disks": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), - })}), - "root_block_device": cty.ListVal([]cty.Value{ + "disks": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.NullVal(cty.String), + "mount_point": cty.StringVal("/var/diska"), + "size": cty.NullVal(cty.String), + }), + }), + "root_block_device": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), }), }), }), + AfterValMarks: []cty.PathValueMarks{ + { + Path: cty.Path{cty.GetAttrStep{Name: "disks"}}, + Marks: cty.NewValueMarks(marks.Sensitive), + }, + }, RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingList), + Schema: testSchema(configschema.NestingSet), + ExpectedOutput: ` # test_instance.example will be created + + resource "test_instance" "example" { + + ami = "ami-AFTER" + + disks = (sensitive value) + + id = "i-02ae66f368e8518a9" + + + root_block_device { + + volume_type = "gp2" + } + } +`, + }, + "in-place update - creation": { + Action: plans.Update, + Mode: addrs.ManagedResourceMode, + Before: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("i-02ae66f368e8518a9"), + "ami": cty.StringVal("ami-BEFORE"), + "disks": cty.SetValEmpty(cty.Object(map[string]cty.Type{ + "mount_point": cty.String, + "size": cty.String, + })), + "root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{ + "volume_type": cty.String, + })), + }), + After: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("i-02ae66f368e8518a9"), + "ami": cty.StringVal("ami-AFTER"), + "disks": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.NullVal(cty.String), + }), + }), + "root_block_device": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + }), + }), + }), + RequiredReplace: cty.NewPathSet(), + Schema: testSchema(configschema.NestingSet), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" ~ disks = [ + { + mount_point = "/var/diska" - + size = "50GB" }, ] id = "i-02ae66f368e8518a9" - + root_block_device {} + + root_block_device { + + volume_type = "gp2" + } } `, }, - "in-place update - first insertion": { + "in-place update - creation - sensitive set": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "root_block_device": cty.ListValEmpty(cty.Object(map[string]cty.Type{ - "volume_type": cty.String, - })), - "disks": cty.ListValEmpty(cty.Object(map[string]cty.Type{ + "disks": cty.SetValEmpty(cty.Object(map[string]cty.Type{ "mount_point": cty.String, "size": cty.String, })), + "root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{ + "volume_type": cty.String, + })), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disks": cty.ListVal([]cty.Value{ + "disks": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diska"), "size": cty.NullVal(cty.String), }), }), - "root_block_device": cty.ListVal([]cty.Value{ + "root_block_device": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), }), }), }), + AfterValMarks: []cty.PathValueMarks{ + { + Path: cty.Path{cty.GetAttrStep{Name: "disks"}}, + Marks: cty.NewValueMarks(marks.Sensitive), + }, + }, RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingList), + Schema: testSchema(configschema.NestingSet), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disks = [ - + { - + mount_point = "/var/diska" - }, - ] + # Warning: this attribute value will be marked as sensitive and will not + # display in UI output after applying this change. + ~ disks = (sensitive value) id = "i-02ae66f368e8518a9" + root_block_device { + volume_type = "gp2" } } +`, + }, + "in-place update - marking set sensitive": { + Action: plans.Update, + Mode: addrs.ManagedResourceMode, + Before: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("i-02ae66f368e8518a9"), + "ami": cty.StringVal("ami-BEFORE"), + "disks": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), + }), + }), + "root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{ + "volume_type": cty.String, + })), + }), + After: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("i-02ae66f368e8518a9"), + "ami": cty.StringVal("ami-AFTER"), + "disks": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), + }), + }), + "root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{ + "volume_type": cty.String, + })), + }), + AfterValMarks: []cty.PathValueMarks{ + { + Path: cty.Path{cty.GetAttrStep{Name: "disks"}}, + Marks: cty.NewValueMarks(marks.Sensitive), + }, + }, + RequiredReplace: cty.NewPathSet(), + Schema: testSchema(configschema.NestingSet), + ExpectedOutput: ` # test_instance.example will be updated in-place + ~ resource "test_instance" "example" { + ~ ami = "ami-BEFORE" -> "ami-AFTER" + # Warning: this attribute value will be marked as sensitive and will not + # display in UI output after applying this change. The value is unchanged. + ~ disks = (sensitive value) + id = "i-02ae66f368e8518a9" + } `, }, "in-place update - insertion": { @@ -2912,17 +3208,17 @@ func TestResourceChange_nestedList(t *testing.T) { Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.ListVal([]cty.Value{ + "disks": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diska"), "size": cty.NullVal(cty.String), }), cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diskb"), - "size": cty.StringVal("50GB"), + "size": cty.StringVal("100GB"), }), }), - "root_block_device": cty.ListVal([]cty.Value{ + "root_block_device": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), "new_field": cty.NullVal(cty.String), @@ -2932,17 +3228,17 @@ func TestResourceChange_nestedList(t *testing.T) { After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disks": cty.ListVal([]cty.Value{ + "disks": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diska"), "size": cty.StringVal("50GB"), }), cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diskb"), - "size": cty.StringVal("50GB"), + "size": cty.StringVal("100GB"), }), }), - "root_block_device": cty.ListVal([]cty.Value{ + "root_block_device": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), "new_field": cty.StringVal("new_value"), @@ -2950,272 +3246,234 @@ func TestResourceChange_nestedList(t *testing.T) { }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaPlus(configschema.NestingList), + Schema: testSchemaPlus(configschema.NestingSet), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" ~ disks = [ - ~ { + + { + + mount_point = "/var/diska" + size = "50GB" - # (1 unchanged attribute hidden) + }, + - { + - mount_point = "/var/diska" -> null }, # (1 unchanged element hidden) ] id = "i-02ae66f368e8518a9" - ~ root_block_device { + + root_block_device { + new_field = "new_value" - # (1 unchanged attribute hidden) + + volume_type = "gp2" + } + - root_block_device { + - volume_type = "gp2" -> null } } `, }, - "force-new update (inside blocks)": { + "force-new update (whole block)": { Action: plans.DeleteThenCreate, ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.ListVal([]cty.Value{ + "root_block_device": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), + "volume_type": cty.StringVal("gp2"), }), }), - "root_block_device": cty.ListVal([]cty.Value{ + "disks": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), }), }), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disks": cty.ListVal([]cty.Value{ + "root_block_device": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diskb"), - "size": cty.StringVal("50GB"), + "volume_type": cty.StringVal("different"), }), }), - "root_block_device": cty.ListVal([]cty.Value{ + "disks": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("different"), + "mount_point": cty.StringVal("/var/diskb"), + "size": cty.StringVal("50GB"), }), }), }), RequiredReplace: cty.NewPathSet( - cty.Path{ - cty.GetAttrStep{Name: "root_block_device"}, - cty.IndexStep{Key: cty.NumberIntVal(0)}, - cty.GetAttrStep{Name: "volume_type"}, - }, - cty.Path{ - cty.GetAttrStep{Name: "disks"}, - cty.IndexStep{Key: cty.NumberIntVal(0)}, - cty.GetAttrStep{Name: "mount_point"}, - }, + cty.Path{cty.GetAttrStep{Name: "root_block_device"}}, + cty.Path{cty.GetAttrStep{Name: "disks"}}, ), - Schema: testSchema(configschema.NestingList), + Schema: testSchema(configschema.NestingSet), ExpectedOutput: ` # test_instance.example must be replaced -/+ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" ~ disks = [ - ~ { - ~ mount_point = "/var/diska" -> "/var/diskb" # forces replacement - # (1 unchanged attribute hidden) + - { # forces replacement + - mount_point = "/var/diska" -> null + - size = "50GB" -> null + }, + + { # forces replacement + + mount_point = "/var/diskb" + + size = "50GB" }, ] id = "i-02ae66f368e8518a9" - ~ root_block_device { - ~ volume_type = "gp2" -> "different" # forces replacement + + root_block_device { # forces replacement + + volume_type = "different" + } + - root_block_device { # forces replacement + - volume_type = "gp2" -> null } } `, }, - "force-new update (whole block)": { - Action: plans.DeleteThenCreate, - ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate, - Mode: addrs.ManagedResourceMode, + "in-place update - deletion": { + Action: plans.Update, + Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.ListVal([]cty.Value{ + "root_block_device": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), + "volume_type": cty.StringVal("gp2"), + "new_field": cty.StringVal("new_value"), }), }), - "root_block_device": cty.ListVal([]cty.Value{ + "disks": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), }), }), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disks": cty.ListVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diskb"), - "size": cty.StringVal("50GB"), - }), - }), - "root_block_device": cty.ListVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("different"), - }), - }), + "root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{ + "volume_type": cty.String, + "new_field": cty.String, + })), + "disks": cty.SetValEmpty(cty.Object(map[string]cty.Type{ + "mount_point": cty.String, + "size": cty.String, + })), }), - RequiredReplace: cty.NewPathSet( - cty.Path{cty.GetAttrStep{Name: "root_block_device"}}, - cty.Path{cty.GetAttrStep{Name: "disks"}}, - ), - Schema: testSchema(configschema.NestingList), - ExpectedOutput: ` # test_instance.example must be replaced --/+ resource "test_instance" "example" { + RequiredReplace: cty.NewPathSet(), + Schema: testSchemaPlus(configschema.NestingSet), + ExpectedOutput: ` # test_instance.example will be updated in-place + ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disks = [ # forces replacement - ~ { - ~ mount_point = "/var/diska" -> "/var/diskb" - # (1 unchanged attribute hidden) + ~ disks = [ + - { + - mount_point = "/var/diska" -> null + - size = "50GB" -> null }, ] id = "i-02ae66f368e8518a9" - ~ root_block_device { # forces replacement - ~ volume_type = "gp2" -> "different" + - root_block_device { + - new_field = "new_value" -> null + - volume_type = "gp2" -> null } } `, }, - "in-place update - deletion": { + "in-place update - empty nested sets": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.ListVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), - }), - }), - "root_block_device": cty.ListVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), - }), + "disks": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ + "mount_point": cty.String, + "size": cty.String, + }))), + "root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{ + "volume_type": cty.String, + })), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disks": cty.ListValEmpty(cty.Object(map[string]cty.Type{ + "disks": cty.SetValEmpty(cty.Object(map[string]cty.Type{ "mount_point": cty.String, "size": cty.String, })), - "root_block_device": cty.ListValEmpty(cty.Object(map[string]cty.Type{ + "root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{ "volume_type": cty.String, })), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingList), + Schema: testSchema(configschema.NestingSet), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disks = [ - - { - - mount_point = "/var/diska" -> null - - size = "50GB" -> null - }, + + disks = [ ] id = "i-02ae66f368e8518a9" - - - root_block_device { - - volume_type = "gp2" -> null - } } `, }, - "with dynamically-typed attribute": { + "in-place update - null insertion": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ - "block": cty.EmptyTupleVal, + "id": cty.StringVal("i-02ae66f368e8518a9"), + "ami": cty.StringVal("ami-BEFORE"), + "disks": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ + "mount_point": cty.String, + "size": cty.String, + }))), + "root_block_device": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + "new_field": cty.NullVal(cty.String), + }), + }), }), After: cty.ObjectVal(map[string]cty.Value{ - "block": cty.TupleVal([]cty.Value{ + "id": cty.StringVal("i-02ae66f368e8518a9"), + "ami": cty.StringVal("ami-AFTER"), + "disks": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ - "attr": cty.StringVal("foo"), + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), }), + }), + "root_block_device": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ - "attr": cty.True, + "volume_type": cty.StringVal("gp2"), + "new_field": cty.StringVal("new_value"), }), }), }), RequiredReplace: cty.NewPathSet(), - Schema: &configschema.Block{ - BlockTypes: map[string]*configschema.NestedBlock{ - "block": { - Block: configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "attr": {Type: cty.DynamicPseudoType, Optional: true}, - }, - }, - Nesting: configschema.NestingList, - }, - }, - }, - ExpectedOutput: ` # test_instance.example will be updated in-place - ~ resource "test_instance" "example" { - + block { - + attr = "foo" - } - + block { - + attr = true - } - } -`, - }, - "in-place sequence update - deletion": { - Action: plans.Update, - Mode: addrs.ManagedResourceMode, - Before: cty.ObjectVal(map[string]cty.Value{ - "list": cty.ListVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{"attr": cty.StringVal("x")}), - cty.ObjectVal(map[string]cty.Value{"attr": cty.StringVal("y")}), - }), - }), - After: cty.ObjectVal(map[string]cty.Value{ - "list": cty.ListVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{"attr": cty.StringVal("y")}), - cty.ObjectVal(map[string]cty.Value{"attr": cty.StringVal("z")}), - }), - }), - RequiredReplace: cty.NewPathSet(), - Schema: &configschema.Block{ - BlockTypes: map[string]*configschema.NestedBlock{ - "list": { - Block: configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "attr": { - Type: cty.String, - Required: true, - }, - }, - }, - Nesting: configschema.NestingList, - }, - }, - }, + Schema: testSchemaPlus(configschema.NestingSet), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { - ~ list { - ~ attr = "x" -> "y" + ~ ami = "ami-BEFORE" -> "ami-AFTER" + + disks = [ + + { + + mount_point = "/var/diska" + + size = "50GB" + }, + ] + id = "i-02ae66f368e8518a9" + + + root_block_device { + + new_field = "new_value" + + volume_type = "gp2" } - ~ list { - ~ attr = "y" -> "z" + - root_block_device { + - volume_type = "gp2" -> null } } `, @@ -3226,13 +3484,13 @@ func TestResourceChange_nestedList(t *testing.T) { Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.ListVal([]cty.Value{ + "disks": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diska"), "size": cty.StringVal("50GB"), }), }), - "root_block_device": cty.ListVal([]cty.Value{ + "root_block_device": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), "new_field": cty.StringVal("new_value"), @@ -3242,11 +3500,11 @@ func TestResourceChange_nestedList(t *testing.T) { After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disks": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{ + "disks": cty.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{ "mount_point": cty.String, "size": cty.String, }))), - "root_block_device": cty.ListVal([]cty.Value{ + "root_block_device": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), "new_field": cty.StringVal("new_value"), @@ -3254,7 +3512,7 @@ func TestResourceChange_nestedList(t *testing.T) { }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaPlus(configschema.NestingList), + Schema: testSchemaPlus(configschema.NestingSet), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" @@ -3270,129 +3528,54 @@ func TestResourceChange_nestedList(t *testing.T) { } `, }, - "in-place update - modification": { + } + runTestCases(t, testCases) +} + +func TestResourceChange_nestedMap(t *testing.T) { + testCases := map[string]testCase{ + "creation from null": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ - "id": cty.StringVal("i-02ae66f368e8518a9"), - "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.ListVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), - }), - cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diskb"), - "size": cty.StringVal("50GB"), - }), - cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diskc"), - "size": cty.StringVal("50GB"), - }), - }), - "root_block_device": cty.ListVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - "new_field": cty.StringVal("new_value"), - }), - }), + "id": cty.NullVal(cty.String), + "ami": cty.NullVal(cty.String), + "disks": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{ + "mount_point": cty.String, + "size": cty.String, + }))), + "root_block_device": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{ + "volume_type": cty.String, + }))), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disks": cty.ListVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ + "disks": cty.MapVal(map[string]cty.Value{ + "disk_a": cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), - }), - cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diskb"), - "size": cty.StringVal("75GB"), - }), - cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diskc"), - "size": cty.StringVal("25GB"), + "size": cty.NullVal(cty.String), }), }), - "root_block_device": cty.ListVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ + "root_block_device": cty.MapVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), - "new_field": cty.StringVal("new_value"), }), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaPlus(configschema.NestingList), + Schema: testSchema(configschema.NestingMap), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { - ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disks = [ - ~ { - ~ size = "50GB" -> "75GB" - # (1 unchanged attribute hidden) + + ami = "ami-AFTER" + + disks = { + + "disk_a" = { + + mount_point = "/var/diska" }, - ~ { - ~ size = "50GB" -> "25GB" - # (1 unchanged attribute hidden) - }, - # (1 unchanged element hidden) - ] - id = "i-02ae66f368e8518a9" - - # (1 unchanged block hidden) - } -`, - }, - } - runTestCases(t, testCases) -} - -func TestResourceChange_nestedSet(t *testing.T) { - testCases := map[string]testCase{ - "creation from null - sensitive set": { - Action: plans.Create, - Mode: addrs.ManagedResourceMode, - Before: cty.NullVal(cty.Object(map[string]cty.Type{ - "id": cty.String, - "ami": cty.String, - "disks": cty.Set(cty.Object(map[string]cty.Type{ - "mount_point": cty.String, - "size": cty.String, - })), - "root_block_device": cty.Set(cty.Object(map[string]cty.Type{ - "volume_type": cty.String, - })), - })), - After: cty.ObjectVal(map[string]cty.Value{ - "id": cty.StringVal("i-02ae66f368e8518a9"), - "ami": cty.StringVal("ami-AFTER"), - "disks": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.NullVal(cty.String), - }), - }), - "root_block_device": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), - }), - }), - AfterValMarks: []cty.PathValueMarks{ - { - Path: cty.Path{cty.GetAttrStep{Name: "disks"}}, - Marks: cty.NewValueMarks(marks.Sensitive), - }, - }, - RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingSet), - ExpectedOutput: ` # test_instance.example will be created - + resource "test_instance" "example" { - + ami = "ami-AFTER" - + disks = (sensitive value) + } + id = "i-02ae66f368e8518a9" - + root_block_device { + + root_block_device "a" { + volume_type = "gp2" } } @@ -3404,142 +3587,99 @@ func TestResourceChange_nestedSet(t *testing.T) { Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.SetValEmpty(cty.Object(map[string]cty.Type{ + "disks": cty.MapValEmpty(cty.Object(map[string]cty.Type{ "mount_point": cty.String, "size": cty.String, })), - "root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{ + "root_block_device": cty.MapValEmpty(cty.Object(map[string]cty.Type{ "volume_type": cty.String, })), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disks": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ + "disks": cty.MapVal(map[string]cty.Value{ + "disk_a": cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diska"), "size": cty.NullVal(cty.String), }), }), - "root_block_device": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ + "root_block_device": cty.MapVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), }), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingSet), + Schema: testSchema(configschema.NestingMap), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disks = [ - + { + ~ disks = { + + "disk_a" = { + mount_point = "/var/diska" }, - ] + } id = "i-02ae66f368e8518a9" - + root_block_device { + + root_block_device "a" { + volume_type = "gp2" } } `, }, - "in-place update - creation - sensitive set": { + "in-place update - change attr": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.SetValEmpty(cty.Object(map[string]cty.Type{ - "mount_point": cty.String, - "size": cty.String, - })), - "root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{ - "volume_type": cty.String, - })), - }), - After: cty.ObjectVal(map[string]cty.Value{ - "id": cty.StringVal("i-02ae66f368e8518a9"), - "ami": cty.StringVal("ami-AFTER"), - "disks": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ + "disks": cty.MapVal(map[string]cty.Value{ + "disk_a": cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diska"), "size": cty.NullVal(cty.String), }), }), - "root_block_device": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ + "root_block_device": cty.MapVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), + "new_field": cty.NullVal(cty.String), }), }), }), - AfterValMarks: []cty.PathValueMarks{ - { - Path: cty.Path{cty.GetAttrStep{Name: "disks"}}, - Marks: cty.NewValueMarks(marks.Sensitive), - }, - }, - RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingSet), - ExpectedOutput: ` # test_instance.example will be updated in-place - ~ resource "test_instance" "example" { - ~ ami = "ami-BEFORE" -> "ami-AFTER" - # Warning: this attribute value will be marked as sensitive and will not - # display in UI output after applying this change. - ~ disks = (sensitive value) - id = "i-02ae66f368e8518a9" - - + root_block_device { - + volume_type = "gp2" - } - } -`, - }, - "in-place update - marking set sensitive": { - Action: plans.Update, - Mode: addrs.ManagedResourceMode, - Before: cty.ObjectVal(map[string]cty.Value{ - "id": cty.StringVal("i-02ae66f368e8518a9"), - "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), - }), - }), - "root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{ - "volume_type": cty.String, - })), - }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disks": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ + "disks": cty.MapVal(map[string]cty.Value{ + "disk_a": cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diska"), "size": cty.StringVal("50GB"), }), }), - "root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{ - "volume_type": cty.String, - })), + "root_block_device": cty.MapVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + "new_field": cty.StringVal("new_value"), + }), + }), }), - AfterValMarks: []cty.PathValueMarks{ - { - Path: cty.Path{cty.GetAttrStep{Name: "disks"}}, - Marks: cty.NewValueMarks(marks.Sensitive), - }, - }, RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingSet), + Schema: testSchemaPlus(configschema.NestingMap), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" - # Warning: this attribute value will be marked as sensitive and will not - # display in UI output after applying this change. The value is unchanged. - ~ disks = (sensitive value) + ~ disks = { + ~ "disk_a" = { + + size = "50GB" + # (1 unchanged attribute hidden) + }, + } id = "i-02ae66f368e8518a9" + + ~ root_block_device "a" { + + new_field = "new_value" + # (1 unchanged attribute hidden) + } } `, }, @@ -3549,18 +3689,14 @@ func TestResourceChange_nestedSet(t *testing.T) { Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ + "disks": cty.MapVal(map[string]cty.Value{ + "disk_a": cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diska"), - "size": cty.NullVal(cty.String), - }), - cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diskb"), - "size": cty.StringVal("100GB"), + "size": cty.StringVal("50GB"), }), }), - "root_block_device": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ + "root_block_device": cty.MapVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), "new_field": cty.NullVal(cty.String), }), @@ -3569,47 +3705,47 @@ func TestResourceChange_nestedSet(t *testing.T) { After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disks": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ + "disks": cty.MapVal(map[string]cty.Value{ + "disk_a": cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diska"), "size": cty.StringVal("50GB"), }), - cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diskb"), - "size": cty.StringVal("100GB"), + "disk_2": cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/disk2"), + "size": cty.StringVal("50GB"), }), }), - "root_block_device": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ + "root_block_device": cty.MapVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + "new_field": cty.NullVal(cty.String), + }), + "b": cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), "new_field": cty.StringVal("new_value"), }), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaPlus(configschema.NestingSet), + Schema: testSchemaPlus(configschema.NestingMap), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disks = [ - + { - + mount_point = "/var/diska" + ~ disks = { + + "disk_2" = { + + mount_point = "/var/disk2" + size = "50GB" }, - - { - - mount_point = "/var/diska" -> null - }, # (1 unchanged element hidden) - ] + } id = "i-02ae66f368e8518a9" - + root_block_device { + + root_block_device "b" { + new_field = "new_value" + volume_type = "gp2" } - - root_block_device { - - volume_type = "gp2" -> null - } + + # (1 unchanged block hidden) } `, }, @@ -3620,59 +3756,62 @@ func TestResourceChange_nestedSet(t *testing.T) { Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "root_block_device": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), - }), - "disks": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ + "disks": cty.MapVal(map[string]cty.Value{ + "disk_a": cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diska"), "size": cty.StringVal("50GB"), }), }), + "root_block_device": cty.MapVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + }), + "b": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("standard"), + }), + }), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "root_block_device": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("different"), + "disks": cty.MapVal(map[string]cty.Value{ + "disk_a": cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("100GB"), }), }), - "disks": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diskb"), - "size": cty.StringVal("50GB"), + "root_block_device": cty.MapVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("different"), + }), + "b": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("standard"), }), }), }), - RequiredReplace: cty.NewPathSet( - cty.Path{cty.GetAttrStep{Name: "root_block_device"}}, + RequiredReplace: cty.NewPathSet(cty.Path{ + cty.GetAttrStep{Name: "root_block_device"}, + cty.IndexStep{Key: cty.StringVal("a")}, + }, cty.Path{cty.GetAttrStep{Name: "disks"}}, ), - Schema: testSchema(configschema.NestingSet), + Schema: testSchema(configschema.NestingMap), ExpectedOutput: ` # test_instance.example must be replaced -/+ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disks = [ - - { # forces replacement - - mount_point = "/var/diska" -> null - - size = "50GB" -> null - }, - + { # forces replacement - + mount_point = "/var/diskb" - + size = "50GB" + ~ disks = { + ~ "disk_a" = { # forces replacement + ~ size = "50GB" -> "100GB" + # (1 unchanged attribute hidden) }, - ] + } id = "i-02ae66f368e8518a9" - + root_block_device { # forces replacement - + volume_type = "different" - } - - root_block_device { # forces replacement - - volume_type = "gp2" -> null + ~ root_block_device "a" { # forces replacement + ~ volume_type = "gp2" -> "different" } + + # (1 unchanged block hidden) } `, }, @@ -3682,212 +3821,231 @@ func TestResourceChange_nestedSet(t *testing.T) { Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "root_block_device": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - "new_field": cty.StringVal("new_value"), - }), - }), - "disks": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ + "disks": cty.MapVal(map[string]cty.Value{ + "disk_a": cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diska"), "size": cty.StringVal("50GB"), }), }), + "root_block_device": cty.MapVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + "new_field": cty.StringVal("new_value"), + }), + }), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{ - "volume_type": cty.String, - "new_field": cty.String, - })), - "disks": cty.SetValEmpty(cty.Object(map[string]cty.Type{ + "disks": cty.MapValEmpty(cty.Object(map[string]cty.Type{ "mount_point": cty.String, "size": cty.String, })), + "root_block_device": cty.MapValEmpty(cty.Object(map[string]cty.Type{ + "volume_type": cty.String, + "new_field": cty.String, + })), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaPlus(configschema.NestingSet), + Schema: testSchemaPlus(configschema.NestingMap), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disks = [ - - { + ~ disks = { + - "disk_a" = { - mount_point = "/var/diska" -> null - size = "50GB" -> null }, - ] + } id = "i-02ae66f368e8518a9" - - root_block_device { + - root_block_device "a" { - new_field = "new_value" -> null - volume_type = "gp2" -> null } } `, }, - "in-place update - empty nested sets": { + "in-place update - unknown": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ - "mount_point": cty.String, - "size": cty.String, - }))), - "root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{ - "volume_type": cty.String, - })), + "disks": cty.MapVal(map[string]cty.Value{ + "disk_a": cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), + }), + }), + "root_block_device": cty.MapVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + "new_field": cty.StringVal("new_value"), + }), + }), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disks": cty.SetValEmpty(cty.Object(map[string]cty.Type{ + "disks": cty.UnknownVal(cty.Map(cty.Object(map[string]cty.Type{ "mount_point": cty.String, "size": cty.String, - })), - "root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{ - "volume_type": cty.String, - })), + }))), + "root_block_device": cty.MapVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + "new_field": cty.StringVal("new_value"), + }), + }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingSet), + Schema: testSchemaPlus(configschema.NestingMap), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" - + disks = [ - ] + ~ disks = { + - "disk_a" = { + - mount_point = "/var/diska" -> null + - size = "50GB" -> null + }, + } -> (known after apply) id = "i-02ae66f368e8518a9" + + # (1 unchanged block hidden) } `, }, - "in-place update - null insertion": { + "in-place update - insertion sensitive": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ + "disks": cty.MapValEmpty(cty.Object(map[string]cty.Type{ "mount_point": cty.String, "size": cty.String, - }))), - "root_block_device": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ + })), + "root_block_device": cty.MapVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), - "new_field": cty.NullVal(cty.String), + "new_field": cty.StringVal("new_value"), }), }), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disks": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ + "disks": cty.MapVal(map[string]cty.Value{ + "disk_a": cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diska"), "size": cty.StringVal("50GB"), }), }), - "root_block_device": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ + "root_block_device": cty.MapVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), "new_field": cty.StringVal("new_value"), }), }), }), + AfterValMarks: []cty.PathValueMarks{ + { + Path: cty.Path{cty.GetAttrStep{Name: "disks"}, + cty.IndexStep{Key: cty.StringVal("disk_a")}, + cty.GetAttrStep{Name: "mount_point"}, + }, + Marks: cty.NewValueMarks(marks.Sensitive), + }, + }, RequiredReplace: cty.NewPathSet(), - Schema: testSchemaPlus(configschema.NestingSet), + Schema: testSchemaPlus(configschema.NestingMap), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" - + disks = [ - + { - + mount_point = "/var/diska" + ~ disks = { + + "disk_a" = { + + mount_point = (sensitive value) + size = "50GB" }, - ] + } id = "i-02ae66f368e8518a9" - + root_block_device { - + new_field = "new_value" - + volume_type = "gp2" - } - - root_block_device { - - volume_type = "gp2" -> null - } + # (1 unchanged block hidden) } `, }, - "in-place update - unknown": { + "in-place update - multiple unchanged blocks": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ + "disks": cty.MapVal(map[string]cty.Value{ + "disk_a": cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diska"), "size": cty.StringVal("50GB"), }), }), - "root_block_device": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ + "root_block_device": cty.MapVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + }), + "b": cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), - "new_field": cty.StringVal("new_value"), }), }), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disks": cty.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{ - "mount_point": cty.String, - "size": cty.String, - }))), - "root_block_device": cty.SetVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ + "disks": cty.MapVal(map[string]cty.Value{ + "disk_a": cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), + }), + }), + "root_block_device": cty.MapVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + }), + "b": cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), - "new_field": cty.StringVal("new_value"), }), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaPlus(configschema.NestingSet), + Schema: testSchema(configschema.NestingMap), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disks = [ - - { - - mount_point = "/var/diska" -> null - - size = "50GB" -> null - }, - ] -> (known after apply) id = "i-02ae66f368e8518a9" + # (1 unchanged attribute hidden) - # (1 unchanged block hidden) + # (2 unchanged blocks hidden) } `, }, - } - runTestCases(t, testCases) -} - -func TestResourceChange_nestedMap(t *testing.T) { - testCases := map[string]testCase{ - "creation from null": { + "in-place update - multiple blocks first changed": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ - "id": cty.NullVal(cty.String), - "ami": cty.NullVal(cty.String), - "disks": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{ - "mount_point": cty.String, - "size": cty.String, - }))), - "root_block_device": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{ - "volume_type": cty.String, - }))), + "id": cty.StringVal("i-02ae66f368e8518a9"), + "ami": cty.StringVal("ami-BEFORE"), + "disks": cty.MapVal(map[string]cty.Value{ + "disk_a": cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), + }), + }), + "root_block_device": cty.MapVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + }), + "b": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + }), + }), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), @@ -3895,46 +4053,54 @@ func TestResourceChange_nestedMap(t *testing.T) { "disks": cty.MapVal(map[string]cty.Value{ "disk_a": cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diska"), - "size": cty.NullVal(cty.String), + "size": cty.StringVal("50GB"), }), }), "root_block_device": cty.MapVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), }), + "b": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp3"), + }), }), }), RequiredReplace: cty.NewPathSet(), Schema: testSchema(configschema.NestingMap), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { - + ami = "ami-AFTER" - + disks = { - + "disk_a" = { - + mount_point = "/var/diska" - }, - } - + id = "i-02ae66f368e8518a9" - - + root_block_device "a" { - + volume_type = "gp2" + ~ ami = "ami-BEFORE" -> "ami-AFTER" + id = "i-02ae66f368e8518a9" + # (1 unchanged attribute hidden) + + ~ root_block_device "b" { + ~ volume_type = "gp2" -> "gp3" } + + # (1 unchanged block hidden) } `, }, - "in-place update - creation": { + "in-place update - multiple blocks second changed": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.MapValEmpty(cty.Object(map[string]cty.Type{ - "mount_point": cty.String, - "size": cty.String, - })), - "root_block_device": cty.MapValEmpty(cty.Object(map[string]cty.Type{ - "volume_type": cty.String, - })), + "disks": cty.MapVal(map[string]cty.Value{ + "disk_a": cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), + }), + }), + "root_block_device": cty.MapVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + }), + "b": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + }), + }), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), @@ -3942,11 +4108,14 @@ func TestResourceChange_nestedMap(t *testing.T) { "disks": cty.MapVal(map[string]cty.Value{ "disk_a": cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diska"), - "size": cty.NullVal(cty.String), + "size": cty.StringVal("50GB"), }), }), "root_block_device": cty.MapVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp3"), + }), + "b": cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), }), }), @@ -3956,20 +4125,18 @@ func TestResourceChange_nestedMap(t *testing.T) { ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disks = { - + "disk_a" = { - + mount_point = "/var/diska" - }, - } id = "i-02ae66f368e8518a9" + # (1 unchanged attribute hidden) - + root_block_device "a" { - + volume_type = "gp2" + ~ root_block_device "a" { + ~ volume_type = "gp2" -> "gp3" } + + # (1 unchanged block hidden) } `, }, - "in-place update - change attr": { + "in-place update - multiple blocks changed": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ @@ -3978,13 +4145,15 @@ func TestResourceChange_nestedMap(t *testing.T) { "disks": cty.MapVal(map[string]cty.Value{ "disk_a": cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diska"), - "size": cty.NullVal(cty.String), + "size": cty.StringVal("50GB"), }), }), "root_block_device": cty.MapVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), - "new_field": cty.NullVal(cty.String), + }), + "b": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), }), }), }), @@ -3999,32 +4168,31 @@ func TestResourceChange_nestedMap(t *testing.T) { }), "root_block_device": cty.MapVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - "new_field": cty.StringVal("new_value"), + "volume_type": cty.StringVal("gp3"), + }), + "b": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp3"), }), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaPlus(configschema.NestingMap), + Schema: testSchema(configschema.NestingMap), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disks = { - ~ "disk_a" = { - + size = "50GB" - # (1 unchanged attribute hidden) - }, - } id = "i-02ae66f368e8518a9" + # (1 unchanged attribute hidden) ~ root_block_device "a" { - + new_field = "new_value" - # (1 unchanged attribute hidden) + ~ volume_type = "gp2" -> "gp3" + } + ~ root_block_device "b" { + ~ volume_type = "gp2" -> "gp3" } } `, }, - "in-place update - insertion": { + "in-place update - multiple different unchanged blocks": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ @@ -4039,7 +4207,11 @@ func TestResourceChange_nestedMap(t *testing.T) { "root_block_device": cty.MapVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), - "new_field": cty.NullVal(cty.String), + }), + }), + "leaf_block_device": cty.MapVal(map[string]cty.Value{ + "b": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), }), }), }), @@ -4051,49 +4223,33 @@ func TestResourceChange_nestedMap(t *testing.T) { "mount_point": cty.StringVal("/var/diska"), "size": cty.StringVal("50GB"), }), - "disk_2": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/disk2"), - "size": cty.StringVal("50GB"), - }), }), "root_block_device": cty.MapVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), - "new_field": cty.NullVal(cty.String), }), + }), + "leaf_block_device": cty.MapVal(map[string]cty.Value{ "b": cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), - "new_field": cty.StringVal("new_value"), }), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaPlus(configschema.NestingMap), + Schema: testSchemaMultipleBlocks(configschema.NestingMap), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disks = { - + "disk_2" = { - + mount_point = "/var/disk2" - + size = "50GB" - }, - # (1 unchanged element hidden) - } id = "i-02ae66f368e8518a9" + # (1 unchanged attribute hidden) - + root_block_device "b" { - + new_field = "new_value" - + volume_type = "gp2" - } - - # (1 unchanged block hidden) + # (2 unchanged blocks hidden) } `, }, - "force-new update (whole block)": { - Action: plans.DeleteThenCreate, - ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate, - Mode: addrs.ManagedResourceMode, + "in-place update - multiple different blocks first changed": { + Action: plans.Update, + Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), @@ -4107,8 +4263,10 @@ func TestResourceChange_nestedMap(t *testing.T) { "a": cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), }), + }), + "leaf_block_device": cty.MapVal(map[string]cty.Value{ "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("standard"), + "volume_type": cty.StringVal("gp2"), }), }), }), @@ -4118,45 +4276,37 @@ func TestResourceChange_nestedMap(t *testing.T) { "disks": cty.MapVal(map[string]cty.Value{ "disk_a": cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("100GB"), + "size": cty.StringVal("50GB"), }), }), "root_block_device": cty.MapVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("different"), + "volume_type": cty.StringVal("gp2"), }), + }), + "leaf_block_device": cty.MapVal(map[string]cty.Value{ "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("standard"), + "volume_type": cty.StringVal("gp3"), }), }), }), - RequiredReplace: cty.NewPathSet(cty.Path{ - cty.GetAttrStep{Name: "root_block_device"}, - cty.IndexStep{Key: cty.StringVal("a")}, - }, - cty.Path{cty.GetAttrStep{Name: "disks"}}, - ), - Schema: testSchema(configschema.NestingMap), - ExpectedOutput: ` # test_instance.example must be replaced --/+ resource "test_instance" "example" { + RequiredReplace: cty.NewPathSet(), + Schema: testSchemaMultipleBlocks(configschema.NestingMap), + ExpectedOutput: ` # test_instance.example will be updated in-place + ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disks = { - ~ "disk_a" = { # forces replacement - ~ size = "50GB" -> "100GB" - # (1 unchanged attribute hidden) - }, - } id = "i-02ae66f368e8518a9" + # (1 unchanged attribute hidden) - ~ root_block_device "a" { # forces replacement - ~ volume_type = "gp2" -> "different" + ~ leaf_block_device "b" { + ~ volume_type = "gp2" -> "gp3" } # (1 unchanged block hidden) } `, }, - "in-place update - deletion": { + "in-place update - multiple different blocks second changed": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ @@ -4171,43 +4321,51 @@ func TestResourceChange_nestedMap(t *testing.T) { "root_block_device": cty.MapVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), - "new_field": cty.StringVal("new_value"), + }), + }), + "leaf_block_device": cty.MapVal(map[string]cty.Value{ + "b": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), }), }), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disks": cty.MapValEmpty(cty.Object(map[string]cty.Type{ - "mount_point": cty.String, - "size": cty.String, - })), - "root_block_device": cty.MapValEmpty(cty.Object(map[string]cty.Type{ - "volume_type": cty.String, - "new_field": cty.String, - })), + "disks": cty.MapVal(map[string]cty.Value{ + "disk_a": cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), + }), + }), + "root_block_device": cty.MapVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp3"), + }), + }), + "leaf_block_device": cty.MapVal(map[string]cty.Value{ + "b": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + }), + }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaPlus(configschema.NestingMap), + Schema: testSchemaMultipleBlocks(configschema.NestingMap), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disks = { - - "disk_a" = { - - mount_point = "/var/diska" -> null - - size = "50GB" -> null - }, - } id = "i-02ae66f368e8518a9" + # (1 unchanged attribute hidden) - - root_block_device "a" { - - new_field = "new_value" -> null - - volume_type = "gp2" -> null + ~ root_block_device "a" { + ~ volume_type = "gp2" -> "gp3" } + + # (1 unchanged block hidden) } `, }, - "in-place update - unknown": { + "in-place update - multiple different blocks changed": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ @@ -4222,61 +4380,58 @@ func TestResourceChange_nestedMap(t *testing.T) { "root_block_device": cty.MapVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), - "new_field": cty.StringVal("new_value"), + }), + }), + "leaf_block_device": cty.MapVal(map[string]cty.Value{ + "b": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), }), }), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disks": cty.UnknownVal(cty.Map(cty.Object(map[string]cty.Type{ - "mount_point": cty.String, - "size": cty.String, - }))), + "disks": cty.MapVal(map[string]cty.Value{ + "disk_a": cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), + }), + }), "root_block_device": cty.MapVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - "new_field": cty.StringVal("new_value"), + "volume_type": cty.StringVal("gp3"), + }), + }), + "leaf_block_device": cty.MapVal(map[string]cty.Value{ + "b": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp3"), }), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaPlus(configschema.NestingMap), + Schema: testSchemaMultipleBlocks(configschema.NestingMap), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disks = { - - "disk_a" = { - - mount_point = "/var/diska" -> null - - size = "50GB" -> null - }, - } -> (known after apply) id = "i-02ae66f368e8518a9" + # (1 unchanged attribute hidden) - # (1 unchanged block hidden) + ~ leaf_block_device "b" { + ~ volume_type = "gp2" -> "gp3" + } + + ~ root_block_device "a" { + ~ volume_type = "gp2" -> "gp3" + } } `, }, - "in-place update - insertion sensitive": { + "in-place update - mixed blocks unchanged": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.MapValEmpty(cty.Object(map[string]cty.Type{ - "mount_point": cty.String, - "size": cty.String, - })), - "root_block_device": cty.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - "new_field": cty.StringVal("new_value"), - }), - }), - }), - After: cty.ObjectVal(map[string]cty.Value{ - "id": cty.StringVal("i-02ae66f368e8518a9"), - "ami": cty.StringVal("ami-AFTER"), "disks": cty.MapVal(map[string]cty.Value{ "disk_a": cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diska"), @@ -4286,49 +4441,12 @@ func TestResourceChange_nestedMap(t *testing.T) { "root_block_device": cty.MapVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), - "new_field": cty.StringVal("new_value"), }), - }), - }), - AfterValMarks: []cty.PathValueMarks{ - { - Path: cty.Path{cty.GetAttrStep{Name: "disks"}, - cty.IndexStep{Key: cty.StringVal("disk_a")}, - cty.GetAttrStep{Name: "mount_point"}, - }, - Marks: cty.NewValueMarks(marks.Sensitive), - }, - }, - RequiredReplace: cty.NewPathSet(), - Schema: testSchemaPlus(configschema.NestingMap), - ExpectedOutput: ` # test_instance.example will be updated in-place - ~ resource "test_instance" "example" { - ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disks = { - + "disk_a" = { - + mount_point = (sensitive value) - + size = "50GB" - }, - } - id = "i-02ae66f368e8518a9" - - # (1 unchanged block hidden) - } -`, - }, - "in-place update - multiple unchanged blocks": { - Action: plans.Update, - Mode: addrs.ManagedResourceMode, - Before: cty.ObjectVal(map[string]cty.Value{ - "id": cty.StringVal("i-02ae66f368e8518a9"), - "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.MapVal(map[string]cty.Value{ - "disk_a": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), + "b": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), }), }), - "root_block_device": cty.MapVal(map[string]cty.Value{ + "leaf_block_device": cty.MapVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), }), @@ -4354,20 +4472,28 @@ func TestResourceChange_nestedMap(t *testing.T) { "volume_type": cty.StringVal("gp2"), }), }), + "leaf_block_device": cty.MapVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + }), + "b": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + }), + }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingMap), + Schema: testSchemaMultipleBlocks(configschema.NestingMap), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" id = "i-02ae66f368e8518a9" # (1 unchanged attribute hidden) - # (2 unchanged blocks hidden) + # (4 unchanged blocks hidden) } `, }, - "in-place update - multiple blocks first changed": { + "in-place update - mixed blocks changed": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ @@ -4387,6 +4513,14 @@ func TestResourceChange_nestedMap(t *testing.T) { "volume_type": cty.StringVal("gp2"), }), }), + "leaf_block_device": cty.MapVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + }), + "b": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + }), + }), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), @@ -4405,507 +4539,373 @@ func TestResourceChange_nestedMap(t *testing.T) { "volume_type": cty.StringVal("gp3"), }), }), + "leaf_block_device": cty.MapVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + }), + "b": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp3"), + }), + }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingMap), + Schema: testSchemaMultipleBlocks(configschema.NestingMap), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" id = "i-02ae66f368e8518a9" # (1 unchanged attribute hidden) + ~ leaf_block_device "b" { + ~ volume_type = "gp2" -> "gp3" + } + ~ root_block_device "b" { ~ volume_type = "gp2" -> "gp3" } - # (1 unchanged block hidden) + # (2 unchanged blocks hidden) } `, }, - "in-place update - multiple blocks second changed": { + } + runTestCases(t, testCases) +} + +func TestResourceChange_nestedSingle(t *testing.T) { + testCases := map[string]testCase{ + "in-place update - equal": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.MapVal(map[string]cty.Value{ - "disk_a": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), - }), + "root_block_device": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), }), - "root_block_device": cty.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), - "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), + "disk": cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), }), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disks": cty.MapVal(map[string]cty.Value{ - "disk_a": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), - }), + "root_block_device": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), }), - "root_block_device": cty.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp3"), - }), - "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), + "disk": cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingMap), + Schema: testSchema(configschema.NestingSingle), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { - ~ ami = "ami-BEFORE" -> "ami-AFTER" - id = "i-02ae66f368e8518a9" + ~ ami = "ami-BEFORE" -> "ami-AFTER" + id = "i-02ae66f368e8518a9" # (1 unchanged attribute hidden) - ~ root_block_device "a" { - ~ volume_type = "gp2" -> "gp3" - } - # (1 unchanged block hidden) } `, }, - "in-place update - multiple blocks changed": { + "in-place update - creation": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.MapVal(map[string]cty.Value{ - "disk_a": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), - }), - }), - "root_block_device": cty.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), - "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), - }), + "root_block_device": cty.NullVal(cty.Object(map[string]cty.Type{ + "volume_type": cty.String, + })), + "disk": cty.NullVal(cty.Object(map[string]cty.Type{ + "mount_point": cty.String, + "size": cty.String, + })), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disks": cty.MapVal(map[string]cty.Value{ - "disk_a": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), - }), + "disk": cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), }), - "root_block_device": cty.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp3"), - }), - "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp3"), - }), + "root_block_device": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.NullVal(cty.String), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingMap), + Schema: testSchema(configschema.NestingSingle), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { - ~ ami = "ami-BEFORE" -> "ami-AFTER" - id = "i-02ae66f368e8518a9" - # (1 unchanged attribute hidden) - - ~ root_block_device "a" { - ~ volume_type = "gp2" -> "gp3" - } - ~ root_block_device "b" { - ~ volume_type = "gp2" -> "gp3" + ~ ami = "ami-BEFORE" -> "ami-AFTER" + + disk = { + + mount_point = "/var/diska" + + size = "50GB" } + id = "i-02ae66f368e8518a9" + + + root_block_device {} } `, }, - "in-place update - multiple different unchanged blocks": { - Action: plans.Update, - Mode: addrs.ManagedResourceMode, + "force-new update (inside blocks)": { + Action: plans.DeleteThenCreate, + ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate, + Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.MapVal(map[string]cty.Value{ - "disk_a": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), - }), - }), - "root_block_device": cty.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), + "disk": cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), }), - "leaf_block_device": cty.MapVal(map[string]cty.Value{ - "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), + "root_block_device": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), }), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disks": cty.MapVal(map[string]cty.Value{ - "disk_a": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), - }), - }), - "root_block_device": cty.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), + "disk": cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diskb"), + "size": cty.StringVal("50GB"), }), - "leaf_block_device": cty.MapVal(map[string]cty.Value{ - "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), + "root_block_device": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("different"), }), }), - RequiredReplace: cty.NewPathSet(), - Schema: testSchemaMultipleBlocks(configschema.NestingMap), - ExpectedOutput: ` # test_instance.example will be updated in-place - ~ resource "test_instance" "example" { - ~ ami = "ami-BEFORE" -> "ami-AFTER" - id = "i-02ae66f368e8518a9" - # (1 unchanged attribute hidden) - - # (2 unchanged blocks hidden) - } -`, - }, - "in-place update - multiple different blocks first changed": { - Action: plans.Update, - Mode: addrs.ManagedResourceMode, + RequiredReplace: cty.NewPathSet( + cty.Path{ + cty.GetAttrStep{Name: "root_block_device"}, + cty.GetAttrStep{Name: "volume_type"}, + }, + cty.Path{ + cty.GetAttrStep{Name: "disk"}, + cty.GetAttrStep{Name: "mount_point"}, + }, + ), + Schema: testSchema(configschema.NestingSingle), + ExpectedOutput: ` # test_instance.example must be replaced +-/+ resource "test_instance" "example" { + ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disk = { + ~ mount_point = "/var/diska" -> "/var/diskb" # forces replacement + # (1 unchanged attribute hidden) + } + id = "i-02ae66f368e8518a9" + + ~ root_block_device { + ~ volume_type = "gp2" -> "different" # forces replacement + } + } +`, + }, + "force-new update (whole block)": { + Action: plans.DeleteThenCreate, + ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate, + Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.MapVal(map[string]cty.Value{ - "disk_a": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), - }), - }), - "root_block_device": cty.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), + "disk": cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), }), - "leaf_block_device": cty.MapVal(map[string]cty.Value{ - "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), + "root_block_device": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), }), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disks": cty.MapVal(map[string]cty.Value{ - "disk_a": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), - }), - }), - "root_block_device": cty.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), + "disk": cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diskb"), + "size": cty.StringVal("50GB"), }), - "leaf_block_device": cty.MapVal(map[string]cty.Value{ - "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp3"), - }), + "root_block_device": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("different"), }), }), - RequiredReplace: cty.NewPathSet(), - Schema: testSchemaMultipleBlocks(configschema.NestingMap), - ExpectedOutput: ` # test_instance.example will be updated in-place - ~ resource "test_instance" "example" { - ~ ami = "ami-BEFORE" -> "ami-AFTER" - id = "i-02ae66f368e8518a9" - # (1 unchanged attribute hidden) - - ~ leaf_block_device "b" { - ~ volume_type = "gp2" -> "gp3" + RequiredReplace: cty.NewPathSet( + cty.Path{cty.GetAttrStep{Name: "root_block_device"}}, + cty.Path{cty.GetAttrStep{Name: "disk"}}, + ), + Schema: testSchema(configschema.NestingSingle), + ExpectedOutput: ` # test_instance.example must be replaced +-/+ resource "test_instance" "example" { + ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disk = { # forces replacement + ~ mount_point = "/var/diska" -> "/var/diskb" + # (1 unchanged attribute hidden) } + id = "i-02ae66f368e8518a9" - # (1 unchanged block hidden) + ~ root_block_device { # forces replacement + ~ volume_type = "gp2" -> "different" + } } `, }, - "in-place update - multiple different blocks second changed": { + "in-place update - deletion": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.MapVal(map[string]cty.Value{ - "disk_a": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), - }), - }), - "root_block_device": cty.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), + "disk": cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), }), - "leaf_block_device": cty.MapVal(map[string]cty.Value{ - "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), + "root_block_device": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), }), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disks": cty.MapVal(map[string]cty.Value{ - "disk_a": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), - }), - }), - "root_block_device": cty.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp3"), - }), - }), - "leaf_block_device": cty.MapVal(map[string]cty.Value{ - "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), - }), + "root_block_device": cty.NullVal(cty.Object(map[string]cty.Type{ + "volume_type": cty.String, + })), + "disk": cty.NullVal(cty.Object(map[string]cty.Type{ + "mount_point": cty.String, + "size": cty.String, + })), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaMultipleBlocks(configschema.NestingMap), + Schema: testSchema(configschema.NestingSingle), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { - ~ ami = "ami-BEFORE" -> "ami-AFTER" - id = "i-02ae66f368e8518a9" - # (1 unchanged attribute hidden) + ~ ami = "ami-BEFORE" -> "ami-AFTER" + - disk = { + - mount_point = "/var/diska" -> null + - size = "50GB" -> null + } -> null + id = "i-02ae66f368e8518a9" - ~ root_block_device "a" { - ~ volume_type = "gp2" -> "gp3" + - root_block_device { + - volume_type = "gp2" -> null } - - # (1 unchanged block hidden) } `, }, - "in-place update - multiple different blocks changed": { + "with dynamically-typed attribute": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ - "id": cty.StringVal("i-02ae66f368e8518a9"), - "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.MapVal(map[string]cty.Value{ - "disk_a": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), - }), - }), - "root_block_device": cty.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), - }), - "leaf_block_device": cty.MapVal(map[string]cty.Value{ - "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), - }), + "block": cty.NullVal(cty.Object(map[string]cty.Type{ + "attr": cty.String, + })), }), After: cty.ObjectVal(map[string]cty.Value{ - "id": cty.StringVal("i-02ae66f368e8518a9"), - "ami": cty.StringVal("ami-AFTER"), - "disks": cty.MapVal(map[string]cty.Value{ - "disk_a": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), - }), - }), - "root_block_device": cty.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp3"), - }), - }), - "leaf_block_device": cty.MapVal(map[string]cty.Value{ - "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp3"), - }), + "block": cty.ObjectVal(map[string]cty.Value{ + "attr": cty.StringVal("foo"), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaMultipleBlocks(configschema.NestingMap), + Schema: &configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + "block": { + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "attr": {Type: cty.DynamicPseudoType, Optional: true}, + }, + }, + Nesting: configschema.NestingSingle, + }, + }, + }, ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { - ~ ami = "ami-BEFORE" -> "ami-AFTER" - id = "i-02ae66f368e8518a9" - # (1 unchanged attribute hidden) - - ~ leaf_block_device "b" { - ~ volume_type = "gp2" -> "gp3" - } - - ~ root_block_device "a" { - ~ volume_type = "gp2" -> "gp3" + + block { + + attr = "foo" } } `, }, - "in-place update - mixed blocks unchanged": { + "in-place update - unknown": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.MapVal(map[string]cty.Value{ - "disk_a": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), - }), - }), - "root_block_device": cty.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), - "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), + "disk": cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), }), - "leaf_block_device": cty.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), - "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), + "root_block_device": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + "new_field": cty.StringVal("new_value"), }), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disks": cty.MapVal(map[string]cty.Value{ - "disk_a": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), - }), - }), - "root_block_device": cty.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), - "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), - }), - "leaf_block_device": cty.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), - "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), + "disk": cty.UnknownVal(cty.Object(map[string]cty.Type{ + "mount_point": cty.String, + "size": cty.String, + })), + "root_block_device": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + "new_field": cty.StringVal("new_value"), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaMultipleBlocks(configschema.NestingMap), + Schema: testSchemaPlus(configschema.NestingSingle), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { - ~ ami = "ami-BEFORE" -> "ami-AFTER" - id = "i-02ae66f368e8518a9" - # (1 unchanged attribute hidden) + ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disk = { + ~ mount_point = "/var/diska" -> (known after apply) + ~ size = "50GB" -> (known after apply) + } -> (known after apply) + id = "i-02ae66f368e8518a9" - # (4 unchanged blocks hidden) + # (1 unchanged block hidden) } `, }, - "in-place update - mixed blocks changed": { + "in-place update - modification": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-BEFORE"), - "disks": cty.MapVal(map[string]cty.Value{ - "disk_a": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), - }), - }), - "root_block_device": cty.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), - "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), + "disk": cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), }), - "leaf_block_device": cty.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), - "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), + "root_block_device": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + "new_field": cty.StringVal("new_value"), }), }), After: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("i-02ae66f368e8518a9"), "ami": cty.StringVal("ami-AFTER"), - "disks": cty.MapVal(map[string]cty.Value{ - "disk_a": cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), - }), - }), - "root_block_device": cty.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), - "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp3"), - }), + "disk": cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("25GB"), }), - "leaf_block_device": cty.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), - "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp3"), - }), + "root_block_device": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + "new_field": cty.StringVal("new_value"), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaMultipleBlocks(configschema.NestingMap), + Schema: testSchemaPlus(configschema.NestingSingle), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { - ~ ami = "ami-BEFORE" -> "ami-AFTER" - id = "i-02ae66f368e8518a9" - # (1 unchanged attribute hidden) - - ~ leaf_block_device "b" { - ~ volume_type = "gp2" -> "gp3" - } - - ~ root_block_device "b" { - ~ volume_type = "gp2" -> "gp3" + ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disk = { + ~ size = "50GB" -> "25GB" + # (1 unchanged attribute hidden) } + id = "i-02ae66f368e8518a9" - # (2 unchanged blocks hidden) + # (1 unchanged block hidden) } `, },