backend-app-api: validate extension point deps

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-08-12 13:58:08 +02:00
parent b7e0362496
commit 57a10c6c69
3 changed files with 53 additions and 7 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-app-api': patch
---
Add validation to make sure that extension points do not cross plugin boundaries.
@@ -294,4 +294,36 @@ describe('BackendInitializer', () => {
"Circular dependency detected for modules of plugin 'test', 'modA' -> 'modB' -> 'modA'",
);
});
it('should reject modules that depend on extension points other plugins', async () => {
const init = new BackendInitializer(new ServiceRegistry(baseFactories));
const extA = createExtensionPoint<string>({ id: 'a' });
init.add(
createBackendPlugin({
pluginId: 'testA',
register(reg) {
reg.registerExtensionPoint(extA, 'a');
reg.registerInit({
deps: {},
async init() {},
});
},
})(),
);
init.add(
createBackendModule({
pluginId: 'testB',
moduleId: 'mod',
register(reg) {
reg.registerInit({
deps: { ext: extA },
async init() {},
});
},
})(),
);
await expect(init.start()).rejects.toThrow(
"Extension point registered for plugin 'testA' may not be used by module for plugin 'testB'",
);
});
});
@@ -42,7 +42,10 @@ export interface BackendRegisterInit {
export class BackendInitializer {
#startPromise?: Promise<void>;
#features = new Array<InternalBackendFeature>();
#extensionPoints = new Map<ExtensionPoint<unknown>, unknown>();
#extensionPoints = new Map<
ExtensionPoint<unknown>,
{ impl: unknown; pluginId: string }
>();
#serviceHolder: EnumerableServiceHolder;
constructor(serviceHolder: EnumerableServiceHolder) {
@@ -57,11 +60,14 @@ export class BackendInitializer {
const missingRefs = new Set<ServiceOrExtensionPoint>();
for (const [name, ref] of Object.entries(deps)) {
const extensionPoint = this.#extensionPoints.get(
ref as ExtensionPoint<unknown>,
);
if (extensionPoint) {
result.set(name, extensionPoint);
const ep = this.#extensionPoints.get(ref as ExtensionPoint<unknown>);
if (ep) {
if (ep.pluginId !== pluginId) {
throw new Error(
`Extension point registered for plugin '${ep.pluginId}' may not be used by module for plugin '${pluginId}'`,
);
}
result.set(name, ep.impl);
} else {
const impl = await this.#serviceHolder.get(
ref as ServiceRef<unknown>,
@@ -169,7 +175,10 @@ export class BackendInitializer {
`ExtensionPoint with ID '${extRef.id}' is already registered`,
);
}
this.#extensionPoints.set(extRef, extImpl);
this.#extensionPoints.set(extRef, {
impl: extImpl,
pluginId: r.pluginId,
});
provides.add(extRef);
}
}