From 29b652c6f4064dc00bab9d3e76a36755df3b9b22 Mon Sep 17 00:00:00 2001 From: Mo Mesgin Date: Wed, 22 Apr 2026 23:21:38 -0700 Subject: [PATCH 1/9] only apply os/arch restriction to rancher repos --- shell/mixins/chart.js | 8 +- .../pages/c/_cluster/apps/charts/install.vue | 12 ++- shell/store/__tests__/catalog.test.ts | 93 ++++++++++++++++++- shell/store/catalog.js | 20 +++- 4 files changed, 126 insertions(+), 7 deletions(-) diff --git a/shell/mixins/chart.js b/shell/mixins/chart.js index 737bfc2eade..a93a1e9f91f 100644 --- a/shell/mixins/chart.js +++ b/shell/mixins/chart.js @@ -13,7 +13,7 @@ import { CAPI, CATALOG } from '@shell/config/types'; import { isPrerelease } from '@shell/utils/version'; import { compareChartVersions } from '@shell/utils/chart'; import difference from 'lodash/difference'; -import { LINUX, APP_UPGRADE_STATUS } from '@shell/store/catalog'; +import { LINUX, APP_UPGRADE_STATUS, isRancherRepo } from '@shell/store/catalog'; import { clone } from '@shell/utils/object'; import { merge } from 'lodash'; @@ -73,7 +73,11 @@ export default { keywords: version.keywords }; - const permittedSystems = (version?.annotations?.[CATALOG_ANNOTATIONS.PERMITTED_OS] || LINUX).split(','); + const permittedOs = version?.annotations?.[CATALOG_ANNOTATIONS.PERMITTED_OS]; + + const isRancher = isRancherRepo(this.repo, this.chart); + const fallbackOs = isRancher ? LINUX : ''; + const permittedSystems = (permittedOs || fallbackOs).split(',').filter(Boolean); if (permittedSystems.length > 0 && difference(OSs, permittedSystems).length > 0) { nue.disabled = true; diff --git a/shell/pages/c/_cluster/apps/charts/install.vue b/shell/pages/c/_cluster/apps/charts/install.vue index c666b6949e7..00ab4d964fc 100644 --- a/shell/pages/c/_cluster/apps/charts/install.vue +++ b/shell/pages/c/_cluster/apps/charts/install.vue @@ -41,7 +41,7 @@ import { import { ignoreVariables } from './install.helpers'; import { findBy, insertAt } from '@shell/utils/array'; import { saferDump } from '@shell/utils/create-yaml'; -import { LINUX, WINDOWS } from '@shell/store/catalog'; +import { WINDOWS, isRancherRepo } from '@shell/store/catalog'; import { SETTING } from '@shell/config/settings'; import SelectOrCreateAuthSecret from '@shell/components/form/SelectOrCreateAuthSecret.vue'; import { generateRandomAlphaString } from '@shell/utils/string'; @@ -767,7 +767,15 @@ export default { return this.t('catalog.charts.windowsIncompatible'); } if (this.versionInfo) { - const incompatibleVersion = !(this.versionInfo?.chart?.annotations?.[CATALOG_ANNOTATIONS.PERMITTED_OS] || LINUX).includes('windows'); + const isRancher = isRancherRepo(this.repo, this.chart); + const permittedOs = this.versionInfo?.chart?.annotations?.[CATALOG_ANNOTATIONS.PERMITTED_OS]; + let incompatibleVersion = false; + + if (permittedOs) { + incompatibleVersion = !permittedOs.includes('windows'); + } else if (isRancher) { + incompatibleVersion = true; + } if (incompatibleVersion && !this.chart.windowsIncompatible) { return this.t('catalog.charts.versionWindowsIncompatible'); diff --git a/shell/store/__tests__/catalog.test.ts b/shell/store/__tests__/catalog.test.ts index 2106e33b636..03fe30b8c1d 100644 --- a/shell/store/__tests__/catalog.test.ts +++ b/shell/store/__tests__/catalog.test.ts @@ -1,6 +1,6 @@ import { CATALOG } from '@shell/config/types'; import { - state, getters, actions, mutations, filterAndArrangeCharts + state, getters, actions, mutations, filterAndArrangeCharts, isRancherRepo, compatibleVersionsFor } from '../catalog'; import { createStore } from 'vuex'; @@ -455,4 +455,95 @@ describe('catalog', () => { }); }); }); + + describe('isRancherRepo', () => { + it('should return true if chart.isRancherRepo is true', () => { + const chart = { isRancherRepo: true } as any; + + expect(isRancherRepo(null, chart)).toBe(true); + }); + + it('should return true if repo.isRancherSource is true', () => { + const repo = { isRancherSource: true } as any; + + expect(isRancherRepo(repo, null)).toBe(true); + }); + + it('should return true if repo is airgapped rancher-charts', () => { + const repo = { type: CATALOG.CLUSTER_REPO, name: 'rancher-charts' } as any; + + expect(isRancherRepo(repo, null)).toBe(true); + }); + + it('should return true if repo is airgapped rancher-partner-charts', () => { + const repo = { type: CATALOG.CLUSTER_REPO, name: 'rancher-partner-charts' } as any; + + expect(isRancherRepo(repo, null)).toBe(true); + }); + + it('should return false if repo is a namespace-scoped rancher-charts', () => { + const repo = { type: CATALOG.REPO, name: 'rancher-charts' } as any; + + expect(isRancherRepo(repo, null)).toBe(false); + }); + + it('should return false for unrelated repo', () => { + const repo = { type: CATALOG.CLUSTER_REPO, name: 'other-repo' } as any; + + expect(isRancherRepo(repo, null)).toBe(false); + }); + }); + + describe('compatibleVersionsFor', () => { + it('should allow versions if no OS constraint is provided', () => { + const chart = { versions: [{ version: '1.0.0' }] } as any; + const versions = compatibleVersionsFor(chart, undefined); + + expect(versions).toHaveLength(1); + }); + + it('should allow windows nodes if permitted-os includes windows', () => { + const chart = { + versions: [{ + version: '1.0.0', + annotations: { 'catalog.cattle.io/permits-os': 'linux,windows' } + }] + } as any; + const versions = compatibleVersionsFor(chart, 'windows'); + + expect(versions).toHaveLength(1); + }); + + it('should block windows nodes if permitted-os does not include windows', () => { + const chart = { + versions: [{ + version: '1.0.0', + annotations: { 'catalog.cattle.io/permits-os': 'linux' } + }] + } as any; + const versions = compatibleVersionsFor(chart, 'windows'); + + expect(versions).toHaveLength(0); + }); + + it('should fallback to LINUX for rancher repos and block windows nodes', () => { + const chart = { + isRancherRepo: true, + versions: [{ version: '1.0.0' }] + } as any; + const versions = compatibleVersionsFor(chart, 'windows'); + + expect(versions).toHaveLength(0); + }); + + it('should not fallback to LINUX for non-rancher repos and allow windows nodes', () => { + const chart = { + isRancherRepo: false, + versions: [{ version: '1.0.0' }] + } as any; + const versions = compatibleVersionsFor(chart, 'windows'); + + expect(versions).toHaveLength(1); + }); + }); }); diff --git a/shell/store/catalog.js b/shell/store/catalog.js index 58e93cabd5f..11b69b4556a 100644 --- a/shell/store/catalog.js +++ b/shell/store/catalog.js @@ -690,13 +690,15 @@ export function compatibleVersionsFor(chart, os, includePrerelease = true) { } return versions.filter((ver) => { - const osPermitted = (ver?.annotations?.[CATALOG_ANNOTATIONS.PERMITTED_OS] || LINUX).split(','); + const permittedOs = ver?.annotations?.[CATALOG_ANNOTATIONS.PERMITTED_OS]; + const fallbackOs = chart.isRancherRepo ? LINUX : ''; + const osPermitted = (permittedOs || fallbackOs).split(',').filter(Boolean); if ( !includePrerelease && isPrerelease(ver.version) ) { return false; } - if ( !os || difference(os, osPermitted).length === 0) { + if ( !os || osPermitted.length === 0 || difference(os, osPermitted).length === 0) { return true; } @@ -785,3 +787,17 @@ export function filterAndArrangeCharts(charts, { return sortBy(out, ['certifiedSort', 'repoName', 'chartNameDisplay']); } + +/* + Detects if a repository is a Rancher repository. + Airgapped environments often use mirrored registries, meaning `isRancherSource` + will return false because the URL doesn't point to a *.rancher.io domain. We check the repo + names directly to ensure we still correctly identify these mirrored rancher repos. +*/ +export function isRancherRepo(repo, chart) { + if (chart?.isRancherRepo || repo?.isRancherSource) { + return true; + } + + return repo?.type === CATALOG.CLUSTER_REPO && (repo?.name === 'rancher-charts' || repo?.name === 'rancher-partner-charts'); +} From acafe3c52e326b908b367eefda9bdcf1fd65f2fb Mon Sep 17 00:00:00 2001 From: momesgin Date: Thu, 23 Apr 2026 10:22:48 -0700 Subject: [PATCH 2/9] Update shell/store/catalog.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- shell/store/catalog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/store/catalog.js b/shell/store/catalog.js index 11b69b4556a..d3c399dd99f 100644 --- a/shell/store/catalog.js +++ b/shell/store/catalog.js @@ -691,7 +691,7 @@ export function compatibleVersionsFor(chart, os, includePrerelease = true) { return versions.filter((ver) => { const permittedOs = ver?.annotations?.[CATALOG_ANNOTATIONS.PERMITTED_OS]; - const fallbackOs = chart.isRancherRepo ? LINUX : ''; + const fallbackOs = chart?.isRancherRepo === false ? '' : LINUX; const osPermitted = (permittedOs || fallbackOs).split(',').filter(Boolean); if ( !includePrerelease && isPrerelease(ver.version) ) { From 6afe44c3145dae3ff0ac4e98815abe049b614433 Mon Sep 17 00:00:00 2001 From: momesgin Date: Thu, 23 Apr 2026 10:22:58 -0700 Subject: [PATCH 3/9] Update shell/store/__tests__/catalog.test.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- shell/store/__tests__/catalog.test.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/shell/store/__tests__/catalog.test.ts b/shell/store/__tests__/catalog.test.ts index 03fe30b8c1d..9c8b7fc604a 100644 --- a/shell/store/__tests__/catalog.test.ts +++ b/shell/store/__tests__/catalog.test.ts @@ -527,22 +527,34 @@ describe('catalog', () => { }); it('should fallback to LINUX for rancher repos and block windows nodes', () => { + const rancherRepo = { + metadata: { name: 'rancher-charts' } + } as any; const chart = { - isRancherRepo: true, - versions: [{ version: '1.0.0' }] + versions: [{ version: '1.0.0' }] } as any; + + chart.isRancherRepo = (isRancherRepo as any)(rancherRepo, chart); + const versions = compatibleVersionsFor(chart, 'windows'); + expect(chart.isRancherRepo).toBe(true); expect(versions).toHaveLength(0); }); it('should not fallback to LINUX for non-rancher repos and allow windows nodes', () => { + const nonRancherRepo = { + metadata: { name: 'partner-charts' } + } as any; const chart = { - isRancherRepo: false, - versions: [{ version: '1.0.0' }] + versions: [{ version: '1.0.0' }] } as any; + + chart.isRancherRepo = (isRancherRepo as any)(nonRancherRepo, chart); + const versions = compatibleVersionsFor(chart, 'windows'); + expect(chart.isRancherRepo).toBe(false); expect(versions).toHaveLength(1); }); }); From 297eac51249c21f3eea953a1120bfec765f1936a Mon Sep 17 00:00:00 2001 From: Mo Mesgin Date: Thu, 23 Apr 2026 11:56:13 -0700 Subject: [PATCH 4/9] fix tests --- shell/store/__tests__/catalog.test.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/shell/store/__tests__/catalog.test.ts b/shell/store/__tests__/catalog.test.ts index 9c8b7fc604a..6df702414b1 100644 --- a/shell/store/__tests__/catalog.test.ts +++ b/shell/store/__tests__/catalog.test.ts @@ -527,12 +527,8 @@ describe('catalog', () => { }); it('should fallback to LINUX for rancher repos and block windows nodes', () => { - const rancherRepo = { - metadata: { name: 'rancher-charts' } - } as any; - const chart = { - versions: [{ version: '1.0.0' }] - } as any; + const rancherRepo = { type: CATALOG.CLUSTER_REPO, name: 'rancher-charts' } as any; + const chart = { versions: [{ version: '1.0.0' }] } as any; chart.isRancherRepo = (isRancherRepo as any)(rancherRepo, chart); @@ -543,12 +539,8 @@ describe('catalog', () => { }); it('should not fallback to LINUX for non-rancher repos and allow windows nodes', () => { - const nonRancherRepo = { - metadata: { name: 'partner-charts' } - } as any; - const chart = { - versions: [{ version: '1.0.0' }] - } as any; + const nonRancherRepo = { type: CATALOG.REPO, name: 'partner-charts' } as any; + const chart = { versions: [{ version: '1.0.0' }] } as any; chart.isRancherRepo = (isRancherRepo as any)(nonRancherRepo, chart); From 06f5d6721c25f0e6424ee66d52a5a77dd7640722 Mon Sep 17 00:00:00 2001 From: Mo Mesgin Date: Thu, 23 Apr 2026 13:16:26 -0700 Subject: [PATCH 5/9] fix early return + update addChart --- shell/pages/c/_cluster/apps/charts/install.vue | 11 ++++++----- shell/store/catalog.js | 12 +++++++++++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/shell/pages/c/_cluster/apps/charts/install.vue b/shell/pages/c/_cluster/apps/charts/install.vue index 00ab4d964fc..16ed2baee05 100644 --- a/shell/pages/c/_cluster/apps/charts/install.vue +++ b/shell/pages/c/_cluster/apps/charts/install.vue @@ -763,9 +763,6 @@ export default { }, windowsIncompatible() { - if (this.chart?.windowsIncompatible) { - return this.t('catalog.charts.windowsIncompatible'); - } if (this.versionInfo) { const isRancher = isRancherRepo(this.repo, this.chart); const permittedOs = this.versionInfo?.chart?.annotations?.[CATALOG_ANNOTATIONS.PERMITTED_OS]; @@ -777,8 +774,12 @@ export default { incompatibleVersion = true; } - if (incompatibleVersion && !this.chart.windowsIncompatible) { - return this.t('catalog.charts.versionWindowsIncompatible'); + if (incompatibleVersion) { + if (!this.chart?.windowsIncompatible) { + return this.t('catalog.charts.versionWindowsIncompatible'); + } + + return this.t('catalog.charts.windowsIncompatible'); } } diff --git a/shell/store/catalog.js b/shell/store/catalog.js index d3c399dd99f..f4b193245a6 100644 --- a/shell/store/catalog.js +++ b/shell/store/catalog.js @@ -557,7 +557,16 @@ function addChart(ctx, map, chart, repo) { const primeOnly = chart.annotations?.[CATALOG_ANNOTATIONS.PRIME_ONLY] === 'true'; const experimental = !!chart.annotations?.[CATALOG_ANNOTATIONS.EXPERIMENTAL]; - const windowsIncompatible = !(chart.annotations?.[CATALOG_ANNOTATIONS.PERMITTED_OS] || '').includes('windows'); + + const isRancherRepoFlag = isRancherRepo(repo, chart); + const permittedOs = chart.annotations?.[CATALOG_ANNOTATIONS.PERMITTED_OS]; + let windowsIncompatible = false; + + if (permittedOs) { + windowsIncompatible = !permittedOs.includes('windows'); + } else if (isRancherRepoFlag) { + windowsIncompatible = true; + } const deploysOnWindows = (chart.annotations?.[CATALOG_ANNOTATIONS.DEPLOYED_OS] || '').includes('windows'); const tags = []; @@ -606,6 +615,7 @@ function addChart(ctx, map, chart, repo) { provides: [], windowsIncompatible, deploysOnWindows, + isRancherRepo: isRancherRepoFlag, tags }); From 82488c75c33a8d5df617d188d08d9b063c029449 Mon Sep 17 00:00:00 2001 From: Mo Mesgin Date: Thu, 23 Apr 2026 14:00:57 -0700 Subject: [PATCH 6/9] remove unused code --- shell/components/SelectIconGrid.vue | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/shell/components/SelectIconGrid.vue b/shell/components/SelectIconGrid.vue index 5ffcd6c8e3b..1ffec120848 100644 --- a/shell/components/SelectIconGrid.vue +++ b/shell/components/SelectIconGrid.vue @@ -131,16 +131,6 @@ export default { class="side-label" :class="{'indicator': true }" /> -
- -
-
- -
Date: Thu, 30 Apr 2026 11:07:02 -0700 Subject: [PATCH 7/9] add getPermittedOSs helper --- shell/mixins/chart.js | 7 +-- .../pages/c/_cluster/apps/charts/install.vue | 12 ++--- shell/store/__tests__/catalog.test.ts | 50 ++++++++++++++++++- shell/store/catalog.js | 27 ++++++---- 4 files changed, 70 insertions(+), 26 deletions(-) diff --git a/shell/mixins/chart.js b/shell/mixins/chart.js index a93a1e9f91f..aa1284c0c74 100644 --- a/shell/mixins/chart.js +++ b/shell/mixins/chart.js @@ -13,7 +13,7 @@ import { CAPI, CATALOG } from '@shell/config/types'; import { isPrerelease } from '@shell/utils/version'; import { compareChartVersions } from '@shell/utils/chart'; import difference from 'lodash/difference'; -import { LINUX, APP_UPGRADE_STATUS, isRancherRepo } from '@shell/store/catalog'; +import { APP_UPGRADE_STATUS, isRancherRepo, getPermittedOSs } from '@shell/store/catalog'; import { clone } from '@shell/utils/object'; import { merge } from 'lodash'; @@ -73,11 +73,8 @@ export default { keywords: version.keywords }; - const permittedOs = version?.annotations?.[CATALOG_ANNOTATIONS.PERMITTED_OS]; - const isRancher = isRancherRepo(this.repo, this.chart); - const fallbackOs = isRancher ? LINUX : ''; - const permittedSystems = (permittedOs || fallbackOs).split(',').filter(Boolean); + const permittedSystems = getPermittedOSs(version?.annotations, isRancher); if (permittedSystems.length > 0 && difference(OSs, permittedSystems).length > 0) { nue.disabled = true; diff --git a/shell/pages/c/_cluster/apps/charts/install.vue b/shell/pages/c/_cluster/apps/charts/install.vue index 16ed2baee05..edf283a8100 100644 --- a/shell/pages/c/_cluster/apps/charts/install.vue +++ b/shell/pages/c/_cluster/apps/charts/install.vue @@ -41,7 +41,7 @@ import { import { ignoreVariables } from './install.helpers'; import { findBy, insertAt } from '@shell/utils/array'; import { saferDump } from '@shell/utils/create-yaml'; -import { WINDOWS, isRancherRepo } from '@shell/store/catalog'; +import { WINDOWS, isRancherRepo, getPermittedOSs } from '@shell/store/catalog'; import { SETTING } from '@shell/config/settings'; import SelectOrCreateAuthSecret from '@shell/components/form/SelectOrCreateAuthSecret.vue'; import { generateRandomAlphaString } from '@shell/utils/string'; @@ -765,14 +765,8 @@ export default { windowsIncompatible() { if (this.versionInfo) { const isRancher = isRancherRepo(this.repo, this.chart); - const permittedOs = this.versionInfo?.chart?.annotations?.[CATALOG_ANNOTATIONS.PERMITTED_OS]; - let incompatibleVersion = false; - - if (permittedOs) { - incompatibleVersion = !permittedOs.includes('windows'); - } else if (isRancher) { - incompatibleVersion = true; - } + const permittedSystems = getPermittedOSs(this.versionInfo?.chart?.annotations, isRancher); + const incompatibleVersion = permittedSystems.length > 0 && !permittedSystems.includes('windows'); if (incompatibleVersion) { if (!this.chart?.windowsIncompatible) { diff --git a/shell/store/__tests__/catalog.test.ts b/shell/store/__tests__/catalog.test.ts index 6df702414b1..54a279080a3 100644 --- a/shell/store/__tests__/catalog.test.ts +++ b/shell/store/__tests__/catalog.test.ts @@ -1,6 +1,7 @@ import { CATALOG } from '@shell/config/types'; +import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations'; import { - state, getters, actions, mutations, filterAndArrangeCharts, isRancherRepo, compatibleVersionsFor + state, getters, actions, mutations, filterAndArrangeCharts, isRancherRepo, compatibleVersionsFor, getPermittedOSs } from '../catalog'; import { createStore } from 'vuex'; @@ -550,4 +551,51 @@ describe('catalog', () => { expect(versions).toHaveLength(1); }); }); + + describe('getPermittedOSs', () => { + it('should return explicitly permitted OSs when the annotation is present on a Rancher repo', () => { + const annotations = { [CATALOG_ANNOTATIONS.PERMITTED_OS]: 'linux,windows' }; + const result = getPermittedOSs(annotations, true); + + expect(result).toHaveLength(2); + expect(result).toContain('linux'); + expect(result).toContain('windows'); + }); + + it('should return explicitly permitted OSs when the annotation is present on a non-Rancher repo', () => { + const annotations = { [CATALOG_ANNOTATIONS.PERMITTED_OS]: 'linux' }; + const result = getPermittedOSs(annotations, false); + + expect(result).toHaveLength(1); + expect(result).toContain('linux'); + }); + + it('should fallback to linux if the annotation is missing on a Rancher repo', () => { + const annotations = {}; + const result = getPermittedOSs(annotations, true); + + expect(result).toHaveLength(1); + expect(result).toContain('linux'); + }); + + it('should return an empty array (no restrictions) if the annotation is missing on a non-Rancher repo', () => { + const annotations = {}; + const result = getPermittedOSs(annotations, false); + + expect(result).toHaveLength(0); + }); + + it('should handle undefined annotations safely for Rancher repos', () => { + const result = getPermittedOSs(undefined, true); + + expect(result).toHaveLength(1); + expect(result).toContain('linux'); + }); + + it('should handle undefined annotations safely for non-Rancher repos', () => { + const result = getPermittedOSs(undefined, false); + + expect(result).toHaveLength(0); + }); + }); }); diff --git a/shell/store/catalog.js b/shell/store/catalog.js index f4b193245a6..7da518e44d0 100644 --- a/shell/store/catalog.js +++ b/shell/store/catalog.js @@ -559,14 +559,8 @@ function addChart(ctx, map, chart, repo) { const experimental = !!chart.annotations?.[CATALOG_ANNOTATIONS.EXPERIMENTAL]; const isRancherRepoFlag = isRancherRepo(repo, chart); - const permittedOs = chart.annotations?.[CATALOG_ANNOTATIONS.PERMITTED_OS]; - let windowsIncompatible = false; - - if (permittedOs) { - windowsIncompatible = !permittedOs.includes('windows'); - } else if (isRancherRepoFlag) { - windowsIncompatible = true; - } + const permittedSystems = getPermittedOSs(chart.annotations, isRancherRepoFlag); + const windowsIncompatible = permittedSystems.length > 0 && !permittedSystems.includes('windows'); const deploysOnWindows = (chart.annotations?.[CATALOG_ANNOTATIONS.DEPLOYED_OS] || '').includes('windows'); const tags = []; @@ -700,9 +694,7 @@ export function compatibleVersionsFor(chart, os, includePrerelease = true) { } return versions.filter((ver) => { - const permittedOs = ver?.annotations?.[CATALOG_ANNOTATIONS.PERMITTED_OS]; - const fallbackOs = chart?.isRancherRepo === false ? '' : LINUX; - const osPermitted = (permittedOs || fallbackOs).split(',').filter(Boolean); + const osPermitted = getPermittedOSs(ver?.annotations, chart?.isRancherRepo); if ( !includePrerelease && isPrerelease(ver.version) ) { return false; @@ -811,3 +803,16 @@ export function isRancherRepo(repo, chart) { return repo?.type === CATALOG.CLUSTER_REPO && (repo?.name === 'rancher-charts' || repo?.name === 'rancher-partner-charts'); } + +/** + * Returns an array of permitted operating systems for a given chart or version. + * If the chart explicitly defines permitted OSs via annotation, those are returned. + * Otherwise, if the chart is from a Rancher repository, it defaults to Linux. + * External charts with no annotations have no OS restrictions (returns empty array). + */ +export function getPermittedOSs(annotations, isRancher) { + const permittedOs = annotations?.[CATALOG_ANNOTATIONS.PERMITTED_OS]; + const fallbackOs = isRancher ? LINUX : ''; + + return (permittedOs || fallbackOs).split(',').filter(Boolean); +} From 43540740b4de989dd1af7d4f24bdf59473264b56 Mon Sep 17 00:00:00 2001 From: Mo Mesgin Date: Thu, 30 Apr 2026 11:20:46 -0700 Subject: [PATCH 8/9] lint --- shell/store/__tests__/catalog.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shell/store/__tests__/catalog.test.ts b/shell/store/__tests__/catalog.test.ts index 54a279080a3..8d0e00cd039 100644 --- a/shell/store/__tests__/catalog.test.ts +++ b/shell/store/__tests__/catalog.test.ts @@ -556,7 +556,7 @@ describe('catalog', () => { it('should return explicitly permitted OSs when the annotation is present on a Rancher repo', () => { const annotations = { [CATALOG_ANNOTATIONS.PERMITTED_OS]: 'linux,windows' }; const result = getPermittedOSs(annotations, true); - + expect(result).toHaveLength(2); expect(result).toContain('linux'); expect(result).toContain('windows'); @@ -565,7 +565,7 @@ describe('catalog', () => { it('should return explicitly permitted OSs when the annotation is present on a non-Rancher repo', () => { const annotations = { [CATALOG_ANNOTATIONS.PERMITTED_OS]: 'linux' }; const result = getPermittedOSs(annotations, false); - + expect(result).toHaveLength(1); expect(result).toContain('linux'); }); @@ -573,7 +573,7 @@ describe('catalog', () => { it('should fallback to linux if the annotation is missing on a Rancher repo', () => { const annotations = {}; const result = getPermittedOSs(annotations, true); - + expect(result).toHaveLength(1); expect(result).toContain('linux'); }); @@ -581,20 +581,20 @@ describe('catalog', () => { it('should return an empty array (no restrictions) if the annotation is missing on a non-Rancher repo', () => { const annotations = {}; const result = getPermittedOSs(annotations, false); - + expect(result).toHaveLength(0); }); it('should handle undefined annotations safely for Rancher repos', () => { const result = getPermittedOSs(undefined, true); - + expect(result).toHaveLength(1); expect(result).toContain('linux'); }); it('should handle undefined annotations safely for non-Rancher repos', () => { const result = getPermittedOSs(undefined, false); - + expect(result).toHaveLength(0); }); }); From 4653691a5303a5c24632ffc7b7bbcd4eedf3cf2d Mon Sep 17 00:00:00 2001 From: Mo Mesgin Date: Wed, 20 May 2026 10:48:16 -0700 Subject: [PATCH 9/9] remove logic to detect rancher repos for airgapped env --- shell/store/__tests__/catalog.test.ts | 36 +++------------------------ shell/store/catalog.js | 15 +++-------- 2 files changed, 8 insertions(+), 43 deletions(-) diff --git a/shell/store/__tests__/catalog.test.ts b/shell/store/__tests__/catalog.test.ts index fe4874024dc..38edebdd7a3 100644 --- a/shell/store/__tests__/catalog.test.ts +++ b/shell/store/__tests__/catalog.test.ts @@ -523,26 +523,8 @@ describe('catalog', () => { expect(isRancherRepo(repo, null)).toBe(true); }); - it('should return true if repo is airgapped rancher-charts', () => { - const repo = { type: CATALOG.CLUSTER_REPO, name: 'rancher-charts' } as any; - - expect(isRancherRepo(repo, null)).toBe(true); - }); - - it('should return true if repo is airgapped rancher-partner-charts', () => { - const repo = { type: CATALOG.CLUSTER_REPO, name: 'rancher-partner-charts' } as any; - - expect(isRancherRepo(repo, null)).toBe(true); - }); - - it('should return false if repo is a namespace-scoped rancher-charts', () => { - const repo = { type: CATALOG.REPO, name: 'rancher-charts' } as any; - - expect(isRancherRepo(repo, null)).toBe(false); - }); - - it('should return false for unrelated repo', () => { - const repo = { type: CATALOG.CLUSTER_REPO, name: 'other-repo' } as any; + it('should return false if repo is not a rancher source', () => { + const repo = { isRancherSource: false } as any; expect(isRancherRepo(repo, null)).toBe(false); }); @@ -581,26 +563,16 @@ describe('catalog', () => { }); it('should fallback to LINUX for rancher repos and block windows nodes', () => { - const rancherRepo = { type: CATALOG.CLUSTER_REPO, name: 'rancher-charts' } as any; - const chart = { versions: [{ version: '1.0.0' }] } as any; - - chart.isRancherRepo = (isRancherRepo as any)(rancherRepo, chart); - + const chart = { isRancherRepo: true, versions: [{ version: '1.0.0' }] } as any; const versions = compatibleVersionsFor(chart, 'windows'); - expect(chart.isRancherRepo).toBe(true); expect(versions).toHaveLength(0); }); it('should not fallback to LINUX for non-rancher repos and allow windows nodes', () => { - const nonRancherRepo = { type: CATALOG.REPO, name: 'partner-charts' } as any; - const chart = { versions: [{ version: '1.0.0' }] } as any; - - chart.isRancherRepo = (isRancherRepo as any)(nonRancherRepo, chart); - + const chart = { isRancherRepo: false, versions: [{ version: '1.0.0' }] } as any; const versions = compatibleVersionsFor(chart, 'windows'); - expect(chart.isRancherRepo).toBe(false); expect(versions).toHaveLength(1); }); }); diff --git a/shell/store/catalog.js b/shell/store/catalog.js index b4a2c5bcf07..a93904a592e 100644 --- a/shell/store/catalog.js +++ b/shell/store/catalog.js @@ -792,18 +792,11 @@ export function filterAndArrangeCharts(charts, { return sortBy(out, ['certifiedSort', 'repoName', 'chartNameDisplay']); } -/* - Detects if a repository is a Rancher repository. - Airgapped environments often use mirrored registries, meaning `isRancherSource` - will return false because the URL doesn't point to a *.rancher.io domain. We check the repo - names directly to ensure we still correctly identify these mirrored rancher repos. -*/ +/** + * Detects if a repository is a Rancher repository. + */ export function isRancherRepo(repo, chart) { - if (chart?.isRancherRepo || repo?.isRancherSource) { - return true; - } - - return repo?.type === CATALOG.CLUSTER_REPO && (repo?.name === 'rancher-charts' || repo?.name === 'rancher-partner-charts'); + return !!(chart?.isRancherRepo || repo?.isRancherSource); } /**