Merge pull request #18921 from PeteLevineA/continued-mysql-support

patch: Add Continued MySQL Support
This commit is contained in:
Fredrik Adelöw
2023-08-22 13:23:03 +02:00
committed by GitHub
28 changed files with 172 additions and 66 deletions
+1
View File
@@ -92,6 +92,7 @@
"minimatch": "^5.0.0",
"minimist": "^1.2.5",
"morgan": "^1.10.0",
"mysql2": "^2.2.5",
"node-fetch": "^2.6.7",
"node-forge": "^1.3.1",
"pg": "^8.3.0",
@@ -425,6 +425,33 @@ describe('DatabaseManager', () => {
);
});
it('generates a database name override when prefix is not explicitly set for mysql', async () => {
const testManager = DatabaseManager.fromConfig(
new ConfigReader({
backend: {
database: {
client: 'mysql',
connection: {
host: 'localhost',
user: 'foo',
password: 'bar',
database: 'foodb',
},
},
},
}),
);
await testManager.forPlugin('testplugin').getClient();
const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1);
const [_baseConfig, overrides] = mockCalls[0];
expect(overrides).toHaveProperty(
'connection.database',
expect.stringContaining('backstage_plugin_'),
);
});
it('uses values from plugin connection string if top level client should be used', async () => {
const pluginId = 'stringoverride';
await manager.forPlugin(pluginId).getClient();
@@ -26,7 +26,7 @@ exports.up = async function up(knex) {
await knex.schema.createTable('backstage_backend_tasks__tasks', table => {
table.comment('Tasks used for scheduling work on multiple workers');
table
.text('id')
.string('id')
.primary()
.notNullable()
.comment('The unique ID of this particular task');
@@ -43,7 +43,7 @@ jest.setTimeout(60_000);
describe('migrations', () => {
const databases = TestDatabases.create({
ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
ids: ['POSTGRES_13', 'POSTGRES_9', 'MYSQL_8', 'SQLITE_3'],
});
it.each(databases.eachSupportedId())(
@@ -42,6 +42,7 @@ describe('PluginTaskSchedulerJanitor', () => {
'POSTGRES_13',
'POSTGRES_9',
'SQLITE_3',
'MYSQL_8',
],
});
@@ -25,7 +25,7 @@ jest.setTimeout(60_000);
describe('TaskScheduler', () => {
const logger = getVoidLogger();
const databases = TestDatabases.create({
ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3', 'MYSQL_8'],
});
async function createDatabase(
@@ -28,7 +28,7 @@ jest.setTimeout(60_000);
describe('TaskWorker', () => {
const logger = getVoidLogger();
const databases = TestDatabases.create({
ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3', 'MYSQL_8'],
});
beforeEach(() => {
+18 -9
View File
@@ -175,11 +175,15 @@ export class TaskWorker {
const time = new CronTime(settings.cadence)
.sendAt()
.minus({ seconds: 1 }) // immediately, if "* * * * * *"
.toUTC()
.toISO();
startAt = this.knex.client.config.client.includes('sqlite3')
? this.knex.raw('datetime(?)', [time])
: this.knex.raw(`?`, [time]);
.toUTC();
if (this.knex.client.config.client.includes('sqlite3')) {
startAt = this.knex.raw('datetime(?)', [time.toISO()]);
} else if (this.knex.client.config.client.includes('mysql')) {
startAt = this.knex.raw(`?`, [time.toSQL({ includeOffset: false })]);
} else {
startAt = this.knex.raw(`?`, [time.toISO()]);
}
} else {
startAt = this.knex.fn.now();
}
@@ -279,11 +283,16 @@ export class TaskWorker {
let nextRun: Knex.Raw;
if (isCron) {
const time = new CronTime(settings.cadence).sendAt().toUTC().toISO();
const time = new CronTime(settings.cadence).sendAt().toUTC();
this.logger.debug(`task: ${this.taskId} will next occur around ${time}`);
nextRun = this.knex.client.config.client.includes('sqlite3')
? this.knex.raw('datetime(?)', [time])
: this.knex.raw(`?`, [time]);
if (this.knex.client.config.client.includes('sqlite3')) {
nextRun = this.knex.raw('datetime(?)', [time.toISO()]);
} else if (this.knex.client.config.client.includes('mysql')) {
nextRun = this.knex.raw(`?`, [time.toSQL({ includeOffset: false })]);
} else {
nextRun = this.knex.raw(`?`, [time.toISO()]);
}
} else {
const dt = Duration.fromISO(settings.cadence).as('seconds');
this.logger.debug(
+1
View File
@@ -87,6 +87,7 @@
"express-prom-bundle": "^6.3.6",
"express-promise-router": "^4.1.0",
"luxon": "^3.0.0",
"mysql2": "^2.2.5",
"pg": "^8.3.0",
"pg-connection-string": "^2.3.0",
"prom-client": "^14.0.1",
+1
View File
@@ -35,6 +35,7 @@
"cross-fetch": "^3.1.5",
"fs-extra": "10.1.0",
"handlebars": "^4.7.3",
"mysql2": "^2.2.5",
"pgtools": "^1.0.0",
"puppeteer": "^17.0.0",
"tree-kill": "^1.2.2"
+37 -17
View File
@@ -31,7 +31,10 @@ import {
waitForExit,
print,
} from '../lib/helpers';
import mysql from 'mysql2/promise';
import pgtools from 'pgtools';
import { findPaths } from '@backstage/cli-common';
// eslint-disable-next-line no-restricted-syntax
@@ -66,9 +69,12 @@ export async function run() {
print('Starting the app');
await testAppServe(pluginId, appDir);
if (Boolean(process.env.POSTGRES_USER)) {
print('Testing the PostgreSQL backend startup');
await preCleanPostgres();
if (
Boolean(process.env.POSTGRES_USER) ||
Boolean(process.env.MYSQL_CONNECTION)
) {
print('Testing the database backend startup');
await preCleanDatabase();
const appConfig = path.resolve(appDir, 'app-config.yaml');
const productionConfig = path.resolve(appDir, 'app-config.production.yaml');
await testBackendStart(
@@ -79,7 +85,7 @@ export async function run() {
productionConfig,
);
}
print('Testing the SQLite backend startup');
print('Testing the Database backend startup');
await testBackendStart(appDir);
if (process.env.CI) {
@@ -427,24 +433,39 @@ async function testAppServe(pluginId: string, appDir: string) {
}
/** Drops PG databases */
async function dropDB(database: string) {
const config = {
host: process.env.POSTGRES_HOST,
port: process.env.POSTGRES_PORT,
user: process.env.POSTGRES_USER,
password: process.env.POSTGRES_PASSWORD,
};
async function dropDB(database: string, client: string) {
try {
await pgtools.dropdb(config, database);
if (client === 'postgres') {
const config = {
host: process.env.POSTGRES_HOST,
port: process.env.POSTGRES_PORT,
user: process.env.POSTGRES_USER,
password: process.env.POSTGRES_PASSWORD,
};
await pgtools.dropdb(config, database);
} else if (client === 'mysql') {
const connectionString = process.env.MYSQL_CONNECTION ?? '';
const connection = await mysql.createConnection(connectionString);
await connection.execute('DROP DATABASE ?', [database]);
}
} catch (_) {
/* do nothing*/
/* do nothing */
}
}
/** Clean remnants from prior e2e runs */
async function preCleanPostgres() {
async function preCleanDatabase() {
print('Dropping old DBs');
if (Boolean(process.env.POSTGRES_HOST)) {
await dropClientDatabases('postgres');
}
if (Boolean(process.env.MYSQL_CONNECTION)) {
await dropClientDatabases('mysql');
}
print('Dropped DBs');
}
async function dropClientDatabases(client: string) {
await Promise.all(
[
'catalog',
@@ -454,9 +475,8 @@ async function preCleanPostgres() {
'proxy',
'techdocs',
'search',
].map(name => dropDB(`backstage_plugin_${name}`)),
].map(name => dropDB(`backstage_plugin_${name}`, client)),
);
print('Created DBs');
}
/**