Add support for drift detection ignore rules#1627
Conversation
029fa79 to
02e1e73
Compare
|
Any specific reason for the API to not match the API of the helm-controller? |
02e1e73 to
e688fe8
Compare
We discussed this at the dev meetings. KC does drift detection and correction by design, so the envelop we use in HC would be confusing here, people would expect |
e688fe8 to
d0de310
Compare
Signed-off-by: Dipti Pai <diptipai89@outlook.com>
d0de310 to
b938333
Compare
Changes include:
DriftIgnoreRules[]tokustomizationspec.[]jsondiff.IgnoreRuleand set inApplyOptionsFix: #1138
Test summary with kustomize-controller
E2E Test Results — DriftIgnoreRules
Setup
Source manifests (in
ssaDriftIgnoreRulesTest/) deploy:test-config—managed-key: "v1",ignored-key: "original",another-ignored: "keep-me"test-nginx—nginx:1.25, 1 replica, with resource requests/limitstest-service— with annotationexternal-dns.alpha.kubernetes.io/hostname: "test.example.com"Reconciliation verification approach
Each test triggers reconciliation after introducing drift, checks the controller
logs and confirms non-ignored fields are corrected while ignored fields are preserved.
Test 1 — Ignore
/spec/replicason Deployments (HPA use case)Kustomization:
Initial state:
Simulate HPA scaling + image tamper (server-side apply with
hpa-controllerfield manager):After reconciliation — resource verification:
Controller log:
{ "level": "info", "ts": "2026-05-12T16:37:45.163Z", "msg": "server-side apply completed", "controller": "kustomization", "controllerGroup": "kustomize.toolkit.fluxcd.io", "controllerKind": "Kustomization", "Kustomization": { "name": "test-basic-ignore", "namespace": "drift-verbose-1" }, "namespace": "drift-verbose-1", "name": "test-basic-ignore", "reconcileID": "b9d220b6-7b6c-404f-a7e1-d0f2a9c07eb7", "output": { "ConfigMap/drift-verbose-1/test-config": "unchanged", "Deployment/drift-verbose-1/test-nginx": "configured", "Namespace/drift-verbose-1": "unchanged", "Service/drift-verbose-1/test-service": "unchanged" }, "revision": "main@sha1:b2a96870abdf6a295501e298a147e1e4ea5d878a" }Test 2 — Targeted selectors (external-dns + ConfigMap field)
Kustomization:
Initial state:
Simulate external-dns and controller changes (server-side apply):
After reconciliation — resource verification:
Controller log:
{ "level": "info", "ts": "2026-05-12T16:38:27.539Z", "msg": "server-side apply completed", "controller": "kustomization", "controllerGroup": "kustomize.toolkit.fluxcd.io", "controllerKind": "Kustomization", "Kustomization": { "name": "test-targeted-ignore", "namespace": "drift-verbose-2" }, "namespace": "drift-verbose-2", "name": "test-targeted-ignore", "reconcileID": "b890bdfe-aec9-4418-b5ce-8c7f8a4c6c93", "output": { "ConfigMap/drift-verbose-2/test-config": "configured", "Deployment/drift-verbose-2/test-nginx": "unchanged", "Namespace/drift-verbose-2": "unchanged", "Service/drift-verbose-2/test-service": "unchanged" }, "revision": "main@sha1:b2a96870abdf6a295501e298a147e1e4ea5d878a" }Test 3 — Multi-resource (Deployment replicas + Service annotations + ConfigMap fields)
Kustomization:
Out-of-band changes via server-side apply (3 different field managers):
Before reconciliation:
After reconciliation — resource verification:
Controller log:
{ "level": "info", "ts": "2026-05-12T16:39:10.689Z", "msg": "server-side apply completed", "controller": "kustomization", "controllerGroup": "kustomize.toolkit.fluxcd.io", "controllerKind": "Kustomization", "Kustomization": { "name": "test-multi-resource", "namespace": "drift-verbose-3" }, "namespace": "drift-verbose-3", "name": "test-multi-resource", "reconcileID": "2bfb5e95-0a12-40d0-94ce-6d7e0a0295dd", "output": { "ConfigMap/drift-verbose-3/test-config": "configured", "Deployment/drift-verbose-3/test-nginx": "configured", "Namespace/drift-verbose-3": "unchanged", "Service/drift-verbose-3/test-service": "unchanged" }, "revision": "main@sha1:b2a96870abdf6a295501e298a147e1e4ea5d878a" }Test 4 — Nested paths (VPA container resources)
Kustomization:
Initial state:
Simulate VPA adjusting resources + image tamper (server-side apply with
vpa-recommenderfield manager):After reconciliation — VPA resources preserved, image corrected:
Controller log:
{ "level": "info", "ts": "2026-05-12T16:39:53.423Z", "msg": "server-side apply completed", "controller": "kustomization", "controllerGroup": "kustomize.toolkit.fluxcd.io", "controllerKind": "Kustomization", "Kustomization": { "name": "test-nested-paths", "namespace": "drift-verbose-4" }, "namespace": "drift-verbose-4", "name": "test-nested-paths", "reconcileID": "650121b8-bc1c-46e1-98c7-981ec3901f8b", "output": { "ConfigMap/drift-verbose-4/test-config": "unchanged", "Deployment/drift-verbose-4/test-nginx": "configured", "Namespace/drift-verbose-4": "unchanged", "Service/drift-verbose-4/test-service": "unchanged" }, "revision": "main@sha1:b2a96870abdf6a295501e298a147e1e4ea5d878a" }Test 5 — Edge cases (non-existent paths + non-matching selector)
Kustomization:
Initial state — reconciles successfully despite non-existent paths:
Change
ignored-keyvia SSA (rule targetsdoes-not-existCM, so should NOT matchtest-config):After reconciliation:
No errors in controller logs:
Controller log:
{ "level": "info", "ts": "2026-05-12T16:40:33.786Z", "msg": "server-side apply completed", "controller": "kustomization", "controllerGroup": "kustomize.toolkit.fluxcd.io", "controllerKind": "Kustomization", "Kustomization": { "name": "test-edge-cases", "namespace": "drift-verbose-5" }, "namespace": "drift-verbose-5", "name": "test-edge-cases", "reconcileID": "a17c71f0-c19a-4e64-bb71-4d5054085c1e", "output": { "ConfigMap/drift-verbose-5/test-config": "configured", "Deployment/drift-verbose-5/test-nginx": "unchanged", "Namespace/drift-verbose-5": "unchanged", "Service/drift-verbose-5/test-service": "unchanged" }, "revision": "main@sha1:b2a96870abdf6a295501e298a147e1e4ea5d878a" }Test 6a — Ignore immutable
/spec/selector+ image driftKustomization:
After introducing image drift and reconciling:
Test 6b — driftAdopt — ignored field preserved when Flux is sole Apply owner
Kustomization:
Initial state:
Drift BOTH fields via non-SSA patch (Update operation — no other Apply manager claims ignored-key):
After reconciliation — driftAdopt behavior:
Flux retains ownership of ignored-key (adopted into payload):
Test 6c — driftAdopt on mandatory field (spec/replicas, Flux sole owner)
Kustomization:
Drift replicas + image via non-SSA patch:
After reconciliation:
Test 7 — No target selector — annotations ignored on ALL resources
Kustomization:
Add custom annotations on ALL resource types via SSA + tamper image:
After reconciliation — annotations preserved on ALL resources, non-ignored drift corrected:
Controller log:
{ "level": "info", "ts": "2026-05-12T16:43:18.546Z", "msg": "server-side apply completed", "controller": "kustomization", "controllerGroup": "kustomize.toolkit.fluxcd.io", "controllerKind": "Kustomization", "Kustomization": { "name": "test-no-target", "namespace": "drift-verbose-7" }, "namespace": "drift-verbose-7", "name": "test-no-target", "reconcileID": "707b8268-1b35-4b53-a414-8ce081ca75a3", "output": { "ConfigMap/drift-verbose-7/test-config": "configured", "Deployment/drift-verbose-7/test-nginx": "configured", "Namespace/drift-verbose-7": "unchanged", "Service/drift-verbose-7/test-service": "unchanged" }, "revision": "main@sha1:b2a96870abdf6a295501e298a147e1e4ea5d878a" }Test 8 — Only-ignored-field drift → Unchanged (no resource version bump)
Kustomization:
Introduce drift ONLY in ignored field (spec.replicas via HPA SSA):
After reconciliation — Deployment treated as unchanged:
Controller log — Deployment shows as
"unchanged"(not"configured"):{ "level": "info", "ts": "2026-05-12T16:44:20.938Z", "msg": "server-side apply completed", "controller": "kustomization", "controllerGroup": "kustomize.toolkit.fluxcd.io", "controllerKind": "Kustomization", "Kustomization": { "name": "test-unchanged", "namespace": "drift-verbose-8" }, "namespace": "drift-verbose-8", "name": "test-unchanged", "reconcileID": "e0c8033c-a6d7-49a1-901e-4a867a9cba69", "output": { "ConfigMap/drift-verbose-8/test-config": "unchanged", "Deployment/drift-verbose-8/test-nginx": "unchanged", "Namespace/drift-verbose-8": "unchanged", "Service/drift-verbose-8/test-service": "unchanged" }, "revision": "main@sha1:b2a96870abdf6a295501e298a147e1e4ea5d878a" }Test 9 — Managed fields ownership — Flux releases ignored fields
Kustomization:
After initial create, Flux owns ignored-key:
After external controller claims ignored-key via SSA and reconciliation:
Flux no longer owns ignored-key (released ownership):
Test 10 — Non-drifted ignored fields stay in payload (ownership retained)
Kustomization:
Drift ONLY non-ignored field (image), replicas NOT drifted:
Flux still owns spec.replicas (non-drifted, not stripped from payload):
Test 11 — Label-selector targeting
Kustomization:
Drift replicas + image, label selector
team=platformmatches:Test 12 — driftAdopt with array-indexed path (merge key resolution)
Kustomization:
Initial resources:
After drifting resources (non-SSA) and image, then reconciling:
Test 13 — Array index ownership — k:{name:nginx} resolves from index 0
Kustomization:
VPA claims resources via SSA (separate Apply manager), then image tampered:
vpa-recommender still owns resources in managedFields:
Test 14 — kubectl edit/patch (Update-type) — driftAdopt across resources
Kustomization:
Initial state:
Simulate kubectl edit on all 3 resources (strategic merge / JSON / merge patches):
After reconciliation — driftAdopt across all resources:
Managed fields verification:
Controller log:
{ "level": "info", "ts": "2026-05-12T16:49:09.524Z", "msg": "server-side apply completed", "controller": "kustomization", "controllerGroup": "kustomize.toolkit.fluxcd.io", "controllerKind": "Kustomization", "Kustomization": { "name": "test-kubectl-edit", "namespace": "drift-verbose-15" }, "namespace": "drift-verbose-15", "name": "test-kubectl-edit", "reconcileID": "f8770f04-a93c-42fa-83df-fdc934a7b6b3", "output": { "ConfigMap/drift-verbose-15/test-config": "configured", "Deployment/drift-verbose-15/test-nginx": "configured", "Namespace/drift-verbose-15": "unchanged", "Service/drift-verbose-15/test-service": "configured" }, "revision": "main@sha1:b2a96870abdf6a295501e298a147e1e4ea5d878a" }