-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #195 from Icinga/migration-hook
Provide migration hook & migrate jobs config to database
- Loading branch information
Showing
9 changed files
with
378 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
<?php | ||
|
||
/* Icinga Web 2 X.509 Module | (c) 2023 Icinga GmbH | GPLv2 */ | ||
|
||
namespace Icinga\Module\X509\Clicommands; | ||
|
||
use DateTime; | ||
use Icinga\Application\Logger; | ||
use Icinga\Authentication\Auth; | ||
use Icinga\Module\X509\Command; | ||
use Icinga\Module\X509\Job; | ||
use Icinga\Repository\IniRepository; | ||
use Icinga\User; | ||
use Icinga\Util\Json; | ||
use ipl\Scheduler\Cron; | ||
use ipl\Sql\Connection; | ||
use ipl\Sql\Expression; | ||
use stdClass; | ||
|
||
use function ipl\Stdlib\get_php_type; | ||
|
||
class MigrateCommand extends Command | ||
{ | ||
/** | ||
* Migrate the jobs config rom INI to the database | ||
* | ||
* USAGE | ||
* | ||
* icingacli x509 migrate jobs --author=<name> | ||
* | ||
* OPTIONS | ||
* | ||
* --author=<name> | ||
* An Icinga Web 2 user used to mark as an author for all the migrated jobs. | ||
*/ | ||
public function jobsAction(): void | ||
{ | ||
/** @var string $author */ | ||
$author = $this->params->getRequired('author'); | ||
/** @var User $user */ | ||
$user = Auth::getInstance()->getUser(); | ||
$user->setUsername($author); | ||
|
||
$this->migrateJobs(); | ||
|
||
Logger::info('Successfully applied all pending migrations'); | ||
} | ||
|
||
protected function migrateJobs(): void | ||
{ | ||
$repo = new class () extends IniRepository { | ||
/** @var array<string, array<int, string>> */ | ||
protected $queryColumns = [ | ||
'jobs' => ['name', 'cidrs', 'ports', 'exclude_targets', 'schedule', 'frequencyType'] | ||
]; | ||
|
||
/** @var array<string, array<string, string>> */ | ||
protected $configs = [ | ||
'jobs' => [ | ||
'module' => 'x509', | ||
'name' => 'jobs', | ||
'keyColumn' => 'name' | ||
] | ||
]; | ||
}; | ||
|
||
$conn = $this->getDb(); | ||
$conn->transaction(function (Connection $conn) use ($repo) { | ||
/** @var User $user */ | ||
$user = Auth::getInstance()->getUser(); | ||
/** @var stdClass $data */ | ||
foreach ($repo->select() as $data) { | ||
$config = []; | ||
if (! isset($data->frequencyType) && ! empty($data->schedule)) { | ||
$frequency = new Cron($data->schedule); | ||
$config = [ | ||
'type' => get_php_type($frequency), | ||
'frequency' => Json::encode($frequency) | ||
]; | ||
} elseif (! empty($data->schedule)) { | ||
$config = [ | ||
'type' => $data->frequencyType, | ||
'frequency' => $data->schedule // Is already json encoded | ||
]; | ||
} | ||
|
||
$excludes = $data->exclude_targets; | ||
if (empty($excludes)) { | ||
$excludes = new Expression('NULL'); | ||
} | ||
|
||
$conn->insert('x509_job', [ | ||
'name' => $data->name, | ||
'author' => $user->getUsername(), | ||
'cidrs' => $data->cidrs, | ||
'ports' => $data->ports, | ||
'exclude_targets' => $excludes, | ||
'ctime' => (new DateTime())->getTimestamp() * 1000, | ||
'mtime' => (new DateTime())->getTimestamp() * 1000 | ||
]); | ||
|
||
$jobId = (int) $conn->lastInsertId(); | ||
if (! empty($config)) { | ||
$config['rescan'] = 'n'; | ||
$config['full_scan'] = 'n'; | ||
$config['since_last_scan'] = Job::DEFAULT_SINCE_LAST_SCAN; | ||
|
||
$conn->insert('x509_schedule', [ | ||
'job_id' => $jobId, | ||
'name' => $data->name . ' Schedule', | ||
'author' => $user->getUsername(), | ||
'config' => Json::encode($config), | ||
'ctime' => (new DateTime())->getTimestamp() * 1000, | ||
'mtime' => (new DateTime())->getTimestamp() * 1000, | ||
]); | ||
} | ||
} | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<?php | ||
|
||
/* Icinga Web 2 X.509 Module | (c) 2023 Icinga GmbH | GPLv2 */ | ||
|
||
namespace Icinga\Module\X509\Model; | ||
|
||
use DateTime; | ||
use ipl\Orm\Behavior\BoolCast; | ||
use ipl\Orm\Behavior\MillisecondTimestamp; | ||
use ipl\Orm\Behaviors; | ||
use ipl\Orm\Model; | ||
|
||
/** | ||
* A database model for x509 schema version table | ||
* | ||
* @property int $id Unique identifier of the database schema entries | ||
* @property string $version The current schema version of Icinga Web | ||
* @property DateTime $timestamp The insert/modify time of the schema entry | ||
* @property bool $success Whether the database migration of the current version was successful | ||
* @property ?string $reason The reason why the database migration has failed | ||
*/ | ||
class Schema extends Model | ||
{ | ||
public function getTableName(): string | ||
{ | ||
return 'x509_schema'; | ||
} | ||
|
||
public function getKeyName() | ||
{ | ||
return 'id'; | ||
} | ||
|
||
public function getColumns(): array | ||
{ | ||
return [ | ||
'version', | ||
'timestamp', | ||
'success', | ||
'reason' | ||
]; | ||
} | ||
|
||
public function createBehaviors(Behaviors $behaviors): void | ||
{ | ||
$behaviors->add(new BoolCast(['success'])); | ||
$behaviors->add(new MillisecondTimestamp(['timestamp'])); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
<?php | ||
|
||
/* Icinga Web 2 X.509 Module | (c) 2023 Icinga GmbH | GPLv2 */ | ||
|
||
namespace Icinga\Module\X509\ProvidedHook; | ||
|
||
use Icinga\Application\Hook\DbMigrationHook; | ||
use Icinga\Module\X509\Common\Database; | ||
use Icinga\Module\X509\Model\Schema; | ||
use ipl\Orm\Query; | ||
use ipl\Sql; | ||
use ipl\Sql\Adapter\Pgsql; | ||
|
||
class DbMigration extends DbMigrationHook | ||
{ | ||
use Database { | ||
getDb as private getX509Db; | ||
} | ||
|
||
public function getName(): string | ||
{ | ||
return $this->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) { | ||
if (static::getColumnType($conn, 'x509_certificate', 'valid_from') === 'bigint(20) unsigned') { | ||
$this->version = '1.0.0'; | ||
} else { | ||
$this->version = '1.1.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()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.