fix(harbor): clean up orphaned jobservice PVC and ACME solver resources on delete (#3061) - #3077
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses resource leakage issues that occur when deleting a Harbor application. By implementing a post-delete cleanup hook, the system now ensures that persistent volume claims and transient cert-manager ACME artifacts are properly removed, preventing namespace clutter and potential conflicts during future deployments. Highlights
New Features🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on Gemini (@gemini-code-assist) comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
📝 WalkthroughWalkthroughHarbor now renders a post-delete cleanup Job with dedicated RBAC. The job removes orphaned PVCs and cert-manager ACME solver resources, and tests now cover the hook output plus the phony ChangesHarbor cleanup hook
Sequence Diagram(s)sequenceDiagram
participant CleanupJob as "cleanup Job"
participant KubernetesAPI as "Kubernetes API"
participant CertManager as "cert-manager"
CleanupJob->>KubernetesAPI: list and delete PVCs for {{ .Release.Name }}-system
CleanupJob->>KubernetesAPI: delete certificates, orders, and challenges for {{ .Release.Name }}-ingress-tls
CleanupJob->>KubernetesAPI: list and delete solver Challenges and Ingresses for the release host
KubernetesAPI->>CertManager: remove ACME and solver objects
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related issues
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a post-delete cleanup hook for the Harbor package to remove orphaned PVCs and cert-manager ACME resources upon uninstallation, along with corresponding unit tests and a Makefile update. Feedback on these changes highlights that the cleanup job requires the deletecollection RBAC verb to delete resources by label selector, and the cleanup container needs resource requests and limits defined in accordance with the repository style guide. Additionally, the unit tests should be updated to reflect the RBAC changes.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/apps/harbor/tests/cleanup_hook_test.yaml (1)
26-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd assertions for solver
Ingress/Service/Podcleanup and matching RBAC.Current tests validate CR cleanup only. Add checks that the command includes deletes for solver ingress/service/pod resources and that Role rules include those API groups/resources, so the teardown contract is protected.
Also applies to: 52-79
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/apps/harbor/tests/cleanup_hook_test.yaml` around lines 26 - 41, The cleanup hook test currently only asserts CR cleanup, but it should also verify solver resource teardown and RBAC coverage. Update the cleanup test in cleanup_hook_test.yaml so the command built by the cleanup hook includes delete handling for the ACME solver Ingress, Service, and Pod resources, using the existing cleanup command assertions around the target symbols in the test. Also add assertions on the generated Role rules to confirm the needed API groups/resources are granted for those solver objects, so the teardown contract stays protected.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/apps/harbor/templates/hooks/cleanup.yaml`:
- Around line 54-63: The cleanup hook only deletes cert-manager CRs and a
label-selected subset, so orphaned cm-acme-http-solver-* workload/network
objects can remain. Update the cleanup logic in the hook that runs around the
kubectl delete calls to explicitly delete solver Ingress, Service, and Pod
resources associated with the release, and adjust the RBAC used by the cleanup
job to grant delete access for those resource types. Keep the existing
certificate/order/challenge cleanup, and make sure the additional deletions
target the same release/solver scope used by the current cleanup flow.
---
Nitpick comments:
In `@packages/apps/harbor/tests/cleanup_hook_test.yaml`:
- Around line 26-41: The cleanup hook test currently only asserts CR cleanup,
but it should also verify solver resource teardown and RBAC coverage. Update the
cleanup test in cleanup_hook_test.yaml so the command built by the cleanup hook
includes delete handling for the ACME solver Ingress, Service, and Pod
resources, using the existing cleanup command assertions around the target
symbols in the test. Also add assertions on the generated Role rules to confirm
the needed API groups/resources are granted for those solver objects, so the
teardown contract stays protected.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: eab85d91-b6f4-4950-9daa-2ce80bc7d4fa
📒 Files selected for processing (3)
packages/apps/harbor/Makefilepackages/apps/harbor/templates/hooks/cleanup.yamlpackages/apps/harbor/tests/cleanup_hook_test.yaml
9e5ff9c to
159267f
Compare
IvanHunters
left a comment
There was a problem hiding this comment.
Verdict
NOT LGTM
Operational risks remain: the post-delete hook fires synchronously while the inner <release>-system HR is still being uninstalled by Flux, leaving the user-visible behaviour of Terminating PVCs unverified on a live cluster; the PR body also still advertises a label-selector ACME sweep that was deliberately removed before merge.
Findings
[MINOR] packages/apps/harbor/templates/hooks/cleanup.yaml:55-67 — PR body claims the hook "sweeps leftover solver objects by acme.cert-manager.io/http01-solver=true", but the implementation deliberately does NOT do this and the unit test (tests/cleanup_hook_test.yaml:36-43) asserts notMatchRegex against that label.
Update the "What this PR does" section so the release-note reader sees the actual scope: deterministic <release>-ingress-tls Certificate deletion with reliance on cert-manager owner-ref cascade for the solver-side objects. The current PR body advertises behaviour that was removed before merge.
[MINOR] packages/apps/harbor/templates/hooks/cleanup.yaml:25 — Cleanup image docker.io/clastix/kubectl:v1.32 is a moving tag, not pinned by digest, and is not routed through cozy-lib.images-registry.
Air-gapped / mirrored installs that rely on the cozystack images registry will fail to pull this image at the worst possible moment (during app uninstall). The same gap exists in packages/apps/mariadb/templates/hooks/cleanup-pvc.yaml:23, so this is pre-existing fleet debt rather than a new regression — but it is now duplicated. Recommend digest-pinning and routing through cozy-lib.images-registry in both before this is fanned out further.
Claim mismatches
- "sweeps leftover solver objects by acme.cert-manager.io/http01-solver=true" — [MISSING] Removed from the implementation; tests forbid it.
Operational risks
-
Hook vs inner-HR uninstall race (
packages/apps/harbor/templates/hooks/cleanup.yaml:50-58+packages/apps/harbor/templates/harbor.yaml): when the outerharborrelease is uninstalled, Helm'spost-deletehook runs synchronously, but the inner<release>-systemHR (the upstream Harbor subchart) is reconciled and uninstalled asynchronously by Flux helm-controller. The hook'skubectl delete pvc -l app.kubernetes.io/instance=<release>-systemmay fire while jobservice / trivy pods are still running. Kubernetes will hold each PVC inTerminatinguntil consumer pods exit, so the outcome is eventually-correct, but operators get no diagnosable failure mode if the inner uninstall stalls — the Job's "PVC deletion: completed" log line is misleading when the PVC is actuallyTerminating, not deleted. Either gate the PVC sweep behind a wait-until-<release>-system-HR-is-gone check, or document this clearly in the Job's log + operator docs. -
Out-of-scope orphans remain: the hook handles jobservice / trivy PVCs from the upstream Harbor subchart, plus this release's
<release>-ingress-tlsCertificate. CNPGClusterPVCs (<release>-db-N) follow CNPG's own reclaim policy and are NOT touched. Re-installing Harbor with a different DB size into the same namespace will leak the old DB PVCs. If this PR is positioned as "cleanup orphans on Harbor app delete", the scope boundary needs to be either widened to CNPG PVCs (with explicit risk discussion, since DB data is on those) or called out in the PR body so operators know what is and is not cleaned up. -
PR-body ACME claim divergence (see Finding 1): the published claim shapes what operators expect to be cleaned. Shipping with the current PR body teaches operators they don't need to manually sweep
cm-acme-http-solver-*pods, which is true today only because cert-manager's owner-ref cascade is doing the actual work — not because of any label-selector sweep the hook performs. If cert-manager's behaviour shifts (e.g. a future Challenge created without an owner-ref), the silent loss of the documented sweep will surface as new orphans with no in-repo signal that the cleanup ever covered that path.
Caveats
- I did not exercise the full uninstall path on a live cluster; the race-window analysis is from reading
helm.toolkit.fluxcd.io/v2semantics and Helm hook ordering, not from a repro. Smoke-testing on a dev cluster with a real Harbor instance that has issued at least one Certificate (ideally during an in-flight HTTP-01 challenge) is needed to confirm or refute the operational-risk severity. helm templateagainst the modified chart fails ondashboard-resourcemap.yamlwhen rendered with--namespace defaultbecause of thetenant.nameguard; with--namespace tenant-rootthe chart renders cleanly.helm unittestpasses all 8 tests (2 suites). Confirmed locally.make generatewas not required: no changes tovalues.yaml,values.schema.json,Chart.yaml, orREADME.md.- I did not deeply verify whether
kubectl delete pvc -l <selector>requires thedeletecollectionRBAC verb on this cluster'scozystack-api-driven RBAC layer. kubectl normally does LIST + DELETE-per-object, which only needslist+delete, but if any admission proxy in the cluster intercepts and rewrites todeletecollection, the current Role would be insufficient. Worth a one-offkubectl auth can-itest as part of the smoke test above.
Recommended follow-ups
- Update the PR description to remove the "sweeps …
http01-solver=true" claim, mirroring the in-code comment, before merge. - Open a follow-up to digest-pin
docker.io/clastix/kubectl:v1.32and route it throughcozy-lib.images-registryin bothpackages/apps/harbor/templates/hooks/cleanup.yamlandpackages/apps/mariadb/templates/hooks/cleanup-pvc.yaml. - Decide and document the cleanup scope boundary re CNPG PVCs (either widen the hook or call out the limitation in PR body and operator docs).
- Add a real-cluster smoke test (port-forward / kind / dev cluster) covering: install Harbor → issue at least one cert → uninstall → assert no jobservice PVC, no Certificate, no
cm-acme-http-solver-*resources in the namespace within a bounded timeout.
…es on delete (#3061) The jobservice PVC (resourcePolicy: keep) and cert-manager ACME http01-solver objects were left orphaned after a Harbor app CR was deleted. Add a post-delete hook that deletes the release PVCs (selecting by app.kubernetes.io/instance=<release>-system, the label the upstream subchart stamps) and the release Certificate/Order/Challenge plus the http01-solver sweep. A hook is required because resourcePolicy: keep blocks normal reclaim. The cleanup Job runs non-root with a read-only root fs and dropped capabilities; failures are logged but never block teardown. Signed-off-by: Алексей Артамонов <aleksei.artamonov@aenix.io>
159267f to
d113273
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/apps/harbor/tests/cleanup_hook_test.yaml (1)
86-157: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAlso assert hook annotations on the ServiceAccount/Role/RoleBinding.
Lines 86-157 currently prove the RBAC objects render, but not that they remain part of the
post-deletehook lifecycle. That annotation/delete-policy is the critical uninstall contract for these helper resources too, so a regression there would break teardown while this suite still passes.Suggested assertions
- it: creates a ServiceAccount for the cleanup job documentIndex: 1 asserts: - equal: path: kind value: ServiceAccount - equal: path: metadata.name value: harbor-test-cleanup + - equal: + path: metadata.annotations["helm.sh/hook"] + value: post-delete + - equal: + path: metadata.annotations["helm.sh/hook-delete-policy"] + value: before-hook-creation,hook-succeeded - it: grants RBAC for PVCs and cert-manager ACME resources documentIndex: 2 asserts: - equal: path: kind value: Role - equal: path: metadata.name value: harbor-test-cleanup + - equal: + path: metadata.annotations["helm.sh/hook"] + value: post-delete - it: binds the Role to the cleanup ServiceAccount documentIndex: 3 asserts: - equal: path: kind value: RoleBinding ... + - equal: + path: metadata.annotations["helm.sh/hook"] + value: post-delete🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/apps/harbor/tests/cleanup_hook_test.yaml` around lines 86 - 157, Add assertions in cleanup_hook_test.yaml for the hook annotations on the ServiceAccount, Role, and RoleBinding rendered by the cleanup hook. Use the existing document checks for the ServiceAccount, Role, and RoleBinding to verify the `helm.sh/hook` lifecycle metadata (including `post-delete`) and the delete-policy annotation so these helper resources stay tied to uninstall. Focus the new assertions near the existing `ServiceAccount`, `Role`, and `RoleBinding` tests to keep the RBAC objects covered by the hook contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/apps/harbor/templates/hooks/cleanup.yaml`:
- Around line 58-60: The PVC cleanup in the cleanup hook is too broad because it
targets every PVC for the `{{ .Release.Name }}-system` instance instead of only
jobservice storage. Update the deletion logic in the cleanup template to scope
the `kubectl delete pvc` command to the jobservice PVC specifically, using the
relevant component label or known PVC name from the hook rather than the generic
instance label. Keep the warning fallback, but ensure the match only affects
jobservice-related PVCs.
---
Nitpick comments:
In `@packages/apps/harbor/tests/cleanup_hook_test.yaml`:
- Around line 86-157: Add assertions in cleanup_hook_test.yaml for the hook
annotations on the ServiceAccount, Role, and RoleBinding rendered by the cleanup
hook. Use the existing document checks for the ServiceAccount, Role, and
RoleBinding to verify the `helm.sh/hook` lifecycle metadata (including
`post-delete`) and the delete-policy annotation so these helper resources stay
tied to uninstall. Focus the new assertions near the existing `ServiceAccount`,
`Role`, and `RoleBinding` tests to keep the RBAC objects covered by the hook
contract.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 3a8b93f2-aed1-474c-9d31-78f6c486f16a
📒 Files selected for processing (3)
packages/apps/harbor/Makefilepackages/apps/harbor/templates/hooks/cleanup.yamlpackages/apps/harbor/tests/cleanup_hook_test.yaml
✅ Files skipped from review due to trivial changes (1)
- packages/apps/harbor/Makefile
|
IvanHunters thanks for the review (re: your CHANGES_REQUESTED review). On the It covers digest-pinning + routing through |
|
IvanHunters addressed your review points in the PR description (no behaviour change to the hook itself, which already does the host-scoped sweep):
Also resolved the open CodeRabbit/Gemini threads (resources requests/limits were already added; Re the E2E failure: it's in the base install step ( |
Aleksei Sviridkin (lexfrei)
left a comment
There was a problem hiding this comment.
NOT LGTM — the PVC cleanup silently misses the trivy PVC, so it does not fully fix the orphan it claims to.
The post-delete hook deletes PVCs with -l app.kubernetes.io/instance=<release>-system (templates/hooks/cleanup.yaml:59). That label is only set by the upstream subchart's harbor.labels helper, which the jobservice PVC uses — so jobservice is handled correctly. The trivy PVC is different: it is a StatefulSet volumeClaimTemplate whose selector uses harbor.matchLabels (release, app — no app.kubernetes.io/instance), and the StatefulSet controller only merges spec.selector.matchLabels onto the PVC, so data-<release>-trivy-0 never carries app.kubernetes.io/instance and is not matched by the sweep. There is no persistentVolumeClaimRetentionPolicy on the trivy StatefulSet, so it is retained on uninstall. Trivy is on by default, so a default Harbor delete leaves the trivy PVC orphaned — which contradicts the PR body, the release note, and the code comment ("intentionally covers both the jobservice and trivy PVCs").
Suggested fix: select by the label both PVCs share — -l release=<release>-system matches jobservice (harbor.labels) and trivy (harbor.matchLabels), while CNPG/Redis PVCs use different label schemes and the registry is on S3, so nothing extra is caught. The unit test that pins the app.kubernetes.io/instance=<release>-system selector must be updated to assert the corrected selector.
Non-blocking:
kubectl delete certificate,order,challenge <release>-ingress-tlsonly resolves a real object forcertificate(Orders/Challenges are hash-suffixed), so the Order/Challenge kinds are no-ops here — the real cleanup is the Certificate owner-ref cascade plus the host-scoped sweep. Consider droppingorder,challengefrom the by-name delete.- In Gateway mode no per-app Ingress or
<release>-ingress-tlsCertificate is rendered, so the Certificate delete is a no-op there; the dnsName-scoped challenge sweep still covers http01. Worth a one-line comment.
The ACME solver handling (release-scoped Certificate delete + host-scoped Challenge/Ingress sweep, no blanket label sweep), the hardened Job, the hook ordering, and the RBAC scoping all look correct.
…so cleaned The post-delete cleanup hook selected PVCs by app.kubernetes.io/instance=<release>-system, a label only the jobservice PVC carries (via the chart's "harbor.labels" helper). The trivy PVC is a StatefulSet volumeClaimTemplate labeled via "harbor.legacy.labels" with a selector using "harbor.matchLabels" — neither sets app.kubernetes.io/instance — so the sweep silently missed it. Trivy is enabled by default, so a default Harbor delete left the trivy PVC orphaned, contradicting the PR's own claim. Select on release=<release>-system instead: both kept PVCs carry it, while CNPG (db) and Redis PVCs use different label schemes and the registry is on S3, so only the two intended PVCs match. The unit test now asserts the corrected selector and guards against regressing to the instance label. Also drop the by-name delete of Orders/Challenges (their hash-suffixed names make a by-name delete a no-op; the Certificate owner-ref cascade plus the host-scoped sweep handle them), narrow the hook RBAC to challenges only, and note the Gateway-mode no-op. Assisted-By: Claude <noreply@anthropic.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Алексей Артамонов <alexeyartamonov1987@gmail.com>
|
Aleksei Sviridkin (@lexfrei) thanks — confirmed and fixed in b25f294. Verified your analysis against the vendored chart (
So the old selector did silently miss trivy (on by default). Blocking fix: PVC sweep now selects Non-blocking, both applied:
Residual item (matches your own caveat): the "CNPG/Redis PVCs don't carry |
Aleksei Sviridkin (lexfrei)
left a comment
There was a problem hiding this comment.
LGTM — the prior blocker (the PVC sweep missed the trivy PVC) is resolved; -l release=<release>-system correctly matches both kept PVCs, verified against the chart's label helpers.
The trivy miss is fixed. Verified: harbor.labels (jobservice PVC) and harbor.legacy.labels (trivy volumeClaimTemplate) both set release: .Release.Name, and the trivy StatefulSet's matchLabels (release, app, component, no app.kubernetes.io/instance) are stamped onto its PVC — so the old instance selector missed trivy (enabled by default) while release=<release>-system catches both. CNPG (cnpg.io/cluster) and redis-operator PVCs use different label schemes, so nothing extra is matched; the release value is an exact match, so there is no cross-release collision. The ACME handling (by-name <release>-ingress-tls Certificate delete + host-scoped Challenge/solver-Ingress sweep, with no blanket http01-solver label sweep) is correct. helm-unittest passes (9/9) and pins the full contract including the no-instance-label and no-blanket-sweep guards.
Non-blocking follow-ups
- The PR description is now inaccurate: it states PVCs are selected by
app.kubernetes.io/instance=<release>-systemand that they carry that label "not<release>" — the shipped selector isrelease=<release>-system(the opposite), which is precisely what the final commit changed. The RBAC paragraph also listsorders, which the hook deliberately does not grant. Worth correcting before merge so the release note and merge record match the code. - The PVC delete runs with the default
--wait=trueand the Job has noactiveDeadlineSeconds. Because the inner<release>-systemHelmRelease is uninstalled asynchronously by Flux, the hook can fire while jobservice/trivy pods are still running and block onpvc-protection; if that uninstall stalls, the hook has no bounded failure mode. The sibling SeaweedFS cleanup hook uses--wait=false+activeDeadlineSeconds: 120— applying the same here would bound the failure and make the body's "deletion request always succeeds" note accurate. - No e2e coverage:
harbor.batsexists but is not extended to assert the PVCs/Certificate/solver objects are gone after delete. helm-unittest only pins the rendered command, so real deletion is unverified by automated tests.
IvanHunters
left a comment
There was a problem hiding this comment.
Verdict
LGTM with non-blocking notes
The cleanup hook is correctly scoped, PSS-restricted compliant, follows the existing mariadb cleanup-hook pattern, and unittest passes (9/9). One stale paragraph in the PR description contradicts the code, and the documented force-delete of previously kept PVCs is a behaviour change worth a follow-up audit.
Findings
[MINOR] packages/apps/harbor/templates/hooks/cleanup.yaml:66 — PR body description contradicts the actual selector
The PR body states the hook selects by app.kubernetes.io/instance=<release>-system, but the code uses -l release=<release>-system. The in-template comment (lines 51-63) correctly explains the reason for picking release= (the trivy PVC, populated via harbor.legacy.labels, does not carry app.kubernetes.io/instance), and tests/cleanup_hook_test.yaml explicitly guards against regressing to the instance label. The PR description is stale; tests and code are correct. Please update the description so reviewers and changelog readers do not have to reconcile the discrepancy.
[MINOR] packages/apps/harbor/templates/hooks/cleanup.yaml:30 — docker.io/clastix/kubectl:v1.32 not digest-pinned and not routed through cozy-lib.images-registry
Pre-existing debt shared with packages/apps/mariadb/templates/hooks/cleanup-pvc.yaml:23 and acknowledged in the PR body as follow-up #3088. Air-gapped customers must mirror this image themselves. Out of scope for this PR but worth tracking.
[MINOR] packages/apps/harbor/templates/hooks/cleanup.yaml:13 — no backoffLimit or activeDeadlineSeconds on the cleanup Job
The script catches every kubectl failure with || echo WARNING, so the Job will normally exit 0. However, on ImagePullBackOff or a stuck API call the Job inherits Kubernetes defaults (backoffLimit: 6, no deadline) and a post-delete hook will block helm/Flux until it eventually fails. Consider backoffLimit: 1 and activeDeadlineSeconds: 300 to bound teardown time. Same shape as mariadb cleanup, so introducing it here is the right place to set the pattern.
Caveats
- Behaviour change for existing tenants: tenants that have relied on
persistence.resourcePolicy: keepto retain jobservice file logs or the trivy vulnerability-DB cache across ahelm uninstallwill now see those PVCs force-deleted on next teardown. The release-note flags this; CNPG and Redis PVCs are verified untouched (CNPG usescnpg.io/clusterlabels, Redis is managed by redis-operator with its own labels — neither carriesrelease=<release>-system). - Eventually-consistent PVC reclaim while the inner
<release>-systemHelmRelease is still being uninstalled by Flux: PVCs may stay inTerminatinguntil jobservice/trivy Pods exit. Acknowledged in the PR body NOTE; the Job'skubectl delete pvcitself always succeeds. - Gateway mode (where no per-app Ingress or
<release>-ingress-tlsCertificate is rendered): thekubectl delete certificateis a no-op (covered by--ignore-not-found), and the dnsName-scoped Challenge sweep still applies. Verified againsttemplates/httproute.yamlandtemplates/ingress.yaml. - Job runs in a tenant-restricted PSS namespace: pod-level
runAsNonRoot/runAsUser=65534/seccompProfile=RuntimeDefaultand container-levelallowPrivilegeEscalation=false/readOnlyRootFilesystem=true/capabilities.drop=[ALL]satisfy restricted PSS.
Recommended follow-ups
- Update the PR description to match the actual selector (
release=<release>-system, notapp.kubernetes.io/instance=<release>-system). - In #3088, in addition to digest-pinning, consider adding
backoffLimit/activeDeadlineSecondsto both the harbor and mariadb cleanup Jobs so a hook failure cannot indefinitely stall app deletion.
What this PR does
After deleting a
Harborapp CR, two things were left orphaned in the tenantnamespace:
<release>-jobservice, and<release>-trivywhen Trivy is enabled), because
packages/apps/harbor/templates/harbor.yamlsets
persistence.resourcePolicy: "keep", which annotates the upstream Harborsubchart's PVCs with
helm.sh/resource-policy: keepso Helm/Flux deliberatelynever deletes them;
cm-acme-http-solver-*), viathe same stuck-challenge mechanism as the Bucket issue (bucket: cert-manager ACME http-solver resources orphaned after deletion #3060) when Harbor
issues a non-wildcard cert.
This adds a single
post-deletecleanup hook that:app.kubernetes.io/instance=<release>-system(the subchart runs as the<release>-systemHelmRelease, so its PVCs carry that label, not<release>). A hook is required precisely becauseresourcePolicy: keepblocks normal reclaim. This intentionally covers both the jobservice and trivy
PVCs: both hold only regenerable data (jobservice file logs; trivy's
vulnerability-DB cache), and registry images live in S3.
Certificate(deterministic name<release>-ingress-tls); cert-manager then cascade-deletes theOrder,Challengeand solverPod/Service/Ingressobjects it owns via ownerreferences.
cert-manager may have orphaned (e.g. the Certificate was already gone, so
deleting it did not cascade). The sweep is scoped strictly to this release's
Harbor host — Challenges filtered by
.spec.dnsName, solver Ingresses by.spec.rules[0].host— not a namespace-wideacme.cert-manager.io/http01-solver=truelabel sweep, because a Cozystacktenant namespace can host many apps and a blanket label sweep would delete
sibling apps' in-flight challenge solvers.
The Job is hardened: non-root (uid 65534), read-only root fs, all caps dropped,
seccompProfile: RuntimeDefault, with CPU/memory requests and limits set. RBACis scoped to exactly the resources it deletes (
persistentvolumeclaims,certificates,orders,challenges, solveringresses) and verified againstthe vendored cert-manager CRDs. Failures are logged but never block teardown.
Cleanup scope — what is and isn't removed
Removed: the upstream Harbor subchart's kept PVCs (jobservice + trivy) and
this release's cert-manager ACME artifacts (Certificate, and host-scoped
Order/Challenge/solver-Ingress).
Not touched (by design):
<release>-db-N) — owned by the CNPGClusterandgoverned by CNPG's own reclaim policy; they carry CNPG labels, not
app.kubernetes.io/instance=<release>-system. Re-installing Harbor with adifferent DB size into the same namespace can therefore leak the old DB PVCs;
this is left to CNPG's lifecycle on purpose (the data is the user's).
untouched.
Pod/Service— not deleted directly; they are removed bycert-manager's owner-ref cascade when the Certificate/Challenge is deleted, so
the hook grants no RBAC for them.
Note
The PVC sweep is eventually-consistent: while the inner
<release>-systemHelmRelease is still being uninstalled by Flux, a PVC may briefly stay in
Terminatinguntil its consumer Pods (jobservice/trivy) exit. The Job'sdeletion request always succeeds; reclaim completes once the pods are gone.
Warning
This overrides the intentional
persistence.resourcePolicy: "keep"settingand force-deletes the jobservice/trivy PVCs on app deletion. Those PVCs hold
only jobservice file logs and the trivy vuln-DB cache (registry images live in
S3), so data-loss risk is low — but if retaining them across re-installs is
desired, this is a conscious product decision to revisit. The ACME cleanup
affects only cert-manager's transient artifacts.
Fixes #3061.
Release note