Merge pull request #24916 from backstage/rugvip/events
events-backend: added scaleable events service implementation
This commit is contained in:
@@ -3,6 +3,9 @@
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { AuthService } from '@backstage/backend-plugin-api';
|
||||
import { DiscoveryService } from '@backstage/backend-plugin-api';
|
||||
import { LifecycleService } from '@backstage/backend-plugin-api';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { ServiceFactory } from '@backstage/backend-plugin-api';
|
||||
import { ServiceRef } from '@backstage/backend-plugin-api';
|
||||
@@ -11,7 +14,15 @@ import { ServiceRef } from '@backstage/backend-plugin-api';
|
||||
export class DefaultEventsService implements EventsService {
|
||||
// (undocumented)
|
||||
static create(options: { logger: LoggerService }): DefaultEventsService;
|
||||
forPlugin(pluginId: string): EventsService;
|
||||
forPlugin(
|
||||
pluginId: string,
|
||||
options?: {
|
||||
discovery: DiscoveryService;
|
||||
logger: LoggerService;
|
||||
auth: AuthService;
|
||||
lifecycle: LifecycleService;
|
||||
},
|
||||
): EventsService;
|
||||
// (undocumented)
|
||||
publish(params: EventParams): Promise<void>;
|
||||
// (undocumented)
|
||||
|
||||
@@ -51,7 +51,11 @@
|
||||
"test": "backstage-cli package test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-plugin-api": "workspace:^"
|
||||
"@backstage/backend-plugin-api": "workspace:^",
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@backstage/types": "workspace:^",
|
||||
"cross-fetch": "^4.0.0",
|
||||
"uri-template": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-common": "^0.25.0",
|
||||
|
||||
@@ -14,9 +14,320 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
AuthService,
|
||||
DiscoveryService,
|
||||
LifecycleService,
|
||||
LoggerService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { EventParams } from './EventParams';
|
||||
import { EventsService, EventsServiceSubscribeOptions } from './EventsService';
|
||||
import { DefaultApiClient } from '../generated';
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
|
||||
const POLL_BACKOFF_START_MS = 1_000;
|
||||
const POLL_BACKOFF_MAX_MS = 60_000;
|
||||
const POLL_BACKOFF_FACTOR = 2;
|
||||
|
||||
/**
|
||||
* Local event bus for subscribers within the same process.
|
||||
*
|
||||
* When publishing events we'll keep track of which subscribers we managed to
|
||||
* reach locally, and forward those subscriber IDs to the events backend if it
|
||||
* is in use. The events backend will then both avoid forwarding the same events
|
||||
* to those subscribers again, but also avoid storing the event altogether if
|
||||
* there are no other subscribers.
|
||||
* @internal
|
||||
*/
|
||||
export class LocalEventBus {
|
||||
readonly #logger: LoggerService;
|
||||
|
||||
readonly #subscribers = new Map<
|
||||
string,
|
||||
Omit<EventsServiceSubscribeOptions, 'topics'>[]
|
||||
>();
|
||||
|
||||
constructor(logger: LoggerService) {
|
||||
this.#logger = logger;
|
||||
}
|
||||
|
||||
async publish(
|
||||
params: EventParams,
|
||||
): Promise<{ notifiedSubscribers: string[] }> {
|
||||
this.#logger.debug(
|
||||
`Event received: topic=${params.topic}, metadata=${JSON.stringify(
|
||||
params.metadata,
|
||||
)}, payload=${JSON.stringify(params.eventPayload)}`,
|
||||
);
|
||||
|
||||
if (!this.#subscribers.has(params.topic)) {
|
||||
return { notifiedSubscribers: [] };
|
||||
}
|
||||
|
||||
const onEventPromises: Promise<string>[] = [];
|
||||
this.#subscribers.get(params.topic)?.forEach(subscription => {
|
||||
onEventPromises.push(
|
||||
(async () => {
|
||||
try {
|
||||
await subscription.onEvent(params);
|
||||
} catch (error) {
|
||||
this.#logger.warn(
|
||||
`Subscriber "${subscription.id}" failed to process event for topic "${params.topic}"`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
return subscription.id;
|
||||
})(),
|
||||
);
|
||||
});
|
||||
|
||||
return { notifiedSubscribers: await Promise.all(onEventPromises) };
|
||||
}
|
||||
|
||||
async subscribe(options: EventsServiceSubscribeOptions): Promise<void> {
|
||||
options.topics.forEach(topic => {
|
||||
if (!this.#subscribers.has(topic)) {
|
||||
this.#subscribers.set(topic, []);
|
||||
}
|
||||
|
||||
this.#subscribers.get(topic)!.push({
|
||||
id: options.id,
|
||||
onEvent: options.onEvent,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin specific events bus that delegates to the local bus, as well as the
|
||||
* events backend if it is available.
|
||||
*/
|
||||
class PluginEventsService implements EventsService {
|
||||
constructor(
|
||||
private readonly pluginId: string,
|
||||
private readonly localBus: LocalEventBus,
|
||||
private readonly logger: LoggerService,
|
||||
private client?: DefaultApiClient,
|
||||
private readonly auth?: AuthService,
|
||||
) {}
|
||||
|
||||
async publish(params: EventParams): Promise<void> {
|
||||
const lock = this.#getShutdownLock();
|
||||
try {
|
||||
const { notifiedSubscribers } = await this.localBus.publish(params);
|
||||
|
||||
if (!this.client) {
|
||||
return;
|
||||
}
|
||||
const token = await this.#getToken();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
const res = await this.client.postEvent(
|
||||
{
|
||||
body: {
|
||||
event: { payload: params.eventPayload, topic: params.topic },
|
||||
notifiedSubscribers,
|
||||
},
|
||||
},
|
||||
{ token },
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
if (res.status === 404) {
|
||||
this.logger.warn(
|
||||
`Event publish request failed with status 404, events backend not found. Future events will not be persisted.`,
|
||||
);
|
||||
delete this.client;
|
||||
return;
|
||||
}
|
||||
throw await ResponseError.fromResponse(res);
|
||||
}
|
||||
} finally {
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
async subscribe(options: EventsServiceSubscribeOptions): Promise<void> {
|
||||
const subscriptionId = `${this.pluginId}.${options.id}`;
|
||||
|
||||
await this.localBus.subscribe({
|
||||
id: subscriptionId,
|
||||
topics: options.topics,
|
||||
onEvent: options.onEvent,
|
||||
});
|
||||
|
||||
if (!this.client) {
|
||||
return;
|
||||
}
|
||||
const token = await this.#getToken();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
const res = await this.client.putSubscription(
|
||||
{
|
||||
path: { subscriptionId },
|
||||
body: { topics: options.topics },
|
||||
},
|
||||
{ token },
|
||||
);
|
||||
if (!res.ok) {
|
||||
if (res.status === 404) {
|
||||
this.logger.warn(
|
||||
`Event subscribe request failed with status 404, events backend not found. Will only receive events that were sent locally on this process.`,
|
||||
);
|
||||
delete this.client;
|
||||
return;
|
||||
}
|
||||
throw await ResponseError.fromResponse(res);
|
||||
}
|
||||
|
||||
this.#startPolling(subscriptionId, options.topics, options.onEvent);
|
||||
}
|
||||
|
||||
#startPolling(
|
||||
subscriptionId: string,
|
||||
topics: string[],
|
||||
onEvent: EventsServiceSubscribeOptions['onEvent'],
|
||||
) {
|
||||
let backoffMs = POLL_BACKOFF_START_MS;
|
||||
const poll = async () => {
|
||||
if (!this.client) {
|
||||
return;
|
||||
}
|
||||
const lock = this.#getShutdownLock();
|
||||
try {
|
||||
const token = await this.#getToken();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
const res = await this.client.getSubscriptionEvents(
|
||||
{
|
||||
path: { subscriptionId },
|
||||
},
|
||||
{ token },
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
if (res.status === 404) {
|
||||
this.logger.info(
|
||||
`Polling event subscription resulted in a 404, recreating subscription`,
|
||||
);
|
||||
const putRes = await this.client.putSubscription(
|
||||
{
|
||||
path: { subscriptionId },
|
||||
body: { topics },
|
||||
},
|
||||
{ token },
|
||||
);
|
||||
if (!putRes.ok) {
|
||||
throw await ResponseError.fromResponse(res);
|
||||
}
|
||||
}
|
||||
throw await ResponseError.fromResponse(res);
|
||||
}
|
||||
backoffMs = POLL_BACKOFF_START_MS;
|
||||
|
||||
// 202 means there were no immediately available events, but the
|
||||
// response will block until either new events are available or the
|
||||
// request times out. In both cases we should should try to read events
|
||||
// immediately again
|
||||
if (res.status === 202) {
|
||||
lock.release();
|
||||
await res.body?.getReader()?.closed;
|
||||
process.nextTick(poll);
|
||||
} else if (res.status === 200) {
|
||||
const data = await res.json();
|
||||
if (data) {
|
||||
for (const event of data.events ?? []) {
|
||||
try {
|
||||
await onEvent({
|
||||
topic: event.topic,
|
||||
eventPayload: event.payload,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.warn(
|
||||
`Subscriber "${subscriptionId}" failed to process event for topic "${event.topic}"`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
process.nextTick(poll);
|
||||
} else {
|
||||
this.logger.warn(
|
||||
`Unexpected response status ${res.status} from events backend for subscription "${subscriptionId}"`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.warn(
|
||||
`Poll failed for subscription "${subscriptionId}", retrying in ${backoffMs.toFixed(
|
||||
0,
|
||||
)}ms`,
|
||||
error,
|
||||
);
|
||||
setTimeout(poll, backoffMs);
|
||||
backoffMs = Math.min(
|
||||
backoffMs * POLL_BACKOFF_FACTOR,
|
||||
POLL_BACKOFF_MAX_MS,
|
||||
);
|
||||
} finally {
|
||||
lock.release();
|
||||
}
|
||||
};
|
||||
poll();
|
||||
}
|
||||
|
||||
async #getToken() {
|
||||
if (!this.auth) {
|
||||
throw new Error('Auth service not available');
|
||||
}
|
||||
|
||||
try {
|
||||
const { token } = await this.auth.getPluginRequestToken({
|
||||
onBehalfOf: await this.auth.getOwnServiceCredentials(),
|
||||
targetPluginId: 'events',
|
||||
});
|
||||
return token;
|
||||
} catch (error) {
|
||||
// This is a bit hacky, but handles the case where new auth is used
|
||||
// without legacy auth fallback, and the events backend is not installed
|
||||
if (String(error).includes('Unable to generate legacy token')) {
|
||||
this.logger.warn(
|
||||
`The events backend is not available and neither is legacy auth. Future events will not be persisted.`,
|
||||
);
|
||||
delete this.client;
|
||||
return undefined;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async shutdown() {
|
||||
this.#isShuttingDown = true;
|
||||
await Promise.all(this.#shutdownLocks);
|
||||
}
|
||||
|
||||
#isShuttingDown = false;
|
||||
#shutdownLocks: Promise<void>[] = [];
|
||||
|
||||
// This locking mechanism helps ensure that we are either idle or waiting for
|
||||
// a blocked events call before shutting down. It increases out changes of
|
||||
// never dropping any events on shutdown.
|
||||
#getShutdownLock(): { release(): void } {
|
||||
if (this.#isShuttingDown) {
|
||||
throw new Error('Service is shutting down');
|
||||
}
|
||||
|
||||
let release: () => void;
|
||||
this.#shutdownLocks.push(
|
||||
new Promise<void>(resolve => {
|
||||
release = resolve;
|
||||
}),
|
||||
);
|
||||
return { release: release! };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* In-process event broker which will pass the event to all registered subscribers
|
||||
@@ -28,15 +339,16 @@ import { EventsService, EventsServiceSubscribeOptions } from './EventsService';
|
||||
*/
|
||||
// TODO(pjungermann): add opentelemetry? (see plugins/catalog-backend/src/util/opentelemetry.ts, etc.)
|
||||
export class DefaultEventsService implements EventsService {
|
||||
private readonly subscribers = new Map<
|
||||
string,
|
||||
Omit<EventsServiceSubscribeOptions, 'topics'>[]
|
||||
>();
|
||||
|
||||
private constructor(private readonly logger: LoggerService) {}
|
||||
private constructor(
|
||||
private readonly logger: LoggerService,
|
||||
private readonly localBus: LocalEventBus,
|
||||
) {}
|
||||
|
||||
static create(options: { logger: LoggerService }): DefaultEventsService {
|
||||
return new DefaultEventsService(options.logger);
|
||||
return new DefaultEventsService(
|
||||
options.logger,
|
||||
new LocalEventBus(options.logger),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,60 +357,40 @@ export class DefaultEventsService implements EventsService {
|
||||
*
|
||||
* @param pluginId - The plugin that the `EventService` should be created for.
|
||||
*/
|
||||
forPlugin(pluginId: string): EventsService {
|
||||
return {
|
||||
publish: (params: EventParams): Promise<void> => {
|
||||
return this.publish(params);
|
||||
},
|
||||
subscribe: (options: EventsServiceSubscribeOptions): Promise<void> => {
|
||||
return this.subscribe({
|
||||
...options,
|
||||
id: `${pluginId}.${options.id}`,
|
||||
});
|
||||
},
|
||||
};
|
||||
forPlugin(
|
||||
pluginId: string,
|
||||
options?: {
|
||||
discovery: DiscoveryService;
|
||||
logger: LoggerService;
|
||||
auth: AuthService;
|
||||
lifecycle: LifecycleService;
|
||||
},
|
||||
): EventsService {
|
||||
const client =
|
||||
options &&
|
||||
new DefaultApiClient({
|
||||
discoveryApi: options.discovery,
|
||||
fetchApi: { fetch }, // use native node fetch
|
||||
});
|
||||
const logger = options?.logger ?? this.logger;
|
||||
const service = new PluginEventsService(
|
||||
pluginId,
|
||||
this.localBus,
|
||||
logger,
|
||||
client,
|
||||
options?.auth,
|
||||
);
|
||||
options?.lifecycle.addShutdownHook(async () => {
|
||||
await service.shutdown();
|
||||
});
|
||||
return service;
|
||||
}
|
||||
|
||||
async publish(params: EventParams): Promise<void> {
|
||||
this.logger.debug(
|
||||
`Event received: topic=${params.topic}, metadata=${JSON.stringify(
|
||||
params.metadata,
|
||||
)}, payload=${JSON.stringify(params.eventPayload)}`,
|
||||
);
|
||||
|
||||
if (!this.subscribers.has(params.topic)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const onEventPromises: Promise<void>[] = [];
|
||||
this.subscribers.get(params.topic)?.forEach(subscription => {
|
||||
onEventPromises.push(
|
||||
(async () => {
|
||||
try {
|
||||
await subscription.onEvent(params);
|
||||
} catch (error) {
|
||||
this.logger.warn(
|
||||
`Subscriber "${subscription.id}" failed to process event for topic "${params.topic}"`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
})(),
|
||||
);
|
||||
});
|
||||
|
||||
await Promise.all(onEventPromises);
|
||||
await this.localBus.publish(params);
|
||||
}
|
||||
|
||||
async subscribe(options: EventsServiceSubscribeOptions): Promise<void> {
|
||||
options.topics.forEach(topic => {
|
||||
if (!this.subscribers.has(topic)) {
|
||||
this.subscribers.set(topic, []);
|
||||
}
|
||||
|
||||
this.subscribers.get(topic)!.push({
|
||||
id: options.id,
|
||||
onEvent: options.onEvent,
|
||||
});
|
||||
});
|
||||
this.localBus.subscribe(options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
import { DiscoveryApi } from '../types/discovery';
|
||||
import { FetchApi } from '../types/fetch';
|
||||
import crossFetch from 'cross-fetch';
|
||||
import { pluginId } from '../pluginId';
|
||||
import * as parser from 'uri-template';
|
||||
|
||||
import { GetSubscriptionEvents200Response } from '../models/GetSubscriptionEvents200Response.model';
|
||||
import { PostEventRequest } from '../models/PostEventRequest.model';
|
||||
import { PutSubscriptionRequest } from '../models/PutSubscriptionRequest.model';
|
||||
|
||||
/**
|
||||
* Wraps the Response type to convey a type on the json call.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type TypedResponse<T> = Omit<Response, 'json'> & {
|
||||
json: () => Promise<T>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Options you can pass into a request for additional information.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface RequestOptions {
|
||||
token?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* no description
|
||||
*/
|
||||
export class DefaultApiClient {
|
||||
private readonly discoveryApi: DiscoveryApi;
|
||||
private readonly fetchApi: FetchApi;
|
||||
|
||||
constructor(options: {
|
||||
discoveryApi: { getBaseUrl(pluginId: string): Promise<string> };
|
||||
fetchApi?: { fetch: typeof fetch };
|
||||
}) {
|
||||
this.discoveryApi = options.discoveryApi;
|
||||
this.fetchApi = options.fetchApi || { fetch: crossFetch };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get new events for the provided subscription
|
||||
* @param subscriptionId
|
||||
*/
|
||||
public async getSubscriptionEvents(
|
||||
// @ts-ignore
|
||||
request: {
|
||||
path: {
|
||||
subscriptionId: string;
|
||||
};
|
||||
},
|
||||
options?: RequestOptions,
|
||||
): Promise<TypedResponse<void | GetSubscriptionEvents200Response>> {
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
|
||||
|
||||
const uriTemplate = `/bus/v1/subscriptions/{subscriptionId}/events`;
|
||||
|
||||
const uri = parser.parse(uriTemplate).expand({
|
||||
subscriptionId: request.path.subscriptionId,
|
||||
});
|
||||
|
||||
return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
|
||||
},
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish a new event
|
||||
* @param postEventRequest
|
||||
*/
|
||||
public async postEvent(
|
||||
// @ts-ignore
|
||||
request: {
|
||||
body: PostEventRequest;
|
||||
},
|
||||
options?: RequestOptions,
|
||||
): Promise<TypedResponse<void>> {
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
|
||||
|
||||
const uriTemplate = `/bus/v1/events`;
|
||||
|
||||
const uri = parser.parse(uriTemplate).expand({});
|
||||
|
||||
return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify(request.body),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the subscription exists with the provided configuration
|
||||
* @param subscriptionId
|
||||
* @param putSubscriptionRequest
|
||||
*/
|
||||
public async putSubscription(
|
||||
// @ts-ignore
|
||||
request: {
|
||||
path: {
|
||||
subscriptionId: string;
|
||||
};
|
||||
body: PutSubscriptionRequest;
|
||||
},
|
||||
options?: RequestOptions,
|
||||
): Promise<TypedResponse<void>> {
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
|
||||
|
||||
const uriTemplate = `/bus/v1/subscriptions/{subscriptionId}`;
|
||||
|
||||
const uri = parser.parse(uriTemplate).expand({
|
||||
subscriptionId: request.path.subscriptionId,
|
||||
});
|
||||
|
||||
return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
|
||||
},
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(request.body),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2024 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 * from './DefaultApi.client';
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2024 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 * from './apis';
|
||||
export * from './models';
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
export interface ErrorError {
|
||||
name: string;
|
||||
message: string;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
export interface ErrorRequest {
|
||||
method: string;
|
||||
url: string;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
export interface ErrorResponse {
|
||||
statusCode: number;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
export interface Event {
|
||||
/**
|
||||
* The topic that the event is published on
|
||||
*/
|
||||
topic: string;
|
||||
/**
|
||||
* The event payload
|
||||
*/
|
||||
payload: any | null;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
import { Event } from '../models/Event.model';
|
||||
|
||||
export interface GetSubscriptionEvents200Response {
|
||||
events: Array<Event>;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
import { ErrorError } from '../models/ErrorError.model';
|
||||
import { ErrorRequest } from '../models/ErrorRequest.model';
|
||||
import { ErrorResponse } from '../models/ErrorResponse.model';
|
||||
|
||||
export interface ModelError {
|
||||
error: ErrorError;
|
||||
request: ErrorRequest;
|
||||
response: ErrorResponse;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
import { Event } from '../models/Event.model';
|
||||
|
||||
export interface PostEventRequest {
|
||||
event: Event;
|
||||
/**
|
||||
* The IDs of subscriptions that have already received this event
|
||||
*/
|
||||
notifiedSubscribers?: Array<string>;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
export interface PutSubscriptionRequest {
|
||||
/**
|
||||
* The topics to subscribe to
|
||||
*/
|
||||
topics: Array<string>;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2024 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 * from '../models/ErrorError.model';
|
||||
export * from '../models/ErrorRequest.model';
|
||||
export * from '../models/ErrorResponse.model';
|
||||
export * from '../models/Event.model';
|
||||
export * from '../models/GetSubscriptionEvents200Response.model';
|
||||
export * from '../models/ModelError.model';
|
||||
export * from '../models/PostEventRequest.model';
|
||||
export * from '../models/PutSubscriptionRequest.model';
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2024 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 const pluginId = 'events';
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a copy of the DiscoveryApi, to avoid importing core-plugin-api.
|
||||
*/
|
||||
export type DiscoveryApi = {
|
||||
getBaseUrl(pluginId: string): Promise<string>;
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a copy of FetchApi, to avoid importing core-plugin-api.
|
||||
*/
|
||||
export type FetchApi = {
|
||||
fetch: typeof fetch;
|
||||
};
|
||||
@@ -38,11 +38,23 @@ export const eventsServiceFactory = createServiceFactory({
|
||||
deps: {
|
||||
pluginMetadata: coreServices.pluginMetadata,
|
||||
rootLogger: coreServices.rootLogger,
|
||||
discovery: coreServices.discovery,
|
||||
logger: coreServices.logger,
|
||||
lifecycle: coreServices.lifecycle,
|
||||
auth: coreServices.auth,
|
||||
},
|
||||
async createRootContext({ rootLogger }) {
|
||||
return DefaultEventsService.create({ logger: rootLogger });
|
||||
},
|
||||
async factory({ pluginMetadata }, eventsService) {
|
||||
return eventsService.forPlugin(pluginMetadata.getId());
|
||||
async factory(
|
||||
{ pluginMetadata, discovery, logger, lifecycle, auth },
|
||||
eventsService,
|
||||
) {
|
||||
return eventsService.forPlugin(pluginMetadata.getId(), {
|
||||
discovery,
|
||||
logger,
|
||||
lifecycle,
|
||||
auth,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user