Skip to content

Commit

Permalink
Check for supported API version annotation when loading extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
jordojordo committed Sep 26, 2024
1 parent 31519da commit c0186e8
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 2 deletions.
18 changes: 18 additions & 0 deletions cypress/e2e/po/pages/extensions.po.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,20 @@ export default class ExtensionsPagePo extends PagePo {
return this.extensionCard(extensionName).getId(`extension-card-uninstall-btn-${ extensionName }`).click();
}

extensionCardErrorTooltip(extensionName: string): Cypress.Chainable {
return this.extensionCard(extensionName).getId(`extension-card-error-${ extensionName }`);
}

extensionCardErrorTooltipContent(extensionName: string): Cypress.Chainable {
return this.extensionCardErrorTooltip(extensionName).trigger('mouseenter').then(() => {
return cy.get('.v-popper__popper.v-popper--theme-tooltip .v-popper__inner');
});
}

extensionCardInstallButton(extensionName: string): Cypress.Chainable {
return this.extensionCard(extensionName).getId(`extension-card-install-btn-${ extensionName }`);
}

// ------------------ extension install modal ------------------
extensionInstallModal() {
return this.self().get('[data-modal="installPluginDialog"]');
Expand Down Expand Up @@ -205,6 +219,10 @@ export default class ExtensionsPagePo extends PagePo {
return this.extensionDetails().getId('extension-details-close').click();
}

extensionDetailsErrorBanner(): Cypress.Chainable {
return this.extensionDetails().getId('extension-details-error');
}

// ------------------ extension tabs ------------------
extensionTabInstalledClick(): Cypress.Chainable {
return this.extensionTabs.clickNthTab(1);
Expand Down
158 changes: 158 additions & 0 deletions cypress/e2e/tests/pages/extensions/extensions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,4 +441,162 @@ describe('Extensions page', { tags: ['@extensions', '@adminUser'] }, () => {
extensionsPo.extensionCardClick(DISABLED_CACHE_EXTENSION_NAME);
extensionsPo.extensionDetailsTitle().should('contain', DISABLED_CACHE_EXTENSION_NAME);
});

it.skip('[Vue3 Skip]: Should display error for installed extension without required API version annotation', () => {
const extensionsPo = new ExtensionsPagePo();

extensionsPo.addExtensionsRepository('https://github.com/rancher/ui-plugin-examples', 'main', 'rancher-plugin-examples');

// Intercept the request fetching chart data to modify the 'clock' extension
cy.intercept('GET', '/v1/catalog.cattle.io.clusterrepos*', (req) => {
req.continue((res) => {
if (res.body && res.body.data) {
res.body.data.forEach((chart) => {
if (chart.metadata.name === 'clock') {
// Remove the required API annotation to simulate the missing condition
delete chart.versions[0].annotations['catalog.cattle.io/ui-extensions-version'];
}
});
}
});
});

extensionsPo.goTo();
extensionsPo.extensionTabAvailableClick();

extensionsPo.extensionCardInstallClick('clock');
extensionsPo.extensionInstallModal().should('be.visible');
extensionsPo.installModalInstallClick();

extensionsPo.extensionReloadBanner().should('be.visible');
extensionsPo.extensionReloadClick();

extensionsPo.extensionTabInstalledClick();

// Verify that the 'clock' extension card displays an error icon with tooltip
extensionsPo.extensionCardErrorTooltip('clock').should('exist');
extensionsPo.extensionCardErrorTooltipContent('clock').should('contain', 'This Extension is not compatible with the current Extensions API version');

extensionsPo.extensionCardClick('clock');

// Verify that the extension details display an error banner with the correct message
extensionsPo.extensionDetailsErrorBanner().should('contain', 'This Extension is not compatible with the current Extensions API version');

extensionsPo.extensionDetailsCloseClick();

extensionsPo.extensionCardUninstallClick('clock');
extensionsPo.extensionUninstallModal().should('be.visible');
extensionsPo.uninstallModaluninstallClick();

extensionsPo.extensionReloadBanner().should('be.visible');
extensionsPo.extensionReloadClick();

extensionsPo.extensionTabInstalledClick();
extensionsPo.extensionCard('clock').should('not.exist');
});

it.skip('[Vue3 Skip]: Should display error for installed extension with outdated required API version', () => {
const extensionsPo = new ExtensionsPagePo();

extensionsPo.addExtensionsRepository('https://github.com/rancher/ui-plugin-examples', 'main', 'rancher-plugin-examples');

// Intercept the request fetching chart data to modify the 'clock' extension
cy.intercept('GET', '/v1/catalog.cattle.io.clusterrepos*', (req) => {
req.continue((res) => {
if (res.body && res.body.data) {
res.body.data.forEach((chart) => {
if (chart.metadata.name === 'clock') {
// Set the required API version to a value less than UI_EXTENSIONS_API_VERSION
chart.versions[0].annotations['catalog.cattle.io/ui-extensions-version'] = '>=2.0.0';
}
});
}
});
});

extensionsPo.goTo();
extensionsPo.extensionTabAvailableClick();

extensionsPo.extensionCardInstallClick('clock');
extensionsPo.extensionInstallModal().should('be.visible');
extensionsPo.installModalInstallClick();

extensionsPo.extensionReloadBanner().should('be.visible');
extensionsPo.extensionReloadClick();

extensionsPo.extensionTabInstalledClick();

// Verify that the 'clock' extension card displays an error icon with tooltip
extensionsPo.extensionCardErrorTooltip('clock').should('exist');
extensionsPo.extensionCardErrorTooltipContent('clock').should('contain', 'This Extension is not compatible with the current Extensions API version');

extensionsPo.extensionCardClick('clock');

// Verify that the extension details display an error banner with the correct message
extensionsPo.extensionDetailsErrorBanner().should('contain', 'This Extension is not compatible with the current Extensions API version');

extensionsPo.extensionDetailsCloseClick();

extensionsPo.extensionCardUninstallClick('clock');
extensionsPo.extensionUninstallModal().should('be.visible');
extensionsPo.uninstallModaluninstallClick();

extensionsPo.extensionReloadBanner().should('be.visible');
extensionsPo.extensionReloadClick();

extensionsPo.extensionTabInstalledClick();
extensionsPo.extensionCard('clock').should('not.exist');
});

it.skip('[Vue3 Skip]: Should successfully install extension with satisfying required API version', () => {
const extensionsPo = new ExtensionsPagePo();

extensionsPo.addExtensionsRepository('https://github.com/rancher/ui-plugin-examples', 'main', 'rancher-plugin-examples');

// Intercept the request fetching chart data to modify the 'clock' extension
cy.intercept('GET', '/v1/catalog.cattle.io.clusterrepos*', (req) => {
req.continue((res) => {
if (res.body && res.body.data) {
res.body.data.forEach((chart) => {
if (chart.metadata.name === 'clock') {
// Set the required API version to a value that satisfies the UI version
chart.versions[0].annotations['catalog.cattle.io/ui-extensions-version'] = '>=3.0.0';
}
});
}
});
});

extensionsPo.goTo();
extensionsPo.extensionTabAvailableClick();

extensionsPo.extensionCardInstallClick('clock');
extensionsPo.extensionInstallModal().should('be.visible');
extensionsPo.installModalInstallClick();

extensionsPo.extensionReloadBanner().should('be.visible');
extensionsPo.extensionReloadClick();

extensionsPo.extensionTabInstalledClick();

// Verify that the 'clock' extension card does not display an error icon
extensionsPo.extensionCardErrorTooltip('clock').should('not.exist');

extensionsPo.extensionCardClick('clock');

// Verify that the extension details do not display an error banner
extensionsPo.extensionDetailsErrorBanner().should('not.exist');

extensionsPo.extensionDetailsCloseClick();

extensionsPo.extensionCardUninstallClick('clock');
extensionsPo.extensionUninstallModal().should('be.visible');
extensionsPo.uninstallModaluninstallClick();

extensionsPo.extensionReloadBanner().should('be.visible');
extensionsPo.extensionReloadClick();

extensionsPo.extensionTabInstalledClick();
extensionsPo.extensionCard('clock').should('not.exist');
});
});
16 changes: 14 additions & 2 deletions shell/config/uiplugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ export function uiPluginAnnotation(chart, name) {
return undefined;
}

/**
* Coerce the UI_EXTENSIONS_API_VERSION to remove any pre-release identifiers
*/
export function coerceCurrentExtensionsVersion() {
return semver.coerce(UI_EXTENSIONS_API_VERSION)?.version;
}

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

// Should we load a plugin, based on the metadata returned by the backend?
Expand All @@ -104,8 +111,12 @@ export function shouldNotLoadPlugin(plugin, rancherVersion, loadedPlugins) {
// 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 currentVersion = coerceCurrentExtensionsVersion();

if (requiredAPI && !semver.satisfies(UI_EXTENSIONS_API_VERSION, requiredAPI)) {
// If an extension does not contain the `catalog.cattle.io/ui-extensions-version`
// annotation, it is considered as a legacy extension.
// Otherwise, the ui-extensions-version is compared with the current shell version.
if (!requiredAPI || !semver.satisfies(currentVersion, requiredAPI)) {
return 'plugins.error.api';
}

Expand Down Expand Up @@ -145,8 +156,9 @@ export function isSupportedChartVersion(versionsData) {

// Plugin specified a required extension API version
const requiredAPI = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION];
const currentVersion = coerceCurrentExtensionsVersion();

if (requiredAPI && !semver.satisfies(UI_EXTENSIONS_API_VERSION, requiredAPI)) {
if (requiredAPI && !semver.satisfies(currentVersion, requiredAPI)) {
return false;
}

Expand Down
1 change: 1 addition & 0 deletions shell/pages/c/_cluster/uiplugins/PluginInfoPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ export default {
v-if="info.error"
color="error"
:label="info.error"
data-testid="extension-details-error"
class="mt-10"
/>
<Banner
Expand Down
1 change: 1 addition & 0 deletions shell/pages/c/_cluster/uiplugins/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -888,6 +888,7 @@ export default {
<div
v-clean-tooltip="plugin.error"
class="plugin-error"
:data-testid="`extension-card-error-${plugin.name}`"
>
<i class="icon icon-warning" />
</div>
Expand Down
5 changes: 5 additions & 0 deletions shell/vue.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,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.
Expand All @@ -377,6 +381,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',
Expand Down

0 comments on commit c0186e8

Please sign in to comment.