Skip to content

feat(controller): guarded repo-wide prune for git-as-source (homelab B2)#135

Merged
astrojerms merged 1 commit into
mainfrom
feat/homelab-gitops-prune
Jul 13, 2026
Merged

feat(controller): guarded repo-wide prune for git-as-source (homelab B2)#135
astrojerms merged 1 commit into
mainfrom
feat/homelab-gitops-prune

Conversation

@astrojerms

Copy link
Copy Markdown
Owner

Roadmap target B2. Makes the repo the desired SET, not just an additive source: after a pull, resources whose manifest file was removed from the repo are deleted. Opt-in via manifests.gitops.pull.prune (default OFF — this deletes real infrastructure).

Safety-first design

manifests.Pruner is deliberately conservative — every path errs toward NOT deleting:

  • File still on disk → never a candidate (present = still desired).
  • Composite children are skipped. Checks both the generic owner labels (openctl.io/owner-kind/name) and the operative k3s.openctl.io/cluster label. The live Cluster.Apply path tags a cluster's VMs with the latter, not owner labels — checking only one scheme would orphan-delete a running cluster's VMs. Deleting the parent composite cascades to its children instead.
  • Hand-managed resources are protected. A resource whose latest successful apply was cli/ui-sourced is skipped. Provenance comes from the operations table (there's no source column on applied_manifests). Unknown provenance (GC'd ops) is treated as prunable — so the feature keeps working after op GC — but the child guard still applies.

Wired after Watcher.Sync in the pull loop; deletes go through the same gitops-sourced Delete-op path as deleteOnRemove.

Why these guards matter (from a safety audit)

  • applied_manifests has no source column → provenance must come from the ops table, and may be GC'd.
  • Startup Reconcile re-materializes missing files — but prune runs in the pull loop and deletes the store row, so a re-materialize can't revive a pruned resource.
  • The operative k3s path does not set owner labels — hence the dual label check.

Tests

Every guard branch: file-present keep, gitops/unknown-provenance prune, cli-source skip, both child-label schemes skip, and nil-source-lookup still guards children.

Next: B3 (GitHub/PR flow).

Makes the repo the desired SET, not just an additive source: after a pull,
resources whose manifest file was removed from the repo are deleted. Opt-in via
manifests.gitops.pull.prune (default OFF — this deletes real infrastructure).

manifests.Pruner is deliberately conservative; every path errs toward NOT
deleting:
- A resource whose file is still on disk is desired → never a candidate.
- Composite children are skipped (owner labels AND the operative k3s
  `k3s.openctl.io/cluster` label — the live Cluster.Apply path uses the latter,
  not owner labels, so checking only one would orphan-delete a cluster's VMs).
  Deleting the parent composite cascades to its children.
- Resources whose latest successful apply was cli/ui-sourced are protected
  (provenance read from the operations table, since applied_manifests has no
  source column). Unknown provenance (GC'd ops) is treated as prunable, not
  protected-forever, so the feature keeps working after op GC — but the child
  guard still applies.

Wired after Watcher.Sync in the pull loop; deletes go through the same
gitops-sourced Delete-op path as deleteOnRemove. Unit tests cover every guard
branch (file-present keep, gitops/unknown prune, cli-source skip, both child
label schemes skip, nil-source-lookup still guards children).

Target B2 of Homelab-completion. Next: B3 (GitHub/PR flow).
@astrojerms
astrojerms merged commit 3a7d9a3 into main Jul 13, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant