Merge pull request #26930 from Bonial-International-GmbH/pjungermann/bitbucket/fix-event-support
fix!: fix event support for Bitbucket Cloud
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-bitbucket-cloud': minor
|
||||
---
|
||||
|
||||
Fixes the event-based updates at `BitbucketCloudEntityProvider`.
|
||||
|
||||
Previously, this entity provider had optional event support for legacy backends
|
||||
that could be enabled by passing `catalogApi`, `events`, and `tokenManager`.
|
||||
|
||||
For the new/current backend system, the `catalogModuleBitbucketCloudEntityProvider`
|
||||
(`catalog.bitbucket-cloud-entity-provider`), event support was enabled by default.
|
||||
|
||||
A recent change removed `tokenManager` as a dependency from the module as well as removed it as input.
|
||||
While this didn't break the instantiation of the module, it broke the event-based updates,
|
||||
and led to a runtime misbehavior, accompanied by an info log message.
|
||||
|
||||
This change will replace the use of `tokenManager` with the use of `auth` (`AuthService`).
|
||||
|
||||
Additionally, to simplify, it will make `catalogApi` and `events` required dependencies.
|
||||
For the current backend system, this change is transparent and doesn't require any action.
|
||||
For the legacy backend system, this change will require you to pass those dependencies
|
||||
if you didn't do it already.
|
||||
|
||||
BREAKING CHANGES:
|
||||
|
||||
_(For legacy backend users only.)_
|
||||
|
||||
Previously optional `catalogApi`, and `events` are required now.
|
||||
A new required dependency `auth` was added.
|
||||
@@ -51,33 +51,6 @@ Further documentation:
|
||||
|
||||
### Installation with Legacy Backend System
|
||||
|
||||
#### Installation without Events Support
|
||||
|
||||
And then add the entity provider to your catalog builder:
|
||||
|
||||
```ts title="packages/backend/src/plugins/catalog.ts"
|
||||
/* highlight-add-next-line */
|
||||
import { BitbucketCloudEntityProvider } from '@backstage/plugin-catalog-backend-module-bitbucket-cloud';
|
||||
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
const builder = await CatalogBuilder.create(env);
|
||||
/* highlight-add-start */
|
||||
builder.addEntityProvider(
|
||||
BitbucketCloudEntityProvider.fromConfig(env.config, {
|
||||
logger: env.logger,
|
||||
scheduler: env.scheduler,
|
||||
}),
|
||||
);
|
||||
/* highlight-add-end */
|
||||
|
||||
// ..
|
||||
}
|
||||
```
|
||||
|
||||
#### Installation with Events Support
|
||||
|
||||
Please follow the installation instructions at
|
||||
|
||||
- <https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md>
|
||||
@@ -104,19 +77,17 @@ export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
const builder = await CatalogBuilder.create(env);
|
||||
builder.addProcessor(new ScaffolderEntitiesProcessor());
|
||||
/* highlight-add-start */
|
||||
const bitbucketCloudProvider = BitbucketCloudEntityProvider.fromConfig(
|
||||
env.config,
|
||||
{
|
||||
auth: env.auth,
|
||||
catalogApi: new CatalogClient({ discoveryApi: env.discovery }),
|
||||
events: env.events,
|
||||
logger: env.logger,
|
||||
scheduler: env.scheduler,
|
||||
tokenManager: env.tokenManager,
|
||||
},
|
||||
);
|
||||
env.eventBroker.subscribe(bitbucketCloudProvider);
|
||||
builder.addEntityProvider(bitbucketCloudProvider);
|
||||
/* highlight-add-end */
|
||||
const { processingEngine, router } = await builder.build();
|
||||
|
||||
@@ -51,7 +51,6 @@
|
||||
"test": "backstage-cli package test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.25.0",
|
||||
"@backstage/backend-plugin-api": "workspace:^",
|
||||
"@backstage/catalog-client": "workspace:^",
|
||||
"@backstage/catalog-model": "workspace:^",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
> 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 { CatalogApi } from '@backstage/catalog-client';
|
||||
import { Config } from '@backstage/config';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-node';
|
||||
@@ -12,7 +13,6 @@ import { EventsService } from '@backstage/plugin-events-node';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { SchedulerService } from '@backstage/backend-plugin-api';
|
||||
import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api';
|
||||
import { TokenManager } from '@backstage/backend-common';
|
||||
|
||||
// @public
|
||||
export class BitbucketCloudEntityProvider implements EntityProvider {
|
||||
@@ -21,12 +21,12 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
|
||||
static fromConfig(
|
||||
config: Config,
|
||||
options: {
|
||||
catalogApi?: CatalogApi;
|
||||
events?: EventsService;
|
||||
auth: AuthService;
|
||||
catalogApi: CatalogApi;
|
||||
events: EventsService;
|
||||
logger: LoggerService;
|
||||
schedule?: SchedulerServiceTaskRunner;
|
||||
scheduler?: SchedulerService;
|
||||
tokenManager?: TokenManager;
|
||||
},
|
||||
): BitbucketCloudEntityProvider[];
|
||||
getProviderName(): string;
|
||||
@@ -39,7 +39,7 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
|
||||
|
||||
// Warnings were encountered during analysis:
|
||||
//
|
||||
// src/providers/BitbucketCloudEntityProvider.d.ts:28:5 - (ae-undocumented) Missing documentation for "fromConfig".
|
||||
// src/providers/BitbucketCloudEntityProvider.d.ts:44:5 - (ae-undocumented) Missing documentation for "refresh".
|
||||
// src/providers/BitbucketCloudEntityProvider.d.ts:47:5 - (ae-undocumented) Missing documentation for "onRepoPush".
|
||||
// src/providers/BitbucketCloudEntityProvider.d.ts:26:5 - (ae-undocumented) Missing documentation for "fromConfig".
|
||||
// src/providers/BitbucketCloudEntityProvider.d.ts:42:5 - (ae-undocumented) Missing documentation for "refresh".
|
||||
// src/providers/BitbucketCloudEntityProvider.d.ts:44:5 - (ae-undocumented) Missing documentation for "onRepoPush".
|
||||
```
|
||||
|
||||
+11
-1
@@ -34,6 +34,7 @@ export const catalogModuleBitbucketCloudEntityProvider = createBackendModule({
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
auth: coreServices.auth,
|
||||
catalog: catalogProcessingExtensionPoint,
|
||||
catalogApi: catalogServiceRef,
|
||||
config: coreServices.rootConfig,
|
||||
@@ -41,8 +42,17 @@ export const catalogModuleBitbucketCloudEntityProvider = createBackendModule({
|
||||
logger: coreServices.logger,
|
||||
scheduler: coreServices.scheduler,
|
||||
},
|
||||
async init({ catalog, catalogApi, config, events, logger, scheduler }) {
|
||||
async init({
|
||||
auth,
|
||||
catalog,
|
||||
catalogApi,
|
||||
config,
|
||||
events,
|
||||
logger,
|
||||
scheduler,
|
||||
}) {
|
||||
const providers = BitbucketCloudEntityProvider.fromConfig(config, {
|
||||
auth,
|
||||
catalogApi,
|
||||
events,
|
||||
logger,
|
||||
|
||||
+51
-9
@@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { TokenManager } from '@backstage/backend-common';
|
||||
import {
|
||||
SchedulerServiceTaskInvocationDefinition,
|
||||
SchedulerServiceTaskRunner,
|
||||
@@ -92,11 +91,6 @@ describe('BitbucketCloudEntityProvider', () => {
|
||||
applyMutation: jest.fn(),
|
||||
refresh: jest.fn(),
|
||||
};
|
||||
const tokenManager = {
|
||||
getToken: async () => {
|
||||
return { token: 'fake-token' };
|
||||
},
|
||||
} as any as TokenManager;
|
||||
const repoPushEvent: Events.RepoPushEvent = {
|
||||
actor: {
|
||||
type: 'user',
|
||||
@@ -158,8 +152,14 @@ describe('BitbucketCloudEntityProvider', () => {
|
||||
});
|
||||
|
||||
it('no provider config', () => {
|
||||
const auth = mockServices.auth.mock();
|
||||
const catalogApi = catalogServiceMock.mock();
|
||||
const config = new ConfigReader({});
|
||||
const events = DefaultEventsService.create({ logger });
|
||||
const providers = BitbucketCloudEntityProvider.fromConfig(config, {
|
||||
auth,
|
||||
catalogApi,
|
||||
events,
|
||||
logger,
|
||||
schedule,
|
||||
});
|
||||
@@ -168,7 +168,13 @@ describe('BitbucketCloudEntityProvider', () => {
|
||||
});
|
||||
|
||||
it('single simple provider config', () => {
|
||||
const auth = mockServices.auth.mock();
|
||||
const catalogApi = catalogServiceMock.mock();
|
||||
const events = DefaultEventsService.create({ logger });
|
||||
const providers = BitbucketCloudEntityProvider.fromConfig(simpleConfig, {
|
||||
auth,
|
||||
catalogApi,
|
||||
events,
|
||||
logger,
|
||||
schedule,
|
||||
});
|
||||
@@ -180,14 +186,24 @@ describe('BitbucketCloudEntityProvider', () => {
|
||||
});
|
||||
|
||||
it('fail without schedule and scheduler', () => {
|
||||
const auth = mockServices.auth.mock();
|
||||
const catalogApi = catalogServiceMock.mock();
|
||||
const events = DefaultEventsService.create({ logger });
|
||||
|
||||
expect(() =>
|
||||
BitbucketCloudEntityProvider.fromConfig(simpleConfig, {
|
||||
auth,
|
||||
catalogApi,
|
||||
events,
|
||||
logger,
|
||||
}),
|
||||
).toThrow('Either schedule or scheduler must be provided.');
|
||||
});
|
||||
|
||||
it('fail with scheduler but no schedule config', () => {
|
||||
const auth = mockServices.auth.mock();
|
||||
const catalogApi = catalogServiceMock.mock();
|
||||
const events = DefaultEventsService.create({ logger });
|
||||
const scheduler = mockServices.scheduler.mock();
|
||||
const config = new ConfigReader({
|
||||
catalog: {
|
||||
@@ -201,6 +217,9 @@ describe('BitbucketCloudEntityProvider', () => {
|
||||
|
||||
expect(() =>
|
||||
BitbucketCloudEntityProvider.fromConfig(config, {
|
||||
auth,
|
||||
catalogApi,
|
||||
events,
|
||||
logger,
|
||||
scheduler,
|
||||
}),
|
||||
@@ -210,6 +229,9 @@ describe('BitbucketCloudEntityProvider', () => {
|
||||
});
|
||||
|
||||
it('single simple provider config with schedule in config', () => {
|
||||
const auth = mockServices.auth.mock();
|
||||
const catalogApi = catalogServiceMock.mock();
|
||||
const events = DefaultEventsService.create({ logger });
|
||||
const scheduler = mockServices.scheduler.mock();
|
||||
const config = new ConfigReader({
|
||||
catalog: {
|
||||
@@ -226,6 +248,9 @@ describe('BitbucketCloudEntityProvider', () => {
|
||||
});
|
||||
|
||||
const providers = BitbucketCloudEntityProvider.fromConfig(config, {
|
||||
auth,
|
||||
catalogApi,
|
||||
events,
|
||||
logger,
|
||||
scheduler,
|
||||
});
|
||||
@@ -237,6 +262,8 @@ describe('BitbucketCloudEntityProvider', () => {
|
||||
});
|
||||
|
||||
it('multiple provider configs', () => {
|
||||
const auth = mockServices.auth.mock();
|
||||
const catalogApi = catalogServiceMock.mock();
|
||||
const config = new ConfigReader({
|
||||
catalog: {
|
||||
providers: {
|
||||
@@ -251,7 +278,11 @@ describe('BitbucketCloudEntityProvider', () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
const events = DefaultEventsService.create({ logger });
|
||||
const providers = BitbucketCloudEntityProvider.fromConfig(config, {
|
||||
auth,
|
||||
catalogApi,
|
||||
events,
|
||||
logger,
|
||||
schedule,
|
||||
});
|
||||
@@ -266,7 +297,13 @@ describe('BitbucketCloudEntityProvider', () => {
|
||||
});
|
||||
|
||||
it('apply full update on scheduled execution', async () => {
|
||||
const auth = mockServices.auth.mock();
|
||||
const catalogApi = catalogServiceMock.mock();
|
||||
const events = DefaultEventsService.create({ logger });
|
||||
const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, {
|
||||
auth,
|
||||
catalogApi,
|
||||
events,
|
||||
logger,
|
||||
schedule,
|
||||
})[0];
|
||||
@@ -436,6 +473,9 @@ describe('BitbucketCloudEntityProvider', () => {
|
||||
'added-module/catalog-custom.yaml',
|
||||
);
|
||||
|
||||
const auth = mockServices.auth.mock({
|
||||
getPluginRequestToken: async () => ({ token: 'fake-token' }),
|
||||
});
|
||||
const events = DefaultEventsService.create({ logger });
|
||||
const catalogApi = catalogServiceMock.mock({
|
||||
getEntities: async (
|
||||
@@ -457,11 +497,11 @@ describe('BitbucketCloudEntityProvider', () => {
|
||||
},
|
||||
});
|
||||
const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, {
|
||||
auth,
|
||||
catalogApi,
|
||||
events,
|
||||
logger,
|
||||
schedule,
|
||||
tokenManager,
|
||||
})[0];
|
||||
|
||||
server.use(
|
||||
@@ -569,14 +609,15 @@ describe('BitbucketCloudEntityProvider', () => {
|
||||
});
|
||||
|
||||
it('no onRepoPush update on non-matching workspace slug', async () => {
|
||||
const auth = mockServices.auth.mock();
|
||||
const catalogApi = catalogServiceMock.mock();
|
||||
const events = DefaultEventsService.create({ logger });
|
||||
const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, {
|
||||
auth,
|
||||
catalogApi,
|
||||
events,
|
||||
logger,
|
||||
schedule,
|
||||
tokenManager,
|
||||
})[0];
|
||||
|
||||
await provider.connect(entityProviderConnection);
|
||||
@@ -599,14 +640,15 @@ describe('BitbucketCloudEntityProvider', () => {
|
||||
});
|
||||
|
||||
it('no onRepoPush update on non-matching repo slug', async () => {
|
||||
const auth = mockServices.auth.mock();
|
||||
const catalogApi = catalogServiceMock.mock();
|
||||
const events = DefaultEventsService.create({ logger });
|
||||
const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, {
|
||||
auth,
|
||||
catalogApi,
|
||||
events,
|
||||
logger,
|
||||
schedule,
|
||||
tokenManager,
|
||||
})[0];
|
||||
|
||||
await provider.connect(entityProviderConnection);
|
||||
|
||||
+36
-56
@@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { TokenManager } from '@backstage/backend-common';
|
||||
import {
|
||||
AuthService,
|
||||
LoggerService,
|
||||
SchedulerService,
|
||||
SchedulerServiceTaskRunner,
|
||||
@@ -66,26 +66,25 @@ interface IngestionTarget {
|
||||
* @public
|
||||
*/
|
||||
export class BitbucketCloudEntityProvider implements EntityProvider {
|
||||
private readonly auth: AuthService;
|
||||
private readonly catalogApi: CatalogApi;
|
||||
private readonly client: BitbucketCloudClient;
|
||||
private readonly config: BitbucketCloudEntityProviderConfig;
|
||||
private readonly events: EventsService;
|
||||
private readonly logger: LoggerService;
|
||||
private readonly scheduleFn: () => Promise<void>;
|
||||
private readonly catalogApi?: CatalogApi;
|
||||
private readonly events?: EventsService;
|
||||
private readonly tokenManager?: TokenManager;
|
||||
private connection?: EntityProviderConnection;
|
||||
|
||||
private eventConfigErrorThrown = false;
|
||||
private connection?: EntityProviderConnection;
|
||||
|
||||
static fromConfig(
|
||||
config: Config,
|
||||
options: {
|
||||
catalogApi?: CatalogApi;
|
||||
events?: EventsService;
|
||||
auth: AuthService;
|
||||
catalogApi: CatalogApi;
|
||||
events: EventsService;
|
||||
logger: LoggerService;
|
||||
schedule?: SchedulerServiceTaskRunner;
|
||||
scheduler?: SchedulerService;
|
||||
tokenManager?: TokenManager;
|
||||
},
|
||||
): BitbucketCloudEntityProvider[] {
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
@@ -112,35 +111,35 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
|
||||
options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!);
|
||||
|
||||
return new BitbucketCloudEntityProvider(
|
||||
options.auth,
|
||||
options.catalogApi,
|
||||
providerConfig,
|
||||
options.events,
|
||||
integration,
|
||||
options.logger,
|
||||
taskRunner,
|
||||
options.catalogApi,
|
||||
options.events,
|
||||
options.tokenManager,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private constructor(
|
||||
auth: AuthService,
|
||||
catalogApi: CatalogApi,
|
||||
config: BitbucketCloudEntityProviderConfig,
|
||||
events: EventsService,
|
||||
integration: BitbucketCloudIntegration,
|
||||
logger: LoggerService,
|
||||
taskRunner: SchedulerServiceTaskRunner,
|
||||
catalogApi?: CatalogApi,
|
||||
events?: EventsService,
|
||||
tokenManager?: TokenManager,
|
||||
) {
|
||||
this.auth = auth;
|
||||
this.catalogApi = catalogApi;
|
||||
this.client = BitbucketCloudClient.fromConfig(integration.config);
|
||||
this.config = config;
|
||||
this.events = events;
|
||||
this.logger = logger.child({
|
||||
target: this.getProviderName(),
|
||||
});
|
||||
this.scheduleFn = this.createScheduleFn(taskRunner);
|
||||
this.catalogApi = catalogApi;
|
||||
this.events = events;
|
||||
this.tokenManager = tokenManager;
|
||||
}
|
||||
|
||||
private createScheduleFn(
|
||||
@@ -185,19 +184,17 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
|
||||
this.connection = connection;
|
||||
await this.scheduleFn();
|
||||
|
||||
if (this.events) {
|
||||
await this.events.subscribe({
|
||||
id: this.getProviderName(),
|
||||
topics: [TOPIC_REPO_PUSH],
|
||||
onEvent: async params => {
|
||||
if (params.topic !== TOPIC_REPO_PUSH) {
|
||||
return;
|
||||
}
|
||||
await this.events.subscribe({
|
||||
id: this.getProviderName(),
|
||||
topics: [TOPIC_REPO_PUSH],
|
||||
onEvent: async params => {
|
||||
if (params.topic !== TOPIC_REPO_PUSH) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.onRepoPush(params.eventPayload as Events.RepoPushEvent);
|
||||
},
|
||||
});
|
||||
}
|
||||
await this.onRepoPush(params.eventPayload as Events.RepoPushEvent);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async refresh(logger: LoggerService) {
|
||||
@@ -220,32 +217,12 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private enhanceEvent(event: Events.RepoPushEvent): void {
|
||||
// add missing slug
|
||||
event.repository.slug = event.repository.full_name!.split('/', 2)[1];
|
||||
}
|
||||
|
||||
async onRepoPush(event: Events.RepoPushEvent): Promise<void> {
|
||||
if (!this.canHandleEvents()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.connection) {
|
||||
throw new Error('Not initialized');
|
||||
}
|
||||
@@ -273,8 +250,7 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
|
||||
|
||||
const targets = await this.findCatalogFiles(repoSlug);
|
||||
|
||||
const { token } = await this.tokenManager!.getToken();
|
||||
const existing = await this.findExistingLocations(repoUrl, token);
|
||||
const existing = await this.findExistingLocations(repoUrl);
|
||||
|
||||
const added: DeferredEntity[] = this.toDeferredEntities(
|
||||
targets.filter(
|
||||
@@ -321,16 +297,20 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
|
||||
|
||||
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[]>;
|
||||
const { token } = await this.auth.getPluginRequestToken({
|
||||
onBehalfOf: await this.auth.getOwnServiceCredentials(),
|
||||
targetPluginId: 'catalog',
|
||||
});
|
||||
|
||||
return this.catalogApi
|
||||
.getEntities({ filter }, { token })
|
||||
.then(result => result.items) as Promise<LocationEntity[]>;
|
||||
}
|
||||
|
||||
private async findCatalogFiles(
|
||||
|
||||
@@ -5581,7 +5581,6 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-catalog-backend-module-bitbucket-cloud@workspace:plugins/catalog-backend-module-bitbucket-cloud"
|
||||
dependencies:
|
||||
"@backstage/backend-common": ^0.25.0
|
||||
"@backstage/backend-plugin-api": "workspace:^"
|
||||
"@backstage/backend-test-utils": "workspace:^"
|
||||
"@backstage/catalog-client": "workspace:^"
|
||||
|
||||
Reference in New Issue
Block a user