From 88ef06b7cf5753e2f618ac47a6989b4240acb11c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 16:16:43 +0000 Subject: [PATCH 1/5] Initial plan From 8a87842e776b88e6ba47c0a25a36c03fe6d6f7f5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 16:26:31 +0000 Subject: [PATCH 2/5] Fix AddCustomBadgeDialog to use MANAGEMENT.CLUSTER instead of NORMAN.CLUSTER Fixes #15491 When MCM (multi-cluster-management) is disabled, the NORMAN.CLUSTER schema is not available. The AddCustomBadgeDialog was trying to fetch and save badge annotations on NORMAN.CLUSTER, which caused "Unknown schema for type: cluster" errors. This change updates the dialog to use MANAGEMENT.CLUSTER (which is what currentCluster already is) instead of NORMAN.CLUSTER for reading and writing badge annotations. This works regardless of MCM status since MANAGEMENT.CLUSTER is always available. Co-authored-by: nwmac <1955897+nwmac@users.noreply.github.com> --- shell/dialog/AddCustomBadgeDialog.vue | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/shell/dialog/AddCustomBadgeDialog.vue b/shell/dialog/AddCustomBadgeDialog.vue index 319c687e973..883108e5d96 100644 --- a/shell/dialog/AddCustomBadgeDialog.vue +++ b/shell/dialog/AddCustomBadgeDialog.vue @@ -11,7 +11,7 @@ import { Checkbox } from '@components/Form/Checkbox'; import { LabeledInput } from '@components/Form/LabeledInput'; import ColorInput from '@shell/components/form/ColorInput'; import { parseColor, textColor } from '@shell/utils/color'; -import { NORMAN } from '@shell/config/types'; +import { MANAGEMENT } from '@shell/config/types'; import { abbreviateClusterName } from '@shell/utils/cluster'; import { _CREATE, _EDIT } from '@shell/config/query-params'; import ClusterIconMenu from '@shell/components/ClusterIconMenu'; @@ -67,10 +67,10 @@ export default { if (this.isCreate || this.clusterExplorer) { return; } - // INFO: Fetch the cluster object if it's an edit - await this.$store.dispatch('rancher/find', { type: NORMAN.CLUSTER, id: this.currentCluster?.id }); + // INFO: currentCluster is already a MANAGEMENT.CLUSTER with badge annotations, no need to fetch NORMAN.CLUSTER + // This also handles the case when MCM is disabled and NORMAN.CLUSTER schema doesn't exist - if (this.currentCluster.metadata?.annotations) { + if (this.currentCluster?.metadata?.annotations) { this.badgeComment = this.currentCluster.metadata?.annotations[CLUSTER_BADGE.TEXT]; this.useCustomComment = this.badgeComment?.length > 0; this.badgeBgColor = this.currentCluster.metadata?.annotations[CLUSTER_BADGE.COLOR] || 'transparent'; @@ -179,17 +179,19 @@ export default { try { // INFO: If we're editing a cluster, update the cluster object if (!this.isCreate && !this.clusterExplorer) { - const norman = await this.$store.dispatch('rancher/find', { type: NORMAN.CLUSTER, id: this.currentCluster?.id }); + // Use MANAGEMENT.CLUSTER (currentCluster) instead of NORMAN.CLUSTER + // This works with MCM disabled where NORMAN.CLUSTER schema doesn't exist + const cluster = await this.$store.dispatch('management/find', { type: MANAGEMENT.CLUSTER, id: this.currentCluster?.id }); - delete norman.annotations[CLUSTER_BADGE.COLOR]; - delete norman.annotations[CLUSTER_BADGE.ICON_TEXT]; - delete norman.annotations[CLUSTER_BADGE.TEXT]; + delete cluster.metadata.annotations[CLUSTER_BADGE.COLOR]; + delete cluster.metadata.annotations[CLUSTER_BADGE.ICON_TEXT]; + delete cluster.metadata.annotations[CLUSTER_BADGE.TEXT]; - norman.annotations[CLUSTER_BADGE.COLOR] = this.badgeColorPicker ? this.badgeBgColor : 'transparent'; - norman.annotations[CLUSTER_BADGE.ICON_TEXT] = this.letter.toUpperCase(); - norman.annotations[CLUSTER_BADGE.TEXT] = this.badgeComment; + cluster.metadata.annotations[CLUSTER_BADGE.COLOR] = this.badgeColorPicker ? this.badgeBgColor : 'transparent'; + cluster.metadata.annotations[CLUSTER_BADGE.ICON_TEXT] = this.letter.toUpperCase(); + cluster.metadata.annotations[CLUSTER_BADGE.TEXT] = this.badgeComment; - await norman.save(); + await cluster.save(); buttonDone(true); this.close(); From 4c4d31a7d68970a775b6cacbf5947ee1a056f4f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 16:31:38 +0000 Subject: [PATCH 3/5] Add defensive checks for NORMAN.CLUSTER in Fleet operations When MCM is disabled, NORMAN.CLUSTER schema is not available. Added checks to gracefully handle this in: - Fleet cluster edit view (fetch and save) - Fleet cluster model (norman getter and saveYaml) - AssignToDialog (fetch and apply) These operations will now skip norman cluster operations when MCM is disabled, preventing "Unknown schema for type: cluster" errors. Co-authored-by: nwmac <1955897+nwmac@users.noreply.github.com> --- shell/dialog/AssignToDialog.vue | 16 +++++++++++++-- shell/edit/fleet.cattle.io.cluster.vue | 26 ++++++++++++++++--------- shell/models/fleet.cattle.io.cluster.js | 16 ++++++++++++--- 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/shell/dialog/AssignToDialog.vue b/shell/dialog/AssignToDialog.vue index aa1e60572d5..80da0a08b6f 100644 --- a/shell/dialog/AssignToDialog.vue +++ b/shell/dialog/AssignToDialog.vue @@ -38,7 +38,12 @@ export default { }, async fetch() { - await this.$store.dispatch('rancher/findAll', { type: NORMAN.CLUSTER }); + // Check if NORMAN.CLUSTER schema exists (it won't when MCM is disabled) + const normanSchema = this.$store.getters['rancher/schemaFor'](NORMAN.CLUSTER, false, false); + + if (normanSchema) { + await this.$store.dispatch('rancher/findAll', { type: NORMAN.CLUSTER }); + } this.allWorkspaces = await this.$store.dispatch('management/findAll', { type: FLEET.WORKSPACE }); this.moveTo = this.workspace; this.loaded = true; @@ -73,7 +78,14 @@ export default { this.errors = []; for ( const fleetCluster of this.toAssign ) { - const c = await this.$store.dispatch(`rancher/clone`, { resource: fleetCluster.norman }); + // Skip if norman cluster is not available (MCM disabled) + const norman = await fleetCluster.norman; + + if (!norman) { + continue; + } + + const c = await this.$store.dispatch(`rancher/clone`, { resource: norman }); if ( !c ) { continue; diff --git a/shell/edit/fleet.cattle.io.cluster.vue b/shell/edit/fleet.cattle.io.cluster.vue index 38e74760d54..4af0163ed82 100644 --- a/shell/edit/fleet.cattle.io.cluster.vue +++ b/shell/edit/fleet.cattle.io.cluster.vue @@ -32,14 +32,19 @@ export default { }, async fetch() { - const norman = await this.$store.dispatch('rancher/find', { type: NORMAN.CLUSTER, id: this.value.metadata.labels[FLEET.CLUSTER_NAME] }); - const nc = await this.$store.dispatch(`rancher/clone`, { resource: norman }); + // Check if NORMAN.CLUSTER schema exists (it won't when MCM is disabled) + const normanSchema = this.$store.getters['rancher/schemaFor'](NORMAN.CLUSTER, false, false); - if ( !nc.metadata ) { - nc.metadata = {}; - } + if (normanSchema) { + const norman = await this.$store.dispatch('rancher/find', { type: NORMAN.CLUSTER, id: this.value.metadata.labels[FLEET.CLUSTER_NAME] }); + const nc = await this.$store.dispatch(`rancher/clone`, { resource: norman }); + + if ( !nc.metadata ) { + nc.metadata = {}; + } - this.normanCluster = nc; + this.normanCluster = nc; + } }, data() { @@ -59,10 +64,13 @@ export default { await this.value.save(); - await this.normanCluster.save(); + // Only save norman cluster if it was loaded (MCM enabled) + if (this.normanCluster) { + await this.normanCluster.save(); - // Changes (such as labels or annotations fields) to normanCluster are reflected in the fleet cluster via Rancher services, so wait for that to occur - await this.waitForFleetClusterLastRevision(); + // Changes (such as labels or annotations fields) to normanCluster are reflected in the fleet cluster via Rancher services, so wait for that to occur + await this.waitForFleetClusterLastRevision(); + } this.done(); buttonDone(true); diff --git a/shell/models/fleet.cattle.io.cluster.js b/shell/models/fleet.cattle.io.cluster.js index 9ef5fe18820..a9f2447cbd4 100644 --- a/shell/models/fleet.cattle.io.cluster.js +++ b/shell/models/fleet.cattle.io.cluster.js @@ -187,6 +187,13 @@ export default class FleetCluster extends SteveModel { return this.basicNorman; } + // Check if NORMAN.CLUSTER schema exists (it won't when MCM is disabled) + const normanSchema = this.$rootGetters['rancher/schemaFor'](NORMAN.CLUSTER, false, false); + + if (!normanSchema) { + return null; + } + // If navigate to YAML view directly, norman is not loaded yet return this.$dispatch('rancher/find', { type: NORMAN.CLUSTER, id: this.metadata.labels[FLEET_LABELS.CLUSTER_NAME] }, { root: true }); } @@ -230,9 +237,12 @@ export default class FleetCluster extends SteveModel { const norman = await this.normanClone(); - norman.setLabels(parsed.metadata.labels); - norman.setAnnotations(parsed.metadata.annotations); + // Only save to norman cluster if it's available (MCM enabled) + if (norman) { + norman.setLabels(parsed.metadata.labels); + norman.setAnnotations(parsed.metadata.annotations); - await norman.save(); + await norman.save(); + } } } From b47d6d7010f6e6dda056531c55c03258706e5818 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 16:32:33 +0000 Subject: [PATCH 4/5] Add defensive check to provisioning cluster findNormanCluster Added schema check to prevent errors when NORMAN.CLUSTER is unavailable with MCM disabled. Co-authored-by: nwmac <1955897+nwmac@users.noreply.github.com> --- shell/models/provisioning.cattle.io.cluster.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/shell/models/provisioning.cattle.io.cluster.js b/shell/models/provisioning.cattle.io.cluster.js index fd663492722..4fc29c2cc90 100644 --- a/shell/models/provisioning.cattle.io.cluster.js +++ b/shell/models/provisioning.cattle.io.cluster.js @@ -236,6 +236,13 @@ export default class ProvCluster extends SteveModel { return null; } + // Check if NORMAN.CLUSTER schema exists (it won't when MCM is disabled) + const normanSchema = this.$rootGetters['rancher/schemaFor'](NORMAN.CLUSTER, false, false); + + if (!normanSchema) { + return null; + } + return await this.$dispatch('rancher/find', { type: NORMAN.CLUSTER, id: name }, { root: true }); } From 6d943256f1c41503e091a5648aed6a5731c95530 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 16:34:13 +0000 Subject: [PATCH 5/5] Address code review comments - add null checks - Add check for cluster.metadata.annotations existence before deleting - Add checks for parsed.metadata.labels and annotations before calling setters Co-authored-by: nwmac <1955897+nwmac@users.noreply.github.com> --- shell/dialog/AddCustomBadgeDialog.vue | 4 ++++ shell/models/fleet.cattle.io.cluster.js | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/shell/dialog/AddCustomBadgeDialog.vue b/shell/dialog/AddCustomBadgeDialog.vue index 883108e5d96..f5e32fd8543 100644 --- a/shell/dialog/AddCustomBadgeDialog.vue +++ b/shell/dialog/AddCustomBadgeDialog.vue @@ -183,6 +183,10 @@ export default { // This works with MCM disabled where NORMAN.CLUSTER schema doesn't exist const cluster = await this.$store.dispatch('management/find', { type: MANAGEMENT.CLUSTER, id: this.currentCluster?.id }); + if (!cluster.metadata.annotations) { + cluster.metadata.annotations = {}; + } + delete cluster.metadata.annotations[CLUSTER_BADGE.COLOR]; delete cluster.metadata.annotations[CLUSTER_BADGE.ICON_TEXT]; delete cluster.metadata.annotations[CLUSTER_BADGE.TEXT]; diff --git a/shell/models/fleet.cattle.io.cluster.js b/shell/models/fleet.cattle.io.cluster.js index a9f2447cbd4..0039185d5b0 100644 --- a/shell/models/fleet.cattle.io.cluster.js +++ b/shell/models/fleet.cattle.io.cluster.js @@ -239,8 +239,12 @@ export default class FleetCluster extends SteveModel { // Only save to norman cluster if it's available (MCM enabled) if (norman) { - norman.setLabels(parsed.metadata.labels); - norman.setAnnotations(parsed.metadata.annotations); + if (parsed.metadata?.labels) { + norman.setLabels(parsed.metadata.labels); + } + if (parsed.metadata?.annotations) { + norman.setAnnotations(parsed.metadata.annotations); + } await norman.save(); }