diff --git a/.changeset/beige-kiwis-know.md b/.changeset/beige-kiwis-know.md new file mode 100644 index 0000000000..696b9e81cb --- /dev/null +++ b/.changeset/beige-kiwis-know.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-plugin-api': minor +--- + +Introduced new package for creating backend plugins using the new alpha backend plugin framework. +This package is still considered **EXPERIMENTAL** and things will change without warning. Do not use this for production. diff --git a/.changeset/lazy-steaks-tell.md b/.changeset/lazy-steaks-tell.md new file mode 100644 index 0000000000..5e63816ada --- /dev/null +++ b/.changeset/lazy-steaks-tell.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-node': minor +--- + +Added alpha exports for the new experimental backend system. diff --git a/.changeset/polite-moose-beam.md b/.changeset/polite-moose-beam.md new file mode 100644 index 0000000000..5e41018ae6 --- /dev/null +++ b/.changeset/polite-moose-beam.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': minor +--- + +Export experimental `catalogPlugin` for the new backend system. This export is not considered stable and should not be used in production. diff --git a/.changeset/short-wolves-applaud.md b/.changeset/short-wolves-applaud.md new file mode 100644 index 0000000000..fa5cebffc3 --- /dev/null +++ b/.changeset/short-wolves-applaud.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +Export experimental `scaffolderCatalogExtension` for the new backend system. This export is not considered stable and should not be used in production. diff --git a/.changeset/swift-plants-fix.md b/.changeset/swift-plants-fix.md new file mode 100644 index 0000000000..52873b7985 --- /dev/null +++ b/.changeset/swift-plants-fix.md @@ -0,0 +1,7 @@ +--- +'@backstage/backend-app-api': minor +--- + +Add initial plumbing for creating backends using the experimental backend framework. + +This package is highly **EXPERIMENTAL** and should not be used in production. diff --git a/packages/backend-app-api/.eslintrc.js b/packages/backend-app-api/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/packages/backend-app-api/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/packages/backend-app-api/README.md b/packages/backend-app-api/README.md new file mode 100644 index 0000000000..ee608ceb43 --- /dev/null +++ b/packages/backend-app-api/README.md @@ -0,0 +1,19 @@ +# @backstage/backend-app-api + +**This package is HIGHLY EXPERIMENTAL, do not use this for production** + +This package provides the core API used by Backstage backend apps. + +## Installation + +Add the library to your backend app package: + +```bash +# From your Backstage root directory +yarn add --cwd packages/backend @backstage/backend-app-api +``` + +## Documentation + +- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md new file mode 100644 index 0000000000..a37ec6abc6 --- /dev/null +++ b/packages/backend-app-api/api-report.md @@ -0,0 +1,25 @@ +## API Report File for "@backstage/backend-app-api" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { AnyServiceFactory } from '@backstage/backend-plugin-api'; +import { BackendRegistrable } from '@backstage/backend-plugin-api'; + +// @public (undocumented) +export interface Backend { + // (undocumented) + add(extension: BackendRegistrable): void; + // (undocumented) + start(): Promise; +} + +// @public (undocumented) +export function createBackend(options?: CreateBackendOptions): Backend; + +// @public (undocumented) +export interface CreateBackendOptions { + // (undocumented) + apis: AnyServiceFactory[]; +} +``` diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json new file mode 100644 index 0000000000..ae6f7eb03b --- /dev/null +++ b/packages/backend-app-api/package.json @@ -0,0 +1,52 @@ +{ + "name": "@backstage/backend-app-api", + "description": "Core API used by Backstage backend apps", + "version": "0.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "private": false, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts", + "alphaTypes": "dist/index.alpha.d.ts" + }, + "backstage": { + "role": "node-library" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/backend-app-api" + }, + "keywords": [ + "backstage" + ], + "license": "Apache-2.0", + "scripts": { + "build": "backstage-cli package build --experimental-type-build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" + }, + "dependencies": { + "@backstage/backend-plugin-api": "^0.0.0", + "@backstage/backend-common": "^0.14.1-next.2", + "@backstage/backend-tasks": "^0.3.3-next.2", + "@backstage/plugin-permission-node": "^0.6.3-next.1", + "express": "^4.17.1", + "express-promise-router": "^4.1.0", + "winston": "^3.2.1" + }, + "devDependencies": { + "@backstage/cli": "^0.18.0-next.2" + }, + "files": [ + "dist", + "alpha" + ] +} diff --git a/packages/backend-app-api/src/index.ts b/packages/backend-app-api/src/index.ts new file mode 100644 index 0000000000..c58379c3e4 --- /dev/null +++ b/packages/backend-app-api/src/index.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/** + * Core API used by Backstage backend apps. + * + * @packageDocumentation + */ + +export * from './wiring'; diff --git a/packages/backend-app-api/src/services/implementations/cacheService.ts b/packages/backend-app-api/src/services/implementations/cacheService.ts new file mode 100644 index 0000000000..034f85f917 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/cacheService.ts @@ -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 { CacheManager } from '@backstage/backend-common'; +import { + configServiceRef, + createServiceFactory, + cacheServiceRef, +} from '@backstage/backend-plugin-api'; + +// TODO: Work out some naming and implementation patterns for these +export const cacheFactory = createServiceFactory({ + service: cacheServiceRef, + deps: { + configFactory: configServiceRef, + }, + factory: async ({ configFactory }) => { + const config = await configFactory('root'); + const cacheManager = CacheManager.fromConfig(config); + return async (pluginId: string) => { + return cacheManager.forPlugin(pluginId); + }; + }, +}); diff --git a/packages/backend-app-api/src/services/implementations/configService.ts b/packages/backend-app-api/src/services/implementations/configService.ts new file mode 100644 index 0000000000..94a59baf34 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/configService.ts @@ -0,0 +1,40 @@ +/* + * 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 { loadBackendConfig } from '@backstage/backend-common'; +import { + configServiceRef, + createServiceFactory, + loggerToWinstonLogger, + loggerServiceRef, +} from '@backstage/backend-plugin-api'; + +export const configFactory = createServiceFactory({ + service: configServiceRef, + deps: { + loggerFactory: loggerServiceRef, + }, + factory: async ({ loggerFactory }) => { + const logger = await loggerFactory('root'); + const config = await loadBackendConfig({ + argv: process.argv, + logger: loggerToWinstonLogger(logger), + }); + return async () => { + return config; + }; + }, +}); diff --git a/packages/backend-app-api/src/services/implementations/databaseService.ts b/packages/backend-app-api/src/services/implementations/databaseService.ts new file mode 100644 index 0000000000..a52da1e444 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/databaseService.ts @@ -0,0 +1,36 @@ +/* + * 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 { DatabaseManager } from '@backstage/backend-common'; +import { + configServiceRef, + createServiceFactory, + databaseServiceRef, +} from '@backstage/backend-plugin-api'; + +export const databaseFactory = createServiceFactory({ + service: databaseServiceRef, + deps: { + configFactory: configServiceRef, + }, + factory: async ({ configFactory }) => { + const config = await configFactory('root'); + const databaseManager = DatabaseManager.fromConfig(config); + return async (pluginId: string) => { + return databaseManager.forPlugin(pluginId); + }; + }, +}); diff --git a/packages/backend-app-api/src/services/implementations/discoveryService.ts b/packages/backend-app-api/src/services/implementations/discoveryService.ts new file mode 100644 index 0000000000..23af1924a0 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/discoveryService.ts @@ -0,0 +1,36 @@ +/* + * 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 { SingleHostDiscovery } from '@backstage/backend-common'; +import { + configServiceRef, + createServiceFactory, + discoveryServiceRef, +} from '@backstage/backend-plugin-api'; + +export const discoveryFactory = createServiceFactory({ + service: discoveryServiceRef, + deps: { + configFactory: configServiceRef, + }, + factory: async ({ configFactory }) => { + const config = await configFactory('root'); + const discovery = SingleHostDiscovery.fromConfig(config); + return async () => { + return discovery; + }; + }, +}); diff --git a/packages/backend-app-api/src/services/implementations/httpRouterService.ts b/packages/backend-app-api/src/services/implementations/httpRouterService.ts new file mode 100644 index 0000000000..1f72378643 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/httpRouterService.ts @@ -0,0 +1,49 @@ +/* + * 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 { + createServiceFactory, + httpRouterServiceRef, + configServiceRef, +} from '@backstage/backend-plugin-api'; +import Router from 'express-promise-router'; +import { Handler } from 'express'; +import { createServiceBuilder } from '@backstage/backend-common'; + +export const httpRouterFactory = createServiceFactory({ + service: httpRouterServiceRef, + deps: { + configFactory: configServiceRef, + }, + factory: async ({ configFactory }) => { + const rootRouter = Router(); + + const service = createServiceBuilder(module) + .loadConfig(await configFactory('root')) + .addRouter('', rootRouter); + + await service.start(); + + return async (pluginId?: string) => { + const path = pluginId ? `/api/${pluginId}` : ''; + return { + use(handler: Handler) { + rootRouter.use(path, handler); + }, + }; + }; + }, +}); diff --git a/packages/backend-app-api/src/services/implementations/index.ts b/packages/backend-app-api/src/services/implementations/index.ts new file mode 100644 index 0000000000..e6b6604069 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/index.ts @@ -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 { cacheFactory } from './cacheService'; +import { configFactory } from './configService'; +import { databaseFactory } from './databaseService'; +import { discoveryFactory } from './discoveryService'; +import { loggerFactory } from './loggerService'; +import { permissionsFactory } from './permissionsService'; +import { schedulerFactory } from './schedulerService'; +import { tokenManagerFactory } from './tokenManagerService'; +import { urlReaderFactory } from './urlReaderService'; +import { httpRouterFactory } from './httpRouterService'; + +export const defaultServiceFactories = [ + cacheFactory, + configFactory, + databaseFactory, + discoveryFactory, + loggerFactory, + permissionsFactory, + schedulerFactory, + tokenManagerFactory, + urlReaderFactory, + httpRouterFactory, +]; diff --git a/packages/backend-app-api/src/services/implementations/loggerService.ts b/packages/backend-app-api/src/services/implementations/loggerService.ts new file mode 100644 index 0000000000..fbeb4b9ade --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/loggerService.ts @@ -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 { createRootLogger } from '@backstage/backend-common'; +import { + createServiceFactory, + Logger, + loggerServiceRef, +} from '@backstage/backend-plugin-api'; +import { Logger as WinstonLogger } from 'winston'; + +class BackstageLogger implements Logger { + static fromWinston(logger: WinstonLogger): BackstageLogger { + return new BackstageLogger(logger); + } + + private constructor(private readonly winston: WinstonLogger) {} + + info(message: string, ...meta: any[]): void { + this.winston.info(message, ...meta); + } + + child(fields: { [name: string]: string }): Logger { + return new BackstageLogger(this.winston.child(fields)); + } +} + +export const loggerFactory = createServiceFactory({ + service: loggerServiceRef, + deps: {}, + factory: async () => { + const root = BackstageLogger.fromWinston(createRootLogger()); + return async (pluginId: string) => { + return root.child({ pluginId }); + }; + }, +}); diff --git a/packages/backend-app-api/src/services/implementations/permissionsService.ts b/packages/backend-app-api/src/services/implementations/permissionsService.ts new file mode 100644 index 0000000000..97f0330917 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/permissionsService.ts @@ -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 { + configServiceRef, + createServiceFactory, + discoveryServiceRef, + permissionsServiceRef, + tokenManagerServiceRef, +} from '@backstage/backend-plugin-api'; +import { ServerPermissionClient } from '@backstage/plugin-permission-node'; + +export const permissionsFactory = createServiceFactory({ + service: permissionsServiceRef, + deps: { + configFactory: configServiceRef, + discoveryFactory: discoveryServiceRef, + tokenManagerFactory: tokenManagerServiceRef, + }, + factory: async ({ configFactory, discoveryFactory, tokenManagerFactory }) => { + const config = await configFactory('root'); + const discovery = await discoveryFactory('root'); + const tokenManager = await tokenManagerFactory('root'); + const permissions = ServerPermissionClient.fromConfig(config, { + discovery, + tokenManager, + }); + return async (_pluginId: string) => { + return permissions; + }; + }, +}); diff --git a/packages/backend-app-api/src/services/implementations/schedulerService.ts b/packages/backend-app-api/src/services/implementations/schedulerService.ts new file mode 100644 index 0000000000..130ccb3f5f --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/schedulerService.ts @@ -0,0 +1,36 @@ +/* + * 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, + createServiceFactory, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { TaskScheduler } from '@backstage/backend-tasks'; + +export const schedulerFactory = createServiceFactory({ + service: schedulerServiceRef, + deps: { + configFactory: configServiceRef, + }, + factory: async ({ configFactory }) => { + const config = await configFactory('root'); + const taskScheduler = TaskScheduler.fromConfig(config); + return async (pluginId: string) => { + return taskScheduler.forPlugin(pluginId); + }; + }, +}); diff --git a/packages/backend-app-api/src/services/implementations/tokenManagerService.ts b/packages/backend-app-api/src/services/implementations/tokenManagerService.ts new file mode 100644 index 0000000000..2e82fce9d9 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/tokenManagerService.ts @@ -0,0 +1,60 @@ +/* + * 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, + loggerServiceRef, + createServiceFactory, + tokenManagerServiceRef, + loggerToWinstonLogger, +} from '@backstage/backend-plugin-api'; +import { ServerTokenManager } from '@backstage/backend-common'; + +export const tokenManagerFactory = createServiceFactory({ + service: tokenManagerServiceRef, + deps: { + configFactory: configServiceRef, + loggerFactory: loggerServiceRef, + }, + factory: async ({ configFactory, loggerFactory }) => { + const logger = await loggerFactory('root'); + const config = await configFactory('root'); + return async (_pluginId: string) => { + // doesn't the logger want to be inferred from the plugin tho here? + // maybe ... also why do we recreate it every time otherwise + // we should memoize on a per plugin right? so I think it's should be fine to re-use the plugin one + // we shouldn't recreate on a per plugin basis. + // hm - on the other hand, is this really ever called more than once? + // not this function right. should only be called when the plugin requests this serviceRef + // yeah so no need to worry about memo probably + // but we still want to scope the logger to the ServrTokenmanagfer>? + // mm sure maybe + // maybe in this case it doesn't provide so much value b + // oh hang on - isn't it up to THE MANAGER to make a child internally if it wants to do that + // so that it becomes a property intrinsic to that class, no matter how it's constructed + // or is that too much responsibility for it - making the constructor complex so to speak, making it harder to tweak that behavior + // this is not ultra efficient :) + + // I think the naming here is wrong to be gonest + // this isn't like the cache manager or the database manager + // the manager name is confusuion i think + // aye perhaps + return ServerTokenManager.fromConfig(config, { + logger: loggerToWinstonLogger(logger), + }); + }; + }, +}); diff --git a/packages/backend-app-api/src/services/implementations/urlReaderService.ts b/packages/backend-app-api/src/services/implementations/urlReaderService.ts new file mode 100644 index 0000000000..74431dfa0d --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/urlReaderService.ts @@ -0,0 +1,41 @@ +/* + * 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 { UrlReaders } from '@backstage/backend-common'; +import { + configServiceRef, + createServiceFactory, + loggerServiceRef, + loggerToWinstonLogger, + urlReaderServiceRef, +} from '@backstage/backend-plugin-api'; + +export const urlReaderFactory = createServiceFactory({ + service: urlReaderServiceRef, + deps: { + configFactory: configServiceRef, + loggerFactory: loggerServiceRef, + }, + factory: async ({ configFactory, loggerFactory }) => { + return async (pluginId: string) => { + const logger = await loggerFactory(pluginId); + return UrlReaders.default({ + logger: loggerToWinstonLogger(logger), + config: await configFactory(pluginId), + }); + }; + }, +}); diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.ts b/packages/backend-app-api/src/wiring/BackendInitializer.ts new file mode 100644 index 0000000000..ab086f4144 --- /dev/null +++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts @@ -0,0 +1,155 @@ +/* + * 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 { + BackendRegistrable, + ExtensionPoint, + ServiceRef, +} from '@backstage/backend-plugin-api'; +import { BackendRegisterInit, ServiceHolder } from './types'; + +type ServiceOrExtensionPoint = ExtensionPoint | ServiceRef; + +export class BackendInitializer { + #started = false; + #extensions = new Map(); + #registerInits = new Array(); + #extensionPoints = new Map(); + #serviceHolder: ServiceHolder; + + constructor(serviceHolder: ServiceHolder) { + this.#serviceHolder = serviceHolder; + } + + async #getInitDeps( + deps: { [name: string]: ServiceOrExtensionPoint }, + pluginId: string, + ) { + return Object.fromEntries( + await Promise.all( + Object.entries(deps).map(async ([name, ref]) => [ + name, + this.#extensionPoints.get(ref) || + (await this.#serviceHolder.get(ref as ServiceRef)!( + pluginId, + )), + ]), + ), + ); + } + + add(extension: BackendRegistrable, options?: TOptions) { + if (this.#started) { + throw new Error( + 'extension can not be added after the backend has started', + ); + } + this.#extensions.set(extension, options); + } + + async start(): Promise { + console.log(`Starting backend`); + if (this.#started) { + throw new Error('Backend has already started'); + } + this.#started = true; + + for (const [extension] of this.#extensions) { + const provides = new Set>(); + + let registerInit: BackendRegisterInit | undefined = undefined; + + console.log('Registering', extension.id); + extension.register({ + registerExtensionPoint: (extensionPointRef, impl) => { + if (registerInit) { + throw new Error('registerExtensionPoint called after registerInit'); + } + if (this.#extensionPoints.has(extensionPointRef)) { + throw new Error(`API ${extensionPointRef.id} already registered`); + } + this.#extensionPoints.set(extensionPointRef, impl); + provides.add(extensionPointRef); + }, + registerInit: registerOptions => { + if (registerInit) { + throw new Error('registerInit must only be called once'); + } + registerInit = { + id: extension.id, + provides, + consumes: new Set(Object.values(registerOptions.deps)), + deps: registerOptions.deps, + init: registerOptions.init as BackendRegisterInit['init'], + }; + }, + }); + + if (!registerInit) { + throw new Error( + `registerInit was not called by register in ${extension.id}`, + ); + } + + this.#registerInits.push(registerInit); + } + + this.validateSetup(); + + const orderedRegisterResults = this.#resolveInitOrder(this.#registerInits); + + for (const registerInit of orderedRegisterResults) { + const deps = await this.#getInitDeps(registerInit.deps, registerInit.id); + await registerInit.init(deps); + } + } + + private validateSetup() {} + + #resolveInitOrder(registerInits: Array) { + let registerInitsToOrder = registerInits.slice(); + const orderedRegisterInits = new Array(); + + // TODO: Validate duplicates + + while (registerInitsToOrder.length > 0) { + const toRemove = new Set(); + + for (const registerInit of registerInitsToOrder) { + const unInitializedDependents = []; + + for (const serviceRef of registerInit.provides) { + if ( + registerInitsToOrder.some( + init => init !== registerInit && init.consumes.has(serviceRef), + ) + ) { + unInitializedDependents.push(serviceRef); + } + } + + if (unInitializedDependents.length === 0) { + console.log(`DEBUG: pushed ${registerInit.id} to results`); + orderedRegisterInits.push(registerInit); + toRemove.add(registerInit); + } + } + + registerInitsToOrder = registerInitsToOrder.filter(r => !toRemove.has(r)); + } + return orderedRegisterInits; + } +} diff --git a/packages/backend-app-api/src/wiring/BackstageBackend.ts b/packages/backend-app-api/src/wiring/BackstageBackend.ts new file mode 100644 index 0000000000..104d2b6fc8 --- /dev/null +++ b/packages/backend-app-api/src/wiring/BackstageBackend.ts @@ -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 { + AnyServiceFactory, + BackendRegistrable, +} from '@backstage/backend-plugin-api'; +import { BackendInitializer } from './BackendInitializer'; +import { ServiceRegistry } from './ServiceRegistry'; +import { Backend } from './types'; + +export class BackstageBackend implements Backend { + #services: ServiceRegistry; + #initializer: BackendInitializer; + + constructor(apiFactories: AnyServiceFactory[]) { + this.#services = new ServiceRegistry(apiFactories); + this.#initializer = new BackendInitializer(this.#services); + } + + add(extension: BackendRegistrable): void { + this.#initializer.add(extension); + } + + async start(): Promise { + await this.#initializer.start(); + } + + // async stop(): Promise { + // await this.#initializer.stop(); + // } +} diff --git a/packages/backend-app-api/src/wiring/ServiceRegistry.ts b/packages/backend-app-api/src/wiring/ServiceRegistry.ts new file mode 100644 index 0000000000..bdaecf30c6 --- /dev/null +++ b/packages/backend-app-api/src/wiring/ServiceRegistry.ts @@ -0,0 +1,63 @@ +/* + * 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 { + AnyServiceFactory, + FactoryFunc, + ServiceRef, +} from '@backstage/backend-plugin-api'; + +export class ServiceRegistry { + readonly #implementations: Map>; + readonly #factories: Map; + + constructor(factories: AnyServiceFactory[]) { + this.#factories = new Map(factories.map(f => [f.service.id, f])); + this.#implementations = new Map(); + } + + get(ref: ServiceRef): FactoryFunc | undefined { + const factory = this.#factories.get(ref.id); + if (!factory) { + return undefined; + } + + return async (pluginId: string): Promise => { + let implementations = this.#implementations.get(ref.id); + if (implementations) { + if (implementations.has(pluginId)) { + return implementations.get(pluginId) as T; + } + } else { + implementations = new Map(); + this.#implementations.set(ref.id, implementations); + } + + const factoryDeps = Object.fromEntries( + Object.entries(factory.deps).map(([name, serviceRef]) => [ + name, + this.get(serviceRef)!, // TODO: throw + ]), + ); + + const factoryFunc = await factory.factory(factoryDeps); + const implementation = await factoryFunc(pluginId); + + implementations.set(pluginId, implementation); + + return implementation as T; + }; + } +} diff --git a/packages/backend-app-api/src/wiring/index.ts b/packages/backend-app-api/src/wiring/index.ts new file mode 100644 index 0000000000..1dd91e30e1 --- /dev/null +++ b/packages/backend-app-api/src/wiring/index.ts @@ -0,0 +1,18 @@ +/* + * 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 type { Backend, CreateBackendOptions } from './types'; +export { createBackend } from './types'; diff --git a/packages/backend-app-api/src/wiring/types.ts b/packages/backend-app-api/src/wiring/types.ts new file mode 100644 index 0000000000..112fe06b05 --- /dev/null +++ b/packages/backend-app-api/src/wiring/types.ts @@ -0,0 +1,62 @@ +/* + * 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 { + AnyServiceFactory, + BackendRegistrable, + FactoryFunc, + ServiceRef, +} from '@backstage/backend-plugin-api'; +import { defaultServiceFactories } from '../services/implementations'; +import { BackstageBackend } from './BackstageBackend'; + +/** + * @public + */ +export interface Backend { + add(extension: BackendRegistrable): void; + start(): Promise; +} + +export interface BackendRegisterInit { + id: string; + consumes: Set>; + provides: Set>; + deps: { [name: string]: ServiceRef }; + init: (deps: { [name: string]: unknown }) => Promise; +} + +/** + * @public + */ +export interface CreateBackendOptions { + apis: AnyServiceFactory[]; +} + +export type ServiceHolder = { + get(api: ServiceRef): FactoryFunc | undefined; +}; + +/** + * @public + */ +export function createBackend(options?: CreateBackendOptions): Backend { + // TODO: merge with provided APIs + return new BackstageBackend([ + ...defaultServiceFactories, + ...(options?.apis ?? []), + ]); +} diff --git a/packages/backend-next/.eslintrc.js b/packages/backend-next/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/packages/backend-next/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/packages/backend-next/README.md b/packages/backend-next/README.md new file mode 100644 index 0000000000..94fc0767c4 --- /dev/null +++ b/packages/backend-next/README.md @@ -0,0 +1,5 @@ +# example-backend-next + +This is an example backend for the new **EXPERIMENTAL** Backstage backend system. + +Do not use this in your own projects. diff --git a/packages/backend-next/package.json b/packages/backend-next/package.json new file mode 100644 index 0000000000..673116790e --- /dev/null +++ b/packages/backend-next/package.json @@ -0,0 +1,38 @@ +{ + "name": "example-backend-next", + "version": "0.0.0", + "main": "dist/index.cjs.js", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": true, + "backstage": { + "role": "backend" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/backend-next" + }, + "keywords": [ + "backstage" + ], + "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" + }, + "dependencies": { + "@backstage/backend-app-api": "^0.0.0", + "@backstage/plugin-catalog-backend": "^1.2.1-next.2", + "@backstage/plugin-scaffolder-backend": "^1.4.0-next.2" + }, + "devDependencies": { + "@backstage/cli": "^0.18.0-next.2" + }, + "files": [ + "dist" + ] +} diff --git a/packages/backend-next/src/index.ts b/packages/backend-next/src/index.ts new file mode 100644 index 0000000000..6463dc7ed5 --- /dev/null +++ b/packages/backend-next/src/index.ts @@ -0,0 +1,27 @@ +/* + * 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 { createBackend } from '@backstage/backend-app-api'; +import { catalogPlugin } from '@backstage/plugin-catalog-backend'; +import { scaffolderCatalogModule } from '@backstage/plugin-scaffolder-backend'; + +const backend = createBackend({ + apis: [], +}); + +backend.add(catalogPlugin({})); +backend.add(scaffolderCatalogModule({})); +backend.start(); diff --git a/packages/backend-plugin-api/.eslintrc.js b/packages/backend-plugin-api/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/packages/backend-plugin-api/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/packages/backend-plugin-api/README.md b/packages/backend-plugin-api/README.md new file mode 100644 index 0000000000..364655dd60 --- /dev/null +++ b/packages/backend-plugin-api/README.md @@ -0,0 +1,19 @@ +# @backstage/backend-plugin-api + +**This package is HIGHLY EXPERIMENTAL, do not use this for production** + +This package provides the core API used by Backstage backend plugins. + +## Installation + +Add the library to your backend plugin package: + +```bash +# From your Backstage root directory +yarn add --cwd plugin/-backend @backstage/backend-plugin-api +``` + +## Documentation + +- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md new file mode 100644 index 0000000000..df8474d670 --- /dev/null +++ b/packages/backend-plugin-api/api-report.md @@ -0,0 +1,197 @@ +## API Report File for "@backstage/backend-plugin-api" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { Config } from '@backstage/config'; +import { Handler } from 'express'; +import { Logger as Logger_2 } from 'winston'; +import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; +import { PermissionEvaluator } from '@backstage/plugin-permission-common'; +import { PluginCacheManager } from '@backstage/backend-common'; +import { PluginDatabaseManager } from '@backstage/backend-common'; +import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; +import { TokenManager } from '@backstage/backend-common'; +import { TransportStreamOptions } from 'winston-transport'; +import { UrlReader } from '@backstage/backend-common'; + +// @public (undocumented) +export type AnyServiceFactory = ServiceFactory< + unknown, + unknown, + { + [key in string]: unknown; + } +>; + +// @public (undocumented) +export interface BackendInitRegistry { + // (undocumented) + registerExtensionPoint( + ref: ServiceRef, + impl: TExtensionPoint, + ): void; + // (undocumented) + registerInit< + Deps extends { + [name in string]: unknown; + }, + >(options: { + deps: { + [name in keyof Deps]: ServiceRef; + }; + init: (deps: Deps) => Promise; + }): void; +} + +// @public (undocumented) +export interface BackendModuleConfig { + // (undocumented) + moduleId: string; + // (undocumented) + pluginId: string; + // (undocumented) + register( + reg: Omit, + options: TOptions, + ): void; +} + +// @public (undocumented) +export interface BackendPluginConfig { + // (undocumented) + id: string; + // (undocumented) + register(reg: BackendInitRegistry, options: TOptions): void; +} + +// @public (undocumented) +export interface BackendRegistrable { + // (undocumented) + id: string; + // (undocumented) + register(reg: BackendInitRegistry): void; +} + +// @public (undocumented) +export const cacheServiceRef: ServiceRef; + +// @public (undocumented) +export const configServiceRef: ServiceRef; + +// @public (undocumented) +export function createBackendModule( + config: BackendModuleConfig, +): (option: TOptions) => BackendRegistrable; + +// @public (undocumented) +export function createBackendPlugin( + config: BackendPluginConfig, +): (option: TOptions) => BackendRegistrable; + +// @public (undocumented) +export function createExtensionPoint(options: { + id: string; +}): ExtensionPoint; + +// @public (undocumented) +export function createServiceFactory< + Api, + Impl extends Api, + Deps extends { + [name in string]: unknown; + }, +>(factory: ServiceFactory): ServiceFactory; + +// @public (undocumented) +export function createServiceRef(options: { id: string }): ServiceRef; + +// @public (undocumented) +export const databaseServiceRef: ServiceRef; + +// @public (undocumented) +export type DepsToDepFactories = { + [key in keyof T]: (pluginId: string) => Promise; +}; + +// @public (undocumented) +export const discoveryServiceRef: ServiceRef; + +// @public +export type ExtensionPoint = { + id: string; + T: T; + toString(): string; + $$ref: 'extension-point'; +}; + +// @public (undocumented) +export type FactoryFunc = (pluginId: string) => Promise; + +// @public (undocumented) +export interface HttpRouterService { + // (undocumented) + use(handler: Handler): void; +} + +// @public (undocumented) +export const httpRouterServiceRef: ServiceRef; + +// @public (undocumented) +export interface Logger { + // (undocumented) + child(fields: { [name: string]: string }): Logger; + // (undocumented) + info(message: string): void; +} + +// @public (undocumented) +export const loggerServiceRef: ServiceRef; + +// @public (undocumented) +export function loggerToWinstonLogger( + logger: Logger, + opts?: TransportStreamOptions, +): Logger_2; + +// @public (undocumented) +export const permissionsServiceRef: ServiceRef< + PermissionAuthorizer | PermissionEvaluator +>; + +// @public (undocumented) +export const schedulerServiceRef: ServiceRef; + +// @public (undocumented) +export type ServiceFactory< + TApi, + TImpl extends TApi, + TDeps extends { + [name in string]: unknown; + }, +> = { + service: ServiceRef; + deps: TypesToServiceRef; + factory(deps: DepsToDepFactories): Promise>; +}; + +// @public +export type ServiceRef = { + id: string; + T: T; + toString(): string; + $$ref: 'service'; +}; + +// @public (undocumented) +export const tokenManagerServiceRef: ServiceRef; + +// @public (undocumented) +export type TypesToServiceRef = { + [key in keyof T]: ServiceRef; +}; + +// @public (undocumented) +export const urlReaderServiceRef: ServiceRef; +``` diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json new file mode 100644 index 0000000000..f9109ae680 --- /dev/null +++ b/packages/backend-plugin-api/package.json @@ -0,0 +1,53 @@ +{ + "name": "@backstage/backend-plugin-api", + "description": "Core API used by Backstage backend plugins", + "version": "0.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "private": false, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts", + "alphaTypes": "dist/index.alpha.d.ts" + }, + "backstage": { + "role": "node-library" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/backend-plugin-api" + }, + "keywords": [ + "backstage" + ], + "license": "Apache-2.0", + "scripts": { + "build": "backstage-cli package build --experimental-type-build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" + }, + "dependencies": { + "@backstage/config": "^1.0.1", + "@backstage/backend-common": "^0.14.1-next.2", + "@backstage/plugin-permission-common": "^0.6.3-next.0", + "@backstage/backend-tasks": "^0.3.3-next.2", + "@types/express": "^4.17.6", + "express": "^4.17.1", + "winston": "^3.2.1", + "winston-transport": "^4.5.0" + }, + "devDependencies": { + "@backstage/cli": "^0.18.0-next.2" + }, + "files": [ + "dist", + "alpha" + ] +} diff --git a/packages/backend-plugin-api/src/index.ts b/packages/backend-plugin-api/src/index.ts new file mode 100644 index 0000000000..452fa046f9 --- /dev/null +++ b/packages/backend-plugin-api/src/index.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/** + * Core API used by Backstage backend plugins. + * + * @packageDocumentation + */ + +export * from './services'; +export * from './wiring'; diff --git a/packages/backend-plugin-api/src/services/definitions/cacheServiceRef.ts b/packages/backend-plugin-api/src/services/definitions/cacheServiceRef.ts new file mode 100644 index 0000000000..0b572f1712 --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/cacheServiceRef.ts @@ -0,0 +1,25 @@ +/* + * 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 { createServiceRef } from '../system/types'; +import { PluginCacheManager } from '@backstage/backend-common'; + +/** + * @public + */ +export const cacheServiceRef = createServiceRef({ + id: 'core.cache', +}); diff --git a/packages/backend-plugin-api/src/services/definitions/configServiceRef.ts b/packages/backend-plugin-api/src/services/definitions/configServiceRef.ts new file mode 100644 index 0000000000..ba5dcacdc8 --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/configServiceRef.ts @@ -0,0 +1,25 @@ +/* + * 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 { createServiceRef } from '../system/types'; + +/** + * @public + */ +export const configServiceRef = createServiceRef({ + id: 'core.config', +}); diff --git a/packages/backend-plugin-api/src/services/definitions/databaseServiceRef.ts b/packages/backend-plugin-api/src/services/definitions/databaseServiceRef.ts new file mode 100644 index 0000000000..b41159dc9e --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/databaseServiceRef.ts @@ -0,0 +1,25 @@ +/* + * 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 { PluginDatabaseManager } from '@backstage/backend-common'; +import { createServiceRef } from '../system/types'; + +/** + * @public + */ +export const databaseServiceRef = createServiceRef({ + id: 'core.database', +}); diff --git a/packages/backend-plugin-api/src/services/definitions/discoveryServiceRef.ts b/packages/backend-plugin-api/src/services/definitions/discoveryServiceRef.ts new file mode 100644 index 0000000000..675ac206a2 --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/discoveryServiceRef.ts @@ -0,0 +1,25 @@ +/* + * 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 { createServiceRef } from '../system/types'; +import { PluginEndpointDiscovery } from '@backstage/backend-common'; + +/** + * @public + */ +export const discoveryServiceRef = createServiceRef({ + id: 'core.discovery', +}); diff --git a/packages/backend-plugin-api/src/services/definitions/httpRouterServiceRef.ts b/packages/backend-plugin-api/src/services/definitions/httpRouterServiceRef.ts new file mode 100644 index 0000000000..fbc5a27e87 --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/httpRouterServiceRef.ts @@ -0,0 +1,32 @@ +/* + * 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 { createServiceRef } from '../system/types'; +import { Handler } from 'express'; + +/** + * @public + */ +export interface HttpRouterService { + use(handler: Handler): void; +} + +/** + * @public + */ +export const httpRouterServiceRef = createServiceRef({ + id: 'core.httpRouter', +}); diff --git a/packages/backend-plugin-api/src/services/definitions/index.ts b/packages/backend-plugin-api/src/services/definitions/index.ts new file mode 100644 index 0000000000..797cfb5a76 --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/index.ts @@ -0,0 +1,28 @@ +/* + * 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 { configServiceRef } from './configServiceRef'; +export { httpRouterServiceRef } from './httpRouterServiceRef'; +export type { HttpRouterService } from './httpRouterServiceRef'; +export { loggerServiceRef } from './loggerServiceRef'; +export type { Logger } from './loggerServiceRef'; +export { urlReaderServiceRef } from './urlReaderServiceRef'; +export { cacheServiceRef } from './cacheServiceRef'; +export { databaseServiceRef } from './databaseServiceRef'; +export { discoveryServiceRef } from './discoveryServiceRef'; +export { tokenManagerServiceRef } from './tokenManagerServiceRef'; +export { permissionsServiceRef } from './permissionsServiceRef'; +export { schedulerServiceRef } from './schedulerServiceRef'; diff --git a/packages/backend-plugin-api/src/services/definitions/loggerServiceRef.ts b/packages/backend-plugin-api/src/services/definitions/loggerServiceRef.ts new file mode 100644 index 0000000000..e99bc4e7d4 --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/loggerServiceRef.ts @@ -0,0 +1,32 @@ +/* + * 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 { createServiceRef } from '../system/types'; + +/** + * @public + */ +export interface Logger { + info(message: string): void; + child(fields: { [name: string]: string }): Logger; +} + +/** + * @public + */ +export const loggerServiceRef = createServiceRef({ + id: 'core.logger', +}); diff --git a/packages/backend-plugin-api/src/services/definitions/permissionsServiceRef.ts b/packages/backend-plugin-api/src/services/definitions/permissionsServiceRef.ts new file mode 100644 index 0000000000..b13ca9e240 --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/permissionsServiceRef.ts @@ -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 { createServiceRef } from '../system/types'; +import { + PermissionAuthorizer, + PermissionEvaluator, +} from '@backstage/plugin-permission-common'; + +/** + * @public + */ +export const permissionsServiceRef = createServiceRef< + PermissionEvaluator | PermissionAuthorizer +>({ + id: 'core.permissions', +}); diff --git a/packages/backend-plugin-api/src/services/definitions/schedulerServiceRef.ts b/packages/backend-plugin-api/src/services/definitions/schedulerServiceRef.ts new file mode 100644 index 0000000000..ca442dd34d --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/schedulerServiceRef.ts @@ -0,0 +1,25 @@ +/* + * 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 { createServiceRef } from '../system/types'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; + +/** + * @public + */ +export const schedulerServiceRef = createServiceRef({ + id: 'core.scheduler', +}); diff --git a/packages/backend-plugin-api/src/services/definitions/tokenManagerServiceRef.ts b/packages/backend-plugin-api/src/services/definitions/tokenManagerServiceRef.ts new file mode 100644 index 0000000000..6995033e81 --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/tokenManagerServiceRef.ts @@ -0,0 +1,25 @@ +/* + * 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 { createServiceRef } from '../system/types'; +import { TokenManager } from '@backstage/backend-common'; + +/** + * @public + */ +export const tokenManagerServiceRef = createServiceRef({ + id: 'core.tokenManager', +}); diff --git a/packages/backend-plugin-api/src/services/definitions/urlReaderServiceRef.ts b/packages/backend-plugin-api/src/services/definitions/urlReaderServiceRef.ts new file mode 100644 index 0000000000..ebb32f1aed --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/urlReaderServiceRef.ts @@ -0,0 +1,25 @@ +/* + * 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 { createServiceRef } from '../system/types'; +import { UrlReader } from '@backstage/backend-common'; + +/** + * @public + */ +export const urlReaderServiceRef = createServiceRef({ + id: 'core.urlReader', +}); diff --git a/packages/backend-plugin-api/src/services/helpers/index.ts b/packages/backend-plugin-api/src/services/helpers/index.ts new file mode 100644 index 0000000000..24336c1af2 --- /dev/null +++ b/packages/backend-plugin-api/src/services/helpers/index.ts @@ -0,0 +1,17 @@ +/* + * 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 { loggerToWinstonLogger } from './loggerToWinstonLogger'; diff --git a/packages/backend-plugin-api/src/services/helpers/loggerToWinstonLogger.ts b/packages/backend-plugin-api/src/services/helpers/loggerToWinstonLogger.ts new file mode 100644 index 0000000000..b9ccd70d3f --- /dev/null +++ b/packages/backend-plugin-api/src/services/helpers/loggerToWinstonLogger.ts @@ -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 { Logger as BackstageLogger } from '../definitions'; +import { Logger as WinstonLogger, createLogger } from 'winston'; +import Transport, { TransportStreamOptions } from 'winston-transport'; + +class BackstageLoggerTransport extends Transport { + constructor( + private readonly backstageLogger: BackstageLogger, + opts?: TransportStreamOptions, + ) { + super(opts); + } + + log(info: { message: string }, callback: VoidFunction) { + // TODO: add support for levels and fields + this.backstageLogger.info(info.message); + callback(); + } +} + +/** @public */ +export function loggerToWinstonLogger( + logger: BackstageLogger, + opts?: TransportStreamOptions, +): WinstonLogger { + return createLogger({ + transports: [new BackstageLoggerTransport(logger, opts)], + }); +} diff --git a/packages/backend-plugin-api/src/services/index.ts b/packages/backend-plugin-api/src/services/index.ts new file mode 100644 index 0000000000..96bd3d320a --- /dev/null +++ b/packages/backend-plugin-api/src/services/index.ts @@ -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 * from './definitions'; +export * from './helpers'; +export * from './system'; diff --git a/packages/backend-plugin-api/src/services/system/index.ts b/packages/backend-plugin-api/src/services/system/index.ts new file mode 100644 index 0000000000..eead2297a5 --- /dev/null +++ b/packages/backend-plugin-api/src/services/system/index.ts @@ -0,0 +1,25 @@ +/* + * 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 type { + ServiceRef, + TypesToServiceRef, + DepsToDepFactories, + FactoryFunc, + ServiceFactory, + AnyServiceFactory, +} from './types'; +export { createServiceRef, createServiceFactory } from './types'; diff --git a/packages/backend-plugin-api/src/services/system/types.ts b/packages/backend-plugin-api/src/services/system/types.ts new file mode 100644 index 0000000000..d578434170 --- /dev/null +++ b/packages/backend-plugin-api/src/services/system/types.ts @@ -0,0 +1,90 @@ +/* + * 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. + */ + +/** + * TODO + * + * @public + */ +export type ServiceRef = { + id: string; + + /** + * Utility for getting the type of the service, using `typeof serviceRef.T`. + * Attempting to actually read this value will result in an exception. + */ + T: T; + + toString(): string; + + $$ref: 'service'; +}; + +/** @public */ +export type TypesToServiceRef = { [key in keyof T]: ServiceRef }; + +/** @public */ +export type DepsToDepFactories = { + [key in keyof T]: (pluginId: string) => Promise; +}; + +/** @public */ +export type FactoryFunc = (pluginId: string) => Promise; + +/** @public */ +export type ServiceFactory< + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, +> = { + service: ServiceRef; + deps: TypesToServiceRef; + factory(deps: DepsToDepFactories): Promise>; +}; + +/** @public */ +export type AnyServiceFactory = ServiceFactory< + unknown, + unknown, + { [key in string]: unknown } +>; + +/** + * @public + */ +export function createServiceRef(options: { id: string }): ServiceRef { + return { + id: options.id, + get T(): T { + throw new Error(`tried to read ServiceRef.T of ${this}`); + }, + toString() { + return `serviceRef{${options.id}}`; + }, + $$ref: 'service', // TODO: declare + }; +} + +/** + * @public + */ +export function createServiceFactory< + Api, + Impl extends Api, + Deps extends { [name in string]: unknown }, +>(factory: ServiceFactory): ServiceFactory { + return factory; +} diff --git a/packages/backend-plugin-api/src/wiring/index.ts b/packages/backend-plugin-api/src/wiring/index.ts new file mode 100644 index 0000000000..9340dc8fa9 --- /dev/null +++ b/packages/backend-plugin-api/src/wiring/index.ts @@ -0,0 +1,18 @@ +/* + * 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 type { BackendRegistrable } from './types'; +export * from './types'; diff --git a/packages/backend-plugin-api/src/wiring/types.ts b/packages/backend-plugin-api/src/wiring/types.ts new file mode 100644 index 0000000000..2b0bef16f7 --- /dev/null +++ b/packages/backend-plugin-api/src/wiring/types.ts @@ -0,0 +1,113 @@ +/* + * 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 { ServiceRef } from '../services/system/types'; + +/** + * TODO + * + * @public + */ +export type ExtensionPoint = { + id: string; + + /** + * Utility for getting the type of the extension point, using `typeof extensionPoint.T`. + * Attempting to actually read this value will result in an exception. + */ + T: T; + + toString(): string; + + $$ref: 'extension-point'; +}; + +/** @public */ +export function createExtensionPoint(options: { + id: string; +}): ExtensionPoint { + return { + id: options.id, + get T(): T { + throw new Error(`tried to read ExtensionPoint.T of ${this}`); + }, + toString() { + return `extensionPoint{${options.id}}`; + }, + $$ref: 'extension-point', // TODO: declare + }; +} + +/** @public */ +export interface BackendInitRegistry { + registerExtensionPoint( + ref: ServiceRef, + impl: TExtensionPoint, + ): void; + registerInit(options: { + deps: { [name in keyof Deps]: ServiceRef }; + init: (deps: Deps) => Promise; + }): void; +} + +/** @public */ +export interface BackendRegistrable { + id: string; + register(reg: BackendInitRegistry): void; +} + +/** @public */ +export interface BackendPluginConfig { + id: string; + register(reg: BackendInitRegistry, options: TOptions): void; +} + +// TODO: Make option optional in the returned factory if they are indeed optional +/** @public */ +export function createBackendPlugin( + config: BackendPluginConfig, +): (option: TOptions) => BackendRegistrable { + return options => ({ + id: config.id, + register(register) { + return config.register(register, options); + }, + }); +} + +/** @public */ +export interface BackendModuleConfig { + pluginId: string; + moduleId: string; + register( + reg: Omit, + options: TOptions, + ): void; +} + +// TODO: Make option optional in the returned factory if they are indeed optional +/** @public */ +export function createBackendModule( + config: BackendModuleConfig, +): (option: TOptions) => BackendRegistrable { + return options => ({ + id: `${config.pluginId}.${config.moduleId}`, + register(register) { + // TODO: Hide registerExtensionPoint + return config.register(register, options); + }, + }); +} diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index 3c78fb6a56..22a7f8192c 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -5,6 +5,7 @@ ```ts /// +import { BackendRegistrable } from '@backstage/backend-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; import { CatalogEntityDocument } from '@backstage/plugin-catalog-common'; import { CatalogProcessor } from '@backstage/plugin-catalog-node'; @@ -222,6 +223,9 @@ export type CatalogEnvironment = { export type CatalogPermissionRule = PermissionRule; +// @alpha +export const catalogPlugin: (option: unknown) => BackendRegistrable; + // @public (undocumented) export interface CatalogProcessingEngine { // (undocumented) diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 8c9c35a9d5..64ad601b78 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -34,6 +34,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { + "@backstage/backend-plugin-api": "^0.0.0", "@backstage/plugin-catalog-node": "^0.0.0", "@backstage/backend-common": "^0.14.1-next.2", "@backstage/catalog-client": "^1.0.4-next.1", diff --git a/plugins/catalog-backend/src/service/CatalogPlugin.ts b/plugins/catalog-backend/src/service/CatalogPlugin.ts new file mode 100644 index 0000000000..b82797ec8b --- /dev/null +++ b/plugins/catalog-backend/src/service/CatalogPlugin.ts @@ -0,0 +1,104 @@ +/* + * 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, + createBackendPlugin, + databaseServiceRef, + loggerServiceRef, + loggerToWinstonLogger, + permissionsServiceRef, + urlReaderServiceRef, + httpRouterServiceRef, +} from '@backstage/backend-plugin-api'; +import { CatalogBuilder } from './CatalogBuilder'; +import { + CatalogProcessor, + CatalogProcessingExtensionPoint, + catalogProcessingExtentionPoint, + EntityProvider, +} from '@backstage/plugin-catalog-node'; + +class CatalogExtensionPointImpl implements CatalogProcessingExtensionPoint { + #processors = new Array(); + #entityProviders = new Array(); + + addProcessor(processor: CatalogProcessor): void { + this.#processors.push(processor); + } + + addEntityProvider(provider: EntityProvider): void { + this.#entityProviders.push(provider); + } + + get processors() { + return this.#processors; + } + + get entityProviders() { + return this.#entityProviders; + } +} + +/** + * Catalog plugin + * @alpha + */ +export const catalogPlugin = createBackendPlugin({ + id: 'catalog', + register(env) { + const processingExtensions = new CatalogExtensionPointImpl(); + // plugins depending on this API will be initialized before this plugins init method is executed. + env.registerExtensionPoint( + catalogProcessingExtentionPoint, + processingExtensions, + ); + + env.registerInit({ + deps: { + logger: loggerServiceRef, + config: configServiceRef, + reader: urlReaderServiceRef, + permissions: permissionsServiceRef, + database: databaseServiceRef, + httpRouter: httpRouterServiceRef, + }, + async init({ + logger, + config, + reader, + database, + permissions, + httpRouter, + }) { + const winstonLogger = loggerToWinstonLogger(logger); + const builder = await CatalogBuilder.create({ + config, + reader, + permissions, + database, + logger: winstonLogger, + }); + builder.addProcessor(...processingExtensions.processors); + builder.addEntityProvider(...processingExtensions.entityProviders); + const { processingEngine, router } = await builder.build(); + + await processingEngine.start(); + + httpRouter.use(router); + }, + }); + }, +}); diff --git a/plugins/catalog-backend/src/service/index.ts b/plugins/catalog-backend/src/service/index.ts index b1e91e4382..7e0c6025c3 100644 --- a/plugins/catalog-backend/src/service/index.ts +++ b/plugins/catalog-backend/src/service/index.ts @@ -16,3 +16,4 @@ export type { CatalogEnvironment } from './CatalogBuilder'; export { CatalogBuilder } from './CatalogBuilder'; +export { catalogPlugin } from './CatalogPlugin'; diff --git a/plugins/catalog-node/api-report.md b/plugins/catalog-node/api-report.md index d3460e4e27..0449708bc3 100644 --- a/plugins/catalog-node/api-report.md +++ b/plugins/catalog-node/api-report.md @@ -8,6 +8,18 @@ import { CompoundEntityRef } from '@backstage/catalog-model'; import { Entity } from '@backstage/catalog-model'; import { JsonValue } from '@backstage/types'; +import { ServiceRef } from '@backstage/backend-plugin-api'; + +// @alpha (undocumented) +export interface CatalogProcessingExtensionPoint { + // (undocumented) + addEntityProvider(provider: EntityProvider): void; + // (undocumented) + addProcessor(processor: CatalogProcessor): void; +} + +// @alpha (undocumented) +export const catalogProcessingExtentionPoint: ServiceRef; // @public (undocumented) export type CatalogProcessor = { diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index a1c96990a0..d6228c717f 100644 --- a/plugins/catalog-node/package.json +++ b/plugins/catalog-node/package.json @@ -25,6 +25,7 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { + "@backstage/backend-plugin-api": "^0.0.0", "@backstage/catalog-model": "^1.1.0-next.2", "@backstage/errors": "1.1.0-next.0", "@backstage/types": "^1.0.0" diff --git a/plugins/catalog-node/src/extensions.ts b/plugins/catalog-node/src/extensions.ts new file mode 100644 index 0000000000..560d9d4db1 --- /dev/null +++ b/plugins/catalog-node/src/extensions.ts @@ -0,0 +1,34 @@ +/* + * 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 { createServiceRef } from '@backstage/backend-plugin-api'; +import { EntityProvider } from './api'; +import { CatalogProcessor } from './api/processor'; + +/** + * @alpha + */ +export interface CatalogProcessingExtensionPoint { + addProcessor(processor: CatalogProcessor): void; + addEntityProvider(provider: EntityProvider): void; +} + +/** + * @alpha + */ +export const catalogProcessingExtentionPoint = + createServiceRef({ + id: 'catalog.processing', + }); diff --git a/plugins/catalog-node/src/index.ts b/plugins/catalog-node/src/index.ts index 6a1accfcbd..fc7a1b1b67 100644 --- a/plugins/catalog-node/src/index.ts +++ b/plugins/catalog-node/src/index.ts @@ -20,5 +20,7 @@ * @packageDocumentation */ +export type { CatalogProcessingExtensionPoint } from './extensions'; +export { catalogProcessingExtentionPoint } from './extensions'; export * from './api'; export * from './processing'; diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 2053f01e39..dfca5994fd 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -5,6 +5,7 @@ ```ts /// +import { BackendRegistrable } from '@backstage/backend-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; import { CatalogProcessor } from '@backstage/plugin-catalog-backend'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-backend'; @@ -543,6 +544,9 @@ export type RunCommandOptions = { logStream?: Writable; }; +// @alpha +export const scaffolderCatalogModule: (option: unknown) => BackendRegistrable; + // @public (undocumented) export class ScaffolderEntitiesProcessor implements CatalogProcessor { // (undocumented) diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 36b7f9597f..29adfd0fa9 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -9,7 +9,8 @@ "publishConfig": { "access": "public", "main": "dist/index.cjs.js", - "types": "dist/index.d.ts" + "types": "dist/index.d.ts", + "alphaTypes": "dist/index.alpha.d.ts" }, "backstage": { "role": "backend-plugin" @@ -25,7 +26,7 @@ ], "scripts": { "start": "backstage-cli package start", - "build": "backstage-cli package build", + "build": "backstage-cli package build --experimental-type-build", "lint": "backstage-cli package lint", "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", @@ -42,6 +43,8 @@ "@backstage/integration": "^1.2.2-next.2", "@backstage/plugin-catalog-backend": "^1.2.1-next.2", "@backstage/plugin-scaffolder-common": "^1.1.2-next.0", + "@backstage/backend-plugin-api": "^0.0.0", + "@backstage/plugin-catalog-node": "^0.0.0", "@backstage/types": "^1.0.0", "@gitbeaker/core": "^35.6.0", "@gitbeaker/node": "^35.1.0", @@ -93,6 +96,7 @@ "yaml": "^2.0.0" }, "files": [ + "alpha", "dist", "migrations", "config.d.ts", diff --git a/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.test.ts b/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.test.ts new file mode 100644 index 0000000000..f07eb0f7ae --- /dev/null +++ b/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.test.ts @@ -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 { BackendInitRegistry } from '@backstage/backend-plugin-api'; +import { ScaffolderEntitiesProcessor } from '../processor'; +import { scaffolderCatalogModule } from './ScaffolderCatalogModule'; + +describe('ScaffolderCatalogModule', () => { + it('should register the extension point', () => { + // TODO(jhaals): clean this up and add test helpers for backend system. + const ext = scaffolderCatalogModule({}); + expect(ext.id).toBe('catalog.scaffolder.module'); + const registry: jest.Mocked = { + registerInit: jest.fn(), + } as any; + ext.register(registry); + + const extensionPoint = { + addProcessor: jest.fn(), + }; + + registry.registerInit.mock.calls[0][0].init({ + catalogProcessingExtensionPoint: extensionPoint, + }); + expect(extensionPoint.addProcessor).toHaveBeenCalledWith( + new ScaffolderEntitiesProcessor(), + ); + }); +}); diff --git a/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.ts b/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.ts new file mode 100644 index 0000000000..bdce46a31a --- /dev/null +++ b/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.ts @@ -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 { createBackendModule } from '@backstage/backend-plugin-api'; +import { catalogProcessingExtentionPoint } from '@backstage/plugin-catalog-node'; +import { ScaffolderEntitiesProcessor } from '../processor'; + +/** + * @alpha + * Registers the ScaffolderEntitiesProcessor with the catalog processing extension point. + */ +export const scaffolderCatalogModule = createBackendModule({ + moduleId: 'scaffolder.module', + pluginId: 'catalog', + register(env) { + env.registerInit({ + deps: { + catalogProcessingExtensionPoint: catalogProcessingExtentionPoint, + }, + async init({ catalogProcessingExtensionPoint }) { + catalogProcessingExtensionPoint.addProcessor( + new ScaffolderEntitiesProcessor(), + ); + }, + }); + }, +}); diff --git a/plugins/scaffolder-backend/src/extension/index.ts b/plugins/scaffolder-backend/src/extension/index.ts new file mode 100644 index 0000000000..c827d9cafd --- /dev/null +++ b/plugins/scaffolder-backend/src/extension/index.ts @@ -0,0 +1,16 @@ +/* + * 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 { scaffolderCatalogModule } from './ScaffolderCatalogModule'; diff --git a/plugins/scaffolder-backend/src/index.ts b/plugins/scaffolder-backend/src/index.ts index 5978b6389b..a1032449d5 100644 --- a/plugins/scaffolder-backend/src/index.ts +++ b/plugins/scaffolder-backend/src/index.ts @@ -24,3 +24,4 @@ export * from './scaffolder'; export * from './service/router'; export * from './lib'; export * from './processor'; +export * from './extension';