Migrations: implement DB migration helpers

This commit is contained in:
Thomas Gelf 2016-02-08 22:26:13 +01:00
parent 81123ea78f
commit 84cbf522c9
84 changed files with 286 additions and 6 deletions

View File

@ -28,8 +28,7 @@ Create Icinga Director database
MySQL:
mysql -e "CREATE DATABASE director;
GRANT SELECT, INSERT, UPDATE, DELETE ON director.* TO director@localhost
IDENTIFIED BY 'some-password';"
GRANT ALL ON director.* TO director@localhost IDENTIFIED BY 'some-password';"
mysql director < schema/mysql.sql

View File

@ -0,0 +1,66 @@
<?php
namespace Icinga\Module\Director\Clicommands;
use Icinga\Module\Director\Cli\Command;
use Icinga\Module\Director\Db\Migrations;
/**
* Handle DB migrations
*
* This command retrieves information about unapplied database migration and
* helps applying them.
*/
class MigrationCommand extends Command
{
/**
* Check whether there are pending migrations
*
* This is mostly for automation, so one could create a Puppet manifest
* as follows:
*
* exec { 'Icinga Director DB migration':
* command => 'icingacli director migration run',
* onlyif => 'icingacli director migration pending',
* }
*
* Exit code 0 means that there are pending migrations, code 1 that there
* are no such. Use --verbose
*/
public function pendingAction()
{
if ($count = $this->migrations()->countPendingMigrations()) {
if ($this->isVerbose) {
if ($count === 1) {
echo "There is 1 pending migration\n";
} else {
printf("There are %d pending migrations\n", $count);
}
}
exit(0);
} else {
if ($this->isVerbose) {
echo "There are no pending migrations\n";
}
exit(1);
}
}
/**
* Run any pending migrations
*
* All pending migrations will be silently applied
*/
public function runAction()
{
$this->migrations()->applyPendingMigrations();
exit(0);
}
protected function migrations()
{
return new Migrations($this->db());
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace Icinga\Module\Director\Db;
use Exception;
use Icinga\Exception\IcingaException;
use Icinga\Module\Director\Db;
class Migration
{
/**
* @var string
*/
protected $sql;
/**
* @var int
*/
protected $version;
public function __construct($version, $sql)
{
$this->version = $version;
$this->sql = $sql;
}
public function apply(Db $connection)
{
$db = $connection->getDbAdapter();
$queries = preg_split('/[\n\s\t]*\;[\n\s\t]*/s', $this->sql, -1, PREG_SPLIT_NO_EMPTY);
if (empty($queries)) {
throw new IcingaException(
'Migration %d has no queries',
$this->version
);
}
try {
$db->beginTransaction();
foreach ($queries as $query) {
$db->exec($query);
}
$db->commit();
} catch (Exception $e) {
$db->rollback();
throw new IcingaException(
'Migration %d failed: %s',
$this->version,
$e->getMessage()
);
}
return $this;
}
}

View File

@ -0,0 +1,136 @@
<?php
namespace Icinga\Module\Director\Db;
use DirectoryIterator;
use Exception;
use Icinga\Module\Director\Db;
class Migrations
{
/**
* @var \Zend_Db_Adapter_Abstract
*/
protected $db;
/**
* @var Db
*/
protected $connection;
protected $migrationsDir;
public function __construct(Db $connection)
{
$this->connection = $connection;
$this->db = $connection->getDbAdapter();
}
public function getLastMigrationNumber()
{
try {
$query = $this->db->select()->from(
array('m' => 'director_schema_migration'),
array('schema_version' => 'MAX(schema_version)')
);
return (int) $this->db->fetchOne($query);
} catch (Exception $e) {
return 0;
}
}
public function hasPendingMigrations()
{
return $this->countPendingMigrations() > 0;
}
public function countPendingMigrations()
{
return count($this->listPendingMigrations());
}
public function getPendingMigrations()
{
$migrations = array();
foreach ($this->listPendingMigrations() as $version) {
$migrations[] = new Migration(
$version,
$this->loadMigrationFile($version)
);
}
return $migrations;
}
public function applyPendingMigrations()
{
foreach ($this->getPendingMigrations() as $migration) {
$migration->apply($this->connection);
}
return $this;
}
public function listPendingMigrations()
{
return $this->listMigrationsAfter($this->getLastMigrationNumber());
}
public function listAllMigrations()
{
$dir = $this->getMigrationsDir();
if (! is_readable($dir)) {
return array();
}
$versions = array();
foreach (new DirectoryIterator($this->getMigrationsDir()) as $file) {
if($file->isDot()) continue;
$filename = $file->getFilename();
if (preg_match('/^upgrade_(\d+)\.sql$/', $filename, $match)) {
$versions[] = $match[1];
}
}
sort($versions);
return $versions;
}
public function loadMigrationFile($version)
{
$filename = sprintf(
'%s/upgrade_%d.sql',
$this->getMigrationsDir(),
$version
);
return file_get_contents($filename);
}
protected function listMigrationsAfter($version)
{
$filtered = array();
foreach ($this->listAllMigrations() as $available) {
if ($available > $version) {
$filtered[] = $available;
}
}
return $filtered;
}
protected function getMigrationsDir()
{
if ($this->migrationsDir === null) {
$this->migrationsDir = dirname(dirname(dirname(__DIR__)))
. '/schema/'
. $this->connection->getDbType()
. '-migrations';
}
return $this->migrationsDir;
}
}

View File

@ -0,0 +1,12 @@
CREATE TABLE director_schema_migration (
schema_version SMALLINT UNSIGNED NOT NULL,
migration_time DATETIME NOT NULL,
PRIMARY KEY(schema_version)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE director_dbversion;
INSERT INTO director_schema_migration
SET migration_time = NOW(),
schema_version = 63;

View File

@ -16,10 +16,6 @@
SET sql_mode = 'STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,NO_ENGINE_SUBSTITUTION,PIPES_AS_CONCAT,ANSI_QUOTES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER';
CREATE TABLE director_dbversion (
schema_version INT(10) UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE director_activity_log (
id BIGINT(20) UNSIGNED AUTO_INCREMENT NOT NULL,
object_type VARCHAR(64) NOT NULL,
@ -149,6 +145,12 @@ CREATE TABLE director_datafield_setting (
ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE director_schema_migration (
schema_version SMALLINT UNSIGNED NOT NULL,
migration_time DATETIME NOT NULL,
PRIMARY KEY(schema_version)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE icinga_zone (
id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL,
parent_id INT(10) UNSIGNED DEFAULT NULL,
@ -1022,3 +1024,8 @@ CREATE TABLE import_row_modifier_setting (
setting_value TEXT DEFAULT NULL,
PRIMARY KEY (modifier_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO director_schema_migration
SET migration_time = NOW(),
schema_version = 63;