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) 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) {