Skip to content

Commit bd8cae3

Browse files
authored
Merge pull request #29398 from hashicorp/jbardin/diff-nested-attrs
Empty nested attribute handling in diffs
2 parents 8407ce7 + 296a757 commit bd8cae3

File tree

2 files changed

+116
-4
lines changed

2 files changed

+116
-4
lines changed

internal/command/format/diff.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -471,8 +471,28 @@ func (p *blockBodyDiffPrinter) writeAttrDiff(name string, attrS *configschema.At
471471
}
472472

473473
if attrS.NestedType != nil {
474-
p.writeNestedAttrDiff(name, attrS.NestedType, old, new, nameLen, indent, path, action, showJustNew)
475-
return false
474+
renderNested := true
475+
476+
// If the collection values are empty or null, we render them as single attributes
477+
switch attrS.NestedType.Nesting {
478+
case configschema.NestingList, configschema.NestingSet, configschema.NestingMap:
479+
var oldLen, newLen int
480+
if !old.IsNull() && old.IsKnown() {
481+
oldLen = old.LengthInt()
482+
}
483+
if !new.IsNull() && new.IsKnown() {
484+
newLen = new.LengthInt()
485+
}
486+
487+
if oldLen+newLen == 0 {
488+
renderNested = false
489+
}
490+
}
491+
492+
if renderNested {
493+
p.writeNestedAttrDiff(name, attrS.NestedType, old, new, nameLen, indent, path, action, showJustNew)
494+
return false
495+
}
476496
}
477497

478498
p.buf.WriteString("\n")
@@ -613,6 +633,7 @@ func (p *blockBodyDiffPrinter) writeNestedAttrDiff(
613633
allItems := make([]cty.Value, 0, len(oldItems)+len(newItems))
614634
allItems = append(allItems, oldItems...)
615635
allItems = append(allItems, newItems...)
636+
616637
all := cty.SetVal(allItems)
617638

618639
p.buf.WriteString(" = [")
@@ -625,11 +646,11 @@ func (p *blockBodyDiffPrinter) writeNestedAttrDiff(
625646
case !val.IsKnown():
626647
action = plans.Update
627648
newValue = val
628-
case !old.HasElement(val).True():
649+
case old.IsNull() || !old.HasElement(val).True():
629650
action = plans.Create
630651
oldValue = cty.NullVal(val.Type())
631652
newValue = val
632-
case !new.HasElement(val).True():
653+
case new.IsNull() || !new.HasElement(val).True():
633654
action = plans.Delete
634655
oldValue = val
635656
newValue = cty.NullVal(val.Type())

internal/command/format/diff_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2861,6 +2861,97 @@ func TestResourceChange_nestedSet(t *testing.T) {
28612861
- volume_type = "gp2" -> null
28622862
}
28632863
}
2864+
`,
2865+
},
2866+
"in-place update - empty nested sets": {
2867+
Action: plans.Update,
2868+
Mode: addrs.ManagedResourceMode,
2869+
Before: cty.ObjectVal(map[string]cty.Value{
2870+
"id": cty.StringVal("i-02ae66f368e8518a9"),
2871+
"ami": cty.StringVal("ami-BEFORE"),
2872+
"disks": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
2873+
"mount_point": cty.String,
2874+
"size": cty.String,
2875+
}))),
2876+
"root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{
2877+
"volume_type": cty.String,
2878+
})),
2879+
}),
2880+
After: cty.ObjectVal(map[string]cty.Value{
2881+
"id": cty.StringVal("i-02ae66f368e8518a9"),
2882+
"ami": cty.StringVal("ami-AFTER"),
2883+
"disks": cty.SetValEmpty(cty.Object(map[string]cty.Type{
2884+
"mount_point": cty.String,
2885+
"size": cty.String,
2886+
})),
2887+
"root_block_device": cty.SetValEmpty(cty.Object(map[string]cty.Type{
2888+
"volume_type": cty.String,
2889+
})),
2890+
}),
2891+
RequiredReplace: cty.NewPathSet(),
2892+
Schema: testSchema(configschema.NestingSet),
2893+
ExpectedOutput: ` # test_instance.example will be updated in-place
2894+
~ resource "test_instance" "example" {
2895+
~ ami = "ami-BEFORE" -> "ami-AFTER"
2896+
+ disks = []
2897+
id = "i-02ae66f368e8518a9"
2898+
}
2899+
`,
2900+
},
2901+
"in-place update - null insertion": {
2902+
Action: plans.Update,
2903+
Mode: addrs.ManagedResourceMode,
2904+
Before: cty.ObjectVal(map[string]cty.Value{
2905+
"id": cty.StringVal("i-02ae66f368e8518a9"),
2906+
"ami": cty.StringVal("ami-BEFORE"),
2907+
"disks": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
2908+
"mount_point": cty.String,
2909+
"size": cty.String,
2910+
}))),
2911+
"root_block_device": cty.SetVal([]cty.Value{
2912+
cty.ObjectVal(map[string]cty.Value{
2913+
"volume_type": cty.StringVal("gp2"),
2914+
"new_field": cty.NullVal(cty.String),
2915+
}),
2916+
}),
2917+
}),
2918+
After: cty.ObjectVal(map[string]cty.Value{
2919+
"id": cty.StringVal("i-02ae66f368e8518a9"),
2920+
"ami": cty.StringVal("ami-AFTER"),
2921+
"disks": cty.SetVal([]cty.Value{
2922+
cty.ObjectVal(map[string]cty.Value{
2923+
"mount_point": cty.StringVal("/var/diska"),
2924+
"size": cty.StringVal("50GB"),
2925+
}),
2926+
}),
2927+
"root_block_device": cty.SetVal([]cty.Value{
2928+
cty.ObjectVal(map[string]cty.Value{
2929+
"volume_type": cty.StringVal("gp2"),
2930+
"new_field": cty.StringVal("new_value"),
2931+
}),
2932+
}),
2933+
}),
2934+
RequiredReplace: cty.NewPathSet(),
2935+
Schema: testSchemaPlus(configschema.NestingSet),
2936+
ExpectedOutput: ` # test_instance.example will be updated in-place
2937+
~ resource "test_instance" "example" {
2938+
~ ami = "ami-BEFORE" -> "ami-AFTER"
2939+
+ disks = [
2940+
+ {
2941+
+ mount_point = "/var/diska"
2942+
+ size = "50GB"
2943+
},
2944+
]
2945+
id = "i-02ae66f368e8518a9"
2946+
2947+
+ root_block_device {
2948+
+ new_field = "new_value"
2949+
+ volume_type = "gp2"
2950+
}
2951+
- root_block_device {
2952+
- volume_type = "gp2" -> null
2953+
}
2954+
}
28642955
`,
28652956
},
28662957
}

0 commit comments

Comments
 (0)