diff --git a/internal/command/format/diff.go b/internal/command/format/diff.go index 8c7d3ce84ba9..0ea6a694f588 100644 --- a/internal/command/format/diff.go +++ b/internal/command/format/diff.go @@ -398,7 +398,7 @@ func (p *blockBodyDiffPrinter) writeAttrDiff(name string, attrS *configschema.At } if attrS.NestedType != nil { - p.writeNestedAttrDiff(name, attrS.NestedType, old, new, nameLen, indent, path, action, showJustNew) + p.writeNestedAttrDiff(name, attrS, old, new, nameLen, indent, path, action, showJustNew) return false } @@ -416,7 +416,7 @@ func (p *blockBodyDiffPrinter) writeAttrDiff(name string, attrS *configschema.At p.buf.WriteString(" = ") if attrS.Sensitive { - p.buf.WriteString("(sensitive value)") + p.buf.WriteString("(sensitive)") if p.pathForcesNewResource(path) { p.buf.WriteString(p.color.Color(forcesNewResourceCaption)) } @@ -441,9 +441,11 @@ func (p *blockBodyDiffPrinter) writeAttrDiff(name string, attrS *configschema.At // writeNestedAttrDiff is responsible for formatting Attributes with NestedTypes // in the diff. func (p *blockBodyDiffPrinter) writeNestedAttrDiff( - name string, objS *configschema.Object, old, new cty.Value, + name string, attrWithNestedS *configschema.Attribute, old, new cty.Value, nameLen, indent int, path cty.Path, action plans.Action, showJustNew bool) { + objS := attrWithNestedS.NestedType + p.buf.WriteString("\n") p.writeSensitivityWarning(old, new, indent, action, false) p.buf.WriteString(strings.Repeat(" ", indent)) @@ -454,8 +456,11 @@ func (p *blockBodyDiffPrinter) writeNestedAttrDiff( p.buf.WriteString(p.color.Color("[reset]")) p.buf.WriteString(strings.Repeat(" ", nameLen-len(name))) - if old.HasMark(marks.Sensitive) || new.HasMark(marks.Sensitive) { - p.buf.WriteString(" = (sensitive value)") + // Then schema of the attribute itself can be marked sensitive, or the values assigned + sensitive := attrWithNestedS.Sensitive || old.HasMark(marks.Sensitive) || new.HasMark(marks.Sensitive) + if sensitive { + p.buf.WriteString(" = (sensitive)") + if p.pathForcesNewResource(path) { p.buf.WriteString(p.color.Color(forcesNewResourceCaption)) } @@ -475,6 +480,12 @@ func (p *blockBodyDiffPrinter) writeNestedAttrDiff( p.buf.WriteString(strings.Repeat(" ", indent+2)) p.buf.WriteString("}") + if !new.IsKnown() { + p.buf.WriteString(" -> (known after apply)") + } else if new.IsNull() { + p.buf.WriteString(p.color.Color("[dark_gray] -> null[reset]")) + } + case configschema.NestingList: p.buf.WriteString(" = [") if action != plans.NoOp && (p.pathForcesNewResource(path) || p.pathForcesNewResource(path[:len(path)-1])) { @@ -558,6 +569,8 @@ func (p *blockBodyDiffPrinter) writeNestedAttrDiff( if !new.IsKnown() { p.buf.WriteString(" -> (known after apply)") + } else if new.IsNull() { + p.buf.WriteString(p.color.Color("[dark_gray] -> null[reset]")) } case configschema.NestingSet: @@ -636,6 +649,8 @@ func (p *blockBodyDiffPrinter) writeNestedAttrDiff( if !new.IsKnown() { p.buf.WriteString(" -> (known after apply)") + } else if new.IsNull() { + p.buf.WriteString(p.color.Color("[dark_gray] -> null[reset]")) } case configschema.NestingMap: @@ -711,6 +726,8 @@ func (p *blockBodyDiffPrinter) writeNestedAttrDiff( p.buf.WriteString("}") if !new.IsKnown() { p.buf.WriteString(" -> (known after apply)") + } else if new.IsNull() { + p.buf.WriteString(p.color.Color("[dark_gray] -> null[reset]")) } } } diff --git a/internal/command/format/diff_test.go b/internal/command/format/diff_test.go index 56e2eafeafed..cd4705fc627a 100644 --- a/internal/command/format/diff_test.go +++ b/internal/command/format/diff_test.go @@ -411,11 +411,11 @@ new line ExpectedOutput: ` # test_instance.example will be created + resource "test_instance" "example" { + conn_info = { - + password = (sensitive value) + + password = (sensitive) + user = "not-secret" } + id = (known after apply) - + password = (sensitive value) + + password = (sensitive) } `, }, @@ -2427,7 +2427,7 @@ func TestResourceChange_map(t *testing.T) { runTestCases(t, testCases) } -func TestResourceChange_nestedList(t *testing.T) { +func TestResourceChange_nestedSingle(t *testing.T) { testCases := map[string]testCase{ "in-place update - equal": { Action: plans.Update, @@ -2435,39 +2435,31 @@ func TestResourceChange_nestedList(t *testing.T) { 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"), - }), + "root_block_device": 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"), - }), + "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"), - "root_block_device": cty.ListVal([]cty.Value{ - 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"), }), - "disks": cty.ListVal([]cty.Value{ - 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"), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingList), + 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) # (1 unchanged block hidden) @@ -2480,10 +2472,10 @@ func TestResourceChange_nestedList(t *testing.T) { 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{ + "root_block_device": cty.NullVal(cty.Object(map[string]cty.Type{ "volume_type": cty.String, })), - "disks": cty.ListValEmpty(cty.Object(map[string]cty.Type{ + "disk": cty.NullVal(cty.Object(map[string]cty.Type{ "mount_point": cty.String, "size": cty.String, })), @@ -2491,141 +2483,26 @@ 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{cty.ObjectVal(map[string]cty.Value{ + "disk": 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), - }), - }), - }), - 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"), - }), + "root_block_device": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.NullVal(cty.String), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingList), + Schema: testSchema(configschema.NestingSingle), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { - ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disks = [ - + { - + mount_point = "/var/diska" - }, - ] - id = "i-02ae66f368e8518a9" - - + root_block_device { - + volume_type = "gp2" + ~ ami = "ami-BEFORE" -> "ami-AFTER" + + disk = { + + mount_point = "/var/diska" + + size = "50GB" } - } -`, - }, - "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" + id = "i-02ae66f368e8518a9" - ~ root_block_device { - + new_field = "new_value" - # (1 unchanged attribute hidden) - } + + root_block_device {} } `, }, @@ -2636,56 +2513,44 @@ 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{ - 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.ListVal([]cty.Value{ - 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.ListVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diskb"), - "size": cty.StringVal("50GB"), - }), + "disk": 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.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: "disks"}, - cty.IndexStep{Key: cty.NumberIntVal(0)}, + cty.GetAttrStep{Name: "disk"}, cty.GetAttrStep{Name: "mount_point"}, }, ), - Schema: testSchema(configschema.NestingList), + Schema: testSchema(configschema.NestingSingle), 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) - }, - ] - id = "i-02ae66f368e8518a9" + ~ 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 @@ -2700,48 +2565,38 @@ 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{ - 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.ListVal([]cty.Value{ - 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.ListVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diskb"), - "size": cty.StringVal("50GB"), - }), + "disk": 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.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: "disks"}}, + cty.Path{cty.GetAttrStep{Name: "disk"}}, ), - Schema: testSchema(configschema.NestingList), + Schema: testSchema(configschema.NestingSingle), ExpectedOutput: ` # test_instance.example must be replaced -/+ resource "test_instance" "example" { - ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disks = [ # forces replacement - ~ { - ~ mount_point = "/var/diska" -> "/var/diskb" - # (1 unchanged attribute hidden) - }, - ] - id = "i-02ae66f368e8518a9" + ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disk = { # forces replacement + ~ mount_point = "/var/diska" -> "/var/diskb" + # (1 unchanged attribute hidden) + } + id = "i-02ae66f368e8518a9" ~ root_block_device { # forces replacement ~ volume_type = "gp2" -> "different" @@ -2755,41 +2610,35 @@ 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{ - 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.ListVal([]cty.Value{ - 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.ListValEmpty(cty.Object(map[string]cty.Type{ + "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, })), - "root_block_device": cty.ListValEmpty(cty.Object(map[string]cty.Type{ - "volume_type": cty.String, - })), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingList), + Schema: testSchema(configschema.NestingSingle), 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 - }, - ] - id = "i-02ae66f368e8518a9" + ~ ami = "ami-BEFORE" -> "ami-AFTER" + - disk = { + - mount_point = "/var/diska" -> null + - size = "50GB" -> null + } -> null + id = "i-02ae66f368e8518a9" - root_block_device { - volume_type = "gp2" -> null @@ -2801,16 +2650,13 @@ func TestResourceChange_nestedList(t *testing.T) { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ - "block": cty.EmptyTupleVal, + "block": cty.NullVal(cty.Object(map[string]cty.Type{ + "attr": cty.String, + })), }), After: cty.ObjectVal(map[string]cty.Value{ - "block": cty.TupleVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ - "attr": cty.StringVal("foo"), - }), - cty.ObjectVal(map[string]cty.Value{ - "attr": cty.True, - }), + "block": cty.ObjectVal(map[string]cty.Value{ + "attr": cty.StringVal("foo"), }), }), RequiredReplace: cty.NewPathSet(), @@ -2822,7 +2668,7 @@ func TestResourceChange_nestedList(t *testing.T) { "attr": {Type: cty.DynamicPseudoType, Optional: true}, }, }, - Nesting: configschema.NestingList, + Nesting: configschema.NestingSingle, }, }, }, @@ -2831,263 +2677,219 @@ func TestResourceChange_nestedList(t *testing.T) { + block { + attr = "foo" } - + block { - + attr = true - } } `, }, - "in-place sequence update - deletion": { + "in-place update - unknown": { 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")}), + "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"), }), }), 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")}), + "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"), }), }), 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.NestingSingle), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { - ~ list { - ~ attr = "x" -> "y" - } - ~ list { - ~ attr = "y" -> "z" - } + ~ 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) } `, }, - "in-place update - unknown": { + "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.ListVal([]cty.Value{ - 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.ListVal([]cty.Value{ - cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - "new_field": cty.StringVal("new_value"), - }), + "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.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"), - }), + "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"), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaPlus(configschema.NestingList), + Schema: testSchemaPlus(configschema.NestingSingle), 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" + ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disk = { + ~ size = "50GB" -> "25GB" + # (1 unchanged attribute hidden) + } + id = "i-02ae66f368e8518a9" # (1 unchanged block hidden) } `, }, - "in-place update - modification": { + } + runTestCases(t, testCases) +} + +func TestResourceChange_nestedList(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.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{ - "mount_point": cty.StringVal("/var/diskb"), - "size": cty.StringVal("50GB"), + "volume_type": cty.StringVal("gp2"), }), + }), + "disks": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diskc"), + "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.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: testSchemaPlus(configschema.NestingList), + Schema: testSchema(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) } `, }, - } - runTestCases(t, testCases) -} - -func TestResourceChange_nestedSet(t *testing.T) { - testCases := map[string]testCase{ - "creation from null - sensitive set": { - Action: plans.Create, + "in-place update - creation": { + Action: plans.Update, 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{ + 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, })), - "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{ + "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"), + "volume_type": 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 created - + resource "test_instance" "example" { - + ami = "ami-AFTER" - + disks = (sensitive value) - + id = "i-02ae66f368e8518a9" + 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 { - + volume_type = "gp2" - } + + root_block_device {} } `, }, - "in-place update - creation": { + "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"), - "disks": cty.SetValEmpty(cty.Object(map[string]cty.Type{ + "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, })), - "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{ + "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.SetVal([]cty.Value{ + "root_block_device": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), }), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingSet), + Schema: testSchema(configschema.NestingList), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" @@ -3104,170 +2906,130 @@ func TestResourceChange_nestedSet(t *testing.T) { } `, }, - "in-place update - creation - sensitive set": { + "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.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{ + "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.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), + "mount_point": cty.StringVal("/var/diskb"), + "size": cty.StringVal("50GB"), }), }), - }), - 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{ + "root_block_device": cty.ListVal([]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.NullVal(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{ + "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"), + }), }), - "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), + Schema: testSchemaPlus(configschema.NestingList), 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 = [ + ~ { + + size = "50GB" + # (1 unchanged attribute hidden) + }, + # (1 unchanged element hidden) + ] id = "i-02ae66f368e8518a9" + + ~ root_block_device { + + new_field = "new_value" + # (1 unchanged attribute hidden) + } } `, }, - "in-place update - insertion": { - 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.SetVal([]cty.Value{ + "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("100GB"), + "size": cty.StringVal("50GB"), }), }), - "root_block_device": cty.SetVal([]cty.Value{ + "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.SetVal([]cty.Value{ - 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/diskb"), - "size": cty.StringVal("100GB"), + "size": cty.StringVal("50GB"), }), }), - "root_block_device": cty.SetVal([]cty.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"), + "volume_type": cty.StringVal("different"), }), }), }), - RequiredReplace: cty.NewPathSet(), - Schema: testSchemaPlus(configschema.NestingSet), - ExpectedOutput: ` # test_instance.example will be updated in-place - ~ resource "test_instance" "example" { + 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"}, + }, + ), + Schema: testSchema(configschema.NestingList), + ExpectedOutput: ` # test_instance.example must be replaced +-/+ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" ~ disks = [ - + { - + mount_point = "/var/diska" - + size = "50GB" - }, - - { - - mount_point = "/var/diska" -> null + ~ { + ~ mount_point = "/var/diska" -> "/var/diskb" # forces replacement + # (1 unchanged attribute hidden) }, - # (1 unchanged element hidden) ] id = "i-02ae66f368e8518a9" - + root_block_device { - + new_field = "new_value" - + volume_type = "gp2" - } - - root_block_device { - - volume_type = "gp2" -> null + ~ root_block_device { + ~ volume_type = "gp2" -> "different" # forces replacement } } `, @@ -3279,30 +3041,30 @@ 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{ + "disks": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), }), }), - "disks": cty.SetVal([]cty.Value{ + "root_block_device": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), + "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.SetVal([]cty.Value{ + "disks": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("different"), + "mount_point": cty.StringVal("/var/diskb"), + "size": cty.StringVal("50GB"), }), }), - "disks": cty.SetVal([]cty.Value{ + "root_block_device": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diskb"), - "size": cty.StringVal("50GB"), + "volume_type": cty.StringVal("different"), }), }), }), @@ -3310,27 +3072,20 @@ func TestResourceChange_nestedSet(t *testing.T) { cty.Path{cty.GetAttrStep{Name: "root_block_device"}}, cty.Path{cty.GetAttrStep{Name: "disks"}}, ), - Schema: testSchema(configschema.NestingSet), + Schema: testSchema(configschema.NestingList), 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 = [ # forces replacement + ~ { + ~ mount_point = "/var/diska" -> "/var/diskb" + # (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 { # forces replacement + ~ volume_type = "gp2" -> "different" } } `, @@ -3341,33 +3096,31 @@ 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{ + "disks": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - "new_field": cty.StringVal("new_value"), + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("50GB"), }), }), - "disks": cty.SetVal([]cty.Value{ + "root_block_device": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), + "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.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.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: testSchemaPlus(configschema.NestingSet), + Schema: testSchema(configschema.NestingList), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" @@ -3380,75 +3133,120 @@ func TestResourceChange_nestedSet(t *testing.T) { id = "i-02ae66f368e8518a9" - root_block_device { - - new_field = "new_value" -> null - volume_type = "gp2" -> null } } `, }, - "in-place update - empty nested sets": { + "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.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, - })), + "block": cty.EmptyTupleVal, }), 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{ - "mount_point": cty.String, - "size": cty.String, - })), - "root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{ - "volume_type": cty.String, - })), + "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(), - Schema: testSchema(configschema.NestingSet), + 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" { - ~ ami = "ami-BEFORE" -> "ami-AFTER" - + disks = [ - ] - id = "i-02ae66f368e8518a9" + + block { + + attr = "foo" + } + + block { + + attr = true + } } `, }, - "in-place update - null insertion": { + "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, + }, + }, + }, + ExpectedOutput: ` # test_instance.example will be updated in-place + ~ resource "test_instance" "example" { + ~ list { + ~ attr = "x" -> "y" + } + ~ list { + ~ attr = "y" -> "z" + } + } +`, + }, + "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.SetVal([]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.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{ - "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), - }), - }), - "root_block_device": cty.SetVal([]cty.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"), @@ -3456,41 +3254,43 @@ func TestResourceChange_nestedSet(t *testing.T) { }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaPlus(configschema.NestingSet), + Schema: testSchemaPlus(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" + ~ disks = [ + - { + - mount_point = "/var/diska" -> null + - size = "50GB" -> null }, - ] + ] -> (known after apply) 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 - 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.SetVal([]cty.Value{ + "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.SetVal([]cty.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"), @@ -3500,11 +3300,21 @@ 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.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{ - "mount_point": cty.String, - "size": cty.String, - }))), - "root_block_device": cty.SetVal([]cty.Value{ + "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"), @@ -3512,16 +3322,21 @@ func TestResourceChange_nestedSet(t *testing.T) { }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaPlus(configschema.NestingSet), + Schema: testSchemaPlus(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" -> null - - size = "50GB" -> null + ~ { + ~ size = "50GB" -> "75GB" + # (1 unchanged attribute hidden) }, - ] -> (known after apply) + ~ { + ~ size = "50GB" -> "25GB" + # (1 unchanged attribute hidden) + }, + # (1 unchanged element hidden) + ] id = "i-02ae66f368e8518a9" # (1 unchanged block hidden) @@ -3532,50 +3347,52 @@ func TestResourceChange_nestedSet(t *testing.T) { runTestCases(t, testCases) } -func TestResourceChange_nestedMap(t *testing.T) { +func TestResourceChange_nestedSet(t *testing.T) { testCases := map[string]testCase{ - "creation from null": { - Action: plans.Update, + "creation from null - sensitive set": { + Action: plans.Create, 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{ + 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.NullVal(cty.Map(cty.Object(map[string]cty.Type{ + })), + "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.MapVal(map[string]cty.Value{ - "disk_a": cty.ObjectVal(map[string]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.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]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.NestingMap), - ExpectedOutput: ` # test_instance.example will be updated in-place - ~ resource "test_instance" "example" { + Schema: testSchema(configschema.NestingSet), + ExpectedOutput: ` # test_instance.example will be created + + resource "test_instance" "example" { + ami = "ami-AFTER" - + disks = { - + "disk_a" = { - + mount_point = "/var/diska" - }, - } + + disks = (sensitive) + id = "i-02ae66f368e8518a9" - + root_block_device "a" { + + root_block_device { + volume_type = "gp2" } } @@ -3587,116 +3404,163 @@ func TestResourceChange_nestedMap(t *testing.T) { 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{ + "disks": cty.SetValEmpty(cty.Object(map[string]cty.Type{ "mount_point": cty.String, "size": cty.String, })), - "root_block_device": cty.MapValEmpty(cty.Object(map[string]cty.Type{ + "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.MapVal(map[string]cty.Value{ - "disk_a": cty.ObjectVal(map[string]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.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ + "root_block_device": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), }), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingMap), + Schema: testSchema(configschema.NestingSet), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disks = { - + "disk_a" = { + ~ disks = [ + + { + mount_point = "/var/diska" }, - } + ] id = "i-02ae66f368e8518a9" - + root_block_device "a" { + + root_block_device { + volume_type = "gp2" } } `, }, - "in-place update - change attr": { + "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"), - "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.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - "new_field": cty.NullVal(cty.String), - }), - }), + "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.MapVal(map[string]cty.Value{ - "disk_a": cty.ObjectVal(map[string]cty.Value{ + "disks": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ "mount_point": cty.StringVal("/var/diska"), - "size": cty.StringVal("50GB"), + "size": cty.NullVal(cty.String), }), }), - "root_block_device": cty.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]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"), }), }), }), + AfterValMarks: []cty.PathValueMarks{ + { + Path: cty.Path{cty.GetAttrStep{Name: "disks"}}, + Marks: cty.NewValueMarks(marks.Sensitive), + }, + }, RequiredReplace: cty.NewPathSet(), - Schema: testSchemaPlus(configschema.NestingMap), + Schema: testSchema(configschema.NestingSet), 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) - }, - } + # Warning: this attribute value will be marked as sensitive and will not + # display in UI output after applying this change. + ~ disks = (sensitive) id = "i-02ae66f368e8518a9" - ~ root_block_device "a" { - + new_field = "new_value" - # (1 unchanged attribute hidden) + + root_block_device { + + volume_type = "gp2" } } `, }, - "in-place update - insertion": { + "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.MapVal(map[string]cty.Value{ - "disk_a": cty.ObjectVal(map[string]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.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ + "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) + 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.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("100GB"), + }), + }), + "root_block_device": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), "new_field": cty.NullVal(cty.String), }), @@ -3705,47 +3569,47 @@ func TestResourceChange_nestedMap(t *testing.T) { 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{ + "disks": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ "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"), + cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diskb"), + "size": cty.StringVal("100GB"), }), }), - "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{ + "root_block_device": cty.SetVal([]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.NestingMap), + Schema: testSchemaPlus(configschema.NestingSet), 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" + ~ disks = [ + + { + + mount_point = "/var/diska" + size = "50GB" }, + - { + - mount_point = "/var/diska" -> null + }, # (1 unchanged element hidden) - } + ] id = "i-02ae66f368e8518a9" - + root_block_device "b" { + + root_block_device { + new_field = "new_value" + volume_type = "gp2" } - - # (1 unchanged block hidden) + - root_block_device { + - volume_type = "gp2" -> null + } } `, }, @@ -3756,62 +3620,59 @@ func TestResourceChange_nestedMap(t *testing.T) { 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{ + "root_block_device": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), }), - "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("standard"), + }), + "disks": cty.SetVal([]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"), - "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"), - }), - }), - "root_block_device": cty.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ + "root_block_device": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("different"), }), - "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("standard"), + }), + "disks": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "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.StringVal("a")}, - }, + RequiredReplace: cty.NewPathSet( + cty.Path{cty.GetAttrStep{Name: "root_block_device"}}, cty.Path{cty.GetAttrStep{Name: "disks"}}, ), - Schema: testSchema(configschema.NestingMap), + Schema: testSchema(configschema.NestingSet), ExpectedOutput: ` # test_instance.example must be replaced -/+ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disks = { - ~ "disk_a" = { # forces replacement - ~ size = "50GB" -> "100GB" - # (1 unchanged attribute hidden) + ~ disks = [ + - { # forces replacement + - mount_point = "/var/diska" -> null + - size = "50GB" -> null }, - } + + { # forces replacement + + mount_point = "/var/diskb" + + size = "50GB" + }, + ] id = "i-02ae66f368e8518a9" - ~ root_block_device "a" { # forces replacement - ~ volume_type = "gp2" -> "different" + + root_block_device { # forces replacement + + volume_type = "different" + } + - root_block_device { # forces replacement + - volume_type = "gp2" -> null } - - # (1 unchanged block hidden) } `, }, @@ -3821,248 +3682,273 @@ func TestResourceChange_nestedMap(t *testing.T) { 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{ + "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{ + "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.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{ + "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(), - Schema: testSchemaPlus(configschema.NestingMap), + Schema: testSchemaPlus(configschema.NestingSet), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disks = { - - "disk_a" = { + ~ disks = [ + - { - mount_point = "/var/diska" -> null - size = "50GB" -> null }, - } + ] id = "i-02ae66f368e8518a9" - - root_block_device "a" { + - root_block_device { - new_field = "new_value" -> null - volume_type = "gp2" -> null } } `, }, - "in-place update - unknown": { + "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.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"), - }), - }), + "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.UnknownVal(cty.Map(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.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - "new_field": cty.StringVal("new_value"), - }), - }), + })), + "root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{ + "volume_type": cty.String, + })), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaPlus(configschema.NestingMap), + Schema: testSchema(configschema.NestingSet), 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) + + disks = [ + ] id = "i-02ae66f368e8518a9" - - # (1 unchanged block hidden) } `, }, - "in-place update - insertion sensitive": { + "in-place update - null 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.MapValEmpty(cty.Object(map[string]cty.Type{ + "disks": cty.NullVal(cty.Set(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{ + }))), + "root_block_device": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), - "new_field": cty.StringVal("new_value"), + "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.MapVal(map[string]cty.Value{ - "disk_a": cty.ObjectVal(map[string]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.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]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"), }), }), }), - 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), + Schema: testSchemaPlus(configschema.NestingSet), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" - ~ disks = { - + "disk_a" = { - + mount_point = (sensitive) + + disks = [ + + { + + mount_point = "/var/diska" + size = "50GB" }, - } + ] id = "i-02ae66f368e8518a9" - # (1 unchanged block hidden) + + root_block_device { + + new_field = "new_value" + + volume_type = "gp2" + } + - root_block_device { + - volume_type = "gp2" -> null + } } `, }, - "in-place update - multiple unchanged blocks": { + "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{ + "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.MapVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), - }), - "b": cty.ObjectVal(map[string]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"), }), }), }), 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{ + "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{ "volume_type": cty.StringVal("gp2"), + "new_field": cty.StringVal("new_value"), }), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingMap), + 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" -> null + - size = "50GB" -> null + }, + ] -> (known after apply) id = "i-02ae66f368e8518a9" - # (1 unchanged attribute hidden) - # (2 unchanged blocks hidden) + # (1 unchanged block hidden) } `, }, - "in-place update - multiple blocks first changed": { + } + 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.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-BEFORE"), + "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"), + "size": cty.NullVal(cty.String), }), }), "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"), - }), }), }), + 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" + } + } +`, + }, + "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.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, + })), + }), 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"), + "size": cty.NullVal(cty.String), }), }), "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(), @@ -4070,18 +3956,20 @@ 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 "b" { - ~ volume_type = "gp2" -> "gp3" + + root_block_device "a" { + + volume_type = "gp2" } - - # (1 unchanged block hidden) } `, }, - "in-place update - multiple blocks second changed": { + "in-place update - change attr": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ @@ -4090,15 +3978,13 @@ 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("50GB"), + "size": cty.NullVal(cty.String), }), }), "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.NullVal(cty.String), }), }), }), @@ -4113,30 +3999,32 @@ 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("gp3"), - }), - "b": cty.ObjectVal(map[string]cty.Value{ "volume_type": cty.StringVal("gp2"), + "new_field": cty.StringVal("new_value"), }), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingMap), + 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" = { + + size = "50GB" + # (1 unchanged attribute hidden) + }, + } id = "i-02ae66f368e8518a9" - # (1 unchanged attribute hidden) ~ root_block_device "a" { - ~ volume_type = "gp2" -> "gp3" + + new_field = "new_value" + # (1 unchanged attribute hidden) } - - # (1 unchanged block hidden) } `, }, - "in-place update - multiple blocks changed": { + "in-place update - insertion": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ @@ -4151,9 +4039,7 @@ 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"), - }), - "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), + "new_field": cty.NullVal(cty.String), }), }), }), @@ -4165,36 +4051,49 @@ 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("gp3"), + "volume_type": cty.StringVal("gp2"), + "new_field": cty.NullVal(cty.String), }), "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp3"), + "volume_type": cty.StringVal("gp2"), + "new_field": cty.StringVal("new_value"), }), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchema(configschema.NestingMap), + Schema: testSchemaPlus(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 "a" { - ~ volume_type = "gp2" -> "gp3" - } - ~ root_block_device "b" { - ~ volume_type = "gp2" -> "gp3" + + root_block_device "b" { + + new_field = "new_value" + + volume_type = "gp2" } + + # (1 unchanged block hidden) } `, }, - "in-place update - multiple different unchanged blocks": { - Action: plans.Update, - Mode: addrs.ManagedResourceMode, + "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"), @@ -4208,10 +4107,8 @@ 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("gp2"), + "volume_type": cty.StringVal("standard"), }), }), }), @@ -4221,33 +4118,45 @@ 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("50GB"), + "size": cty.StringVal("100GB"), }), }), "root_block_device": cty.MapVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), + "volume_type": cty.StringVal("different"), }), - }), - "leaf_block_device": cty.MapVal(map[string]cty.Value{ "b": cty.ObjectVal(map[string]cty.Value{ - "volume_type": cty.StringVal("gp2"), + "volume_type": cty.StringVal("standard"), }), }), }), - 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) + 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" { + ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disks = { + ~ "disk_a" = { # forces replacement + ~ size = "50GB" -> "100GB" + # (1 unchanged attribute hidden) + }, + } + id = "i-02ae66f368e8518a9" - # (2 unchanged blocks hidden) + ~ root_block_device "a" { # forces replacement + ~ volume_type = "gp2" -> "different" + } + + # (1 unchanged block hidden) } `, }, - "in-place update - multiple different blocks first changed": { + "in-place update - deletion": { Action: plans.Update, Mode: addrs.ManagedResourceMode, Before: cty.ObjectVal(map[string]cty.Value{ @@ -4262,9 +4171,167 @@ 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{ + }), + 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, + })), + }), + 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 = "/var/diska" -> null + - size = "50GB" -> null + }, + } + id = "i-02ae66f368e8518a9" + + - root_block_device "a" { + - new_field = "new_value" -> null + - volume_type = "gp2" -> null + } + } +`, + }, + "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"), + "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.Map(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"), + }), + }), + }), + 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 = "/var/diska" -> null + - size = "50GB" -> null + }, + } -> (known after apply) + id = "i-02ae66f368e8518a9" + + # (1 unchanged block hidden) + } +`, + }, + "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.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"), + "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"), + }), + }), + }), + 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) + + 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"), + }), + }), + "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"), }), @@ -4283,288 +4350,1060 @@ func TestResourceChange_nestedMap(t *testing.T) { "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{ + }), + RequiredReplace: cty.NewPathSet(), + Schema: testSchema(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 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"), + "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"), + "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"), }), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaMultipleBlocks(configschema.NestingMap), + Schema: testSchema(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) + + ~ root_block_device "b" { + ~ volume_type = "gp2" -> "gp3" + } + + # (1 unchanged block hidden) + } +`, + }, + "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.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"), + "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"), + }), + "b": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + }), + }), + }), + RequiredReplace: cty.NewPathSet(), + Schema: testSchema(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) + + ~ root_block_device "a" { + ~ volume_type = "gp2" -> "gp3" + } + + # (1 unchanged block hidden) + } +`, + }, + "in-place update - multiple blocks 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.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"), + "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"), + }), + "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-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" + } + } +`, + }, + "in-place update - multiple different 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"), + }), + }), + "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"), + }), + }), + }), + 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"), + }), + }), + "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: 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, + 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"), + }), + }), + }), + 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"), + }), + }), + "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: 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" + } + + # (1 unchanged block hidden) + } +`, + }, + "in-place update - multiple different 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.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"), + }), + }), + }), + 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"), + }), + }), + }), + 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) + + ~ root_block_device "a" { + ~ volume_type = "gp2" -> "gp3" + } + + # (1 unchanged block hidden) + } +`, + }, + "in-place update - multiple different blocks 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.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"), + }), + }), + }), + 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"), + }), + }), + }), + 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" + } + + ~ root_block_device "a" { + ~ volume_type = "gp2" -> "gp3" + } + } +`, + }, + "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.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"), + }), + }), + }), + 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"), + }), + }), + }), + 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) + + # (4 unchanged blocks hidden) + } +`, + }, + "in-place update - mixed blocks 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.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"), + }), + }), + }), + 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"), + }), + }), + "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: 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" + } + + # (2 unchanged blocks hidden) + } +`, + }, + } + runTestCases(t, testCases) +} + +func TestResourceChange_nestedMapSensitiveSchema(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.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, + }))), + }), + 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.NullVal(cty.String), + }), + }), + }), + RequiredReplace: cty.NewPathSet(), + Schema: testSchemaSensitive(configschema.NestingMap), + ExpectedOutput: ` # test_instance.example will be updated in-place + ~ resource "test_instance" "example" { + + ami = "ami-AFTER" + + disks = (sensitive value) + + id = "i-02ae66f368e8518a9" + } +`, + }, + "in-place update": { + 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, + })), + }), + 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.NullVal(cty.String), + }), + }), + }), + RequiredReplace: cty.NewPathSet(), + Schema: testSchemaSensitive(configschema.NestingMap), + ExpectedOutput: ` # test_instance.example will be updated in-place + ~ resource "test_instance" "example" { + ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disks = (sensitive value) + id = "i-02ae66f368e8518a9" + } +`, + }, + "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"), + }), + }), + }), + 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("100GB"), + }), + }), + }), + RequiredReplace: cty.NewPathSet( + cty.Path{cty.GetAttrStep{Name: "disks"}}, + ), + Schema: testSchemaSensitive(configschema.NestingMap), + ExpectedOutput: ` # test_instance.example must be replaced +-/+ resource "test_instance" "example" { + ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disks = (sensitive value) # forces replacement + id = "i-02ae66f368e8518a9" + } +`, + }, + "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"), + }), + }), + }), + After: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("i-02ae66f368e8518a9"), + "ami": cty.StringVal("ami-AFTER"), + "disks": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{ + "mount_point": cty.String, + "size": cty.String, + }))), + }), + RequiredReplace: cty.NewPathSet(), + Schema: testSchemaSensitive(configschema.NestingMap), + ExpectedOutput: ` # test_instance.example will be updated in-place + ~ resource "test_instance" "example" { + ~ ami = "ami-BEFORE" -> "ami-AFTER" + - disks = (sensitive value) + id = "i-02ae66f368e8518a9" + } +`, + }, + "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"), + }), + }), + }), + 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, + }))), + }), + RequiredReplace: cty.NewPathSet(), + Schema: testSchemaSensitive(configschema.NestingMap), + ExpectedOutput: ` # test_instance.example will be updated in-place + ~ resource "test_instance" "example" { + ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disks = (sensitive value) + id = "i-02ae66f368e8518a9" + } +`, + }, + } + runTestCases(t, testCases) +} + +func TestResourceChange_nestedListSensitiveSchema(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.NullVal(cty.String), + "ami": cty.NullVal(cty.String), + "disks": cty.NullVal(cty.List(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), + }), + }), + }), + RequiredReplace: cty.NewPathSet(), + Schema: testSchemaSensitive(configschema.NestingList), + ExpectedOutput: ` # test_instance.example will be updated in-place + ~ resource "test_instance" "example" { + + ami = "ami-AFTER" + + disks = (sensitive value) + + id = "i-02ae66f368e8518a9" + } +`, + }, + "in-place update": { + 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.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), + }), + }), + }), + RequiredReplace: cty.NewPathSet(), + Schema: testSchemaSensitive(configschema.NestingList), + ExpectedOutput: ` # test_instance.example will be updated in-place + ~ resource "test_instance" "example" { + ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disks = (sensitive value) + id = "i-02ae66f368e8518a9" + } +`, + }, + "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{ + 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.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.StringVal("100GB"), + }), + }), + }), + RequiredReplace: cty.NewPathSet( + cty.Path{cty.GetAttrStep{Name: "disks"}}, + ), + Schema: testSchemaSensitive(configschema.NestingList), + ExpectedOutput: ` # test_instance.example must be replaced +-/+ resource "test_instance" "example" { + ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disks = (sensitive value) # forces replacement + id = "i-02ae66f368e8518a9" + } +`, + }, + "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{ + 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.NullVal(cty.List(cty.Object(map[string]cty.Type{ + "mount_point": cty.String, + "size": cty.String, + }))), + }), + RequiredReplace: cty.NewPathSet(), + Schema: testSchemaSensitive(configschema.NestingList), + ExpectedOutput: ` # test_instance.example will be updated in-place + ~ resource "test_instance" "example" { + ~ ami = "ami-BEFORE" -> "ami-AFTER" + - disks = (sensitive value) + id = "i-02ae66f368e8518a9" + } +`, + }, + "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.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"), + "disks": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{ + "mount_point": cty.String, + "size": cty.String, + }))), + }), + RequiredReplace: cty.NewPathSet(), + Schema: testSchemaSensitive(configschema.NestingList), + ExpectedOutput: ` # test_instance.example will be updated in-place + ~ resource "test_instance" "example" { + ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disks = (sensitive value) + id = "i-02ae66f368e8518a9" + } +`, + }, + } + runTestCases(t, testCases) +} + +func TestResourceChange_nestedSetSensitiveSchema(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.NullVal(cty.String), + "ami": cty.NullVal(cty.String), + "disks": cty.NullVal(cty.Set(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.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "mount_point": cty.StringVal("/var/diska"), + "size": cty.NullVal(cty.String), + }), + }), + }), + RequiredReplace: cty.NewPathSet(), + Schema: testSchemaSensitive(configschema.NestingSet), 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" - } - - # (1 unchanged block hidden) + + ami = "ami-AFTER" + + disks = (sensitive value) + + id = "i-02ae66f368e8518a9" } `, }, - "in-place update - multiple different blocks second changed": { + "in-place update": { 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"), - }), - }), + "disks": cty.SetValEmpty(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{ + "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.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"), + "size": cty.NullVal(cty.String), }), }), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaMultipleBlocks(configschema.NestingMap), + Schema: testSchemaSensitive(configschema.NestingSet), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disks = (sensitive value) id = "i-02ae66f368e8518a9" - # (1 unchanged attribute hidden) - - ~ root_block_device "a" { - ~ volume_type = "gp2" -> "gp3" - } - - # (1 unchanged block hidden) } `, }, - "in-place update - multiple different blocks changed": { - Action: plans.Update, - Mode: addrs.ManagedResourceMode, + "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{ + "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.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"), - }), - }), }), 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{ + "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.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"), + "size": cty.StringVal("100GB"), }), }), }), - RequiredReplace: cty.NewPathSet(), - Schema: testSchemaMultipleBlocks(configschema.NestingMap), - ExpectedOutput: ` # test_instance.example will be updated in-place - ~ resource "test_instance" "example" { + RequiredReplace: cty.NewPathSet( + cty.Path{cty.GetAttrStep{Name: "disks"}}, + ), + Schema: testSchemaSensitive(configschema.NestingSet), + ExpectedOutput: ` # test_instance.example must be replaced +-/+ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disks = (sensitive value) # forces replacement id = "i-02ae66f368e8518a9" - # (1 unchanged attribute hidden) - - ~ leaf_block_device "b" { - ~ volume_type = "gp2" -> "gp3" - } - - ~ root_block_device "a" { - ~ volume_type = "gp2" -> "gp3" - } } `, }, - "in-place update - mixed blocks unchanged": { + "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{ + "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.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"), - }), - }), }), 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"), - }), - }), + "disks": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ + "mount_point": cty.String, + "size": cty.String, + }))), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaMultipleBlocks(configschema.NestingMap), + Schema: testSchemaSensitive(configschema.NestingSet), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" + - disks = (sensitive value) id = "i-02ae66f368e8518a9" - # (1 unchanged attribute hidden) - - # (4 unchanged blocks hidden) } `, }, - "in-place update - mixed blocks changed": { + "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{ + "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.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"), - }), - }), }), 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"), - }), - }), - "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"), - }), - }), + "disks": cty.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{ + "mount_point": cty.String, + "size": cty.String, + }))), }), RequiredReplace: cty.NewPathSet(), - Schema: testSchemaMultipleBlocks(configschema.NestingMap), + Schema: testSchemaSensitive(configschema.NestingSet), ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { ~ ami = "ami-BEFORE" -> "ami-AFTER" + ~ disks = (sensitive value) id = "i-02ae66f368e8518a9" - # (1 unchanged attribute hidden) - - ~ leaf_block_device "b" { - ~ volume_type = "gp2" -> "gp3" - } - - ~ root_block_device "b" { - ~ volume_type = "gp2" -> "gp3" - } - - # (2 unchanged blocks hidden) } `, }, @@ -5301,7 +6140,7 @@ func TestResourceChange_sensitiveVariable(t *testing.T) { }, ExpectedOutput: ` # test_instance.example will be updated in-place ~ resource "test_instance" "example" { - ~ ami = (sensitive) + ~ ami = (sensitive value) id = "i-02ae66f368e8518a9" ~ list_field = [ - (sensitive), @@ -5685,7 +6524,7 @@ func TestResourceChange_sensitiveVariable(t *testing.T) { ), ExpectedOutput: ` # test_instance.example must be replaced -/+ resource "test_instance" "example" { - ~ ami = (sensitive value) # forces replacement + ~ ami = (sensitive) # forces replacement id = "i-02ae66f368e8518a9" } `, @@ -5728,7 +6567,7 @@ func TestResourceChange_sensitiveVariable(t *testing.T) { ExpectedOutput: ` # test_instance.example must be replaced -/+ resource "test_instance" "example" { ~ conn_info = { # forces replacement - ~ password = (sensitive value) + ~ password = (sensitive) # (1 unchanged attribute hidden) } id = "i-02ae66f368e8518a9" @@ -5985,7 +6824,7 @@ func TestOutputChanges(t *testing.T) { }, ` ~ a = 1 -> 2 - ~ b = (sensitive value) + ~ b = (sensitive) ~ c = false -> true`, }, } @@ -6023,11 +6862,16 @@ func outputChange(name string, before, after cty.Value, sensitive bool) *plans.O // A basic test schema using a configurable NestingMode for one (NestedType) attribute and one block func testSchema(nesting configschema.NestingMode) *configschema.Block { + var diskKey = "disks" + if nesting == configschema.NestingSingle { + diskKey = "disk" + } + return &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "id": {Type: cty.String, Optional: true, Computed: true}, "ami": {Type: cty.String, Optional: true}, - "disks": { + diskKey: { NestedType: &configschema.Object{ Attributes: map[string]*configschema.Attribute{ "mount_point": {Type: cty.String, Optional: true}, @@ -6054,6 +6898,27 @@ func testSchema(nesting configschema.NestingMode) *configschema.Block { } } +// A basic test schema using a configurable NestingMode for one (NestedType) +// attribute marked sensitive. +func testSchemaSensitive(nesting configschema.NestingMode) *configschema.Block { + return &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Optional: true, Computed: true}, + "ami": {Type: cty.String, Optional: true}, + "disks": { + Sensitive: true, + NestedType: &configschema.Object{ + Attributes: map[string]*configschema.Attribute{ + "mount_point": {Type: cty.String, Optional: true}, + "size": {Type: cty.String, Optional: true}, + }, + Nesting: nesting, + }, + }, + }, + } +} + func testSchemaMultipleBlocks(nesting configschema.NestingMode) *configschema.Block { return &configschema.Block{ Attributes: map[string]*configschema.Attribute{ @@ -6100,11 +6965,16 @@ func testSchemaMultipleBlocks(nesting configschema.NestingMode) *configschema.Bl // similar to testSchema with the addition of a "new_field" block func testSchemaPlus(nesting configschema.NestingMode) *configschema.Block { + var diskKey = "disks" + if nesting == configschema.NestingSingle { + diskKey = "disk" + } + return &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "id": {Type: cty.String, Optional: true, Computed: true}, "ami": {Type: cty.String, Optional: true}, - "disks": { + diskKey: { NestedType: &configschema.Object{ Attributes: map[string]*configschema.Attribute{ "mount_point": {Type: cty.String, Optional: true},