clean up and expose dropDatabase again
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
@@ -34,8 +34,6 @@ import { HostDiscovery as HostDiscovery_2 } from '@backstage/backend-app-api';
|
||||
import { HttpAuthService } from '@backstage/backend-plugin-api';
|
||||
import { IdentityService } from '@backstage/backend-plugin-api';
|
||||
import { isChildPath } from '@backstage/cli-common';
|
||||
import { Knex } from 'knex';
|
||||
import knexFactory from 'knex';
|
||||
import { KubeConfig } from '@kubernetes/client-node';
|
||||
import { LifecycleService } from '@backstage/backend-plugin-api';
|
||||
import { LoadConfigOptionsRemote } from '@backstage/config-loader';
|
||||
@@ -225,16 +223,6 @@ export interface ContainerRunner {
|
||||
runContainer(opts: RunContainerOptions): Promise<void>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export function createDatabaseClient(
|
||||
dbConfig: Config,
|
||||
overrides?: Partial<Knex.Config>,
|
||||
deps?: {
|
||||
lifecycle: LifecycleService;
|
||||
pluginMetadata: PluginMetadataService;
|
||||
},
|
||||
): knexFactory.Knex<any, any[]>;
|
||||
|
||||
// @public
|
||||
export function createLegacyAuthAdapters<
|
||||
TOptions extends {
|
||||
@@ -315,13 +303,7 @@ export class DockerContainerRunner implements ContainerRunner {
|
||||
// @public
|
||||
export function dropDatabase(
|
||||
dbConfig: Config,
|
||||
...databases: Array<string>
|
||||
): Promise<void>;
|
||||
|
||||
// @public
|
||||
export function ensureDatabaseExists(
|
||||
dbConfig: Config,
|
||||
...databases: Array<string>
|
||||
...databaseNames: string[]
|
||||
): Promise<void>;
|
||||
|
||||
// @public
|
||||
|
||||
@@ -13,44 +13,91 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { omit } from 'lodash';
|
||||
import path from 'path';
|
||||
import { DatabaseManager } from './DatabaseManager';
|
||||
import {
|
||||
createDatabaseClient,
|
||||
ensureDatabaseExists,
|
||||
ensureSchemaExists,
|
||||
} from './connection';
|
||||
import { DatabaseManagerImpl } from './DatabaseManager';
|
||||
import { Connector } from './types';
|
||||
|
||||
jest.mock('./connection', () => ({
|
||||
...jest.requireActual('./connection'),
|
||||
createDatabaseClient: jest.fn(),
|
||||
ensureDatabaseExists: jest.fn(),
|
||||
ensureSchemaExists: jest.fn(),
|
||||
}));
|
||||
describe('DatabaseManagerImpl', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('DatabaseManager', () => {
|
||||
// This is similar to the ts-jest `mocked` helper.
|
||||
const mocked = (f: Function) => f as jest.Mock;
|
||||
it('calls the right connector, only once per plugin id', async () => {
|
||||
const connector1 = {
|
||||
getClient: jest.fn(),
|
||||
dropDatabase: jest.fn(),
|
||||
} satisfies Connector;
|
||||
const connector2 = {
|
||||
getClient: jest.fn(),
|
||||
dropDatabase: jest.fn(),
|
||||
} satisfies Connector;
|
||||
|
||||
afterEach(() => jest.resetAllMocks());
|
||||
const impl = new DatabaseManagerImpl(
|
||||
new ConfigReader({
|
||||
client: 'pg',
|
||||
}),
|
||||
{
|
||||
pg: connector1,
|
||||
notpg: connector2,
|
||||
},
|
||||
);
|
||||
|
||||
describe('DatabaseManager.fromConfig', () => {
|
||||
const backendConfig = {
|
||||
backend: {
|
||||
database: {
|
||||
client: 'pg',
|
||||
connection: {
|
||||
host: 'localhost',
|
||||
user: 'foo',
|
||||
password: 'bar',
|
||||
database: 'foodb',
|
||||
await impl.forPlugin('plugin1').getClient();
|
||||
expect(connector1.getClient).toHaveBeenCalledTimes(1);
|
||||
expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', undefined);
|
||||
expect(connector2.getClient).toHaveBeenCalledTimes(0);
|
||||
|
||||
await impl.forPlugin('plugin1').getClient();
|
||||
expect(connector1.getClient).toHaveBeenCalledTimes(1);
|
||||
expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', undefined);
|
||||
expect(connector2.getClient).toHaveBeenCalledTimes(0);
|
||||
|
||||
await impl.forPlugin('plugin2').getClient();
|
||||
expect(connector1.getClient).toHaveBeenCalledTimes(2);
|
||||
expect(connector1.getClient).toHaveBeenLastCalledWith('plugin2', undefined);
|
||||
expect(connector2.getClient).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('respects per-plugin overridden connectors', async () => {
|
||||
const connector1 = {
|
||||
getClient: jest.fn(),
|
||||
dropDatabase: jest.fn(),
|
||||
} satisfies Connector;
|
||||
const connector2 = {
|
||||
getClient: jest.fn(),
|
||||
dropDatabase: jest.fn(),
|
||||
} satisfies Connector;
|
||||
|
||||
const impl = new DatabaseManagerImpl(
|
||||
new ConfigReader({
|
||||
client: 'pg',
|
||||
plugin: {
|
||||
plugin2: {
|
||||
client: 'mysql',
|
||||
},
|
||||
},
|
||||
}),
|
||||
{
|
||||
pg: connector1,
|
||||
mysql: connector2,
|
||||
},
|
||||
};
|
||||
);
|
||||
|
||||
await impl.forPlugin('plugin1').getClient();
|
||||
expect(connector1.getClient).toHaveBeenCalledTimes(1);
|
||||
expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', undefined);
|
||||
expect(connector2.getClient).toHaveBeenCalledTimes(0);
|
||||
|
||||
await impl.forPlugin('plugin2').getClient();
|
||||
expect(connector1.getClient).toHaveBeenCalledTimes(1);
|
||||
expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', undefined);
|
||||
expect(connector2.getClient).toHaveBeenCalledTimes(1);
|
||||
expect(connector2.getClient).toHaveBeenLastCalledWith('plugin2', undefined);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/no-commented-out-tests
|
||||
/*
|
||||
it('accesses the backend.database key', () => {
|
||||
const config = new ConfigReader(backendConfig);
|
||||
const getConfigSpy = jest.spyOn(config, 'getConfig');
|
||||
@@ -848,4 +895,5 @@ describe('DatabaseManager', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
*/
|
||||
});
|
||||
|
||||
@@ -54,44 +54,10 @@ export type LegacyRootDatabaseService = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Manages database connections for Backstage backend plugins.
|
||||
*
|
||||
* @public
|
||||
* @remarks
|
||||
*
|
||||
* The database manager allows the user to set connection and client settings on
|
||||
* a per pluginId basis by defining a database config block under
|
||||
* `plugin.<pluginId>` in addition to top level defaults. Optionally, a user may
|
||||
* set `prefix` which is used to prefix generated database names if config is
|
||||
* not provided.
|
||||
* Testable implementation class for {@link DatabaseManager} below.
|
||||
*/
|
||||
export class DatabaseManager implements LegacyRootDatabaseService {
|
||||
/**
|
||||
* Creates a {@link DatabaseManager} from `backend.database` config.
|
||||
*
|
||||
* @param config - The loaded application configuration.
|
||||
* @param options - An optional configuration object.
|
||||
*/
|
||||
static fromConfig(
|
||||
config: Config,
|
||||
options?: DatabaseManagerOptions,
|
||||
): DatabaseManager {
|
||||
const databaseConfig = config.getConfig('backend.database');
|
||||
const prefix = databaseConfig.getOptionalString('prefix');
|
||||
return new DatabaseManager(
|
||||
databaseConfig,
|
||||
{
|
||||
pg: new PgConnector(config, prefix),
|
||||
sqlite3: new Sqlite3Connector(config, prefix),
|
||||
'better-sqlite3': new Sqlite3Connector(config, prefix),
|
||||
mysql: new MysqlConnector(config, prefix),
|
||||
mysql2: new MysqlConnector(config, prefix),
|
||||
},
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
private constructor(
|
||||
export class DatabaseManagerImpl implements LegacyRootDatabaseService {
|
||||
constructor(
|
||||
private readonly config: Config,
|
||||
private readonly connectors: Record<string, Connector>,
|
||||
private readonly options?: DatabaseManagerOptions,
|
||||
@@ -204,3 +170,84 @@ export class DatabaseManager implements LegacyRootDatabaseService {
|
||||
}, 60 * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: This class looks odd but is kept around for API compatibility reasons
|
||||
/**
|
||||
* Manages database connections for Backstage backend plugins.
|
||||
*
|
||||
* @public
|
||||
* @remarks
|
||||
*
|
||||
* The database manager allows the user to set connection and client settings on
|
||||
* a per pluginId basis by defining a database config block under
|
||||
* `plugin.<pluginId>` in addition to top level defaults. Optionally, a user may
|
||||
* set `prefix` which is used to prefix generated database names if config is
|
||||
* not provided.
|
||||
*/
|
||||
export class DatabaseManager implements LegacyRootDatabaseService {
|
||||
/**
|
||||
* Creates a {@link DatabaseManager} from `backend.database` config.
|
||||
*
|
||||
* @param config - The loaded application configuration.
|
||||
* @param options - An optional configuration object.
|
||||
*/
|
||||
static fromConfig(
|
||||
config: Config,
|
||||
options?: DatabaseManagerOptions,
|
||||
): DatabaseManager {
|
||||
const databaseConfig = config.getConfig('backend.database');
|
||||
const prefix =
|
||||
databaseConfig.getOptionalString('prefix') || 'backstage_plugin_';
|
||||
return new DatabaseManager(
|
||||
new DatabaseManagerImpl(
|
||||
databaseConfig,
|
||||
{
|
||||
pg: new PgConnector(databaseConfig, prefix),
|
||||
sqlite3: new Sqlite3Connector(databaseConfig),
|
||||
'better-sqlite3': new Sqlite3Connector(databaseConfig),
|
||||
mysql: new MysqlConnector(databaseConfig, prefix),
|
||||
mysql2: new MysqlConnector(databaseConfig, prefix),
|
||||
},
|
||||
options,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private constructor(private readonly impl: DatabaseManagerImpl) {}
|
||||
|
||||
/**
|
||||
* Generates a PluginDatabaseManager for consumption by plugins.
|
||||
*
|
||||
* @param pluginId - The plugin that the database manager should be created for. Plugin names
|
||||
* should be unique as they are used to look up database config overrides under
|
||||
* `backend.database.plugin`.
|
||||
*/
|
||||
forPlugin(
|
||||
pluginId: string,
|
||||
deps?: {
|
||||
lifecycle: LifecycleService;
|
||||
pluginMetadata: PluginMetadataService;
|
||||
},
|
||||
): PluginDatabaseManager {
|
||||
return this.impl.forPlugin(pluginId, deps);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for deleting databases, only exists for backend-test-utils for now.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export async function dropDatabase(
|
||||
dbConfig: Config,
|
||||
...databaseNames: string[]
|
||||
): Promise<void> {
|
||||
const client = dbConfig.getString('client');
|
||||
const prefix = dbConfig.getOptionalString('prefix') || 'backstage_plugin_';
|
||||
|
||||
if (client === 'pg') {
|
||||
await new PgConnector(dbConfig, prefix).dropDatabase(...databaseNames);
|
||||
} else if (client === 'mysql' || client === 'mysql2') {
|
||||
await new MysqlConnector(dbConfig, prefix).dropDatabase(...databaseNames);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,218 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// * 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 { Config } from '@backstage/config';
|
||||
// import { JsonObject } from '@backstage/types';
|
||||
// import { InputError } from '@backstage/errors';
|
||||
// import knexFactory, { Knex } from 'knex';
|
||||
// import limiterFactory from 'p-limit';
|
||||
// import { mergeDatabaseConfig } from './config';
|
||||
// import { DatabaseConnector } from './types';
|
||||
|
||||
// import { mysqlConnector, pgConnector, sqlite3Connector } from './connectors';
|
||||
// import {
|
||||
// LifecycleService,
|
||||
// PluginMetadataService,
|
||||
// } from '@backstage/backend-plugin-api';
|
||||
|
||||
// type DatabaseClient =
|
||||
// | 'pg'
|
||||
// | 'better-sqlite3'
|
||||
// | 'sqlite3'
|
||||
// | 'mysql'
|
||||
// | 'mysql2'
|
||||
// | string;
|
||||
|
||||
// // This limits the number of concurrent CREATE DATABASE and CREATE SCHEMA
|
||||
// // commands, globally, to just one. This is overly defensive, and was added as
|
||||
// // an attempt to counteract the pool issues on recent node versions. See
|
||||
// // https://github.com/backstage/backstage/pull/19988
|
||||
// const ddlLimiter = limiterFactory(1);
|
||||
|
||||
// /**
|
||||
// * Mapping of client type to supported database connectors
|
||||
// *
|
||||
// * Database connectors can be aliased here, for example mysql2 uses
|
||||
// * the same connector as mysql.
|
||||
// */
|
||||
// const ConnectorMapping: Record<DatabaseClient, DatabaseConnector> = {
|
||||
// pg: pgConnector,
|
||||
// 'better-sqlite3': sqlite3Connector,
|
||||
// sqlite3: sqlite3Connector,
|
||||
// mysql: mysqlConnector,
|
||||
// mysql2: mysqlConnector,
|
||||
// };
|
||||
|
||||
// /**
|
||||
// * Creates a knex database connection
|
||||
// *
|
||||
// * @public
|
||||
// * @param dbConfig - The database config
|
||||
// * @param overrides - Additional options to merge with the config
|
||||
// */
|
||||
// export function createDatabaseClient(
|
||||
// dbConfig: Config,
|
||||
// overrides?: Partial<Knex.Config>,
|
||||
// deps?: {
|
||||
// lifecycle: LifecycleService;
|
||||
// pluginMetadata: PluginMetadataService;
|
||||
// },
|
||||
// ) {
|
||||
// const client: DatabaseClient = dbConfig.getString('client');
|
||||
|
||||
// return (
|
||||
// ConnectorMapping[client]?.createClient(dbConfig, overrides, deps) ??
|
||||
// knexFactory(mergeDatabaseConfig(dbConfig.get(), overrides))
|
||||
// );
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Ensures that the given databases all exist, creating them if they do not.
|
||||
// *
|
||||
// * @public
|
||||
// */
|
||||
// export async function ensureDatabaseExists(
|
||||
// dbConfig: Config,
|
||||
// ...databases: Array<string>
|
||||
// ): Promise<void> {
|
||||
// const client: DatabaseClient = dbConfig.getString('client');
|
||||
|
||||
// return await ddlLimiter(() =>
|
||||
// ConnectorMapping[client]?.ensureDatabaseExists?.(dbConfig, ...databases),
|
||||
// );
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Drops the given databases.
|
||||
// *
|
||||
// * @public
|
||||
// */
|
||||
// export async function dropDatabase(
|
||||
// dbConfig: Config,
|
||||
// ...databases: Array<string>
|
||||
// ): Promise<void> {
|
||||
// const client: DatabaseClient = dbConfig.getString('client');
|
||||
|
||||
// return await ddlLimiter(() =>
|
||||
// ConnectorMapping[client]?.dropDatabase?.(dbConfig, ...databases),
|
||||
// );
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Ensures that the given schemas all exist, creating them if they do not.
|
||||
// *
|
||||
// * @public
|
||||
// */
|
||||
// export async function ensureSchemaExists(
|
||||
// dbConfig: Config,
|
||||
// ...schemas: Array<string>
|
||||
// ): Promise<void> {
|
||||
// const client: DatabaseClient = dbConfig.getString('client');
|
||||
|
||||
// return await ddlLimiter(() =>
|
||||
// ConnectorMapping[client]?.ensureSchemaExists?.(dbConfig, ...schemas),
|
||||
// );
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Provides a `Knex.Config` object with the provided database name for a given
|
||||
// * client.
|
||||
// */
|
||||
// export function createNameOverride(
|
||||
// client: string,
|
||||
// name: string,
|
||||
// ): Partial<Knex.Config> {
|
||||
// try {
|
||||
// return ConnectorMapping[client].createNameOverride(name);
|
||||
// } catch (e) {
|
||||
// throw new InputError(
|
||||
// `Unable to create database name override for '${client}' connector`,
|
||||
// e,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Provides a `Knex.Config` object with the provided database schema for a given
|
||||
// * client. Currently only supported by `pg`.
|
||||
// */
|
||||
// export function createSchemaOverride(
|
||||
// client: string,
|
||||
// name: string,
|
||||
// ): Partial<Knex.Config | undefined> {
|
||||
// try {
|
||||
// return ConnectorMapping[client]?.createSchemaOverride?.(name);
|
||||
// } catch (e) {
|
||||
// throw new InputError(
|
||||
// `Unable to create database schema override for '${client}' connector`,
|
||||
// e,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Parses a connection string for a given client and provides a connection config.
|
||||
// */
|
||||
// export function parseConnectionString(
|
||||
// connectionString: string,
|
||||
// client?: string,
|
||||
// ): Knex.StaticConnectionConfig {
|
||||
// if (typeof client === 'undefined' || client === null) {
|
||||
// throw new InputError(
|
||||
// 'Database connection string client type auto-detection is not yet supported.',
|
||||
// );
|
||||
// }
|
||||
|
||||
// try {
|
||||
// return ConnectorMapping[client].parseConnectionString(connectionString);
|
||||
// } catch (e) {
|
||||
// throw new InputError(
|
||||
// `Unable to parse connection string for '${client}' connector`,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Normalizes a connection config or string into an object which can be passed
|
||||
// * to Knex.
|
||||
// */
|
||||
// export function normalizeConnection(
|
||||
// connection: Knex.StaticConnectionConfig | JsonObject | string | undefined,
|
||||
// client: string,
|
||||
// ): Partial<Knex.StaticConnectionConfig> {
|
||||
// if (typeof connection === 'undefined' || connection === null) {
|
||||
// return {};
|
||||
// }
|
||||
|
||||
// return typeof connection === 'string' || connection instanceof String
|
||||
// ? parseConnectionString(connection as string, client)
|
||||
// : connection;
|
||||
// }
|
||||
@@ -23,7 +23,6 @@ import { InputError } from '@backstage/errors';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import knexFactory, { Knex } from 'knex';
|
||||
import { merge, omit } from 'lodash';
|
||||
import path from 'path';
|
||||
import yn from 'yn';
|
||||
import { Connector, DatabaseConnector } from '../types';
|
||||
import defaultNameOverride from './defaultNameOverride';
|
||||
@@ -236,17 +235,6 @@ export const mysqlConnector: DatabaseConnector = Object.freeze({
|
||||
dropDatabase: dropMysqlDatabase,
|
||||
});
|
||||
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Provides a config lookup path for a plugin's config block.
|
||||
*/
|
||||
@@ -267,20 +255,6 @@ function normalizeConnection(
|
||||
: connection;
|
||||
}
|
||||
|
||||
function createSchemaOverride(
|
||||
client: string,
|
||||
name: string,
|
||||
): Partial<Knex.Config | undefined> {
|
||||
try {
|
||||
return mysqlConnector.createSchemaOverride?.(name);
|
||||
} catch (e) {
|
||||
throw new InputError(
|
||||
`Unable to create database schema override for '${client}' connector`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function createNameOverride(
|
||||
client: string,
|
||||
name: string,
|
||||
@@ -298,7 +272,7 @@ function createNameOverride(
|
||||
export class MysqlConnector implements Connector {
|
||||
constructor(
|
||||
private readonly config: Config,
|
||||
private readonly prefix: string = 'backstage_plugin_',
|
||||
private readonly prefix: string,
|
||||
) {}
|
||||
|
||||
async getClient(
|
||||
@@ -315,7 +289,7 @@ export class MysqlConnector implements Connector {
|
||||
const databaseName = this.getDatabaseName(pluginId);
|
||||
if (databaseName && this.getEnsureExistsConfig(pluginId)) {
|
||||
try {
|
||||
await mysqlConnector.ensureDatabaseExists(pluginConfig, databaseName);
|
||||
await mysqlConnector.ensureDatabaseExists!(pluginConfig, databaseName);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to connect to the database to make sure that '${databaseName}' exists, ${error}`,
|
||||
@@ -323,24 +297,16 @@ export class MysqlConnector implements Connector {
|
||||
}
|
||||
}
|
||||
|
||||
let schemaOverrides;
|
||||
if (this.getPluginDivisionModeConfig() === 'schema') {
|
||||
schemaOverrides = this.getSchemaOverrides(pluginId);
|
||||
if (this.getEnsureExistsConfig(pluginId)) {
|
||||
try {
|
||||
await mysqlConnector.ensureSchemaExists(pluginConfig, pluginId);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to connect to the database to make sure that schema for plugin '${pluginId}' exists, ${error}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
const pluginDivisionMode = this.getPluginDivisionModeConfig();
|
||||
if (pluginDivisionMode !== 'database') {
|
||||
throw new Error(
|
||||
`The MySQL driver does not suppoert plugin division mode '${pluginDivisionMode}'`,
|
||||
);
|
||||
}
|
||||
|
||||
const databaseClientOverrides = mergeDatabaseConfig(
|
||||
{},
|
||||
this.getDatabaseOverrides(pluginId),
|
||||
schemaOverrides,
|
||||
);
|
||||
|
||||
const client = mysqlConnector.createClient(
|
||||
@@ -352,29 +318,24 @@ export class MysqlConnector implements Connector {
|
||||
return client;
|
||||
}
|
||||
|
||||
async dropDatabase(...databaseNames: string[]): Promise<void> {
|
||||
return await dropMysqlDatabase(this.config, ...databaseNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the canonical database name for a given plugin.
|
||||
*
|
||||
* This method provides the effective database name which is determined using global
|
||||
* and plugin specific database config. If no explicit database name is configured
|
||||
* and `pluginDivisionMode` is not `schema`, this method will provide a generated name
|
||||
* which is the pluginId prefixed with 'backstage_plugin_'. If `pluginDivisionMode` is
|
||||
* `schema`, it will fallback to using the default database for the knex instance.
|
||||
* This method provides the effective database name which is determined using
|
||||
* global and plugin specific database config. If no explicit database name,
|
||||
* this method will provide a generated name which is the pluginId prefixed
|
||||
* with 'backstage_plugin_'.
|
||||
*
|
||||
* @param pluginId - Lookup the database name for given plugin
|
||||
* @returns String representing the plugin's database name
|
||||
*/
|
||||
private getDatabaseName(pluginId: string): string | undefined {
|
||||
const connection = this.getConnectionConfig(pluginId);
|
||||
|
||||
const databaseName = (connection as Knex.ConnectionConfig)?.database;
|
||||
|
||||
// `pluginDivisionMode` as `schema` should use overridden databaseName if supplied or fallback to default knex database
|
||||
if (this.getPluginDivisionModeConfig() === 'schema') {
|
||||
return databaseName;
|
||||
}
|
||||
|
||||
// all other supported databases should fallback to an auto-prefixed name
|
||||
return databaseName ?? `${this.prefix}${pluginId}`;
|
||||
}
|
||||
|
||||
@@ -500,17 +461,6 @@ export class MysqlConnector implements Connector {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a partial `Knex.Config` database schema override for a given
|
||||
* plugin.
|
||||
*
|
||||
* @param pluginId - Target plugin to get database schema override
|
||||
* @returns Partial `Knex.Config` with database schema override
|
||||
*/
|
||||
private getSchemaOverrides(pluginId: string): Knex.Config | undefined {
|
||||
return createSchemaOverride(this.getClientType(pluginId).client, pluginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a partial `Knex.Config`• database name override for a given plugin.
|
||||
*
|
||||
|
||||
@@ -23,7 +23,6 @@ import { ForwardedError, InputError } from '@backstage/errors';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import knexFactory, { Knex } from 'knex';
|
||||
import { merge, omit } from 'lodash';
|
||||
import path from 'path';
|
||||
import { Client } from 'pg';
|
||||
import { Connector, DatabaseConnector } from '../types';
|
||||
import defaultNameOverride from './defaultNameOverride';
|
||||
@@ -216,11 +215,15 @@ export async function dropPgDatabase(
|
||||
...databases: Array<string>
|
||||
) {
|
||||
const admin = createPgDatabaseClient(dbConfig);
|
||||
await Promise.all(
|
||||
databases.map(async database => {
|
||||
await admin.raw(`DROP DATABASE ??`, [database]);
|
||||
}),
|
||||
);
|
||||
try {
|
||||
await Promise.all(
|
||||
databases.map(async database => {
|
||||
await admin.raw(`DROP DATABASE ??`, [database]);
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
await admin.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export const pgConnector: DatabaseConnector = Object.freeze({
|
||||
@@ -233,17 +236,6 @@ export const pgConnector: DatabaseConnector = Object.freeze({
|
||||
dropDatabase: dropPgDatabase,
|
||||
});
|
||||
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Provides a config lookup path for a plugin's config block.
|
||||
*/
|
||||
@@ -295,7 +287,7 @@ function createNameOverride(
|
||||
export class PgConnector implements Connector {
|
||||
constructor(
|
||||
private readonly config: Config,
|
||||
private readonly prefix: string = 'backstage_plugin_',
|
||||
private readonly prefix: string,
|
||||
) {}
|
||||
|
||||
async getClient(
|
||||
@@ -312,7 +304,7 @@ export class PgConnector implements Connector {
|
||||
const databaseName = this.getDatabaseName(pluginId);
|
||||
if (databaseName && this.getEnsureExistsConfig(pluginId)) {
|
||||
try {
|
||||
await pgConnector.ensureDatabaseExists(pluginConfig, databaseName);
|
||||
await pgConnector.ensureDatabaseExists!(pluginConfig, databaseName);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to connect to the database to make sure that '${databaseName}' exists, ${error}`,
|
||||
@@ -325,7 +317,7 @@ export class PgConnector implements Connector {
|
||||
schemaOverrides = this.getSchemaOverrides(pluginId);
|
||||
if (this.getEnsureExistsConfig(pluginId)) {
|
||||
try {
|
||||
await pgConnector.ensureSchemaExists(pluginConfig, pluginId);
|
||||
await pgConnector.ensureSchemaExists!(pluginConfig, pluginId);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to connect to the database to make sure that schema for plugin '${pluginId}' exists, ${error}`,
|
||||
@@ -349,6 +341,10 @@ export class PgConnector implements Connector {
|
||||
return client;
|
||||
}
|
||||
|
||||
async dropDatabase(...databaseNames: string[]): Promise<void> {
|
||||
return await dropPgDatabase(this.config, ...databaseNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the canonical database name for a given plugin.
|
||||
*
|
||||
|
||||
@@ -168,17 +168,6 @@ export const sqliteConnector: DatabaseConnector = Object.freeze({
|
||||
parseConnectionString: parseSqliteConnectionString,
|
||||
});
|
||||
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Provides a config lookup path for a plugin's config block.
|
||||
*/
|
||||
@@ -199,20 +188,6 @@ function normalizeConnection(
|
||||
: connection;
|
||||
}
|
||||
|
||||
function createSchemaOverride(
|
||||
client: string,
|
||||
name: string,
|
||||
): Partial<Knex.Config | undefined> {
|
||||
try {
|
||||
return sqliteConnector.createSchemaOverride?.(name);
|
||||
} catch (e) {
|
||||
throw new InputError(
|
||||
`Unable to create database schema override for '${client}' connector`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function createNameOverride(
|
||||
client: string,
|
||||
name: string,
|
||||
@@ -228,10 +203,7 @@ function createNameOverride(
|
||||
}
|
||||
|
||||
export class Sqlite3Connector implements Connector {
|
||||
constructor(
|
||||
private readonly config: Config,
|
||||
private readonly prefix: string = 'backstage_plugin_',
|
||||
) {}
|
||||
constructor(private readonly config: Config) {}
|
||||
|
||||
async getClient(
|
||||
pluginId: string,
|
||||
@@ -244,35 +216,16 @@ export class Sqlite3Connector implements Connector {
|
||||
this.getConfigForPlugin(pluginId) as JsonObject,
|
||||
);
|
||||
|
||||
const databaseName = this.getDatabaseName(pluginId);
|
||||
if (databaseName && this.getEnsureExistsConfig(pluginId)) {
|
||||
try {
|
||||
await sqliteConnector.ensureDatabaseExists(pluginConfig, databaseName);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to connect to the database to make sure that '${databaseName}' exists, ${error}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let schemaOverrides;
|
||||
if (this.getPluginDivisionModeConfig() === 'schema') {
|
||||
schemaOverrides = this.getSchemaOverrides(pluginId);
|
||||
if (this.getEnsureExistsConfig(pluginId)) {
|
||||
try {
|
||||
await sqliteConnector.ensureSchemaExists(pluginConfig, pluginId);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to connect to the database to make sure that schema for plugin '${pluginId}' exists, ${error}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
const pluginDivisionMode = this.getPluginDivisionModeConfig();
|
||||
if (pluginDivisionMode !== 'database') {
|
||||
throw new Error(
|
||||
`The SQLite driver does not suppoert plugin division mode '${pluginDivisionMode}'`,
|
||||
);
|
||||
}
|
||||
|
||||
const databaseClientOverrides = mergeDatabaseConfig(
|
||||
{},
|
||||
this.getDatabaseOverrides(pluginId),
|
||||
schemaOverrides,
|
||||
);
|
||||
|
||||
const client = sqliteConnector.createClient(
|
||||
@@ -284,6 +237,10 @@ export class Sqlite3Connector implements Connector {
|
||||
return client;
|
||||
}
|
||||
|
||||
async dropDatabase(..._databaseNames: string[]): Promise<void> {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the canonical database name for a given plugin.
|
||||
*
|
||||
@@ -299,30 +256,18 @@ export class Sqlite3Connector implements Connector {
|
||||
private getDatabaseName(pluginId: string): string | undefined {
|
||||
const connection = this.getConnectionConfig(pluginId);
|
||||
|
||||
if (this.getClientType(pluginId).client.includes('sqlite3')) {
|
||||
const sqliteFilename: string | undefined = (
|
||||
connection as Knex.Sqlite3ConnectionConfig
|
||||
).filename;
|
||||
const sqliteFilename: string | undefined = (
|
||||
connection as Knex.Sqlite3ConnectionConfig
|
||||
).filename;
|
||||
|
||||
if (sqliteFilename === ':memory:') {
|
||||
return sqliteFilename;
|
||||
}
|
||||
|
||||
const sqliteDirectory =
|
||||
(connection as { directory?: string }).directory ?? '.';
|
||||
|
||||
return path.join(sqliteDirectory, sqliteFilename ?? `${pluginId}.sqlite`);
|
||||
if (sqliteFilename === ':memory:') {
|
||||
return sqliteFilename;
|
||||
}
|
||||
|
||||
const databaseName = (connection as Knex.ConnectionConfig)?.database;
|
||||
const sqliteDirectory =
|
||||
(connection as { directory?: string }).directory ?? '.';
|
||||
|
||||
// `pluginDivisionMode` as `schema` should use overridden databaseName if supplied or fallback to default knex database
|
||||
if (this.getPluginDivisionModeConfig() === 'schema') {
|
||||
return databaseName;
|
||||
}
|
||||
|
||||
// all other supported databases should fallback to an auto-prefixed name
|
||||
return databaseName ?? `${this.prefix}${pluginId}`;
|
||||
return path.join(sqliteDirectory, sqliteFilename ?? `${pluginId}.sqlite`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -377,14 +322,6 @@ export class Sqlite3Connector implements Connector {
|
||||
return merge(baseConfig, pluginConfig);
|
||||
}
|
||||
|
||||
private getEnsureExistsConfig(pluginId: string): boolean {
|
||||
const baseConfig = this.config.getOptionalBoolean('ensureExists') ?? true;
|
||||
return (
|
||||
this.config.getOptionalBoolean(`${pluginPath(pluginId)}.ensureExists`) ??
|
||||
baseConfig
|
||||
);
|
||||
}
|
||||
|
||||
private getPluginDivisionModeConfig(): string {
|
||||
return this.config.getOptionalString('pluginDivisionMode') ?? 'database';
|
||||
}
|
||||
@@ -459,17 +396,6 @@ export class Sqlite3Connector implements Connector {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a partial `Knex.Config` database schema override for a given
|
||||
* plugin.
|
||||
*
|
||||
* @param pluginId - Target plugin to get database schema override
|
||||
* @returns Partial `Knex.Config` with database schema override
|
||||
*/
|
||||
private getSchemaOverrides(pluginId: string): Knex.Config | undefined {
|
||||
return createSchemaOverride(this.getClientType(pluginId).client, pluginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a partial `Knex.Config`• database name override for a given plugin.
|
||||
*
|
||||
|
||||
@@ -14,7 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './DatabaseManager';
|
||||
export { DatabaseManager, dropDatabase } from './DatabaseManager';
|
||||
export type {
|
||||
DatabaseManagerOptions,
|
||||
LegacyRootDatabaseService,
|
||||
} from './DatabaseManager';
|
||||
|
||||
export type { PluginDatabaseManager } from './types';
|
||||
export { isDatabaseConflictError } from './util';
|
||||
|
||||
@@ -95,4 +95,6 @@ export interface Connector {
|
||||
pluginMetadata: PluginMetadataService;
|
||||
},
|
||||
): Promise<Knex>;
|
||||
|
||||
dropDatabase(...databaseNames: string[]): Promise<void>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user