diff --git a/CHANGELOG.md b/CHANGELOG.md index 1860a4de6f..c5229a6794 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ If you encounter issues while upgrading to a newer version, don't hesitate to re - The default mount point for backend plugins have been changed to `/api`. These changes are done in the backend package itself, so it is recommended that you sync up existing backend packages with this new pattern. [#2562](https://github.com/spotify/backstage/pull/2562) - A service discovery mechanism for backend plugins has been added, and is now a requirement for several backend plugins. See [packages/backend/src/index.ts](./packages/backend/src/index.ts) for how to set it up using `SingleHostDiscovery` from `@backstage/backend-common`. Note that the default base path for plugins is set to `/api` to that change, but it can be set to use the old behavior via the `basePath` option. [#2600](https://github.com/spotify/backstage/pull/2600) +- A plugin database manager has been created, and plugins can now accept that interface as an argument during initialisation. Notably, the `auth` plugin has a [`createRouter` signature change](./plugins/auth-backend/src/service/router.ts). See [packages/backend/src/index.ts](./packages/backend/src/index.ts) on how to set it up. [#2697](https://github.com/spotify/backstage/pull/2697) ### @backstage/auth-backend diff --git a/packages/backend-common/src/database/SingleConnection.ts b/packages/backend-common/src/database/SingleConnection.ts index 8ed93166cf..c34c3a8310 100644 --- a/packages/backend-common/src/database/SingleConnection.ts +++ b/packages/backend-common/src/database/SingleConnection.ts @@ -17,16 +17,28 @@ import Knex from 'knex'; import { Config } from '@backstage/config'; import { createDatabaseClient, ensureDatabaseExists } from './connection'; -import { PluginDatabaseClientFactory } from './types'; +import { PluginDatabaseManager } from './types'; /** - * Implements a Database Manager which will automatically create new databases for plugins when requested. All - * requested databases are created with the credentials provided. + * Implements a Database Manager which will automatically create new databases + * for plugins when requested. All requested databases are created with the + * credentials provided. */ -export class SingleConnectionManager { +export class SingleConnectionDatabaseManager { private readonly config: Config; - constructor(config: Config) { + /** + * Creates a new SingleConnectionDatabaseManager instance by reading from the `backend` + * config section, specifically the `.database` key for discovering the management + * database configuration. + */ + static fromConfig(config: Config): SingleConnectionDatabaseManager { + return new SingleConnectionDatabaseManager( + config.getConfig('backend.database'), + ); + } + + private constructor(config: Config) { this.config = config; } @@ -39,17 +51,21 @@ export class SingleConnectionManager { } /** - * Generates a PluginDatabaseClientFactory for consumption by plugins. + * Generates a PluginDatabaseManager for consumption by plugins. */ - getDatabaseClientFactory(pluginId: string): PluginDatabaseClientFactory { - return (database?: string): Promise => { - return this.getDatabase(pluginId, database); + forPlugin(pluginId: string): PluginDatabaseManager { + const _this = this; + + return { + getClient(database?: string): Promise { + return _this.getDatabase(pluginId, database); + }, }; } private async getDatabase(pluginId: string, suffix?: string): Promise { const config = this.getDatabaseConfig(); - const overrides = SingleConnectionManager.getDatabaseOverrides( + const overrides = SingleConnectionDatabaseManager.getDatabaseOverrides( pluginId, suffix, ); diff --git a/packages/backend-common/src/database/types.ts b/packages/backend-common/src/database/types.ts index b616ca848a..915a12a24e 100644 --- a/packages/backend-common/src/database/types.ts +++ b/packages/backend-common/src/database/types.ts @@ -17,11 +17,17 @@ import knex from 'knex'; /** - * The PluginDatabaseClientFactory is used to provide a mechanism for backend plugins to obtain database connections - * for itself. - * - * The purpose of this factory is to allow plugins to get isolated data stores so that plugins are discouraged from - * database integration. Plugins can omit the `database` parameter to get the default plugin database, or - * provide an identifier that will be used to identify a separate database from the default. + * The PluginDatabaseManager manages access to databases that Plugins get. */ -export type PluginDatabaseClientFactory = (database?: string) => Promise; +export interface PluginDatabaseManager { + /** + * getClient provides backend plugins database connections for itself. + * + * The purpose of this method is to allow plugins to get isolated data + * stores so that plugins are discouraged from database integration. Plugins + * can omit the `database` parameter to get the default plugin database, or + * provide an identifier that will be used to identify a separate database + * from the default. + */ + getClient(database?: string): Promise; +} diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index a3b700de46..18fd44f089 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -29,7 +29,7 @@ import { getRootLogger, useHotMemoize, notFoundHandler, - SingleConnectionManager, + SingleConnectionDatabaseManager, SingleHostDiscovery, UrlReaders, } from '@backstage/backend-common'; @@ -54,14 +54,12 @@ function makeCreateEnv(config: ConfigReader) { root.info(`Created UrlReader ${reader}`); - const databaseManager = new SingleConnectionManager( - config.getConfig('backend.database'), - ); + const databaseManager = SingleConnectionDatabaseManager.fromConfig(config); return (plugin: string): PluginEnvironment => { const logger = root.child({ type: 'plugin', plugin }); - const databaseFactory = databaseManager.getDatabaseClientFactory(plugin); - return { logger, databaseClientFactory: databaseFactory, config, reader, discovery }; + const database = databaseManager.forPlugin(plugin); + return { logger, database, config, reader, discovery }; }; } diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts index 1e933fb398..913c8b783d 100644 --- a/packages/backend/src/plugins/auth.ts +++ b/packages/backend/src/plugins/auth.ts @@ -19,14 +19,9 @@ import { PluginEnvironment } from '../types'; export default async function createPlugin({ logger, - databaseClientFactory, + database, config, discovery, }: PluginEnvironment) { - return await createRouter({ - logger, - config, - database: databaseClientFactory, - discovery, - }); + return await createRouter({ logger, config, database, discovery }); } diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts index 04e3304f0c..b8e99e6398 100644 --- a/packages/backend/src/plugins/catalog.ts +++ b/packages/backend/src/plugins/catalog.ts @@ -30,14 +30,13 @@ export default async function createPlugin({ logger, config, reader, - databaseClientFactory, + database, }: PluginEnvironment) { const locationReader = new LocationReaders({ logger, reader, config }); - const db = await DatabaseManager.createDatabase( - await databaseClientFactory(), - { logger }, - ); + const db = await DatabaseManager.createDatabase(await database.getClient(), { + logger, + }); const entitiesCatalog = new DatabaseEntitiesCatalog(db); const locationsCatalog = new DatabaseLocationsCatalog(db); const higherOrderOperation = new HigherOrderOperations( diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index b032917f1d..f63b9dd780 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -17,14 +17,14 @@ import { Logger } from 'winston'; import { Config } from '@backstage/config'; import { - PluginDatabaseClientFactory, + PluginDatabaseManager, PluginEndpointDiscovery, - UrlReader + UrlReader, } from '@backstage/backend-common'; export type PluginEnvironment = { logger: Logger; - databaseClientFactory: PluginDatabaseClientFactory; + database: PluginDatabaseManager; config: Config; reader: UrlReader; discovery: PluginEndpointDiscovery; diff --git a/packages/create-app/templates/default-app/packages/backend/src/index.ts b/packages/create-app/templates/default-app/packages/backend/src/index.ts index b35f21c5e9..d58ebe4e42 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/index.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/index.ts @@ -13,7 +13,7 @@ import { getRootLogger, useHotMemoize, notFoundHandler, - SingleConnectionManager, + SingleConnectionDatabaseManager, SingleHostDiscovery, UrlReaders, } from '@backstage/backend-common'; @@ -32,20 +32,12 @@ function makeCreateEnv(config: ConfigReader) { root.info(`Created UrlReader ${reader}`); - const databaseManager = new SingleConnectionManager( - config.getConfig('backend.database') - ); + const databaseManager = SingleConnectionDatabaseManager.fromConfig(config); return (plugin: string): PluginEnvironment => { const logger = root.child({ type: 'plugin', plugin }); - const databaseFactory = databaseManager.getDatabaseClientFactory(plugin); - return { - logger, - databaseClientFactory: databaseFactory, - config, - reader, - discovery - }; + const database = databaseManager.forPlugin(plugin); + return { logger, database, config, reader, discovery }; }; } diff --git a/packages/create-app/templates/default-app/packages/backend/src/plugins/auth.ts b/packages/create-app/templates/default-app/packages/backend/src/plugins/auth.ts index 0f8cc21dec..fe19855d5d 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/plugins/auth.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/plugins/auth.ts @@ -3,14 +3,9 @@ import { PluginEnvironment } from '../types'; export default async function createPlugin({ logger, - databaseClientFactory, + database, config, discovery, }: PluginEnvironment) { - return await createRouter({ - logger, - config, - database: databaseClientFactory, - discovery - }); + return await createRouter({ logger, config, database, discovery }); } diff --git a/packages/create-app/templates/default-app/packages/backend/src/plugins/catalog.ts b/packages/create-app/templates/default-app/packages/backend/src/plugins/catalog.ts index a0ff556ed0..aee38a5fa0 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/plugins/catalog.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/plugins/catalog.ts @@ -14,12 +14,11 @@ export default async function createPlugin({ logger, config, reader, - databaseClientFactory, + database, }: PluginEnvironment) { const locationReader = new LocationReaders({ logger, reader, config }); - const db = await DatabaseManager.createDatabase( - await databaseClientFactory(), + const db = await DatabaseManager.createDatabase(await database.getClient(), { logger }, ); const entitiesCatalog = new DatabaseEntitiesCatalog(db); diff --git a/packages/create-app/templates/default-app/packages/backend/src/types.ts b/packages/create-app/templates/default-app/packages/backend/src/types.ts index c2b7b5fa8e..757a0e5acf 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/types.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/types.ts @@ -1,14 +1,14 @@ import { Logger } from 'winston'; import { Config } from '@backstage/config'; import { - PluginDatabaseClientFactory, + PluginDatabaseManager, PluginEndpointDiscovery, - UrlReader + UrlReader, } from '@backstage/backend-common'; export type PluginEnvironment = { logger: Logger; - databaseClientFactory: PluginDatabaseClientFactory; + database: PluginDatabaseManager; config: Config; reader: UrlReader discovery: PluginEndpointDiscovery; diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index f21b2d58fc..6bb100de6b 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -24,12 +24,12 @@ import { DatabaseKeyStore, TokenFactory, createOidcRouter } from '../identity'; import { NotFoundError, PluginEndpointDiscovery, - PluginDatabaseClientFactory, + PluginDatabaseManager, } from '@backstage/backend-common'; export interface RouterOptions { logger: Logger; - database: PluginDatabaseClientFactory; + database: PluginDatabaseManager; config: Config; discovery: PluginEndpointDiscovery; } @@ -48,7 +48,7 @@ export async function createRouter({ const keyDurationSeconds = 3600; const keyStore = await DatabaseKeyStore.create({ - database: await database(), + database: await database.getClient(), }); const tokenIssuer = new TokenFactory({ issuer: authUrl, diff --git a/plugins/auth-backend/src/service/standaloneServer.ts b/plugins/auth-backend/src/service/standaloneServer.ts index b5c15b3704..d00e7d523d 100644 --- a/plugins/auth-backend/src/service/standaloneServer.ts +++ b/plugins/auth-backend/src/service/standaloneServer.ts @@ -53,8 +53,10 @@ export async function startStandaloneServer( const router = await createRouter({ logger, config, - database: async () => { - return database; + database: { + async getClient() { + return database; + }, }, discovery, });