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 {