Skip to content

[DRAFT] Generic CloudProviders#2824

Draft
grolu wants to merge 9 commits intomasterfrom
ccp/generic_secret_dialog2
Draft

[DRAFT] Generic CloudProviders#2824
grolu wants to merge 9 commits intomasterfrom
ccp/generic_secret_dialog2

Conversation

@grolu
Copy link
Member

@grolu grolu commented Mar 10, 2026

What this PR does / why we need it:

Which issue(s) this PR fixes:
Fixes #

Special notes for your reviewer:

Release note:


Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced generic credential input interface supporting dynamic field configurations for all cloud providers and DNS services.
    • Added support for structured data fields (JSON/YAML) with improved validation and visibility toggles.
  • Bug Fixes

    • Enhanced base64 credential decoding to gracefully handle edge cases and JSON parsing failures.
    • Improved secret data persistence and retrieval with better error handling.
  • Refactor

    • Unified provider-specific credential dialogs into a vendor-agnostic configuration system, simplifying credential management across supported clouds and DNS providers.

grolu added 7 commits March 10, 2026 09:53
Introduces GGenericInputField Component that parses a DSL to render for specific vendor
Use GGenericSecretDialog everywhere except for OpenStack
Refactored OpenStack Dialog to also make use of GGenericInputFields so it becomes way easier
Moved all provider config from store to dedicated config package
Refactored cloudprofiles fixtures for better re-use
…config

Added missing onmetal cloud provider configuration
Changed useShootContext test suite to dynamically execute tests on all configured infras
@gardener-prow gardener-prow bot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. do-not-merge/needs-kind Indicates a PR lacks a `kind/foo` label and requires one. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Mar 10, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 10, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a318b67f-8432-4a47-8904-7e2cad8a26b6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR refactors credential dialogs and provider configuration from a provider-specific component approach to a data-driven, generic model. Provider-specific credential components are removed and replaced with generic input field components. Provider configurations are externalized into vendor data modules organized by type (infra, DNS). Composables and utilities are added to support structured text fields and vendor-based secret resolution.

Changes

Cohort / File(s) Summary
Cloud Profile Fixtures
frontend/__fixtures__/cloudprofiles.js
Refactored to use factory helper functions (createMachineType, createVolumeType, createZone, createRegion, createCloudProfile, createOpenstackMachineTypes) instead of inline literals for generating cloud profile test data.
Test Updates
frontend/__tests__/composables/credential.helper.spec.js, frontend/__tests__/composables/useShootContext.spec.js
New test file for credential helper and updated useShootContext tests to dynamically generate tests per provider type instead of hard-coding individual provider cases.
Deleted Provider-Specific Credential Components
frontend/src/components/Credentials/GSecretDialog{Alicloud,Aws,Azure,Cloudflare,DDns,Gcp,HCloud,Infoblox,Metal,Netlify,Powerdns,VSphere}.vue
Removed 12 provider-specific credential dialog components; replaced by generic input field approach.
Generic Credential Components
frontend/src/components/GGenericInputField.vue, frontend/src/components/GGenericInputFields.vue
Introduced new reusable input field components supporting text, password, select, and structured (YAML/JSON) input types with validation.
Generic Secret Dialog Updates
frontend/src/components/Credentials/GSecretDialog.vue, frontend/src/components/Credentials/GSecretDialogGeneric.vue
Updated to use form submission handling and conditional field rendering via generic input components; added credential prop and vendorSecretConfiguration-driven UI.
Secret Dialog Wrapper Refactoring
frontend/src/components/Credentials/GSecretDialogWrapper.vue, frontend/src/components/Credentials/GSecretDialogOpenstack.vue
Removed provider-specific dialog mappings from wrapper; updated Openstack dialog to use GGenericInputField and new authentication method UI.
Vendor Data Modules (Infrastructure)
frontend/src/data/vendors/infra/{aws,alicloud,azure,gcp,hcloud,metal,onmetal,openstack,vsphere,ironcore,stackit,local}.js, frontend/src/data/vendors/infra/index.js
Introduced 12 new infrastructure vendor configuration modules exporting metadata, shoot templates, networking defaults, and secret schemas; aggregated in index.js.
Vendor Data Modules (DNS)
frontend/src/data/vendors/dns/{aws-route53,alicloud-dns,azure-dns,azure-private-dns,cloudflare,google-clouddns,infoblox,netlify,openstack-designate,powerdns,rfc2136}.js, frontend/src/data/vendors/dns/index.js
Introduced 11 new DNS vendor configuration modules with provider metadata and secret schemas; aggregated in index.js.
Vendor Data Modules (Machine Images)
frontend/src/data/vendors/machineImage/index.js
New module defining machine image vendor list (gardenlinux, ubuntu, coreos, flatcar, suse-jeos, suse-chost, memoryone-chost).
Credential Helper Refactoring
frontend/src/composables/credential/helper.js
Replaced hard-coded provider-specific secret detail definitions with generic vendor-config-based resolver using detailValueResolver and resolveSecretDetailsFromVendorConfig.
Secret Context Updates
frontend/src/composables/credential/useSecretContext.js
Enhanced base64 decoding/encoding to handle JSON parsing, non-string values, and fallback to raw decoded strings.
New Structured Text Field Composable
frontend/src/composables/useStructuredTextField.js
Introduced composable for managing YAML/JSON text fields with conversion utilities and parse error handling.
Utility Updates
frontend/src/utils/index.js, frontend/src/utils/shoot.js
Updated handleTextFieldDrop to use raw text pass-through and adjusted DOM element selection; refactored shoot.js to use vendor-driven template resolution instead of hard-coded switch cases.
Store Configuration
frontend/src/store/config.js
Replaced inline vendor data arrays with imports from newly created vendor data modules.

Sequence Diagram(s)

sequenceDiagram
    participant UI as Credential Dialog UI
    participant GenericInput as GGenericInputField
    participant SecretContext as useSecretContext
    participant VendorConfig as Vendor Data Config
    participant Helper as credential/helper.js
    
    UI->>VendorConfig: Load vendorSecretConfiguration(providerType)
    VendorConfig-->>UI: Return { fields, details, help }
    UI->>GenericInput: Render field inputs with validators
    GenericInput->>SecretContext: Update secretStringData
    SecretContext->>SecretContext: Encode to base64
    SecretContext-->>GenericInput: Sync state
    UI->>Helper: Resolve secret details
    Helper->>VendorConfig: Fetch provider config
    VendorConfig-->>Helper: Return detail definitions
    Helper-->>UI: Display decoded secret values
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • gardener/dashboard#2779: Related to dashboard credential UI and migration flow — touches the same credential components and secret binding handling infrastructure.

Suggested labels

kind/enhancement, cla: yes

Suggested reviewers

  • holgerkoser
  • klocke-io

Poem

🐰 Vendor configs hop into their cozy data homes,
Generic dialogs dance where specific ones once roamed,
Credentials now flow through reusable fields so bright,
No more per-provider chaos—just clean, structured delight! ✨

🚥 Pre-merge checks | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is incomplete and uses only the template placeholders without substantive content. The 'What this PR does / why we need it' and 'Special notes' sections are empty, and only the release note contains a generic single-line entry. Complete the description by filling in: (1) what changes are made and why, (2) which issue(s) are fixed, (3) any special review considerations, and (4) a complete release note explaining the changes to users.
Docstring Coverage ⚠️ Warning Docstring coverage is 10.34% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title '[DRAFT] Generic CloudProviders' is vague and does not clearly describe the main change. While it hints at cloud providers, it lacks specificity about the refactoring and restructuring work involved. Provide a more specific title that describes the key change, such as 'Refactor cloud provider configuration into vendor-driven data structures' or 'Extract provider-specific dialogs into generic configuration-based system'.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ccp/generic_secret_dialog2
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Tip

You can get early access to new features in CodeRabbit.

Enable the early_access setting to enable early access features such as new models, tools, and more.

@grolu
Copy link
Member Author

grolu commented Mar 10, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Mar 10, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@gardener-prow
Copy link

gardener-prow bot commented Mar 10, 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 assign klocke-io for approval. 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

@grolu grolu added the kind/enhancement Enhancement, improvement, extension label Mar 10, 2026
@gardener-prow gardener-prow bot added cla: yes Indicates the PR's author has signed the cla-assistant.io CLA. and removed do-not-merge/needs-kind Indicates a PR lacks a `kind/foo` label and requires one. labels Mar 10, 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: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/components/Credentials/GSecretDialogOpenstack.vue (1)

169-175: ⚠️ Potential issue | 🟠 Major

Restore the auth mode and existing non-secret fields in edit mode.

Lines 173 and 291-295 currently reopen every existing secret as USER and only repopulate domainName/tenantName. Editing an application-credential secret therefore shows the wrong form and drops values like authURL, username, applicationCredentialID, and applicationCredentialName from the edit flow. Derive authenticationMethod from the stored keys before rendering, then repopulate the matching non-secret fields during reset(). Based on learnings: dialog components under frontend/src/components/Credentials/GSecretDialogWrapper.vue are recreated on each open via v-if="visibleDialog", so this default is reapplied every time.

Also applies to: 279-296

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

In `@frontend/src/components/Credentials/GSecretDialogOpenstack.vue` around lines
169 - 175, When opening the dialog for edit, don't default authenticationMethod
to 'USER'—detect and set authenticationMethod from the stored secret keys (e.g.
presence of applicationCredentialID/applicationCredentialName vs username)
before the component renders and before reset() runs, and update reset() to
repopulate the corresponding non-secret fields (authURL, username,
applicationCredentialID, applicationCredentialName, domainName/tenantName) for
the selected authenticationMethod instead of only restoring domain/tenantName;
locate and change the authenticationMethod default in data() and the reset()
implementation so the correct form shows and all non-secret values are restored
in edit mode.
🧹 Nitpick comments (7)
frontend/src/data/vendors/infra/stackit.js (1)

8-9: Consider capitalizing displayName for consistency.

The displayName is 'stackit' (lowercase) while other providers use proper capitalization for UI display (e.g., 'AWS', 'Hetzner Cloud', 'OnMetal'). Consider using 'StackIT' or 'STACKIT' to match the branding.

📝 Suggested change
 export default {
   name: 'stackit',
-  displayName: 'stackit',
+  displayName: 'StackIT',
   weight: 1100,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/data/vendors/infra/stackit.js` around lines 8 - 9, displayName
for the provider object is lowercase ('stackit') and should follow the
capitalization used by other providers; update the provider object's displayName
field (the 'displayName' property alongside 'name' in this module) to a properly
capitalized form such as 'StackIT' so UI displays match branding and other
providers.
frontend/__tests__/composables/credential.helper.spec.js (1)

18-35: Include PowerDNS apiKey in the shared fixture.

The new provider adds an apiKey detail path, but this fixture only sets server, so the snapshot never exercises the masked API key case.

✅ Suggested test update
     Zone: encode('example.org.'),
     server: encode('https://powerdns.example.org'),
+    apiKey: encode('example-powerdns-api-key'),
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/__tests__/composables/credential.helper.spec.js` around lines 18 -
35, The test fixture function createSecretData() is missing the PowerDNS apiKey,
so add an encoded apiKey entry (e.g., apiKey:
encode('example-powerdns-api-key')) to the returned object so the snapshot
covers the masked API key path; update the object alongside the existing server
property to ensure tests exercise the new provider's apiKey behavior.
frontend/src/components/GGenericInputField.vue (2)

194-225: Consider using ref() for the touched flag.

The touched variable is a plain mutable let binding. While this works for synchronous read/write within the component, using ref() would make the reactive intent clearer and align with Vue 3 conventions.

✏️ Suggested change
 // ----- structured fields (yaml/json) -----
-let touched = false
+const touched = ref(false)

 const {
   rawText,
   setRawTextWithObject,
   parseRawTextToObject,
 } = useStructuredTextField(computed(() => props.field.type))

 const structuredRawText = computed({
   get: () => rawText.value,
   set: value => {
-    touched = true
+    touched.value = true
     rawText.value = value
     // ...
   },
 })

 watch(
   () => props.modelValue,
   value => {
-    if (isStructuredLike.value && !touched) {
+    if (isStructuredLike.value && !touched.value) {
       setRawTextWithObject(value)
     }
   },
   { immediate: true },
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/GGenericInputField.vue` around lines 194 - 225,
Replace the plain let touched = false with a Vue ref (e.g., const touched =
ref(false)) and update all usages to use touched.value (set touched.value = true
inside the structuredRawText setter and check if (!touched.value) in the watch
callback) so the touched flag is reactive and follows Vue 3 conventions; update
any other references to touched accordingly (e.g., in structuredRawText setter,
watch, or elsewhere relying on that flag like setRawTextWithObject).

251-251: TODO: Address incomplete YAML error handling.

The TODO comment indicates that invalid YAML may not properly update modelValue on parse errors. This could lead to stale data being submitted.

Would you like me to help implement proper error handling for invalid YAML/JSON that ensures modelValue reflects the parse state?

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

In `@frontend/src/components/GGenericInputField.vue` at line 251, The TODO notes
that in the switch branch for 'isValidObject' the component doesn't update
modelValue when YAML/JSON parsing fails, leaving stale data; update the
'isValidObject' handling in GGenericInputField.vue so that when parse fails you
set a clear parse error state and also emit/update modelValue with a canonical
representation (e.g., null or the raw text) to reflect the invalid parse, and
ensure you call the existing emit/model update method used elsewhere (the same
function that sets modelValue and emits 'update:modelValue') so consumers get
the parse-state change; keep the error message and parse result in the
component's validation/error properties so the UI can show the YAML/JSON error.
frontend/src/data/vendors/infra/vsphere.js (1)

88-91: Consider using English locale for VMware documentation link.

The VMware vSphere documentation link uses the German locale (vmware.com/de/). For an international audience, consider using the English locale or a locale-neutral URL.

✏️ Suggested change
         <a href="https://docs.vmware.com/de/VMware-vSphere/index.html">
+        <a href="https://docs.vmware.com/en/VMware-vSphere/index.html">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/data/vendors/infra/vsphere.js` around lines 88 - 91, Update the
VMware documentation link to use an English or locale-neutral URL by replacing
the current href "https://docs.vmware.com/de/VMware-vSphere/index.html" (the
anchor with text "VMware vSphere Documentation") with the English version (e.g.,
"https://docs.vmware.com/en/VMware-vSphere/index.html") or a locale-neutral root
URL; locate the anchor element in frontend/src/data/vendors/infra/vsphere.js and
change only the href value to the preferred URL.
frontend/src/components/Credentials/GSecretDialogGeneric.vue (1)

29-40: Render helpJSONTemplate independently of helpHtml.

Lines 29-40 nest the code block under v-if="helpHtml", so a provider that only supplies helpJSONTemplate falls back to the generic help text instead of showing its example payload.

♻️ Proposed adjustment
-      <template v-if="helpHtml">
+      <template v-if="helpHtml || helpJSONTemplate">
         <div
+          v-if="helpHtml"
           class="markdown"
           v-html="helpHtml"
         />
         <g-code-block
           v-if="helpJSONTemplate"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/Credentials/GSecretDialogGeneric.vue` around lines 29
- 40, The g-code-block is currently nested inside the template guarded by
v-if="helpHtml", so providers that only provide helpJSONTemplate don't render
the example; change the template structure so g-code-block is rendered based
solely on v-if="helpJSONTemplate" (move it out of the element controlled by
v-if="helpHtml" or create a sibling block), keeping the existing props (class,
max-height, lang, :content) and the conditional v-if="helpJSONTemplate" on the
g-code-block itself.
frontend/src/components/GGenericInputFields.vue (1)

68-72: Keep fieldData synced when the parent replaces modelValue.

Lines 68-72 only hydrate once. After the first non-empty assignment, parent-side resets/replacements are ignored, so the rendered inputs can drift from the source object.

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

In `@frontend/src/components/GGenericInputFields.vue` around lines 68 - 72, The
watcher on props.modelValue only hydrates fieldData once because it only assigns
when fieldData is empty; update the watcher for props.modelValue so it keeps
fieldData in sync on subsequent parent replacements (not just initial
hydration). In practice, change the watch handler for props.modelValue to
overwrite/update fieldData.value whenever the incoming value changes (use a
proper equality/clone strategy if you need to preserve reactivity or avoid
mutating parent objects), referencing the existing watch(() => props.modelValue,
...) and the fieldData ref/isEmpty logic to locate and replace the assignment.
🤖 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/__fixtures__/cloudprofiles.js`:
- Around line 9-25: createMachineType currently keeps the caller's storage
object by reference which causes shared mutation across fixtures (e.g., in
createOpenstackMachineTypes); change createMachineType so that when storage is
provided it is cloned (e.g., shallow-copy the storage into a new object) before
attaching it to the returned object, by replacing the current ...(storage ? {
storage } : {}) with a cloned storage assignment (e.g., ...(storage ? { storage:
{ ...storage } } : {})) or an equivalent safe clone method.
- Around line 63-74: The default kubernetes and machine image defaults are only
shallow-copied, causing nested version/image objects to be shared; in
createCloudProfile update the kubernetes and machineImagesProfile parameter
defaults to deep-clone the fixtures (e.g., deep clone kubernetesVersions and
machineImages) so each profile gets its own nested objects—use
structuredClone(kubernetesVersions) or a fallback deep-clone
(JSON.parse(JSON.stringify(...)) or _.cloneDeep) when assigning the default
values inside createCloudProfile to avoid shared mutable state.

In `@frontend/src/components/GGenericInputField.vue`:
- Around line 251-260: The isValidObject validator incorrectly allows non-empty
raw text to pass when parsing failed because it only checks isEmpty(value) OR
Object.keys(props.modelValue).length > 0; update the validator (symbol:
compiledValidators[validatorName] in the isValidObject case) to require that
props.modelValue is a successfully parsed object before using its keys — e.g.
replace the second condition with a check that props.modelValue is a non-null
object (typeof === 'object') and has keys; this ensures the validator fails if
the raw input is non-empty but parsing did not produce a valid object; keep the
existing YAML/JSON error messages (isYAML, isJSON).

In `@frontend/src/composables/credential/helper.js`:
- Around line 174-177: The function secretDetails dereferences secret.data
before any guard, causing a crash when secret is undefined; update secretDetails
to first check that secret is truthy (or use optional chaining) before accessing
secret.data and computing secretData, e.g. move the const secretData = ...
assignment inside the try after verifying secret or change it to const
secretData = secret?.data || {}; ensure any subsequent logic that uses
providerConfig (from cloudProviderConfigByName.get(providerType)) still runs
only when secret is present so the function returns undefined consistently when
secret is missing.

In `@frontend/src/composables/credential/useSecretContext.js`:
- Around line 112-133: The getter currently JSON.parse() any decoded string
which coerces plain values like "0","false","null" to non-string types and the
setter uses truthy checks that drop falsy values; update the getter in
useSecretContext.js to only attempt JSON.parse when the decoded string (after
trimming) starts with '{' or '[' (i.e., object/array payloads) and keep the
existing fallback to the raw decoded string, and change the null-check guards in
both the getter and setter from a truthy check (!v) to a nullish check (v ==
null) so values like 0, false, and "" are preserved; reference symbols:
secretData.value, decodeBase64, encodeBase64, mapValues.

In `@frontend/src/data/vendors/dns/google-clouddns.js`:
- Around line 27-30: The Cloud DNS anchor tag is malformed (uses `href==`) in
the HTML snippet containing the URL "https://cloud.google.com/dns/docs/zones";
fix it by changing `href==` to a single equals `href=` so the anchor reads `<a
href="https://cloud.google.com/dns/docs/zones">...` ensuring the link works
correctly.

In `@frontend/src/data/vendors/dns/netlify.js`:
- Around line 36-38: Fix the inconsistent example token in the help text by
making the token value consistent between the prose and the example command:
replace either the prose token '1234567890123456' or the echo argument in the
<pre> block currently showing '1234567890123456789' so they match (prefer using
the 16‑char value '1234567890123456' in both places) and ensure the displayed
echo command is $ echo -n '1234567890123456' | base64.
- Around line 13-18: The details entry for the Netlify DNS provider is missing a
key mapping, so the secret won't bind to the UI; update the details object (the
one with label 'API Key') to include a key property (e.g., key: 'apiKey' or key:
['API Key','apiKey']) so the UI can correctly map and display the secret value.

In `@frontend/src/data/vendors/dns/powerdns.js`:
- Around line 18-21: The PowerDNS secret details entry is missing the detail
key, so add the key that maps to secret.data.apiKey to the object with label
'API Key' (the entry that currently has label: 'API Key' and hidden: true) by
adding key: 'apiKey' so secret.details can resolve and display the masked API
key in the PowerDNS details view.

In `@frontend/src/data/vendors/dns/rfc2136.js`:
- Around line 120-125: Fix the spelling mistake in the provider help text by
updating the 'help' property in rfc2136.js: replace "authoritive" with
"authoritative" inside the template string used for the DNS provider help
description so the sentence reads "...create and manage DNS entries for
authoritative DNS server supporting dynamic updates...".
- Around line 30-31: Fix the typo in the UI label string used for the DNS
RFC2136 vendor config: update the 'label' value that currently reads "label:
'<host>:<port> of the authorive DNS server'" to use the correct spelling
"authoritative" so the label becomes "<host>:<port> of the authoritative DNS
server"; locate the 'label' property in the RFC2136 vendor definition
(frontend/src/data/vendors/dns/rfc2136.js) and replace the misspelled word only.

In `@frontend/src/data/vendors/infra/azure.js`:
- Around line 68-77: The label string for the config entry with key 'tenantId'
contains an extraneous trailing double-quote; update the object where key:
'tenantId' to change the label property from 'Tenant Id"' to 'Tenant Id' (remove
the stray quote) so the UI displays the correct label.

In `@frontend/src/data/vendors/infra/gcp.js`:
- Around line 92-95: Replace the incorrect anchor attribute `url="..."` in the
paragraph containing the link text "Service Account Documentation" with a proper
`href="..."` so the link becomes clickable; also locate the similar malformation
`href==` in the other vendor file (the anchor with `href==` in the Google Cloud
DNS snippet) and correct it to a single equals `href="..."`. Ensure both anchors
use standard `href` attributes and valid URL strings.
- Around line 56-60: The projectID validator uses pattern
/^[a-z][a-z0-9-]{4,28}[a-z0-9]+$/, where the trailing + lets IDs exceed the
intended max length; update the regex in the projectID field (key: 'project_id'
in gcp.js) to require exactly one final alphanumeric char so the total length is
capped (e.g. change the trailing [a-z0-9]+ to [a-z0-9]), i.e. use
/^[a-z][a-z0-9-]{4,28}[a-z0-9]$/ so gcp.secret.fields and any consumers like
google-clouddns inherit the tightened validation.

In `@frontend/src/data/vendors/infra/hcloud.js`:
- Around line 44-53: The hcloudToken form field currently uses type: 'text'
which exposes the credential; update the field object for key 'hcloudToken' to
use type: 'password' (leave validators and label unchanged) so the input is
masked in the UI; verify other provider token fields (e.g., secretAccessKey) are
consistent and run a quick UI check to ensure masking works for the
'hcloudToken' input component.

In `@frontend/src/data/vendors/infra/vsphere.js`:
- Around line 68-77: The NSXTPassword field is using type: 'text' which exposes
the secret; update the field definition for key 'NSXTPassword' to use type:
'password' (matching the existing vSpherePassword pattern) so the input is
masked; locate the object with key 'NSXTPassword' and change its type property
from 'text' to 'password'.

In `@frontend/src/utils/shoot.js`:
- Around line 83-85: getKubernetesTemplate currently returns the vendor template
object by reference (via
resolveShootVendor(providerType)?.shoot?.templates?.kubernetes), which allows
callers to mutate global vendor defaults; change getKubernetesTemplate to return
a deep clone of the template (null-safe) instead of the original object (use
structuredClone if available or JSON.parse(JSON.stringify(...)) as a fallback)
and apply the same cloning fix to the analogous returns in getProviderTemplate
and getNetworkingTemplate so no live vendor config objects are leaked.

---

Outside diff comments:
In `@frontend/src/components/Credentials/GSecretDialogOpenstack.vue`:
- Around line 169-175: When opening the dialog for edit, don't default
authenticationMethod to 'USER'—detect and set authenticationMethod from the
stored secret keys (e.g. presence of
applicationCredentialID/applicationCredentialName vs username) before the
component renders and before reset() runs, and update reset() to repopulate the
corresponding non-secret fields (authURL, username, applicationCredentialID,
applicationCredentialName, domainName/tenantName) for the selected
authenticationMethod instead of only restoring domain/tenantName; locate and
change the authenticationMethod default in data() and the reset() implementation
so the correct form shows and all non-secret values are restored in edit mode.

---

Nitpick comments:
In `@frontend/__tests__/composables/credential.helper.spec.js`:
- Around line 18-35: The test fixture function createSecretData() is missing the
PowerDNS apiKey, so add an encoded apiKey entry (e.g., apiKey:
encode('example-powerdns-api-key')) to the returned object so the snapshot
covers the masked API key path; update the object alongside the existing server
property to ensure tests exercise the new provider's apiKey behavior.

In `@frontend/src/components/Credentials/GSecretDialogGeneric.vue`:
- Around line 29-40: The g-code-block is currently nested inside the template
guarded by v-if="helpHtml", so providers that only provide helpJSONTemplate
don't render the example; change the template structure so g-code-block is
rendered based solely on v-if="helpJSONTemplate" (move it out of the element
controlled by v-if="helpHtml" or create a sibling block), keeping the existing
props (class, max-height, lang, :content) and the conditional
v-if="helpJSONTemplate" on the g-code-block itself.

In `@frontend/src/components/GGenericInputField.vue`:
- Around line 194-225: Replace the plain let touched = false with a Vue ref
(e.g., const touched = ref(false)) and update all usages to use touched.value
(set touched.value = true inside the structuredRawText setter and check if
(!touched.value) in the watch callback) so the touched flag is reactive and
follows Vue 3 conventions; update any other references to touched accordingly
(e.g., in structuredRawText setter, watch, or elsewhere relying on that flag
like setRawTextWithObject).
- Line 251: The TODO notes that in the switch branch for 'isValidObject' the
component doesn't update modelValue when YAML/JSON parsing fails, leaving stale
data; update the 'isValidObject' handling in GGenericInputField.vue so that when
parse fails you set a clear parse error state and also emit/update modelValue
with a canonical representation (e.g., null or the raw text) to reflect the
invalid parse, and ensure you call the existing emit/model update method used
elsewhere (the same function that sets modelValue and emits 'update:modelValue')
so consumers get the parse-state change; keep the error message and parse result
in the component's validation/error properties so the UI can show the YAML/JSON
error.

In `@frontend/src/components/GGenericInputFields.vue`:
- Around line 68-72: The watcher on props.modelValue only hydrates fieldData
once because it only assigns when fieldData is empty; update the watcher for
props.modelValue so it keeps fieldData in sync on subsequent parent replacements
(not just initial hydration). In practice, change the watch handler for
props.modelValue to overwrite/update fieldData.value whenever the incoming value
changes (use a proper equality/clone strategy if you need to preserve reactivity
or avoid mutating parent objects), referencing the existing watch(() =>
props.modelValue, ...) and the fieldData ref/isEmpty logic to locate and replace
the assignment.

In `@frontend/src/data/vendors/infra/stackit.js`:
- Around line 8-9: displayName for the provider object is lowercase ('stackit')
and should follow the capitalization used by other providers; update the
provider object's displayName field (the 'displayName' property alongside 'name'
in this module) to a properly capitalized form such as 'StackIT' so UI displays
match branding and other providers.

In `@frontend/src/data/vendors/infra/vsphere.js`:
- Around line 88-91: Update the VMware documentation link to use an English or
locale-neutral URL by replacing the current href
"https://docs.vmware.com/de/VMware-vSphere/index.html" (the anchor with text
"VMware vSphere Documentation") with the English version (e.g.,
"https://docs.vmware.com/en/VMware-vSphere/index.html") or a locale-neutral root
URL; locate the anchor element in frontend/src/data/vendors/infra/vsphere.js and
change only the href value to the preferred URL.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 90a757c2-ff9c-4588-bcba-01b252af5847

📥 Commits

Reviewing files that changed from the base of the PR and between 53ba9ba and 8401f6d.

⛔ Files ignored due to path filters (3)
  • frontend/__tests__/composables/__snapshots__/credential.helper.spec.js.snap is excluded by !**/*.snap
  • frontend/__tests__/composables/__snapshots__/useShootContext.spec.js.snap is excluded by !**/*.snap
  • frontend/__tests__/stores/__snapshots__/credential.spec.js.snap is excluded by !**/*.snap
📒 Files selected for processing (53)
  • frontend/__fixtures__/cloudprofiles.js
  • frontend/__tests__/composables/credential.helper.spec.js
  • frontend/__tests__/composables/useShootContext.spec.js
  • frontend/src/components/Credentials/GSecretDialog.vue
  • frontend/src/components/Credentials/GSecretDialogAlicloud.vue
  • frontend/src/components/Credentials/GSecretDialogAws.vue
  • frontend/src/components/Credentials/GSecretDialogAzure.vue
  • frontend/src/components/Credentials/GSecretDialogCloudflare.vue
  • frontend/src/components/Credentials/GSecretDialogDDns.vue
  • frontend/src/components/Credentials/GSecretDialogGcp.vue
  • frontend/src/components/Credentials/GSecretDialogGeneric.vue
  • frontend/src/components/Credentials/GSecretDialogHCloud.vue
  • frontend/src/components/Credentials/GSecretDialogInfoblox.vue
  • frontend/src/components/Credentials/GSecretDialogMetal.vue
  • frontend/src/components/Credentials/GSecretDialogNetlify.vue
  • frontend/src/components/Credentials/GSecretDialogOpenstack.vue
  • frontend/src/components/Credentials/GSecretDialogPowerdns.vue
  • frontend/src/components/Credentials/GSecretDialogVSphere.vue
  • frontend/src/components/Credentials/GSecretDialogWrapper.vue
  • frontend/src/components/GGenericInputField.vue
  • frontend/src/components/GGenericInputFields.vue
  • frontend/src/composables/credential/helper.js
  • frontend/src/composables/credential/useSecretContext.js
  • frontend/src/composables/useStructuredTextField.js
  • frontend/src/data/vendors/dns/alicloud-dns.js
  • frontend/src/data/vendors/dns/aws-route53.js
  • frontend/src/data/vendors/dns/azure-dns.js
  • frontend/src/data/vendors/dns/azure-private-dns.js
  • frontend/src/data/vendors/dns/cloudflare.js
  • frontend/src/data/vendors/dns/google-clouddns.js
  • frontend/src/data/vendors/dns/index.js
  • frontend/src/data/vendors/dns/infoblox.js
  • frontend/src/data/vendors/dns/netlify.js
  • frontend/src/data/vendors/dns/openstack-designate.js
  • frontend/src/data/vendors/dns/powerdns.js
  • frontend/src/data/vendors/dns/rfc2136.js
  • frontend/src/data/vendors/infra/alicloud.js
  • frontend/src/data/vendors/infra/aws.js
  • frontend/src/data/vendors/infra/azure.js
  • frontend/src/data/vendors/infra/gcp.js
  • frontend/src/data/vendors/infra/hcloud.js
  • frontend/src/data/vendors/infra/index.js
  • frontend/src/data/vendors/infra/ironcore.js
  • frontend/src/data/vendors/infra/local.js
  • frontend/src/data/vendors/infra/metal.js
  • frontend/src/data/vendors/infra/onmetal.js
  • frontend/src/data/vendors/infra/openstack.js
  • frontend/src/data/vendors/infra/stackit.js
  • frontend/src/data/vendors/infra/vsphere.js
  • frontend/src/data/vendors/machineImage/index.js
  • frontend/src/store/config.js
  • frontend/src/utils/index.js
  • frontend/src/utils/shoot.js
💤 Files with no reviewable changes (12)
  • frontend/src/components/Credentials/GSecretDialogDDns.vue
  • frontend/src/components/Credentials/GSecretDialogCloudflare.vue
  • frontend/src/components/Credentials/GSecretDialogInfoblox.vue
  • frontend/src/components/Credentials/GSecretDialogMetal.vue
  • frontend/src/components/Credentials/GSecretDialogPowerdns.vue
  • frontend/src/components/Credentials/GSecretDialogVSphere.vue
  • frontend/src/components/Credentials/GSecretDialogNetlify.vue
  • frontend/src/components/Credentials/GSecretDialogGcp.vue
  • frontend/src/components/Credentials/GSecretDialogHCloud.vue
  • frontend/src/components/Credentials/GSecretDialogAlicloud.vue
  • frontend/src/components/Credentials/GSecretDialogAzure.vue
  • frontend/src/components/Credentials/GSecretDialogAws.vue

Comment on lines +63 to +74
const createCloudProfile = ({
metadataName,
displayName,
type,
seedNames,
providerConfig,
machineTypes,
regions,
volumeTypes,
kubernetes = { versions: [...kubernetesVersions] },
machineImagesProfile = [...machineImages],
}) => ({
Copy link

@coderabbitai coderabbitai bot Mar 10, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd cloudprofiles.js --type f

Repository: gardener/dashboard

Length of output: 174


🏁 Script executed:

cat -n frontend/__fixtures__/cloudprofiles.js | head -100

Repository: gardener/dashboard

Length of output: 2659


🏁 Script executed:

# Get context around lines 63-74 and understand the fixture structure
cat -n frontend/__fixtures__/cloudprofiles.js | sed -n '1,80p'

Repository: gardener/dashboard

Length of output: 2048


🏁 Script executed:

# Check if kubernetesVersions and machineImages are defined and what they contain
cat -n frontend/__fixtures__/cloudprofiles.js | grep -A 50 "kubernetesVersions\|machineImages" | head -150

Repository: gardener/dashboard

Length of output: 4510


🏁 Script executed:

# Search for usages of createCloudProfile to understand mutation patterns
rg "createCloudProfile" --type js -B 2 -A 5

Repository: gardener/dashboard

Length of output: 6575


🏁 Script executed:

# Check if there are mutations on kubernetes or machineImages in tests
rg "kubernetes.*=|machineImages.*=" --type js -B 2 -A 2

Repository: gardener/dashboard

Length of output: 20082


Deep-clone the default Kubernetes and machine image fixtures.

Lines 72-73 only clone the outer arrays. The nested version/image objects are still shared by every profile that uses the defaults, so a mutation in one provider fixture can leak into the others and into the exported base fixtures.

Proposed fix
+const cloneKubernetes = ({ versions }) => ({
+  versions: versions.map(version => ({ ...version })),
+})
+
+const cloneMachineImages = images => images.map(image => ({
+  ...image,
+  versions: image.versions.map(version => ({
+    ...version,
+    ...(version.cri
+      ? {
+          cri: version.cri.map(cri => ({
+            ...cri,
+            ...(cri.containerRuntimes
+              ? {
+                  containerRuntimes: cri.containerRuntimes.map(runtime => ({ ...runtime })),
+                }
+              : {}),
+          })),
+        }
+      : {}),
+    ...(version.architectures ? { architectures: [...version.architectures] } : {}),
+  })),
+}))
+
 const createCloudProfile = ({
   metadataName,
   displayName,
   type,
   seedNames,
   providerConfig,
   machineTypes,
   regions,
   volumeTypes,
-  kubernetes = { versions: [...kubernetesVersions] },
-  machineImagesProfile = [...machineImages],
+  kubernetes = cloneKubernetes({ versions: kubernetesVersions }),
+  machineImagesProfile = cloneMachineImages(machineImages),
 }) => ({
📝 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
const createCloudProfile = ({
metadataName,
displayName,
type,
seedNames,
providerConfig,
machineTypes,
regions,
volumeTypes,
kubernetes = { versions: [...kubernetesVersions] },
machineImagesProfile = [...machineImages],
}) => ({
const cloneKubernetes = ({ versions }) => ({
versions: versions.map(version => ({ ...version })),
})
const cloneMachineImages = images => images.map(image => ({
...image,
versions: image.versions.map(version => ({
...version,
...(version.cri
? {
cri: version.cri.map(cri => ({
...cri,
...(cri.containerRuntimes
? {
containerRuntimes: cri.containerRuntimes.map(runtime => ({ ...runtime })),
}
: {}),
})),
}
: {}),
...(version.architectures ? { architectures: [...version.architectures] } : {}),
})),
}))
const createCloudProfile = ({
metadataName,
displayName,
type,
seedNames,
providerConfig,
machineTypes,
regions,
volumeTypes,
kubernetes = cloneKubernetes({ versions: kubernetesVersions }),
machineImagesProfile = cloneMachineImages(machineImages),
}) => ({
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/__fixtures__/cloudprofiles.js` around lines 63 - 74, The default
kubernetes and machine image defaults are only shallow-copied, causing nested
version/image objects to be shared; in createCloudProfile update the kubernetes
and machineImagesProfile parameter defaults to deep-clone the fixtures (e.g.,
deep clone kubernetesVersions and machineImages) so each profile gets its own
nested objects—use structuredClone(kubernetesVersions) or a fallback deep-clone
(JSON.parse(JSON.stringify(...)) or _.cloneDeep) when assigning the default
values inside createCloudProfile to avoid shared mutable state.

Copy link
Member Author

Choose a reason for hiding this comment

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

right now this is not an issue in our test suite. I think it can stay like this for now as we dont mutate these things

Copy link

Choose a reason for hiding this comment

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

@grolu, understood — if the fixtures are treated as read-only throughout the test suite, the shallow spread is perfectly fine. I'll note this for future reviews.


✏️ Learnings added
Learnt from: grolu
Repo: gardener/dashboard PR: 2824
File: frontend/__fixtures__/cloudprofiles.js:63-74
Timestamp: 2026-03-11T17:51:28.917Z
Learning: In `frontend/__fixtures__/cloudprofiles.js` (gardener/dashboard), the `createCloudProfile` factory uses shallow spreads for `kubernetes` and `machineImagesProfile` defaults (`[...kubernetesVersions]` / `[...machineImages]`). This is intentional — the fixture objects are treated as read-only in the test suite, so deep-cloning is not required and should not be flagged.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines +56 to +60
projectID: {
type: 'hasObjectProp',
key: 'project_id',
pattern: /^[a-z][a-z0-9-]{4,28}[a-z0-9]+$/,
},
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

Tighten the project_id regex.

Line 59 ends with [a-z0-9]+, so the {4,28} middle segment no longer caps the overall length. Overlong project IDs will pass here, and google-clouddns inherits the same validator via gcp.secret.fields.

🐛 Proposed fix
-            pattern: /^[a-z][a-z0-9-]{4,28}[a-z0-9]+$/,
+            pattern: /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/data/vendors/infra/gcp.js` around lines 56 - 60, The projectID
validator uses pattern /^[a-z][a-z0-9-]{4,28}[a-z0-9]+$/, where the trailing +
lets IDs exceed the intended max length; update the regex in the projectID field
(key: 'project_id' in gcp.js) to require exactly one final alphanumeric char so
the total length is capped (e.g. change the trailing [a-z0-9]+ to [a-z0-9]),
i.e. use /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/ so gcp.secret.fields and any consumers
like google-clouddns inherit the tightened validation.

Comment on lines 83 to 85
export function getKubernetesTemplate (providerType) {
switch (providerType) {
case 'metal':
return {
kubeControllerManager: {
nodeCIDRMaskSize: 23,
},
kubelet: {
maxPods: 510,
},
}
}
return resolveShootVendor(providerType)?.shoot?.templates?.kubernetes
}
Copy link

@coderabbitai coderabbitai bot Mar 10, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Return clones here, not live vendor config objects.

Unlike getProviderTemplate and getNetworkingTemplate, Lines 84 and 228 hand back the registry object by reference. Any caller that mutates the returned template/provider config will mutate the global vendor defaults and leak state into later shoot creations.

🐛 Proposed fix
+import cloneDeep from 'lodash/cloneDeep'
+
 export function getKubernetesTemplate (providerType) {
-  return resolveShootVendor(providerType)?.shoot?.templates?.kubernetes
+  return cloneDeep(resolveShootVendor(providerType)?.shoot?.templates?.kubernetes)
 }
@@
 export function getWorkerProviderConfig (providerType) {
-  return resolveShootVendor(providerType)?.shoot?.workerProviderConfig
+  return cloneDeep(resolveShootVendor(providerType)?.shoot?.workerProviderConfig)
 }

Also applies to: 227-229

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

In `@frontend/src/utils/shoot.js` around lines 83 - 85, getKubernetesTemplate
currently returns the vendor template object by reference (via
resolveShootVendor(providerType)?.shoot?.templates?.kubernetes), which allows
callers to mutate global vendor defaults; change getKubernetesTemplate to return
a deep clone of the template (null-safe) instead of the original object (use
structuredClone if available or JSON.parse(JSON.stringify(...)) as a fallback)
and apply the same cloning fix to the analogous returns in getProviderTemplate
and getNetworkingTemplate so no live vendor config objects are leaked.

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah I know but this is current limitation AFAIK that we have to set here for this provider 🤷

Copy link

Choose a reason for hiding this comment

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

@grolu, understood — thanks for the context! Would you like me to open a follow-up GitHub issue to track this so it doesn't get lost? That way the mutation risk in getKubernetesTemplate and getWorkerProviderConfig can be addressed in a future PR when the limitation is lifted.


🧠 Learnings used
Learnt from: grolu
Repo: gardener/dashboard PR: 2824
File: frontend/__fixtures__/cloudprofiles.js:63-74
Timestamp: 2026-03-11T17:51:28.917Z
Learning: In `frontend/__fixtures__/cloudprofiles.js` (gardener/dashboard), the `createCloudProfile` factory uses shallow spreads for `kubernetes` and `machineImagesProfile` defaults (`[...kubernetesVersions]` / `[...machineImages]`). This is intentional — the fixture objects are treated as read-only in the test suite, so deep-cloning is not required and should not be flagged.

Fixed OpenStack Secret Dialog Defaulting
Default autocomplete to 'off' for password fields
FIx object references in cloudprofiles fixtures
Some Cleanup (mainly comments)
Fixed some text=>password field type errors
@gardener-prow gardener-prow bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Mar 12, 2026
@gardener-prow
Copy link

gardener-prow bot commented Mar 12, 2026

PR needs rebase.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

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. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. kind/enhancement Enhancement, improvement, extension needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. 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.

1 participant