From 3538d9ad2c4d19bef31d4280c56f27d0fed66227 Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Thu, 2 Mar 2023 16:19:39 +0000 Subject: [PATCH 1/9] tweak exports to allow decoupling of backend plugins Signed-off-by: Brian Fletcher --- .changeset/tall-meals-dress.md | 5 +++++ packages/backend/src/index.ts | 15 ++++++--------- packages/backend/src/plugins/catalog.ts | 7 ++++--- .../src/plugins/catalogEventBasedProviders.ts | 3 ++- packages/backend/src/plugins/events.ts | 4 +--- packages/backend/src/types.ts | 2 ++ plugins/events-backend/api-report.md | 12 ++++++++++++ plugins/events-backend/src/index.ts | 1 + .../src/service/InMemoryEventBroker.ts | 2 ++ .../service/http/HttpPostIngressEventPublisher.ts | 2 +- 10 files changed, 36 insertions(+), 17 deletions(-) create mode 100644 .changeset/tall-meals-dress.md diff --git a/.changeset/tall-meals-dress.md b/.changeset/tall-meals-dress.md new file mode 100644 index 0000000000..42d50444aa --- /dev/null +++ b/.changeset/tall-meals-dress.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-events-backend': patch +--- + +Export `InMemoryEventBroker` to allow decoupling of the catalog and events backends in the `example-backend`. diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 011be941cf..3c07c3aad4 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -42,7 +42,6 @@ import { metricsInit, metricsHandler } from './metrics'; import auth from './plugins/auth'; import azureDevOps from './plugins/azure-devops'; import catalog from './plugins/catalog'; -import catalogEventBasedProviders from './plugins/catalogEventBasedProviders'; import codeCoverage from './plugins/codecoverage'; import entityFeedback from './plugins/entityFeedback'; import events from './plugins/events'; @@ -68,6 +67,7 @@ import linguist from './plugins/linguist'; import { PluginEnvironment } from './types'; import { ServerPermissionClient } from '@backstage/plugin-permission-node'; import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; +import { InMemoryEventBroker } from '@backstage/plugin-events-backend'; function makeCreateEnv(config: Config) { const root = getRootLogger(); @@ -85,6 +85,8 @@ function makeCreateEnv(config: Config) { discovery, }); + const eventBroker = new InMemoryEventBroker(root.child({ type: 'plugin' })); + root.info(`Created UrlReader ${reader}`); return (plugin: string): PluginEnvironment => { @@ -99,6 +101,7 @@ function makeCreateEnv(config: Config) { database, config, reader, + eventBroker, discovery, tokenManager, permissions, @@ -156,18 +159,12 @@ async function main() { const exploreEnv = useHotMemoize(module, () => createEnv('explore')); const lighthouseEnv = useHotMemoize(module, () => createEnv('lighthouse')); - const eventBasedEntityProviders = await catalogEventBasedProviders( - catalogEnv, - ); const linguistEnv = useHotMemoize(module, () => createEnv('linguist')); const apiRouter = Router(); - apiRouter.use( - '/catalog', - await catalog(catalogEnv, eventBasedEntityProviders), - ); + apiRouter.use('/catalog', await catalog(catalogEnv)); apiRouter.use('/code-coverage', await codeCoverage(codeCoverageEnv)); - apiRouter.use('/events', await events(eventsEnv, eventBasedEntityProviders)); + apiRouter.use('/events', await events(eventsEnv)); apiRouter.use('/rollbar', await rollbar(rollbarEnv)); apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); apiRouter.use('/tech-insights', await techInsights(techInsightsEnv)); diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts index f6fe25f86a..cd1a92ad7b 100644 --- a/packages/backend/src/plugins/catalog.ts +++ b/packages/backend/src/plugins/catalog.ts @@ -15,18 +15,19 @@ */ import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; -import { EntityProvider } from '@backstage/plugin-catalog-node'; import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; import { Router } from 'express'; import { PluginEnvironment } from '../types'; +import createCatalogEventBasedProviders from './catalogEventBasedProviders'; export default async function createPlugin( env: PluginEnvironment, - providers?: Array, ): Promise { + const providers = await createCatalogEventBasedProviders(env); const builder = await CatalogBuilder.create(env); builder.addProcessor(new ScaffolderEntitiesProcessor()); - builder.addEntityProvider(providers ?? []); + env.eventBroker.subscribe(providers); + builder.addEntityProvider(providers); const { processingEngine, router } = await builder.build(); await processingEngine.start(); return router; diff --git a/packages/backend/src/plugins/catalogEventBasedProviders.ts b/packages/backend/src/plugins/catalogEventBasedProviders.ts index 346ed40fa6..3f1ea708df 100644 --- a/packages/backend/src/plugins/catalogEventBasedProviders.ts +++ b/packages/backend/src/plugins/catalogEventBasedProviders.ts @@ -29,10 +29,11 @@ class DemoEventBasedEntityProvider implements EntityProvider, EventSubscriber { ) {} async onEvent(params: EventParams): Promise { + const request = params.eventPayload as Request; this.logger.info( `onEvent: topic=${params.topic}, metadata=${JSON.stringify( params.metadata, - )}, payload=${JSON.stringify(params.eventPayload)}`, + )}, payload=${JSON.stringify(request.body)}`, ); } diff --git a/packages/backend/src/plugins/events.ts b/packages/backend/src/plugins/events.ts index 7427d34217..f3ff354240 100644 --- a/packages/backend/src/plugins/events.ts +++ b/packages/backend/src/plugins/events.ts @@ -18,13 +18,11 @@ import { EventsBackend, HttpPostIngressEventPublisher, } from '@backstage/plugin-events-backend'; -import { EventSubscriber } from '@backstage/plugin-events-node'; import { Router } from 'express'; import { PluginEnvironment } from '../types'; export default async function createPlugin( env: PluginEnvironment, - subscribers: EventSubscriber[], ): Promise { const eventsRouter = Router(); @@ -35,8 +33,8 @@ export default async function createPlugin( http.bind(eventsRouter); await new EventsBackend(env.logger) + .setEventBroker(env.eventBroker) .addPublishers(http) - .addSubscribers(subscribers) .start(); return eventsRouter; diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 895581c702..ab1baf0c95 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -26,6 +26,7 @@ import { import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { IdentityApi } from '@backstage/plugin-auth-node'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; +import { EventBroker } from '@backstage/plugin-events-node'; export type PluginEnvironment = { logger: Logger; @@ -38,4 +39,5 @@ export type PluginEnvironment = { permissions: PermissionEvaluator; scheduler: PluginTaskScheduler; identity: IdentityApi; + eventBroker: EventBroker; }; diff --git a/plugins/events-backend/api-report.md b/plugins/events-backend/api-report.md index 7d8ede1979..5d520f10c2 100644 --- a/plugins/events-backend/api-report.md +++ b/plugins/events-backend/api-report.md @@ -5,6 +5,7 @@ ```ts import { Config } from '@backstage/config'; import { EventBroker } from '@backstage/plugin-events-node'; +import { EventParams } from '@backstage/plugin-events-node'; import { EventPublisher } from '@backstage/plugin-events-node'; import { EventSubscriber } from '@backstage/plugin-events-node'; import express from 'express'; @@ -42,4 +43,15 @@ export class HttpPostIngressEventPublisher implements EventPublisher { // (undocumented) setEventBroker(eventBroker: EventBroker): Promise; } + +// @public +export class InMemoryEventBroker implements EventBroker { + constructor(logger: Logger); + // (undocumented) + publish(params: EventParams): Promise; + // (undocumented) + subscribe( + ...subscribers: Array> + ): void; +} ``` diff --git a/plugins/events-backend/src/index.ts b/plugins/events-backend/src/index.ts index 4645e9a479..d443eeabca 100644 --- a/plugins/events-backend/src/index.ts +++ b/plugins/events-backend/src/index.ts @@ -22,3 +22,4 @@ export { EventsBackend } from './service/EventsBackend'; export { HttpPostIngressEventPublisher } from './service/http'; +export { InMemoryEventBroker } from './service/InMemoryEventBroker'; diff --git a/plugins/events-backend/src/service/InMemoryEventBroker.ts b/plugins/events-backend/src/service/InMemoryEventBroker.ts index 04be01f658..d800d88dc5 100644 --- a/plugins/events-backend/src/service/InMemoryEventBroker.ts +++ b/plugins/events-backend/src/service/InMemoryEventBroker.ts @@ -25,6 +25,8 @@ import { Logger } from 'winston'; * In-memory event broker which will pass the event to all registered subscribers * interested in it. * Events will not be persisted in any form. + * + * @public */ // TODO(pjungermann): add prom metrics? (see plugins/catalog-backend/src/util/metrics.ts, etc.) export class InMemoryEventBroker implements EventBroker { diff --git a/plugins/events-backend/src/service/http/HttpPostIngressEventPublisher.ts b/plugins/events-backend/src/service/http/HttpPostIngressEventPublisher.ts index b5a6ccbca1..54dead0afa 100644 --- a/plugins/events-backend/src/service/http/HttpPostIngressEventPublisher.ts +++ b/plugins/events-backend/src/service/http/HttpPostIngressEventPublisher.ts @@ -107,7 +107,7 @@ export class HttpPostIngressEventPublisher implements EventPublisher { return; } - const eventPayload = request.body; + const eventPayload = request; await this.eventBroker!.publish({ topic, eventPayload, From 6be62344a07f7670cc3eee0d323c55ec6204bfb6 Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Thu, 2 Mar 2023 16:31:09 +0000 Subject: [PATCH 2/9] fix Signed-off-by: Brian Fletcher --- packages/backend/src/plugins/catalogEventBasedProviders.ts | 3 +-- .../src/service/http/HttpPostIngressEventPublisher.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/plugins/catalogEventBasedProviders.ts b/packages/backend/src/plugins/catalogEventBasedProviders.ts index 3f1ea708df..346ed40fa6 100644 --- a/packages/backend/src/plugins/catalogEventBasedProviders.ts +++ b/packages/backend/src/plugins/catalogEventBasedProviders.ts @@ -29,11 +29,10 @@ class DemoEventBasedEntityProvider implements EntityProvider, EventSubscriber { ) {} async onEvent(params: EventParams): Promise { - const request = params.eventPayload as Request; this.logger.info( `onEvent: topic=${params.topic}, metadata=${JSON.stringify( params.metadata, - )}, payload=${JSON.stringify(request.body)}`, + )}, payload=${JSON.stringify(params.eventPayload)}`, ); } diff --git a/plugins/events-backend/src/service/http/HttpPostIngressEventPublisher.ts b/plugins/events-backend/src/service/http/HttpPostIngressEventPublisher.ts index 54dead0afa..b5a6ccbca1 100644 --- a/plugins/events-backend/src/service/http/HttpPostIngressEventPublisher.ts +++ b/plugins/events-backend/src/service/http/HttpPostIngressEventPublisher.ts @@ -107,7 +107,7 @@ export class HttpPostIngressEventPublisher implements EventPublisher { return; } - const eventPayload = request; + const eventPayload = request.body; await this.eventBroker!.publish({ topic, eventPayload, From 90ead6f213d235abf09f8eb66aa56c48c89e0096 Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Wed, 8 Mar 2023 09:45:17 +0000 Subject: [PATCH 3/9] addressing code review comments Signed-off-by: Brian Fletcher --- ...dProviders.ts => DemoEventBasedEntityProvider.ts} | 6 ++++-- packages/backend/src/plugins/catalog.ts | 12 ++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) rename packages/backend/src/plugins/{catalogEventBasedProviders.ts => DemoEventBasedEntityProvider.ts} (91%) diff --git a/packages/backend/src/plugins/catalogEventBasedProviders.ts b/packages/backend/src/plugins/DemoEventBasedEntityProvider.ts similarity index 91% rename from packages/backend/src/plugins/catalogEventBasedProviders.ts rename to packages/backend/src/plugins/DemoEventBasedEntityProvider.ts index 346ed40fa6..57f9f05570 100644 --- a/packages/backend/src/plugins/catalogEventBasedProviders.ts +++ b/packages/backend/src/plugins/DemoEventBasedEntityProvider.ts @@ -22,7 +22,9 @@ import { EventParams, EventSubscriber } from '@backstage/plugin-events-node'; import { Logger } from 'winston'; import { PluginEnvironment } from '../types'; -class DemoEventBasedEntityProvider implements EntityProvider, EventSubscriber { +export class DemoEventBasedEntityProvider + implements EntityProvider, EventSubscriber +{ constructor( private readonly logger: Logger, private readonly topics: string[], @@ -55,7 +57,7 @@ export default async function createCatalogEventBasedProviders( const providers: Array< (EntityProvider & EventSubscriber) | Array > = []; - providers.push(new DemoEventBasedEntityProvider(env.logger, ['example'])); + providers.push(); // add your event-based entity providers here return providers.flat(); } diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts index cd1a92ad7b..d895b0e85a 100644 --- a/packages/backend/src/plugins/catalog.ts +++ b/packages/backend/src/plugins/catalog.ts @@ -18,16 +18,20 @@ import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; import { Router } from 'express'; import { PluginEnvironment } from '../types'; -import createCatalogEventBasedProviders from './catalogEventBasedProviders'; +import { DemoEventBasedEntityProvider } from './DemoEventBasedEntityProvider'; export default async function createPlugin( env: PluginEnvironment, ): Promise { - const providers = await createCatalogEventBasedProviders(env); + const { eventBroker, logger } = env; + const builder = await CatalogBuilder.create(env); builder.addProcessor(new ScaffolderEntitiesProcessor()); - env.eventBroker.subscribe(providers); - builder.addEntityProvider(providers); + + const demoProvider = new DemoEventBasedEntityProvider(logger, ['example']); + eventBroker.subscribe(demoProvider); + builder.addEntityProvider(demoProvider); + const { processingEngine, router } = await builder.build(); await processingEngine.start(); return router; From c251ef8007d021c1ab060fd143b59ab78595966d Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Wed, 8 Mar 2023 10:30:20 +0000 Subject: [PATCH 4/9] update readme to indicate changes Signed-off-by: Brian Fletcher --- .../plugins/DemoEventBasedEntityProvider.ts | 12 ------- plugins/events-backend/README.md | 34 ++++++++++++------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/packages/backend/src/plugins/DemoEventBasedEntityProvider.ts b/packages/backend/src/plugins/DemoEventBasedEntityProvider.ts index 57f9f05570..198349cb06 100644 --- a/packages/backend/src/plugins/DemoEventBasedEntityProvider.ts +++ b/packages/backend/src/plugins/DemoEventBasedEntityProvider.ts @@ -20,7 +20,6 @@ import { } from '@backstage/plugin-catalog-node'; import { EventParams, EventSubscriber } from '@backstage/plugin-events-node'; import { Logger } from 'winston'; -import { PluginEnvironment } from '../types'; export class DemoEventBasedEntityProvider implements EntityProvider, EventSubscriber @@ -50,14 +49,3 @@ export class DemoEventBasedEntityProvider return DemoEventBasedEntityProvider.name; } } - -export default async function createCatalogEventBasedProviders( - env: PluginEnvironment, -): Promise> { - const providers: Array< - (EntityProvider & EventSubscriber) | Array - > = []; - providers.push(); - // add your event-based entity providers here - return providers.flat(); -} diff --git a/plugins/events-backend/README.md b/plugins/events-backend/README.md index cef8ac7330..869ee85ea2 100644 --- a/plugins/events-backend/README.md +++ b/plugins/events-backend/README.md @@ -24,6 +24,20 @@ to the used event broker. yarn add --cwd packages/backend @backstage/plugin-events-backend ``` +You will need to add the following to the backend configuration `#makeCreateEnv`. + +```diff +// packages/backend/src/index.ts ++ const eventBroker = new InMemoryEventBroker(root.child({ type: 'plugin' })); +``` + +Then update plugin environment to include the event broker. + +```diff +// packages/backend/src/types.ts ++ eventBroker: EventBroker; +``` + Add a file [`packages/backend/src/plugins/events.ts`](../../packages/backend/src/plugins/events.ts) to your Backstage project. @@ -38,25 +52,20 @@ Additionally, add the events plugin to your backend. // [...] + const eventsEnv = useHotMemoize(module, () => createEnv('events')); // [...] -+ apiRouter.use('/events', await events(eventsEnv, [])); ++ apiRouter.use('/events', await events(eventsEnv)); // [...] ``` ### With Event-based Entity Providers In case you use event-based `EntityProviders`, -you may need something like the following: +you will need to add the subscription to the catalog plugin creation: ```diff -// packages/backend/src/index.ts -- apiRouter.use('/events', await events(eventsEnv, [])); -+ apiRouter.use('/events', await events(eventsEnv, eventBasedEntityProviders)); +// packages/backend/src/plugins/catalog.ts ++ ``` -as well as a file -[`packages/backend/src/plugins/catalogEventBasedProviders.ts`](../../packages/backend/src/plugins/catalogEventBasedProviders.ts) -which contains event-based entity providers. - In case you don't have this dependency added yet: ```bash @@ -67,18 +76,19 @@ yarn add --cwd packages/backend @backstage/plugin-events-backend ```diff // packages/backend/src/plugins/catalog.ts import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; -+import { EntityProvider } from '@backstage/plugin-catalog-node'; ++import { DemoEventBasedEntityProvider } from './DemoEventBasedEntityProvider'; import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; import { Router } from 'express'; import { PluginEnvironment } from '../types'; export default async function createPlugin( env: PluginEnvironment, -+ providers?: Array, ): Promise { const builder = await CatalogBuilder.create(env); builder.addProcessor(new ScaffolderEntitiesProcessor()); -+ builder.addEntityProvider(providers ?? []); ++ const demoProvider = new DemoEventBasedEntityProvider(logger, ['example']); ++ env.eventBroker.subscribe(demoProvider); ++ builder.addEntityProvider(demoProvider); const { processingEngine, router } = await builder.build(); await processingEngine.start(); return router; From a444c53a033f20b6563566cf46b531357b35fe9f Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Wed, 8 Mar 2023 15:58:40 +0000 Subject: [PATCH 5/9] address code review comments Signed-off-by: Brian Fletcher --- .changeset/tall-meals-dress.md | 4 +++- packages/backend/src/index.ts | 4 ++-- .../plugins/DemoEventBasedEntityProvider.ts | 23 +++++++++++++++---- packages/backend/src/plugins/catalog.ts | 9 ++++---- plugins/events-backend/README.md | 2 +- plugins/events-backend/api-report.md | 22 +++++++++--------- plugins/events-backend/src/index.ts | 2 +- ...ker.test.ts => DefaultEventBroker.test.ts} | 8 +++---- ...ryEventBroker.ts => DefaultEventBroker.ts} | 4 ++-- .../src/service/EventsBackend.ts | 4 ++-- .../src/service/EventsPlugin.ts | 4 ++-- 11 files changed, 51 insertions(+), 35 deletions(-) rename plugins/events-backend/src/service/{InMemoryEventBroker.test.ts => DefaultEventBroker.test.ts} (94%) rename plugins/events-backend/src/service/{InMemoryEventBroker.ts => DefaultEventBroker.ts} (93%) diff --git a/.changeset/tall-meals-dress.md b/.changeset/tall-meals-dress.md index 42d50444aa..25f67847ae 100644 --- a/.changeset/tall-meals-dress.md +++ b/.changeset/tall-meals-dress.md @@ -2,4 +2,6 @@ '@backstage/plugin-events-backend': patch --- -Export `InMemoryEventBroker` to allow decoupling of the catalog and events backends in the `example-backend`. +Export `DefaultEventBroker` to allow decoupling of the catalog and events backends in the `example-backend`. + +Please look at `plugins/events-backend/README.md` for the currently advised was to set up the event backend and catalog providers. diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 3c07c3aad4..593d30bc3b 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -67,7 +67,7 @@ import linguist from './plugins/linguist'; import { PluginEnvironment } from './types'; import { ServerPermissionClient } from '@backstage/plugin-permission-node'; import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; -import { InMemoryEventBroker } from '@backstage/plugin-events-backend'; +import { DefaultEventBroker } from '@backstage/plugin-events-backend'; function makeCreateEnv(config: Config) { const root = getRootLogger(); @@ -85,7 +85,7 @@ function makeCreateEnv(config: Config) { discovery, }); - const eventBroker = new InMemoryEventBroker(root.child({ type: 'plugin' })); + const eventBroker = new DefaultEventBroker(root.child({ type: 'plugin' })); root.info(`Created UrlReader ${reader}`); diff --git a/packages/backend/src/plugins/DemoEventBasedEntityProvider.ts b/packages/backend/src/plugins/DemoEventBasedEntityProvider.ts index 198349cb06..b793816095 100644 --- a/packages/backend/src/plugins/DemoEventBasedEntityProvider.ts +++ b/packages/backend/src/plugins/DemoEventBasedEntityProvider.ts @@ -18,16 +18,29 @@ import { EntityProvider, EntityProviderConnection, } from '@backstage/plugin-catalog-node'; -import { EventParams, EventSubscriber } from '@backstage/plugin-events-node'; +import { + EventBroker, + EventParams, + EventSubscriber, +} from '@backstage/plugin-events-node'; import { Logger } from 'winston'; export class DemoEventBasedEntityProvider implements EntityProvider, EventSubscriber { - constructor( - private readonly logger: Logger, - private readonly topics: string[], - ) {} + private readonly logger: Logger; + private readonly topics: string[]; + + constructor(opts: { + eventBroker: EventBroker; + logger: Logger; + topics: string[]; + }) { + const { eventBroker, logger, topics } = opts; + eventBroker.subscribe(this); + this.logger = logger; + this.topics = topics; + } async onEvent(params: EventParams): Promise { this.logger.info( diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts index d895b0e85a..ae00d5a7ec 100644 --- a/packages/backend/src/plugins/catalog.ts +++ b/packages/backend/src/plugins/catalog.ts @@ -23,13 +23,14 @@ import { DemoEventBasedEntityProvider } from './DemoEventBasedEntityProvider'; export default async function createPlugin( env: PluginEnvironment, ): Promise { - const { eventBroker, logger } = env; - const builder = await CatalogBuilder.create(env); builder.addProcessor(new ScaffolderEntitiesProcessor()); - const demoProvider = new DemoEventBasedEntityProvider(logger, ['example']); - eventBroker.subscribe(demoProvider); + const demoProvider = new DemoEventBasedEntityProvider({ + logger: env.logger, + topics: ['example'], + eventBroker: env.eventBroker, + }); builder.addEntityProvider(demoProvider); const { processingEngine, router } = await builder.build(); diff --git a/plugins/events-backend/README.md b/plugins/events-backend/README.md index 869ee85ea2..af9812eab7 100644 --- a/plugins/events-backend/README.md +++ b/plugins/events-backend/README.md @@ -28,7 +28,7 @@ You will need to add the following to the backend configuration `#makeCreateEnv` ```diff // packages/backend/src/index.ts -+ const eventBroker = new InMemoryEventBroker(root.child({ type: 'plugin' })); ++ const eventBroker = new DefaultEventBroker(root.child({ type: 'plugin' })); ``` Then update plugin environment to include the event broker. diff --git a/plugins/events-backend/api-report.md b/plugins/events-backend/api-report.md index 5d520f10c2..aeb6f9363d 100644 --- a/plugins/events-backend/api-report.md +++ b/plugins/events-backend/api-report.md @@ -12,6 +12,17 @@ import express from 'express'; import { HttpPostIngressOptions } from '@backstage/plugin-events-node'; import { Logger } from 'winston'; +// @public +export class DefaultEventBroker implements EventBroker { + constructor(logger: Logger); + // (undocumented) + publish(params: EventParams): Promise; + // (undocumented) + subscribe( + ...subscribers: Array> + ): void; +} + // @public export class EventsBackend { constructor(logger: Logger); @@ -43,15 +54,4 @@ export class HttpPostIngressEventPublisher implements EventPublisher { // (undocumented) setEventBroker(eventBroker: EventBroker): Promise; } - -// @public -export class InMemoryEventBroker implements EventBroker { - constructor(logger: Logger); - // (undocumented) - publish(params: EventParams): Promise; - // (undocumented) - subscribe( - ...subscribers: Array> - ): void; -} ``` diff --git a/plugins/events-backend/src/index.ts b/plugins/events-backend/src/index.ts index d443eeabca..be173b677c 100644 --- a/plugins/events-backend/src/index.ts +++ b/plugins/events-backend/src/index.ts @@ -22,4 +22,4 @@ export { EventsBackend } from './service/EventsBackend'; export { HttpPostIngressEventPublisher } from './service/http'; -export { InMemoryEventBroker } from './service/InMemoryEventBroker'; +export { DefaultEventBroker } from './service/DefaultEventBroker'; diff --git a/plugins/events-backend/src/service/InMemoryEventBroker.test.ts b/plugins/events-backend/src/service/DefaultEventBroker.test.ts similarity index 94% rename from plugins/events-backend/src/service/InMemoryEventBroker.test.ts rename to plugins/events-backend/src/service/DefaultEventBroker.test.ts index 861cdda940..99e5f6f72d 100644 --- a/plugins/events-backend/src/service/InMemoryEventBroker.test.ts +++ b/plugins/events-backend/src/service/DefaultEventBroker.test.ts @@ -17,15 +17,15 @@ import { getVoidLogger } from '@backstage/backend-common'; import { TestEventSubscriber } from '@backstage/plugin-events-backend-test-utils'; import { EventParams, EventSubscriber } from '@backstage/plugin-events-node'; -import { InMemoryEventBroker } from './InMemoryEventBroker'; +import { DefaultEventBroker } from './DefaultEventBroker'; const logger = getVoidLogger(); -describe('InMemoryEventBroker', () => { +describe('DefaultEventBroker', () => { it('passes events to interested subscribers', () => { const subscriber1 = new TestEventSubscriber('test1', ['topicA', 'topicB']); const subscriber2 = new TestEventSubscriber('test2', ['topicB', 'topicC']); - const eventBroker = new InMemoryEventBroker(logger); + const eventBroker = new DefaultEventBroker(logger); eventBroker.subscribe(subscriber1); eventBroker.subscribe(subscriber2); @@ -86,7 +86,7 @@ describe('InMemoryEventBroker', () => { })(); const errorSpy = jest.spyOn(logger, 'error'); - const eventBroker = new InMemoryEventBroker(logger); + const eventBroker = new DefaultEventBroker(logger); eventBroker.subscribe(subscriber1); await eventBroker.publish({ topic, eventPayload: '1' }); diff --git a/plugins/events-backend/src/service/InMemoryEventBroker.ts b/plugins/events-backend/src/service/DefaultEventBroker.ts similarity index 93% rename from plugins/events-backend/src/service/InMemoryEventBroker.ts rename to plugins/events-backend/src/service/DefaultEventBroker.ts index d800d88dc5..c3824b3e7d 100644 --- a/plugins/events-backend/src/service/InMemoryEventBroker.ts +++ b/plugins/events-backend/src/service/DefaultEventBroker.ts @@ -22,14 +22,14 @@ import { import { Logger } from 'winston'; /** - * In-memory event broker which will pass the event to all registered subscribers + * In process event broker which will pass the event to all registered subscribers * interested in it. * Events will not be persisted in any form. * * @public */ // TODO(pjungermann): add prom metrics? (see plugins/catalog-backend/src/util/metrics.ts, etc.) -export class InMemoryEventBroker implements EventBroker { +export class DefaultEventBroker implements EventBroker { constructor(private readonly logger: Logger) {} private readonly subscribers: { diff --git a/plugins/events-backend/src/service/EventsBackend.ts b/plugins/events-backend/src/service/EventsBackend.ts index 77b1b538f7..4415b8703a 100644 --- a/plugins/events-backend/src/service/EventsBackend.ts +++ b/plugins/events-backend/src/service/EventsBackend.ts @@ -20,7 +20,7 @@ import { EventSubscriber, } from '@backstage/plugin-events-node'; import { Logger } from 'winston'; -import { InMemoryEventBroker } from './InMemoryEventBroker'; +import { DefaultEventBroker } from './DefaultEventBroker'; /** * A builder that helps wire up all component parts of the event management. @@ -33,7 +33,7 @@ export class EventsBackend { private subscribers: EventSubscriber[] = []; constructor(logger: Logger) { - this.eventBroker = new InMemoryEventBroker(logger); + this.eventBroker = new DefaultEventBroker(logger); } setEventBroker(eventBroker: EventBroker): EventsBackend { diff --git a/plugins/events-backend/src/service/EventsPlugin.ts b/plugins/events-backend/src/service/EventsPlugin.ts index 1ae038a2ac..07f2b11731 100644 --- a/plugins/events-backend/src/service/EventsPlugin.ts +++ b/plugins/events-backend/src/service/EventsPlugin.ts @@ -29,7 +29,7 @@ import { EventSubscriber, HttpPostIngressOptions, } from '@backstage/plugin-events-node'; -import { InMemoryEventBroker } from './InMemoryEventBroker'; +import { DefaultEventBroker } from './DefaultEventBroker'; import Router from 'express-promise-router'; import { HttpPostIngressEventPublisher } from './http'; @@ -113,7 +113,7 @@ export const eventsPlugin = createBackendPlugin({ router.use(eventsRouter); const eventBroker = - extensionPoint.eventBroker ?? new InMemoryEventBroker(winstonLogger); + extensionPoint.eventBroker ?? new DefaultEventBroker(winstonLogger); eventBroker.subscribe(extensionPoint.subscribers); [extensionPoint.publishers, http] From ba8fa5e4e1743ae4176064c700a2f6ef71277869 Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Fri, 10 Mar 2023 13:28:05 +0000 Subject: [PATCH 6/9] fixing up the readme Signed-off-by: Brian Fletcher --- plugins/events-backend/README.md | 144 +++++++++++++++++++++---------- 1 file changed, 99 insertions(+), 45 deletions(-) diff --git a/plugins/events-backend/README.md b/plugins/events-backend/README.md index af9812eab7..2288c91f2e 100644 --- a/plugins/events-backend/README.md +++ b/plugins/events-backend/README.md @@ -6,7 +6,7 @@ This plugin provides the wiring of all extension points for managing events as defined by [plugin-events-node](../events-node) including backend plugin `EventsPlugin` and `EventsBackend`. -Additionally, it uses a simple in-memory implementation for +Additionally, it uses a simple in-process implementation for the `EventBroker` by default which you can replace with a more sophisticated implementation of your choice as you need (e.g., via module). @@ -24,7 +24,13 @@ to the used event broker. yarn add --cwd packages/backend @backstage/plugin-events-backend ``` -You will need to add the following to the backend configuration `#makeCreateEnv`. +### Event Broker + +First you will need to add and implementation of the `EventBroker` interface to the backend plugin environment. +This will allow event broker instance any backend plugins to publish and subscribe to events in order to communicate +between them. + +Add the following to `makeCreateEnv` ```diff // packages/backend/src/index.ts @@ -38,10 +44,69 @@ Then update plugin environment to include the event broker. + eventBroker: EventBroker; ``` -Add a file [`packages/backend/src/plugins/events.ts`](../../packages/backend/src/plugins/events.ts) -to your Backstage project. +### Publishing and Subscribing to events with the broker -There, you can add all publishers, subscribers, etc. you want. +Backend plugins are passed the event broker in the plugin environment at startup of the application. The plugin can +make use of this to communicate between parts of the application. + +Here is an example of a plugin publishing a payload to a topic. + +```typescript jsx +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + env.eventBroker.publish({ + topic: 'publish.example', + eventPayload: { message: 'Hello, World!' }, + metadata: {}, + }); +} +``` + +Here is an example of a plugin subscribing to a topic. + +```typescript jsx +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + env.eventBroker.subscribe([ + { + supportsEventTopics: ['publish.example'], + onEvent: async (params: EventParams) => { + env.logger.info(`receieved ${params.topic} event`); + }, + }, + ]); +} +``` + +### Implementing an `EventSubscriber` class + +More complex solutions might warrent the creation of a class that implements the `EventSubscriber` interface. e.g. + +```typescript jsx +import { EventSubscriber } from "./EventSubscriber"; + +class ExampleSubscriber implements EventSubscriber { + ... + + supportsEventTopics() { + return ['publish.example'] + } + + async onEvent(params: EventParams) { + env.logger.info(`receieved ${params.topic} event`) + } +} +``` + +### Events Backend + +The events backend plugin provides a router to handler http events and publish the http requests onto the event +broker. + +To configure it add a file [`packages/backend/src/plugins/events.ts`](../../packages/backend/src/plugins/events.ts) +to your Backstage project. Additionally, add the events plugin to your backend. @@ -56,46 +121,7 @@ Additionally, add the events plugin to your backend. // [...] ``` -### With Event-based Entity Providers - -In case you use event-based `EntityProviders`, -you will need to add the subscription to the catalog plugin creation: - -```diff -// packages/backend/src/plugins/catalog.ts -+ -``` - -In case you don't have this dependency added yet: - -```bash -# From your Backstage root directory -yarn add --cwd packages/backend @backstage/plugin-events-backend -``` - -```diff -// packages/backend/src/plugins/catalog.ts - import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; -+import { DemoEventBasedEntityProvider } from './DemoEventBasedEntityProvider'; - import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; - import { Router } from 'express'; - import { PluginEnvironment } from '../types'; - - export default async function createPlugin( - env: PluginEnvironment, - ): Promise { - const builder = await CatalogBuilder.create(env); - builder.addProcessor(new ScaffolderEntitiesProcessor()); -+ const demoProvider = new DemoEventBasedEntityProvider(logger, ['example']); -+ env.eventBroker.subscribe(demoProvider); -+ builder.addEntityProvider(demoProvider); - const { processingEngine, router } = await builder.build(); - await processingEngine.start(); - return router; - } -``` - -## Configuration +#### Configuration In order to create HTTP endpoints to receive events for a certain topic, you need to add them at your configuration: @@ -125,6 +151,34 @@ in combination with suitable event subscribers. However, it is not limited to these use cases. +### Event-based Entity Providers + +You can implement the `EventSubscriber` interface on an `EntityProviders` to allow it to handle events from other plugins e.g. the event backend plugin +mentioned above. + +Assuming you have configured the `eventBroker` into the `PluginEnvironment` you can pass the broker to the entity provider for it to subscribe. + +```diff +// packages/backend/src/plugins/catalog.ts + import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; ++import { DemoEventBasedEntityProvider } from './DemoEventBasedEntityProvider'; + import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; + import { Router } from 'express'; + import { PluginEnvironment } from '../types'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + const builder = await CatalogBuilder.create(env); + builder.addProcessor(new ScaffolderEntitiesProcessor()); ++ const demoProvider = new DemoEventBasedEntityProvider({ logger: env.logger, topics: ['example'], eventBroker: env.eventBroker }); ++ builder.addEntityProvider(demoProvider); + const { processingEngine, router } = await builder.build(); + await processingEngine.start(); + return router; + } +``` + ## Use Cases ### Custom Event Broker From 2fbaf646110d98c32dc2661973357e69873ecd22 Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Fri, 10 Mar 2023 13:33:47 +0000 Subject: [PATCH 7/9] fix typo Signed-off-by: Brian Fletcher --- plugins/events-backend/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/events-backend/README.md b/plugins/events-backend/README.md index 2288c91f2e..d5a321df94 100644 --- a/plugins/events-backend/README.md +++ b/plugins/events-backend/README.md @@ -82,7 +82,7 @@ export default async function createPlugin( ### Implementing an `EventSubscriber` class -More complex solutions might warrent the creation of a class that implements the `EventSubscriber` interface. e.g. +More complex solutions might need the creation of a class that implements the `EventSubscriber` interface. e.g. ```typescript jsx import { EventSubscriber } from "./EventSubscriber"; From 1d9da597be8c9193fcd15af0875ef7a8cae8a360 Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Fri, 10 Mar 2023 13:46:33 +0000 Subject: [PATCH 8/9] move review comments Signed-off-by: Brian Fletcher --- .changeset/tall-meals-dress.md | 2 +- docs/integrations/bitbucketCloud/discovery.md | 33 +++++++++---------- docs/integrations/github/discovery.md | 33 ++++++++++--------- docs/integrations/github/org.md | 32 +++++++++--------- 4 files changed, 50 insertions(+), 50 deletions(-) diff --git a/.changeset/tall-meals-dress.md b/.changeset/tall-meals-dress.md index 25f67847ae..3d39c369fe 100644 --- a/.changeset/tall-meals-dress.md +++ b/.changeset/tall-meals-dress.md @@ -4,4 +4,4 @@ Export `DefaultEventBroker` to allow decoupling of the catalog and events backends in the `example-backend`. -Please look at `plugins/events-backend/README.md` for the currently advised was to set up the event backend and catalog providers. +Please look at `plugins/events-backend/README.md` for the currently advised way to set up the event backend and catalog providers. diff --git a/docs/integrations/bitbucketCloud/discovery.md b/docs/integrations/bitbucketCloud/discovery.md index f6ceab9ff7..c5291e3241 100644 --- a/docs/integrations/bitbucketCloud/discovery.md +++ b/docs/integrations/bitbucketCloud/discovery.md @@ -72,30 +72,29 @@ Additionally, you need to decide how you want to receive events from external so Set up your provider ```diff -// packages/backend/src/plugins/catalogEventBasedProviders.ts -+import { CatalogClient } from '@backstage/catalog-client'; +// packages/backend/src/plugins/catalog.ts + import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +import { BitbucketCloudEntityProvider } from '@backstage/plugin-catalog-backend-module-bitbucket-cloud'; - import { EntityProvider } from '@backstage/plugin-catalog-node'; - import { EventSubscriber } from '@backstage/plugin-events-node'; + import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; + import { Router } from 'express'; import { PluginEnvironment } from '../types'; - export default async function createCatalogEventBasedProviders( -- _: PluginEnvironment, -+ env: PluginEnvironment, - ): Promise> { - const providers: Array< - (EntityProvider & EventSubscriber) | Array - > = []; -- // add your event-based entity providers here -+ providers.push( -+ BitbucketCloudEntityProvider.fromConfig(env.config, { + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + const builder = await CatalogBuilder.create(env); + builder.addProcessor(new ScaffolderEntitiesProcessor()); ++ const bitBucketProvider = BitbucketCloudEntityProvider.fromConfig(env.config, { + catalogApi: new CatalogClient({ discoveryApi: env.discovery }), + logger: env.logger, + scheduler: env.scheduler, + tokenManager: env.tokenManager, -+ }), -+ ); - return providers.flat(); ++ }); ++ env.eventBroker.subscribe(bitBucketProvider); ++ builder.addEntityProvider(bitBucketProvider); + const { processingEngine, router } = await builder.build(); + await processingEngine.start(); + return router; } ``` diff --git a/docs/integrations/github/discovery.md b/docs/integrations/github/discovery.md index bbbe86a834..da4af4b61b 100644 --- a/docs/integrations/github/discovery.md +++ b/docs/integrations/github/discovery.md @@ -68,21 +68,19 @@ Additionally, you need to decide how you want to receive events from external so Set up your provider ```diff -// packages/backend/src/plugins/catalogEventBasedProviders.ts +// packages/backend/src/plugins/catalog.ts + import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +import { GithubEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; - import { EntityProvider } from '@backstage/plugin-catalog-node'; - import { EventSubscriber } from '@backstage/plugin-events-node'; + import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; + import { Router } from 'express'; import { PluginEnvironment } from '../types'; - export default async function createCatalogEventBasedProviders( -- _: PluginEnvironment, -+ env: PluginEnvironment, - ): Promise> { - const providers: Array< - (EntityProvider & EventSubscriber) | Array - > = []; -- // add your event-based entity providers here -+ providers.push( -+ GithubEntityProvider.fromConfig(env.config, { + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + const builder = await CatalogBuilder.create(env); + builder.addProcessor(new ScaffolderEntitiesProcessor()); ++ const githubProvider = GithubEntityProvider.fromConfig(env.config, { + logger: env.logger, + // optional: alternatively, use scheduler with schedule defined in app-config.yaml + schedule: env.scheduler.createScheduledTaskRunner({ @@ -91,9 +89,12 @@ Set up your provider + }), + // optional: alternatively, use schedule + scheduler: env.scheduler, -+ }), -+ ); - return providers.flat(); ++ }); ++ env.eventBroker.subscribe(githubProvider); ++ builder.addEntityProvider(demoProvider); + const { processingEngine, router } = await builder.build(); + await processingEngine.start(); + return router; } ``` diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md index 0aa9997bae..c0b7a74d69 100644 --- a/docs/integrations/github/org.md +++ b/docs/integrations/github/org.md @@ -75,21 +75,19 @@ Additionally, you need to decide how you want to receive events from external so Set up your provider ```diff -// packages/backend/src/plugins/catalogEventBasedProviders.ts +// packages/backend/src/plugins/catalog.ts + import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +import { GithubOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; - import { EntityProvider } from '@backstage/plugin-catalog-node'; - import { EventSubscriber } from '@backstage/plugin-events-node'; + import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; + import { Router } from 'express'; import { PluginEnvironment } from '../types'; - export default async function createCatalogEventBasedProviders( -- _: PluginEnvironment, -+ env: PluginEnvironment, - ): Promise> { - const providers: Array< - (EntityProvider & EventSubscriber) | Array - > = []; -- // add your event-based entity providers here -+ providers.push( -+ GithubOrgEntityProvider.fromConfig(env.config, { + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + const builder = await CatalogBuilder.create(env); + builder.addProcessor(new ScaffolderEntitiesProcessor()); ++ const githubOrgProvider = GithubOrgEntityProvider.fromConfig(env.config, { + id: 'production', + orgUrl: 'https://github.com/backstage', + logger: env.logger, @@ -97,9 +95,11 @@ Set up your provider + frequency: { minutes: 60 }, + timeout: { minutes: 15 }, + }), -+ }), -+ ); - return providers.flat(); ++ env.eventBroker.subscribe(githubOrgProvider); ++ builder.addEntityProvider(githubOrgProvider); + const { processingEngine, router } = await builder.build(); + await processingEngine.start(); + return router; } ``` From 386cfe0fc9f490e4b6011548f635be4ed4af17e6 Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Mon, 20 Mar 2023 09:04:23 +0000 Subject: [PATCH 9/9] review comment Signed-off-by: Brian Fletcher --- docs/integrations/bitbucketCloud/discovery.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/integrations/bitbucketCloud/discovery.md b/docs/integrations/bitbucketCloud/discovery.md index e1bfc6ad45..6e1533dea0 100644 --- a/docs/integrations/bitbucketCloud/discovery.md +++ b/docs/integrations/bitbucketCloud/discovery.md @@ -92,7 +92,7 @@ export default async function createPlugin( const builder = await CatalogBuilder.create(env); builder.addProcessor(new ScaffolderEntitiesProcessor()); /* highlight-add-start */ - const bitBucketProvider = BitbucketCloudEntityProvider.fromConfig( + const bitbucketCloudProvider = BitbucketCloudEntityProvider.fromConfig( env.config, { catalogApi: new CatalogClient({ discoveryApi: env.discovery }), @@ -101,8 +101,8 @@ export default async function createPlugin( tokenManager: env.tokenManager, }, ); - env.eventBroker.subscribe(bitBucketProvider); - builder.addEntityProvider(bitBucketProvider); + env.eventBroker.subscribe(bitbucketCloudProvider); + builder.addEntityProvider(bitbucketCloudProvider); /* highlight-add-end */ const { processingEngine, router } = await builder.build(); await processingEngine.start();