From f1a0569506443ebee19d7512c06e1f3f7a427d39 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Wed, 27 Mar 2024 11:59:02 +0100 Subject: [PATCH] backend-app-api: add signing keys migration Signed-off-by: Vincenzo Scamporlino --- .../migrations/20240327104803_public_keys.js | 43 +++++++++++++++++++ packages/backend-app-api/package.json | 3 +- .../publicKeyStoreServiceFactory.ts | 27 +++++++++++- 3 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 packages/backend-app-api/migrations/20240327104803_public_keys.js diff --git a/packages/backend-app-api/migrations/20240327104803_public_keys.js b/packages/backend-app-api/migrations/20240327104803_public_keys.js new file mode 100644 index 0000000000..4498729eb9 --- /dev/null +++ b/packages/backend-app-api/migrations/20240327104803_public_keys.js @@ -0,0 +1,43 @@ +/* + * 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. + */ + +// @ts-check + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = async function up(knex) { + await knex.schema.createTable('signing_keys', table => { + table + .string('id') + .primary() + .notNullable() + .comment('The unique ID of a public key'); + + table.text('keys').notNullable().comment('JSON serialized public key'); + + table.timestamp('expires_at').notNullable(); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = async function down(knex) { + return knex.schema.dropTable('signing_keys'); +}; diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index c601bc1b04..d72b94281e 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -100,6 +100,7 @@ "files": [ "dist", "config.d.ts", - "alpha" + "alpha", + "migrations/**/*.{js,d.ts}" ] } diff --git a/packages/backend-app-api/src/services/implementations/publicKeyStore/publicKeyStoreServiceFactory.ts b/packages/backend-app-api/src/services/implementations/publicKeyStore/publicKeyStoreServiceFactory.ts index cb2c346376..58400bacf1 100644 --- a/packages/backend-app-api/src/services/implementations/publicKeyStore/publicKeyStoreServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/publicKeyStore/publicKeyStoreServiceFactory.ts @@ -15,6 +15,7 @@ */ import { + DatabaseService, PublicKeyStoreService, coreServices, createServiceFactory, @@ -22,6 +23,7 @@ import { import { DateTime } from 'luxon'; import { Knex } from 'knex'; import { JsonObject } from '@backstage/types'; +import { resolvePackagePath } from '@backstage/backend-common'; const TABLE = 'signing_keys'; @@ -33,7 +35,17 @@ type Row = { /** @internal */ export class DatabaseKeyStore implements PublicKeyStoreService { - constructor(private readonly client: Knex) {} + private constructor(private readonly client: Knex) {} + + static async create(options: { database: DatabaseService }) { + const { database } = options; + + const client = await database.getClient(); + if (!database.migrations?.skip) { + await applyDatabaseMigrations(client); + } + return new DatabaseKeyStore(client); + } async addKey(options: { id: string; @@ -72,7 +84,7 @@ export const publicKeyStoreServiceFactory = createServiceFactory({ database: coreServices.database, }, async factory({ database }) { - return new DatabaseKeyStore(await database.getClient()); + return DatabaseKeyStore.create({ database }); }, }); @@ -90,3 +102,14 @@ function parseDate(date: string | Date) { return parsedDate.toJSDate(); } + +export function applyDatabaseMigrations(knex: Knex): Promise { + const migrationsDir = resolvePackagePath( + '@backstage/backend-app-api', + 'migrations', + ); + + return knex.migrate.latest({ + directory: migrationsDir, + }); +}