🐛 fix(api): snapshot.enabled: false is unsettable and never cleans up - #6
Merged
thilak009 merged 3 commits intoAug 18, 2026
Merged
Conversation
Enabled was a non-pointer bool with omitempty and +kubebuilder:default=true.
false is Go's zero value, so it was dropped on marshal and the API server
re-applied the default, meaning an explicit false could not survive the
operator's own full-object write when adding the finalizer.
Verified before the fix:
enabled=false marshals to: {"schedule":"0 * * * *","retentionCount":5}
enabled=true marshals to: {"enabled":true,"schedule":"0 * * * *","retentionCount":5}
A CR that declares snapshot.enabled: false therefore ends up running a
snapshot CronJob anyway.
Behaviour-neutral on its own: the call site still gates on Enabled, so no
CronJob is deleted until that gate is removed. The CRD schema is unchanged —
*bool and bool both render as type: boolean with default: true.
S3BackupSpec.Enabled is deliberately left a plain bool: it has no kubebuilder
default, so absent and explicit-false are already equivalent.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ble removes it The call site gated on Spec.Snapshot.Enabled, but the delete branch lives inside the function being gated. The function could therefore only ever run when snapshots were enabled, and its first act was to check whether they were disabled — dead code. Disabling snapshots never removed an existing CronJob. Combined with the enabled:false fix, "disable snapshots" now works end to end and cleans up CronJobs left behind on clusters that no longer want them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Minor, not patch: disabling snapshots now actually disables them, which changes behaviour on every existing cluster. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
thilak009
force-pushed
the
fix/snapshot-cronjob-scheduling
branch
from
August 17, 2026 02:08
be1de5f to
de9a37d
Compare
thilak009
force-pushed
the
fix/snapshot-enabled-false
branch
from
August 17, 2026 02:08
ec6e455 to
f35225c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A CR that declares
snapshot.enabled: falseruns a snapshot CronJob anyway. Two defects, and each one alone is enough to cause it.1.
falseis not representable.SnapshotSpec.Enabledwas a non-pointerboolwithomitemptyand+kubebuilder:default=true.falseis Go's zero value, so it is dropped on marshal and the API server re-applies the default:falseis indistinguishable from "unset" on the wire, so it cannot survive the operator's own full-object write when adding the finalizer (memgraphcluster_controller.go:100). A client that applies literal JSON getsfalsestored correctly on create; the operator's write then drops just the zero-valued bool, and the API server defaults it back totrue.The signature is distinctive: every other field in
spec.snapshotround-trips exactly, and onlyenabledflips.Worth noting for anyone reproducing this: it is not "every reconcile". The only full-object CR writes are the finalizer add — which fires once, on the first reconcile of a new CR — and the finalizer removal on delete. The other
r.Updatecalls target the StatefulSet and Service. So the flip happens once at CR creation and then sticks, because nothing writes the CR again to correct it.2. The cleanup path was unreachable. The call site gated on
Spec.Snapshot.Enabled, but the delete branch lives inside the function being gated. The function could only ever run when snapshots were enabled, and its first act was to check whether they were disabled. Dead code — disabling snapshots never removed an existing CronJob.Changes
memgraphcluster_types.go—Enabledbecomes*bool, plus anIsEnabled()accessor treating nil as true to match the CRD default.omitemptyskips only nil pointers, so an explicitfalsenow marshals correctly.The CRD schema is unchanged —
*boolandboolboth render astype: booleanwithdefault: true. The only yaml diff is the field description. Existing CRs are unaffected.S3BackupSpec.Enabledis deliberately left a plainbool: it has no kubebuilder default, so absent and explicit-false are already equivalent there.memgraphcluster_controller.go— callreconcileSnapshotCronJobunconditionally and let it decide, making the existing delete path reachable.Owns(&batchv1.CronJob{})is already wired, so deletions re-trigger reconcile correctly.Behaviour change
snapshot.enabled: falsewill have its snapshot CronJob deleted on the first reconcile after upgrade.That is the intended fix — those clusters asked for no snapshots and have been running one regardless — but it is a real behaviour change on upgrade rather than a pure bugfix, which is why it is versioned as a minor and split from #5.
Clusters that want snapshots (
enabled: true, or the field unset) are unaffected; nil still means enabled.Testing
make cipasses — lint 0 issues, all packages green, build succeeds.api/v1alpha1coverage 89.9% → 90.2%.New coverage:
TestSnapshotSpecEnabledFalseSurvivesMarshal— direct regression test asserting"enabled":falseis present after marshal.TestSnapshotSpecIsEnabled— nil/true/false accessor behaviour.TestReconcileSnapshotCronJobDeletesWhenDisabled— a disabled cluster's CronJob is deleted.Note on that last test: it passes without the call-site change, because the delete branch itself was always correct — the bug was purely that nothing called it. The guard for the actual fix is that exactly one
Snapshot.IsEnabled()read site now remains, insidesnapshot.gowhere the delete branch lives.Verification on a cluster
Not addressed
spec.snapshot.retentionCountandspec.snapshot.s3.retentionDaysare accepted by the CRD and read by no controller code. Local snapshots are pruned solely by Memgraph's own--storage-snapshot-retention-count; S3 backups are pruned by nothing. Tracked separately.Note the near-miss for whoever picks that up:
spec.config.snapshotRetentionCountis a different field and is wired up, atstatefulset.go:205.🤖 Generated with Claude Code