move over cache and database services
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/backend-defaults': minor
|
||||
'@backstage/backend-common': minor
|
||||
---
|
||||
|
||||
Deprecated and moved over core services to `@backstage/backend-defaults`
|
||||
@@ -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<CacheClient, 'plugin'>;
|
||||
export const cacheServiceFactory: () => ServiceFactory<CacheService, 'plugin'>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function createConfigSecretEnumerator(options: {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
+3
-8
@@ -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';
|
||||
|
||||
+29
@@ -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';
|
||||
+12
-25
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
HostDiscovery,
|
||||
SingleHostDiscovery,
|
||||
|
||||
@@ -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<CacheClient, 'plugin'>;
|
||||
export const cacheServiceFactory: () => ServiceFactory<CacheService, 'plugin'>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface PluginCacheManager {
|
||||
// (undocumented)
|
||||
getClient(options?: CacheServiceOptions): CacheService;
|
||||
}
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
|
||||
@@ -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<void>;
|
||||
|
||||
// @public
|
||||
export type LegacyRootDatabaseService = {
|
||||
forPlugin(pluginId: string): DatabaseService;
|
||||
};
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
|
||||
@@ -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": {
|
||||
|
||||
+20
-19
@@ -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();
|
||||
+36
-27
@@ -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),
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -15,3 +15,5 @@
|
||||
*/
|
||||
|
||||
export { cacheServiceFactory } from './cacheServiceFactory';
|
||||
export { CacheManager } from './CacheManager';
|
||||
export type { CacheManagerOptions, PluginCacheManager } from './types';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
+1
-1
@@ -41,7 +41,7 @@ function pluginPath(pluginId: string): string {
|
||||
* @public
|
||||
*/
|
||||
export type DatabaseManagerOptions = {
|
||||
migrations?: PluginDatabaseManager['migrations'];
|
||||
migrations?: DatabaseService['migrations'];
|
||||
logger?: LoggerService;
|
||||
};
|
||||
|
||||
@@ -15,3 +15,9 @@
|
||||
*/
|
||||
|
||||
export { databaseServiceFactory } from './databaseServiceFactory';
|
||||
export {
|
||||
DatabaseManager,
|
||||
type DatabaseManagerOptions,
|
||||
type LegacyRootDatabaseService,
|
||||
dropDatabase,
|
||||
} from './DatabaseManager';
|
||||
|
||||
@@ -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<Knex.Config>,
|
||||
deps?: {
|
||||
lifecycle: LifecycleService;
|
||||
pluginMetadata: PluginMetadataService;
|
||||
},
|
||||
): Knex;
|
||||
|
||||
/**
|
||||
* Provides a partial knex config sufficient to override a database name.
|
||||
*/
|
||||
createNameOverride(name: string): Partial<Knex.Config>;
|
||||
|
||||
/**
|
||||
* Provides a partial knex config sufficient to override a PostgreSQL schema
|
||||
* name within utilizing the `searchPath` knex configuration.
|
||||
*/
|
||||
createSchemaOverride?(name: string): Partial<Knex.Config>;
|
||||
|
||||
/**
|
||||
* 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<string>
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* 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<string>
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes databases.
|
||||
*/
|
||||
dropDatabase?(dbConfig: Config, ...databases: Array<string>): Promise<void>;
|
||||
}
|
||||
|
||||
export interface Connector {
|
||||
getClient(
|
||||
pluginId: string,
|
||||
deps?: {
|
||||
lifecycle: LifecycleService;
|
||||
pluginMetadata: PluginMetadataService;
|
||||
},
|
||||
): Promise<Knex>;
|
||||
|
||||
dropDatabase(...databaseNames: string[]): Promise<void>;
|
||||
}
|
||||
@@ -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<CloudflareAccessResult>;
|
||||
};
|
||||
cache?: CacheClient | undefined;
|
||||
cache?: CacheService | undefined;
|
||||
}) => AuthProviderFactory_2;
|
||||
resolvers: Readonly<cloudflareAccessSignInResolvers>;
|
||||
}>;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user