diff --git a/.changeset/old-dingos-shave.md b/.changeset/old-dingos-shave.md new file mode 100644 index 0000000000..3263a89c0d --- /dev/null +++ b/.changeset/old-dingos-shave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Honor database migration configuration diff --git a/.changeset/seven-rabbits-shave.md b/.changeset/seven-rabbits-shave.md new file mode 100644 index 0000000000..9e14cde2e7 --- /dev/null +++ b/.changeset/seven-rabbits-shave.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Add options argument to support additional database migrations configuration diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 9c66c0573e..e623f656ab 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -175,9 +175,17 @@ export function createStatusCheckRouter(options: { // @public (undocumented) export class DatabaseManager { forPlugin(pluginId: string): PluginDatabaseManager; - static fromConfig(config: Config): DatabaseManager; + static fromConfig( + config: Config, + options?: DatabaseManagerOptions, + ): DatabaseManager; } +// @public +export type DatabaseManagerOptions = { + migrations?: PluginDatabaseManager['migrations']; +}; + // @public (undocumented) export class DockerContainerRunner implements ContainerRunner { constructor({ dockerClient }: { dockerClient: Docker }); @@ -395,6 +403,9 @@ export type PluginCacheManager = { // @public export interface PluginDatabaseManager { getClient(): Promise; + migrations?: { + skip?: boolean; + }; } // @public @@ -642,4 +653,8 @@ export function useHotCleanup( // @public export function useHotMemoize(_module: NodeModule, valueFactory: () => T): T; + +// Warnings were encountered during analysis: +// +// src/database/types.d.ts:23:12 - (tsdoc-undefined-tag) The TSDoc tag "@default" is not defined in this configuration ``` diff --git a/packages/backend-common/src/database/DatabaseManager.test.ts b/packages/backend-common/src/database/DatabaseManager.test.ts index 1d2ff58b41..bd2e7b9f71 100644 --- a/packages/backend-common/src/database/DatabaseManager.test.ts +++ b/packages/backend-common/src/database/DatabaseManager.test.ts @@ -36,25 +36,45 @@ describe('DatabaseManager', () => { afterEach(() => jest.resetAllMocks()); describe('DatabaseManager.fromConfig', () => { - it('accesses the backend.database key', () => { - const config = new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: { - host: 'localhost', - user: 'foo', - password: 'bar', - database: 'foodb', - }, + const backendConfig = { + backend: { + database: { + client: 'pg', + connection: { + host: 'localhost', + user: 'foo', + password: 'bar', + database: 'foodb', }, }, - }); + }, + }; + + it('accesses the backend.database key', () => { + const config = new ConfigReader(backendConfig); const getConfigSpy = jest.spyOn(config, 'getConfig'); DatabaseManager.fromConfig(config); expect(getConfigSpy).toHaveBeenCalledWith('backend.database'); }); + + it('handles default options', () => { + const config = new ConfigReader(backendConfig); + const database = DatabaseManager.fromConfig(config); + const client = database.forPlugin('test'); + + expect(client.migrations?.skip).toBe(false); + }); + + it('handles migrations options', () => { + const config = new ConfigReader(backendConfig); + const database = DatabaseManager.fromConfig(config, { + migrations: { skip: true }, + }); + const client = database.forPlugin('test'); + + expect(client.migrations?.skip).toBe(true); + }); }); describe('DatabaseManager.forPlugin', () => { diff --git a/packages/backend-common/src/database/DatabaseManager.ts b/packages/backend-common/src/database/DatabaseManager.ts index a3f31e98f2..8e23a219ec 100644 --- a/packages/backend-common/src/database/DatabaseManager.ts +++ b/packages/backend-common/src/database/DatabaseManager.ts @@ -36,6 +36,15 @@ function pluginPath(pluginId: string): string { return `plugin.${pluginId}`; } +/** + * Configuration options object. + * + * @public + */ +export type DatabaseManagerOptions = { + migrations?: PluginDatabaseManager['migrations']; +}; + /** @public */ export class DatabaseManager { /** @@ -47,19 +56,25 @@ export class DatabaseManager { * names if config is not provided. * * @param config - The loaded application configuration. + * @param options - An optional configuration object. */ - static fromConfig(config: Config): DatabaseManager { + static fromConfig( + config: Config, + options?: DatabaseManagerOptions, + ): DatabaseManager { const databaseConfig = config.getConfig('backend.database'); return new DatabaseManager( databaseConfig, databaseConfig.getOptionalString('prefix'), + options, ); } private constructor( private readonly config: Config, private readonly prefix: string = 'backstage_plugin_', + private readonly options?: DatabaseManagerOptions, ) {} /** @@ -76,6 +91,10 @@ export class DatabaseManager { getClient(): Promise { return _this.getDatabase(pluginId); }, + migrations: { + skip: false, + ..._this.options?.migrations, + }, }; } diff --git a/packages/backend-common/src/database/types.ts b/packages/backend-common/src/database/types.ts index e96f86980b..344f1088b8 100644 --- a/packages/backend-common/src/database/types.ts +++ b/packages/backend-common/src/database/types.ts @@ -30,6 +30,18 @@ export interface PluginDatabaseManager { * stores so that plugins are discouraged from database integration. */ getClient(): Promise; + + /** + * This property is used to control the behavior of database migrations. + */ + migrations?: { + /** + * skip database migrations. Useful if connecting to a read-only database. + * + * @default false + */ + skip?: boolean; + }; } /** diff --git a/plugins/catalog-backend/src/service/NextCatalogBuilder.ts b/plugins/catalog-backend/src/service/NextCatalogBuilder.ts index c9ebc8f723..1618ef8d7b 100644 --- a/plugins/catalog-backend/src/service/NextCatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/NextCatalogBuilder.ts @@ -336,7 +336,10 @@ export class NextCatalogBuilder { const parser = this.parser || defaultEntityDataParser; const dbClient = await database.getClient(); - await applyDatabaseMigrations(dbClient); + if (!database.migrations?.skip) { + logger.info('Performing database migration'); + await applyDatabaseMigrations(dbClient); + } const db = new CommonDatabase(dbClient, logger);