make async and readonly
Signed-off-by: aramissennyeydd <aramis.sennyey@doordash.com>
This commit is contained in:
committed by
Fredrik Adelöw
parent
92f5823498
commit
6cf2ba39d9
@@ -1093,7 +1093,9 @@ describe('BackendInitializer', () => {
|
||||
instanceMetadata: coreServices.instanceMetadata,
|
||||
},
|
||||
async init({ instanceMetadata }) {
|
||||
expect(instanceMetadata.getInstalledPlugins()).toEqual([
|
||||
await expect(
|
||||
instanceMetadata.getInstalledPlugins(),
|
||||
).resolves.toEqual([
|
||||
{
|
||||
pluginId: 'test',
|
||||
modules: [
|
||||
@@ -1127,6 +1129,29 @@ describe('BackendInitializer', () => {
|
||||
await backend.start();
|
||||
});
|
||||
|
||||
it('should prevent writes to the instance metadata service', async () => {
|
||||
expect.assertions(1);
|
||||
const backend = new BackendInitializer(baseFactories);
|
||||
const plugin = createBackendPlugin({
|
||||
pluginId: 'test',
|
||||
register(reg) {
|
||||
reg.registerInit({
|
||||
deps: {
|
||||
instanceMetadata: coreServices.instanceMetadata,
|
||||
},
|
||||
async init({ instanceMetadata }) {
|
||||
const plugins = await instanceMetadata.getInstalledPlugins();
|
||||
await expect(() => {
|
||||
(plugins[0] as any).pluginId = 'foo';
|
||||
}).toThrow(/Cannot assign to read only property/);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
backend.add(plugin);
|
||||
await backend.start();
|
||||
});
|
||||
|
||||
it('should properly wait for all modules that consume an extension point to really finish, before starting the module that provides that extension point', async () => {
|
||||
expect.assertions(3);
|
||||
const backend = new BackendInitializer(baseFactories);
|
||||
|
||||
@@ -39,7 +39,7 @@ import { ForwardedError, ConflictError, assertError } from '@backstage/errors';
|
||||
import { DependencyGraph } from '../lib/DependencyGraph';
|
||||
import { ServiceRegistry } from './ServiceRegistry';
|
||||
import { createInitializationLogger } from './createInitializationLogger';
|
||||
import { unwrapFeature } from './helpers';
|
||||
import { deepFreeze, unwrapFeature } from './helpers';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import type { InstanceMetadataServicePluginInfo } from '../../../backend-plugin-api/src/services/definitions/InstanceMetadataService';
|
||||
|
||||
@@ -135,13 +135,17 @@ function createInstanceMetadataServiceFactory(
|
||||
logger: coreServices.rootLogger,
|
||||
},
|
||||
factory: async ({ logger }) => {
|
||||
const readonlyInstalledPlugins = deepFreeze(
|
||||
Object.values(installedPlugins),
|
||||
);
|
||||
const instanceMetadata = {
|
||||
getInstalledPlugins: () => Object.values(installedPlugins),
|
||||
getInstalledPlugins: () => Promise.resolve(readonlyInstalledPlugins),
|
||||
};
|
||||
|
||||
const plugins = await instanceMetadata.getInstalledPlugins();
|
||||
|
||||
logger.info(
|
||||
`Installed plugins on this instance: ${instanceMetadata
|
||||
.getInstalledPlugins()
|
||||
`Installed plugins on this instance: ${plugins
|
||||
.map(p => p.pluginId)
|
||||
.join(', ')}`,
|
||||
);
|
||||
|
||||
@@ -34,3 +34,29 @@ export function unwrapFeature(
|
||||
|
||||
return feature;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export type DeepReadonly<T> = {
|
||||
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
|
||||
};
|
||||
|
||||
/**
|
||||
* Deeply freezes an object by recursively freezing all of its properties.
|
||||
*
|
||||
* - https://gist.github.com/tkrotoff/e997cd6ff8d6cf6e51e6bb6146407fc3
|
||||
* - https://stackoverflow.com/a/69656011
|
||||
*
|
||||
* FIXME Should be part of Lodash and related: https://github.com/Maggi64/moderndash/issues/139
|
||||
*
|
||||
* Does not work with Set and Map: https://stackoverflow.com/q/31509175
|
||||
*/
|
||||
export function deepFreeze<
|
||||
T,
|
||||
// Can cause: "Type instantiation is excessively deep and possibly infinite."
|
||||
>(obj: T) {
|
||||
// @ts-expect-error
|
||||
Object.values(obj).forEach(
|
||||
value => Object.isFrozen(value) || deepFreeze(value),
|
||||
);
|
||||
return Object.freeze(obj) as DeepReadonly<T>;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,14 @@ export interface InstanceMetadataServicePluginInfo {
|
||||
}[];
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export type DeepReadonly<T> = {
|
||||
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export interface InstanceMetadataService {
|
||||
getInstalledPlugins: () => readonly InstanceMetadataServicePluginInfo[];
|
||||
getInstalledPlugins: () => Promise<
|
||||
ReadonlyArray<DeepReadonly<InstanceMetadataServicePluginInfo>>
|
||||
>;
|
||||
}
|
||||
|
||||
@@ -38,11 +38,11 @@ export const gatewayPlugin = createBackendPlugin({
|
||||
async init({ logger, discovery, instanceMeta, rootHttpRouter }) {
|
||||
rootHttpRouter.use(
|
||||
'/api/:pluginId',
|
||||
createRouter({
|
||||
(await createRouter({
|
||||
discovery,
|
||||
instanceMeta,
|
||||
logger,
|
||||
}) as Handler,
|
||||
})) as Handler,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -23,7 +23,7 @@ import { createProxyMiddleware } from 'http-proxy-middleware';
|
||||
import { context } from '@opentelemetry/api';
|
||||
import { getRPCMetadata } from '@opentelemetry/core';
|
||||
|
||||
export function createRouter({
|
||||
export async function createRouter({
|
||||
discovery,
|
||||
instanceMeta,
|
||||
}: {
|
||||
@@ -31,9 +31,8 @@ export function createRouter({
|
||||
instanceMeta: InstanceMetadataService;
|
||||
logger: LoggerService;
|
||||
}) {
|
||||
const localPluginIds = new Set(
|
||||
instanceMeta.getInstalledPlugins().map(f => f.pluginId),
|
||||
);
|
||||
const plugins = await instanceMeta.getInstalledPlugins();
|
||||
const localPluginIds = new Set(plugins.map(f => f.pluginId));
|
||||
|
||||
const proxy = createProxyMiddleware({
|
||||
changeOrigin: true,
|
||||
|
||||
Reference in New Issue
Block a user