diff --git a/shell/components/SelectIconGrid.vue b/shell/components/SelectIconGrid.vue
index 5ffcd6c8e3..1ffec12084 100644
--- a/shell/components/SelectIconGrid.vue
+++ b/shell/components/SelectIconGrid.vue
@@ -131,16 +131,6 @@ export default {
class="side-label"
:class="{'indicator': true }"
/>
-
-
-
-
-
-
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 f75e1c1a2c..7d7b92993a 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, 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';
@@ -766,14 +766,17 @@ export default {
},
windowsIncompatible() {
- if (this.chart?.windowsIncompatible) {
- 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 permittedSystems = getPermittedOSs(this.versionInfo?.chart?.annotations, isRancher);
+ const incompatibleVersion = permittedSystems.length > 0 && !permittedSystems.includes('windows');
+
+ if (incompatibleVersion) {
+ if (!this.chart?.windowsIncompatible) {
+ return this.t('catalog.charts.versionWindowsIncompatible');
+ }
- if (incompatibleVersion && !this.chart.windowsIncompatible) {
- return this.t('catalog.charts.versionWindowsIncompatible');
+ return this.t('catalog.charts.windowsIncompatible');
}
}
diff --git a/shell/store/__tests__/catalog.test.ts b/shell/store/__tests__/catalog.test.ts
index 6070fe17d9..38edebdd7a 100644
--- a/shell/store/__tests__/catalog.test.ts
+++ b/shell/store/__tests__/catalog.test.ts
@@ -1,7 +1,7 @@
import { CATALOG } from '@shell/config/types';
import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations';
import {
- state, getters, actions, mutations, filterAndArrangeCharts
+ state, getters, actions, mutations, filterAndArrangeCharts, isRancherRepo, compatibleVersionsFor, getPermittedOSs
} from '../catalog';
import { createStore } from 'vuex';
@@ -509,4 +509,118 @@ 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 false if repo is not a rancher source', () => {
+ const repo = { isRancherSource: false } 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);
+ });
+ });
+
+ 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 0147cd64fa..a93904a592 100644
--- a/shell/store/catalog.js
+++ b/shell/store/catalog.js
@@ -559,7 +559,10 @@ 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 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 = [];
@@ -608,6 +611,7 @@ function addChart(ctx, map, chart, repo) {
provides: [],
windowsIncompatible,
deploysOnWindows,
+ isRancherRepo: isRancherRepoFlag,
tags
});
@@ -692,13 +696,13 @@ export function compatibleVersionsFor(chart, os, includePrerelease = true) {
}
return versions.filter((ver) => {
- const osPermitted = (ver?.annotations?.[CATALOG_ANNOTATIONS.PERMITTED_OS] || LINUX).split(',');
+ const osPermitted = getPermittedOSs(ver?.annotations, chart?.isRancherRepo);
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;
}
@@ -787,3 +791,23 @@ export function filterAndArrangeCharts(charts, {
return sortBy(out, ['certifiedSort', 'repoName', 'chartNameDisplay']);
}
+
+/**
+ * Detects if a repository is a Rancher repository.
+ */
+export function isRancherRepo(repo, chart) {
+ return !!(chart?.isRancherRepo || repo?.isRancherSource);
+}
+
+/**
+ * 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);
+}