Skip to content

fix(harbor): preserve jobservice and trivy storageClass on upgrade - #2930

Merged
Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
fix/harbor-jobservice-storageclass
Jun 30, 2026
Merged

fix(harbor): preserve jobservice and trivy storageClass on upgrade#2930
Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
fix/harbor-jobservice-storageclass

Conversation

@myasnikovdaniil

@myasnikovdaniil myasnikovdaniil commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

What this PR does

The apps/harbor chart never passed storageClass to the jobservice jobLog PVC, so the upstream chart rendered it without a storageClassName. On first install the default-StorageClass admission then stamps the cluster default onto the live PVC. Because a PVC's storageClassName is immutable, the rendered manifest (no class) and the live object (admission-defaulted class) drift apart, which surfaced as upgrade failures on the harbor-*-system HelmRelease.

This propagates storageClass to the jobservice PVC, with one safeguard: a lookup reads the live jobservice PVC and reuses its current storageClassName when the PVC already exists, only falling back to the configured storageClass for a fresh install. Keeping the rendered value equal to the live one means an existing PVC whose admission-defaulted class differs from the configured storageClass is preserved rather than triggering an immutable-field rejection on upgrade. A live PVC pinned to an explicit "" (no dynamic provisioning) round-trips through the upstream "-" sentinel instead of collapsing to an omitted field, and the spec access is guarded so a PVC with no storageClassName key stays omitted.

The trivy StatefulSet's volumeClaimTemplate carried the same latent drift — .Values.storageClass was forwarded blindly into a VCT, which cannot be patched at all — so it now gets the identical lookup-preserve treatment, keyed off data-<release>-trivy-0. This closes both halves of #2368.

Also adds a helm-unittest regression suite and wires a test: target so make unit-tests stops skipping packages/apps/harbor.

Validated with helm template across storageClass/trivy permutations and the new unit tests. The jobservice preserve path was additionally confirmed with a server-side dry-run against a running harbor release: lookup resolved the live PVC's class, and with storageClass overridden to a different class the jobservice render stayed pinned to the live PVC's class — confirming the immutable-field rejection is avoided. The trivy change applies the same proven pattern. Note: under helm template/helm-unittest lookup returns empty, so the unit suite exercises only the fresh-install fallback; the preserve branches are covered by the live lookup.

Fixes #2368

Release note

fix(harbor): preserve the jobservice PVC and trivy volumeClaimTemplate storageClass on upgrade, fixing upgrade failures caused by storageClassName drift

Summary by CodeRabbit

  • Bug Fixes

    • Harbor now preserves the existing jobservice storage class during upgrades and ensures it’s applied on initial installation when available, with consistent handling for Trivy persistence as well.
  • Tests

    • Added a Helm unittest regression suite covering storage class propagation and the correct conditional rendering of persistence blocks when storage class and Trivy settings vary.

@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 an issue where the Harbor jobservice PVC was created without an explicit storageClassName, causing it to default to the cluster's default storage class. Because PVC storage classes are immutable, this led to upgrade failures when the rendered chart configuration differed from the cluster-assigned default. The changes ensure the storageClass is correctly propagated while intelligently preserving existing PVC configurations to maintain compatibility during upgrades.

Highlights

  • StorageClass Propagation: Propagated the storageClass configuration to the Harbor jobservice PVC to prevent drift between rendered manifests and live objects.
  • Immutable PVC Handling: Implemented a lookup mechanism to preserve the existing storageClassName for already deployed PVCs, avoiding immutable-field rejection errors during upgrades.
  • Regression Testing: Added a new helm-unittest suite to validate storageClass propagation and enabled the unit test target in the Makefile.
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.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2696fc02-517f-4f91-b7f8-bb26474c70e1

📥 Commits

Reviewing files that changed from the base of the PR and between f7e449e and bdf520b.

📒 Files selected for processing (2)
  • packages/apps/harbor/templates/harbor.yaml
  • packages/apps/harbor/tests/jobservice_storageclass_test.yaml
✅ Files skipped from review due to trivial changes (1)
  • packages/apps/harbor/tests/jobservice_storageclass_test.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/apps/harbor/templates/harbor.yaml

📝 Walkthrough

Walkthrough

The Harbor Helm template now looks up existing jobservice and Trivy PVC storageClass values during render, reuses them when present, and conditionally emits the persistence block. A new helm-unittest suite covers propagation and omission cases.

Changes

Harbor jobservice PVC storageClass fix

Layer / File(s) Summary
storageClass lookup and persistence rendering
packages/apps/harbor/templates/harbor.yaml
Adds live PVC lookups for jobservice and Trivy storageClassName, falls back to .Values.storageClass when absent, and renders persistentVolumeClaim only when jobservice storageClass data exists or Trivy is enabled.
helm-unittest regression cases
packages/apps/harbor/tests/jobservice_storageclass_test.yaml
Adds tests covering storageClass propagation to jobservice and Trivy, plus omission of the jobservice block and the full persistence block under unset/disabled inputs.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

  • #2368: The PR implements the storageClass propagation fix described in the issue for both jobservice PVCs and Trivy PVCs.
  • #2430: The PR addresses the same PVC storageClass drift during Harbor upgrades and adds regression tests for it.

Poem

🐇 I hopped through Helm with a careful ear,
Found PVC storageClass hidden near.
Jobservice and Trivy now keep what they knew,
And upgrades bounce along in a steadier queue.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: preserving Harbor jobservice and trivy storageClass values during upgrades.
Linked Issues check ✅ Passed The template preserves existing PVC storageClassName via lookup and passes storageClass on fresh installs, addressing #2368 for jobservice and trivy.
Out of Scope Changes check ✅ Passed The PR stays focused on Harbor storageClass handling and its regression test, with no unrelated code changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/harbor-jobservice-storageclass

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.

@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/M This PR changes 30-99 lines, ignoring generated files labels Jun 16, 2026

@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 addresses issue #2368 in the Harbor package by preserving the existing storageClassName of the jobservice PVC during upgrades via a cluster lookup, falling back to the configured storageClass on fresh installs. It also adds a test target to the Makefile and introduces a comprehensive helm-unittest suite to verify this behavior. The review feedback suggests using the dig function when accessing nested fields on the looked-up PVC to prevent potential template rendering errors if the spec field is missing or nil.

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.

{{- $jobservicePvc := lookup "v1" "PersistentVolumeClaim" .Release.Namespace (printf "%s-jobservice" .Release.Name) }}
{{- $jobserviceStorageClass := .Values.storageClass }}
{{- if $jobservicePvc }}
{{- $jobserviceStorageClass = $jobservicePvc.spec.storageClassName | default "" }}

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.

medium

To prevent potential template rendering errors due to nil pointer dereferences, it is safer to use the dig function when navigating nested maps returned by lookup. If spec is somehow missing or nil in the returned resource, accessing .spec.storageClassName directly will cause a rendering failure. Using dig provides a safe fallback.

  {{- $jobserviceStorageClass = dig "spec" "storageClassName" "" $jobservicePvc }}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in bdf520bf7 — the spec access is now guarded with $jobservicePvc.spec | default dict before hasKey, so a missing/nil spec can't panic. I kept hasKey rather than dig "spec" "storageClassName" "" because dig's default can't distinguish an explicit storageClassName: "" (which must round-trip as the upstream "-" sentinel) from an absent field (which must stay omitted) — both would collapse to "".

@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

🤖 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/harbor.yaml`:
- Around line 34-38: The issue is that the jobservice storage class lookup uses
`| default ""` which treats an explicitly set empty string (`""` in
`spec.storageClassName`) as falsy and collapses it, losing the explicit "no
dynamic provisioning" state. Fix this by using a conditional that checks whether
the `spec.storageClassName` field exists (not just whether it is truthy) so that
explicitly empty values are preserved as `"-"`. Apply this fix to both the
initial jobservice PVC lookup block around line 37 (where
`$jobserviceStorageClass` is assigned from
`$jobservicePvc.spec.storageClassName`) and to any other similar storage class
assignment blocks that reference the jobservice PVC (around lines 125-131),
ensuring that explicit empty strings are maintained across upgrades instead of
being omitted from the template output.
🪄 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: b6e2f09a-f8d5-4011-b33d-7cd1d6549790

📥 Commits

Reviewing files that changed from the base of the PR and between 39f3ac7 and 9e9cb99.

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

Comment thread packages/apps/harbor/templates/harbor.yaml
@myasnikovdaniil myasnikovdaniil added the backport Should change be backported on previous release label Jun 16, 2026
@myasnikovdaniil myasnikovdaniil self-assigned this Jun 23, 2026
@myasnikovdaniil
myasnikovdaniil force-pushed the fix/harbor-jobservice-storageclass branch from 9e9cb99 to f7e449e Compare June 23, 2026 09:33

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 — approving. No blockers: the jobservice PVC storageClass is correctly threaded, and the lookup-preserve logic keeps the rendered manifest equal to the live object so the immutable-field rejection on upgrade is avoided.

Business context: Harbor's jobservice PVC was rendered without a storageClassName, so default-StorageClass admission stamped a class onto the live PVC that the rendered manifest never carried; the resulting drift against the immutable PVC field broke upgrades of the harbor system HelmRelease.

I verified the fix against the upstream subchart and the live-PVC semantics. The problem is real — upstream emits storageClassName only when jobLog.storageClass is set, and main never passed it through for jobservice. The common path is correct: an existing PVC's class is read back and re-rendered so the manifest matches the live object; a fresh install falls back to .Values.storageClass, and a fresh install with an empty storageClass self-heals on the next reconcile once lookup observes the admission-defaulted class.

Blockers: none.

Non-blocking:

  1. Explicit-empty PVC class is not preserved. When an existing jobservice PVC has spec.storageClassName: "" (upstream's - / no-dynamic-provisioning state), lookup ... | default "" collapses to falsy and the {{- with $jobserviceStorageClass }} block is skipped, so the rendered manifest omits storageClassName instead of reproducing "". That deviates from the PR's own "keep rendered equal to live" invariant. Narrow in practice — a cluster with a default StorageClass never produces "" — but it is the one input the preserve logic does not round-trip. Evidence: packages/apps/harbor/templates/harbor.yaml:37 (| default "") and :127 ({{- with ... }}), against upstream packages/system/harbor/charts/harbor/templates/jobservice/jobservice-pvc.yaml:25-31 (- renders storageClassName: "").

  2. Fixes #2368 over-closes the issue. The linked issue covers both the jobservice PVC and the trivy StatefulSet VCT; this PR addresses only jobservice, while the trivy block still passes .Values.storageClass blindly into an immutable VCT. On merge the issue auto-closes with the trivy half unfixed. Consider Refs #2368 (or a dedicated trivy follow-up) so the trivy immutability is not silently dropped. Evidence: issue #2368 body lists both PVCs; packages/apps/harbor/templates/harbor.yaml:132-138 (trivy still uses .Values.storageClass, unchanged from main).

  3. The upgrade path itself is not unit-tested. helm-unittest renders lookup as empty, so all four new cases exercise only the fresh-install fallback; the preserve-existing-class branch — the actual upgrade-safety logic — is covered only by the manual server-side dry-run noted in the description. This is an inherent helm-unittest limitation, not a request to add a test. Evidence: packages/apps/harbor/tests/jobservice_storageclass_test.yaml (every case sets or omits storageClass; none mocks an existing PVC).

The cozy-harbor wrapper never passed storageClass to the jobservice
jobLog PVC, so it was created without a storageClassName and the
default-StorageClass admission stamped the cluster default. Because PVC
storageClassName is immutable, this caused rendered-vs-live drift on
upgrade. Preserve an existing PVC's class via lookup and fall back to the
configured storageClass for fresh installs.

The trivy StatefulSet's volumeClaimTemplate had the same latent drift --
.Values.storageClass was forwarded blindly into a VCT that cannot be
patched at all -- so it gets the same lookup-preserve treatment, keyed
off data-<release>-trivy-0.

A live object pinned to an explicit "" (no dynamic provisioning) now
round-trips through upstream's "-" sentinel instead of collapsing to an
omitted storageClassName, and the spec lookup is guarded with
`| default dict` so a PVC carrying no storageClassName key stays omitted.

Adds a helm-unittest regression suite and wires a test target so
make unit-tests stops skipping packages/apps/harbor.

Fixes #2368

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
@myasnikovdaniil
myasnikovdaniil force-pushed the fix/harbor-jobservice-storageclass branch from f7e449e to bdf520b Compare June 29, 2026 08:16
@myasnikovdaniil myasnikovdaniil changed the title fix(harbor): propagate storageClass to jobservice PVC fix(harbor): preserve jobservice and trivy storageClass on upgrade Jun 29, 2026
@github-actions github-actions Bot added size/L This PR changes 100-499 lines, ignoring generated files and removed size/M This PR changes 30-99 lines, ignoring generated files labels Jun 29, 2026
@myasnikovdaniil

Copy link
Copy Markdown
Contributor Author

Aleksei Sviridkin (@lexfrei) thanks for the thorough review. Addressed in bdf520bf7:

  1. Explicit-empty PVC class — fixed. The lookup uses hasKey on the live spec: an explicit storageClassName: "" maps to the upstream "-" sentinel (re-rendered as storageClassName: ""), a real class is preserved verbatim, and an absent key keeps the field omitted. The "rendered == live" invariant now holds for all three states.
  2. Fixes #2368 / trivy half — closed the trivy half rather than downgrading to Refs. The trivy StatefulSet VCT had the identical blind-.Values.storageClass drift (and a VCT can't be patched at all), so it now gets the same lookup-preserve treatment keyed off data-<release>-trivy-0. Fixes #2368 is now accurate.
  3. Preserve branch not unit-tested — acknowledged; that's the helm-unittest lookup-returns-empty limitation you noted. The suite still covers only the fresh-install fallback; the preserve path (jobservice and now trivy) is covered by the manual server-side dry-run. No test added.

One caveat for transparency: the server-side dry-run validation was jobservice-only — the trivy change applies the same proven pattern but wasn't separately dry-run'd.

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 — propagates storageClass to the jobservice PVC and trivy VCT with a lookup-preserve guard, fixing the immutable-storageClassName drift from #2368.

Verified:

  • PVC naming is correct: harbor.fullnameOverride: {{ .Release.Name }} makes the upstream subchart name resources <release>-*, so <release>-jobservice and data-<release>-trivy-0 are the right lookup keys.
  • The lookup-preserve logic is sound across all four states: fresh install (configured class), existing PVC (preserve live class), explicit "" (round-trips via the "-" sentinel), and no storageClassName key (stays omitted to match live).
  • | quote is necessary, not cosmetic: an unquoted - sentinel would be ambiguous YAML; quoting yields "-".
  • lookup is an established, working pattern in this very chart (credentials/token-cert preservation already rely on it), which de-risks the Flux helm-controller concern.

Non-blocking:

  • PR/commit body says it "wires a test: target so make unit-tests stops skipping packages/apps/harbor", but the test: target and a tests/ suite already exist on main — harbor was already covered. The new suite does run; only the description is inaccurate. Worth correcting so git log stays accurate.
  • helm-unittest can't exercise the preserve branches (lookup returns nil there), so only the fresh-install fallback is covered in CI; the actual new behavior rests on the manual server-side dry-run. An e2e assertion would close the loop.
  • db/redis still forward .Values.storageClass without the lookup-preserve guard; if those PVCs can hit the same #2368 drift when storageClass is unset, they'd need the same treatment — confirm they're immune (operator-managed) or out of scope.

@lexfrei
Aleksei Sviridkin (lexfrei) merged commit 544ba5b into main Jun 30, 2026
13 checks passed
@lexfrei
Aleksei Sviridkin (lexfrei) deleted the fix/harbor-jobservice-storageclass branch June 30, 2026 08:27
@github-actions

Copy link
Copy Markdown

Successfully created backport PR for release-1.5:

myasnikovdaniil added a commit that referenced this pull request Jun 30, 2026
…rageClass on upgrade (#3147)

# Description
Backport of #2930 to `release-1.5`.
@myasnikovdaniil myasnikovdaniil added the backport-previous Backport target — previous release line label Jun 30, 2026
@github-actions

Copy link
Copy Markdown

Backport failed for release-1.5, because it was unable to cherry-pick the commit(s).

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin release-1.5
git worktree add -d .worktree/backport-2930-to-release-1.5 origin/release-1.5
cd .worktree/backport-2930-to-release-1.5
git switch --create backport-2930-to-release-1.5
git cherry-pick -x bdf520bf74a7ed17a62bfeaa669ed2eef7b45509

@github-actions

Copy link
Copy Markdown

Successfully created backport PR for release-1.4:

myasnikovdaniil added a commit that referenced this pull request Jun 30, 2026
…rageClass on upgrade (#3151)

# Description
Backport of #2930 to `release-1.4`.
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) backport Should change be backported on previous release backport-previous Backport target — previous release line 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] Missing storageClass for trivy and jobservice PVCs breaks upgrades

2 participants