From 02103becc6849f77ff08618d3c551797e0df7ecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 21 May 2024 12:39:27 +0200 Subject: [PATCH] move over cache and database services MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/six-llamas-give.md | 6 + packages/backend-app-api/api-report.md | 4 +- packages/backend-common/api-report.md | 47 ++- .../src/cache/CacheManager.test.ts | 384 ------------------ .../src/cache/cacheToPluginCacheManager.ts | 34 ++ packages/backend-common/src/cache/index.ts | 11 +- packages/backend-common/src/cache/reexport.ts | 29 ++ packages/backend-common/src/cache/types.ts | 37 +- packages/backend-common/src/database/index.ts | 7 +- .../backend-common/src/database/reexport.ts | 37 ++ .../src/discovery/HostDiscovery.ts | 9 +- .../backend-common/src/discovery/index.ts | 1 + packages/backend-defaults/api-report-cache.md | 33 +- .../backend-defaults/api-report-database.md | 37 ++ packages/backend-defaults/package.json | 13 +- .../entrypoints}/cache/CacheClient.test.ts | 0 .../src/entrypoints}/cache/CacheClient.ts | 0 .../cache/CacheManager.integration.test.ts | 39 +- .../src/entrypoints}/cache/CacheManager.ts | 63 +-- .../entrypoints/cache/cacheServiceFactory.ts | 7 +- .../src/entrypoints/cache/index.ts | 2 + .../src/entrypoints/cache/types.ts | 46 +++ .../database/DatabaseManager.test.ts | 0 .../entrypoints}/database/DatabaseManager.ts | 2 +- .../connectors/defaultNameOverride.test.ts | 0 .../connectors/defaultNameOverride.ts | 0 .../connectors/defaultSchemaOverride.test.ts | 0 .../connectors/defaultSchemaOverride.ts | 0 .../entrypoints}/database/connectors/index.ts | 0 .../connectors/mergeDatabaseConfig.test.ts | 0 .../connectors/mergeDatabaseConfig.ts | 0 .../database/connectors/mysql.test.ts | 0 .../entrypoints}/database/connectors/mysql.ts | 0 .../database/connectors/postgres.test.ts | 0 .../database/connectors/postgres.ts | 0 .../database/connectors/sqlite3.test.ts | 0 .../database/connectors/sqlite3.ts | 0 .../src/entrypoints/database/index.ts | 6 + .../src/entrypoints/database/types.ts | 100 +++++ plugins/auth-backend/api-report.md | 4 +- yarn.lock | 11 + 41 files changed, 469 insertions(+), 500 deletions(-) create mode 100644 .changeset/six-llamas-give.md delete mode 100644 packages/backend-common/src/cache/CacheManager.test.ts create mode 100644 packages/backend-common/src/cache/cacheToPluginCacheManager.ts create mode 100644 packages/backend-common/src/cache/reexport.ts create mode 100644 packages/backend-common/src/database/reexport.ts rename packages/{backend-common/src => backend-defaults/src/entrypoints}/cache/CacheClient.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/cache/CacheClient.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/cache/CacheManager.integration.test.ts (76%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/cache/CacheManager.ts (82%) create mode 100644 packages/backend-defaults/src/entrypoints/cache/types.ts rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/DatabaseManager.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/DatabaseManager.ts (99%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/defaultNameOverride.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/defaultNameOverride.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/defaultSchemaOverride.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/defaultSchemaOverride.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/index.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/mergeDatabaseConfig.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/mergeDatabaseConfig.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/mysql.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/mysql.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/postgres.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/postgres.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/sqlite3.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/sqlite3.ts (100%) create mode 100644 packages/backend-defaults/src/entrypoints/database/types.ts diff --git a/.changeset/six-llamas-give.md b/.changeset/six-llamas-give.md new file mode 100644 index 0000000000..9bfbf52625 --- /dev/null +++ b/.changeset/six-llamas-give.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-defaults': minor +'@backstage/backend-common': minor +--- + +Deprecated and moved over core services to `@backstage/backend-defaults` diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index 49965f4c46..11d9dd90d4 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -8,7 +8,7 @@ import type { AppConfig } from '@backstage/config'; import { AuthService } from '@backstage/backend-plugin-api'; import { BackendFeature } from '@backstage/backend-plugin-api'; -import { CacheClient } from '@backstage/backend-common'; +import { CacheService } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { ConfigSchema } from '@backstage/config-loader'; import { CorsOptions } from 'cors'; @@ -66,7 +66,7 @@ export interface Backend { } // @public @deprecated (undocumented) -export const cacheServiceFactory: () => ServiceFactory; +export const cacheServiceFactory: () => ServiceFactory; // @public (undocumented) export function createConfigSecretEnumerator(options: { diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 4e92420667..7e677bd570 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -17,11 +17,12 @@ import { BackendFeature } from '@backstage/backend-plugin-api'; import { BitbucketCloudIntegration } from '@backstage/integration'; import { BitbucketIntegration } from '@backstage/integration'; import { BitbucketServerIntegration } from '@backstage/integration'; -import { CacheService as CacheClient } from '@backstage/backend-plugin-api'; -import { CacheServiceOptions as CacheClientOptions } from '@backstage/backend-plugin-api'; -import { CacheServiceSetOptions as CacheClientSetOptions } from '@backstage/backend-plugin-api'; +import { CacheService } from '@backstage/backend-plugin-api'; +import { CacheServiceOptions } from '@backstage/backend-plugin-api'; +import type { CacheServiceSetOptions } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import cors from 'cors'; +import { DiscoveryService } from '@backstage/backend-plugin-api'; import Docker from 'dockerode'; import { ErrorRequestHandler } from 'express'; import express from 'express'; @@ -44,7 +45,6 @@ import { LoggerService } from '@backstage/backend-plugin-api'; import { MergeResult } from 'isomorphic-git'; import { PermissionsService } from '@backstage/backend-plugin-api'; import { DatabaseService as PluginDatabaseManager } from '@backstage/backend-plugin-api'; -import { DiscoveryService as PluginEndpointDiscovery } from '@backstage/backend-plugin-api'; import { PluginMetadataService } from '@backstage/backend-plugin-api'; import { PushResult } from 'isomorphic-git'; import { Readable } from 'stream'; @@ -193,18 +193,26 @@ export class BitbucketUrlReader implements UrlReader { toString(): string; } -export { CacheClient }; +// @public @deprecated (undocumented) +export type CacheClient = CacheService; -export { CacheClientOptions }; +// @public @deprecated (undocumented) +export type CacheClientOptions = CacheServiceOptions; -export { CacheClientSetOptions }; +// @public @deprecated (undocumented) +export type CacheClientSetOptions = CacheServiceSetOptions; // @public export class CacheManager { - forPlugin(pluginId: string): PluginCacheManager; + forPlugin(pluginId: string): { + getClient(options?: CacheServiceOptions): CacheService; + }; static fromConfig( config: Config, - options?: CacheManagerOptions, + options?: { + logger?: LoggerService; + onError?: (err: Error) => void; + }, ): CacheManager; } @@ -214,10 +222,10 @@ export type CacheManagerOptions = { onError?: (err: Error) => void; }; -// @public (undocumented) -export function cacheToPluginCacheManager( - cache: CacheClient, -): PluginCacheManager; +// @public +export function cacheToPluginCacheManager(cache: CacheService): { + getClient(options?: CacheServiceOptions): CacheService; +}; // @public @deprecated export const coloredFormat: winston.Logform.Format; @@ -575,10 +583,10 @@ export const legacyPlugin: ( default: LegacyCreateRouter< TransformedEnv< { - cache: CacheClient; + cache: CacheService; config: RootConfigService; database: PluginDatabaseManager; - discovery: PluginEndpointDiscovery; + discovery: DiscoveryService; logger: LoggerService; permissions: PermissionsService; scheduler: SchedulerService; @@ -588,7 +596,9 @@ export const legacyPlugin: ( }, { logger: (log: LoggerService) => Logger; - cache: (cache: CacheClient) => PluginCacheManager; + cache: (cache: CacheService) => { + getClient(options?: CacheServiceOptions | undefined): CacheService; + }; } > >; @@ -639,12 +649,13 @@ export function notFoundHandler(): RequestHandler; // @public (undocumented) export interface PluginCacheManager { // (undocumented) - getClient(options?: CacheClientOptions): CacheClient; + getClient(options?: CacheServiceOptions): CacheService; } export { PluginDatabaseManager }; -export { PluginEndpointDiscovery }; +// @public @deprecated (undocumented) +export type PluginEndpointDiscovery = DiscoveryService; // @public export interface PullOptions { diff --git a/packages/backend-common/src/cache/CacheManager.test.ts b/packages/backend-common/src/cache/CacheManager.test.ts deleted file mode 100644 index 4b7bb05efd..0000000000 --- a/packages/backend-common/src/cache/CacheManager.test.ts +++ /dev/null @@ -1,384 +0,0 @@ -/* - * Copyright 2021 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 { ConfigReader } from '@backstage/config'; -import Keyv from 'keyv'; -import KeyvMemcache from '@keyv/memcache'; -import KeyvRedis from '@keyv/redis'; -import { DefaultCacheClient } from './CacheClient'; -import { CacheManager } from './CacheManager'; - -jest.createMockFromModule('keyv'); -jest.mock('keyv'); -jest.createMockFromModule('@keyv/memcache'); -jest.mock('@keyv/memcache'); -jest.createMockFromModule('@keyv/redis'); -jest.mock('@keyv/redis'); -jest.mock('./CacheClient', () => { - return { - DefaultCacheClient: jest.fn(), - }; -}); - -const globalDefaultTtl = 1234; -describe('CacheManager', () => { - const defaultConfigOptions = { - backend: { - cache: { - store: 'memory', - defaultTtl: globalDefaultTtl, - }, - }, - }; - const defaultConfig = () => new ConfigReader(defaultConfigOptions); - - afterEach(() => jest.resetAllMocks()); - - describe('CacheManager.fromConfig', () => { - it('accesses the backend.cache key', () => { - const getOptionalString = jest.fn(); - const getOptionalBoolean = jest.fn(); - const getOptionalNumber = jest.fn(); - const config = defaultConfig(); - config.getOptionalString = getOptionalString; - config.getOptionalBoolean = getOptionalBoolean; - config.getOptionalNumber = getOptionalNumber; - - CacheManager.fromConfig(config); - - expect(getOptionalString.mock.calls[0][0]).toEqual('backend.cache.store'); - expect(getOptionalString.mock.calls[1][0]).toEqual( - 'backend.cache.connection', - ); - expect(getOptionalBoolean.mock.calls[0][0]).toEqual( - 'backend.cache.useRedisSets', - ); - expect(getOptionalNumber.mock.calls[0][0]).toEqual( - 'backend.cache.defaultTtl', - ); - }); - - it('does not require the backend.cache key', () => { - const config = new ConfigReader({ backend: {} }); - expect(() => { - CacheManager.fromConfig(config); - }).not.toThrow(); - }); - - it('throws on unknown cache store', () => { - const config = new ConfigReader({ - backend: { cache: { store: 'notreal' } }, - }); - expect(() => { - CacheManager.fromConfig(config); - }).toThrow(); - }); - }); - - describe('CacheManager.forPlugin', () => { - const manager = CacheManager.fromConfig(defaultConfig()); - - it('connects to a cache store scoped to the plugin', async () => { - const pluginId = 'test1'; - manager.forPlugin(pluginId).getClient(); - - const client = DefaultCacheClient as jest.Mock; - expect(client).toHaveBeenCalledTimes(1); - }); - - it('attaches error handler to client', () => { - const pluginId = 'error-test'; - manager.forPlugin(pluginId).getClient(); - - const client = DefaultCacheClient as jest.Mock; - const mockCalls = client.mock.calls.splice(-1); - const realClient = mockCalls[0][0] as Keyv; - expect(realClient.on).toHaveBeenCalledWith('error', expect.any(Function)); - }); - - it('provides different plugins different cache clients', async () => { - const plugin1Id = 'test1'; - const plugin2Id = 'test2'; - const expectedTtl = 3600; - manager.forPlugin(plugin1Id).getClient({ defaultTtl: expectedTtl }); - manager.forPlugin(plugin2Id).getClient({ defaultTtl: expectedTtl }); - - const client = DefaultCacheClient as jest.Mock; - const cache = Keyv as unknown as jest.Mock; - expect(cache).toHaveBeenCalledTimes(2); - expect(client).toHaveBeenCalledTimes(2); - - const plugin1CallArgs = cache.mock.calls[0]; - const plugin2CallArgs = cache.mock.calls[1]; - expect(plugin1CallArgs[0].namespace).not.toEqual( - plugin2CallArgs[0].namespace, - ); - }); - }); - - describe('CacheManager.forPlugin stores', () => { - it('returns memory client when no cache is configured', () => { - const manager = CacheManager.fromConfig( - new ConfigReader({ backend: {} }), - ); - const expectedTtl = 3600; - const expectedNamespace = 'test-plugin'; - manager - .forPlugin(expectedNamespace) - .getClient({ defaultTtl: expectedTtl }); - - const cache = Keyv as unknown as jest.Mock; - const mockCalls = cache.mock.calls.splice(-1); - const callArgs = mockCalls[0]; - expect(callArgs[0]).toMatchObject({ - ttl: expectedTtl, - namespace: expectedNamespace, - }); - }); - - it('returns memory client when explicitly configured', () => { - const manager = CacheManager.fromConfig(defaultConfig()); - const expectedTtl = 3600; - const expectedNamespace = 'test-plugin'; - manager - .forPlugin(expectedNamespace) - .getClient({ defaultTtl: expectedTtl }); - - const cache = Keyv as unknown as jest.Mock; - const mockCalls = cache.mock.calls.splice(-1); - const callArgs = mockCalls[0]; - expect(callArgs[0]).toMatchObject({ - ttl: expectedTtl, - namespace: expectedNamespace, - }); - }); - - it('returns memory client with a global defaultTtl when explicitly configured', () => { - const manager = CacheManager.fromConfig(defaultConfig()); - const expectedNamespace = 'test-plugin'; - manager.forPlugin(expectedNamespace).getClient(); - - const cache = Keyv as unknown as jest.Mock; - const mockCalls = cache.mock.calls.splice(-1); - const callArgs = mockCalls[0]; - expect(callArgs[0]).toMatchObject({ - ttl: globalDefaultTtl, - namespace: expectedNamespace, - }); - }); - - it('shares memory across multiple instances of the memory client', () => { - const manager = CacheManager.fromConfig(defaultConfig()); - const plugin = 'test-plugin'; - - // Instantiate two in-memory clients. - manager.forPlugin(plugin).getClient({ defaultTtl: 10 }); - manager.forPlugin(plugin).getClient({ defaultTtl: 10 }); - - const cache = Keyv as unknown as jest.Mock; - const mockCall2 = cache.mock.calls.splice(-1)[0][0]; - const mockCall1 = cache.mock.calls.splice(-1)[0][0]; - - // Note: .toBe() checks referential identity of object instances. - expect(mockCall1.store).toBe(mockCall2.store); - }); - - it('returns a memcache client when configured', () => { - const expectedHost = '127.0.0.1:11211'; - const manager = CacheManager.fromConfig( - new ConfigReader({ - backend: { - cache: { - store: 'memcache', - connection: expectedHost, - }, - }, - }), - ); - const expectedTtl = 3600; - manager.forPlugin('test').getClient({ defaultTtl: expectedTtl }); - - const cache = Keyv as unknown as jest.Mock; - const mockCacheCalls = cache.mock.calls.splice(-1); - expect(mockCacheCalls[0][0]).toMatchObject({ - ttl: expectedTtl, - }); - expect(mockCacheCalls[0][0].store).toBeInstanceOf(KeyvMemcache); - const memcache = KeyvMemcache as unknown as jest.Mock; - const mockMemcacheCalls = memcache.mock.calls.splice(-1); - expect(mockMemcacheCalls[0][0]).toEqual(expectedHost); - }); - - it('returns a memcache client with a global defaultTtl when configured', () => { - const expectedHost = '127.0.0.1:11211'; - const manager = CacheManager.fromConfig( - new ConfigReader({ - backend: { - cache: { - store: 'memcache', - connection: expectedHost, - defaultTtl: globalDefaultTtl, - }, - }, - }), - ); - manager.forPlugin('test').getClient(); - - const cache = Keyv as unknown as jest.Mock; - const mockCacheCalls = cache.mock.calls.splice(-1); - expect(mockCacheCalls[0][0]).toMatchObject({ - ttl: globalDefaultTtl, - }); - expect(mockCacheCalls[0][0].store).toBeInstanceOf(KeyvMemcache); - const memcache = KeyvMemcache as unknown as jest.Mock; - const mockMemcacheCalls = memcache.mock.calls.splice(-1); - expect(mockMemcacheCalls[0][0]).toEqual(expectedHost); - }); - - it('returns a Redis client when configured', () => { - const redisConnection = 'redis://127.0.0.1:6379'; - const manager = CacheManager.fromConfig( - new ConfigReader({ - backend: { - cache: { - store: 'redis', - connection: redisConnection, - }, - }, - }), - ); - const expectedTtl = 3600; - manager.forPlugin('test').getClient({ defaultTtl: expectedTtl }); - - const cache = Keyv as unknown as jest.Mock; - const mockCacheCalls = cache.mock.calls.splice(-1); - expect(mockCacheCalls[0][0]).toMatchObject({ - ttl: expectedTtl, - }); - expect(mockCacheCalls[0][0].store).toBeInstanceOf(KeyvRedis); - const redis = KeyvRedis as unknown as jest.Mock; - const mockRedisCalls = redis.mock.calls.splice(-1); - expect(mockRedisCalls[0][0]).toEqual(redisConnection); - }); - - it('returns a Redis client with a global defaultTtl when configured', () => { - const redisConnection = 'redis://127.0.0.1:6379'; - const manager = CacheManager.fromConfig( - new ConfigReader({ - backend: { - cache: { - store: 'redis', - connection: redisConnection, - defaultTtl: globalDefaultTtl, - }, - }, - }), - ); - manager.forPlugin('test').getClient(); - - const cache = Keyv as unknown as jest.Mock; - const mockCacheCalls = cache.mock.calls.splice(-1); - expect(mockCacheCalls[0][0]).toMatchObject({ - ttl: globalDefaultTtl, - }); - expect(mockCacheCalls[0][0].store).toBeInstanceOf(KeyvRedis); - const redis = KeyvRedis as unknown as jest.Mock; - const mockRedisCalls = redis.mock.calls.splice(-1); - expect(mockRedisCalls[0][0]).toEqual(redisConnection); - }); - - it('returns a Redis client when configured with useRedisSets flag', () => { - const redisConnection = 'redis://127.0.0.1:6379'; - const useRedisSets = false; - const manager = CacheManager.fromConfig( - new ConfigReader({ - backend: { - cache: { - store: 'redis', - connection: redisConnection, - useRedisSets: useRedisSets, - }, - }, - }), - ); - const expectedTtl = 3600; - manager.forPlugin('test').getClient({ defaultTtl: expectedTtl }); - - const cache = Keyv as unknown as jest.Mock; - const mockCacheCalls = cache.mock.calls.splice(-1); - expect(mockCacheCalls[0][0]).toMatchObject({ - ttl: expectedTtl, - useRedisSets: useRedisSets, - }); - expect(mockCacheCalls[0][0].store).toBeInstanceOf(KeyvRedis); - const redis = KeyvRedis as unknown as jest.Mock; - const mockRedisCalls = redis.mock.calls.splice(-1); - expect(mockRedisCalls[0][0]).toEqual(redisConnection); - }); - }); - - describe('connection errors', () => { - it('uses provided logger', () => { - // Set up and inject mock logger. - const mockLogger = { child: jest.fn(), error: jest.fn() }; - mockLogger.child.mockImplementation(() => mockLogger as any); - const manager = CacheManager.fromConfig(defaultConfig(), { - logger: mockLogger as any, - }); - - // Set up a cache client using the configured manager. - manager.forPlugin('error-logger-test').getClient(); - - // Retrieve the error handler attached to the cache client. - const client = DefaultCacheClient as jest.Mock; - const mockCalls = client.mock.calls.splice(-1); - const realClient = mockCalls[0][0] as Keyv; - const realOnError = realClient.on as jest.Mock; - const realHandler = realOnError.mock.calls.splice(-1)[0][1]; - - // Invoke the actual error handler. - const expectedError = new Error('some error'); - realHandler(expectedError); - expect(mockLogger.error).toHaveBeenCalledWith( - 'Failed to create cache client', - expectedError, - ); - }); - - it('calls provided handler', () => { - // Set up and inject mock logger. - const mockHandler = jest.fn(); - const manager = CacheManager.fromConfig(defaultConfig(), { - onError: mockHandler, - }); - - // Set up a cache client using the configured manager. - manager.forPlugin('error-handler-test').getClient(); - - // Retrieve the error handler attached to the cache client. - const client = DefaultCacheClient as jest.Mock; - const mockCalls = client.mock.calls.splice(-1); - const realClient = mockCalls[0][0] as Keyv; - const realOnError = realClient.on as jest.Mock; - const realHandler = realOnError.mock.calls.splice(-1)[0][1]; - - // Invoke the actual error handler. - const expectedError = new Error('some error'); - realHandler(expectedError); - expect(mockHandler).toHaveBeenCalledWith(expectedError); - }); - }); -}); diff --git a/packages/backend-common/src/cache/cacheToPluginCacheManager.ts b/packages/backend-common/src/cache/cacheToPluginCacheManager.ts new file mode 100644 index 0000000000..2654934804 --- /dev/null +++ b/packages/backend-common/src/cache/cacheToPluginCacheManager.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2021 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 { + CacheService, + CacheServiceOptions, +} from '@backstage/backend-plugin-api'; + +/** + * Compatibility wrapper for going from a new-backend cache service to the + * old-backend plugin cache manager. + * + * @public + */ +export function cacheToPluginCacheManager(cache: CacheService): { + getClient(options?: CacheServiceOptions): CacheService; +} { + return { + getClient: (opts: CacheServiceOptions) => cache.withOptions(opts), + }; +} diff --git a/packages/backend-common/src/cache/index.ts b/packages/backend-common/src/cache/index.ts index 0187aa45d5..9d05745ebd 100644 --- a/packages/backend-common/src/cache/index.ts +++ b/packages/backend-common/src/cache/index.ts @@ -14,11 +14,6 @@ * limitations under the License. */ -export { CacheManager, cacheToPluginCacheManager } from './CacheManager'; -export type { - CacheClient, - CacheClientSetOptions, - PluginCacheManager, - CacheManagerOptions, - CacheClientOptions, -} from './types'; +export { cacheToPluginCacheManager } from './cacheToPluginCacheManager'; +export * from './reexport'; +export * from './types'; diff --git a/packages/backend-common/src/cache/reexport.ts b/packages/backend-common/src/cache/reexport.ts new file mode 100644 index 0000000000..9f6106de60 --- /dev/null +++ b/packages/backend-common/src/cache/reexport.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2024 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. + */ + +/* + * NOTE(freben): This is a temporary hack. We use cross-package imports so that + * we do not have to maintain double implementations for the time being, until + * backend-common is properly removed. + */ + +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +export { CacheManager } from '../../../backend-defaults/src/entrypoints/cache/CacheManager'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +export { + type PluginCacheManager, + type CacheManagerOptions, +} from '../../../backend-defaults/src/entrypoints/cache/types'; diff --git a/packages/backend-common/src/cache/types.ts b/packages/backend-common/src/cache/types.ts index 93fa0ac77c..83b6916843 100644 --- a/packages/backend-common/src/cache/types.ts +++ b/packages/backend-common/src/cache/types.ts @@ -14,39 +14,26 @@ * limitations under the License. */ -import { LoggerService } from '@backstage/backend-plugin-api'; -import { +import type { CacheService, + CacheServiceSetOptions, CacheServiceOptions, } from '@backstage/backend-plugin-api'; -export type { - CacheService as CacheClient, - CacheServiceSetOptions as CacheClientSetOptions, - CacheServiceOptions as CacheClientOptions, -} from '@backstage/backend-plugin-api'; - /** - * Options given when constructing a {@link CacheManager}. - * * @public + * @deprecated Use `CacheService` from the `@backstage/backend-plugin-api` package instead */ -export type CacheManagerOptions = { - /** - * An optional logger for use by the PluginCacheManager. - */ - logger?: LoggerService; - - /** - * An optional handler for connection errors emitted from the underlying data - * store. - */ - onError?: (err: Error) => void; -}; +export type CacheClient = CacheService; /** * @public + * @deprecated Use `CacheServiceSetOptions` from the `@backstage/backend-plugin-api` package instead */ -export interface PluginCacheManager { - getClient(options?: CacheServiceOptions): CacheService; -} +export type CacheClientSetOptions = CacheServiceSetOptions; + +/** + * @public + * @deprecated Use `CacheServiceOptions` from the `@backstage/backend-plugin-api` package instead + */ +export type CacheClientOptions = CacheServiceOptions; diff --git a/packages/backend-common/src/database/index.ts b/packages/backend-common/src/database/index.ts index 77d75653b8..0e95261d82 100644 --- a/packages/backend-common/src/database/index.ts +++ b/packages/backend-common/src/database/index.ts @@ -14,10 +14,5 @@ * limitations under the License. */ -export { DatabaseManager, dropDatabase } from './DatabaseManager'; -export type { - DatabaseManagerOptions, - LegacyRootDatabaseService, -} from './DatabaseManager'; - +export * from './reexport'; export type { PluginDatabaseManager } from './types'; diff --git a/packages/backend-common/src/database/reexport.ts b/packages/backend-common/src/database/reexport.ts new file mode 100644 index 0000000000..1ecff9be15 --- /dev/null +++ b/packages/backend-common/src/database/reexport.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2024 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. + */ + +/* + * NOTE(freben): This is a temporary hack. We use cross-package imports so that + * we do not have to maintain double implementations for the time being, until + * backend-common is properly removed. When it is, the impleemntation should be + * moved into this part of the repo instead. + */ + +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { + DatabaseManager, + dropDatabase, + type DatabaseManagerOptions, + type LegacyRootDatabaseService, +} from '../../../backend-defaults/src/entrypoints/database/DatabaseManager'; + +export { + DatabaseManager, + dropDatabase, + type DatabaseManagerOptions, + type LegacyRootDatabaseService, +}; diff --git a/packages/backend-common/src/discovery/HostDiscovery.ts b/packages/backend-common/src/discovery/HostDiscovery.ts index cf0ddff611..77810b7f6d 100644 --- a/packages/backend-common/src/discovery/HostDiscovery.ts +++ b/packages/backend-common/src/discovery/HostDiscovery.ts @@ -15,8 +15,13 @@ */ import { HostDiscovery as _HostDiscovery } from '@backstage/backend-app-api'; +import { DiscoveryService } from '@backstage/backend-plugin-api'; -export type { DiscoveryService as PluginEndpointDiscovery } from '@backstage/backend-plugin-api'; +/** + * @public + * @deprecated Use `DiscoveryService` from `@backstage/backend-plugin-api` instead + */ +export type PluginEndpointDiscovery = DiscoveryService; /** * HostDiscovery is a basic PluginEndpointDiscovery implementation @@ -40,6 +45,6 @@ export const HostDiscovery = _HostDiscovery; * resolved to the same host, so there won't be any balancing of internal traffic. * * @public - * @deprecated Use {@link HostDiscovery} instead + * @deprecated Use `HostDiscovery` from `@backstage/backend-defaults/discovery` instead */ export const SingleHostDiscovery = _HostDiscovery; diff --git a/packages/backend-common/src/discovery/index.ts b/packages/backend-common/src/discovery/index.ts index bad721d4da..827fd059ad 100644 --- a/packages/backend-common/src/discovery/index.ts +++ b/packages/backend-common/src/discovery/index.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export { HostDiscovery, SingleHostDiscovery, diff --git a/packages/backend-defaults/api-report-cache.md b/packages/backend-defaults/api-report-cache.md index 150ed391f0..2cd2f3efc9 100644 --- a/packages/backend-defaults/api-report-cache.md +++ b/packages/backend-defaults/api-report-cache.md @@ -3,11 +3,40 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { CacheClient } from '@backstage/backend-common'; +import { CacheService } from '@backstage/backend-plugin-api'; +import { CacheServiceOptions } from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; +import { LoggerService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; +// @public +export class CacheManager { + forPlugin(pluginId: string): { + getClient(options?: CacheServiceOptions): CacheService; + }; + static fromConfig( + config: Config, + options?: { + logger?: LoggerService; + onError?: (err: Error) => void; + }, + ): CacheManager; +} + +// @public +export type CacheManagerOptions = { + logger?: LoggerService; + onError?: (err: Error) => void; +}; + // @public (undocumented) -export const cacheServiceFactory: () => ServiceFactory; +export const cacheServiceFactory: () => ServiceFactory; + +// @public (undocumented) +export interface PluginCacheManager { + // (undocumented) + getClient(options?: CacheServiceOptions): CacheService; +} // (No @packageDocumentation comment for this package) ``` diff --git a/packages/backend-defaults/api-report-database.md b/packages/backend-defaults/api-report-database.md index 512e1febc9..0edadaad59 100644 --- a/packages/backend-defaults/api-report-database.md +++ b/packages/backend-defaults/api-report-database.md @@ -3,14 +3,51 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { Config } from '@backstage/config'; +import { DatabaseService } from '@backstage/backend-plugin-api'; +import { LifecycleService } from '@backstage/backend-plugin-api'; +import { LoggerService } from '@backstage/backend-plugin-api'; import { PluginDatabaseManager } from '@backstage/backend-common'; +import { PluginMetadataService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; +// @public +export class DatabaseManager implements LegacyRootDatabaseService { + forPlugin( + pluginId: string, + deps?: { + lifecycle: LifecycleService; + pluginMetadata: PluginMetadataService; + }, + ): DatabaseService; + static fromConfig( + config: Config, + options?: DatabaseManagerOptions, + ): DatabaseManager; +} + +// @public +export type DatabaseManagerOptions = { + migrations?: DatabaseService['migrations']; + logger?: LoggerService; +}; + // @public (undocumented) export const databaseServiceFactory: () => ServiceFactory< PluginDatabaseManager, 'plugin' >; +// @public +export function dropDatabase( + dbConfig: Config, + ...databaseNames: string[] +): Promise; + +// @public +export type LegacyRootDatabaseService = { + forPlugin(pluginId: string): DatabaseService; +}; + // (No @packageDocumentation comment for this package) ``` diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index e4db289e33..f1eb7e0718 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-defaults", - "description": "Backend defaults used by Backstage backend apps", "version": "0.2.19-next.0", + "description": "Backend defaults used by Backstage backend apps", "backstage": { "role": "node-library" }, @@ -84,6 +84,7 @@ "dependencies": { "@backstage/backend-app-api": "workspace:^", "@backstage/backend-common": "workspace:^", + "@backstage/backend-dev-utils": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", "@backstage/config-loader": "workspace:^", @@ -91,12 +92,22 @@ "@backstage/plugin-events-node": "workspace:^", "@backstage/plugin-permission-node": "workspace:^", "@backstage/types": "workspace:^", + "@keyv/memcache": "^1.3.5", + "@keyv/redis": "^2.5.3", "@opentelemetry/api": "^1.3.0", + "better-sqlite3": "^9.0.0", "cron": "^3.0.0", + "fs-extra": "^11.2.0", + "keyv": "^4.5.2", "knex": "^3.0.0", "lodash": "^4.17.21", "luxon": "^3.0.0", + "mysql2": "^3.0.0", + "p-limit": "^3.1.0", + "pg": "^8.11.3", + "pg-connection-string": "^2.3.0", "uuid": "^9.0.0", + "yn": "^4.0.0", "zod": "^3.22.4" }, "devDependencies": { diff --git a/packages/backend-common/src/cache/CacheClient.test.ts b/packages/backend-defaults/src/entrypoints/cache/CacheClient.test.ts similarity index 100% rename from packages/backend-common/src/cache/CacheClient.test.ts rename to packages/backend-defaults/src/entrypoints/cache/CacheClient.test.ts diff --git a/packages/backend-common/src/cache/CacheClient.ts b/packages/backend-defaults/src/entrypoints/cache/CacheClient.ts similarity index 100% rename from packages/backend-common/src/cache/CacheClient.ts rename to packages/backend-defaults/src/entrypoints/cache/CacheClient.ts diff --git a/packages/backend-common/src/cache/CacheManager.integration.test.ts b/packages/backend-defaults/src/entrypoints/cache/CacheManager.integration.test.ts similarity index 76% rename from packages/backend-common/src/cache/CacheManager.integration.test.ts rename to packages/backend-defaults/src/entrypoints/cache/CacheManager.integration.test.ts index 7dc60a9051..a10b02a62e 100644 --- a/packages/backend-common/src/cache/CacheManager.integration.test.ts +++ b/packages/backend-defaults/src/entrypoints/cache/CacheManager.integration.test.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/config'; -import { CacheManager } from './CacheManager'; +import { mockServices } from '@backstage/backend-test-utils'; import KeyvRedis from '@keyv/redis'; +import { CacheManager } from './CacheManager'; // This test is in a separate file because the main test file uses other mocking // that might interfere with this one. @@ -24,24 +24,27 @@ import KeyvRedis from '@keyv/redis'; // Contrived code because it's hard to spy on a default export jest.mock('@keyv/redis', () => { const ActualKeyvRedis = jest.requireActual('@keyv/redis'); - return jest - .fn() - .mockImplementation((...args: any[]) => new ActualKeyvRedis(...args)); + return jest.fn((...args: any[]) => { + return new ActualKeyvRedis(...args); + }); }); describe('CacheManager integration', () => { describe('redis', () => { it('only creates one underlying connection', async () => { + const connection = + process.env.BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING; + if (!connection) { + return; + } + const manager = CacheManager.fromConfig( - new ConfigReader({ - backend: { - cache: { - store: 'redis', - // no actual connection errors will be seen since we don't interact with it - connection: 'redis://localhost:6379', - }, + mockServices.rootConfig({ + data: { + backend: { cache: { store: 'redis', connection } }, }, }), + { onError: e => expect(e).not.toBeDefined() }, ); manager.forPlugin('p1').getClient(); @@ -56,20 +59,18 @@ describe('CacheManager integration', () => { // TODO(freben): This could be frameworkified as TestCaches just like // TestDatabases, but that will have to come some other day const connection = - process.env.BACKSTAGE_TEST_CACHE_REDIS_CONNECTION_STRING; + process.env.BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING; if (!connection) { return; } const manager = CacheManager.fromConfig( - new ConfigReader({ - backend: { - cache: { - store: 'redis', - connection, - }, + mockServices.rootConfig({ + data: { + backend: { cache: { store: 'redis', connection } }, }, }), + { onError: e => expect(e).not.toBeDefined() }, ); const plugin1 = manager.forPlugin('p1').getClient(); diff --git a/packages/backend-common/src/cache/CacheManager.ts b/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts similarity index 82% rename from packages/backend-common/src/cache/CacheManager.ts rename to packages/backend-defaults/src/entrypoints/cache/CacheManager.ts index 8af742be8c..70f952ea46 100644 --- a/packages/backend-common/src/cache/CacheManager.ts +++ b/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts @@ -14,21 +14,25 @@ * limitations under the License. */ -import { Config } from '@backstage/config'; -import Keyv from 'keyv'; -import KeyvMemcache from '@keyv/memcache'; -import KeyvRedis from '@keyv/redis'; import { CacheService, CacheServiceOptions, LoggerService, } from '@backstage/backend-plugin-api'; -import { getRootLogger } from '../logging'; +import { Config } from '@backstage/config'; +import Keyv from 'keyv'; import { DefaultCacheClient } from './CacheClient'; -import { CacheManagerOptions, PluginCacheManager } from './types'; +import { CacheManagerOptions } from './types'; type StoreFactory = (pluginId: string, defaultTtl: number | undefined) => Keyv; +/* + * TODO(freben): This class intentionally inlines the CacheManagerOptions and + * PluginCacheManager types, to not break the api reports in backend-common + * which re-exports it. When backend-common is deprecated, we can stop inlining + * those types. + */ + /** * Implements a Cache Manager which will automatically create new cache clients * for plugins when requested. All requested cache clients are created with the @@ -47,7 +51,7 @@ export class CacheManager { memory: this.createMemoryStoreFactory(), }; - private readonly logger: LoggerService; + private readonly logger?: LoggerService; private readonly store: keyof CacheManager['storeFactories']; private readonly connection: string; private readonly useRedisSets: boolean; @@ -62,7 +66,18 @@ export class CacheManager { */ static fromConfig( config: Config, - options: CacheManagerOptions = {}, + options: { + /** + * An optional logger for use by the PluginCacheManager. + */ + logger?: LoggerService; + + /** + * An optional handler for connection errors emitted from the underlying data + * store. + */ + onError?: (err: Error) => void; + } = {}, ): CacheManager { // If no `backend.cache` config is provided, instantiate the CacheManager // with an in-memory cache client. @@ -72,27 +87,26 @@ export class CacheManager { config.getOptionalString('backend.cache.connection') || ''; const useRedisSets = config.getOptionalBoolean('backend.cache.useRedisSets') ?? true; - - // TODO: Make logger required and remove the default logger after moving this class to the `backstage-defaults`package - const logger = (options.logger || getRootLogger()).child({ + const logger = options.logger?.child({ type: 'cacheManager', }); return new CacheManager( store, connectionString, useRedisSets, - logger, options.onError, + logger, defaultTtl, ); } - private constructor( + /** @internal */ + constructor( store: string, connectionString: string, useRedisSets: boolean, - logger: LoggerService, errorHandler: CacheManagerOptions['onError'], + logger?: LoggerService, defaultTtl?: number, ) { if (!this.storeFactories.hasOwnProperty(store)) { @@ -112,7 +126,9 @@ export class CacheManager { * @param pluginId - The plugin that the cache manager should be created for. * Plugin names should be unique. */ - forPlugin(pluginId: string): PluginCacheManager { + forPlugin(pluginId: string): { + getClient(options?: CacheServiceOptions): CacheService; + } { return { getClient: (defaultOptions = {}) => { const clientFactory = (options: CacheServiceOptions) => { @@ -124,7 +140,7 @@ export class CacheManager { // Always provide an error handler to avoid stopping the process. concreteClient.on('error', (err: Error) => { // In all cases, just log the error. - this.logger.error('Failed to create cache client', err); + this.logger?.error('Failed to create cache client', err); // Invoke any custom error handler if provided. if (typeof this.errorHandler === 'function') { @@ -149,7 +165,8 @@ export class CacheManager { } private createRedisStoreFactory(): StoreFactory { - let store: KeyvRedis | undefined; + const KeyvRedis = require('@keyv/redis'); + let store: typeof KeyvRedis | undefined; return (pluginId, defaultTtl) => { if (!store) { store = new KeyvRedis(this.connection); @@ -164,7 +181,8 @@ export class CacheManager { } private createMemcacheStoreFactory(): StoreFactory { - let store: KeyvMemcache | undefined; + const KeyvMemcache = require('@keyv/memcache'); + let store: typeof KeyvMemcache | undefined; return (pluginId, defaultTtl) => { if (!store) { store = new KeyvMemcache(this.connection); @@ -187,12 +205,3 @@ export class CacheManager { }); } } - -/** @public */ -export function cacheToPluginCacheManager( - cache: CacheService, -): PluginCacheManager { - return { - getClient: (opts: CacheServiceOptions) => cache.withOptions(opts), - }; -} diff --git a/packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts b/packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts index d348c455d2..f60d770644 100644 --- a/packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { CacheManager } from '@backstage/backend-common'; import { coreServices, createServiceFactory, } from '@backstage/backend-plugin-api'; +import { CacheManager } from './CacheManager'; /** * @public @@ -28,9 +28,10 @@ export const cacheServiceFactory = createServiceFactory({ deps: { config: coreServices.rootConfig, plugin: coreServices.pluginMetadata, + logger: coreServices.rootLogger, }, - async createRootContext({ config }) { - return CacheManager.fromConfig(config); + async createRootContext({ config, logger }) { + return CacheManager.fromConfig(config, { logger }); }, async factory({ plugin }, manager) { return manager.forPlugin(plugin.getId()).getClient(); diff --git a/packages/backend-defaults/src/entrypoints/cache/index.ts b/packages/backend-defaults/src/entrypoints/cache/index.ts index f96ee77182..b16fa56bd2 100644 --- a/packages/backend-defaults/src/entrypoints/cache/index.ts +++ b/packages/backend-defaults/src/entrypoints/cache/index.ts @@ -15,3 +15,5 @@ */ export { cacheServiceFactory } from './cacheServiceFactory'; +export { CacheManager } from './CacheManager'; +export type { CacheManagerOptions, PluginCacheManager } from './types'; diff --git a/packages/backend-defaults/src/entrypoints/cache/types.ts b/packages/backend-defaults/src/entrypoints/cache/types.ts new file mode 100644 index 0000000000..ccb3ed5f6d --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/cache/types.ts @@ -0,0 +1,46 @@ +/* + * 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. + */ + +import { LoggerService } from '@backstage/backend-plugin-api'; +import { + CacheService, + CacheServiceOptions, +} from '@backstage/backend-plugin-api'; + +/** + * Options given when constructing a {@link CacheManager}. + * + * @public + */ +export type CacheManagerOptions = { + /** + * An optional logger for use by the PluginCacheManager. + */ + logger?: LoggerService; + + /** + * An optional handler for connection errors emitted from the underlying data + * store. + */ + onError?: (err: Error) => void; +}; + +/** + * @public + */ +export interface PluginCacheManager { + getClient(options?: CacheServiceOptions): CacheService; +} diff --git a/packages/backend-common/src/database/DatabaseManager.test.ts b/packages/backend-defaults/src/entrypoints/database/DatabaseManager.test.ts similarity index 100% rename from packages/backend-common/src/database/DatabaseManager.test.ts rename to packages/backend-defaults/src/entrypoints/database/DatabaseManager.test.ts diff --git a/packages/backend-common/src/database/DatabaseManager.ts b/packages/backend-defaults/src/entrypoints/database/DatabaseManager.ts similarity index 99% rename from packages/backend-common/src/database/DatabaseManager.ts rename to packages/backend-defaults/src/entrypoints/database/DatabaseManager.ts index dd36cc3b4a..f19a848056 100644 --- a/packages/backend-common/src/database/DatabaseManager.ts +++ b/packages/backend-defaults/src/entrypoints/database/DatabaseManager.ts @@ -41,7 +41,7 @@ function pluginPath(pluginId: string): string { * @public */ export type DatabaseManagerOptions = { - migrations?: PluginDatabaseManager['migrations']; + migrations?: DatabaseService['migrations']; logger?: LoggerService; }; diff --git a/packages/backend-common/src/database/connectors/defaultNameOverride.test.ts b/packages/backend-defaults/src/entrypoints/database/connectors/defaultNameOverride.test.ts similarity index 100% rename from packages/backend-common/src/database/connectors/defaultNameOverride.test.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/defaultNameOverride.test.ts diff --git a/packages/backend-common/src/database/connectors/defaultNameOverride.ts b/packages/backend-defaults/src/entrypoints/database/connectors/defaultNameOverride.ts similarity index 100% rename from packages/backend-common/src/database/connectors/defaultNameOverride.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/defaultNameOverride.ts diff --git a/packages/backend-common/src/database/connectors/defaultSchemaOverride.test.ts b/packages/backend-defaults/src/entrypoints/database/connectors/defaultSchemaOverride.test.ts similarity index 100% rename from packages/backend-common/src/database/connectors/defaultSchemaOverride.test.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/defaultSchemaOverride.test.ts diff --git a/packages/backend-common/src/database/connectors/defaultSchemaOverride.ts b/packages/backend-defaults/src/entrypoints/database/connectors/defaultSchemaOverride.ts similarity index 100% rename from packages/backend-common/src/database/connectors/defaultSchemaOverride.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/defaultSchemaOverride.ts diff --git a/packages/backend-common/src/database/connectors/index.ts b/packages/backend-defaults/src/entrypoints/database/connectors/index.ts similarity index 100% rename from packages/backend-common/src/database/connectors/index.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/index.ts diff --git a/packages/backend-common/src/database/connectors/mergeDatabaseConfig.test.ts b/packages/backend-defaults/src/entrypoints/database/connectors/mergeDatabaseConfig.test.ts similarity index 100% rename from packages/backend-common/src/database/connectors/mergeDatabaseConfig.test.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/mergeDatabaseConfig.test.ts diff --git a/packages/backend-common/src/database/connectors/mergeDatabaseConfig.ts b/packages/backend-defaults/src/entrypoints/database/connectors/mergeDatabaseConfig.ts similarity index 100% rename from packages/backend-common/src/database/connectors/mergeDatabaseConfig.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/mergeDatabaseConfig.ts diff --git a/packages/backend-common/src/database/connectors/mysql.test.ts b/packages/backend-defaults/src/entrypoints/database/connectors/mysql.test.ts similarity index 100% rename from packages/backend-common/src/database/connectors/mysql.test.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/mysql.test.ts diff --git a/packages/backend-common/src/database/connectors/mysql.ts b/packages/backend-defaults/src/entrypoints/database/connectors/mysql.ts similarity index 100% rename from packages/backend-common/src/database/connectors/mysql.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/mysql.ts diff --git a/packages/backend-common/src/database/connectors/postgres.test.ts b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.test.ts similarity index 100% rename from packages/backend-common/src/database/connectors/postgres.test.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/postgres.test.ts diff --git a/packages/backend-common/src/database/connectors/postgres.ts b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts similarity index 100% rename from packages/backend-common/src/database/connectors/postgres.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts diff --git a/packages/backend-common/src/database/connectors/sqlite3.test.ts b/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.test.ts similarity index 100% rename from packages/backend-common/src/database/connectors/sqlite3.test.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.test.ts diff --git a/packages/backend-common/src/database/connectors/sqlite3.ts b/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.ts similarity index 100% rename from packages/backend-common/src/database/connectors/sqlite3.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.ts diff --git a/packages/backend-defaults/src/entrypoints/database/index.ts b/packages/backend-defaults/src/entrypoints/database/index.ts index d676c8013e..7d6221b856 100644 --- a/packages/backend-defaults/src/entrypoints/database/index.ts +++ b/packages/backend-defaults/src/entrypoints/database/index.ts @@ -15,3 +15,9 @@ */ export { databaseServiceFactory } from './databaseServiceFactory'; +export { + DatabaseManager, + type DatabaseManagerOptions, + type LegacyRootDatabaseService, + dropDatabase, +} from './DatabaseManager'; diff --git a/packages/backend-defaults/src/entrypoints/database/types.ts b/packages/backend-defaults/src/entrypoints/database/types.ts new file mode 100644 index 0000000000..a9cceaa1f9 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/database/types.ts @@ -0,0 +1,100 @@ +/* + * 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. + */ + +import { + LifecycleService, + PluginMetadataService, +} from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; +import { Knex } from 'knex'; + +export type { DatabaseService as PluginDatabaseManager } from '@backstage/backend-plugin-api'; + +/** + * Manages an underlying Knex database driver. + */ +export interface DatabaseConnector { + /** + * Provides an instance of a knex database connector. + */ + createClient( + dbConfig: Config, + overrides?: Partial, + deps?: { + lifecycle: LifecycleService; + pluginMetadata: PluginMetadataService; + }, + ): Knex; + + /** + * Provides a partial knex config sufficient to override a database name. + */ + createNameOverride(name: string): Partial; + + /** + * Provides a partial knex config sufficient to override a PostgreSQL schema + * name within utilizing the `searchPath` knex configuration. + */ + createSchemaOverride?(name: string): Partial; + + /** + * Produces a knex connection config object representing a database connection + * string. + */ + parseConnectionString( + connectionString: string, + client?: string, + ): Knex.StaticConnectionConfig; + + /** + * Performs a side-effect to ensure database names passed in are present. + * + * Calling this function on databases which already exist should do nothing. + * Missing databases should be created if needed. + */ + ensureDatabaseExists?( + dbConfig: Config, + ...databases: Array + ): Promise; + + /** + * Performs a side-effect to ensure schema names passed in are present. + * + * Calling this function on schemas which already exist should do nothing. + * Missing schemas should be created if needed. + */ + ensureSchemaExists?( + dbConfig: Config, + ...schemas: Array + ): Promise; + + /** + * Deletes databases. + */ + dropDatabase?(dbConfig: Config, ...databases: Array): Promise; +} + +export interface Connector { + getClient( + pluginId: string, + deps?: { + lifecycle: LifecycleService; + pluginMetadata: PluginMetadataService; + }, + ): Promise; + + dropDatabase(...databaseNames: string[]): Promise; +} diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index edd462b528..d15399d279 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -14,7 +14,7 @@ import { AwsAlbResult as AwsAlbResult_2 } from '@backstage/plugin-auth-backend-m import { AzureEasyAuthResult } from '@backstage/plugin-auth-backend-module-azure-easyauth-provider'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { BackstageSignInResult } from '@backstage/plugin-auth-node'; -import { CacheClient } from '@backstage/backend-common'; +import { CacheService } from '@backstage/backend-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; import { ClientAuthResponse } from '@backstage/plugin-auth-node'; import { cloudflareAccessSignInResolvers } from '@backstage/plugin-auth-backend-module-cloudflare-access-provider'; @@ -452,7 +452,7 @@ export const providers: Readonly<{ signIn: { resolver: SignInResolver_2; }; - cache?: CacheClient | undefined; + cache?: CacheService | undefined; }) => AuthProviderFactory_2; resolvers: Readonly; }>; diff --git a/yarn.lock b/yarn.lock index d006daa23c..157f44465e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3430,6 +3430,7 @@ __metadata: dependencies: "@backstage/backend-app-api": "workspace:^" "@backstage/backend-common": "workspace:^" + "@backstage/backend-dev-utils": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" @@ -3439,13 +3440,23 @@ __metadata: "@backstage/plugin-events-node": "workspace:^" "@backstage/plugin-permission-node": "workspace:^" "@backstage/types": "workspace:^" + "@keyv/memcache": ^1.3.5 + "@keyv/redis": ^2.5.3 "@opentelemetry/api": ^1.3.0 + better-sqlite3: ^9.0.0 cron: ^3.0.0 + fs-extra: ^11.2.0 + keyv: ^4.5.2 knex: ^3.0.0 lodash: ^4.17.21 luxon: ^3.0.0 + mysql2: ^3.0.0 + p-limit: ^3.1.0 + pg: ^8.11.3 + pg-connection-string: ^2.3.0 uuid: ^9.0.0 wait-for-expect: ^3.0.2 + yn: ^4.0.0 zod: ^3.22.4 languageName: unknown linkType: soft