Skip to content

feat: Add support for namespacedcloudprofiles#2691

Open
klocke-io wants to merge 3 commits intomasterfrom
feature/namespaced-cloud-profile-support
Open

feat: Add support for namespacedcloudprofiles#2691
klocke-io wants to merge 3 commits intomasterfrom
feature/namespaced-cloud-profile-support

Conversation

@klocke-io
Copy link
Member

@klocke-io klocke-io commented Nov 18, 2025

What this PR does / why we need it:
Add support for namespacedcloudprofiles

Which issue(s) this PR fixes:
Fixes # #1971

Special notes for your reviewer:
NSCP in create cluster dialog

image

No more warnings because the cloudprofile is not understood
image
image
image
image

Release note:

Add namespacedcloudprofiles support

Summary by CodeRabbit

Release Notes

  • New Features
    • Added support for namespaced cloud profiles, allowing providers to customize cloud profiles per namespace.
    • Introduced new API endpoint to retrieve namespaced cloud profiles with optional diff view for comparing against parent profiles.
    • Enhanced cloud profile selection UI to display both regular and namespaced cloud profiles.
    • Added authorization controls for accessing namespaced cloud profiles.

@gardener-robot gardener-robot added needs/review Needs review size/L Denotes a PR that changes 100-499 lines, ignoring generated files. needs/second-opinion Needs second review by someone else labels Nov 18, 2025
@klocke-io klocke-io force-pushed the feature/namespaced-cloud-profile-support branch 3 times, most recently from a561dff to 674686f Compare November 19, 2025 08:41
@gardener-robot
Copy link

@holgerkoser, @grolu You have pull request review open invite, please check

@grolu grolu added area/ipcei IPCEI (Important Project of Common European Interest) and removed area/ipcei IPCEI (Important Project of Common European Interest) labels Dec 1, 2025
'/projects': projectsRoute,
'/namespaces/:namespace/shoots': shootsRoute,
'/namespaces/:namespace/tickets': ticketsRoute,
'/namespaces/:namespace/cloudprofiles': namespacedCloudProfilesRoute,
Copy link
Member

Choose a reason for hiding this comment

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

let's align closer to the gardener API (/apis/core.gardener.cloud/v1beta1/namespaces/garden-core/namespacedcloudprofiles)

Suggested change
'/namespaces/:namespace/cloudprofiles': namespacedCloudProfilesRoute,
'/namespaces/:namespace/namespacedcloudprofiles': namespacedCloudProfilesRoute,

@gardener-ci-robot
Copy link
Contributor

The Gardener project currently lacks enough active contributors to adequately respond to all PRs.
This bot triages PRs according to the following rules:

  • After 15d of inactivity, lifecycle/stale is applied
  • After 15d of inactivity since lifecycle/stale was applied, lifecycle/rotten is applied
  • After 7d of inactivity since lifecycle/rotten was applied, the PR is closed

You can:

  • Mark this PR as fresh with /remove-lifecycle stale
  • Mark this PR as rotten with /lifecycle rotten
  • Close this PR with /close

/lifecycle stale

@gardener-robot gardener-robot added the lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. label Dec 23, 2025
@gardener-ci-robot
Copy link
Contributor

The Gardener project currently lacks enough active contributors to adequately respond to all PRs.
This bot triages PRs according to the following rules:

  • After 15d of inactivity, lifecycle/stale is applied
  • After 15d of inactivity since lifecycle/stale was applied, lifecycle/rotten is applied
  • After 7d of inactivity since lifecycle/rotten was applied, the PR is closed

You can:

  • Mark this PR as fresh with /remove-lifecycle rotten
  • Close this PR with /close

/lifecycle rotten

@gardener-robot gardener-robot added lifecycle/rotten Denotes an issue or PR that has aged beyond stale and will be auto-closed. and removed lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. labels Jan 7, 2026
@grolu grolu added lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. lifecycle/frozen Indicates that an issue or PR should not be auto-closed due to staleness. and removed lifecycle/rotten Denotes an issue or PR that has aged beyond stale and will be auto-closed. lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. labels Jan 14, 2026
@klocke-io
Copy link
Member Author

/remove-lifecycle rotten

@klocke-io klocke-io removed the lifecycle/frozen Indicates that an issue or PR should not be auto-closed due to staleness. label Jan 26, 2026
@klocke-io klocke-io self-assigned this Jan 26, 2026
@klocke-io klocke-io marked this pull request as draft January 26, 2026 14:05
@gardener-prow gardener-prow bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jan 26, 2026
@klocke-io klocke-io force-pushed the feature/namespaced-cloud-profile-support branch from 674686f to a3a62e7 Compare January 27, 2026 11:40
@gardener-prow
Copy link

gardener-prow bot commented Jan 27, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please ask for approval from klocke-io. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@gardener-prow gardener-prow bot added cla: yes Indicates the PR's author has signed the cla-assistant.io CLA. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Feb 3, 2026
@klocke-io klocke-io force-pushed the feature/namespaced-cloud-profile-support branch from 23fa0b5 to 6d52a64 Compare March 3, 2026 13:08
@coderabbitai
Copy link

coderabbitai bot commented Mar 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 28a41aaa-0d01-4785-b258-b41685172326

📥 Commits

Reviewing files that changed from the base of the PR and between 6d52a64 and b6bbcc0.

📒 Files selected for processing (2)
  • backend/__fixtures__/index.cjs
  • backend/__fixtures__/namespacedCloudprofiles.cjs

📝 Walkthrough

Walkthrough

This PR introduces comprehensive support for NamespacedCloudProfile resources, a new Kubernetes resource enabling namespace-scoped cloud profile configurations. Changes span backend API routes, services, caching, authorization, frontend UI components, and state management, with integrated diff-based profile specification handling via jsondiffpatch.

Changes

Cohort / File(s) Summary
Core Resource Definition & Runtime
.pnp.cjs, packages/kube-client/lib/resources/GardenerCore.js, charts/gardener-dashboard/charts/application/templates/dashboard/clusterrole.yaml
Adds NamespacedCloudProfile resource class with metadata; includes jsondiffpatch in runtime state; updates RBAC ClusterRole to grant list/watch on namespacedcloudprofiles.
Backend API Routes
backend/lib/routes/index.js, backend/lib/routes/namespacedCloudProfiles.js
New route handler for GET /api/namespaces/:namespace/namespacedcloudprofiles with diff query parameter support; extracts namespace and user, forwards to service layer.
Backend Services
backend/lib/services/index.js, backend/lib/services/namespacedCloudProfiles.js, backend/lib/services/authorization.js
New NamespacedCloudProfiles service with listForNamespace method supporting optional diff-based representation; adds canListNamespacedCloudProfiles authorization check; cloudprofiles service simplified by removing read function.
Backend Caching & Utilities
backend/lib/cache/index.js, backend/lib/hooks.js, backend/lib/utils/index.js
Cache adds getCloudProfile, getNamespacedCloudProfiles methods with deep cloning; informer registered for namespacedcloudprofiles; utilities introduce simplifyCloudProfile and stripProviderConfig for profile normalization.
Backend Configuration & Mocking
backend/package.json, backend/jest.config.cjs, backend/jest.setup.cjs, backend/vitest.config.js, backend/__mocks__/jsondiffpatch.cjs
Adds jsondiffpatch, vitest, and coverage dependencies; updates jest config with moduleNameMapper for jsondiffpatch mock; adds vitest configuration; includes minimal jsondiffpatch mock for Jest.
Backend Fixtures
backend/__fixtures__/index.cjs, backend/__fixtures__/namespacedCloudprofiles.cjs
Registers namespacedcloudprofiles fixture; introduces hardcoded fixture data with three namespaced cloud profiles supporting get/list/create operations.
Backend Tests
backend/__tests__/acceptance/api.cloudprofiles.spec.cjs, backend/__tests__/acceptance/api.namespacedCloudprofiles.spec.cjs, backend/__tests__/cache.spec.cjs, backend/__tests__/services.namespacedCloudProfiles.spec.js
Adds acceptance tests for namespaced profiles API (200/403 scenarios); cloudprofiles test adds unauthorized list check; cache test switches assertion to deep equality; comprehensive service tests for diff handling and spec reconstruction.
Frontend Utilities
frontend/src/utils/index.js, frontend/__tests__/utils/index.spec.js
Introduces getCloudProfileSpec utility to abstract spec access (status.cloudProfileSpec for NamespacedCloudProfile, spec otherwise); includes unit tests covering both kinds and edge cases.
Frontend Composables & Helpers
frontend/src/composables/helper.js, frontend/src/composables/useShootHelper.js, frontend/src/composables/useShootMessages.js, frontend/src/composables/useApi/api.js, frontend/src/composables/useCloudProfile/\*\.js
Multiple composables refactored to use getCloudProfileSpec for consistent spec access; useShootHelper adds isNamespacedCloudProfile and namespace-aware defaults; api.js adds getNamespacedCloudProfiles endpoint call; all useCloudProfile composables (machineTypes, regions, volumes, etc.) delegate spec retrieval to helper.
Frontend Component
frontend/src/components/GSelectCloudProfile.vue
Removes namespacedCloudProfiles prop; unified cloudProfiles source drives single selectItems mapping; item template includes namespaced chip when applicable; relies on cloudProfileStore.cloudProfileByRef for current selection.
Frontend Store
frontend/src/store/cloudProfile/index.js, frontend/src/store/seed.js, frontend/src/router/guards.js
CloudProfile store expands to manage namespacedList, namespacedListsByNamespace with rehydration via diff; adds cloudProfileByRef with namespace-aware resolution; seed matcher refactored to use getCloudProfileSpec; router guards adopt namespace-aware profile loading strategy with conditional fetch logic.
Frontend Tests
frontend/__tests__/stores/cloudProfile.spec.js, frontend/__tests__/composables/useCloudProfile/useMachineTypes.spec.js
Extensive new test suite for cloudProfileByRef resolution, namespaced caching, rehydration with diffs, provider-type aggregation; useMachineTypes adds test case verifying immutability of original cloud profile.
Frontend Dependencies
frontend/package.json
Adds jsondiffpatch (^0.7.3) dependency.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Suggested labels

kind/enhancement, approved, lgtm

Suggested reviewers

  • holgerkoser

Poem

🐰 A namespace-scoped profile takes flight,
With diffs applied and specs made right,
CloudProfiles bloom in every zone,
NamespacedCloudProfiles claim their own—
The dashboard hops through diff-patched dreams! 🌱

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding support for namespacedcloudprofiles, which aligns with the primary objective of the changeset.
Description check ✅ Passed The PR description addresses the template sections with what, why, issue reference, special notes, and a properly formatted release note, though it could be more detailed about implementation specifics.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/namespaced-cloud-profile-support

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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 and usage tips.

@klocke-io klocke-io marked this pull request as ready for review March 3, 2026 13:09
@gardener-prow gardener-prow bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Mar 3, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (7)
frontend/src/composables/useShootMessages.js (1)

49-49: Prefer a more specific JSDoc shape than object for better DX.

Ref<object> works, but a minimal structural type would preserve IntelliSense while still covering both profile kinds.

♻️ Optional JSDoc refinement
- * `@param` {Ref<object>} cloudProfile - A Vue ref containing a CloudProfile or NamespacedCloudProfile object
+ * `@param` {Ref<{ kind?: string, spec?: object }>} cloudProfile - A Vue ref containing a CloudProfile-like object
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/composables/useShootMessages.js` at line 49, The JSDoc for the
cloudProfile parameter is too generic (Ref<object>) which hurts IntelliSense;
update the JSDoc on the cloudProfile param in useShootMessages (the cloudProfile
param of the function exported from
frontend/src/composables/useShootMessages.js) to a minimal structural type that
covers both CloudProfile and NamespacedCloudProfile (for example a Ref with
metadata and optional namespace fields or an explicit union like
Ref<CloudProfile|NamespacedCloudProfile>) so editors can infer properties like
metadata.name/namespace while still accepting both kinds.
backend/__tests__/acceptance/api.namespacedCloudprofiles.spec.cjs (1)

33-37: Add an explicit 200 assertion in the success path.

This test already validates content type and body; adding .expect(200) makes the contract stricter and clearer.

✅ Suggested tweak
       const res = await agent
         .get(`/api/namespaces/${namespace}/namespacedcloudprofiles`)
         .set('cookie', await user.cookie)
+        .expect(200)
         .expect('content-type', /json/)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/__tests__/acceptance/api.namespacedCloudprofiles.spec.cjs` around
lines 33 - 37, The test's request chain lacks an explicit HTTP status assertion,
so update the agent GET call to assert a 200 OK response by adding .expect(200)
in the request chain (e.g., on the
agent.get(`/api/namespaces/${namespace}/namespacedcloudprofiles`) call that
currently sets 'cookie' and expects 'content-type' and inspects res.body);
ensure .expect(200) is placed before or alongside the existing
.expect('content-type', /json/) to make the success contract explicit.
frontend/src/composables/useApi/api.js (1)

239-242: Consider making diff configurable in the API helper.

Hardcoding diff=true couples all callers to diff computation. Exposing a parameter (default as needed) keeps this endpoint reusable and avoids unnecessary backend work.

♻️ Proposed refactor
-export function getNamespacedCloudProfiles ({ namespace }) {
+export function getNamespacedCloudProfiles ({ namespace, diff = true }) {
   namespace = encodeURIComponent(namespace)
-  return getResource(`/api/namespaces/${namespace}/namespacedcloudprofiles?diff=true`)
+  const search = new URLSearchParams({ diff: String(diff) }).toString()
+  return getResource(`/api/namespaces/${namespace}/namespacedcloudprofiles?${search}`)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/composables/useApi/api.js` around lines 239 - 242, Update
getNamespacedCloudProfiles to accept a diff option instead of hardcoding
diff=true: modify the function signature (getNamespacedCloudProfiles) to take an
options object (e.g., { namespace, diff = true }) or an extra parameter,
url-encode namespace as before, build the query string to include diff only when
provided (using the diff value as default true if desired), and call getResource
with `/api/namespaces/${namespace}/namespacedcloudprofiles?diff=${diff}` (or
conditionally append the diff param) so callers can override or omit diff to
avoid forcing diff computation.
backend/lib/utils/index.js (1)

150-185: Consider making simplifyCloudProfile non-mutating.

At Line 168 and Line 182, the function mutates the passed object. Cloning internally would reduce side-effect risk when future callers pass shared references.

♻️ Suggested refactor
 function simplifyCloudProfile (profile) {
+  profile = _.cloneDeep(profile)
   const kind = _.get(profile, ['kind'])

   let entrypath = ['spec']
   if (kind === 'NamespacedCloudProfile') {
     entrypath = ['status', 'cloudProfileSpec']
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/lib/utils/index.js` around lines 150 - 185, simplifyCloudProfile
currently mutates its input by calling simplifyObjectMetadata(profile) and
_.set(profile, [...entrypath, 'providerConfig'], strippedProviderConfig); to
make it non-mutating, create a deep clone of the incoming profile at the start
(e.g., cloneProfile = _.cloneDeep(profile)) and operate on that clone throughout
(pass cloneProfile into simplifyObjectMetadata and set the providerConfig on
cloneProfile instead of profile), and when calling stripProviderConfig ensure
you also clone providerConfig before passing it if stripProviderConfig mutates
its input; return the cloned/modified object (cloneProfile) and leave the
original profile unchanged, referencing the simplifyCloudProfile function,
profile variable, providerConfig variable, simplifyObjectMetadata and
stripProviderConfig helpers, and the _.set usage to locate the changes.
frontend/src/composables/useShootHelper.js (1)

102-107: Avoid namespace shadowing in defaultCloudProfileRef.

The local namespace at Line 102 shadows the outer namespace computed ref, which makes this block harder to read.

🧹 Small readability tweak
-    const namespace = get(defaultCloudProfile, ['metadata', 'namespace'])
+    const profileNamespace = get(defaultCloudProfile, ['metadata', 'namespace'])
     return {
       name,
-      kind: namespace ? 'NamespacedCloudProfile' : 'CloudProfile',
-      ...(namespace && { namespace }),
+      kind: profileNamespace ? 'NamespacedCloudProfile' : 'CloudProfile',
+      ...(profileNamespace && { namespace: profileNamespace }),
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/composables/useShootHelper.js` around lines 102 - 107, The local
variable named "namespace" inside the object returned from defaultCloudProfile
(const namespace = get(defaultCloudProfile, ['metadata', 'namespace'])) shadows
the outer computed ref named "namespace" (defaultCloudProfileRef), hurting
readability; rename the local to something like "profileNamespace" (or
"cpNamespace") and update its usage in the returned object (kind:
profileNamespace ? 'NamespacedCloudProfile' : 'CloudProfile',
...(profileNamespace && { namespace: profileNamespace })) so the outer computed
ref remains unshadowed and the code is clearer.
backend/__tests__/services.namespacedCloudProfiles.spec.js (1)

227-230: Prefer name-based assertions over index-based assertions in these tests.

Using profile identity (e.g., metadata.name) instead of fixed positions will make tests less brittle to ordering changes.

Also applies to: 269-272

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/__tests__/services.namespacedCloudProfiles.spec.js` around lines 227
- 230, Replace brittle index-based assertions on result[0]/result[1] with
name-based assertions that check the set of profile names; for example, map
result to an array of metadata.name and use something like
expect(names).toEqual(expect.arrayContaining(['custom-cloudprofile-1','custom-cloudprofile-2']))
(or toContain for each name). Apply the same change to the other test block that
asserts result[0]/result[1] (the second occurrence referenced) so both tests
assert on metadata.name values rather than fixed positions.
backend/lib/services/namespacedCloudProfiles.js (1)

24-39: Consider memoizing parent-spec simplification in diff mode.

When multiple NamespacedCloudProfiles share a parent, getCloudProfile + simplifyCloudProfile are recomputed for each item. Caching by parent name in the request scope would cut repeated work.

Also applies to: 70-72

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/lib/services/namespacedCloudProfiles.js` around lines 24 - 39,
Multiple NamespacedCloudProfiles recompute getCloudProfile +
simplifyCloudProfile for the same parent; add a request-scoped cache (e.g., a
Map keyed by parentName) inside computeDiff to avoid repeated work: before
calling getCloudProfile/simplifyCloudProfile check the cache for parentName, if
present reuse the cached simplified parent or parentSpec, otherwise call
getCloudProfile and simplifyCloudProfile, store the simplified result in the
cache, then proceed to compute jsondiffpatch.diff; apply the same caching
pattern to the other identical call site around lines 70-72 so both places reuse
the request-scoped memoized simplified parent by parentName.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/__tests__/composables/useCloudProfile/useMachineTypes.spec.js`:
- Line 134: The test creates the computed from useFilteredMachineTypes(region,
architecture) but never evaluates it; change the test to capture the returned
computed (e.g., const filtered = useFilteredMachineTypes(region, architecture))
and then read or assign filtered.value so the computed callback runs and the
defaulting/mutation-sensitive logic executes; ensure any assertions use
filtered.value afterward to validate results.

In `@frontend/src/components/GSelectCloudProfile.vue`:
- Around line 38-49: The custom selection slot currently renders item.raw.title
directly which yields a blank label when the selected value isn't in
selectItems; update the selection template (the template `#selection`="{ item }"
slot) to use a safe fallback chain (e.g. item.raw.title || item.title ||
item.text || item.value) so a meaningful label is shown whenever item.raw.title
is empty or undefined and preserve the existing v-chip logic for
item.raw.namespaced.

In `@frontend/src/router/guards.js`:
- Line 73: The variable assigned to namespace (currently using
to.params.namespace ?? to.query.namespace) must be normalized to a single string
because to.query.namespace can be a string[]; update the assignment in guards.js
so that if to.query.namespace is an array you extract a single string (e.g.,
first element) or join/flatten it, and then fallback to to.params.namespace;
ensure the final namespace is a string (use Array.isArray on to.query.namespace
and coerce/trim as needed) before any validation or API calls that use
namespace.

In `@frontend/src/store/cloudProfile/index.js`:
- Around line 69-75: fetchNamespacedCloudProfilesForNamespaces currently only
adds missing namespaces but never removes stale entries, leaving
namespacedList/allCloudProfiles polluted; update this function to prune cache
entries for namespaces that are no longer in scope: compute the canonical set of
requested namespaces (uniq/filter(Boolean)), call the existing
fetchNamespacedCloudProfiles for any missing ones as before, and then remove any
keys from namespacedList and any entries in allCloudProfiles that belong to
namespaces not in that canonical set (use the store's mutation or helper that
manages namespacedList and allCloudProfiles so state stays consistent). Ensure
you reference and update namespacedList and allCloudProfiles within
fetchNamespacedCloudProfilesForNamespaces so removed namespaces are cleared
after the fetch step.

---

Nitpick comments:
In `@backend/__tests__/acceptance/api.namespacedCloudprofiles.spec.cjs`:
- Around line 33-37: The test's request chain lacks an explicit HTTP status
assertion, so update the agent GET call to assert a 200 OK response by adding
.expect(200) in the request chain (e.g., on the
agent.get(`/api/namespaces/${namespace}/namespacedcloudprofiles`) call that
currently sets 'cookie' and expects 'content-type' and inspects res.body);
ensure .expect(200) is placed before or alongside the existing
.expect('content-type', /json/) to make the success contract explicit.

In `@backend/__tests__/services.namespacedCloudProfiles.spec.js`:
- Around line 227-230: Replace brittle index-based assertions on
result[0]/result[1] with name-based assertions that check the set of profile
names; for example, map result to an array of metadata.name and use something
like
expect(names).toEqual(expect.arrayContaining(['custom-cloudprofile-1','custom-cloudprofile-2']))
(or toContain for each name). Apply the same change to the other test block that
asserts result[0]/result[1] (the second occurrence referenced) so both tests
assert on metadata.name values rather than fixed positions.

In `@backend/lib/services/namespacedCloudProfiles.js`:
- Around line 24-39: Multiple NamespacedCloudProfiles recompute getCloudProfile
+ simplifyCloudProfile for the same parent; add a request-scoped cache (e.g., a
Map keyed by parentName) inside computeDiff to avoid repeated work: before
calling getCloudProfile/simplifyCloudProfile check the cache for parentName, if
present reuse the cached simplified parent or parentSpec, otherwise call
getCloudProfile and simplifyCloudProfile, store the simplified result in the
cache, then proceed to compute jsondiffpatch.diff; apply the same caching
pattern to the other identical call site around lines 70-72 so both places reuse
the request-scoped memoized simplified parent by parentName.

In `@backend/lib/utils/index.js`:
- Around line 150-185: simplifyCloudProfile currently mutates its input by
calling simplifyObjectMetadata(profile) and _.set(profile, [...entrypath,
'providerConfig'], strippedProviderConfig); to make it non-mutating, create a
deep clone of the incoming profile at the start (e.g., cloneProfile =
_.cloneDeep(profile)) and operate on that clone throughout (pass cloneProfile
into simplifyObjectMetadata and set the providerConfig on cloneProfile instead
of profile), and when calling stripProviderConfig ensure you also clone
providerConfig before passing it if stripProviderConfig mutates its input;
return the cloned/modified object (cloneProfile) and leave the original profile
unchanged, referencing the simplifyCloudProfile function, profile variable,
providerConfig variable, simplifyObjectMetadata and stripProviderConfig helpers,
and the _.set usage to locate the changes.

In `@frontend/src/composables/useApi/api.js`:
- Around line 239-242: Update getNamespacedCloudProfiles to accept a diff option
instead of hardcoding diff=true: modify the function signature
(getNamespacedCloudProfiles) to take an options object (e.g., { namespace, diff
= true }) or an extra parameter, url-encode namespace as before, build the query
string to include diff only when provided (using the diff value as default true
if desired), and call getResource with
`/api/namespaces/${namespace}/namespacedcloudprofiles?diff=${diff}` (or
conditionally append the diff param) so callers can override or omit diff to
avoid forcing diff computation.

In `@frontend/src/composables/useShootHelper.js`:
- Around line 102-107: The local variable named "namespace" inside the object
returned from defaultCloudProfile (const namespace = get(defaultCloudProfile,
['metadata', 'namespace'])) shadows the outer computed ref named "namespace"
(defaultCloudProfileRef), hurting readability; rename the local to something
like "profileNamespace" (or "cpNamespace") and update its usage in the returned
object (kind: profileNamespace ? 'NamespacedCloudProfile' : 'CloudProfile',
...(profileNamespace && { namespace: profileNamespace })) so the outer computed
ref remains unshadowed and the code is clearer.

In `@frontend/src/composables/useShootMessages.js`:
- Line 49: The JSDoc for the cloudProfile parameter is too generic (Ref<object>)
which hurts IntelliSense; update the JSDoc on the cloudProfile param in
useShootMessages (the cloudProfile param of the function exported from
frontend/src/composables/useShootMessages.js) to a minimal structural type that
covers both CloudProfile and NamespacedCloudProfile (for example a Ref with
metadata and optional namespace fields or an explicit union like
Ref<CloudProfile|NamespacedCloudProfile>) so editors can infer properties like
metadata.name/namespace while still accepting both kinds.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab65b6d and 6d52a64.

⛔ Files ignored due to path filters (5)
  • .yarn/cache/@dmsnell-diff-match-patch-npm-1.1.0-021308d314-8547bf4a62.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/jsondiffpatch-npm-0.7.3-5579555f56-fae49073e5.zip is excluded by !**/.yarn/**, !**/*.zip
  • backend/__tests__/acceptance/__snapshots__/api.namespacedCloudprofiles.spec.cjs.snap is excluded by !**/*.snap
  • charts/__tests__/gardener-dashboard/application/dashboard/__snapshots__/clusterrole.spec.js.snap is excluded by !**/*.snap
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (44)
  • .pnp.cjs
  • backend/__fixtures__/index.cjs
  • backend/__fixtures__/namespacedcloudprofiles.cjs
  • backend/__mocks__/jsondiffpatch.cjs
  • backend/__tests__/acceptance/api.cloudprofiles.spec.cjs
  • backend/__tests__/acceptance/api.namespacedCloudprofiles.spec.cjs
  • backend/__tests__/cache.spec.cjs
  • backend/__tests__/services.namespacedCloudProfiles.spec.js
  • backend/jest.config.cjs
  • backend/jest.setup.cjs
  • backend/lib/cache/index.js
  • backend/lib/hooks.js
  • backend/lib/routes/index.js
  • backend/lib/routes/namespacedCloudProfiles.js
  • backend/lib/services/authorization.js
  • backend/lib/services/cloudprofiles.js
  • backend/lib/services/index.js
  • backend/lib/services/namespacedCloudProfiles.js
  • backend/lib/utils/index.js
  • backend/package.json
  • backend/vitest.config.js
  • charts/gardener-dashboard/charts/application/templates/dashboard/clusterrole.yaml
  • frontend/__tests__/composables/useCloudProfile/useMachineTypes.spec.js
  • frontend/__tests__/stores/cloudProfile.spec.js
  • frontend/__tests__/utils/index.spec.js
  • frontend/package.json
  • frontend/src/components/GSelectCloudProfile.vue
  • frontend/src/composables/helper.js
  • frontend/src/composables/useApi/api.js
  • frontend/src/composables/useCloudProfile/useDefaultNodesCIDR.js
  • frontend/src/composables/useCloudProfile/useKubernetesVersions.js
  • frontend/src/composables/useCloudProfile/useMachineImages.js
  • frontend/src/composables/useCloudProfile/useMachineTypes.js
  • frontend/src/composables/useCloudProfile/useMetalConstraints.js
  • frontend/src/composables/useCloudProfile/useOpenStackConstraints.js
  • frontend/src/composables/useCloudProfile/useRegions.js
  • frontend/src/composables/useCloudProfile/useVolumeTypes.js
  • frontend/src/composables/useShootHelper.js
  • frontend/src/composables/useShootMessages.js
  • frontend/src/router/guards.js
  • frontend/src/store/cloudProfile/index.js
  • frontend/src/store/seed.js
  • frontend/src/utils/index.js
  • packages/kube-client/lib/resources/GardenerCore.js


const region = ref('region2')
const architecture = ref(undefined)
useFilteredMachineTypes(region, architecture)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the composable returns a lazy computed and the test doesn't dereference it.
rg -n -C2 "function useFilteredMachineTypes|return computed" frontend/src/composables/useCloudProfile/useMachineTypes.js

Repository: gardener/dashboard

Length of output: 661


🏁 Script executed:

#!/bin/bash
rg -n -C4 "should not mutate cloud profile machine types when defaulting architecture|useFilteredMachineTypes\(|\\.value" frontend/__tests__/composables/useCloudProfile/useMachineTypes.spec.js

Repository: gardener/dashboard

Length of output: 3281


Evaluate the computed value in this test to ensure the defaulting logic executes.

Line 134 creates the computed but never accesses it, so the mutation-sensitive logic inside the computed callback won't run. The test should assign and dereference .value to trigger the lazy-evaluated function.

🧪 Suggested fix
-        useFilteredMachineTypes(region, architecture)
+        const dashboardMachineTypes = useFilteredMachineTypes(region, architecture)
+        void dashboardMachineTypes.value
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useFilteredMachineTypes(region, architecture)
const dashboardMachineTypes = useFilteredMachineTypes(region, architecture)
void dashboardMachineTypes.value
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/__tests__/composables/useCloudProfile/useMachineTypes.spec.js` at
line 134, The test creates the computed from useFilteredMachineTypes(region,
architecture) but never evaluates it; change the test to capture the returned
computed (e.g., const filtered = useFilteredMachineTypes(region, architecture))
and then read or assign filtered.value so the computed callback runs and the
defaulting/mutation-sensitive logic executes; ensure any assertions use
filtered.value afterward to validate results.

Comment on lines +38 to +49
<template #selection="{ item }">
<span>{{ item.raw.title }}</span>
<v-chip
v-if="item.raw.namespaced"
size="x-small"
color="primary"
variant="tonal"
class="ml-2"
>
Namespaced
</v-chip>
</template>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Selection label fallback is bypassed in the custom slot.

If the selected value is not present in selectItems, item.raw.title can be empty, so the field renders a blank selection label.

💡 Suggested fix
-    <template `#selection`="{ item }">
-      <span>{{ item.raw.title }}</span>
+    <template `#selection`="{ item }">
+      <span>{{ item.title }}</span>
       <v-chip
-        v-if="item.raw.namespaced"
+        v-if="item.raw.namespaced ?? item.raw.kind === 'NamespacedCloudProfile'"
         size="x-small"
         color="primary"
         variant="tonal"
         class="ml-2"
       >
         Namespaced
       </v-chip>
     </template>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/GSelectCloudProfile.vue` around lines 38 - 49, The
custom selection slot currently renders item.raw.title directly which yields a
blank label when the selected value isn't in selectItems; update the selection
template (the template `#selection`="{ item }" slot) to use a safe fallback chain
(e.g. item.raw.title || item.title || item.text || item.value) so a meaningful
label is shown whenever item.raw.title is empty or undefined and preserve the
existing v-chip logic for item.raw.namespaced.

}

try {
const namespace = to.params.namespace ?? to.query.namespace
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Normalize namespace to a single string before using it.

to.query.namespace may be string[]; forwarding it directly can cause invalid namespace checks and API calls.

💡 Suggested fix
-        const namespace = to.params.namespace ?? to.query.namespace
+        const rawNamespace = to.params.namespace ?? to.query.namespace
+        const namespace = Array.isArray(rawNamespace) ? rawNamespace[0] : rawNamespace
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/router/guards.js` at line 73, The variable assigned to namespace
(currently using to.params.namespace ?? to.query.namespace) must be normalized
to a single string because to.query.namespace can be a string[]; update the
assignment in guards.js so that if to.query.namespace is an array you extract a
single string (e.g., first element) or join/flatten it, and then fallback to
to.params.namespace; ensure the final namespace is a string (use Array.isArray
on to.query.namespace and coerce/trim as needed) before any validation or API
calls that use namespace.

Comment on lines +69 to +75
async function fetchNamespacedCloudProfilesForNamespaces (namespaces = []) {
const namespacesToFetch = uniq(namespaces)
.filter(Boolean)
.filter(namespace => !hasNamespacedCloudProfilesForNamespace(namespace))

await Promise.all(namespacesToFetch.map(namespace => fetchNamespacedCloudProfiles(namespace)))
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Prune stale namespace cache entries when namespace scope shrinks.

Current logic only fetches missing namespaces; it never removes namespaces that are no longer in scope. That leaves stale namespaced cloud profiles in namespacedList / allCloudProfiles.

💡 Suggested fix
 async function fetchNamespacedCloudProfilesForNamespaces (namespaces = []) {
-  const namespacesToFetch = uniq(namespaces)
+  const allowedNamespaces = uniq(namespaces)
     .filter(Boolean)
-    .filter(namespace => !hasNamespacedCloudProfilesForNamespace(namespace))
+
+  // Drop cached namespaces that are no longer in scope
+  namespacedListsByNamespace.value = Object.fromEntries(
+    Object.entries(namespacedListsByNamespace.value)
+      .filter(([namespace]) => allowedNamespaces.includes(namespace)),
+  )
+  namespacedList.value = flatten(Object.values(namespacedListsByNamespace.value))
+
+  const namespacesToFetch = allowedNamespaces
+    .filter(namespace => !hasNamespacedCloudProfilesForNamespace(namespace))
 
   await Promise.all(namespacesToFetch.map(namespace => fetchNamespacedCloudProfiles(namespace)))
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function fetchNamespacedCloudProfilesForNamespaces (namespaces = []) {
const namespacesToFetch = uniq(namespaces)
.filter(Boolean)
.filter(namespace => !hasNamespacedCloudProfilesForNamespace(namespace))
await Promise.all(namespacesToFetch.map(namespace => fetchNamespacedCloudProfiles(namespace)))
}
async function fetchNamespacedCloudProfilesForNamespaces (namespaces = []) {
const allowedNamespaces = uniq(namespaces)
.filter(Boolean)
// Drop cached namespaces that are no longer in scope
namespacedListsByNamespace.value = Object.fromEntries(
Object.entries(namespacedListsByNamespace.value)
.filter(([namespace]) => allowedNamespaces.includes(namespace)),
)
namespacedList.value = flatten(Object.values(namespacedListsByNamespace.value))
const namespacesToFetch = allowedNamespaces
.filter(namespace => !hasNamespacedCloudProfilesForNamespace(namespace))
await Promise.all(namespacesToFetch.map(namespace => fetchNamespacedCloudProfiles(namespace)))
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/store/cloudProfile/index.js` around lines 69 - 75,
fetchNamespacedCloudProfilesForNamespaces currently only adds missing namespaces
but never removes stale entries, leaving namespacedList/allCloudProfiles
polluted; update this function to prune cache entries for namespaces that are no
longer in scope: compute the canonical set of requested namespaces
(uniq/filter(Boolean)), call the existing fetchNamespacedCloudProfiles for any
missing ones as before, and then remove any keys from namespacedList and any
entries in allCloudProfiles that belong to namespaces not in that canonical set
(use the store's mutation or helper that manages namespacedList and
allCloudProfiles so state stays consistent). Ensure you reference and update
namespacedList and allCloudProfiles within
fetchNamespacedCloudProfilesForNamespaces so removed namespaces are cleared
after the fetch step.

@klocke-io klocke-io changed the title feat(backend): add namespaced-cloud-profile-support feat: Add support for namespacedcloudprofiles Mar 3, 2026
Copy link
Member

Choose a reason for hiding this comment

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

use camel case for file like you did with the other files

Copy link
Member Author

Choose a reason for hiding this comment

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

b6bbcc0

done, thanks for catching it :)

@@ -50,6 +50,13 @@ rules:
verbs:
Copy link
Member

Choose a reason for hiding this comment

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

we also need to add this to gardener operator

Copy link
Member Author

@klocke-io klocke-io Mar 10, 2026

Choose a reason for hiding this comment

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

https://github.com/gardener/gardener/pull/13500/changes
Done and merged, or did you mean something else :)

const parentCloudProfile = getCloudProfile(parentName)

if (!parentCloudProfile) {
return null
Copy link
Member

Choose a reason for hiding this comment

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

Should this case throw an exception?

Comment on lines +51 to +58
const result = _.cloneDeep(profile)
result.status = {
...result.status,
cloudProfileSpecDiff: diff || null,
}
delete result.status.cloudProfileSpec

return result
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
const result = _.cloneDeep(profile)
result.status = {
...result.status,
cloudProfileSpecDiff: diff || null,
}
delete result.status.cloudProfileSpec
return result
const { status = {}, ...profileRest } = profile
const { cloudProfileSpec, ...statusRest } = status
return {
...profileRest,
status: {
...statusRest,
cloudProfileSpecDiff: diff ?? null,
},
}

"test": "NODE_PATH=./dist cross-env NODE_ENV=test node --experimental-vm-modules $(yarn bin jest)",
"test-coverage": "yarn test --coverage"
"test-coverage": "yarn test --coverage",
"test-vitest": "vitest run"
Copy link
Member

Choose a reason for hiding this comment

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

test-vitest is currently not run viamake verify, or?

const parentName = profile.spec?.parent?.name
const parent = find(list.value, ['metadata.name', parentName])
if (!parent) {
return profile
Copy link
Member

Choose a reason for hiding this comment

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

isn't this an exception / should be handled as error case?

) ?? null
}
// Fallback: only return when name is unique across namespaces
const matches = filter(namespacedList.value, ['metadata.name', cloudProfileRef.name])
Copy link
Member

Choose a reason for hiding this comment

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

when namespace is not set on cloudProfileRef, the namespace of the parent object (e.g. the shoot) has to be taken

Comment on lines +44 to +46
const isNamespacedInitial = computed(() => {
return Object.keys(namespacedListsByNamespace.value).length === 0
})
Copy link
Member

Choose a reason for hiding this comment

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

drop unused computed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla: yes Indicates the PR's author has signed the cla-assistant.io CLA. needs/review Needs review needs/second-opinion Needs second review by someone else size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants