Skip to content

fix(harbor): clean up orphaned jobservice PVC and ACME solver resources on delete (#3061) - #3077

Merged
scooby87 merged 2 commits into
mainfrom
fix/harbor-pvc-and-acme-orphaned
Jun 30, 2026
Merged

fix(harbor): clean up orphaned jobservice PVC and ACME solver resources on delete (#3061)#3077
scooby87 merged 2 commits into
mainfrom
fix/harbor-pvc-and-acme-orphaned

Conversation

@scooby87

@scooby87 scooby87 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

What this PR does

After deleting a Harbor app CR, two things were left orphaned in the tenant
namespace:

  • the jobservice / trivy PVCs (<release>-jobservice, and <release>-trivy
    when Trivy is enabled), because packages/apps/harbor/templates/harbor.yaml
    sets persistence.resourcePolicy: "keep", which annotates the upstream Harbor
    subchart's PVCs with helm.sh/resource-policy: keep so Helm/Flux deliberately
    never deletes them;
  • cert-manager's ACME http01-solver resources (cm-acme-http-solver-*), via
    the 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-delete cleanup hook that:

  • deletes the upstream Harbor subchart's kept PVCs — selecting by
    app.kubernetes.io/instance=<release>-system (the subchart runs as the
    <release>-system HelmRelease, so its PVCs carry that label, not
    <release>). A hook is required precisely because resourcePolicy: keep
    blocks 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.
  • deletes this release's Certificate (deterministic name
    <release>-ingress-tls); cert-manager then cascade-deletes the Order,
    Challenge and solver Pod/Service/Ingress objects it owns via owner
    references.
  • host-scoped belt-and-braces sweep for any ACME http01 solver objects
    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].hostnot a namespace-wide
    acme.cert-manager.io/http01-solver=true label sweep, because a Cozystack
    tenant 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. RBAC
is scoped to exactly the resources it deletes (persistentvolumeclaims,
certificates, orders, challenges, solver ingresses) and verified against
the 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):

  • CNPG database PVCs (<release>-db-N) — owned by the CNPG Cluster and
    governed by CNPG's own reclaim policy; they carry CNPG labels, not
    app.kubernetes.io/instance=<release>-system. Re-installing Harbor with a
    different 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).
  • Redis PVCs — managed by the redis-operator with their own labels, likewise
    untouched.
  • Solver Pod/Service — not deleted directly; they are removed by
    cert-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>-system
HelmRelease is still being uninstalled by Flux, a PVC may briefly stay in
Terminating until its consumer Pods (jobservice/trivy) exit. The Job's
deletion request always succeeds; reclaim completes once the pods are gone.

Warning

This overrides the intentional persistence.resourcePolicy: "keep" setting
and 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.

Follow-up tracked in #3088: digest-pin the cleanup kubectl image and route it
through cozy-lib.images-registry (pre-existing debt shared with the MariaDB
cleanup hook), out of scope for this PR.

Release note

fix(harbor): clean up the orphaned jobservice/trivy PVCs and cert-manager ACME http01-solver resources left behind after a Harbor app is deleted. NOTE: this force-deletes the jobservice/trivy PVCs, overriding persistence.resourcePolicy=keep (jobservice file logs and trivy vuln-DB cache only; registry images are in S3). CNPG database PVCs are not touched.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, 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

  • Orphaned Resource Cleanup: Introduced a post-delete Helm hook to automatically remove orphaned jobservice PVCs and cert-manager ACME solver resources when a Harbor app is deleted.
  • Security Hardening: The cleanup Job is configured with non-root execution, a read-only root filesystem, dropped capabilities, and a restricted seccomp profile.
  • Testing: Added comprehensive unit tests to verify the cleanup hook's logic, RBAC permissions, and resource targeting.
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 Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment Gemini (@gemini-code-assist) Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions github-actions Bot added area/storage Issues or PRs related to storage (linstor, seaweedfs, bucket, velero, harbor) kind/bug Categorizes issue or PR as related to a bug size/L This PR changes 100-499 lines, ignoring generated files labels Jun 25, 2026
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Harbor 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 make test target.

Changes

Harbor cleanup hook

Layer / File(s) Summary
Cleanup job and RBAC
packages/apps/harbor/templates/hooks/cleanup.yaml
The post-delete cleanup Job deletes orphaned PVCs and cert-manager ACME resources, then adds the dedicated ServiceAccount, Role, and RoleBinding it uses.
Hook render tests and test target
packages/apps/harbor/tests/cleanup_hook_test.yaml, packages/apps/harbor/Makefile
The cleanup hook tests assert the rendered job, command strings, ServiceAccount, Role, and RoleBinding, and make test is forced to run through the phony target.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • #3061: This PR adds a post-delete cleanup hook for orphaned Harbor PVCs and ACME solver resources after deletion.

Suggested reviewers

  • kvaps
  • lllamnyp
  • androndo

Poem

🐰 Hop-hop, I trimmed the trail,
Cleaned PVCs and solver mail.
With RBAC snug and tests held tight,
Harbor’s cleanup glows tonight.
Hop hooray!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The Makefile .PHONY: test change is unrelated to the Harbor deletion cleanup objectives and appears outside the issue scope. Move the Makefile tweak to a separate PR or explain how it directly supports the deletion cleanup work.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the Harbor delete-time cleanup for orphaned jobservice PVC and ACME solver resources.
Linked Issues check ✅ Passed The cleanup hook and tests address the reported Harbor deletion leak by removing the orphaned PVC and ACME solver artifacts.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/harbor-pvc-and-acme-orphaned

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread packages/apps/harbor/templates/hooks/cleanup.yaml
Comment thread packages/apps/harbor/templates/hooks/cleanup.yaml
Comment thread packages/apps/harbor/tests/cleanup_hook_test.yaml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/apps/harbor/tests/cleanup_hook_test.yaml (1)

26-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add assertions for solver Ingress/Service/Pod cleanup 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

📥 Commits

Reviewing files that changed from the base of the PR and between 63f9926 and 9e5ff9c.

📒 Files selected for processing (3)
  • packages/apps/harbor/Makefile
  • packages/apps/harbor/templates/hooks/cleanup.yaml
  • packages/apps/harbor/tests/cleanup_hook_test.yaml

Comment thread packages/apps/harbor/templates/hooks/cleanup.yaml Outdated
@scooby87
scooby87 force-pushed the fix/harbor-pvc-and-acme-orphaned branch from 9e5ff9c to 159267f Compare June 25, 2026 15:48

@IvanHunters IvanHunters left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 outer harbor release is uninstalled, Helm's post-delete hook runs synchronously, but the inner <release>-system HR (the upstream Harbor subchart) is reconciled and uninstalled asynchronously by Flux helm-controller. The hook's kubectl delete pvc -l app.kubernetes.io/instance=<release>-system may fire while jobservice / trivy pods are still running. Kubernetes will hold each PVC in Terminating until 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 actually Terminating, 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-tls Certificate. CNPG Cluster PVCs (<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/v2 semantics 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 template against the modified chart fails on dashboard-resourcemap.yaml when rendered with --namespace default because of the tenant.name guard; with --namespace tenant-root the chart renders cleanly. helm unittest passes all 8 tests (2 suites). Confirmed locally.
  • make generate was not required: no changes to values.yaml, values.schema.json, Chart.yaml, or README.md.
  • I did not deeply verify whether kubectl delete pvc -l <selector> requires the deletecollection RBAC verb on this cluster's cozystack-api-driven RBAC layer. kubectl normally does LIST + DELETE-per-object, which only needs list + delete, but if any admission proxy in the cluster intercepts and rewrites to deletecollection, the current Role would be insufficient. Worth a one-off kubectl auth can-i test as part of the smoke test above.

Recommended follow-ups

  1. Update the PR description to remove the "sweeps … http01-solver=true" claim, mirroring the in-code comment, before merge.
  2. Open a follow-up to digest-pin docker.io/clastix/kubectl:v1.32 and route it through cozy-lib.images-registry in both packages/apps/harbor/templates/hooks/cleanup.yaml and packages/apps/mariadb/templates/hooks/cleanup-pvc.yaml.
  3. 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).
  4. 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>
@scooby87
scooby87 force-pushed the fix/harbor-pvc-and-acme-orphaned branch from 159267f to d113273 Compare June 26, 2026 10:03

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/apps/harbor/tests/cleanup_hook_test.yaml (1)

86-157: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Also 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-delete hook 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

📥 Commits

Reviewing files that changed from the base of the PR and between 9e5ff9c and d113273.

📒 Files selected for processing (3)
  • packages/apps/harbor/Makefile
  • packages/apps/harbor/templates/hooks/cleanup.yaml
  • packages/apps/harbor/tests/cleanup_hook_test.yaml
✅ Files skipped from review due to trivial changes (1)
  • packages/apps/harbor/Makefile

Comment thread packages/apps/harbor/templates/hooks/cleanup.yaml
@scooby87

Copy link
Copy Markdown
Contributor Author

IvanHunters thanks for the review (re: your CHANGES_REQUESTED review).

On the [MINOR] cleanup-image finding — agreed. Since this is pre-existing fleet
debt (the same docker.io/clastix/kubectl:v1.32 hardcode already lives in
packages/apps/mariadb/templates/hooks/cleanup-pvc.yaml:23) and is out of scope
for the Harbor delete-time cleanup this PR targets, I've split it into a dedicated
follow-up that fixes both charts at once: #3088.

It covers digest-pinning + routing through cozy-lib.images-registry, plus the
empty-default edge case (images-registry: "" on standard installs). Keeping it
out of this PR avoids widening the scope further.

@scooby87

Copy link
Copy Markdown
Contributor Author

IvanHunters addressed your review points in the PR description (no behaviour change to the hook itself, which already does the host-scoped sweep):

  • ACME claim mismatch — removed the inaccurate "sweeps leftover solver objects by acme.cert-manager.io/http01-solver=true" wording. The "What this PR does" section now describes the actual scope: deterministic <release>-ingress-tls Certificate deletion (cert-manager owner-ref cascade for Order/Challenge/solver Pod/Service/Ingress) plus a host-scoped belt-and-braces sweep (Challenges by .spec.dnsName, solver Ingresses by .spec.rules[0].host), explicitly not a namespace-wide label sweep.

  • Hook vs inner-HR uninstall race — documented as a NOTE in the description: the PVC sweep is eventually-consistent; a PVC may briefly stay Terminating until jobservice/trivy pods exit, the delete request itself always succeeds.

  • Out-of-scope orphans (CNPG / Redis) — added a "Cleanup scope — what is and isn't removed" section. CNPG DB PVCs (<release>-db-N) and Redis PVCs are intentionally not touched (managed by their own operators, different labels), and the re-install-with-different-DB-size leak is called out explicitly.

  • Image not digest-pinned / not via cozy-lib.images-registry — split into follow-up fix(images): digest-pin cleanup-hook kubectl image and route it through cozy-lib.images-registry (harbor, mariadb) #3088 (covers both this hook and the pre-existing MariaDB one), referenced from the description.

Also resolved the open CodeRabbit/Gemini threads (resources requests/limits were already added; deletecollection is not required since deletes are by-name or kubectl delete -l list+delete; PVC -system selector is intentional — jobservice+trivy kept PVCs only, DB/Redis untouched).

Re the E2E failure: it's in the base install step (tenant-root/ingress, monitoring, seaweedfs timed out waiting for their inner -system HRs) — unrelated to Harbor (the harbor-rd HR went Ready). Looks like a sandbox flake; a re-run should be green. PTAL.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-tls only resolves a real object for certificate (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 dropping order,challenge from the by-name delete.
  • In Gateway mode no per-app Ingress or <release>-ingress-tls Certificate 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>
@scooby87

Copy link
Copy Markdown
Contributor Author

Aleksei Sviridkin (@lexfrei) thanks — confirmed and fixed in b25f294. Verified your analysis against the vendored chart (packages/system/harbor/charts/harbor/):

  • jobservice PVC → harbor.labels → carries app.kubernetes.io/instance and release
  • trivy PVC (data-<release>-trivy-0) → volumeClaimTemplate labels = harbor.legacy.labels (heritage, release, chart, app) + selector matchLabels = harbor.matchLabels (release, app) → no app.kubernetes.io/instance

So the old selector did silently miss trivy (on by default).

Blocking fix: PVC sweep now selects -l release=<release>-system, which both kept PVCs share; CNPG/Redis use different label schemes and the registry is on S3, so only those two match. Unit test updated to assert the new selector and a notMatchRegex guard against regressing to the instance label.

Non-blocking, both applied:

  • Dropped order,challenge from the by-name delete — only kubectl delete certificate <release>-ingress-tls remains (Orders/Challenges are hash-suffixed → by-name was a no-op; the Certificate owner-ref cascade + host-scoped Challenge sweep handle them). Also narrowed the hook RBAC from orders,challengeschallenges only (Orders are cascade-deleted by cert-manager itself, so the hook needs no RBAC for them), with a notContains test guard.
  • Added a one-line comment that in Gateway mode the Certificate delete is a no-op and the dnsName-scoped sweep still covers http01.

helm unittest green (9/9). The PR body/release-note already described trivy coverage, so they're now accurate rather than needing a change.

Residual item (matches your own caveat): the "CNPG/Redis PVCs don't carry release=<release>-system" guarantee is verified by label scheme but is best confirmed by the uninstall smoke test on a live cluster. PTAL.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  1. The PR description is now inaccurate: it states PVCs are selected by app.kubernetes.io/instance=<release>-system and that they carry that label "not <release>" — the shipped selector is release=<release>-system (the opposite), which is precisely what the final commit changed. The RBAC paragraph also lists orders, which the hook deliberately does not grant. Worth correcting before merge so the release note and merge record match the code.
  2. The PVC delete runs with the default --wait=true and the Job has no activeDeadlineSeconds. Because the inner <release>-system HelmRelease is uninstalled asynchronously by Flux, the hook can fire while jobservice/trivy pods are still running and block on pvc-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.
  3. No e2e coverage: harbor.bats exists 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 IvanHunters left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:30docker.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: keep to retain jobservice file logs or the trivy vulnerability-DB cache across a helm uninstall will now see those PVCs force-deleted on next teardown. The release-note flags this; CNPG and Redis PVCs are verified untouched (CNPG uses cnpg.io/cluster labels, Redis is managed by redis-operator with its own labels — neither carries release=<release>-system).
  • Eventually-consistent PVC reclaim while the inner <release>-system HelmRelease is still being uninstalled by Flux: PVCs may stay in Terminating until jobservice/trivy Pods exit. Acknowledged in the PR body NOTE; the Job's kubectl delete pvc itself always succeeds.
  • Gateway mode (where no per-app Ingress or <release>-ingress-tls Certificate is rendered): the kubectl delete certificate is a no-op (covered by --ignore-not-found), and the dnsName-scoped Challenge sweep still applies. Verified against templates/httproute.yaml and templates/ingress.yaml.
  • Job runs in a tenant-restricted PSS namespace: pod-level runAsNonRoot/runAsUser=65534/seccompProfile=RuntimeDefault and container-level allowPrivilegeEscalation=false/readOnlyRootFilesystem=true/capabilities.drop=[ALL] satisfy restricted PSS.

Recommended follow-ups

  • Update the PR description to match the actual selector (release=<release>-system, not app.kubernetes.io/instance=<release>-system).
  • In #3088, in addition to digest-pinning, consider adding backoffLimit / activeDeadlineSeconds to both the harbor and mariadb cleanup Jobs so a hook failure cannot indefinitely stall app deletion.

@scooby87
scooby87 merged commit bef4222 into main Jun 30, 2026
14 checks passed
@scooby87
scooby87 deleted the fix/harbor-pvc-and-acme-orphaned branch June 30, 2026 12:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/storage Issues or PRs related to storage (linstor, seaweedfs, bucket, velero, harbor) kind/bug Categorizes issue or PR as related to a bug size/L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

harbor: jobservice PVC (resourcePolicy: keep) and ACME solver resources orphaned after deletion

3 participants