diff --git a/cypress/e2e/po/pages/explorer/charts/install-charts.po.ts b/cypress/e2e/po/pages/explorer/charts/install-charts.po.ts index 1f002b81f89..680e9345783 100644 --- a/cypress/e2e/po/pages/explorer/charts/install-charts.po.ts +++ b/cypress/e2e/po/pages/explorer/charts/install-charts.po.ts @@ -1,6 +1,9 @@ import PagePo from '@/cypress/e2e/po/pages/page.po'; import AsyncButtonPo from '@/cypress/e2e/po/components/async-button.po'; import TabbedPo from '~/cypress/e2e/po/components/tabbed.po'; +import CheckboxInputPo from '~/cypress/e2e/po/components/checkbox-input.po'; +import LabeledInputPo from '~/cypress/e2e/po/components/labeled-input.po'; +import LabeledSelectPo from '~/cypress/e2e/po/components/labeled-select.po'; export class InstallChartPage extends PagePo { private static createPath(clusterId: string) { @@ -54,4 +57,16 @@ export class InstallChartPage extends PagePo { chartName() { return this.self().get('[data-testid="NameNsDescriptionNameInput"]'); } + + chartVersionSelector(): LabeledSelectPo { + return new LabeledSelectPo('[data-testid="chart-version-selector"]'); + } + + customRegistryCheckbox(): CheckboxInputPo { + return new CheckboxInputPo('[data-testid="custom-registry-checkbox"]'); + } + + customRegistryInput(): LabeledInputPo { + return new LabeledInputPo('[data-testid="custom-registry-input"]'); + } } diff --git a/cypress/e2e/tests/pages/charts/chart-install-wizard.spec.ts b/cypress/e2e/tests/pages/charts/chart-install-wizard.spec.ts index 77fe774a045..86596d4e219 100644 --- a/cypress/e2e/tests/pages/charts/chart-install-wizard.spec.ts +++ b/cypress/e2e/tests/pages/charts/chart-install-wizard.spec.ts @@ -4,6 +4,8 @@ import { InstallChartPage } from '@/cypress/e2e/po/pages/explorer/charts/install import { MEDIUM_TIMEOUT_OPT } from '@/cypress/support/utils/timeouts'; import TabbedPo from '@/cypress/e2e/po/components/tabbed.po'; import LabeledSelectPo from '@/cypress/e2e/po/components/labeled-select.po'; +import ChartInstalledAppsListPagePo from '@/cypress/e2e/po/pages/chart-installed-apps.po'; +import { NamespaceFilterPo } from '@/cypress/e2e/po/components/namespace-filter.po'; const configMapPayload = { apiVersion: 'v1', @@ -67,4 +69,61 @@ describe('Charts Wizard', { testIsolation: 'off', tags: ['@charts', '@adminUser' cy.updateNamespaceFilter('local', 'none', '{"local":["all://user"]}'); }); }); + + describe('Custom registry', () => { + const namespacePicker = new NamespaceFilterPo(); + const installChartPage = new InstallChartPage(); + const chartPage = new ChartPage(); + const chartName = 'Rancher Backups'; + const customRegistry = 'my.custom.registry:5000'; + + it('should persist custom registry when changing chart version', () => { + const installedAppsPage = new ChartInstalledAppsListPagePo('local', 'apps'); + + // We need to install the chart first to have the versions selector show up later when we come back to the install page + ChartPage.navTo(null, chartName); + chartPage.waitForChartHeader(chartName, MEDIUM_TIMEOUT_OPT); + chartPage.goToInstall(); + installChartPage.nextPage(); + + cy.intercept('POST', '/v1/catalog.cattle.io.clusterrepos/rancher-charts?action=install').as('installApp'); + installChartPage.installChart(); + namespacePicker.toggle(); + namespacePicker.clickOptionByLabel('All Namespaces'); + namespacePicker.isChecked('All Namespaces'); + namespacePicker.closeDropdown(); + installedAppsPage.waitForInstallCloseTerminal('installApp', ['rancher-backup', 'rancher-backup-crd']); + + ChartPage.navTo(null, chartName); + chartPage.waitForChartHeader(chartName, MEDIUM_TIMEOUT_OPT); + chartPage.goToInstall(); + + // The version selector should now be visible + installChartPage.chartVersionSelector().self().should('be.visible'); + + installChartPage.customRegistryCheckbox().set(); + + // Enter custom registry + installChartPage.customRegistryInput().self().should('be.visible'); + installChartPage.customRegistryInput().set(customRegistry); + + // Change chart version + installChartPage.chartVersionSelector().toggle(); + installChartPage.chartVersionSelector().clickOption(2); + + // Verify custom registry is still there + installChartPage.customRegistryCheckbox().isChecked(); + installChartPage.customRegistryInput().self().should('have.value', customRegistry); + }); + + after('clean up', () => { + const chartNamespace = 'cattle-resources-system'; + const chartApp = 'rancher-backup'; + const chartCrd = 'rancher-backup-crd'; + + cy.createRancherResource('v1', `catalog.cattle.io.apps/${ chartNamespace }/${ chartApp }?action=uninstall`, '{}'); + cy.createRancherResource('v1', `catalog.cattle.io.apps/${ chartNamespace }/${ chartCrd }?action=uninstall`, '{}'); + cy.updateNamespaceFilter('local', 'none', '{"local":["all://user"]}'); + }); + }); }); diff --git a/shell/pages/c/_cluster/apps/charts/install.vue b/shell/pages/c/_cluster/apps/charts/install.vue index 9aeefe9c87e..de8248c7750 100644 --- a/shell/pages/c/_cluster/apps/charts/install.vue +++ b/shell/pages/c/_cluster/apps/charts/install.vue @@ -310,6 +310,7 @@ export default { two different Helm chart versions is a "user value," or a user-selected customization. */ + this.preserveCustomRegistryValue(); userValues = diff(this.loadedVersionValues, this.chartValues); } else if ( this.existing ) { await this.existing.fetchValues(); // In theory this has already been called, but do again to be safe @@ -824,6 +825,35 @@ export default { }, methods: { + /** + * The custom registry UI fields (checkbox and input) are not directly bound to chartValues. + * Before calculating the diff to carry over user customizations, we must + * first synchronize the state of these UI fields with chartValues. This + * ensures any user changes to the custom registry settings are + * included in the diff and preserved when changing versions. + */ + preserveCustomRegistryValue() { + if (!this.showCustomRegistry) { + return; + } + + if (this.showCustomRegistryInput) { + set(this.chartValues, 'global.systemDefaultRegistry', this.customRegistrySetting); + set(this.chartValues, 'global.cattle.systemDefaultRegistry', this.customRegistrySetting); + } else { + // Note: Using `delete` here is safe because this is not a reactive property update + // that the UI needs to track. This is a one-time mutation before a diff. + if (get(this.chartValues, 'global.systemDefaultRegistry')) { + delete this.chartValues.global.systemDefaultRegistry; + } + if (get(this.chartValues, 'global.cattle.systemDefaultRegistry')) { + // It's possible `this.chartValues.global.cattle` doesn't exist, + // but `get` ensures we only proceed if the full path exists. + delete this.chartValues.global.cattle.systemDefaultRegistry; + } + } + }, + async getClusterRegistry() { const hasPermissionToSeeProvCluster = this.$store.getters[`management/schemaFor`](CAPI.RANCHER_CLUSTER); @@ -1367,6 +1397,7 @@ export default { @@ -1443,6 +1475,7 @@ export default {