From 815cc3a56b7085baa4c137f2a82b1b38e2101c3f Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 19 Jul 2023 13:03:22 +0200 Subject: [PATCH] Provide migration hook & migrate jobs config to database --- library/X509/Model/Schema.php | 39 +++++++++ library/X509/ProvidedHook/DbMigration.php | 98 +++++++++++++++++++++++ run.php | 4 + schema/mysql-upgrades/1.3.0.sql | 30 ++++--- schema/mysql.schema.sql | 14 ++++ schema/pgsql-upgrades/1.3.0.sql | 30 ++++--- schema/pgsql.schema.sql | 14 ++++ 7 files changed, 211 insertions(+), 18 deletions(-) create mode 100644 library/X509/Model/Schema.php create mode 100644 library/X509/ProvidedHook/DbMigration.php diff --git a/library/X509/Model/Schema.php b/library/X509/Model/Schema.php new file mode 100644 index 00000000..c505e543 --- /dev/null +++ b/library/X509/Model/Schema.php @@ -0,0 +1,39 @@ +add(new BoolCast(['success'])); + $behaviors->add(new MillisecondTimestamp(['timestamp'])); + } +} diff --git a/library/X509/ProvidedHook/DbMigration.php b/library/X509/ProvidedHook/DbMigration.php new file mode 100644 index 00000000..cc422811 --- /dev/null +++ b/library/X509/ProvidedHook/DbMigration.php @@ -0,0 +1,98 @@ +translate('Icinga Certificate Monitoring'); + } + + public function providedDescriptions(): array + { + return [ + '1.0.0' => $this->translate( + 'Adjusts the database type of several columns and changes some composed primary keys.' + ), + '1.1.0' => $this->translate( + 'Changes the composed x509_target index and x509_certificate valid from/to types to bigint.' + ), + '1.2.0' => $this->translate( + 'Changes all timestamp columns to bigint and adjusts enum types of "yes/no" to "n/y".' + ), + '1.3.0' => $this->translate( + 'Introduces the required tables to store jobs and job schedules in the database.' + ) + ]; + } + + public function getVersion(): string + { + if ($this->version === null) { + $conn = $this->getDb(); + $schema = $this->getSchemaQuery() + ->columns(['version', 'success']) + ->orderBy('id', SORT_DESC) + ->limit(2); + + if (static::tableExists($conn, $schema->getModel()->getTableName())) { + /** @var Schema $version */ + foreach ($schema as $version) { + if ($version->success) { + $this->version = $version->version; + + break; + } + } + + if (! $this->version) { + // Schema version table exist, but the user has probably deleted the entry! + $this->version = '1.3.0'; + } + } elseif ( + $this->getDb()->getAdapter() instanceof Pgsql + || static::getColumnType($conn, 'x509_certificate', 'ctime') === 'bigint(20) unsigned' + ) { + // We modified a bunch of timestamp columns to bigint in x509 version 1.2.0. + // We have also added Postgres support with x509 version 1.2 and never had an upgrade scripts until now. + $this->version = '1.2.0'; + } elseif (static::getColumnType($conn, 'x509_certificate_subject_alt_name', 'hash') !== null) { + // We know for sure that x509 version 1.0 has been applied, though not whether x509 version 1.1.0 + // did too. Therefore, we have modified the 1.1.0 upgrade script to run multiple times without any + // errors so that we can use 1.0 as the last (migrated) version. + $this->version = '1.0.0'; + } else { + // X509 version 1.0 was the first release of this module, but due to some reason it also contains + // an upgrade script and adds `hash` column. However, if this column doesn't exist yet, we need + // to use the lowest possible release value as the initial (last migrated) version. + $this->version = '0.0.0'; + } + } + + return $this->version; + } + + public function getDb(): Sql\Connection + { + return $this->getX509Db(); + } + + protected function getSchemaQuery(): Query + { + return Schema::on($this->getDb()); + } +} diff --git a/run.php b/run.php index 3c041a2c..b1977a9e 100644 --- a/run.php +++ b/run.php @@ -2,5 +2,9 @@ // Icinga Web 2 X.509 Module | (c) 2018 Icinga GmbH | GPLv2 +/** @var \Icinga\Application\Modules\Module $this */ + +$this->provideHook('DbMigration', '\\Icinga\\Module\\X509\\ProvidedHook\\DbMigration'); + $this->provideHook('director/ImportSource', '\\Icinga\\Module\\X509\\ProvidedHook\\HostsImportSource'); $this->provideHook('director/ImportSource', '\\Icinga\\Module\\X509\\ProvidedHook\\ServicesImportSource'); diff --git a/schema/mysql-upgrades/1.3.0.sql b/schema/mysql-upgrades/1.3.0.sql index c886b0bd..f31e8bd2 100644 --- a/schema/mysql-upgrades/1.3.0.sql +++ b/schema/mysql-upgrades/1.3.0.sql @@ -1,4 +1,4 @@ -CREATE TABLE IF NOT EXISTS x509_job ( +CREATE TABLE x509_job ( id int(10) unsigned NOT NULL AUTO_INCREMENT, name varchar(255) NOT NULL COLLATE utf8mb4_unicode_ci, author varchar(255) NOT NULL COLLATE utf8mb4_unicode_ci, @@ -12,7 +12,7 @@ CREATE TABLE IF NOT EXISTS x509_job ( UNIQUE (name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -CREATE TABLE IF NOT EXISTS x509_schedule ( +CREATE TABLE x509_schedule ( id int(10) unsigned NOT NULL AUTO_INCREMENT, job_id int(10) unsigned NOT NULL, name varchar(255) NOT NULL COLLATE utf8mb4_unicode_ci, @@ -27,13 +27,25 @@ CREATE TABLE IF NOT EXISTS x509_schedule ( DELETE FROM x509_job_run; ALTER TABLE x509_job_run - ADD COLUMN IF NOT EXISTS job_id int(10) unsigned NOT NULL AFTER id, - ADD COLUMN IF NOT EXISTS schedule_id int(10) unsigned DEFAULT NULL AFTER job_id, - DROP COLUMN IF EXISTS `name`, - DROP COLUMN IF EXISTS ctime, - DROP COLUMN IF EXISTS mtime, - DROP CONSTRAINT IF EXISTS fk_x509_job_run_job, - DROP CONSTRAINT IF EXISTS fk_x509_job_run_schedule; + ADD COLUMN job_id int(10) unsigned NOT NULL AFTER id, + ADD COLUMN schedule_id int(10) unsigned DEFAULT NULL AFTER job_id, + DROP COLUMN `name`, + DROP COLUMN ctime, + DROP COLUMN mtime; ALTER TABLE x509_job_run ADD CONSTRAINT fk_x509_job_run_job FOREIGN KEY (job_id) REFERENCES x509_job (id) ON DELETE CASCADE, ADD CONSTRAINT fk_x509_job_run_schedule FOREIGN KEY (schedule_id) REFERENCES x509_schedule (id) ON DELETE CASCADE; + +CREATE TABLE x509_schema ( + id int unsigned NOT NULL AUTO_INCREMENT, + version varchar(64) NOT NULL, + timestamp bigint unsigned NOT NULL, + success enum ('n', 'y') DEFAULT NULL, + reason text DEFAULT NULL, + + PRIMARY KEY (id), + CONSTRAINT idx_x509_schema_version UNIQUE (version) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC; + +INSERT INTO x509_schema (version, timestamp, success, reason) + VALUES ('1.3.0', UNIX_TIMESTAMP() * 1000, 'y', NULL); diff --git a/schema/mysql.schema.sql b/schema/mysql.schema.sql index 8892b357..7e56746f 100644 --- a/schema/mysql.schema.sql +++ b/schema/mysql.schema.sql @@ -120,3 +120,17 @@ CREATE TABLE x509_job_run ( CONSTRAINT fk_x509_job_run_job FOREIGN KEY (job_id) REFERENCES x509_job (id) ON DELETE CASCADE, CONSTRAINT fk_x509_job_run_schedule FOREIGN KEY (schedule_id) REFERENCES x509_schedule (id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE x509_schema ( + id int unsigned NOT NULL AUTO_INCREMENT, + version varchar(64) NOT NULL, + timestamp bigint unsigned NOT NULL, + success enum ('n', 'y') DEFAULT NULL, + reason text DEFAULT NULL, + + PRIMARY KEY (id), + CONSTRAINT idx_x509_schema_version UNIQUE (version) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC; + +INSERT INTO x509_schema (version, timestamp, success) + VALUES ('1.3.0', UNIX_TIMESTAMP() * 1000, 'y'); diff --git a/schema/pgsql-upgrades/1.3.0.sql b/schema/pgsql-upgrades/1.3.0.sql index be8fcfaa..7e1f43a6 100644 --- a/schema/pgsql-upgrades/1.3.0.sql +++ b/schema/pgsql-upgrades/1.3.0.sql @@ -1,4 +1,4 @@ -CREATE TABLE IF NOT EXISTS x509_job ( +CREATE TABLE x509_job ( id serial PRIMARY KEY, name varchar(255) NOT NULL, author varchar(255) NOT NULL, @@ -11,7 +11,7 @@ CREATE TABLE IF NOT EXISTS x509_job ( UNIQUE (name) ); -CREATE TABLE IF NOT EXISTS x509_schedule ( +CREATE TABLE x509_schedule ( id serial PRIMARY KEY, job_id int NOT NULL, name varchar(255) NOT NULL, @@ -25,13 +25,25 @@ CREATE TABLE IF NOT EXISTS x509_schedule ( DELETE FROM x509_job_run; ALTER TABLE x509_job_run - ADD COLUMN IF NOT EXISTS job_id int NOT NULL, - ADD COLUMN IF NOT EXISTS schedule_id int DEFAULT NULL, - DROP COLUMN IF EXISTS name, - DROP COLUMN IF EXISTS ctime, - DROP COLUMN IF EXISTS mtime, - DROP CONSTRAINT IF EXISTS fk_x509_job_run_job, - DROP CONSTRAINT IF EXISTS fk_x509_job_run_schedule; + ADD COLUMN job_id int NOT NULL, + ADD COLUMN schedule_id int DEFAULT NULL, + DROP COLUMN name, + DROP COLUMN ctime, + DROP COLUMN mtime; ALTER TABLE x509_job_run ADD CONSTRAINT fk_x509_job_run_job FOREIGN KEY (job_id) REFERENCES x509_job (id) ON DELETE CASCADE, ADD CONSTRAINT fk_x509_job_run_schedule FOREIGN KEY (schedule_id) REFERENCES x509_schedule (id) ON DELETE CASCADE; + +CREATE TABLE x509_schema ( + id serial, + version varchar(64) NOT NULL, + timestamp bigint NOT NULL, + success boolenum DEFAULT NULL, + reason text DEFAULT NULL, + + CONSTRAINT pk_x509_schema PRIMARY KEY (id), + CONSTRAINT idx_x509_schema_version UNIQUE (version) +); + +INSERT INTO x509_schema (version, timestamp, success, reason) + VALUES ('1.3.0', UNIX_TIMESTAMP() * 1000, 'y', NULL); diff --git a/schema/pgsql.schema.sql b/schema/pgsql.schema.sql index cfaa3793..1d93ef3d 100644 --- a/schema/pgsql.schema.sql +++ b/schema/pgsql.schema.sql @@ -146,3 +146,17 @@ CREATE TABLE x509_job_run ( CONSTRAINT fk_x509_job_run_job FOREIGN KEY (job_id) REFERENCES x509_job (id) ON DELETE CASCADE, CONSTRAINT fk_x509_job_run_schedule FOREIGN KEY (schedule_id) REFERENCES x509_schedule (id) ON DELETE CASCADE ); + +CREATE TABLE x509_schema ( + id serial, + version varchar(64) NOT NULL, + timestamp bigint NOT NULL, + success boolenum DEFAULT NULL, + reason text DEFAULT NULL, + + CONSTRAINT pk_x509_schema PRIMARY KEY (id), + CONSTRAINT idx_x509_schema_version UNIQUE (version) +); + +INSERT INTO x509_schema (version, timestamp, success) + VALUES ('1.3.0', UNIX_TIMESTAMP() * 1000, 'y');