Skip to content

Commit 5d14d97

Browse files
committed
fix: preserve live-only fields in three-way merge for unstructured objects
- Change drift detection to apply new onto current (preserves live-only fields) - Align cleanMetadataForPatch with deleteStatusAndTidyMetadata (add helm annotations) - Update test case: manual annotations should be preserved, not cause a diff This ensures three-way merge behavior matches strategic merge patch: - Live-only fields (e.g., manual annotations) are preserved - Drift on chart-owned fields (e.g., spec fields) is still detected Signed-off-by: yxxhero <aiopsclub@163.com>
1 parent a59ce03 commit 5d14d97

2 files changed

Lines changed: 28 additions & 13 deletions

File tree

manifest/generate.go

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,8 @@ func createPatch(originalObj, currentObj runtime.Object, target *resource.Info)
178178
// 2. Apply this patch to current (live state with manual changes)
179179
// 3. Create a patch from current -> merged result
180180
// 4. If chart changed (old != new), return step 3's patch
181-
// 5. If chart unchanged (old == new) but current != new, return current->new patch applied to current to restore chart state
182-
// (i.e., detect and revert manual changes by restoring chart-owned fields to the chart's desired state)
181+
// 5. If chart unchanged (old == new), build desired state by applying new onto current
182+
// (preserving live-only fields), then diff current -> desired to detect drift
183183

184184
// Clean metadata fields that shouldn't be compared (they're server-managed)
185185
// This prevents "resourceVersion: Invalid value: 0" errors when dry-running patches
@@ -219,10 +219,14 @@ func createPatch(originalObj, currentObj runtime.Object, target *resource.Info)
219219
}
220220

221221
// Chart didn't change (old == new), but we need to detect if current diverges
222-
// from the chart state. This is the case where manual changes were made to
223-
// chart-owned fields.
224-
// Create a patch from current -> new to detect and restore drift
225-
patch, err := jsonpatch.CreateMergePatch(cleanedCurrentData, cleanedNewData)
222+
// from the chart state on chart-owned fields.
223+
// Build desired state by applying new onto current (preserves live-only additions),
224+
// then diff current -> desired to detect drift on chart-owned fields.
225+
desiredData, err := jsonpatch.MergePatch(cleanedCurrentData, cleanedNewData)
226+
if err != nil {
227+
return nil, types.MergePatchType, fmt.Errorf("building desired state: %w", err)
228+
}
229+
patch, err := jsonpatch.CreateMergePatch(cleanedCurrentData, desiredData)
226230
return patch, types.MergePatchType, err
227231
}
228232

@@ -245,16 +249,27 @@ func cleanMetadataForPatch(data []byte) ([]byte, error) {
245249
return nil, err
246250
}
247251

252+
delete(objMap, "status")
253+
248254
if metadata, ok := objMap["metadata"].(map[string]interface{}); ok {
249-
delete(metadata, "resourceVersion")
255+
delete(metadata, "managedFields")
250256
delete(metadata, "generation")
251257
delete(metadata, "creationTimestamp")
258+
delete(metadata, "resourceVersion")
252259
delete(metadata, "uid")
253-
delete(metadata, "managedFields")
254-
delete(metadata, "selfLink")
255-
}
256260

257-
delete(objMap, "status")
261+
// Remove helm-related and k8s annotations that shouldn't be compared
262+
if a := metadata["annotations"]; a != nil {
263+
annotations := a.(map[string]interface{})
264+
delete(annotations, "meta.helm.sh/release-name")
265+
delete(annotations, "meta.helm.sh/release-namespace")
266+
delete(annotations, "deployment.kubernetes.io/revision")
267+
268+
if len(annotations) == 0 {
269+
delete(metadata, "annotations")
270+
}
271+
}
272+
}
258273

259274
return json.Marshal(objMap)
260275
}

manifest/generate_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ func TestCreatePatchForUnstructured(t *testing.T) {
7676
},
7777
},
7878
},
79-
expectedChange: true,
80-
description: "Manual annotation that is not in chart will cause diff to remove it (JSON merge patch semantics)",
79+
expectedChange: false,
80+
description: "Manual annotation not present in chart should be preserved; no effective change expected from three-way merge",
8181
},
8282
{
8383
name: "CR with no manual changes",

0 commit comments

Comments
 (0)