From fa7004d9722c7f8290acaa5842af769512b7cf3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 6 Sep 2023 09:51:03 +0200 Subject: [PATCH] Added a for the new backend system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/rotten-bees-wave.md | 5 + .../alpha-api-report.md | 15 ++ .../catalog-backend-module-github/config.d.ts | 63 ++++++++ .../catalogModuleGithubEntityProvider.test.ts | 4 +- ...talogModuleGithubOrgEntityProvider.test.ts | 77 ++++++++++ .../catalogModuleGithubOrgEntityProvider.ts | 144 ++++++++++++++++++ .../src/module/index.ts | 5 + 7 files changed, 311 insertions(+), 2 deletions(-) create mode 100644 .changeset/rotten-bees-wave.md create mode 100644 plugins/catalog-backend-module-github/src/module/catalogModuleGithubOrgEntityProvider.test.ts create mode 100644 plugins/catalog-backend-module-github/src/module/catalogModuleGithubOrgEntityProvider.ts diff --git a/.changeset/rotten-bees-wave.md b/.changeset/rotten-bees-wave.md new file mode 100644 index 0000000000..a5ce8cae85 --- /dev/null +++ b/.changeset/rotten-bees-wave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-github': minor +--- + +Added a `catalogModuleGithubOrgEntityProvider` for the new backend system diff --git a/plugins/catalog-backend-module-github/alpha-api-report.md b/plugins/catalog-backend-module-github/alpha-api-report.md index 0d4e16b4ea..bf89a57192 100644 --- a/plugins/catalog-backend-module-github/alpha-api-report.md +++ b/plugins/catalog-backend-module-github/alpha-api-report.md @@ -4,9 +4,24 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; +import { ExtensionPoint } from '@backstage/backend-plugin-api'; +import { TeamTransformer } from '@backstage/plugin-catalog-backend-module-github'; +import { UserTransformer } from '@backstage/plugin-catalog-backend-module-github'; // @alpha export const catalogModuleGithubEntityProvider: () => BackendFeature; +// @alpha +export const catalogModuleGithubOrgEntityProvider: () => BackendFeature; + +// @alpha +export interface GithubOrgEntityProviderTransformsExtensionPoint { + setTeamTransformer(transformer: TeamTransformer): void; + setUserTransformer(transformer: UserTransformer): void; +} + +// @alpha +export const githubOrgEntityProviderTransformsExtensionPoint: ExtensionPoint; + // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/catalog-backend-module-github/config.d.ts b/plugins/catalog-backend-module-github/config.d.ts index 9755504ee2..ad4d2d6ec5 100644 --- a/plugins/catalog-backend-module-github/config.d.ts +++ b/plugins/catalog-backend-module-github/config.d.ts @@ -183,6 +183,69 @@ export interface Config { schedule?: TaskScheduleDefinitionConfig; }; }; + + /** + * Configuration for catalogModuleGithubOrgEntityProvider + */ + githubOrg?: + | { + /** + * A stable id for this provider. Entities from this provider will + * be associated with this ID, so you should take care not to change + * it over time since that may lead to orphaned entities and/or + * conflicts. + * + * @example "ghe" + */ + id: string; + + /** + * The target that this provider should consume. + * + * @example "https://mycompany.github.com" + */ + githubUrl: string; + + /** + * The list of the GitHub orgs to consume. By default will consume all accessible + * orgs on the given GitHub instance (support for GitHub App integration only). + */ + orgs?: string[]; + + /** + * The refresh schedule to use. + */ + schedule: TaskScheduleDefinitionConfig; + } + | Array<{ + /** + * A stable id for this provider. Entities from this provider will + * be associated with this ID, so you should take care not to change + * it over time since that may lead to orphaned entities and/or + * conflicts. + * + * @example "ghe" + */ + id: string; + + /** + * The target that this provider should consume. + * + * @example "https://mycompany.github.com" + */ + githubUrl: string; + + /** + * The list of the GitHub orgs to consume. By default will consume all accessible + * orgs on the given GitHub instance (support for GitHub App integration only). + */ + orgs?: string[]; + + /** + * The refresh schedule to use. + */ + schedule: TaskScheduleDefinitionConfig; + }>; }; }; } diff --git a/plugins/catalog-backend-module-github/src/module/catalogModuleGithubEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/module/catalogModuleGithubEntityProvider.test.ts index 171a641fc7..cb38be1a5d 100644 --- a/plugins/catalog-backend-module-github/src/module/catalogModuleGithubEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/module/catalogModuleGithubEntityProvider.test.ts @@ -16,14 +16,14 @@ import { TaskScheduleDefinition } from '@backstage/backend-tasks'; import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import { EntityProvider } from '@backstage/plugin-catalog-node'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; import { Duration } from 'luxon'; import { catalogModuleGithubEntityProvider } from './catalogModuleGithubEntityProvider'; -import { GithubEntityProvider } from '../providers/GithubEntityProvider'; describe('catalogModuleGithubEntityProvider', () => { it('should register provider at the catalog extension point', async () => { - let addedProviders: Array | undefined; + let addedProviders: Array | undefined; let usedSchedule: TaskScheduleDefinition | undefined; const extensionPoint = { diff --git a/plugins/catalog-backend-module-github/src/module/catalogModuleGithubOrgEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/module/catalogModuleGithubOrgEntityProvider.test.ts new file mode 100644 index 0000000000..1b8b115a96 --- /dev/null +++ b/plugins/catalog-backend-module-github/src/module/catalogModuleGithubOrgEntityProvider.test.ts @@ -0,0 +1,77 @@ +/* + * 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 { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import { EntityProvider } from '@backstage/plugin-catalog-node'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; +import { Duration } from 'luxon'; +import { catalogModuleGithubOrgEntityProvider } from './catalogModuleGithubOrgEntityProvider'; + +describe('catalogModuleGithubOrgEntityProvider', () => { + it('should register provider at the catalog extension point', async () => { + let addedProviders: Array | undefined; + let usedSchedule: TaskScheduleDefinition | undefined; + + const extensionPoint = { + addEntityProvider: (...providers: any) => { + addedProviders = providers; + }, + }; + const runner = jest.fn(); + const scheduler = mockServices.scheduler.mock({ + createScheduledTaskRunner(schedule) { + usedSchedule = schedule; + return { run: runner }; + }, + }); + + const config = { + catalog: { + providers: { + githubOrg: [ + { + id: 'default', + githubUrl: 'https://github.com', + orgs: ['backstage'], + schedule: { + frequency: 'P1M', + timeout: 'PT3M', + }, + }, + ], + }, + }, + }; + + await startTestBackend({ + extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]], + features: [ + catalogModuleGithubOrgEntityProvider(), + mockServices.rootConfig.factory({ data: config }), + scheduler.factory, + ], + }); + + expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M')); + expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M')); + expect(addedProviders?.length).toEqual(1); + expect(addedProviders![0].getProviderName()).toEqual( + 'GithubMultiOrgEntityProvider:default', + ); + expect(runner).not.toHaveBeenCalled(); + }); +}); diff --git a/plugins/catalog-backend-module-github/src/module/catalogModuleGithubOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/module/catalogModuleGithubOrgEntityProvider.ts new file mode 100644 index 0000000000..4838be54e1 --- /dev/null +++ b/plugins/catalog-backend-module-github/src/module/catalogModuleGithubOrgEntityProvider.ts @@ -0,0 +1,144 @@ +/* + * 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 { loggerToWinstonLogger } from '@backstage/backend-common'; +import { + coreServices, + createBackendModule, + createExtensionPoint, +} from '@backstage/backend-plugin-api'; +import { + readTaskScheduleDefinitionFromConfig, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; +import { Config } from '@backstage/config'; +import { + GithubMultiOrgEntityProvider, + TeamTransformer, + UserTransformer, +} from '@backstage/plugin-catalog-backend-module-github'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; + +/** + * Interface for {@link githubOrgEntityProviderTransformsExtensionPoint}. + * + * @alpha + */ +export interface GithubOrgEntityProviderTransformsExtensionPoint { + /** + * Set a custom transformer for transforming from GitHub users to catalog + * entities. + */ + setUserTransformer(transformer: UserTransformer): void; + + /** + * Set a custom transformer for transforming from GitHub teams to catalog + * entities. + */ + setTeamTransformer(transformer: TeamTransformer): void; +} + +/** + * Extension point for runtime configuration of {@link catalogModuleGithubOrgEntityProvider}. + * + * @alpha + */ +export const githubOrgEntityProviderTransformsExtensionPoint = + createExtensionPoint({ + id: 'catalog.githubOrgEntityProvider', + }); + +/** + * Registers the `GithubMultiOrgEntityProvider` with the catalog processing extension point. + * + * @alpha + */ +export const catalogModuleGithubOrgEntityProvider = createBackendModule({ + pluginId: 'catalog', + moduleId: 'githubOrgEntityProvider', + register(env) { + let userTransformer: UserTransformer | undefined; + let teamTransformer: TeamTransformer | undefined; + + env.registerExtensionPoint( + githubOrgEntityProviderTransformsExtensionPoint, + { + setUserTransformer(transformer) { + if (userTransformer) { + throw new Error('User transformer may only be set once'); + } + userTransformer = transformer; + }, + setTeamTransformer(transformer) { + if (teamTransformer) { + throw new Error('Team transformer may only be set once'); + } + teamTransformer = transformer; + }, + }, + ); + + env.registerInit({ + deps: { + catalog: catalogProcessingExtensionPoint, + config: coreServices.rootConfig, + logger: coreServices.logger, + scheduler: coreServices.scheduler, + }, + async init({ catalog, config, logger, scheduler }) { + for (const definition of readDefinitionsFromConfig(config)) { + catalog.addEntityProvider( + GithubMultiOrgEntityProvider.fromConfig(config, { + id: definition.id, + githubUrl: definition.githubUrl, + orgs: definition.orgs, + schedule: scheduler.createScheduledTaskRunner( + definition.schedule, + ), + logger: loggerToWinstonLogger(logger), + userTransformer, + teamTransformer, + }), + ); + } + }, + }); + }, +}); + +function readDefinitionsFromConfig(rootConfig: Config): Array<{ + id: string; + githubUrl: string; + orgs?: string[]; + schedule: TaskScheduleDefinition; +}> { + const baseKey = 'catalog.providers.githubOrg'; + const baseConfig = rootConfig.getOptional(baseKey); + if (!baseConfig) { + return []; + } + + const configs = Array.isArray(baseConfig) + ? rootConfig.getConfigArray(baseKey) + : [rootConfig.getConfig(baseKey)]; + + return configs.map(c => ({ + id: c.getString('id'), + githubUrl: c.getString('githubUrl'), + orgs: c.getOptionalStringArray('orgs'), + schedule: readTaskScheduleDefinitionFromConfig(c.getConfig('schedule')), + })); +} diff --git a/plugins/catalog-backend-module-github/src/module/index.ts b/plugins/catalog-backend-module-github/src/module/index.ts index 640a633093..ace488c054 100644 --- a/plugins/catalog-backend-module-github/src/module/index.ts +++ b/plugins/catalog-backend-module-github/src/module/index.ts @@ -15,3 +15,8 @@ */ export { catalogModuleGithubEntityProvider } from './catalogModuleGithubEntityProvider'; +export { + catalogModuleGithubOrgEntityProvider, + githubOrgEntityProviderTransformsExtensionPoint, + type GithubOrgEntityProviderTransformsExtensionPoint, +} from './catalogModuleGithubOrgEntityProvider';