diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 915049366c..a97f8b31d8 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -9,6 +9,7 @@ import aws from 'aws-sdk'; import { AwsS3Integration } from '@backstage/integration'; import { AzureIntegration } from '@backstage/integration'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { BitbucketCloudIntegration } from '@backstage/integration'; import { BitbucketIntegration } from '@backstage/integration'; import { BitbucketServerIntegration } from '@backstage/integration'; @@ -16,6 +17,7 @@ import { CacheClient } from '@backstage/backend-plugin-api'; import { CacheClientOptions } from '@backstage/backend-plugin-api'; import { CacheClientSetOptions } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; +import { ConfigService } from '@backstage/backend-plugin-api'; import cors from 'cors'; import Docker from 'dockerode'; import { Duration } from 'luxon'; @@ -26,6 +28,7 @@ import { GiteaIntegration } from '@backstage/integration'; import { GithubCredentialsProvider } from '@backstage/integration'; import { GithubIntegration } from '@backstage/integration'; import { GitLabIntegration } from '@backstage/integration'; +import { IdentityService } from '@backstage/backend-plugin-api'; import { isChildPath } from '@backstage/cli-common'; import { Knex } from 'knex'; import { KubeConfig } from '@kubernetes/client-node'; @@ -33,6 +36,7 @@ import { LoadConfigOptionsRemote } from '@backstage/config-loader'; import { Logger } from 'winston'; import { LoggerService } from '@backstage/backend-plugin-api'; import { MergeResult } from 'isomorphic-git'; +import { PermissionsService } from '@backstage/backend-plugin-api'; import { CacheService as PluginCacheManager } from '@backstage/backend-plugin-api'; import { DatabaseService as PluginDatabaseManager } from '@backstage/backend-plugin-api'; import { DiscoveryService as PluginEndpointDiscovery } from '@backstage/backend-plugin-api'; @@ -47,10 +51,12 @@ import { ReadUrlOptions } from '@backstage/backend-plugin-api'; import { ReadUrlResponse } from '@backstage/backend-plugin-api'; import { RequestHandler } from 'express'; import { Router } from 'express'; +import { SchedulerService } from '@backstage/backend-plugin-api'; import { SearchOptions } from '@backstage/backend-plugin-api'; import { SearchResponse } from '@backstage/backend-plugin-api'; import { SearchResponseFile } from '@backstage/backend-plugin-api'; import { Server } from 'http'; +import { ServiceRef } from '@backstage/backend-plugin-api'; import { TokenManagerService as TokenManager } from '@backstage/backend-plugin-api'; import { TransportStreamOptions } from 'winston-transport'; import { UrlReaderService as UrlReader } from '@backstage/backend-plugin-api'; @@ -228,6 +234,32 @@ export function createDatabaseClient( overrides?: Partial, ): Knex; +// @public +export const createPluginCompat: ( + name: string, + createRouterImport: Promise<{ + default: LegacyCreateRouter< + TransformedEnv< + { + cache: PluginCacheManager; + config: ConfigService; + database: PluginDatabaseManager; + discovery: PluginEndpointDiscovery; + logger: LoggerService; + permissions: PermissionsService; + scheduler: SchedulerService; + tokenManager: TokenManager; + reader: UrlReader; + identity: IdentityService; + }, + { + logger: (log: LoggerService) => Logger; + } + > + >; + }>, +) => BackendFeature; + // @public export function createRootLogger( options?: winston.LoggerOptions, @@ -500,6 +532,9 @@ export type KubernetesContainerRunnerOptions = { timeoutMs?: number; }; +// @public (undocumented) +export type LegacyCreateRouter = (deps: TEnv) => Promise; + // @public export function loadBackendConfig(options: { logger: LoggerService; @@ -513,6 +548,24 @@ export function loggerToWinstonLogger( opts?: TransportStreamOptions, ): Logger; +// @public +export function makePluginCompat< + TEnv extends Record, + TEnvTransforms extends { + [key in keyof TEnv]?: (dep: TEnv[key]) => unknown; + }, +>( + envMapping: { + [key in keyof TEnv]: ServiceRef; + }, + envTransforms: TEnvTransforms, +): ( + name: string, + createRouterImport: Promise<{ + default: LegacyCreateRouter>; + }>, +) => BackendFeature; + // @public export function notFoundHandler(): RequestHandler; diff --git a/packages/backend-common/src/compat.ts b/packages/backend-common/src/compat.ts new file mode 100644 index 0000000000..16fe48dcb7 --- /dev/null +++ b/packages/backend-common/src/compat.ts @@ -0,0 +1,123 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + coreServices, + createBackendPlugin, + ServiceRef, +} from '@backstage/backend-plugin-api'; +import { RequestHandler } from 'express'; +import { loggerToWinstonLogger } from './logging'; + +/** + * @public + */ +export type LegacyCreateRouter = (deps: TEnv) => Promise; + +/** @ignore */ +type TransformedEnv< + TEnv extends Record, + TEnvTransforms extends { [key in keyof TEnv]?: (dep: TEnv[key]) => unknown }, +> = { + [key in keyof TEnv]: TEnvTransforms[key] extends (dep: TEnv[key]) => infer R + ? R + : TEnv[key]; +}; + +/** + * Creates a new custom plugin compatibility wrapper. + * + * @public + * @remarks + * + * Usually you can use {@link createPluginCompat} directly instead, but you might + * need to use this if you have customized the plugin environment in your backend. + */ +export function makePluginCompat< + TEnv extends Record, + TEnvTransforms extends { [key in keyof TEnv]?: (dep: TEnv[key]) => unknown }, +>( + envMapping: { [key in keyof TEnv]: ServiceRef }, + envTransforms: TEnvTransforms, +) { + return ( + name: string, + createRouterImport: Promise<{ + default: LegacyCreateRouter>; + }>, + ) => { + const compatPlugin = createBackendPlugin({ + id: name, + register(env) { + env.registerInit({ + deps: { ...envMapping, _router: coreServices.httpRouter }, + async init({ _router, ...envDeps }) { + const { default: createRouter } = await createRouterImport; + const pluginEnv = Object.fromEntries( + Object.entries(envDeps).map(([key, dep]) => { + const transform = envTransforms[key]; + if (transform) { + return [key, transform(dep)]; + } + return [key, dep]; + }), + ); + const router = await createRouter( + pluginEnv as TransformedEnv, + ); + _router.use(router); + }, + }); + }, + }); + + return compatPlugin(); + }; +} + +/** + * Helper function to create a plugin from a legacy createRouter function and + * register it with the http router based on the plugin id. + * + * @public + * @remarks + * + * This is intended to be used by plugin authors to ease the transition to the + * new backend system. + * + * @example + * + *```ts + *backend.add(createPluginCompat('kafka', import('./plugins/kafka'))); + *``` + */ +export const createPluginCompat = makePluginCompat( + { + cache: coreServices.cache, + config: coreServices.config, + database: coreServices.database, + discovery: coreServices.discovery, + logger: coreServices.logger, + permissions: coreServices.permissions, + scheduler: coreServices.scheduler, + tokenManager: coreServices.tokenManager, + reader: coreServices.urlReader, + identity: coreServices.identity, + }, + { + logger: log => loggerToWinstonLogger(log), + }, +); diff --git a/packages/backend-common/src/index.ts b/packages/backend-common/src/index.ts index 238e58b2a8..178a064cb9 100644 --- a/packages/backend-common/src/index.ts +++ b/packages/backend-common/src/index.ts @@ -20,6 +20,8 @@ * @packageDocumentation */ +export { makePluginCompat, createPluginCompat } from './compat'; +export type { LegacyCreateRouter } from './compat'; export * from './cache'; export { loadBackendConfig } from './config'; export * from './context';