From 567ff60b648714b5605cc0d33f65196fd072c8ba Mon Sep 17 00:00:00 2001 From: Kara O'Dell <33235324+kro-cat@users.noreply.github.com> Date: Thu, 14 Aug 2025 17:19:34 -0500 Subject: [PATCH 1/2] don't error if the key doesn't exist and value is null. --- manifest/morph/morph.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/manifest/morph/morph.go b/manifest/morph/morph.go index 36f14666c9..074d3e58ec 100644 --- a/manifest/morph/morph.go +++ b/manifest/morph/morph.go @@ -597,12 +597,14 @@ func morphObjectToType(v tftypes.Value, t tftypes.Type, p *tftypes.AttributePath elp := p.WithAttributeName(k) nt, ok := t.(tftypes.Object).AttributeTypes[k] if !ok { - diags = append(diags, &tfprotov5.Diagnostic{ - Attribute: p, - Severity: tfprotov5.DiagnosticSeverityWarning, - Summary: "Attribute not found in schema", - Detail: fmt.Sprintf("Unable to find schema type for attribute:\n%s", attributePathSummary(elp)), - }) + if !v.IsNull() { + diags = append(diags, &tfprotov5.Diagnostic{ + Attribute: p, + Severity: tfprotov5.DiagnosticSeverityWarning, + Summary: "Attribute not found in schema", + Detail: fmt.Sprintf("Unable to find schema type for attribute:\n%s", attributePathSummary(elp)), + }) + } continue } nv, d := ValueToType(v, nt, elp) From e18bcb54dd162a0e8671c3beba6355732d46f48f Mon Sep 17 00:00:00 2001 From: Kara O'Dell <33235324+kro-cat@users.noreply.github.com> Date: Mon, 18 Aug 2025 10:50:39 -0500 Subject: [PATCH 2/2] add unit functionality tests for null value handling and morphing --- manifest/morph/morph_test.go | 99 ++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/manifest/morph/morph_test.go b/manifest/morph/morph_test.go index 03458534f7..d021182d15 100644 --- a/manifest/morph/morph_test.go +++ b/manifest/morph/morph_test.go @@ -509,6 +509,105 @@ func TestMorphValueToType(t *testing.T) { }), }), }, + // morphing with null-valued attributes + "object -> object (null)": { + In: sampleInType{ + V: tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{ + "two": tftypes.String, + "three": tftypes.String, + }}, map[string]tftypes.Value{ + "two": tftypes.NewValue(tftypes.String, "stuff"), + "three": tftypes.NewValue(tftypes.String, nil), + }), + T: tftypes.Object{AttributeTypes: map[string]tftypes.Type{ + "one": tftypes.Number, + "two": tftypes.String, + }}, + }, + Out: tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{ + "one": tftypes.Number, + "two": tftypes.String, + }}, map[string]tftypes.Value{ + "one": tftypes.NewValue(tftypes.Number, nil), + "two": tftypes.NewValue(tftypes.String, "stuff"), + }), + }, + "object -> object (deep null)": { + In: sampleInType{ + V: tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{ + "two": tftypes.String, + "three": tftypes.Object{AttributeTypes: map[string]tftypes.Type{ + "foo": tftypes.String, + "bar": tftypes.String, + }}, + }}, map[string]tftypes.Value{ + "two": tftypes.NewValue(tftypes.String, "stuff"), + "three": tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{ + "foo": tftypes.String, + "bar": tftypes.String, + }}, map[string]tftypes.Value{ + "foo": tftypes.NewValue(tftypes.String, "fourtytwo"), + "bar": tftypes.NewValue(tftypes.String, nil), + }), + }), + T: tftypes.Object{AttributeTypes: map[string]tftypes.Type{ + "one": tftypes.Number, + "two": tftypes.String, + "three": tftypes.Object{AttributeTypes: map[string]tftypes.Type{"foo": tftypes.String}}, + }}, + }, + Out: tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{ + "one": tftypes.Number, + "two": tftypes.String, + "three": tftypes.Object{AttributeTypes: map[string]tftypes.Type{"foo": tftypes.String}}, + }}, map[string]tftypes.Value{ + "one": tftypes.NewValue(tftypes.Number, nil), + "two": tftypes.NewValue(tftypes.String, "stuff"), + "three": tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{ + "foo": tftypes.String, + }}, map[string]tftypes.Value{ + "foo": tftypes.NewValue(tftypes.String, "fourtytwo"), + }), + }), + }, + "object -> object (aggregate null)": { + In: sampleInType{ + V: tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{ + "two": tftypes.String, + "three": tftypes.Object{AttributeTypes: map[string]tftypes.Type{ + "foo": tftypes.String, + "bar": tftypes.String, + }}, + }}, map[string]tftypes.Value{ + "two": tftypes.NewValue(tftypes.String, "stuff"), + "three": tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{ + "foo": tftypes.String, + "bar": tftypes.String, + }}, map[string]tftypes.Value{ + "foo": tftypes.NewValue(tftypes.String, nil), + "bar": tftypes.NewValue(tftypes.String, nil), + }), + }), + T: tftypes.Object{AttributeTypes: map[string]tftypes.Type{ + "one": tftypes.Number, + "two": tftypes.String, + "three": tftypes.Object{AttributeTypes: map[string]tftypes.Type{"foo": tftypes.String}}, + }}, + }, + Out: tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{ + "one": tftypes.Number, + "two": tftypes.String, + "three": tftypes.Object{AttributeTypes: map[string]tftypes.Type{"foo": tftypes.String}}, + }}, map[string]tftypes.Value{ + "one": tftypes.NewValue(tftypes.Number, nil), + "two": tftypes.NewValue(tftypes.String, "stuff"), + "three": tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{ + "foo": tftypes.String, + }}, map[string]tftypes.Value{ + "foo": tftypes.NewValue(tftypes.String, nil), + }), + }), + }, } for n, s := range samples { t.Run(n, func(t *testing.T) {