Skip to content

Commit e5f3b93

Browse files
committed
refactor: move readback preservation into codegen policy
Closes: #1289
1 parent 0dc06cc commit e5f3b93

File tree

4 files changed

+55
-9
lines changed

4 files changed

+55
-9
lines changed

internal/codegen/generator/ir.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,11 @@ type FieldIR struct {
141141
// PreserveUserIntent means this optional field should only be set if user specified it
142142
PreserveUserIntent bool
143143

144+
// PreservePlannedValueOnReadbackOmit means this optional scalar field should
145+
// preserve the planned value when libvirt omits or canonicalizes it on
146+
// readback.
147+
PreservePlannedValueOnReadbackOmit bool
148+
144149
// StringToBool indicates this string field should be boolean in TF
145150
StringToBool *StringToBoolPattern
146151

internal/codegen/policy/field_policy.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ func applyStructPolicies(s *generator.StructIR) {
1919
applyTopLevelIdentityPolicy(s, field)
2020
applyTopLevelImmutabilityPolicy(s, field)
2121
applyReportedFieldPolicy(s, field)
22+
applyReadbackPreservationPolicy(s, field)
2223
}
2324
}
2425

@@ -91,3 +92,16 @@ func applyReportedFieldPolicy(s *generator.StructIR, field *generator.FieldIR) {
9192
}
9293
}
9394
}
95+
96+
func applyReadbackPreservationPolicy(s *generator.StructIR, field *generator.FieldIR) {
97+
switch s.Name {
98+
case "DomainCPU":
99+
if field.TFName == "mode" {
100+
field.PreservePlannedValueOnReadbackOmit = true
101+
}
102+
case "DomainGraphicSpice":
103+
if field.TFName == "listen" {
104+
field.PreservePlannedValueOnReadbackOmit = true
105+
}
106+
}
107+
}

internal/codegen/policy/field_policy_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,29 @@ func TestApplyFieldPoliciesLeavesNestedIdentityUserManaged(t *testing.T) {
5050
t.Fatalf("expected nested id to have no plan modifier, got %q", field.PlanModifier)
5151
}
5252
}
53+
54+
func TestApplyFieldPoliciesMarksReadbackPreservationOverrides(t *testing.T) {
55+
structs := []*generator.StructIR{
56+
{
57+
Name: "DomainCPU",
58+
Fields: []*generator.FieldIR{
59+
{TFName: "mode"},
60+
},
61+
},
62+
{
63+
Name: "DomainGraphicSpice",
64+
Fields: []*generator.FieldIR{
65+
{TFName: "listen"},
66+
},
67+
},
68+
}
69+
70+
ApplyFieldPolicies(structs)
71+
72+
if !structs[0].Fields[0].PreservePlannedValueOnReadbackOmit {
73+
t.Fatal("expected DomainCPU.mode to preserve planned value on omitted readback")
74+
}
75+
if !structs[1].Fields[0].PreservePlannedValueOnReadbackOmit {
76+
t.Fatal("expected DomainGraphicSpice.listen to preserve planned value on omitted readback")
77+
}
78+
}

internal/codegen/templates/convert.go.tmpl

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -202,19 +202,20 @@ func {{ .Name }}FromXML(ctx context.Context, xml *libvirtxml.{{ .Name }}, plan *
202202
} else {
203203
model.Disks = types.ListNull(types.ObjectType{AttrTypes: DomainDiskAttributeTypes()})
204204
}
205-
{{- else if and (eq $structName "DomainCPU") (eq .GoName "Mode") }}
206-
// Optional field: Mode
207-
// Preserve explicit user mode to avoid libvirt normalization drift (e.g. host-model -> custom).
205+
{{- else if .PreservePlannedValueOnReadbackOmit }}
206+
// Optional field: {{ .GoName }}
207+
// Preserve the planned value when libvirt omits or canonicalizes this field on
208+
// readback, to avoid drift for user-managed optional scalars.
208209
if plan == nil {
209-
if xml.Mode != "" {
210-
model.Mode = types.StringValue(xml.Mode)
210+
if xml.{{ .GoName }} != "" {
211+
model.{{ .GoName }} = types.StringValue(xml.{{ .GoName }})
211212
} else {
212-
model.Mode = types.StringNull()
213+
model.{{ .GoName }} = types.StringNull()
213214
}
214-
} else if !plan.Mode.IsNull() && !plan.Mode.IsUnknown() {
215-
model.Mode = plan.Mode
215+
} else if !plan.{{ .GoName }}.IsNull() && !plan.{{ .GoName }}.IsUnknown() {
216+
model.{{ .GoName }} = plan.{{ .GoName }}
216217
} else {
217-
model.Mode = types.StringNull()
218+
model.{{ .GoName }} = types.StringNull()
218219
}
219220
{{- else }}
220221
{{ template "fromXMLField" . }}

0 commit comments

Comments
 (0)