Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

forward-port PRs 11997 and 12018 #12046

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading