mirror of https://github.com/rancher/dashboard.git
273 lines
10 KiB
JavaScript
273 lines
10 KiB
JavaScript
import semver from 'semver';
|
|
|
|
// Version of the plugin API supported
|
|
// here we inject the current shell version that we read in vue.config
|
|
export const UI_EXTENSIONS_API_VERSION = process.env.UI_EXTENSIONS_API_VERSION;
|
|
|
|
export const UI_PLUGIN_HOST_APP = 'rancher-manager';
|
|
|
|
export const UI_PLUGIN_BASE_URL = '/v1/uiplugins';
|
|
export const UI_PLUGIN_NAMESPACE = 'cattle-ui-plugin-system';
|
|
|
|
// Annotation name and value that indicate a chart is a UI plugin
|
|
export const UI_PLUGIN_ANNOTATION = {
|
|
NAME: 'catalog.cattle.io/ui-component',
|
|
VALUE: 'plugins',
|
|
};
|
|
|
|
// Info for the Helm Chart Repositories
|
|
export const UI_PLUGINS_REPOS = {
|
|
OFFICIAL: {
|
|
NAME: 'rancher-ui-plugins',
|
|
URL: 'https://github.com/rancher/ui-plugin-charts',
|
|
BRANCH: 'main',
|
|
},
|
|
PARTNERS: {
|
|
NAME: 'partner-extensions',
|
|
URL: 'https://github.com/rancher/partner-extensions',
|
|
BRANCH: 'main',
|
|
},
|
|
COMMUNITY: {
|
|
NAME: 'community-extensions',
|
|
URL: 'https://github.com/rancher/community-extensions',
|
|
BRANCH: 'main',
|
|
},
|
|
};
|
|
|
|
// Chart annotations
|
|
export const UI_PLUGIN_CHART_ANNOTATIONS = {
|
|
KUBE_VERSION: 'catalog.cattle.io/kube-version',
|
|
RANCHER_VERSION: 'catalog.cattle.io/rancher-version',
|
|
EXTENSIONS_VERSION: 'catalog.cattle.io/ui-extensions-version',
|
|
UI_VERSION: 'catalog.cattle.io/ui-version',
|
|
EXTENSIONS_HOST: 'catalog.cattle.io/ui-extensions-host',
|
|
DISPLAY_NAME: 'catalog.cattle.io/display-name',
|
|
HIDDEN_BUILTIN: 'catalog.cattle.io/ui-hidden-builtin',
|
|
};
|
|
|
|
// Extension catalog labels
|
|
export const UI_PLUGIN_LABELS = {
|
|
CATALOG_IMAGE: 'catalog.cattle.io/ui-extensions-catalog-image',
|
|
REPOSITORY: 'catalog.cattle.io/ui-extensions-repository',
|
|
CATALOG: 'catalog.cattle.io/ui-extensions-catalog',
|
|
};
|
|
|
|
export const EXTENSIONS_INCOMPATIBILITY_TYPES = {
|
|
UI: 'uiVersion',
|
|
EXTENSIONS_API_MISSING: 'extensionsApiVersionMissing',
|
|
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',
|
|
},
|
|
EXTENSIONS_API_MISSING: {
|
|
type: EXTENSIONS_INCOMPATIBILITY_TYPES.EXTENSIONS_API_MISSING,
|
|
cardMessageKey: 'plugins.incompatibleUiExtensionsApiVersionMissing',
|
|
tooltipKey: 'plugins.info.requiresExtensionApiVersionMissing',
|
|
},
|
|
EXTENSIONS_API: {
|
|
type: EXTENSIONS_INCOMPATIBILITY_TYPES.EXTENSIONS_API,
|
|
cardMessageKey: 'plugins.incompatibleUiExtensionsApiVersion',
|
|
tooltipKey: 'plugins.info.requiresExtensionApiVersion',
|
|
},
|
|
KUBE: {
|
|
type: EXTENSIONS_INCOMPATIBILITY_TYPES.KUBE,
|
|
cardMessageKey: 'plugins.incompatibleKubeVersion',
|
|
tooltipKey: 'plugins.info.requiresKubeVersion',
|
|
},
|
|
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) => v.annotations?.[UI_PLUGIN_ANNOTATION.NAME] === UI_PLUGIN_ANNOTATION.VALUE);
|
|
}
|
|
|
|
export function uiPluginHasAnnotation(chart, name, value) {
|
|
return !!chart?.versions.find((v) => v.annotations?.[name] === value);
|
|
}
|
|
|
|
/**
|
|
* Get value of the annotation from the latest version for a chart
|
|
*/
|
|
export function uiPluginAnnotation(chart, name) {
|
|
return chart?.versions?.[0]?.annotations?.[name];
|
|
}
|
|
|
|
/**
|
|
* Parse the Rancher version string
|
|
*/
|
|
function parseRancherVersion(v) {
|
|
let parsedVersion = semver.coerce(v)?.version;
|
|
const splitArr = parsedVersion?.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) {
|
|
parsedVersion = `${ splitArr[0] }.${ splitArr[1] }.999`;
|
|
}
|
|
|
|
return parsedVersion;
|
|
}
|
|
|
|
/**
|
|
* Check if a version is incompatible with the current environment
|
|
*/
|
|
function checkIncompatibility(currentVersion, requiredVersion, incompatibilityData, returnObj, versionObj) {
|
|
if ((incompatibilityData.type === EXTENSIONS_INCOMPATIBILITY_TYPES.EXTENSIONS_API_MISSING && !requiredVersion) || (requiredVersion && !semver.satisfies(currentVersion, requiredVersion))) {
|
|
if (!returnObj) {
|
|
return false;
|
|
}
|
|
versionObj.isVersionCompatible = false;
|
|
versionObj.versionIncompatibilityData = { ...incompatibilityData, required: requiredVersion };
|
|
|
|
return versionObj;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// i18n-uses plugins.error.generic, plugins.error.api, plugins.error.host, plugins.error.kubeVersion, plugins.error.version, plugins.error.developerPkg, plugins.error.apiAnnotationMissing
|
|
|
|
/**
|
|
* Whether an extension should be loaded based on the metadata returned by the backend in the UIPlugins resource instance
|
|
* The output will be used to PREVENT loading of an extension that is already installed but isn't compatible with the system
|
|
*
|
|
* String output will display a message on the extension card to notify users on why the extension was not loaded
|
|
*
|
|
* @returns String | Boolean
|
|
*/
|
|
export function shouldNotLoadPlugin(UIPluginResource, { rancherVersion, kubeVersion }, loadedPlugins) {
|
|
if (!UIPluginResource.name || !UIPluginResource.version || !UIPluginResource.endpoint) {
|
|
return 'plugins.error.generic';
|
|
}
|
|
|
|
// 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.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;
|
|
const parsedRancherVersion = rancherVersion ? parseRancherVersion(rancherVersion) : '';
|
|
const parsedKubeVersion = kubeVersion ? semver.coerce(kubeVersion)?.version : '';
|
|
|
|
if (!requiredUiExtensionsVersion) {
|
|
return 'plugins.error.apiAnnotationMissing';
|
|
} else if (requiredUiExtensionsVersion && !semver.satisfies(parsedUiExtensionsApiVersion, requiredUiExtensionsVersion)) {
|
|
return 'plugins.error.api';
|
|
}
|
|
|
|
// Host application
|
|
const requiredHost = UIPluginResource.metadata?.[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_HOST];
|
|
|
|
if (requiredHost && requiredHost !== UI_PLUGIN_HOST_APP) {
|
|
return 'plugins.error.host';
|
|
}
|
|
|
|
// Kube version
|
|
if (parsedKubeVersion) {
|
|
const requiredKubeVersion = UIPluginResource.metadata?.[UI_PLUGIN_CHART_ANNOTATIONS.KUBE_VERSION];
|
|
|
|
if (requiredKubeVersion && !semver.satisfies(parsedKubeVersion, requiredKubeVersion)) {
|
|
return 'plugins.error.kubeVersion';
|
|
}
|
|
}
|
|
|
|
// Rancher version
|
|
if (parsedRancherVersion) {
|
|
const requiredRancherVersion = UIPluginResource.metadata?.[UI_PLUGIN_CHART_ANNOTATIONS.RANCHER_VERSION];
|
|
|
|
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 === UIPluginResource?.name);
|
|
|
|
if (checkLoaded && checkLoaded.builtin) {
|
|
return 'plugins.error.developerPkg';
|
|
}
|
|
|
|
if (UIPluginResource.metadata?.[UI_PLUGIN_LABELS.CATALOG]) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*
|
|
* The output will be used to display a message on the extension card to notify users if a LATEST version of an extension is available but isn't compatible (cardMessageKey)
|
|
* The output will also disable the buttons in the slide-in panel with extension details, displaying a tooltip message with the reason (tooltipKey)
|
|
*
|
|
* @returns Boolean | Object
|
|
*/
|
|
export function isSupportedChartVersion(versionData, returnObj = false) {
|
|
const { version, rancherVersion, kubeVersion } = versionData;
|
|
const versionObj = {
|
|
...version, isVersionCompatible: true, versionIncompatibilityData: {}
|
|
};
|
|
const parsedRancherVersion = rancherVersion ? parseRancherVersion(rancherVersion) : '';
|
|
const parsedUiExtensionsApiVersion = semver.coerce(UI_EXTENSIONS_API_VERSION)?.version;
|
|
|
|
const checks = [
|
|
{
|
|
currentVersion: kubeVersion,
|
|
requiredVersion: version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.KUBE_VERSION],
|
|
incompatibilityData: EXTENSIONS_INCOMPATIBILITY_DATA.KUBE,
|
|
},
|
|
{
|
|
currentVersion: parsedRancherVersion,
|
|
requiredVersion: version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.RANCHER_VERSION],
|
|
incompatibilityData: EXTENSIONS_INCOMPATIBILITY_DATA.UI,
|
|
},
|
|
{
|
|
currentVersion: parsedRancherVersion,
|
|
requiredVersion: version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.UI_VERSION],
|
|
incompatibilityData: EXTENSIONS_INCOMPATIBILITY_DATA.UI,
|
|
},
|
|
{
|
|
currentVersion: parsedUiExtensionsApiVersion,
|
|
requiredVersion: version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION],
|
|
incompatibilityData: EXTENSIONS_INCOMPATIBILITY_DATA.EXTENSIONS_API_MISSING,
|
|
},
|
|
{
|
|
currentVersion: parsedUiExtensionsApiVersion,
|
|
requiredVersion: version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION],
|
|
incompatibilityData: EXTENSIONS_INCOMPATIBILITY_DATA.EXTENSIONS_API,
|
|
},
|
|
{
|
|
currentVersion: UI_PLUGIN_HOST_APP,
|
|
requiredVersion: version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_HOST],
|
|
incompatibilityData: EXTENSIONS_INCOMPATIBILITY_DATA.HOST,
|
|
},
|
|
];
|
|
|
|
for (const { currentVersion, requiredVersion, incompatibilityData } of checks) {
|
|
const result = checkIncompatibility(currentVersion, requiredVersion, incompatibilityData, returnObj, versionObj);
|
|
|
|
if (result !== true) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return returnObj ? versionObj : true;
|
|
}
|
|
|
|
export function isChartVersionHigher(versionA, versionB) {
|
|
return semver.gt(versionA, versionB);
|
|
}
|