Skip to content

Commit

Permalink
feat: add scalprum backend plugin. (janus-idp#599)
Browse files Browse the repository at this point in the history
* feat: add a `scalprum` backend plugin.

This plugins depends on the `backend-plugin-manager` and
provides access to the scalprum defintinions, and the plugin assets
of the dynamic frontend plugins installed in the
dynamic plugins root folder.

Signed-off-by: David Festal <dfestal@redhat.com>

* Add changeset

Signed-off-by: David Festal <dfestal@redhat.com>

---------

Signed-off-by: David Festal <dfestal@redhat.com>
  • Loading branch information
davidfestal committed Oct 17, 2023
1 parent 2671dba commit 4af2b85
Show file tree
Hide file tree
Showing 13 changed files with 515 additions and 29 deletions.
12 changes: 12 additions & 0 deletions .changeset/many-ads-return.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@internal/plugin-scalprum-backend': patch
'backend': patch
---

Add a `scalprum` backend plugin.

This plugin depends on the `backend-plugin-manager` and
provides access to the scalprum defintinions,
as well as the plugin assets,
of the dynamic frontend plugins which are
installed in the dynamic plugins root folder.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
**/dist
**/node_modules
plugins
!plugins/scalprum-backend
*.local.yaml
coverage
dist-types
Expand Down
1 change: 1 addition & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ FROM skeleton AS deps
COPY $EXTERNAL_SOURCE_NESTED/package.json $EXTERNAL_SOURCE_NESTED/yarn.lock ./
COPY $EXTERNAL_SOURCE_NESTED/packages/app/package.json ./packages/app/package.json
COPY $EXTERNAL_SOURCE_NESTED/packages/backend/package.json ./packages/backend/package.json
COPY $EXTERNAL_SOURCE_NESTED/plugins/scalprum-backend/package.json ./plugins/scalprum-backend/package.json

RUN $YARN install --frozen-lockfile --network-timeout 600000

Expand Down
1 change: 1 addition & 0 deletions docker/brew.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ RUN chmod +x $YARN
COPY $EXTERNAL_SOURCE_NESTED/package.json $EXTERNAL_SOURCE_NESTED/yarn.lock ./
COPY $EXTERNAL_SOURCE_NESTED/packages/app/package.json ./packages/app/package.json
COPY $EXTERNAL_SOURCE_NESTED/packages/backend/package.json ./packages/backend/package.json
COPY $EXTERNAL_SOURCE_NESTED/plugins/scalprum-backend/package.json ./plugins/scalprum-backend/package.json

# Downstream only - debugging
# COPY $REMOTE_SOURCES/ ./
Expand Down
3 changes: 2 additions & 1 deletion packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@
"@roadiehq/backstage-plugin-argo-cd-backend": "2.11.3",
"@roadiehq/scaffolder-backend-argocd": "1.1.17",
"@roadiehq/scaffolder-backend-module-utils": "1.10.4",
"@backstage/backend-plugin-manager": "npm:@janus-idp/backend-plugin-manager@0.0.4-janus.0",
"@internal/plugin-scalprum-backend": "^0.1.0",
"@backstage/backend-plugin-manager": "npm:@janus-idp/backend-plugin-manager@0.0.5-janus.0",
"@backstage/plugin-events-backend": "0.2.14",
"@backstage/plugin-events-node": "0.2.14",
"app": "*",
Expand Down
12 changes: 12 additions & 0 deletions packages/backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
LegacyPluginEnvironment as PluginEnvironment,
} from '@backstage/backend-plugin-manager';
import { DefaultEventBroker } from '@backstage/plugin-events-backend';
import { createRouter as scalprumRouter } from '@internal/plugin-scalprum-backend';

// TODO(davidfestal): The following import is a temporary workaround for a bug
// in the upstream @backstage/backend-plugin-manager package.
Expand Down Expand Up @@ -180,6 +181,17 @@ async function main() {

const apiRouter = Router();

// Scalprum frontend plugins provider
const scalprumEmv = useHotMemoize(module, () => createEnv('scalprum'));
apiRouter.use(
'/scalprum',
await scalprumRouter({
logger: scalprumEmv.logger,
pluginManager,
discovery: scalprumEmv.discovery,
}),
);

// Required plugins
await addPlugin({ plugin: 'proxy', apiRouter, createEnv, router: proxy });
await addPlugin({ plugin: 'auth', apiRouter, createEnv, router: auth });
Expand Down
1 change: 1 addition & 0 deletions plugins/scalprum-backend/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
14 changes: 14 additions & 0 deletions plugins/scalprum-backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# scalprum

Welcome to the scalprum backend plugin!

This plugin depends on the `backend-plugin-manager` and
provides access to the scalprum manifest, as well as plugin assets,
of every dynamic frontend plugin installed in the dynamic plugins root folder.

It is used by the dynamic frontend plugin support in the frontend showcase application.

## Getting started

Your plugin has been added to the backend app in this repository, meaning you'll be able to access it by running `yarn
start-backend` in the root directory, and then navigating to [/api/scalprum](http://localhost:7007/api/scalprum).
47 changes: 47 additions & 0 deletions plugins/scalprum-backend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "@internal/plugin-scalprum-backend",
"version": "0.1.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": true,
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
},
"backstage": {
"role": "backend-plugin"
},
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"clean": "backstage-cli package clean",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack"
},
"dependencies": {
"@backstage/backend-common": "^0.19.7",
"@backstage/backend-plugin-api": "^0.6.5",
"@backstage/backend-plugin-manager": "npm:@janus-idp/backend-plugin-manager@0.0.5-janus.0",
"@backstage/config": "^1.1.0",
"@types/express": "*",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"node-fetch": "^2.6.7",
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "0.22.13",
"@types/supertest": "^2.0.12",
"@types/mock-fs": "^4.13.0",
"mock-fs": "^5.1.10",
"msw": "^1.0.0",
"supertest": "^6.2.4"
},
"files": [
"dist"
]
}
1 change: 1 addition & 0 deletions plugins/scalprum-backend/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './service/router';
212 changes: 212 additions & 0 deletions plugins/scalprum-backend/src/service/router.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import express, { Router } from 'express';
import request from 'supertest';
import url from 'url';
import path from 'path';
import { createRouter } from './router';
import {
PluginManager,
ScannedPluginManifest,
ScannedPluginPackage,
} from '@backstage/backend-plugin-manager';
import mockFs from 'mock-fs';
import { randomUUID } from 'crypto';
import { LoggerService } from '@backstage/backend-plugin-api';

describe('createRouter', () => {
let app: express.Express;
let router: Router;

beforeEach(() => {
app = express();
});

afterEach(() => {
jest.resetAllMocks();
mockFs.restore();
app.delete('scalprum');
});

type TestCase = {
name: string;
packageManifest: ScannedPluginManifest & { scalprum?: { name: string } };
pluginExternalBaseURL: string;
distScalprumDir?: any;
testedPluginsURL: string;
expectedPluginsStatusCode: number;
expectedPluginsBody: any;
testedManifestURL?: string;
expectedManifestStatusCode?: number;
expectedManifestBody?: any;
expectedWarning?: string;
};

it.each<TestCase>([
{
name: 'should add the frontend plugin in the scalprum plugin map',
packageManifest: {
name: 'frontend-dynamic-plugin-test',
version: '0.0.0',
backstage: {
role: 'frontend-plugin',
},
main: 'dist/index.cjs.js',
scalprum: {
name: 'scalprum-plugin',
},
},
pluginExternalBaseURL: 'http://localhost:3000',
distScalprumDir: mockFs.directory({
items: {
'plugin-manifest.json': mockFs.file({
content: JSON.stringify({
name: 'scalprum-plugin',
anotherField: 'anotherValue',
}),
}),
},
}),
testedPluginsURL: '/scalprum/plugins',
expectedPluginsStatusCode: 200,
expectedPluginsBody: {
'scalprum-plugin': {
manifestLocation:
'http://localhost:3000/scalprum-plugin/plugin-manifest.json',
name: 'scalprum-plugin',
},
},
testedManifestURL: '/scalprum/scalprum-plugin/plugin-manifest.json',
expectedManifestStatusCode: 200,
expectedManifestBody: {
name: 'scalprum-plugin',
anotherField: 'anotherValue',
},
},
{
name: 'should skip a frontend plugin when the dist-scalprum sub-folder is missing',
packageManifest: {
name: 'frontend-dynamic-plugin-test',
version: '0.0.0',
backstage: {
role: 'frontend-plugin',
},
main: 'dist/index.cjs.js',
scalprum: {
name: 'scalprum-plugin',
},
},
pluginExternalBaseURL: 'http://localhost:3000',
testedPluginsURL: '/scalprum/plugins',
expectedPluginsStatusCode: 200,
expectedPluginsBody: {},
expectedWarning:
"Could not find 'scalprum-dist' folder for plugin frontend-dynamic-plugin-test@0.0.0",
},
{
name: 'should skip a frontend plugin when the scalprum entry is missing',
packageManifest: {
name: 'frontend-dynamic-plugin-test',
version: '0.0.0',
backstage: {
role: 'frontend-plugin',
},
main: 'dist/index.cjs.js',
},
pluginExternalBaseURL: 'http://localhost:3000',
distScalprumDir: mockFs.directory({}),
testedPluginsURL: '/scalprum/plugins',
expectedPluginsStatusCode: 200,
expectedPluginsBody: {},
expectedWarning:
'Could not find scalprum entry for plugin frontend-dynamic-plugin-test@0.0.0',
},
{
name: 'should skip a frontend plugin when the dist-scalprum/plugin-manifest.json file is missing',
packageManifest: {
name: 'frontend-dynamic-plugin-test',
version: '0.0.0',
backstage: {
role: 'frontend-plugin',
},
main: 'dist/index.cjs.js',
scalprum: {
name: 'scalprum-plugin',
},
},
pluginExternalBaseURL: 'http://localhost:3000',
distScalprumDir: mockFs.directory({}),
testedPluginsURL: '/scalprum/plugins',
expectedPluginsStatusCode: 200,
expectedPluginsBody: {},
expectedWarning:
"Could not find 'dist-scalprum/plugin-manifest.json' for plugin frontend-dynamic-plugin-test@0.0.0",
},
])('$name', async (tc: TestCase): Promise<void> => {
const plugin: ScannedPluginPackage = {
location: url.pathToFileURL(
path.resolve(`/node_modules/jest-tests/${randomUUID()}`),
),
manifest: tc.packageManifest,
};

const mockedFiles: { [key: string]: any } = {};

if (tc.distScalprumDir) {
mockedFiles[
path.join(url.fileURLToPath(plugin.location), 'dist-scalprum')
] = tc.distScalprumDir;
}

mockFs(mockedFiles);

const warn = jest.fn();
const logger: LoggerService = {
error: jest.fn(),
warn: warn,
info: jest.fn(),
debug: jest.fn(),
child: jest.fn(),
};

const pluginManager = new (PluginManager as any)(logger, [plugin], {
logger,
async bootstrap(_: string, __: string[]): Promise<void> {},
load: async (packagePath: string) =>
await import(/* webpackIgnore: true */ packagePath),
});
pluginManager.plugins.push(...(await pluginManager.loadPlugins()));

const getBaseUrl = jest.fn().mockReturnValue('should-not-be-used');
const getExternalBaseUrl = jest
.fn()
.mockReturnValue(tc.pluginExternalBaseURL);

router = await createRouter({
logger: logger,
discovery: {
getBaseUrl,
getExternalBaseUrl,
},
pluginManager,
});

app.use('/scalprum', router);
const response = await request(app).get(tc.testedPluginsURL);
expect(response.status).toEqual(tc.expectedPluginsStatusCode);
expect(response.body).toEqual(tc.expectedPluginsBody);

let manifestStatusCode: number | undefined;
let manifestBody: any | undefined;
if (tc.testedManifestURL) {
const manifestResponse = await request(app).get(tc.testedManifestURL);
manifestStatusCode = manifestResponse.status;
manifestBody = manifestResponse.body;
}
expect(manifestStatusCode).toEqual(tc.expectedManifestStatusCode);
expect(manifestBody).toEqual(tc.expectedManifestBody);

if (tc.expectedWarning) {
// eslint-disable-next-line jest/no-conditional-expect
expect(warn).toHaveBeenCalledWith(tc.expectedWarning);
}
});
});
Loading

0 comments on commit 4af2b85

Please sign in to comment.