diff --git a/shell/assets/translations/en-us.yaml b/shell/assets/translations/en-us.yaml
index 31a5e2b1651..69cdb1530e0 100644
--- a/shell/assets/translations/en-us.yaml
+++ b/shell/assets/translations/en-us.yaml
@@ -4285,9 +4285,10 @@ plugins:
title: Error loading extension
message: Could not load extension code
generic: Extension error
- api: This Extension is not compatible with the current Extensions API version
- host: This Extension is not compatible with this application
- version: This Extension is not compatible with this version of Rancher
+ api: Loading error! The version installed is not compatible with the current Extensions API version
+ host: Loading error! The version installed is not compatible with this application host
+ version: Loading error! The version installed is not compatible with the current version of Rancher
+ kubeVersion: Loading error! The version installed is not compatible with the current Rancher kubernetes version
load: An error occurred loading the code for this Extension
developerPkg: This Extension has been loaded internally, so we won't load the external version
success:
diff --git a/shell/config/uiplugins.js b/shell/config/uiplugins.js
index 10c724faa1c..f671688a1e1 100644
--- a/shell/config/uiplugins.js
+++ b/shell/config/uiplugins.js
@@ -147,7 +147,7 @@ function parseRancherVersion(v) {
* Whether an extension should be loaded based on the metadata returned by the backend in the UIPlugins resource instance
* @returns String || Boolean
*/
-export function shouldNotLoadPlugin(UIPluginResource, rancherVersion, loadedPlugins) {
+export function shouldNotLoadPlugin(UIPluginResource, { rancherVersion, kubeVersion }, loadedPlugins) {
if (!UIPluginResource.name || !UIPluginResource.version || !UIPluginResource.endpoint) {
return 'plugins.error.generic';
}
@@ -155,10 +155,11 @@ export function shouldNotLoadPlugin(UIPluginResource, rancherVersion, loadedPlug
// Extension chart specified a required extension API version
// we are propagating the annotations in pkg/package.json for any extension
// inside the "spec.plugin.metadata" property of UIPlugin resource
- const requiredUiExtensionsVersion = UIPluginResource.spec?.plugin?.metadata?.[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION];
+ const requiredUiExtensionsVersion = UIPluginResource.metadata?.[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION] || '>= 3.0.0';
// semver.coerce will get rid of any suffix on the version numbering (-rc, -head, etc)
const parsedUiExtensionsApiVersion = semver.coerce(UI_EXTENSIONS_API_VERSION)?.version || UI_EXTENSIONS_API_VERSION;
const parsedRancherVersion = rancherVersion ? parseRancherVersion(rancherVersion) : '';
+ const parsedKubeVersion = kubeVersion ? semver.coerce(kubeVersion)?.version : '';
if (requiredUiExtensionsVersion && !semver.satisfies(parsedUiExtensionsApiVersion, requiredUiExtensionsVersion)) {
return 'plugins.error.api';
@@ -171,6 +172,15 @@ export function shouldNotLoadPlugin(UIPluginResource, rancherVersion, loadedPlug
return 'plugins.error.host';
}
+ // Kube version
+ if (parsedKubeVersion) {
+ const requiredKubeVersion = UIPluginResource.metadata?.[UI_PLUGIN_CHART_ANNOTATIONS.KUBE_VERSION];
+
+ if (requiredKubeVersion && !semver.satisfies(parsedRancherVersion, requiredKubeVersion)) {
+ return 'plugins.error.kubeVersion';
+ }
+ }
+
// Rancher version
if (parsedRancherVersion) {
const requiredRancherVersion = UIPluginResource.metadata?.[UI_PLUGIN_METADATA.RANCHER_VERSION];
@@ -262,7 +272,7 @@ export function isSupportedChartVersion(versionData, returnObj = false) {
}
// check "catalog.cattle.io/ui-extensions-version" annotation
- const requiredUiExtensionsApiVersion = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION];
+ const requiredUiExtensionsApiVersion = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION] || '>= 3.0.0';
const parsedUiExtensionsApiVersion = semver.coerce(UI_EXTENSIONS_API_VERSION)?.version || UI_EXTENSIONS_API_VERSION;
if (requiredUiExtensionsApiVersion && parsedUiExtensionsApiVersion && !semver.satisfies(parsedUiExtensionsApiVersion, requiredUiExtensionsApiVersion)) {
diff --git a/shell/config/version.js b/shell/config/version.js
index 15c286cdc53..abbf8856b06 100644
--- a/shell/config/version.js
+++ b/shell/config/version.js
@@ -2,6 +2,7 @@
* Store version data retrieved from the backend /rancherversion API
*/
let _versionData = { RancherPrime: 'false' };
+let _kubeVersionData = {};
export function isRancherPrime() {
return _versionData.RancherPrime?.toLowerCase() === 'true';
@@ -15,3 +16,12 @@ export function setVersionData(v) {
// Remove any properties on 'v' we don't want
_versionData = JSON.parse(JSON.stringify(v));
}
+
+export function getKubeVersionData() {
+ return _kubeVersionData;
+}
+
+export function setKubeVersionData(v) {
+ // Remove any properties on 'v' we don't want
+ _kubeVersionData = JSON.parse(JSON.stringify(v));
+}
diff --git a/shell/initialize/install-plugins.js b/shell/initialize/install-plugins.js
index 69a11ad4f41..45dfb9508c4 100644
--- a/shell/initialize/install-plugins.js
+++ b/shell/initialize/install-plugins.js
@@ -25,7 +25,6 @@ import plugins from '@shell/core/plugins.js';
import pluginsLoader from '@shell/core/plugins-loader.js';
import replaceAll from '@shell/plugins/replaceall';
import steveCreateWorker from '@shell/plugins/steve-create-worker';
-import version from '@shell/plugins/version';
import emberCookie from '@shell/plugins/ember-cookie';
import ShortKey from '@shell/plugins/shortkey';
@@ -43,7 +42,7 @@ export async function installPlugins(vueApp) {
}
export async function installInjectedPlugins(app, vueApp) {
- const pluginDefinitions = [config, cookieUniversal, axios, plugins, pluginsLoader, axiosShell, intNumber, codeMirror, nuxtClientInit, replaceAll, backButton, plugin, version, steveCreateWorker, emberCookie];
+ const pluginDefinitions = [config, cookieUniversal, axios, plugins, pluginsLoader, axiosShell, intNumber, codeMirror, nuxtClientInit, replaceAll, backButton, plugin, steveCreateWorker, emberCookie];
const installations = pluginDefinitions.map(async(pluginDefinition) => {
if (typeof pluginDefinition === 'function') {
diff --git a/shell/pages/c/_cluster/uiplugins/PluginInfoPanel.vue b/shell/pages/c/_cluster/uiplugins/PluginInfoPanel.vue
index fe0f78a3224..9f6598eb97c 100644
--- a/shell/pages/c/_cluster/uiplugins/PluginInfoPanel.vue
+++ b/shell/pages/c/_cluster/uiplugins/PluginInfoPanel.vue
@@ -181,12 +181,6 @@ export default {
-
MAX_DESCRIPTION_LENGTH) {
plugin.description = `${ plugin.description.substr(0, MAX_DESCRIPTION_LENGTH) } ...`;
}
-
- // check if kube version compatibility is met for installed extension
- if (plugin.uiplugin) {
- const versionInstalled = plugin.uiplugin.spec?.plugin?.version;
- const versionInstalledData = plugin.versions.find((v) => v.version === versionInstalled);
-
- if (versionInstalledData) {
- const kubeVersionToCheck = versionInstalledData.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.KUBE_VERSION];
- const versionSupportedData = isSupportedChartVersion({ version: versionInstalledData, kubeVersion: this.kubeVersion });
-
- if (this.kubeVersion && !versionSupportedData.isVersionCompatible && versionSupportedData.versionIncompatibilityData?.type === EXTENSIONS_INCOMPATIBILITY_TYPES.KUBE) {
- plugin.installedError = this.t('plugins.currentInstalledVersionBlockedByKubeVersion', { kubeVersion: this.kubeVersion, kubeVersionToCheck }, true);
- }
- }
- }
});
// Sort by name
@@ -423,66 +408,72 @@ export default {
},
watch: {
- helmOps(neu) {
+ helmOps: {
+ handler(neu) {
// Get Helm operations for UI plugins and order by date
- let pluginOps = neu.filter((op) => {
- return op.namespace === UI_PLUGIN_NAMESPACE;
- });
+ let pluginOps = neu.filter((op) => {
+ return op.namespace === UI_PLUGIN_NAMESPACE;
+ });
- pluginOps = sortBy(pluginOps, 'metadata.creationTimestamp', true);
+ pluginOps = sortBy(pluginOps, 'metadata.creationTimestamp', true);
- // Go through the installed plugins
- (this.available || []).forEach((plugin) => {
- const op = pluginOps.find((o) => o.status?.releaseName === plugin.name);
+ // Go through the installed plugins
+ (this.available || []).forEach((plugin) => {
+ const op = pluginOps.find((o) => o.status?.releaseName === plugin.name);
- if (op) {
- const active = op.metadata.state?.transitioning;
- const error = op.metadata.state?.error;
+ if (op) {
+ const active = op.metadata.state?.transitioning;
+ const error = op.metadata.state?.error;
- this.errors[plugin.name] = error;
+ this.errors[plugin.name] = error;
- if (active) {
+ if (active) {
// Can use the status directly, apart from upgrade, which maps to install
- const status = op.status.action === 'upgrade' ? 'install' : op.status.action;
+ const status = op.status.action === 'upgrade' ? 'install' : op.status.action;
- this.updatePluginInstallStatus(plugin.name, status);
- } else if (op.status.action === 'uninstall') {
+ this.updatePluginInstallStatus(plugin.name, status);
+ } else if (op.status.action === 'uninstall') {
// Uninstall has finished
- this.updatePluginInstallStatus(plugin.name, false);
- } else if (error) {
+ this.updatePluginInstallStatus(plugin.name, false);
+ } else if (error) {
+ this.updatePluginInstallStatus(plugin.name, false);
+ }
+ } else {
this.updatePluginInstallStatus(plugin.name, false);
}
- } else {
- this.updatePluginInstallStatus(plugin.name, false);
- }
- });
+ });
+ },
+ deep: true
},
- plugins(neu, old) {
- const installed = this.$store.getters['uiplugins/plugins'];
- const shouldHaveLoaded = (installed || []).filter((p) => !this.uiErrors[p.name] && !p.builtin);
- let changes = 0;
+ plugins: {
+ handler(neu) {
+ const installed = this.$store.getters['uiplugins/plugins'];
+ const shouldHaveLoaded = (installed || []).filter((p) => !this.uiErrors[p.name] && !p.builtin);
+ let changes = 0;
- // Did the user remove an extension
- if (neu?.length < shouldHaveLoaded.length) {
- changes++;
- }
+ // Did the user remove an extension
+ if (neu?.length < shouldHaveLoaded.length) {
+ changes++;
+ }
- neu.forEach((plugin) => {
- const existing = installed.find((p) => !p.removed && p.name === plugin.name && p.version === plugin.version);
+ neu.forEach((plugin) => {
+ const existing = installed.find((p) => !p.removed && p.name === plugin.name && p.version === plugin.version);
- if (!existing && plugin.isInitialized) {
- if (!this.uiErrors[plugin.name]) {
- changes++;
+ if (!existing && plugin.isInitialized) {
+ if (!this.uiErrors[plugin.name]) {
+ changes++;
+ }
+
+ this.updatePluginInstallStatus(plugin.name, false);
}
+ });
- this.updatePluginInstallStatus(plugin.name, false);
+ if (changes > 0) {
+ this['reloadRequired'] = true;
}
- });
-
- if (changes > 0) {
- this['reloadRequired'] = true;
- }
+ },
+ deep: true
}
},
@@ -852,9 +843,9 @@ export default {
> -> {{ plugin.upgrade }}
-
+
{{ plugin.installedError }}
-
-
-
-
-
i {
+ color: var(--error);
+ height: $error-icon-size;
+ font-size: $error-icon-size;
+ width: $error-icon-size;
+ }
}
}
diff --git a/shell/plugins/plugin.js b/shell/plugins/plugin.js
index 523f9e5da2e..e427ce30d05 100644
--- a/shell/plugins/plugin.js
+++ b/shell/plugins/plugin.js
@@ -1,6 +1,7 @@
// This plugin loads any UI Plugins at app load time
import { allHashSettled } from '@shell/utils/promise';
import { shouldNotLoadPlugin, UI_PLUGIN_BASE_URL } from '@shell/config/uiplugins';
+import { setVersionData, setKubeVersionData } from '@shell/config/version';
export default async function(context) {
if (process.env.excludeOperatorPkg === 'true') {
@@ -26,38 +27,73 @@ export default async function(context) {
}
if (loadPlugins) {
- // TODO: Get rancher version using the new API (can't use setting as we have not loading the store)
- const rancherVersion = undefined;
+ let rancherVersion;
+ let kubeVersion;
+
+ const reqs = [];
+
+ // Fetch rancher version metadata
+ reqs.push(context.store.dispatch('rancher/request', {
+ url: '/rancherversion',
+ method: 'get',
+ redirectUnauthorized: false
+ }));
+
+ // Fetch kubernetes version metadata
+ reqs.push(context.store.dispatch('rancher/request', {
+ url: '/version',
+ method: 'get',
+ redirectUnauthorized: false
+ }));
// Fetch list of installed plugins from endpoint
- try {
- const res = await context.store.dispatch('management/request', {
- url: `${ UI_PLUGIN_BASE_URL }`,
- method: 'GET',
- headers: { accept: 'application/json' },
- redirectUnauthorized: false,
- });
+ reqs.push(context.store.dispatch('management/request', {
+ url: `${ UI_PLUGIN_BASE_URL }`,
+ method: 'GET',
+ headers: { accept: 'application/json' },
+ redirectUnauthorized: false,
+ }));
+
+ const response = await Promise.allSettled(reqs);
- if (res) {
- const entries = res.entries || res.Entries || {};
+ if (response[0]?.status === 'rejected') {
+ console.warn('Failed to fetch Rancher version metadata', response[0]?.reason?.message); // eslint-disable-line no-console
+ }
- Object.values(entries).forEach((plugin) => {
- const shouldNotLoad = shouldNotLoadPlugin(plugin, rancherVersion, context.store.getters['uiplugins/plugins'] || []); // Error key string or boolean
+ if (response[1]?.status === 'rejected') {
+ console.warn('Failed to fetch Kube version metadata', response[1]?.reason?.message); // eslint-disable-line no-console
+ }
- if (!shouldNotLoad) {
- hash[plugin.name] = context.$plugin.loadPluginAsync(plugin);
- } else {
- context.store.dispatch('uiplugins/setError', { name: plugin.name, error: shouldNotLoad });
- }
- });
- }
- } catch (e) {
- if (e?.code === 404) {
- // Not found, so extensions operator probably not installed
- console.log('Could not load UI Extensions list (Extensions Operator may not be installed)'); // eslint-disable-line no-console
- } else {
- console.error('Could not load UI Extensions list', e); // eslint-disable-line no-console
- }
+ if (response[2]?.status === 'rejected') {
+ console.warn('Could not load UI Extensions list', response[2]?.reason?.message); // eslint-disable-line no-console
+ }
+
+ if (response[0]?.status === 'fulfilled') {
+ rancherVersion = response[0]?.value?.Version;
+ setVersionData(response[0]?.value);
+ }
+
+ if (response[1]?.status === 'fulfilled') {
+ kubeVersion = response[1]?.value?.gitVersion;
+ setKubeVersionData(response[1]?.value);
+ }
+
+ if (response[2]?.status === 'fulfilled' && response[2]?.value) {
+ const entries = response[2]?.value.entries || response[2]?.value.Entries || {};
+
+ Object.values(entries).forEach((plugin) => {
+ let shouldNotLoad = shouldNotLoadPlugin(plugin, { rancherVersion, kubeVersion }, context.store.getters['uiplugins/plugins'] || []); // Error key string or boolean
+
+ if (plugin.name === 'elemental') {
+ shouldNotLoad = 'ssss';
+ }
+
+ if (!shouldNotLoad) {
+ hash[plugin.name] = context.$plugin.loadPluginAsync(plugin);
+ } else {
+ context.store.dispatch('uiplugins/setError', { name: plugin.name, error: shouldNotLoad });
+ }
+ });
}
// Load all of the plugins
diff --git a/shell/plugins/version.js b/shell/plugins/version.js
deleted file mode 100644
index d1e51131eec..00000000000
--- a/shell/plugins/version.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * Fetch version metadata from backend /rancherversion API and store it
- *
- * This metadata does not change for an installation of Rancher
- */
-
-import { setVersionData } from '@shell/config/version';
-
-export default async function({ store }) {
- try {
- const response = await store.dispatch('rancher/request', {
- url: '/rancherversion',
- method: 'get',
- redirectUnauthorized: false
- });
-
- setVersionData(response);
- } catch (e) {
- console.warn('Failed to fetch Rancher version metadata', e); // eslint-disable-line no-console
- }
-}
diff --git a/shell/vue.config.js b/shell/vue.config.js
index c00f4ac238d..624c3b287fe 100644
--- a/shell/vue.config.js
+++ b/shell/vue.config.js
@@ -74,6 +74,7 @@ const getProxyConfig = (proxyConfig) => ({
'/meta': proxyMetaOpts(api), // Browser API UI
'/v1-*': proxyOpts(api), // SAML, KDM, etc
'/rancherversion': proxyPrimeOpts(api), // Rancher version endpoint
+ '/version': proxyPrimeOpts(api), // Rancher Kube version endpoint
// These are for Ember embedding
'/c/*/edit': proxyOpts('https://127.0.0.1:8000'), // Can't proxy all of /c because that's used by Vue too
'/k/': proxyOpts('https://127.0.0.1:8000'),
@@ -362,6 +363,10 @@ const getVirtualModulesAutoImport = (dir) => {
return new VirtualModulesPlugin(autoImportTypes);
};
+// Get current shell version
+const shellPkgRawData = fs.readFileSync(path.join(__dirname, 'package.json'));
+const shellPkgData = JSON.parse(shellPkgRawData);
+
/**
* DefinePlugin does string replacement within our code. We may want to consider replacing it with something else. In code we'll see something like
* process.env.commit even though process and env aren't even defined objects. This could cause people to be mislead.
@@ -377,6 +382,7 @@ const createEnvVariablesPlugin = (routerBasePath, rancherEnv) => new webpack.Def
'process.env.rancherEnv': JSON.stringify(rancherEnv),
'process.env.harvesterPkgUrl': JSON.stringify(process.env.HARVESTER_PKG_URL),
'process.env.api': JSON.stringify(api),
+ 'process.env.UI_EXTENSIONS_API_VERSION': JSON.stringify(shellPkgData.version),
// Store the Router Base as env variable that we can use in `shell/config/router.js`
'process.env.routerBase': JSON.stringify(routerBasePath),
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false',