Skip to content

Commit

Permalink
forward-port PRs 11997 and 12018
Browse files Browse the repository at this point in the history
  • Loading branch information
aalves08 committed Sep 27, 2024
1 parent 4c756e9 commit d3e3fd3
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 92 deletions.
12 changes: 8 additions & 4 deletions shell/assets/translations/en-us.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4264,8 +4264,10 @@ inactivity:

# Rancher Extensions
plugins:
incompatibleRancherVersion: "The latest version of this extension ({ version }) is not compatible with the current Rancher version ({ rancherVersion })."
incompatibleKubeVersion: "The latest version of this extension ({ version }) is not compatible with the current Kube version ({ kubeVersion })."
incompatibleRancherVersion: "The latest version of this extension ({ version }) is not compatible with the current Rancher version ({ required })."
incompatibleKubeVersion: "The latest version of this extension ({ version }) is not compatible with the current Kube version ({ required })."
incompatibleUiExtensionsApiVersion: "The latest version of this extension ({ version }) is not compatible with the current Extensions API version ({ required })."
incompatibleHost: 'The latest version of this extension ({ version }) has a host of "{ required }" which is not compatible with this application "{ mainHost }".'
currentInstalledVersionBlockedByKubeVersion: "This version is not compatible with the current Kubernetes version ({ kubeVersion } Vs { kubeVersionToCheck })."
labels:
builtin: Built-in
Expand Down Expand Up @@ -4303,8 +4305,10 @@ plugins:
detail: Detail
versions: Versions
versionError: Could not load version information
requiresRancherVersion: "Requires Rancher {version}"
requiresKubeVersion: "Requires Kube version {version}"
requiresRancherVersion: "Requires Rancher {required}"
requiresKubeVersion: "Requires Kube version {required}"
requiresExtensionApiVersion: "Requires Extensions API version {required}"
requiresHost: 'Requires a host that matches "{mainHost}"'
empty:
all: Extensions are neither installed nor available
available: No Extensions available
Expand Down
195 changes: 134 additions & 61 deletions shell/config/uiplugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,40 @@ export const UI_PLUGIN_METADATA = {
DISPLAY_NAME: 'displayName',
};

export const EXTENSIONS_INCOMPATIBILITY_TYPES = {
UI: 'uiVersion',
EXTENSIONS_API: 'extensionsApiVersion',
KUBE: 'kubeVersion',
HOST: 'host'
};

export const EXTENSIONS_INCOMPATIBILITY_DATA = {
UI: {
type: EXTENSIONS_INCOMPATIBILITY_TYPES.UI,
cardMessageKey: 'plugins.incompatibleRancherVersion',
tooltipKey: 'plugins.info.requiresRancherVersion',
mainHost: UI_PLUGIN_HOST_APP
},
EXTENSIONS_API: {
type: EXTENSIONS_INCOMPATIBILITY_TYPES.EXTENSIONS_API,
cardMessageKey: 'plugins.incompatibleUiExtensionsApiVersion',
tooltipKey: 'plugins.info.requiresExtensionApiVersion',
mainHost: UI_PLUGIN_HOST_APP
},
KUBE: {
type: EXTENSIONS_INCOMPATIBILITY_TYPES.KUBE,
cardMessageKey: 'plugins.incompatibleKubeVersion',
tooltipKey: 'plugins.info.requiresKubeVersion',
mainHost: UI_PLUGIN_HOST_APP
},
HOST: {
type: EXTENSIONS_INCOMPATIBILITY_TYPES.HOST,
cardMessageKey: 'plugins.incompatibleHost',
tooltipKey: 'plugins.info.requiresHost',
mainHost: UI_PLUGIN_HOST_APP
}
};

export function isUIPlugin(chart) {
return !!chart?.versions.find((v) => {
return v.annotations && v.annotations[UI_PLUGIN_ANNOTATION_NAME] === UI_PLUGIN_ANNOTATION_VALUE;
Expand All @@ -91,130 +125,169 @@ export function uiPluginAnnotation(chart, name) {
return undefined;
}

/**
* Parse the rancher version string
*/
function parseRancherVersion(v) {
let parsedRancherVersion = semver.coerce(v)?.version;
const splitArr = parsedRancherVersion.split('.');

// this is a scenario where we are on a "head" version of some sort... we can't infer the patch version from it
// so we apply a big patch version number to make sure we follow through with the minor
if (v.includes('-') && splitArr?.length === 3) {
parsedRancherVersion = `${ splitArr[0] }.${ splitArr[1] }.999`;
}

return parsedRancherVersion;
}

// i18n-uses plugins.error.generic, plugins.error.api, plugins.error.host

// Should we load a plugin, based on the metadata returned by the backend?
// Returns error key string or false
export function shouldNotLoadPlugin(plugin, rancherVersion, loadedPlugins) {
if (!plugin.name || !plugin.version || !plugin.endpoint) {
/**
* 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) {
if (!UIPluginResource.name || !UIPluginResource.version || !UIPluginResource.endpoint) {
return 'plugins.error.generic';
}

// Plugin specified a required extension API version
// 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 requiredAPI = plugin.spec?.plugin?.metadata?.[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION];
const requiredUiExtensionsVersion = UIPluginResource.spec?.plugin?.metadata?.[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION];
// 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) : '';

if (requiredAPI && !semver.satisfies(UI_EXTENSIONS_API_VERSION, requiredAPI)) {
if (requiredUiExtensionsVersion && !semver.satisfies(parsedUiExtensionsApiVersion, requiredUiExtensionsVersion)) {
return 'plugins.error.api';
}

// Host application
const requiredHost = plugin.metadata?.[UI_PLUGIN_METADATA.EXTENSIONS_HOST];
const requiredHost = UIPluginResource.metadata?.[UI_PLUGIN_METADATA.EXTENSIONS_HOST];

if (requiredHost && requiredHost !== UI_PLUGIN_HOST_APP) {
return 'plugins.error.host';
}

// Rancher version
if (rancherVersion) {
const requiredRancherVersion = plugin.metadata?.[UI_PLUGIN_METADATA.RANCHER_VERSION];
if (parsedRancherVersion) {
const requiredRancherVersion = UIPluginResource.metadata?.[UI_PLUGIN_METADATA.RANCHER_VERSION];

if (requiredRancherVersion && !semver.satisfies(rancherVersion, requiredRancherVersion)) {
if (requiredRancherVersion && !semver.satisfies(parsedRancherVersion, requiredRancherVersion)) {
return 'plugins.error.version';
}
}

// check if a builtin extension has been loaded before - improve developer experience
const checkLoaded = loadedPlugins.find((p) => p?.name === plugin?.name);
const checkLoaded = loadedPlugins.find((p) => p?.name === UIPluginResource?.name);

if (checkLoaded && checkLoaded.builtin) {
return 'plugins.error.developerPkg';
}

if (plugin.metadata?.[UI_PLUGIN_LABELS.CATALOG]) {
if (UIPluginResource.metadata?.[UI_PLUGIN_LABELS.CATALOG]) {
return true;
}

return false;
}

// Can a chart version be used for this Rancher (based on the annotations on the chart)?
export function isSupportedChartVersion(versionsData) {
const { version, rancherVersion, kubeVersion } = versionsData;
/**
* Wether an extension version is available to be installed, based on the annotations present in the Helm chart version
* backend may not automatically "limit" a particular version but dashboard will disable that version for install with this check
* @returns Boolean || Object
*/
export function isSupportedChartVersion(versionData, returnObj = false) {
const { version, rancherVersion, kubeVersion } = versionData;

// Plugin specified a required extension API version
const requiredAPI = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION];
// semver.coerce will get rid of any suffix on the version numbering (-rc, -head, etc)
const parsedRancherVersion = rancherVersion ? parseRancherVersion(rancherVersion) : '';
const requiredUiVersion = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.UI_VERSION];
const requiredKubeVersion = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.KUBE_VERSION];
const versionObj = { ...version };

if (requiredAPI && !semver.satisfies(UI_EXTENSIONS_API_VERSION, requiredAPI)) {
return false;
}
// reset compatibility property
versionObj.isVersionCompatible = true;
versionObj.versionIncompatibilityData = {};

// Host application
const requiredHost = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_HOST];
// check "catalog.cattle.io/kube-version" annotation
// we keep it as first check since there is a card notification to be displayed
// in case an extension version installed has an incompatibility with the kube version and is not loaded
if (kubeVersion && requiredKubeVersion && !semver.satisfies(kubeVersion, requiredKubeVersion)) {
if (!returnObj) {
return false;
}

if (requiredHost && requiredHost !== UI_PLUGIN_HOST_APP) {
return false;
versionObj.isVersionCompatible = false;
versionObj.versionIncompatibilityData = Object.assign({}, EXTENSIONS_INCOMPATIBILITY_DATA.KUBE);
versionObj.versionIncompatibilityData.required = requiredKubeVersion;

return versionObj;
}

// Rancher version
if (rancherVersion) {
// we aren't on a "published" version of Rancher and therefore in a "-head" or similar
// Backend will NOT block an extension version from being available IF we are on HEAD versions!!
// we need to enforce that check if we are on a HEAD world
if (rancherVersion && rancherVersion.includes('-')) {
const requiredRancherVersion = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.RANCHER_VERSION];

if (requiredRancherVersion && !semver.satisfies(rancherVersion, requiredRancherVersion)) {
return false;
if (parsedRancherVersion && !semver.satisfies(parsedRancherVersion, requiredRancherVersion)) {
if (!returnObj) {
return false;
}

versionObj.isVersionCompatible = false;
versionObj.versionIncompatibilityData = Object.assign({}, EXTENSIONS_INCOMPATIBILITY_DATA.UI);
versionObj.versionIncompatibilityData.required = requiredRancherVersion;

return versionObj;
}
}

// Kube version
if (kubeVersion) {
const requiredKubeVersion = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.KUBE_VERSION];
// check host application
const requiredHost = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_HOST];

if (requiredKubeVersion && !semver.satisfies(kubeVersion, requiredKubeVersion)) {
if (requiredHost && requiredHost !== UI_PLUGIN_HOST_APP) {
if (!returnObj) {
return false;
}
}

return true;
}

export function isChartVersionAvailableForInstall(versionsData, returnObj = false) {
const { version, rancherVersion, kubeVersion } = versionsData;
versionObj.isVersionCompatible = false;
versionObj.versionIncompatibilityData = Object.assign({}, EXTENSIONS_INCOMPATIBILITY_DATA.HOST);
versionObj.versionIncompatibilityData.required = requiredHost;

const parsedRancherVersion = rancherVersion.split('-')?.[0];
const regexHashString = new RegExp('^[A-Za-z0-9]{9}$');
const isRancherVersionHashStringOrHead = regexHashString.test(rancherVersion) || rancherVersion.includes('head');
const requiredUiVersion = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.UI_VERSION];
const requiredKubeVersion = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.KUBE_VERSION];
const versionObj = { ...version };
return versionObj;
}

versionObj.isCompatibleWithUi = true;
versionObj.isCompatibleWithKubeVersion = true;
// check "catalog.cattle.io/ui-extensions-version" annotation
const requiredUiExtensionsApiVersion = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION];
const parsedUiExtensionsApiVersion = semver.coerce(UI_EXTENSIONS_API_VERSION)?.version || UI_EXTENSIONS_API_VERSION;

// if it's a head version of Rancher, then we skip the validation and enable them all
if (!isRancherVersionHashStringOrHead && requiredUiVersion && !semver.satisfies(parsedRancherVersion, requiredUiVersion)) {
if (requiredUiExtensionsApiVersion && parsedUiExtensionsApiVersion && !semver.satisfies(parsedUiExtensionsApiVersion, requiredUiExtensionsApiVersion)) {
if (!returnObj) {
return false;
}
versionObj.isCompatibleWithUi = false;
versionObj.requiredUiVersion = requiredUiVersion;

if (returnObj) {
return versionObj;
}
versionObj.isVersionCompatible = false;
versionObj.versionIncompatibilityData = Object.assign({}, EXTENSIONS_INCOMPATIBILITY_DATA.EXTENSIONS_API);
versionObj.versionIncompatibilityData.required = requiredUiExtensionsApiVersion;

return versionObj;
}

// check kube version
if (kubeVersion && requiredKubeVersion && !semver.satisfies(kubeVersion, requiredKubeVersion)) {
// check "catalog.cattle.io/ui-version" annotation
if (requiredUiVersion && parsedRancherVersion && !semver.satisfies(parsedRancherVersion, requiredUiVersion)) {
if (!returnObj) {
return false;
}
versionObj.isCompatibleWithKubeVersion = false;
versionObj.requiredKubeVersion = requiredKubeVersion;

if (returnObj) {
return versionObj;
}
versionObj.isVersionCompatible = false;
versionObj.versionIncompatibilityData = Object.assign({}, EXTENSIONS_INCOMPATIBILITY_DATA.UI);
versionObj.versionIncompatibilityData.required = requiredUiVersion;

return versionObj;
}

if (returnObj) {
Expand Down
18 changes: 7 additions & 11 deletions shell/pages/c/_cluster/uiplugins/PluginInfoPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,9 @@ export default {
async loadPluginVersionInfo(version) {
const versionName = version || this.info.displayVersion;
const isVersionNotCompatibleWithUi = this.info.versions?.find((v) => v.version === versionName && !v.isCompatibleWithUi);
const isVersionNotCompatibleWithKubeVersion = this.info.versions?.find((v) => v.version === versionName && !v.isCompatibleWithKubeVersion);
const isVersionNotCompatible = this.info.versions?.find((v) => v.version === versionName && !v.isVersionCompatible);
if (!this.info.chart || isVersionNotCompatibleWithUi || isVersionNotCompatibleWithKubeVersion) {
if (!this.info.chart || isVersionNotCompatible) {
return;
}
Expand Down Expand Up @@ -107,18 +106,15 @@ export default {
},
handleVersionBtnTooltip(version) {
if (version.requiredUiVersion) {
return this.t('plugins.info.requiresRancherVersion', { version: version.requiredUiVersion });
}
if (version.requiredKubeVersion) {
return this.t('plugins.info.requiresKubeVersion', { version: version.requiredKubeVersion });
if (!version.isVersionCompatible && Object.keys(version.versionIncompatibilityData).length) {
return this.t(version.versionIncompatibilityData?.tooltipKey, { required: version.versionIncompatibilityData?.required, mainHost: version.versionIncompatibilityData?.mainHost });
}
return '';
},
handleVersionBtnClass(version) {
return { 'version-active': version.version === this.infoVersion, disabled: !version.isCompatibleWithUi || !version.isCompatibleWithKubeVersion };
return { 'version-active': version.version === this.infoVersion, disabled: !version.isVersionCompatible };
}
}
};
Expand Down Expand Up @@ -218,8 +214,8 @@ export default {
</h3>
<div class="plugin-versions mb-10">
<div
v-for="(v, i) in info.versions"
:key="i"
v-for="v in info.versions"
:key="`${v.name}-${v.version}`"
>
<a
v-clean-tooltip="handleVersionBtnTooltip(v)"
Expand Down
Loading

0 comments on commit d3e3fd3

Please sign in to comment.