From 6e2248abbe8e3080ad694e92b760320f75269b8f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 11 Jan 2023 12:05:20 +0100 Subject: [PATCH 1/3] backend-common: added compatibility wrapper for legacy plugins Co-authored-by: Johan Haals Signed-off-by: Patrik Oldsberg --- packages/backend-common/api-report.md | 53 +++++++++++ packages/backend-common/src/compat.ts | 123 ++++++++++++++++++++++++++ packages/backend-common/src/index.ts | 2 + 3 files changed, 178 insertions(+) create mode 100644 packages/backend-common/src/compat.ts 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'; From cdabd4eefb35c303da00232dd120df0c813965aa Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 11 Jan 2023 13:09:56 +0100 Subject: [PATCH 2/3] backend-common: rename createPluginCompat -> legacyPlugin Signed-off-by: Patrik Oldsberg --- packages/backend-common/api-report.md | 54 +++++++++---------- packages/backend-common/src/index.ts | 4 +- .../src/{compat.ts => legacy.ts} | 8 +-- 3 files changed, 33 insertions(+), 33 deletions(-) rename packages/backend-common/src/{compat.ts => legacy.ts} (93%) diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index a97f8b31d8..b03d6ec866 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -234,32 +234,6 @@ 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, @@ -535,6 +509,32 @@ export type KubernetesContainerRunnerOptions = { // @public (undocumented) export type LegacyCreateRouter = (deps: TEnv) => Promise; +// @public +export const legacyPlugin: ( + 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 loadBackendConfig(options: { logger: LoggerService; @@ -549,7 +549,7 @@ export function loggerToWinstonLogger( ): Logger; // @public -export function makePluginCompat< +export function makeLegacyPlugin< TEnv extends Record, TEnvTransforms extends { [key in keyof TEnv]?: (dep: TEnv[key]) => unknown; diff --git a/packages/backend-common/src/index.ts b/packages/backend-common/src/index.ts index 178a064cb9..46639a1923 100644 --- a/packages/backend-common/src/index.ts +++ b/packages/backend-common/src/index.ts @@ -20,8 +20,8 @@ * @packageDocumentation */ -export { makePluginCompat, createPluginCompat } from './compat'; -export type { LegacyCreateRouter } from './compat'; +export { legacyPlugin, makeLegacyPlugin } from './legacy'; +export type { LegacyCreateRouter } from './legacy'; export * from './cache'; export { loadBackendConfig } from './config'; export * from './context'; diff --git a/packages/backend-common/src/compat.ts b/packages/backend-common/src/legacy.ts similarity index 93% rename from packages/backend-common/src/compat.ts rename to packages/backend-common/src/legacy.ts index 16fe48dcb7..30e7dd9dc5 100644 --- a/packages/backend-common/src/compat.ts +++ b/packages/backend-common/src/legacy.ts @@ -43,10 +43,10 @@ type TransformedEnv< * @public * @remarks * - * Usually you can use {@link createPluginCompat} directly instead, but you might + * Usually you can use {@link legacyPlugin} directly instead, but you might * need to use this if you have customized the plugin environment in your backend. */ -export function makePluginCompat< +export function makeLegacyPlugin< TEnv extends Record, TEnvTransforms extends { [key in keyof TEnv]?: (dep: TEnv[key]) => unknown }, >( @@ -101,10 +101,10 @@ export function makePluginCompat< * @example * *```ts - *backend.add(createPluginCompat('kafka', import('./plugins/kafka'))); + *backend.add(legacyPlugin('kafka', import('./plugins/kafka'))); *``` */ -export const createPluginCompat = makePluginCompat( +export const legacyPlugin = makeLegacyPlugin( { cache: coreServices.cache, config: coreServices.config, From 31e2309c8cfe15d2c79c8800242fac901a17ebda Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 11 Jan 2023 13:24:39 +0100 Subject: [PATCH 3/3] changesets: added changeset for legacyPlugin wrapper Signed-off-by: Patrik Oldsberg --- .changeset/famous-books-matter.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/famous-books-matter.md diff --git a/.changeset/famous-books-matter.md b/.changeset/famous-books-matter.md new file mode 100644 index 0000000000..550bfecf0b --- /dev/null +++ b/.changeset/famous-books-matter.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Added `legacyPlugin` and the lower level `makeLegacyPlugin` wrappers that convert legacy plugins to the new backend system. This will be used to ease the future migration to the new backend system, but we discourage use of it for now.