Merge pull request #13931 from Bonial-International-GmbH/pjungermann/bitbucket-cloud/events
feat: events
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-bitbucket-cloud': patch
|
||||
---
|
||||
|
||||
Handle Bitbucket Cloud `repo:push` events at the `BitbucketCloudEntityProvider`
|
||||
by subscribing to the topic `bitbucketCloud.repo:push.`
|
||||
|
||||
Implements `EventSubscriber` to receive events for the topic `bitbucketCloud.repo:push`.
|
||||
|
||||
On `repo:push`, the affected repository will be refreshed.
|
||||
This includes adding new Location entities, refreshing existing ones,
|
||||
and removing obsolete ones.
|
||||
|
||||
To support this, a new annotation `bitbucket.org/repo-url` was added
|
||||
to Location entities.
|
||||
|
||||
A full refresh will require 1 API call to Bitbucket Cloud to discover all catalog files.
|
||||
When we handle one `repo:push` event, we also need 1 API call in order to know
|
||||
which catalog files exist.
|
||||
This may lead to more discovery-related API calls (code search).
|
||||
The main cause for hitting the rate limits are Locations refresh-related operations.
|
||||
|
||||
A reduction of total API calls to reduce the rate limit issues can only be achieved in
|
||||
combination with
|
||||
|
||||
1. reducing the full refresh frequency (e.g., to monthly)
|
||||
2. reducing the frequency of general Location refresh operations by the processing loop
|
||||
|
||||
For (2.), it is not possible to reduce the frequency only for Bitbucket Cloud-related
|
||||
Locations though.
|
||||
|
||||
Further optimizations might be required to resolve the rate limit issue.
|
||||
|
||||
**Installation and Migration**
|
||||
|
||||
Please find more information at
|
||||
https://backstage.io/docs/integrations/bitbucketCloud/discovery,
|
||||
in particular the section about "_Installation with Events Support_".
|
||||
|
||||
In case of the new backend-plugin-api _(alpha)_ the module will take care of
|
||||
registering itself at both.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
'@backstage/plugin-events-backend-module-bitbucket-cloud': minor
|
||||
---
|
||||
|
||||
Adds a new module `bitbucket-cloud` to plugin-events-backend.
|
||||
|
||||
The module adds a new event router `BitbucketCloudEventRouter`.
|
||||
|
||||
The event router will re-publish events received at topic `bitbucketCloud`
|
||||
under a more specific topic depending on their `x-event-key` value
|
||||
(e.g., `bitbucketCloud.repo:push`).
|
||||
|
||||
Please find more information at
|
||||
https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-bitbucket-cloud/README.md.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
'@backstage/plugin-events-backend-module-github': minor
|
||||
---
|
||||
|
||||
Adds a new module `github` to plugin-events-backend.
|
||||
|
||||
The module adds a new event router `GithubEventRouter`.
|
||||
|
||||
The event router will re-publish events received at topic `github`
|
||||
under a more specific topic depending on their `x-github-event` value
|
||||
(e.g., `github.push`).
|
||||
|
||||
Please find more information at
|
||||
https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-github/README.md.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
'@backstage/plugin-events-backend-module-gerrit': minor
|
||||
---
|
||||
|
||||
Adds a new module `gerrit` to plugin-events-backend.
|
||||
|
||||
The module adds a new event router `GerritEventRouter`.
|
||||
|
||||
The event router will re-publish events received at topic `gerrit`
|
||||
under a more specific topic depending on their `$.type` value
|
||||
(e.g., `gerrit.change-merged`).
|
||||
|
||||
Please find more information at
|
||||
https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-gerrit/README.md.
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
'@backstage/plugin-events-backend-module-aws-sqs': minor
|
||||
---
|
||||
|
||||
Adds a new module `aws-sqs` for plugin-events-backend.
|
||||
|
||||
The module provides an event publisher `AwsSqsConsumingEventPublisher`
|
||||
which will allow you to receive events from
|
||||
an AWS SQS queue and will publish these to the used event broker.
|
||||
|
||||
Please find more information at
|
||||
https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
'@backstage/plugin-events-backend-module-azure': minor
|
||||
---
|
||||
|
||||
Adds a new module `azure` to plugin-events-backend.
|
||||
|
||||
The module adds a new event router `AzureDevOpsEventRouter`.
|
||||
|
||||
The event router will re-publish events received at topic `azureDevOps`
|
||||
under a more specific topic depending on their `$.eventType` value
|
||||
(e.g., `azureDevOps.git.push`).
|
||||
|
||||
Please find more information at
|
||||
https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-azure/README.md.
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
'@backstage/plugin-events-backend': minor
|
||||
'@backstage/plugin-events-node': minor
|
||||
---
|
||||
|
||||
Support events received via HTTP endpoints at plugin-events-backend.
|
||||
|
||||
The plugin provides an event publisher `HttpPostIngressEventPublisher`
|
||||
which will allow you to receive events via
|
||||
HTTP endpoints `POST /api/events/http/{topic}`
|
||||
and will publish these to the used event broker.
|
||||
|
||||
Using a provided custom validator, you can participate in the decision
|
||||
which events are accepted, e.g. by verifying the source of the request.
|
||||
|
||||
Please find more information at
|
||||
https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
'@backstage/plugin-events-backend': minor
|
||||
'@backstage/plugin-events-node': minor
|
||||
'@backstage/plugin-events-backend-test-utils': minor
|
||||
---
|
||||
|
||||
Adds a new backend plugin plugin-events-backend for managing events.
|
||||
|
||||
plugin-events-node exposes interfaces which can be used by modules.
|
||||
|
||||
plugin-events-backend-test-utils provides utilities which can be used while writing tests e.g. for modules.
|
||||
|
||||
Please find more information at
|
||||
https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-bitbucket-cloud-common': patch
|
||||
---
|
||||
|
||||
Add interfaces for Bitbucket Cloud (webhook) events.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
'@backstage/plugin-events-backend-module-gitlab': minor
|
||||
---
|
||||
|
||||
Adds a new module `gitlab` to plugin-events-backend.
|
||||
|
||||
The module adds a new event router `GitlabEventRouter`.
|
||||
|
||||
The event router will re-publish events received at topic `gitlab`
|
||||
under a more specific topic depending on their `$.event_name` value
|
||||
(e.g., `gitlab.push`).
|
||||
|
||||
Please find more information at
|
||||
https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-gitlab/README.md.
|
||||
@@ -36,6 +36,15 @@ yarn.lock @backstage/reviewers @backst
|
||||
/plugins/code-coverage-backend @backstage/reviewers @alde @nissayeva
|
||||
/plugins/cost-insights @backstage/reviewers @backstage/silver-lining
|
||||
/plugins/cost-insights-* @backstage/reviewers @backstage/silver-lining
|
||||
/plugins/events-backend @backstage/reviewers @pjungermann
|
||||
/plugins/events-backend-module-aws-sqs @backstage/reviewers @pjungermann
|
||||
/plugins/events-backend-module-azure @backstage/reviewers @pjungermann
|
||||
/plugins/events-backend-module-bitbucket-cloud @backstage/reviewers @pjungermann
|
||||
/plugins/events-backend-module-gerrit @backstage/reviewers @pjungermann
|
||||
/plugins/events-backend-module-github @backstage/reviewers @pjungermann
|
||||
/plugins/events-backend-module-gitlab @backstage/reviewers @pjungermann
|
||||
/plugins/events-backend-test-utils @backstage/reviewers @pjungermann
|
||||
/plugins/events-node @backstage/reviewers @pjungermann
|
||||
/plugins/explore @backstage/reviewers @backstage/sda-se-reviewers
|
||||
/plugins/explore-react @backstage/reviewers @backstage/sda-se-reviewers
|
||||
/plugins/fossa @backstage/reviewers @backstage/sda-se-reviewers
|
||||
|
||||
@@ -24,10 +24,12 @@ package.
|
||||
yarn add --cwd packages/backend @backstage/plugin-catalog-backend-module-bitbucket-cloud
|
||||
```
|
||||
|
||||
### Installation without Events Support
|
||||
|
||||
And then add the entity provider to your catalog builder:
|
||||
|
||||
```diff
|
||||
// In packages/backend/src/plugins/catalog.ts
|
||||
// packages/backend/src/plugins/catalog.ts
|
||||
+ import { BitbucketCloudEntityProvider } from '@backstage/plugin-catalog-backend-module-bitbucket-cloud';
|
||||
|
||||
export default async function createPlugin(
|
||||
@@ -37,11 +39,7 @@ And then add the entity provider to your catalog builder:
|
||||
+ builder.addEntityProvider(
|
||||
+ BitbucketCloudEntityProvider.fromConfig(env.config, {
|
||||
+ logger: env.logger,
|
||||
+ // optional: alternatively, configure via app-config.yaml
|
||||
+ schedule: env.scheduler.createScheduledTaskRunner({
|
||||
+ frequency: { minutes: 30 },
|
||||
+ timeout: { minutes: 3 },
|
||||
+ }),
|
||||
+ scheduler: env.scheduler,
|
||||
+ }),
|
||||
+ );
|
||||
|
||||
@@ -49,6 +47,62 @@ And then add the entity provider to your catalog builder:
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively to the config-based schedule, you can use
|
||||
|
||||
```diff
|
||||
- scheduler: env.scheduler,
|
||||
+ schedule: env.scheduler.createScheduledTaskRunner({
|
||||
+ frequency: { minutes: 30 },
|
||||
+ timeout: { minutes: 3 },
|
||||
+ }),
|
||||
```
|
||||
|
||||
### Installation with Events Support
|
||||
|
||||
Please follow the installation instructions at
|
||||
|
||||
- https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md
|
||||
- https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-bitbucket-cloud/README.md
|
||||
|
||||
Additionally, you need to decide how you want to receive events from external sources like
|
||||
|
||||
- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md)
|
||||
- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md)
|
||||
|
||||
Set up your provider
|
||||
|
||||
```diff
|
||||
// packages/backend/src/plugins/catalogEventBasedProviders.ts
|
||||
+import { CatalogClient } from '@backstage/catalog-client';
|
||||
+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 { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createCatalogEventBasedProviders(
|
||||
- _: PluginEnvironment,
|
||||
+ env: PluginEnvironment,
|
||||
): Promise<Array<EntityProvider & EventSubscriber>> {
|
||||
const providers: Array<
|
||||
(EntityProvider & EventSubscriber) | Array<EntityProvider & EventSubscriber>
|
||||
> = [];
|
||||
- // add your event-based entity providers here
|
||||
+ providers.push(
|
||||
+ BitbucketCloudEntityProvider.fromConfig(env.config, {
|
||||
+ catalogApi: new CatalogClient({ discoveryApi: env.discovery }),
|
||||
+ logger: env.logger,
|
||||
+ scheduler: env.scheduler,
|
||||
+ tokenManager: env.tokenManager,
|
||||
+ }),
|
||||
+ );
|
||||
return providers.flat();
|
||||
}
|
||||
```
|
||||
|
||||
**Attention:**
|
||||
`catalogApi` and `tokenManager` are required at this variant
|
||||
compared to the one without events support.
|
||||
|
||||
## Configuration
|
||||
|
||||
To use the entity provider, you'll need a [Bitbucket Cloud integration set up](locations.md).
|
||||
|
||||
@@ -39,7 +39,10 @@
|
||||
"@backstage/plugin-azure-sites-backend": "workspace:^",
|
||||
"@backstage/plugin-badges-backend": "workspace:^",
|
||||
"@backstage/plugin-catalog-backend": "workspace:^",
|
||||
"@backstage/plugin-catalog-node": "workspace:^",
|
||||
"@backstage/plugin-code-coverage-backend": "workspace:^",
|
||||
"@backstage/plugin-events-backend": "workspace:^",
|
||||
"@backstage/plugin-events-node": "workspace:^",
|
||||
"@backstage/plugin-graphql-backend": "workspace:^",
|
||||
"@backstage/plugin-jenkins-backend": "workspace:^",
|
||||
"@backstage/plugin-kafka-backend": "workspace:^",
|
||||
|
||||
@@ -42,7 +42,9 @@ 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 events from './plugins/events';
|
||||
import kubernetes from './plugins/kubernetes';
|
||||
import kafka from './plugins/kafka';
|
||||
import rollbar from './plugins/rollbar';
|
||||
@@ -141,10 +143,19 @@ async function main() {
|
||||
);
|
||||
const permissionEnv = useHotMemoize(module, () => createEnv('permission'));
|
||||
const playlistEnv = useHotMemoize(module, () => createEnv('playlist'));
|
||||
const eventsEnv = useHotMemoize(module, () => createEnv('events'));
|
||||
|
||||
const eventBasedEntityProviders = await catalogEventBasedProviders(
|
||||
catalogEnv,
|
||||
);
|
||||
|
||||
const apiRouter = Router();
|
||||
apiRouter.use('/catalog', await catalog(catalogEnv));
|
||||
apiRouter.use(
|
||||
'/catalog',
|
||||
await catalog(catalogEnv, eventBasedEntityProviders),
|
||||
);
|
||||
apiRouter.use('/code-coverage', await codeCoverage(codeCoverageEnv));
|
||||
apiRouter.use('/events', await events(eventsEnv, eventBasedEntityProviders));
|
||||
apiRouter.use('/rollbar', await rollbar(rollbarEnv));
|
||||
apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv));
|
||||
apiRouter.use('/tech-insights', await techInsights(techInsightsEnv));
|
||||
|
||||
@@ -15,15 +15,18 @@
|
||||
*/
|
||||
|
||||
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';
|
||||
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
providers?: Array<EntityProvider>,
|
||||
): Promise<Router> {
|
||||
const builder = await CatalogBuilder.create(env);
|
||||
builder.addProcessor(new ScaffolderEntitiesProcessor());
|
||||
builder.addEntityProvider(providers ?? []);
|
||||
const { processingEngine, router } = await builder.build();
|
||||
await processingEngine.start();
|
||||
return router;
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EntityProvider,
|
||||
EntityProviderConnection,
|
||||
} from '@backstage/plugin-catalog-node';
|
||||
import { EventParams, EventSubscriber } from '@backstage/plugin-events-node';
|
||||
import { Logger } from 'winston';
|
||||
import { PluginEnvironment } from '../types';
|
||||
|
||||
class DemoEventBasedEntityProvider implements EntityProvider, EventSubscriber {
|
||||
constructor(
|
||||
private readonly logger: Logger,
|
||||
private readonly topics: string[],
|
||||
) {}
|
||||
|
||||
async onEvent(params: EventParams): Promise<void> {
|
||||
this.logger.info(
|
||||
`onEvent: topic=${params.topic}, metadata=${JSON.stringify(
|
||||
params.metadata,
|
||||
)}, payload=${JSON.stringify(params.eventPayload)}`,
|
||||
);
|
||||
}
|
||||
|
||||
supportsEventTopics(): string[] {
|
||||
return this.topics;
|
||||
}
|
||||
|
||||
async connect(_: EntityProviderConnection): Promise<void> {
|
||||
// not doing anything here
|
||||
}
|
||||
|
||||
getProviderName(): string {
|
||||
return DemoEventBasedEntityProvider.name;
|
||||
}
|
||||
}
|
||||
|
||||
export default async function createCatalogEventBasedProviders(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Array<EntityProvider & EventSubscriber>> {
|
||||
const providers: Array<
|
||||
(EntityProvider & EventSubscriber) | Array<EntityProvider & EventSubscriber>
|
||||
> = [];
|
||||
providers.push(new DemoEventBasedEntityProvider(env.logger, ['example']));
|
||||
// add your event-based entity providers here
|
||||
return providers.flat();
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
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<Router> {
|
||||
const eventsRouter = Router();
|
||||
const httpRouter = Router();
|
||||
eventsRouter.use('/http', httpRouter);
|
||||
|
||||
const http = HttpPostIngressEventPublisher.fromConfig({
|
||||
config: env.config,
|
||||
logger: env.logger,
|
||||
router: httpRouter,
|
||||
});
|
||||
|
||||
await new EventsBackend(env.logger)
|
||||
.addPublishers(http)
|
||||
.addSubscribers(subscribers)
|
||||
.start();
|
||||
|
||||
return eventsRouter;
|
||||
}
|
||||
@@ -24,6 +24,57 @@ export class BitbucketCloudClient {
|
||||
): WithPagination<Models.SearchResultPage, Models.SearchCodeSearchResult>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export namespace Events {
|
||||
// (undocumented)
|
||||
export interface Change {
|
||||
// (undocumented)
|
||||
closed: boolean;
|
||||
// (undocumented)
|
||||
commits: Models.Commit[];
|
||||
// (undocumented)
|
||||
created: boolean;
|
||||
// (undocumented)
|
||||
forced: boolean;
|
||||
// (undocumented)
|
||||
links: ChangeLinks;
|
||||
// (undocumented)
|
||||
new: Models.Branch;
|
||||
// (undocumented)
|
||||
old: Models.Branch;
|
||||
// (undocumented)
|
||||
truncated: boolean;
|
||||
}
|
||||
// (undocumented)
|
||||
export interface ChangeLinks {
|
||||
// (undocumented)
|
||||
commits: Models.Link;
|
||||
// (undocumented)
|
||||
diff: Models.Link;
|
||||
// (undocumented)
|
||||
html: Models.Link;
|
||||
}
|
||||
// (undocumented)
|
||||
export interface RepoEvent {
|
||||
// (undocumented)
|
||||
actor: Models.Account;
|
||||
// (undocumented)
|
||||
repository: Models.Repository & {
|
||||
workspace: Models.Workspace;
|
||||
};
|
||||
}
|
||||
// (undocumented)
|
||||
export interface RepoPush {
|
||||
// (undocumented)
|
||||
changes: Change[];
|
||||
}
|
||||
// (undocumented)
|
||||
export interface RepoPushEvent extends RepoEvent {
|
||||
// (undocumented)
|
||||
push: RepoPush;
|
||||
}
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type FilterAndSortOptions = {
|
||||
q?: string;
|
||||
@@ -340,6 +391,37 @@ export namespace Models {
|
||||
// (undocumented)
|
||||
self?: Link;
|
||||
}
|
||||
export interface Workspace extends ModelObject {
|
||||
// (undocumented)
|
||||
created_on?: string;
|
||||
is_private?: boolean;
|
||||
// (undocumented)
|
||||
links?: WorkspaceLinks;
|
||||
name?: string;
|
||||
slug?: string;
|
||||
// (undocumented)
|
||||
updated_on?: string;
|
||||
uuid?: string;
|
||||
}
|
||||
// (undocumented)
|
||||
export interface WorkspaceLinks {
|
||||
// (undocumented)
|
||||
avatar?: Link;
|
||||
// (undocumented)
|
||||
html?: Link;
|
||||
// (undocumented)
|
||||
members?: Link;
|
||||
// (undocumented)
|
||||
owners?: Link;
|
||||
// (undocumented)
|
||||
projects?: Link;
|
||||
// (undocumented)
|
||||
repositories?: Link;
|
||||
// (undocumented)
|
||||
self?: Link;
|
||||
// (undocumented)
|
||||
snippets?: Link;
|
||||
}
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -30,6 +30,14 @@ const modelsModule = modelsFile.getModuleOrThrow('Models');
|
||||
const clientFile = project.getSourceFile('src/BitbucketCloudClient.ts');
|
||||
const clientClass = clientFile.getClassOrThrow('BitbucketCloudClient');
|
||||
|
||||
const eventsFile = project.getSourceFile('src/events/index.ts');
|
||||
const eventsModule = eventsFile.getModuleOrThrow('Events');
|
||||
const eventsStmts = [
|
||||
...eventsModule.getClasses(),
|
||||
...eventsModule.getInterfaces(),
|
||||
...eventsModule.getTypeAliases(),
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns an array of the unique items of the provided array.
|
||||
*
|
||||
@@ -79,7 +87,11 @@ function referencedModelsIdentifiers(stmt, processed) {
|
||||
}
|
||||
|
||||
// all directly or transitively referenced/used `Models.[...]` are allowed to stay
|
||||
const allowed = referencedModelsIdentifiers(clientClass);
|
||||
const processed = [];
|
||||
const allowed = referencedModelsIdentifiers(clientClass, processed);
|
||||
allowed.push(
|
||||
...eventsStmts.flatMap(stmt => referencedModelsIdentifiers(stmt, processed)),
|
||||
);
|
||||
|
||||
// remove everything not part of the "allow list"
|
||||
modelsModule
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Models } from '../models';
|
||||
|
||||
// source: https://support.atlassian.com/bitbucket-cloud/docs/event-payloads
|
||||
|
||||
/** @public */
|
||||
export namespace Events {
|
||||
/** @public */
|
||||
export interface RepoEvent {
|
||||
repository: Models.Repository & { workspace: Models.Workspace };
|
||||
actor: Models.Account;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface RepoPushEvent extends RepoEvent {
|
||||
push: RepoPush;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface RepoPush {
|
||||
changes: Change[];
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface Change {
|
||||
old: Models.Branch;
|
||||
new: Models.Branch;
|
||||
truncated: boolean;
|
||||
created: boolean;
|
||||
forced: boolean;
|
||||
closed: boolean;
|
||||
links: ChangeLinks;
|
||||
commits: Models.Commit[];
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface ChangeLinks {
|
||||
commits: Models.Link;
|
||||
diff: Models.Link;
|
||||
html: Models.Link;
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
|
||||
export * from './BitbucketCloudClient';
|
||||
export * from './events';
|
||||
export * from './models';
|
||||
export * from './pagination';
|
||||
export * from './types';
|
||||
|
||||
@@ -518,4 +518,46 @@ export namespace Models {
|
||||
repositories?: Link;
|
||||
self?: Link;
|
||||
}
|
||||
|
||||
/**
|
||||
* A Bitbucket workspace.
|
||||
* Workspaces are used to organize repositories.
|
||||
* @public
|
||||
*/
|
||||
export interface Workspace extends ModelObject {
|
||||
created_on?: string;
|
||||
/**
|
||||
* Indicates whether the workspace is publicly accessible, or whether it is
|
||||
* private to the members and consequently only visible to members.
|
||||
*/
|
||||
is_private?: boolean;
|
||||
links?: WorkspaceLinks;
|
||||
/**
|
||||
* The name of the workspace.
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* The short label that identifies this workspace.
|
||||
*/
|
||||
slug?: string;
|
||||
updated_on?: string;
|
||||
/**
|
||||
* The workspace's immutable id.
|
||||
*/
|
||||
uuid?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface WorkspaceLinks {
|
||||
avatar?: Link;
|
||||
html?: Link;
|
||||
members?: Link;
|
||||
owners?: Link;
|
||||
projects?: Link;
|
||||
repositories?: Link;
|
||||
self?: Link;
|
||||
snippets?: Link;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,24 +4,33 @@
|
||||
|
||||
```ts
|
||||
import { BackendFeature } from '@backstage/backend-plugin-api';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { Config } from '@backstage/config';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-backend';
|
||||
import { EntityProviderConnection } from '@backstage/plugin-catalog-backend';
|
||||
import { EventParams } from '@backstage/plugin-events-node';
|
||||
import { Events } from '@backstage/plugin-bitbucket-cloud-common';
|
||||
import { EventSubscriber } from '@backstage/plugin-events-node';
|
||||
import { Logger } from 'winston';
|
||||
import { PluginTaskScheduler } from '@backstage/backend-tasks';
|
||||
import { TaskRunner } from '@backstage/backend-tasks';
|
||||
import { TokenManager } from '@backstage/backend-common';
|
||||
|
||||
// @public
|
||||
export class BitbucketCloudEntityProvider implements EntityProvider {
|
||||
export class BitbucketCloudEntityProvider
|
||||
implements EntityProvider, EventSubscriber
|
||||
{
|
||||
// (undocumented)
|
||||
connect(connection: EntityProviderConnection): Promise<void>;
|
||||
// (undocumented)
|
||||
static fromConfig(
|
||||
config: Config,
|
||||
options: {
|
||||
catalogApi?: CatalogApi;
|
||||
logger: Logger;
|
||||
schedule?: TaskRunner;
|
||||
scheduler?: PluginTaskScheduler;
|
||||
tokenManager?: TokenManager;
|
||||
},
|
||||
): BitbucketCloudEntityProvider[];
|
||||
// (undocumented)
|
||||
@@ -29,7 +38,13 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
|
||||
// (undocumented)
|
||||
getTaskId(): string;
|
||||
// (undocumented)
|
||||
onEvent(params: EventParams): Promise<void>;
|
||||
// (undocumented)
|
||||
onRepoPush(event: Events.RepoPushEvent): Promise<void>;
|
||||
// (undocumented)
|
||||
refresh(logger: Logger): Promise<void>;
|
||||
// (undocumented)
|
||||
supportsEventTopics(): string[];
|
||||
}
|
||||
|
||||
// @alpha (undocumented)
|
||||
|
||||
@@ -33,13 +33,19 @@
|
||||
"clean": "backstage-cli package clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "workspace:^",
|
||||
"@backstage/backend-plugin-api": "workspace:^",
|
||||
"@backstage/backend-tasks": "workspace:^",
|
||||
"@backstage/catalog-client": "workspace:^",
|
||||
"@backstage/catalog-model": "workspace:^",
|
||||
"@backstage/config": "workspace:^",
|
||||
"@backstage/integration": "workspace:^",
|
||||
"@backstage/plugin-bitbucket-cloud-common": "workspace:^",
|
||||
"@backstage/plugin-catalog-backend": "workspace:^",
|
||||
"@backstage/plugin-catalog-common": "workspace:^",
|
||||
"@backstage/plugin-catalog-node": "workspace:^",
|
||||
"@backstage/plugin-events-node": "workspace:^",
|
||||
"p-limit": "^3.1.0",
|
||||
"uuid": "^8.0.0",
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
|
||||
+2
@@ -333,6 +333,8 @@ describe('BitbucketCloudEntityProvider', () => {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location': `url:${url}`,
|
||||
'backstage.io/managed-by-origin-location': `url:${url}`,
|
||||
'bitbucket.org/repo-url':
|
||||
'https://bitbucket.org/test-ws/test-repo2',
|
||||
},
|
||||
name: 'generated-7c2e6263b6cc2d14e69fd4d029afba601ad6dc3b',
|
||||
},
|
||||
|
||||
+195
-18
@@ -14,7 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { TokenManager } from '@backstage/backend-common';
|
||||
import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import {
|
||||
Entity,
|
||||
LocationEntity,
|
||||
stringifyEntityRef,
|
||||
} from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import {
|
||||
BitbucketCloudIntegration,
|
||||
@@ -22,22 +29,35 @@ import {
|
||||
} from '@backstage/integration';
|
||||
import {
|
||||
BitbucketCloudClient,
|
||||
Events,
|
||||
Models,
|
||||
} from '@backstage/plugin-bitbucket-cloud-common';
|
||||
import {
|
||||
DeferredEntity,
|
||||
EntityProvider,
|
||||
EntityProviderConnection,
|
||||
LocationSpec,
|
||||
locationSpecToLocationEntity,
|
||||
} from '@backstage/plugin-catalog-backend';
|
||||
import { LocationSpec } from '@backstage/plugin-catalog-common';
|
||||
import { EventParams, EventSubscriber } from '@backstage/plugin-events-node';
|
||||
import {
|
||||
BitbucketCloudEntityProviderConfig,
|
||||
readProviderConfigs,
|
||||
} from './BitbucketCloudEntityProviderConfig';
|
||||
import limiterFactory from 'p-limit';
|
||||
import * as uuid from 'uuid';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
const DEFAULT_BRANCH = 'master';
|
||||
const TOPIC_REPO_PUSH = 'bitbucketCloud/repo:push';
|
||||
|
||||
/** @public */
|
||||
export const ANNOTATION_BITBUCKET_CLOUD_REPO_URL = 'bitbucket.org/repo-url';
|
||||
|
||||
interface IngestionTarget {
|
||||
fileUrl: string;
|
||||
repoUrl: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discovers catalog files located in [Bitbucket Cloud](https://bitbucket.org).
|
||||
@@ -47,19 +67,27 @@ const DEFAULT_BRANCH = 'master';
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class BitbucketCloudEntityProvider implements EntityProvider {
|
||||
export class BitbucketCloudEntityProvider
|
||||
implements EntityProvider, EventSubscriber
|
||||
{
|
||||
private readonly client: BitbucketCloudClient;
|
||||
private readonly config: BitbucketCloudEntityProviderConfig;
|
||||
private readonly logger: Logger;
|
||||
private readonly scheduleFn: () => Promise<void>;
|
||||
private readonly catalogApi?: CatalogApi;
|
||||
private readonly tokenManager?: TokenManager;
|
||||
private connection?: EntityProviderConnection;
|
||||
|
||||
private eventConfigErrorThrown = false;
|
||||
|
||||
static fromConfig(
|
||||
config: Config,
|
||||
options: {
|
||||
catalogApi?: CatalogApi;
|
||||
logger: Logger;
|
||||
schedule?: TaskRunner;
|
||||
scheduler?: PluginTaskScheduler;
|
||||
tokenManager?: TokenManager;
|
||||
},
|
||||
): BitbucketCloudEntityProvider[] {
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
@@ -90,6 +118,8 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
|
||||
integration,
|
||||
options.logger,
|
||||
taskRunner,
|
||||
options.catalogApi,
|
||||
options.tokenManager,
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -99,6 +129,8 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
|
||||
integration: BitbucketCloudIntegration,
|
||||
logger: Logger,
|
||||
taskRunner: TaskRunner,
|
||||
catalogApi?: CatalogApi,
|
||||
tokenManager?: TokenManager,
|
||||
) {
|
||||
this.client = BitbucketCloudClient.fromConfig(integration.config);
|
||||
this.config = config;
|
||||
@@ -106,6 +138,8 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
|
||||
target: this.getProviderName(),
|
||||
});
|
||||
this.scheduleFn = this.createScheduleFn(taskRunner);
|
||||
this.catalogApi = catalogApi;
|
||||
this.tokenManager = tokenManager;
|
||||
}
|
||||
|
||||
private createScheduleFn(schedule: TaskRunner): () => Promise<void> {
|
||||
@@ -154,15 +188,7 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
|
||||
logger.info('Discovering catalog files in Bitbucket Cloud repositories');
|
||||
|
||||
const targets = await this.findCatalogFiles();
|
||||
const entities = targets
|
||||
.map(BitbucketCloudEntityProvider.toLocationSpec)
|
||||
.map(location => locationSpecToLocationEntity({ location }))
|
||||
.map(entity => {
|
||||
return {
|
||||
locationKey: this.getProviderName(),
|
||||
entity: entity,
|
||||
};
|
||||
});
|
||||
const entities = this.toDeferredEntities(targets);
|
||||
|
||||
await this.connection.applyMutation({
|
||||
type: 'full',
|
||||
@@ -174,7 +200,135 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
|
||||
);
|
||||
}
|
||||
|
||||
private async findCatalogFiles(): Promise<string[]> {
|
||||
/** {@inheritdoc @backstage/plugin-events-node#EventSubscriber.supportsEventTopics} */
|
||||
supportsEventTopics(): string[] {
|
||||
return [TOPIC_REPO_PUSH];
|
||||
}
|
||||
|
||||
/** {@inheritdoc @backstage/plugin-events-node#EventSubscriber.onEvent} */
|
||||
async onEvent(params: EventParams): Promise<void> {
|
||||
if (params.topic !== TOPIC_REPO_PUSH) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.metadata?.['x-event-key'] === 'repo:push') {
|
||||
await this.onRepoPush(params.eventPayload as Events.RepoPushEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private canHandleEvents(): boolean {
|
||||
if (this.catalogApi && this.tokenManager) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// throw only once
|
||||
if (!this.eventConfigErrorThrown) {
|
||||
this.eventConfigErrorThrown = true;
|
||||
throw new Error(
|
||||
`${this.getProviderName()} not well configured to handle repo:push. Missing CatalogApi and/or TokenManager.`,
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async onRepoPush(event: Events.RepoPushEvent): Promise<void> {
|
||||
if (!this.canHandleEvents()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.connection) {
|
||||
throw new Error('Not initialized');
|
||||
}
|
||||
|
||||
if (event.repository.workspace.slug !== this.config.workspace) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.matchesFilters(event.repository)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const repoName = event.repository.slug;
|
||||
const repoUrl = event.repository.links!.html!.href!;
|
||||
this.logger.info(`handle repo:push event for ${repoUrl}`);
|
||||
|
||||
// The commit information at the webhook only contains some high level metadata.
|
||||
// In order to understand whether relevant files have changed we would need to
|
||||
// look up all commits which would cost additional API calls.
|
||||
// The overall goal is to optimize the necessary amount of API calls.
|
||||
// Hence, we will just trigger a refresh for catalog file(s) within the repository
|
||||
// if we get notified about changes there.
|
||||
|
||||
const targets = await this.findCatalogFiles(repoName);
|
||||
|
||||
const { token } = await this.tokenManager!.getToken();
|
||||
const existing = await this.findExistingLocations(repoUrl, token);
|
||||
|
||||
const added: DeferredEntity[] = this.toDeferredEntities(
|
||||
targets.filter(
|
||||
// All Locations are managed by this provider and only have `target`, never `targets`.
|
||||
// All URLs (fileUrl, target) are created using `BitbucketCloudEntityProvider.toUrl`.
|
||||
// Hence, we can keep the comparison simple and don't need to handle different
|
||||
// casing or encoding, etc.
|
||||
target => !existing.find(item => item.spec.target === target.fileUrl),
|
||||
),
|
||||
);
|
||||
|
||||
const limiter = limiterFactory(10);
|
||||
|
||||
const stillExisting: Entity[] = [];
|
||||
const removed: DeferredEntity[] = [];
|
||||
existing.forEach(item => {
|
||||
if (targets.find(value => value.fileUrl === item.spec.target)) {
|
||||
stillExisting.push(item);
|
||||
} else {
|
||||
removed.push({
|
||||
locationKey: this.getProviderName(),
|
||||
entity: item,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const promises: Promise<void>[] = stillExisting.map(entity =>
|
||||
limiter(async () =>
|
||||
this.catalogApi!.refreshEntity(stringifyEntityRef(entity), { token }),
|
||||
),
|
||||
);
|
||||
|
||||
if (added.length > 0 || removed.length > 0) {
|
||||
const connection = this.connection;
|
||||
promises.push(
|
||||
limiter(async () =>
|
||||
connection.applyMutation({
|
||||
type: 'delta',
|
||||
added: added,
|
||||
removed: removed,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
private async findExistingLocations(
|
||||
repoUrl: string,
|
||||
token: string,
|
||||
): Promise<LocationEntity[]> {
|
||||
const filter: Record<string, string> = {};
|
||||
filter.kind = 'Location';
|
||||
filter[`metadata.annotations.${ANNOTATION_BITBUCKET_CLOUD_REPO_URL}`] =
|
||||
repoUrl;
|
||||
|
||||
return this.catalogApi!.getEntities({ filter }, { token }).then(
|
||||
result => result.items,
|
||||
) as Promise<LocationEntity[]>;
|
||||
}
|
||||
|
||||
private async findCatalogFiles(
|
||||
repoName?: string,
|
||||
): Promise<IngestionTarget[]> {
|
||||
const workspace = this.config.workspace;
|
||||
const catalogPath = this.config.catalogPath;
|
||||
|
||||
@@ -197,12 +351,13 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
|
||||
// ...except the one we need
|
||||
'+values.file.commit.repository.links.html.href',
|
||||
].join(',');
|
||||
const query = `"${catalogFilename}" path:${catalogPath}`;
|
||||
const optRepoFilter = repoName ? ` repo:${repoName}` : '';
|
||||
const query = `"${catalogFilename}" path:${catalogPath}${optRepoFilter}`;
|
||||
const searchResults = this.client
|
||||
.searchCode(workspace, query, { fields })
|
||||
.iterateResults();
|
||||
|
||||
const result: string[] = [];
|
||||
const result: IngestionTarget[] = [];
|
||||
|
||||
for await (const searchResult of searchResults) {
|
||||
// not a file match, but a code match
|
||||
@@ -212,12 +367,13 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
|
||||
|
||||
const repository = searchResult.file!.commit!.repository!;
|
||||
if (this.matchesFilters(repository)) {
|
||||
result.push(
|
||||
BitbucketCloudEntityProvider.toUrl(
|
||||
result.push({
|
||||
fileUrl: BitbucketCloudEntityProvider.toUrl(
|
||||
repository,
|
||||
searchResult.file!.path!,
|
||||
),
|
||||
);
|
||||
repoUrl: repository.links!.html!.href!,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,11 +390,32 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
|
||||
);
|
||||
}
|
||||
|
||||
private toDeferredEntities(targets: IngestionTarget[]): DeferredEntity[] {
|
||||
return targets
|
||||
.map(target => {
|
||||
const location = BitbucketCloudEntityProvider.toLocationSpec(
|
||||
target.fileUrl,
|
||||
);
|
||||
const entity = locationSpecToLocationEntity({ location });
|
||||
entity.metadata.annotations = {
|
||||
...entity.metadata.annotations,
|
||||
[ANNOTATION_BITBUCKET_CLOUD_REPO_URL]: target.repoUrl,
|
||||
};
|
||||
return entity;
|
||||
})
|
||||
.map(entity => {
|
||||
return {
|
||||
locationKey: this.getProviderName(),
|
||||
entity: entity,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private static toUrl(
|
||||
repository: Models.Repository,
|
||||
filePath: string,
|
||||
): string {
|
||||
const repoUrl = repository.links!.html!.href;
|
||||
const repoUrl = repository.links!.html!.href!;
|
||||
const branch = repository.mainbranch?.name ?? DEFAULT_BRANCH;
|
||||
|
||||
return `${repoUrl}/src/${branch}/${filePath}`;
|
||||
|
||||
+25
-4
@@ -15,11 +15,17 @@
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import {
|
||||
getVoidLogger,
|
||||
PluginEndpointDiscovery,
|
||||
TokenManager,
|
||||
} from '@backstage/backend-common';
|
||||
import {
|
||||
configServiceRef,
|
||||
discoveryServiceRef,
|
||||
loggerServiceRef,
|
||||
schedulerServiceRef,
|
||||
tokenManagerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
PluginTaskScheduler,
|
||||
@@ -27,20 +33,27 @@ import {
|
||||
} from '@backstage/backend-tasks';
|
||||
import { startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
|
||||
import { bitbucketCloudEntityProviderCatalogModule } from './BitbucketCloudEntityProviderCatalogModule';
|
||||
import { eventsExtensionPoint } from '@backstage/plugin-events-node';
|
||||
import { Duration } from 'luxon';
|
||||
import { bitbucketCloudEntityProviderCatalogModule } from './BitbucketCloudEntityProviderCatalogModule';
|
||||
import { BitbucketCloudEntityProvider } from '../BitbucketCloudEntityProvider';
|
||||
|
||||
describe('bitbucketCloudEntityProviderCatalogModule', () => {
|
||||
it('should register provider at the catalog extension point', async () => {
|
||||
let addedProviders: Array<BitbucketCloudEntityProvider> | undefined;
|
||||
let addedSubscribers: Array<BitbucketCloudEntityProvider> | undefined;
|
||||
let usedSchedule: TaskScheduleDefinition | undefined;
|
||||
|
||||
const extensionPoint = {
|
||||
const catalogExtensionPointImpl = {
|
||||
addEntityProvider: (providers: any) => {
|
||||
addedProviders = providers;
|
||||
},
|
||||
};
|
||||
const eventsExtensionPointImpl = {
|
||||
addSubscribers: (subscribers: any) => {
|
||||
addedSubscribers = subscribers;
|
||||
},
|
||||
};
|
||||
const runner = jest.fn();
|
||||
const scheduler = {
|
||||
createScheduledTaskRunner: (schedule: TaskScheduleDefinition) => {
|
||||
@@ -48,6 +61,8 @@ describe('bitbucketCloudEntityProviderCatalogModule', () => {
|
||||
return runner;
|
||||
},
|
||||
} as unknown as PluginTaskScheduler;
|
||||
const discovery = jest.fn() as any as PluginEndpointDiscovery;
|
||||
const tokenManager = jest.fn() as any as TokenManager;
|
||||
|
||||
const config = new ConfigReader({
|
||||
catalog: {
|
||||
@@ -64,11 +79,16 @@ describe('bitbucketCloudEntityProviderCatalogModule', () => {
|
||||
});
|
||||
|
||||
await startTestBackend({
|
||||
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
|
||||
extensionPoints: [
|
||||
[catalogProcessingExtensionPoint, catalogExtensionPointImpl],
|
||||
[eventsExtensionPoint, eventsExtensionPointImpl],
|
||||
],
|
||||
services: [
|
||||
[configServiceRef, config],
|
||||
[discoveryServiceRef, discovery],
|
||||
[loggerServiceRef, getVoidLogger()],
|
||||
[schedulerServiceRef, scheduler],
|
||||
[tokenManagerServiceRef, tokenManager],
|
||||
],
|
||||
features: [bitbucketCloudEntityProviderCatalogModule()],
|
||||
});
|
||||
@@ -79,6 +99,7 @@ describe('bitbucketCloudEntityProviderCatalogModule', () => {
|
||||
expect(addedProviders?.pop()?.getProviderName()).toEqual(
|
||||
'bitbucketCloud-provider:default',
|
||||
);
|
||||
expect(addedSubscribers).toEqual(addedProviders);
|
||||
expect(runner).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
+23
-2
@@ -20,8 +20,13 @@ import {
|
||||
loggerServiceRef,
|
||||
loggerToWinstonLogger,
|
||||
schedulerServiceRef,
|
||||
tokenManagerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
|
||||
import {
|
||||
catalogProcessingExtensionPoint,
|
||||
catalogServiceRef,
|
||||
} from '@backstage/plugin-catalog-node';
|
||||
import { eventsExtensionPoint } from '@backstage/plugin-events-node';
|
||||
import { BitbucketCloudEntityProvider } from '../BitbucketCloudEntityProvider';
|
||||
|
||||
/**
|
||||
@@ -34,18 +39,34 @@ export const bitbucketCloudEntityProviderCatalogModule = createBackendModule({
|
||||
env.registerInit({
|
||||
deps: {
|
||||
catalog: catalogProcessingExtensionPoint,
|
||||
catalogApi: catalogServiceRef,
|
||||
config: configServiceRef,
|
||||
// TODO(pjungermann): How to make this optional for those which only want the provider without event support?
|
||||
// Do we even want to support this?
|
||||
events: eventsExtensionPoint,
|
||||
logger: loggerServiceRef,
|
||||
scheduler: schedulerServiceRef,
|
||||
tokenManager: tokenManagerServiceRef,
|
||||
},
|
||||
async init({ catalog, config, logger, scheduler }) {
|
||||
async init({
|
||||
catalog,
|
||||
catalogApi,
|
||||
config,
|
||||
events,
|
||||
logger,
|
||||
scheduler,
|
||||
tokenManager,
|
||||
}) {
|
||||
const winstonLogger = loggerToWinstonLogger(logger);
|
||||
const providers = BitbucketCloudEntityProvider.fromConfig(config, {
|
||||
catalogApi,
|
||||
logger: winstonLogger,
|
||||
scheduler,
|
||||
tokenManager,
|
||||
});
|
||||
|
||||
catalog.addEntityProvider(providers);
|
||||
events.addSubscribers(providers);
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,42 @@
|
||||
# events-backend-module-aws-sqs
|
||||
|
||||
Welcome to the `events-backend-module-aws-sqs` backend plugin!
|
||||
|
||||
This plugin is a module for the `events-backend` backend plugin
|
||||
and extends it with an `AwsSqsConsumingEventPublisher`.
|
||||
|
||||
This event publisher will allow you to receive events from
|
||||
an AWS SQS queue and will publish these to the used event broker.
|
||||
|
||||
## Configuration
|
||||
|
||||
The polled AWS SQS queues depend on your configuration:
|
||||
|
||||
```yaml
|
||||
events:
|
||||
modules:
|
||||
awsSqs:
|
||||
awsSqsConsumingEventPublisher:
|
||||
topics:
|
||||
topicName1: # replace with actual topic name as expected by subscribers
|
||||
queue:
|
||||
url: 'https://sqs.us-east-2.amazonaws.com/123456789012/MyQueue'
|
||||
region: us-east-2
|
||||
# visibilityTimeout - as HumanDuration; defaults to queue-based config
|
||||
# waitTime - as HumanDuration; defaults to max of 20 seconds (long polling)
|
||||
# timeout - as HumanDuration; timeout for the task execution
|
||||
# waitTimeAfterEmptyReceive - as HumanDuration; time to wait before a retry when there was no message.
|
||||
topicName2:
|
||||
# [...]
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
1. Install the [`events-backend` plugin](../events-backend/README.md).
|
||||
2. Install this module
|
||||
3. Add your configuration.
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
yarn add --cwd packages/backend @backstage/plugin-events-backend-module-aws-sqs
|
||||
```
|
||||
@@ -0,0 +1,29 @@
|
||||
## API Report File for "@backstage/plugin-events-backend-module-aws-sqs"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { BackendFeature } from '@backstage/backend-plugin-api';
|
||||
import { Config } from '@backstage/config';
|
||||
import { EventBroker } from '@backstage/plugin-events-node';
|
||||
import { EventPublisher } from '@backstage/plugin-events-node';
|
||||
import { Logger } from 'winston';
|
||||
import { PluginTaskScheduler } from '@backstage/backend-tasks';
|
||||
|
||||
// @public
|
||||
export class AwsSqsConsumingEventPublisher implements EventPublisher {
|
||||
// (undocumented)
|
||||
static fromConfig(env: {
|
||||
config: Config;
|
||||
logger: Logger;
|
||||
scheduler: PluginTaskScheduler;
|
||||
}): AwsSqsConsumingEventPublisher[];
|
||||
// (undocumented)
|
||||
setEventBroker(eventBroker: EventBroker): Promise<void>;
|
||||
}
|
||||
|
||||
// @alpha
|
||||
export const awsSqsConsumingEventPublisherEventsModule: (
|
||||
options?: undefined,
|
||||
) => BackendFeature;
|
||||
```
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { HumanDuration } from '@backstage/types';
|
||||
|
||||
export interface Config {
|
||||
events?: {
|
||||
modules?: {
|
||||
/**
|
||||
* events-backend-module-aws-sqs plugin configuration.
|
||||
*/
|
||||
awsSqs?: {
|
||||
/**
|
||||
* Configuration for AwsSqsConsumingEventPublisher.
|
||||
*/
|
||||
awsSqsConsumingEventPublisher?: {
|
||||
/**
|
||||
* Contains a record per topic for which an AWS SQS queue
|
||||
* should be used as source of events.
|
||||
*/
|
||||
topics: Record<
|
||||
string,
|
||||
{
|
||||
/**
|
||||
* (Required) Queue-related configuration.
|
||||
*/
|
||||
queue: {
|
||||
/**
|
||||
* (Required) The region of the AWS SQS queue.
|
||||
*/
|
||||
region: string;
|
||||
/**
|
||||
* (Required) The absolute URL for the AWS SQS queue to be used.
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* (Optional) Visibility timeout for messages in flight.
|
||||
*/
|
||||
visibilityTimeout: HumanDuration;
|
||||
/**
|
||||
* (Optional) Wait time when polling for available messages.
|
||||
* Default: 20 seconds.
|
||||
*/
|
||||
waitTime: HumanDuration;
|
||||
};
|
||||
/**
|
||||
* (Optional) Timeout for the task execution which includes polling for messages
|
||||
* and publishing the events to the event broker
|
||||
* and the wait time after empty receives.
|
||||
*
|
||||
* Must be greater than `queue.waitTime` + `waitTimeAfterEmptyReceive`.
|
||||
*/
|
||||
timeout: HumanDuration;
|
||||
/**
|
||||
* (Optional) Wait time before polling again if no message was received.
|
||||
* Default: 1 minute.
|
||||
*/
|
||||
waitTimeAfterEmptyReceive: HumanDuration;
|
||||
}
|
||||
>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "@backstage/plugin-events-backend-module-aws-sqs",
|
||||
"version": "0.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"alphaTypes": "dist/index.alpha.d.ts",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "backend-plugin-module"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "backstage-cli package start",
|
||||
"build": "backstage-cli package build --experimental-type-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": {
|
||||
"@aws-sdk/client-sqs": "^3.0.0",
|
||||
"@backstage/backend-plugin-api": "workspace:^",
|
||||
"@backstage/backend-tasks": "workspace:^",
|
||||
"@backstage/config": "workspace:^",
|
||||
"@backstage/plugin-events-node": "workspace:^",
|
||||
"@backstage/types": "workspace:^",
|
||||
"luxon": "^3.0.0",
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-common": "workspace:^",
|
||||
"@backstage/backend-test-utils": "workspace:^",
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@backstage/plugin-events-backend-test-utils": "workspace:^",
|
||||
"aws-sdk-client-mock": "^2.0.0"
|
||||
},
|
||||
"files": [
|
||||
"alpha",
|
||||
"config.d.ts",
|
||||
"dist"
|
||||
],
|
||||
"configSchema": "config.d.ts"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The module "sqs" for the Backstage backend plugin "events"
|
||||
* adding an AWS SQS-based publisher,
|
||||
* receiving events from an AWS SQS queue and passing it to the
|
||||
* internal event broker.
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export { AwsSqsConsumingEventPublisher } from './publisher/AwsSqsConsumingEventPublisher';
|
||||
export { awsSqsConsumingEventPublisherEventsModule } from './service/AwsSqsConsumingEventPublisherEventsModule';
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
DeleteMessageBatchCommand,
|
||||
ReceiveMessageCommand,
|
||||
SQSClient,
|
||||
} from '@aws-sdk/client-sqs';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { PluginTaskScheduler } from '@backstage/backend-tasks';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { TestEventBroker } from '@backstage/plugin-events-backend-test-utils';
|
||||
import { mockClient } from 'aws-sdk-client-mock';
|
||||
import { AwsSqsConsumingEventPublisher } from './AwsSqsConsumingEventPublisher';
|
||||
|
||||
describe('AwsSqsConsumingEventPublisher', () => {
|
||||
it('creates one publisher instance per configured topic', async () => {
|
||||
const config = new ConfigReader({
|
||||
events: {
|
||||
modules: {
|
||||
awsSqs: {
|
||||
awsSqsConsumingEventPublisher: {
|
||||
topics: {
|
||||
fake1: {
|
||||
queue: {
|
||||
region: 'eu-west-1',
|
||||
url: 'https://fake1.queue.url',
|
||||
},
|
||||
},
|
||||
fake2: {
|
||||
queue: {
|
||||
region: 'us-east-1',
|
||||
url: 'https://fake2.queue.url',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const logger = getVoidLogger();
|
||||
const scheduler = {
|
||||
scheduleTask: jest.fn(),
|
||||
} as unknown as PluginTaskScheduler;
|
||||
|
||||
const publishers = AwsSqsConsumingEventPublisher.fromConfig({
|
||||
config,
|
||||
logger,
|
||||
scheduler,
|
||||
});
|
||||
expect(publishers.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('polling will be scheduled after connecting to the EventBroker', async () => {
|
||||
const config = new ConfigReader({
|
||||
events: {
|
||||
modules: {
|
||||
awsSqs: {
|
||||
awsSqsConsumingEventPublisher: {
|
||||
topics: {
|
||||
fake1: {
|
||||
queue: {
|
||||
region: 'eu-west-1',
|
||||
url: 'https://fake1.queue.url',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const logger = getVoidLogger();
|
||||
const scheduler = {
|
||||
scheduleTask: jest.fn(),
|
||||
} as unknown as PluginTaskScheduler;
|
||||
|
||||
const publishers = AwsSqsConsumingEventPublisher.fromConfig({
|
||||
config,
|
||||
logger,
|
||||
scheduler,
|
||||
});
|
||||
expect(publishers.length).toEqual(1);
|
||||
|
||||
const publisher = publishers[0];
|
||||
|
||||
const eventBroker = new TestEventBroker();
|
||||
await publisher.setEventBroker(eventBroker);
|
||||
|
||||
// publisher.connect(..) was causing the polling for events to be scheduled
|
||||
expect(scheduler.scheduleTask).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: 'events.awsSqs.publisher:fake1',
|
||||
frequency: { seconds: 0 },
|
||||
timeout: { seconds: 260 },
|
||||
scope: 'local',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('publishes events for received messages and deletes them in bulk', async () => {
|
||||
const config = new ConfigReader({
|
||||
events: {
|
||||
modules: {
|
||||
awsSqs: {
|
||||
awsSqsConsumingEventPublisher: {
|
||||
topics: {
|
||||
fake1: {
|
||||
queue: {
|
||||
region: 'eu-west-1',
|
||||
url: 'https://fake1.queue.url',
|
||||
},
|
||||
waitTimeAfterEmptyReceive: { seconds: 1 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const logger = getVoidLogger();
|
||||
let taskFn: (() => Promise<void>) | undefined = undefined;
|
||||
const scheduler = {
|
||||
scheduleTask: (spec: { fn: () => Promise<void> }) => {
|
||||
taskFn = spec.fn;
|
||||
},
|
||||
} as unknown as PluginTaskScheduler;
|
||||
|
||||
// on the first attempt, we will return 1 message and 0 messages afterwards
|
||||
const sqsMock = mockClient(SQSClient);
|
||||
sqsMock
|
||||
.on(ReceiveMessageCommand, {
|
||||
MaxNumberOfMessages: 10,
|
||||
QueueUrl: 'https://fake1.queue.url',
|
||||
WaitTimeSeconds: 20,
|
||||
})
|
||||
.resolvesOnce({
|
||||
Messages: [],
|
||||
})
|
||||
.resolvesOnce({
|
||||
Messages: [
|
||||
{
|
||||
Body: '{"event":"payload1"}',
|
||||
ReceiptHandle: 'fake-handle1',
|
||||
MessageAttributes: {
|
||||
'X-Custom-Attr': {
|
||||
DataType: 'String',
|
||||
StringValue: 'value',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Body: '{"event":"payload2"}',
|
||||
ReceiptHandle: 'fake-handle2',
|
||||
},
|
||||
],
|
||||
})
|
||||
.on(DeleteMessageBatchCommand, {
|
||||
Entries: [
|
||||
{
|
||||
Id: 'message-0',
|
||||
ReceiptHandle: 'fake-handle1',
|
||||
},
|
||||
{
|
||||
Id: 'message-1',
|
||||
ReceiptHandle: 'fake-handle2',
|
||||
},
|
||||
],
|
||||
QueueUrl: 'https://fake1.queue.url',
|
||||
})
|
||||
.resolvesOnce({
|
||||
Failed: [
|
||||
{
|
||||
Id: 'message-1',
|
||||
Message: 'test failure',
|
||||
SenderFault: true,
|
||||
Code: '400',
|
||||
},
|
||||
],
|
||||
Successful: [{ Id: 'message-0' }],
|
||||
});
|
||||
|
||||
const publishers = AwsSqsConsumingEventPublisher.fromConfig({
|
||||
config,
|
||||
logger,
|
||||
scheduler,
|
||||
});
|
||||
expect(publishers.length).toEqual(1);
|
||||
const publisher = publishers[0];
|
||||
|
||||
const eventBroker = new TestEventBroker();
|
||||
await publisher.setEventBroker(eventBroker);
|
||||
|
||||
await taskFn!();
|
||||
await taskFn!();
|
||||
await taskFn!();
|
||||
|
||||
expect(eventBroker.published.length).toEqual(2);
|
||||
expect(eventBroker.published[0].topic).toEqual('fake1');
|
||||
expect(eventBroker.published[0].eventPayload).toEqual({
|
||||
event: 'payload1',
|
||||
});
|
||||
expect(eventBroker.published[0].metadata).toEqual({
|
||||
'X-Custom-Attr': 'value',
|
||||
});
|
||||
|
||||
expect(eventBroker.published[1].topic).toEqual('fake1');
|
||||
expect(eventBroker.published[1].eventPayload).toEqual({
|
||||
event: 'payload2',
|
||||
});
|
||||
expect(eventBroker.published[1].metadata).toEqual({});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
DeleteMessageBatchCommand,
|
||||
Message,
|
||||
ReceiveMessageCommand,
|
||||
ReceiveMessageCommandInput,
|
||||
SQSClient,
|
||||
} from '@aws-sdk/client-sqs';
|
||||
import { PluginTaskScheduler } from '@backstage/backend-tasks';
|
||||
import { Config } from '@backstage/config';
|
||||
import { EventBroker, EventPublisher } from '@backstage/plugin-events-node';
|
||||
import { Logger } from 'winston';
|
||||
import { AwsSqsEventSourceConfig, readConfig } from './config';
|
||||
|
||||
/**
|
||||
* Publishes events received from an AWS SQS queue.
|
||||
* The message payload will be used as event payload and passed to registered subscribers.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
// TODO(pjungermann): add prom metrics? (see plugins/catalog-backend/src/util/metrics.ts, etc.)
|
||||
export class AwsSqsConsumingEventPublisher implements EventPublisher {
|
||||
private readonly topic: string;
|
||||
private readonly receiveParams: ReceiveMessageCommandInput;
|
||||
private readonly sqs: SQSClient;
|
||||
private readonly queueUrl: string;
|
||||
private readonly taskTimeoutSeconds: number;
|
||||
private readonly waitTimeAfterEmptyReceiveMs;
|
||||
private eventBroker?: EventBroker;
|
||||
|
||||
static fromConfig(env: {
|
||||
config: Config;
|
||||
logger: Logger;
|
||||
scheduler: PluginTaskScheduler;
|
||||
}): AwsSqsConsumingEventPublisher[] {
|
||||
return readConfig(env.config).map(
|
||||
config =>
|
||||
new AwsSqsConsumingEventPublisher(env.logger, env.scheduler, config),
|
||||
);
|
||||
}
|
||||
|
||||
private constructor(
|
||||
private readonly logger: Logger,
|
||||
private readonly scheduler: PluginTaskScheduler,
|
||||
config: AwsSqsEventSourceConfig,
|
||||
) {
|
||||
this.topic = config.topic;
|
||||
|
||||
this.receiveParams = {
|
||||
MaxNumberOfMessages: 10,
|
||||
MessageAttributeNames: ['All'],
|
||||
QueueUrl: config.queueUrl,
|
||||
VisibilityTimeout: config.visibilityTimeout?.as('seconds'),
|
||||
WaitTimeSeconds: config.pollingWaitTime.as('seconds'),
|
||||
};
|
||||
this.sqs = new SQSClient({ region: config.region });
|
||||
this.queueUrl = config.queueUrl;
|
||||
|
||||
this.taskTimeoutSeconds = config.timeout.as('seconds');
|
||||
this.waitTimeAfterEmptyReceiveMs =
|
||||
config.waitTimeAfterEmptyReceive.as('milliseconds');
|
||||
}
|
||||
|
||||
async setEventBroker(eventBroker: EventBroker): Promise<void> {
|
||||
this.eventBroker = eventBroker;
|
||||
return this.start();
|
||||
}
|
||||
|
||||
private async start(): Promise<void> {
|
||||
const id = `events.awsSqs.publisher:${this.topic}`;
|
||||
const logger = this.logger.child({
|
||||
class: AwsSqsConsumingEventPublisher.prototype.constructor.name,
|
||||
taskId: id,
|
||||
});
|
||||
|
||||
await this.scheduler.scheduleTask({
|
||||
id: id,
|
||||
frequency: { seconds: 0 },
|
||||
timeout: { seconds: this.taskTimeoutSeconds },
|
||||
scope: 'local',
|
||||
fn: async () => {
|
||||
try {
|
||||
const numMessages = await this.consumeMessages();
|
||||
if (numMessages === 0) {
|
||||
await this.sleep(this.waitTimeAfterEmptyReceiveMs);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async deleteMessages(messages?: Message[]): Promise<void> {
|
||||
if (!messages) {
|
||||
return;
|
||||
}
|
||||
|
||||
const deleteParams = {
|
||||
QueueUrl: this.queueUrl,
|
||||
Entries: messages.map((message, index) => {
|
||||
return {
|
||||
Id: message.MessageId ?? `message-${index}`,
|
||||
ReceiptHandle: message.ReceiptHandle,
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await this.sqs.send(
|
||||
new DeleteMessageBatchCommand(deleteParams),
|
||||
);
|
||||
if (result.Failed) {
|
||||
this.logger.error(
|
||||
`Failed to delete ${result.Failed!.length} of ${
|
||||
messages.length
|
||||
} messages from AWS SQS ${this.queueUrl}. First: ${
|
||||
result.Failed[0].Message
|
||||
}`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to delete message from AWS SQS ${this.queueUrl}`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async consumeMessages(): Promise<number> {
|
||||
try {
|
||||
const data = await this.sqs.send(
|
||||
new ReceiveMessageCommand(this.receiveParams),
|
||||
);
|
||||
|
||||
data.Messages?.forEach(message => {
|
||||
const eventPayload = JSON.parse(message.Body!);
|
||||
|
||||
const metadata: Record<string, string | string[]> = {};
|
||||
Object.keys(message.MessageAttributes ?? {}).forEach(key => {
|
||||
const attrValue = message.MessageAttributes![key];
|
||||
if (
|
||||
!attrValue ||
|
||||
!attrValue.DataType ||
|
||||
!['String', 'Number'].includes(attrValue.DataType)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const value = attrValue.StringListValues ?? attrValue.StringValue;
|
||||
if (value !== undefined) {
|
||||
metadata[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
this.eventBroker!.publish({
|
||||
topic: this.topic,
|
||||
eventPayload,
|
||||
metadata,
|
||||
});
|
||||
});
|
||||
await this.deleteMessages(data.Messages);
|
||||
return data.Messages?.length ?? 0;
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to receive events from AWS SQS ${this.queueUrl}`,
|
||||
error,
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private sleep(ms: number): Promise<void> {
|
||||
return new Promise<void>(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { readConfig } from './config';
|
||||
|
||||
describe('readConfig', () => {
|
||||
it('not configured', () => {
|
||||
const config = new ConfigReader({});
|
||||
|
||||
const publisherConfigs = readConfig(config);
|
||||
|
||||
expect(publisherConfigs.length).toBe(0);
|
||||
});
|
||||
|
||||
it('only required fields configured', () => {
|
||||
const config = new ConfigReader({
|
||||
events: {
|
||||
modules: {
|
||||
awsSqs: {
|
||||
awsSqsConsumingEventPublisher: {
|
||||
topics: {
|
||||
fake1: {
|
||||
queue: {
|
||||
region: 'eu-west-1',
|
||||
url: 'https://fake1.queue.url',
|
||||
},
|
||||
},
|
||||
fake2: {
|
||||
queue: {
|
||||
region: 'us-east-1',
|
||||
url: 'https://fake2.queue.url',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const publisherConfigs = readConfig(config);
|
||||
|
||||
expect(publisherConfigs.length).toBe(2);
|
||||
|
||||
expect(publisherConfigs[0].topic).toEqual('fake1');
|
||||
expect(publisherConfigs[0].region).toEqual('eu-west-1');
|
||||
expect(publisherConfigs[0].queueUrl).toEqual('https://fake1.queue.url');
|
||||
expect(publisherConfigs[0].pollingWaitTime.as('seconds')).toBe(20);
|
||||
expect(publisherConfigs[0].timeout.as('seconds')).toBe(260);
|
||||
expect(publisherConfigs[0].waitTimeAfterEmptyReceive.as('seconds')).toBe(
|
||||
60,
|
||||
);
|
||||
});
|
||||
|
||||
it('all fields configured', () => {
|
||||
const config = new ConfigReader({
|
||||
events: {
|
||||
modules: {
|
||||
awsSqs: {
|
||||
awsSqsConsumingEventPublisher: {
|
||||
topics: {
|
||||
fake1: {
|
||||
queue: {
|
||||
region: 'eu-west-1',
|
||||
url: 'https://fake1.queue.url',
|
||||
visibilityTimeout: { minutes: 5 },
|
||||
waitTime: { seconds: 10 },
|
||||
},
|
||||
timeout: { minutes: 5 },
|
||||
waitTimeAfterEmptyReceive: { seconds: 30 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const publisherConfigs = readConfig(config);
|
||||
|
||||
expect(publisherConfigs.length).toBe(1);
|
||||
|
||||
expect(publisherConfigs[0].topic).toEqual('fake1');
|
||||
expect(publisherConfigs[0].region).toEqual('eu-west-1');
|
||||
expect(publisherConfigs[0].queueUrl).toEqual('https://fake1.queue.url');
|
||||
expect(publisherConfigs[0].pollingWaitTime.as('seconds')).toBe(10);
|
||||
expect(publisherConfigs[0].timeout.as('seconds')).toBe(300);
|
||||
expect(publisherConfigs[0].waitTimeAfterEmptyReceive.as('seconds')).toBe(
|
||||
30,
|
||||
);
|
||||
});
|
||||
|
||||
it('fail on negative queue.waitTime', () => {
|
||||
const config = new ConfigReader({
|
||||
events: {
|
||||
modules: {
|
||||
awsSqs: {
|
||||
awsSqsConsumingEventPublisher: {
|
||||
topics: {
|
||||
fake1: {
|
||||
queue: {
|
||||
region: 'eu-west-1',
|
||||
url: 'https://fake1.queue.url',
|
||||
visibilityTimeout: { minutes: 5 },
|
||||
waitTime: { seconds: -10 },
|
||||
},
|
||||
timeout: { minutes: 5 },
|
||||
waitTimeAfterEmptyReceive: { seconds: 30 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(() => readConfig(config)).toThrow(
|
||||
'events.modules.awsSqs.awsSqsConsumingEventPublisher.topics.fake1.queue.waitTime must be within 0..20 seconds',
|
||||
);
|
||||
});
|
||||
|
||||
it('fail on too high queue.waitTime', () => {
|
||||
const config = new ConfigReader({
|
||||
events: {
|
||||
modules: {
|
||||
awsSqs: {
|
||||
awsSqsConsumingEventPublisher: {
|
||||
topics: {
|
||||
fake1: {
|
||||
queue: {
|
||||
region: 'eu-west-1',
|
||||
url: 'https://fake1.queue.url',
|
||||
visibilityTimeout: { minutes: 5 },
|
||||
waitTime: { seconds: 30 },
|
||||
},
|
||||
timeout: { minutes: 5 },
|
||||
waitTimeAfterEmptyReceive: { seconds: 30 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(() => readConfig(config)).toThrow(
|
||||
'events.modules.awsSqs.awsSqsConsumingEventPublisher.topics.fake1.queue.waitTime must be within 0..20 seconds',
|
||||
);
|
||||
});
|
||||
|
||||
it('fail on too low timeout', () => {
|
||||
const config = new ConfigReader({
|
||||
events: {
|
||||
modules: {
|
||||
awsSqs: {
|
||||
awsSqsConsumingEventPublisher: {
|
||||
topics: {
|
||||
fake1: {
|
||||
queue: {
|
||||
region: 'eu-west-1',
|
||||
url: 'https://fake1.queue.url',
|
||||
visibilityTimeout: { minutes: 5 },
|
||||
waitTime: { seconds: 10 },
|
||||
},
|
||||
timeout: { seconds: 10 },
|
||||
waitTimeAfterEmptyReceive: { seconds: 30 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(() => readConfig(config)).toThrow(
|
||||
'The events.modules.awsSqs.awsSqsConsumingEventPublisher.topics.fake1.timeout must be greater than events.modules.awsSqs.awsSqsConsumingEventPublisher.topics.fake1.queue.waitTime',
|
||||
);
|
||||
});
|
||||
|
||||
it('fail on negative waitTimeAfterEmptyReceive', () => {
|
||||
const config = new ConfigReader({
|
||||
events: {
|
||||
modules: {
|
||||
awsSqs: {
|
||||
awsSqsConsumingEventPublisher: {
|
||||
topics: {
|
||||
fake1: {
|
||||
queue: {
|
||||
region: 'eu-west-1',
|
||||
url: 'https://fake1.queue.url',
|
||||
visibilityTimeout: { minutes: 5 },
|
||||
waitTime: { seconds: 10 },
|
||||
},
|
||||
timeout: { minutes: 5 },
|
||||
waitTimeAfterEmptyReceive: { seconds: -30 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(() => readConfig(config)).toThrow(
|
||||
'The events.modules.awsSqs.awsSqsConsumingEventPublisher.topics.fake1.waitTimeAfterEmptyReceive must not be negative',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import { HumanDuration, JsonObject } from '@backstage/types';
|
||||
import { Duration } from 'luxon';
|
||||
|
||||
const CONFIG_PREFIX_MODULE = 'events.modules.awsSqs.';
|
||||
const CONFIG_PREFIX_PUBLISHER = `${CONFIG_PREFIX_MODULE}awsSqsConsumingEventPublisher.`;
|
||||
const DEFAULT_WAIT_TIME_AFTER_EMPTY_RECEIVE = { minutes: 1 };
|
||||
const MAX_WAIT_SECONDS = 20;
|
||||
|
||||
export interface AwsSqsEventSourceConfig {
|
||||
pollingWaitTime: Duration;
|
||||
queueUrl: string;
|
||||
region: string;
|
||||
timeout: Duration;
|
||||
topic: string;
|
||||
visibilityTimeout?: Duration;
|
||||
waitTimeAfterEmptyReceive: Duration;
|
||||
}
|
||||
|
||||
// TODO(pjungermann): validation could be improved similar to `convertToHumanDuration` at @backstage/backend-tasks
|
||||
function readOptionalHumanDuration(
|
||||
config: Config,
|
||||
key: string,
|
||||
): HumanDuration | undefined {
|
||||
return config.getOptional<JsonObject>(key) as HumanDuration;
|
||||
}
|
||||
|
||||
function readOptionalDuration(
|
||||
config: Config,
|
||||
key: string,
|
||||
): Duration | undefined {
|
||||
const duration = readOptionalHumanDuration(config, key);
|
||||
return duration ? Duration.fromObject(duration) : undefined;
|
||||
}
|
||||
|
||||
export function readConfig(config: Config): AwsSqsEventSourceConfig[] {
|
||||
const key = `${CONFIG_PREFIX_PUBLISHER}topics`;
|
||||
const topics = config.getOptionalConfig(key);
|
||||
|
||||
return (
|
||||
topics?.keys()?.map(topic => {
|
||||
const topicConfig = topics.getConfig(topic);
|
||||
const keyPrefix = `${key}.${topic}.`;
|
||||
|
||||
// queue config:
|
||||
const pollingWaitTime = Duration.fromObject(
|
||||
readOptionalHumanDuration(topicConfig, 'queue.waitTime') ?? {
|
||||
seconds: MAX_WAIT_SECONDS,
|
||||
},
|
||||
);
|
||||
if (
|
||||
pollingWaitTime.valueOf() < 0 ||
|
||||
pollingWaitTime.as('seconds') > MAX_WAIT_SECONDS
|
||||
) {
|
||||
throw new Error(
|
||||
`${keyPrefix}queue.waitTime must be within 0..${MAX_WAIT_SECONDS} seconds.`,
|
||||
);
|
||||
}
|
||||
const queueUrl = topicConfig.getString('queue.url');
|
||||
const region = topicConfig.getString('queue.region');
|
||||
const visibilityTimeout = readOptionalDuration(
|
||||
topicConfig,
|
||||
'queue.visibilityTimeout',
|
||||
);
|
||||
|
||||
// task:
|
||||
const waitTimeAfterEmptyReceive = Duration.fromObject(
|
||||
readOptionalHumanDuration(topicConfig, 'waitTimeAfterEmptyReceive') ??
|
||||
DEFAULT_WAIT_TIME_AFTER_EMPTY_RECEIVE,
|
||||
);
|
||||
if (waitTimeAfterEmptyReceive.valueOf() < 0) {
|
||||
throw new Error(
|
||||
`The ${keyPrefix}waitTimeAfterEmptyReceive must not be negative.`,
|
||||
);
|
||||
}
|
||||
const timeout =
|
||||
readOptionalDuration(topicConfig, 'timeout') ??
|
||||
pollingWaitTime
|
||||
.plus(waitTimeAfterEmptyReceive)
|
||||
.plus(Duration.fromObject({ seconds: 180 }));
|
||||
if (
|
||||
timeout.valueOf() <=
|
||||
pollingWaitTime.valueOf() + waitTimeAfterEmptyReceive.valueOf()
|
||||
) {
|
||||
throw new Error(
|
||||
`The ${keyPrefix}timeout must be greater than ${keyPrefix}queue.waitTime + ${keyPrefix}waitTimeAfterEmptyReceive.`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
pollingWaitTime,
|
||||
queueUrl,
|
||||
region,
|
||||
timeout,
|
||||
topic,
|
||||
visibilityTimeout,
|
||||
waitTimeAfterEmptyReceive,
|
||||
};
|
||||
}) ?? []
|
||||
);
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import {
|
||||
configServiceRef,
|
||||
loggerServiceRef,
|
||||
schedulerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { eventsExtensionPoint } from '@backstage/plugin-events-node';
|
||||
import { TestEventBroker } from '@backstage/plugin-events-backend-test-utils';
|
||||
import { awsSqsConsumingEventPublisherEventsModule } from './AwsSqsConsumingEventPublisherEventsModule';
|
||||
import { AwsSqsConsumingEventPublisher } from '../publisher/AwsSqsConsumingEventPublisher';
|
||||
|
||||
describe('awsSqsEventsModule', () => {
|
||||
it('should be correctly wired and set up', async () => {
|
||||
const config = new ConfigReader({
|
||||
events: {
|
||||
modules: {
|
||||
awsSqs: {
|
||||
awsSqsConsumingEventPublisher: {
|
||||
topics: {
|
||||
fake1: {
|
||||
queue: {
|
||||
region: 'eu-west-1',
|
||||
url: 'https://fake1.queue.url',
|
||||
},
|
||||
},
|
||||
fake2: {
|
||||
queue: {
|
||||
region: 'us-east-1',
|
||||
url: 'https://fake2.queue.url',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
let addedPublishers: AwsSqsConsumingEventPublisher[] | undefined;
|
||||
const extensionPoint = {
|
||||
addPublishers: (publishers: any) => {
|
||||
addedPublishers = publishers;
|
||||
},
|
||||
};
|
||||
|
||||
const scheduler = {
|
||||
scheduleTask: jest.fn(),
|
||||
};
|
||||
|
||||
await startTestBackend({
|
||||
extensionPoints: [[eventsExtensionPoint, extensionPoint]],
|
||||
services: [
|
||||
[configServiceRef, config],
|
||||
[loggerServiceRef, getVoidLogger()],
|
||||
[schedulerServiceRef, scheduler],
|
||||
],
|
||||
features: [awsSqsConsumingEventPublisherEventsModule()],
|
||||
});
|
||||
|
||||
expect(addedPublishers).not.toBeUndefined();
|
||||
expect(addedPublishers!.length).toEqual(2);
|
||||
|
||||
const eventBroker = new TestEventBroker();
|
||||
await Promise.all(
|
||||
addedPublishers!.map(publisher => publisher.setEventBroker(eventBroker)),
|
||||
);
|
||||
|
||||
// publisher.connect(..) was causing the polling for events to be scheduled
|
||||
expect(scheduler.scheduleTask).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ id: 'events.awsSqs.publisher:fake1' }),
|
||||
);
|
||||
expect(scheduler.scheduleTask).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ id: 'events.awsSqs.publisher:fake2' }),
|
||||
);
|
||||
});
|
||||
});
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
configServiceRef,
|
||||
createBackendModule,
|
||||
loggerServiceRef,
|
||||
loggerToWinstonLogger,
|
||||
schedulerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { eventsExtensionPoint } from '@backstage/plugin-events-node';
|
||||
import { AwsSqsConsumingEventPublisher } from '../publisher/AwsSqsConsumingEventPublisher';
|
||||
|
||||
/**
|
||||
* AWS SQS module for the Events plugin.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export const awsSqsConsumingEventPublisherEventsModule = createBackendModule({
|
||||
pluginId: 'events',
|
||||
moduleId: 'awsSqsConsumingEventPublisherEventsModule',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
config: configServiceRef,
|
||||
events: eventsExtensionPoint,
|
||||
logger: loggerServiceRef,
|
||||
scheduler: schedulerServiceRef,
|
||||
},
|
||||
async init({ config, events, logger, scheduler }) {
|
||||
const winstonLogger = loggerToWinstonLogger(logger);
|
||||
const sqs = AwsSqsConsumingEventPublisher.fromConfig({
|
||||
config: config,
|
||||
logger: winstonLogger,
|
||||
scheduler: scheduler,
|
||||
});
|
||||
|
||||
events.addPublishers(sqs);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,43 @@
|
||||
# events-backend-module-azure
|
||||
|
||||
Welcome to the `events-backend-module-azure` backend plugin!
|
||||
|
||||
This plugin is a module for the `events-backend` backend plugin
|
||||
and extends it with an `AzureDevOpsEventRouter`.
|
||||
|
||||
The event router will subscribe to the topic `azureDevOps`
|
||||
and route the events to more concrete topics based on the value
|
||||
of the provided `$.eventType` payload field.
|
||||
|
||||
Examples:
|
||||
|
||||
| `$.eventType` | topic |
|
||||
| ------------------------- | ------------------------------------- |
|
||||
| `git.push` | `azureDevOps.git.push` |
|
||||
| `git.pullrequest.created` | `azureDevOps.git.pullrequest.created` |
|
||||
|
||||
Please find all possible webhook event types at the
|
||||
[official documentation of events](https://learn.microsoft.com/en-us/azure/devops/service-hooks/events?source=recommendations&view=azure-devops)
|
||||
and [webhooks](https://learn.microsoft.com/en-us/azure/devops/service-hooks/services/webhooks?view=azure-devops).
|
||||
|
||||
## Installation
|
||||
|
||||
Install the [`events-backend` plugin](../events-backend/README.md).
|
||||
|
||||
Install this module:
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
yarn add --cwd packages/backend @backstage/plugin-events-backend-module-azure
|
||||
```
|
||||
|
||||
Add the event router to the `EventsBackend`:
|
||||
|
||||
```diff
|
||||
+const githubEventRouter = new AzureDevOpsEventRouter();
|
||||
|
||||
EventsBackend
|
||||
+ .addPublishers(githubEventRouter)
|
||||
+ .addSubscribers(githubEventRouter);
|
||||
// [...]
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
## API Report File for "@backstage/plugin-events-backend-module-azure"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { BackendFeature } from '@backstage/backend-plugin-api';
|
||||
import { EventParams } from '@backstage/plugin-events-node';
|
||||
import { SubTopicEventRouter } from '@backstage/plugin-events-node';
|
||||
|
||||
// @public
|
||||
export class AzureDevOpsEventRouter extends SubTopicEventRouter {
|
||||
constructor();
|
||||
// (undocumented)
|
||||
protected determineSubTopic(params: EventParams): string | undefined;
|
||||
}
|
||||
|
||||
// @alpha
|
||||
export const azureDevOpsEventRouterEventsModule: (
|
||||
options?: undefined,
|
||||
) => BackendFeature;
|
||||
```
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@backstage/plugin-events-backend-module-azure",
|
||||
"version": "0.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"alphaTypes": "dist/index.alpha.d.ts",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "backend-plugin-module"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "backstage-cli package start",
|
||||
"build": "backstage-cli package build --experimental-type-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-plugin-api": "workspace:^",
|
||||
"@backstage/plugin-events-node": "workspace:^",
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-test-utils": "workspace:^",
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@backstage/plugin-events-backend-test-utils": "workspace:^",
|
||||
"supertest": "^6.1.3"
|
||||
},
|
||||
"files": [
|
||||
"alpha",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The module "azure" for the Backstage backend plugin "events-backend"
|
||||
* adding an event router for Azure DevOps.
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export { AzureDevOpsEventRouter } from './router/AzureDevOpsEventRouter';
|
||||
export { azureDevOpsEventRouterEventsModule } from './service/AzureDevOpsEventRouterEventsModule';
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { TestEventBroker } from '@backstage/plugin-events-backend-test-utils';
|
||||
import { AzureDevOpsEventRouter } from './AzureDevOpsEventRouter';
|
||||
|
||||
describe('AzureDevOpsEventRouter', () => {
|
||||
const eventRouter = new AzureDevOpsEventRouter();
|
||||
const topic = 'azureDevOps';
|
||||
const eventPayload = { eventType: 'test.type', test: 'payload' };
|
||||
const metadata = {};
|
||||
|
||||
it('no $.eventType', () => {
|
||||
const eventBroker = new TestEventBroker();
|
||||
eventRouter.setEventBroker(eventBroker);
|
||||
|
||||
eventRouter.onEvent({
|
||||
topic,
|
||||
eventPayload: { invalid: 'payload' },
|
||||
metadata,
|
||||
});
|
||||
|
||||
expect(eventBroker.published).toEqual([]);
|
||||
});
|
||||
|
||||
it('with $.eventType', () => {
|
||||
const eventBroker = new TestEventBroker();
|
||||
eventRouter.setEventBroker(eventBroker);
|
||||
|
||||
eventRouter.onEvent({ topic, eventPayload, metadata });
|
||||
|
||||
expect(eventBroker.published.length).toBe(1);
|
||||
expect(eventBroker.published[0].topic).toEqual('azureDevOps.test.type');
|
||||
expect(eventBroker.published[0].eventPayload).toEqual(eventPayload);
|
||||
expect(eventBroker.published[0].metadata).toEqual(metadata);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EventParams,
|
||||
SubTopicEventRouter,
|
||||
} from '@backstage/plugin-events-node';
|
||||
|
||||
/**
|
||||
* Subscribes to the generic `azureDevOps` topic
|
||||
* and publishes the events under the more concrete sub-topic
|
||||
* depending on the `$.eventType` provided.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class AzureDevOpsEventRouter extends SubTopicEventRouter {
|
||||
constructor() {
|
||||
super('azureDevOps');
|
||||
}
|
||||
|
||||
protected determineSubTopic(params: EventParams): string | undefined {
|
||||
if ('eventType' in (params.eventPayload as object)) {
|
||||
const payload = params.eventPayload as { eventType: string };
|
||||
return payload.eventType;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { eventsExtensionPoint } from '@backstage/plugin-events-node';
|
||||
import { azureDevOpsEventRouterEventsModule } from './AzureDevOpsEventRouterEventsModule';
|
||||
import { AzureDevOpsEventRouter } from '../router/AzureDevOpsEventRouter';
|
||||
|
||||
describe('azureDevOpsEventRouterEventsModule', () => {
|
||||
it('should be correctly wired and set up', async () => {
|
||||
let addedPublisher: AzureDevOpsEventRouter | undefined;
|
||||
let addedSubscriber: AzureDevOpsEventRouter | undefined;
|
||||
const extensionPoint = {
|
||||
addPublishers: (publisher: any) => {
|
||||
addedPublisher = publisher;
|
||||
},
|
||||
addSubscribers: (subscriber: any) => {
|
||||
addedSubscriber = subscriber;
|
||||
},
|
||||
};
|
||||
|
||||
await startTestBackend({
|
||||
extensionPoints: [[eventsExtensionPoint, extensionPoint]],
|
||||
services: [],
|
||||
features: [azureDevOpsEventRouterEventsModule()],
|
||||
});
|
||||
|
||||
expect(addedPublisher).not.toBeUndefined();
|
||||
expect(addedPublisher).toBeInstanceOf(AzureDevOpsEventRouter);
|
||||
expect(addedSubscriber).not.toBeUndefined();
|
||||
expect(addedSubscriber).toBeInstanceOf(AzureDevOpsEventRouter);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createBackendModule } from '@backstage/backend-plugin-api';
|
||||
import { eventsExtensionPoint } from '@backstage/plugin-events-node';
|
||||
import { AzureDevOpsEventRouter } from '../router/AzureDevOpsEventRouter';
|
||||
|
||||
/**
|
||||
* Module for the events-backend plugin, adding an event router for Azure DevOps.
|
||||
*
|
||||
* Registers the {@link AzureDevOpsEventRouter}.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export const azureDevOpsEventRouterEventsModule = createBackendModule({
|
||||
pluginId: 'events',
|
||||
moduleId: 'azureDevOpsEventRouter',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
events: eventsExtensionPoint,
|
||||
},
|
||||
async init({ events }) {
|
||||
const eventRouter = new AzureDevOpsEventRouter();
|
||||
|
||||
events.addPublishers(eventRouter);
|
||||
events.addSubscribers(eventRouter);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,43 @@
|
||||
# events-backend-module-bitbucket-cloud
|
||||
|
||||
Welcome to the `events-backend-module-bitbucket-cloud` backend plugin!
|
||||
|
||||
This plugin is a module for the `events-backend` backend plugin
|
||||
and extends it with an `BitbucketCloudEventRouter`.
|
||||
|
||||
The event router will subscribe to the topic `bitbucketCloud`
|
||||
and route the events to more concrete topics based on the value
|
||||
of the provided `x-event-key` metadata field.
|
||||
|
||||
Examples:
|
||||
|
||||
| x-event-key | topic |
|
||||
| --------------------- | ------------------------------------ |
|
||||
| `repo:push` | `bitbucketCloud.repo:push` |
|
||||
| `repo:updated` | `bitbucketCloud.repo:updated` |
|
||||
| `pullrequest:created` | `bitbucketCloud.pullrequest:created` |
|
||||
|
||||
Please find all possible webhook event types at the
|
||||
[official documentation](https://support.atlassian.com/bitbucket-cloud/docs/event-payloads/).
|
||||
|
||||
## Installation
|
||||
|
||||
Install the [`events-backend` plugin](../events-backend/README.md).
|
||||
|
||||
Install this module:
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
yarn add --cwd packages/backend @backstage/plugin-events-backend-module-bitbucket-cloud
|
||||
```
|
||||
|
||||
Add the event router to the `EventsBackend`:
|
||||
|
||||
```diff
|
||||
+const bitbucketCloudEventRouter = new BitbucketCloudEventRouter();
|
||||
|
||||
EventsBackend
|
||||
+ .addPublishers(bitbucketCloudEventRouter)
|
||||
+ .addSubscribers(bitbucketCloudEventRouter);
|
||||
// [...]
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
## API Report File for "@backstage/plugin-events-backend-module-bitbucket-cloud"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { BackendFeature } from '@backstage/backend-plugin-api';
|
||||
import { EventParams } from '@backstage/plugin-events-node';
|
||||
import { SubTopicEventRouter } from '@backstage/plugin-events-node';
|
||||
|
||||
// @public
|
||||
export class BitbucketCloudEventRouter extends SubTopicEventRouter {
|
||||
constructor();
|
||||
// (undocumented)
|
||||
protected determineSubTopic(params: EventParams): string | undefined;
|
||||
}
|
||||
|
||||
// @alpha
|
||||
export const bitbucketCloudEventRouterEventsModule: (
|
||||
options?: undefined,
|
||||
) => BackendFeature;
|
||||
```
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@backstage/plugin-events-backend-module-bitbucket-cloud",
|
||||
"version": "0.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"alphaTypes": "dist/index.alpha.d.ts",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "backend-plugin-module"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "backstage-cli package start",
|
||||
"build": "backstage-cli package build --experimental-type-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-plugin-api": "workspace:^",
|
||||
"@backstage/plugin-events-node": "workspace:^",
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-test-utils": "workspace:^",
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@backstage/plugin-events-backend-test-utils": "workspace:^",
|
||||
"supertest": "^6.1.3"
|
||||
},
|
||||
"files": [
|
||||
"alpha",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The module "bitbucket-cloud" for the Backstage backend plugin "events-backend"
|
||||
* adding an event router for Bitbucket Cloud.
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export { BitbucketCloudEventRouter } from './router/BitbucketCloudEventRouter';
|
||||
export { bitbucketCloudEventRouterEventsModule } from './service/BitbucketCloudEventRouterEventsModule';
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { TestEventBroker } from '@backstage/plugin-events-backend-test-utils';
|
||||
import { BitbucketCloudEventRouter } from './BitbucketCloudEventRouter';
|
||||
|
||||
describe('BitbucketCloudEventRouter', () => {
|
||||
const eventRouter = new BitbucketCloudEventRouter();
|
||||
const topic = 'bitbucketCloud';
|
||||
const eventPayload = { test: 'payload' };
|
||||
const metadata = { 'x-event-key': 'test:type' };
|
||||
|
||||
it('no x-event-key', () => {
|
||||
const eventBroker = new TestEventBroker();
|
||||
eventRouter.setEventBroker(eventBroker);
|
||||
|
||||
eventRouter.onEvent({ topic, eventPayload });
|
||||
|
||||
expect(eventBroker.published).toEqual([]);
|
||||
});
|
||||
|
||||
it('with x-event-key', () => {
|
||||
const eventBroker = new TestEventBroker();
|
||||
eventRouter.setEventBroker(eventBroker);
|
||||
|
||||
eventRouter.onEvent({ topic, eventPayload, metadata });
|
||||
|
||||
expect(eventBroker.published.length).toBe(1);
|
||||
expect(eventBroker.published[0].topic).toEqual('bitbucketCloud.test:type');
|
||||
expect(eventBroker.published[0].eventPayload).toEqual(eventPayload);
|
||||
expect(eventBroker.published[0].metadata).toEqual(metadata);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EventParams,
|
||||
SubTopicEventRouter,
|
||||
} from '@backstage/plugin-events-node';
|
||||
|
||||
/**
|
||||
* Subscribes to the generic `bitbucketCloud` topic
|
||||
* and publishes the events under the more concrete sub-topic
|
||||
* depending on the `x-event-key` provided.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class BitbucketCloudEventRouter extends SubTopicEventRouter {
|
||||
constructor() {
|
||||
super('bitbucketCloud');
|
||||
}
|
||||
|
||||
protected determineSubTopic(params: EventParams): string | undefined {
|
||||
return params.metadata?.['x-event-key'] as string | undefined;
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { eventsExtensionPoint } from '@backstage/plugin-events-node';
|
||||
import { bitbucketCloudEventRouterEventsModule } from './BitbucketCloudEventRouterEventsModule';
|
||||
import { BitbucketCloudEventRouter } from '../router/BitbucketCloudEventRouter';
|
||||
|
||||
describe('bitbucketCloudEventRouterEventsModule', () => {
|
||||
it('should be correctly wired and set up', async () => {
|
||||
let addedPublisher: BitbucketCloudEventRouter | undefined;
|
||||
let addedSubscriber: BitbucketCloudEventRouter | undefined;
|
||||
const extensionPoint = {
|
||||
addPublishers: (publisher: any) => {
|
||||
addedPublisher = publisher;
|
||||
},
|
||||
addSubscribers: (subscriber: any) => {
|
||||
addedSubscriber = subscriber;
|
||||
},
|
||||
};
|
||||
|
||||
await startTestBackend({
|
||||
extensionPoints: [[eventsExtensionPoint, extensionPoint]],
|
||||
services: [],
|
||||
features: [bitbucketCloudEventRouterEventsModule()],
|
||||
});
|
||||
|
||||
expect(addedPublisher).not.toBeUndefined();
|
||||
expect(addedPublisher).toBeInstanceOf(BitbucketCloudEventRouter);
|
||||
expect(addedSubscriber).not.toBeUndefined();
|
||||
expect(addedSubscriber).toBeInstanceOf(BitbucketCloudEventRouter);
|
||||
});
|
||||
});
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createBackendModule } from '@backstage/backend-plugin-api';
|
||||
import { eventsExtensionPoint } from '@backstage/plugin-events-node';
|
||||
import { BitbucketCloudEventRouter } from '../router/BitbucketCloudEventRouter';
|
||||
|
||||
/**
|
||||
* Module for the events-backend plugin, adding an event router for Bitbucket Cloud.
|
||||
*
|
||||
* Registers the {@link BitbucketCloudEventRouter}.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export const bitbucketCloudEventRouterEventsModule = createBackendModule({
|
||||
pluginId: 'events',
|
||||
moduleId: 'bitbucketCloudEventRouter',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
events: eventsExtensionPoint,
|
||||
},
|
||||
async init({ events }) {
|
||||
const eventRouter = new BitbucketCloudEventRouter();
|
||||
|
||||
events.addPublishers(eventRouter);
|
||||
events.addSubscribers(eventRouter);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,42 @@
|
||||
# events-backend-module-gerrit
|
||||
|
||||
Welcome to the `events-backend-module-gerrit` backend plugin!
|
||||
|
||||
This plugin is a module for the `events-backend` backend plugin
|
||||
and extends it with an `GerritEventRouter`.
|
||||
|
||||
The event router will subscribe to the topic `gerrit`
|
||||
and route the events to more concrete topics based on the value
|
||||
of the provided `$.type` payload field.
|
||||
|
||||
Examples:
|
||||
|
||||
| `$.type` | topic |
|
||||
| ---------------- | ----------------------- |
|
||||
| `change-created` | `gerrit.change-created` |
|
||||
| `change-merged` | `gerrit.change-merged` |
|
||||
|
||||
Please find all possible webhook event types at the
|
||||
[official documentation](https://gerrit-review.googlesource.com/Documentation/cmd-stream-events.html#events).
|
||||
|
||||
## Installation
|
||||
|
||||
Install the [`events-backend` plugin](../events-backend/README.md).
|
||||
|
||||
Install this module:
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
yarn add --cwd packages/backend @backstage/plugin-events-backend-module-gerrit
|
||||
```
|
||||
|
||||
Add the event router to the `EventsBackend`:
|
||||
|
||||
```diff
|
||||
+const gerritEventRouter = new GerritEventRouter();
|
||||
|
||||
EventsBackend
|
||||
+ .addPublishers(gerritEventRouter)
|
||||
+ .addSubscribers(gerritEventRouter);
|
||||
// [...]
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
## API Report File for "@backstage/plugin-events-backend-module-gerrit"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { BackendFeature } from '@backstage/backend-plugin-api';
|
||||
import { EventParams } from '@backstage/plugin-events-node';
|
||||
import { SubTopicEventRouter } from '@backstage/plugin-events-node';
|
||||
|
||||
// @public
|
||||
export class GerritEventRouter extends SubTopicEventRouter {
|
||||
constructor();
|
||||
// (undocumented)
|
||||
protected determineSubTopic(params: EventParams): string | undefined;
|
||||
}
|
||||
|
||||
// @alpha
|
||||
export const gerritEventRouterEventsModule: (
|
||||
options?: undefined,
|
||||
) => BackendFeature;
|
||||
```
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@backstage/plugin-events-backend-module-gerrit",
|
||||
"version": "0.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"alphaTypes": "dist/index.alpha.d.ts",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "backend-plugin-module"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "backstage-cli package start",
|
||||
"build": "backstage-cli package build --experimental-type-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-plugin-api": "workspace:^",
|
||||
"@backstage/plugin-events-node": "workspace:^",
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-test-utils": "workspace:^",
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@backstage/plugin-events-backend-test-utils": "workspace:^",
|
||||
"supertest": "^6.1.3"
|
||||
},
|
||||
"files": [
|
||||
"alpha",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The module `gerrit` for the Backstage backend plugin "events-backend"
|
||||
* adding an event router for Gerrit.
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export { GerritEventRouter } from './router/GerritEventRouter';
|
||||
export { gerritEventRouterEventsModule } from './service/GerritEventRouterEventsModule';
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { TestEventBroker } from '@backstage/plugin-events-backend-test-utils';
|
||||
import { GerritEventRouter } from './GerritEventRouter';
|
||||
|
||||
describe('GerritEventRouter', () => {
|
||||
const eventRouter = new GerritEventRouter();
|
||||
const topic = 'gerrit';
|
||||
const eventPayload = { type: 'test-type', test: 'payload' };
|
||||
const metadata = {};
|
||||
|
||||
it('no $.type', () => {
|
||||
const eventBroker = new TestEventBroker();
|
||||
eventRouter.setEventBroker(eventBroker);
|
||||
|
||||
eventRouter.onEvent({
|
||||
topic,
|
||||
eventPayload: { invalid: 'payload' },
|
||||
metadata,
|
||||
});
|
||||
|
||||
expect(eventBroker.published).toEqual([]);
|
||||
});
|
||||
|
||||
it('with $.type', () => {
|
||||
const eventBroker = new TestEventBroker();
|
||||
eventRouter.setEventBroker(eventBroker);
|
||||
|
||||
eventRouter.onEvent({ topic, eventPayload, metadata });
|
||||
|
||||
expect(eventBroker.published.length).toBe(1);
|
||||
expect(eventBroker.published[0].topic).toEqual('gerrit.test-type');
|
||||
expect(eventBroker.published[0].eventPayload).toEqual(eventPayload);
|
||||
expect(eventBroker.published[0].metadata).toEqual(metadata);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EventParams,
|
||||
SubTopicEventRouter,
|
||||
} from '@backstage/plugin-events-node';
|
||||
|
||||
/**
|
||||
* Subscribes to the generic `gerrit` topic
|
||||
* and publishes the events under the more concrete sub-topic
|
||||
* depending on the `$.type` field provided.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class GerritEventRouter extends SubTopicEventRouter {
|
||||
constructor() {
|
||||
super('gerrit');
|
||||
}
|
||||
|
||||
protected determineSubTopic(params: EventParams): string | undefined {
|
||||
if ('type' in (params.eventPayload as object)) {
|
||||
const payload = params.eventPayload as { type: string };
|
||||
return payload.type;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { eventsExtensionPoint } from '@backstage/plugin-events-node';
|
||||
import { gerritEventRouterEventsModule } from './GerritEventRouterEventsModule';
|
||||
import { GerritEventRouter } from '../router/GerritEventRouter';
|
||||
|
||||
describe('gerritEventRouterEventsModule', () => {
|
||||
it('should be correctly wired and set up', async () => {
|
||||
let addedPublisher: GerritEventRouter | undefined;
|
||||
let addedSubscriber: GerritEventRouter | undefined;
|
||||
const extensionPoint = {
|
||||
addPublishers: (publisher: any) => {
|
||||
addedPublisher = publisher;
|
||||
},
|
||||
addSubscribers: (subscriber: any) => {
|
||||
addedSubscriber = subscriber;
|
||||
},
|
||||
};
|
||||
|
||||
await startTestBackend({
|
||||
extensionPoints: [[eventsExtensionPoint, extensionPoint]],
|
||||
services: [],
|
||||
features: [gerritEventRouterEventsModule()],
|
||||
});
|
||||
|
||||
expect(addedPublisher).not.toBeUndefined();
|
||||
expect(addedPublisher).toBeInstanceOf(GerritEventRouter);
|
||||
expect(addedSubscriber).not.toBeUndefined();
|
||||
expect(addedSubscriber).toBeInstanceOf(GerritEventRouter);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createBackendModule } from '@backstage/backend-plugin-api';
|
||||
import { eventsExtensionPoint } from '@backstage/plugin-events-node';
|
||||
import { GerritEventRouter } from '../router/GerritEventRouter';
|
||||
|
||||
/**
|
||||
* Module for the events-backend plugin, adding an event router for Gerrit.
|
||||
*
|
||||
* Registers the {@link GerritEventRouter}.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export const gerritEventRouterEventsModule = createBackendModule({
|
||||
pluginId: 'events',
|
||||
moduleId: 'gerritEventRouter',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
events: eventsExtensionPoint,
|
||||
},
|
||||
async init({ events }) {
|
||||
const eventRouter = new GerritEventRouter();
|
||||
|
||||
events.addPublishers(eventRouter);
|
||||
events.addSubscribers(eventRouter);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,43 @@
|
||||
# events-backend-module-github
|
||||
|
||||
Welcome to the `events-backend-module-github` backend plugin!
|
||||
|
||||
This plugin is a module for the `events-backend` backend plugin
|
||||
and extends it with an `GithubEventRouter`.
|
||||
|
||||
The event router will subscribe to the topic `github`
|
||||
and route the events to more concrete topics based on the value
|
||||
of the provided `x-github-event` metadata field.
|
||||
|
||||
Examples:
|
||||
|
||||
| `x-github-event` | topic |
|
||||
| ---------------- | --------------------- |
|
||||
| `pull_request` | `github.pull_request` |
|
||||
| `push` | `github.push` |
|
||||
| `repository` | `github.repository` |
|
||||
|
||||
Please find all possible webhook event types at the
|
||||
[official documentation](https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads).
|
||||
|
||||
## Installation
|
||||
|
||||
Install the [`events-backend` plugin](../events-backend/README.md).
|
||||
|
||||
Install this module:
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
yarn add --cwd packages/backend @backstage/plugin-events-backend-module-github
|
||||
```
|
||||
|
||||
Add the event router to the `EventsBackend`:
|
||||
|
||||
```diff
|
||||
+const githubEventRouter = new GithubEventRouter();
|
||||
|
||||
EventsBackend
|
||||
+ .addPublishers(githubEventRouter)
|
||||
+ .addSubscribers(githubEventRouter);
|
||||
// [...]
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
## API Report File for "@backstage/plugin-events-backend-module-github"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { BackendFeature } from '@backstage/backend-plugin-api';
|
||||
import { EventParams } from '@backstage/plugin-events-node';
|
||||
import { SubTopicEventRouter } from '@backstage/plugin-events-node';
|
||||
|
||||
// @public
|
||||
export class GithubEventRouter extends SubTopicEventRouter {
|
||||
constructor();
|
||||
// (undocumented)
|
||||
protected determineSubTopic(params: EventParams): string | undefined;
|
||||
}
|
||||
|
||||
// @alpha
|
||||
export const githubEventRouterEventsModule: (
|
||||
options?: undefined,
|
||||
) => BackendFeature;
|
||||
```
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@backstage/plugin-events-backend-module-github",
|
||||
"version": "0.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"alphaTypes": "dist/index.alpha.d.ts",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "backend-plugin-module"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "backstage-cli package start",
|
||||
"build": "backstage-cli package build --experimental-type-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-plugin-api": "workspace:^",
|
||||
"@backstage/plugin-events-node": "workspace:^",
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-test-utils": "workspace:^",
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@backstage/plugin-events-backend-test-utils": "workspace:^",
|
||||
"supertest": "^6.1.3"
|
||||
},
|
||||
"files": [
|
||||
"alpha",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The module `github` for the Backstage backend plugin "events-backend"
|
||||
* adding an event router for GitHub.
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export { GithubEventRouter } from './router/GithubEventRouter';
|
||||
export { githubEventRouterEventsModule } from './service/GithubEventRouterEventsModule';
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { TestEventBroker } from '@backstage/plugin-events-backend-test-utils';
|
||||
import { GithubEventRouter } from './GithubEventRouter';
|
||||
|
||||
describe('GithubEventRouter', () => {
|
||||
const eventRouter = new GithubEventRouter();
|
||||
const topic = 'github';
|
||||
const eventPayload = { test: 'payload' };
|
||||
const metadata = { 'x-github-event': 'test_type' };
|
||||
|
||||
it('no x-github-event', () => {
|
||||
const eventBroker = new TestEventBroker();
|
||||
eventRouter.setEventBroker(eventBroker);
|
||||
|
||||
eventRouter.onEvent({ topic, eventPayload });
|
||||
|
||||
expect(eventBroker.published).toEqual([]);
|
||||
});
|
||||
|
||||
it('with x-github-event', () => {
|
||||
const eventBroker = new TestEventBroker();
|
||||
eventRouter.setEventBroker(eventBroker);
|
||||
|
||||
eventRouter.onEvent({ topic, eventPayload, metadata });
|
||||
|
||||
expect(eventBroker.published.length).toBe(1);
|
||||
expect(eventBroker.published[0].topic).toEqual('github.test_type');
|
||||
expect(eventBroker.published[0].eventPayload).toEqual(eventPayload);
|
||||
expect(eventBroker.published[0].metadata).toEqual(metadata);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EventParams,
|
||||
SubTopicEventRouter,
|
||||
} from '@backstage/plugin-events-node';
|
||||
|
||||
/**
|
||||
* Subscribes to the generic `github` topic
|
||||
* and publishes the events under the more concrete sub-topic
|
||||
* depending on the `x-github-event` provided.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class GithubEventRouter extends SubTopicEventRouter {
|
||||
constructor() {
|
||||
super('github');
|
||||
}
|
||||
|
||||
protected determineSubTopic(params: EventParams): string | undefined {
|
||||
return params.metadata?.['x-github-event'] as string | undefined;
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { eventsExtensionPoint } from '@backstage/plugin-events-node';
|
||||
import { githubEventRouterEventsModule } from './GithubEventRouterEventsModule';
|
||||
import { GithubEventRouter } from '../router/GithubEventRouter';
|
||||
|
||||
describe('githubEventRouterEventsModule', () => {
|
||||
it('should be correctly wired and set up', async () => {
|
||||
let addedPublisher: GithubEventRouter | undefined;
|
||||
let addedSubscriber: GithubEventRouter | undefined;
|
||||
const extensionPoint = {
|
||||
addPublishers: (publisher: any) => {
|
||||
addedPublisher = publisher;
|
||||
},
|
||||
addSubscribers: (subscriber: any) => {
|
||||
addedSubscriber = subscriber;
|
||||
},
|
||||
};
|
||||
|
||||
await startTestBackend({
|
||||
extensionPoints: [[eventsExtensionPoint, extensionPoint]],
|
||||
services: [],
|
||||
features: [githubEventRouterEventsModule()],
|
||||
});
|
||||
|
||||
expect(addedPublisher).not.toBeUndefined();
|
||||
expect(addedPublisher).toBeInstanceOf(GithubEventRouter);
|
||||
expect(addedSubscriber).not.toBeUndefined();
|
||||
expect(addedSubscriber).toBeInstanceOf(GithubEventRouter);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createBackendModule } from '@backstage/backend-plugin-api';
|
||||
import { eventsExtensionPoint } from '@backstage/plugin-events-node';
|
||||
import { GithubEventRouter } from '../router/GithubEventRouter';
|
||||
|
||||
/**
|
||||
* Module for the events-backend plugin, adding an event router for GitHub.
|
||||
*
|
||||
* Registers the {@link GithubEventRouter}.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export const githubEventRouterEventsModule = createBackendModule({
|
||||
pluginId: 'events',
|
||||
moduleId: 'githubEventRouter',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
events: eventsExtensionPoint,
|
||||
},
|
||||
async init({ events }) {
|
||||
const eventRouter = new GithubEventRouter();
|
||||
|
||||
events.addPublishers(eventRouter);
|
||||
events.addSubscribers(eventRouter);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,42 @@
|
||||
# events-backend-module-gitlab
|
||||
|
||||
Welcome to the `events-backend-module-gitlab` backend plugin!
|
||||
|
||||
This plugin is a module for the `events-backend` backend plugin
|
||||
and extends it with an `GitlabEventRouter`.
|
||||
|
||||
The event router will subscribe to the topic `gitlab`
|
||||
and route the events to more concrete topics based on the value
|
||||
of the provided `$.event_name` payload field.
|
||||
|
||||
Examples:
|
||||
|
||||
| `$.event_name` | topic |
|
||||
| --------------- | ---------------------- |
|
||||
| `push` | `gitlab.push` |
|
||||
| `merge_request` | `gitlab.merge_request` |
|
||||
|
||||
Please find all possible webhook event types at the
|
||||
[official documentation](https://docs.gitlab.com/ee/user/project/integrations/webhook_events.html).
|
||||
|
||||
## Installation
|
||||
|
||||
Install the [`events-backend` plugin](../events-backend/README.md).
|
||||
|
||||
Install this module:
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
yarn add --cwd packages/backend @backstage/plugin-events-backend-module-gitlab
|
||||
```
|
||||
|
||||
Add the event router to the `EventsBackend`:
|
||||
|
||||
```diff
|
||||
+const gitlabEventRouter = new GitlabEventRouter();
|
||||
|
||||
EventsBackend
|
||||
+ .addPublishers(gitlabEventRouter)
|
||||
+ .addSubscribers(gitlabEventRouter);
|
||||
// [...]
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
## API Report File for "@backstage/plugin-events-backend-module-gitlab"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { BackendFeature } from '@backstage/backend-plugin-api';
|
||||
import { EventParams } from '@backstage/plugin-events-node';
|
||||
import { SubTopicEventRouter } from '@backstage/plugin-events-node';
|
||||
|
||||
// @public
|
||||
export class GitlabEventRouter extends SubTopicEventRouter {
|
||||
constructor();
|
||||
// (undocumented)
|
||||
protected determineSubTopic(params: EventParams): string | undefined;
|
||||
}
|
||||
|
||||
// @alpha
|
||||
export const gitlabEventRouterEventsModule: (
|
||||
options?: undefined,
|
||||
) => BackendFeature;
|
||||
```
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@backstage/plugin-events-backend-module-gitlab",
|
||||
"version": "0.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"alphaTypes": "dist/index.alpha.d.ts",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "backend-plugin-module"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "backstage-cli package start",
|
||||
"build": "backstage-cli package build --experimental-type-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-plugin-api": "workspace:^",
|
||||
"@backstage/plugin-events-node": "workspace:^",
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-test-utils": "workspace:^",
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@backstage/plugin-events-backend-test-utils": "workspace:^",
|
||||
"supertest": "^6.1.3"
|
||||
},
|
||||
"files": [
|
||||
"alpha",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The module "gitlab" for the Backstage backend plugin "events-backend"
|
||||
* adding an event router for GitLab.
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export { GitlabEventRouter } from './router/GitlabEventRouter';
|
||||
export { gitlabEventRouterEventsModule } from './service/GitlabEventRouterEventsModule';
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { TestEventBroker } from '@backstage/plugin-events-backend-test-utils';
|
||||
import { GitlabEventRouter } from './GitlabEventRouter';
|
||||
|
||||
describe('GitlabEventRouter', () => {
|
||||
const eventRouter = new GitlabEventRouter();
|
||||
const topic = 'gitlab';
|
||||
const eventPayload = { event_name: 'test_type', test: 'payload' };
|
||||
const metadata = {};
|
||||
|
||||
it('no $.event_name', () => {
|
||||
const eventBroker = new TestEventBroker();
|
||||
eventRouter.setEventBroker(eventBroker);
|
||||
|
||||
eventRouter.onEvent({
|
||||
topic,
|
||||
eventPayload: { invalid: 'payload' },
|
||||
metadata,
|
||||
});
|
||||
|
||||
expect(eventBroker.published).toEqual([]);
|
||||
});
|
||||
|
||||
it('with $.event_name', () => {
|
||||
const eventBroker = new TestEventBroker();
|
||||
eventRouter.setEventBroker(eventBroker);
|
||||
|
||||
eventRouter.onEvent({ topic, eventPayload, metadata });
|
||||
|
||||
expect(eventBroker.published.length).toBe(1);
|
||||
expect(eventBroker.published[0].topic).toEqual('gitlab.test_type');
|
||||
expect(eventBroker.published[0].eventPayload).toEqual(eventPayload);
|
||||
expect(eventBroker.published[0].metadata).toEqual(metadata);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EventParams,
|
||||
SubTopicEventRouter,
|
||||
} from '@backstage/plugin-events-node';
|
||||
|
||||
/**
|
||||
* Subscribes to the generic `gitlab` topic
|
||||
* and publishes the events under the more concrete sub-topic
|
||||
* depending on the `$.event_name` field provided.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class GitlabEventRouter extends SubTopicEventRouter {
|
||||
constructor() {
|
||||
super('gitlab');
|
||||
}
|
||||
|
||||
protected determineSubTopic(params: EventParams): string | undefined {
|
||||
if ('event_name' in (params.eventPayload as object)) {
|
||||
const payload = params.eventPayload as { event_name: string };
|
||||
return payload.event_name;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { eventsExtensionPoint } from '@backstage/plugin-events-node';
|
||||
import { gitlabEventRouterEventsModule } from './GitlabEventRouterEventsModule';
|
||||
import { GitlabEventRouter } from '../router/GitlabEventRouter';
|
||||
|
||||
describe('gitlabEventRouterEventsModule', () => {
|
||||
it('should be correctly wired and set up', async () => {
|
||||
let addedPublisher: GitlabEventRouter | undefined;
|
||||
let addedSubscriber: GitlabEventRouter | undefined;
|
||||
const extensionPoint = {
|
||||
addPublishers: (publisher: any) => {
|
||||
addedPublisher = publisher;
|
||||
},
|
||||
addSubscribers: (subscriber: any) => {
|
||||
addedSubscriber = subscriber;
|
||||
},
|
||||
};
|
||||
|
||||
await startTestBackend({
|
||||
extensionPoints: [[eventsExtensionPoint, extensionPoint]],
|
||||
services: [],
|
||||
features: [gitlabEventRouterEventsModule()],
|
||||
});
|
||||
|
||||
expect(addedPublisher).not.toBeUndefined();
|
||||
expect(addedPublisher).toBeInstanceOf(GitlabEventRouter);
|
||||
expect(addedSubscriber).not.toBeUndefined();
|
||||
expect(addedSubscriber).toBeInstanceOf(GitlabEventRouter);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createBackendModule } from '@backstage/backend-plugin-api';
|
||||
import { eventsExtensionPoint } from '@backstage/plugin-events-node';
|
||||
import { GitlabEventRouter } from '../router/GitlabEventRouter';
|
||||
|
||||
/**
|
||||
* Module for the events-backend plugin, adding an event router for GitLab.
|
||||
*
|
||||
* Registers the {@link GitlabEventRouter}.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export const gitlabEventRouterEventsModule = createBackendModule({
|
||||
pluginId: 'events',
|
||||
moduleId: 'gitlabEventRouter',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
events: eventsExtensionPoint,
|
||||
},
|
||||
async init({ events }) {
|
||||
const eventRouter = new GitlabEventRouter();
|
||||
|
||||
events.addPublishers(eventRouter);
|
||||
events.addSubscribers(eventRouter);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,4 @@
|
||||
# plugin-events-backend-test-utils
|
||||
|
||||
Houses implementations of plugin-events-node interfaces
|
||||
which can be useful for test for events-backend and its modules.
|
||||
@@ -0,0 +1,47 @@
|
||||
## API Report File for "@backstage/plugin-events-backend-test-utils"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
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';
|
||||
|
||||
// @public (undocumented)
|
||||
export class TestEventBroker implements EventBroker {
|
||||
// (undocumented)
|
||||
publish(params: EventParams): Promise<void>;
|
||||
// (undocumented)
|
||||
readonly published: EventParams[];
|
||||
// (undocumented)
|
||||
subscribe(
|
||||
...subscribers: Array<EventSubscriber | Array<EventSubscriber>>
|
||||
): void;
|
||||
// (undocumented)
|
||||
readonly subscribed: EventSubscriber[];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class TestEventPublisher implements EventPublisher {
|
||||
// (undocumented)
|
||||
get eventBroker(): EventBroker | undefined;
|
||||
// (undocumented)
|
||||
setEventBroker(eventBroker: EventBroker): Promise<void>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class TestEventSubscriber implements EventSubscriber {
|
||||
constructor(name: string, topics: string[]);
|
||||
// (undocumented)
|
||||
readonly name: string;
|
||||
// (undocumented)
|
||||
onEvent(params: EventParams): Promise<void>;
|
||||
// (undocumented)
|
||||
readonly receivedEvents: Record<string, EventParams[]>;
|
||||
// (undocumented)
|
||||
supportsEventTopics(): string[];
|
||||
// (undocumented)
|
||||
readonly topics: string[];
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "@backstage/plugin-events-backend-test-utils",
|
||||
"description": "The plugin-events-backend-test-utils for @backstage/plugin-events-node",
|
||||
"version": "0.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "node-library"
|
||||
},
|
||||
"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/plugin-events-node": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The events-test-utils module for `@backstage/plugin-events-node`.
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export * from './testUtils';
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EventBroker,
|
||||
EventParams,
|
||||
EventSubscriber,
|
||||
} from '@backstage/plugin-events-node';
|
||||
|
||||
/** @public */
|
||||
export class TestEventBroker implements EventBroker {
|
||||
readonly published: EventParams[] = [];
|
||||
readonly subscribed: EventSubscriber[] = [];
|
||||
|
||||
async publish(params: EventParams): Promise<void> {
|
||||
this.published.push(params);
|
||||
}
|
||||
|
||||
subscribe(
|
||||
...subscribers: Array<EventSubscriber | Array<EventSubscriber>>
|
||||
): void {
|
||||
this.subscribed.push(...subscribers.flat());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { EventBroker, EventPublisher } from '@backstage/plugin-events-node';
|
||||
|
||||
/** @public */
|
||||
export class TestEventPublisher implements EventPublisher {
|
||||
#eventBroker?: EventBroker;
|
||||
|
||||
async setEventBroker(eventBroker: EventBroker): Promise<void> {
|
||||
this.#eventBroker = eventBroker;
|
||||
}
|
||||
|
||||
get eventBroker() {
|
||||
return this.#eventBroker;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { EventParams, EventSubscriber } from '@backstage/plugin-events-node';
|
||||
|
||||
/** @public */
|
||||
export class TestEventSubscriber implements EventSubscriber {
|
||||
readonly name: string;
|
||||
readonly topics: string[];
|
||||
|
||||
readonly receivedEvents: Record<string, EventParams[]> = {};
|
||||
|
||||
constructor(name: string, topics: string[]) {
|
||||
this.name = name;
|
||||
this.topics = topics;
|
||||
}
|
||||
|
||||
supportsEventTopics(): string[] {
|
||||
return this.topics;
|
||||
}
|
||||
|
||||
async onEvent(params: EventParams): Promise<void> {
|
||||
this.receivedEvents[params.topic] = this.receivedEvents[params.topic] ?? [];
|
||||
this.receivedEvents[params.topic].push(params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { TestEventBroker } from './TestEventBroker';
|
||||
export { TestEventPublisher } from './TestEventPublisher';
|
||||
export { TestEventSubscriber } from './TestEventSubscriber';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user