Add a "azuracast:setup:rollback" CLI command to roll back to the DB migration associated with a given stable release.
This commit is contained in:
parent
c205487620
commit
7706457322
|
@ -27,6 +27,7 @@ return function (App\Event\BuildConsoleCommands $event) {
|
|||
'azuracast:config:migrate' => Command\MigrateConfigCommand::class,
|
||||
'azuracast:setup:migrate' => Command\MigrateDbCommand::class,
|
||||
'azuracast:setup:fixtures' => Command\SetupFixturesCommand::class,
|
||||
'azuracast:setup:rollback' => Command\RollbackDbCommand::class,
|
||||
'azuracast:setup' => Command\SetupCommand::class,
|
||||
'azuracast:radio:restart' => Command\RestartRadioCommand::class,
|
||||
'azuracast:sync:nowplaying' => Command\Sync\NowPlayingCommand::class,
|
||||
|
|
|
@ -7,8 +7,11 @@ namespace App\Console\Command;
|
|||
use App\Console\Command\Traits\PassThruProcess;
|
||||
use App\Container\EntityManagerAwareTrait;
|
||||
use App\Container\EnvironmentAwareTrait;
|
||||
use App\Entity\StorageLocation;
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
|
||||
abstract class AbstractDatabaseCommand extends CommandAbstract
|
||||
{
|
||||
|
@ -91,4 +94,53 @@ abstract class AbstractDatabaseCommand extends CommandAbstract
|
|||
$commandEnvVars
|
||||
);
|
||||
}
|
||||
|
||||
protected function saveOrRestoreDatabase(
|
||||
SymfonyStyle $io,
|
||||
): string {
|
||||
$io->section(__('Backing up initial database state...'));
|
||||
|
||||
$tempDir = StorageLocation::DEFAULT_BACKUPS_PATH;
|
||||
$dbDumpPath = $tempDir . '/pre_migration_db.sql';
|
||||
|
||||
$fs = new Filesystem();
|
||||
|
||||
if ($fs->exists($dbDumpPath)) {
|
||||
$io->info([
|
||||
__('We detected a database restore file from a previous (possibly failed) migration.'),
|
||||
__('Attempting to restore that now...'),
|
||||
]);
|
||||
|
||||
$this->restoreDatabaseDump($io, $dbDumpPath);
|
||||
} else {
|
||||
$this->dumpDatabase($io, $dbDumpPath);
|
||||
}
|
||||
|
||||
return $dbDumpPath;
|
||||
}
|
||||
|
||||
protected function tryEmergencyRestore(
|
||||
SymfonyStyle $io,
|
||||
string $dbDumpPath
|
||||
): int {
|
||||
$io->section(__('Attempting to roll back to previous database state...'));
|
||||
|
||||
try {
|
||||
$this->restoreDatabaseDump($io, $dbDumpPath);
|
||||
|
||||
$io->warning([
|
||||
__('Your database was restored due to a failed migration.'),
|
||||
__('Please report this bug to our developers.'),
|
||||
]);
|
||||
return 0;
|
||||
} catch (Exception $e) {
|
||||
$io->error(
|
||||
sprintf(
|
||||
__('Restore failed: %s'),
|
||||
$e->getMessage()
|
||||
)
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Console\Command;
|
||||
|
||||
use App\Entity\StorageLocation;
|
||||
use Exception;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
|
@ -41,42 +40,11 @@ final class MigrateDbCommand extends AbstractDatabaseCommand
|
|||
}
|
||||
|
||||
// Back up current DB state.
|
||||
$io->section(__('Backing up initial database state...'));
|
||||
|
||||
$tempDir = StorageLocation::DEFAULT_BACKUPS_PATH;
|
||||
$dbDumpPath = $tempDir . '/pre_migration_db.sql';
|
||||
|
||||
$fs = new Filesystem();
|
||||
|
||||
if ($fs->exists($dbDumpPath)) {
|
||||
$io->info([
|
||||
__('We detected a database restore file from a previous (possibly failed) migration.'),
|
||||
__('Attempting to restore that now...'),
|
||||
]);
|
||||
|
||||
try {
|
||||
$this->restoreDatabaseDump($io, $dbDumpPath);
|
||||
} catch (Exception $e) {
|
||||
$io->error(
|
||||
sprintf(
|
||||
__('Restore failed: %s'),
|
||||
$e->getMessage()
|
||||
)
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$this->dumpDatabase($io, $dbDumpPath);
|
||||
} catch (Exception $e) {
|
||||
$io->error(
|
||||
sprintf(
|
||||
__('Initial backup failed: %s'),
|
||||
$e->getMessage()
|
||||
)
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
try {
|
||||
$dbDumpPath = $this->saveOrRestoreDatabase($io);
|
||||
} catch (Exception $e) {
|
||||
$io->error($e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Attempt DB migration.
|
||||
|
@ -99,27 +67,9 @@ final class MigrateDbCommand extends AbstractDatabaseCommand
|
|||
)
|
||||
);
|
||||
|
||||
$io->section(__('Attempting to roll back to previous database state...'));
|
||||
|
||||
try {
|
||||
$this->restoreDatabaseDump($io, $dbDumpPath);
|
||||
|
||||
$io->warning([
|
||||
__('Your database was restored due to a failed migration.'),
|
||||
__('Please report this bug to our developers.'),
|
||||
]);
|
||||
return 0;
|
||||
} catch (Exception $e) {
|
||||
$io->error(
|
||||
sprintf(
|
||||
__('Restore failed: %s'),
|
||||
$e->getMessage()
|
||||
)
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
return $this->tryEmergencyRestore($io, $dbDumpPath);
|
||||
} finally {
|
||||
$fs->remove($dbDumpPath);
|
||||
(new Filesystem())->remove($dbDumpPath);
|
||||
}
|
||||
|
||||
$io->newLine();
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Command;
|
||||
|
||||
use App\Container\ContainerAwareTrait;
|
||||
use App\Container\EnvironmentAwareTrait;
|
||||
use App\Entity\Attributes\StableMigration;
|
||||
use Exception;
|
||||
use FilesystemIterator;
|
||||
use InvalidArgumentException;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use ReflectionClass;
|
||||
use SplFileInfo;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Throwable;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'azuracast:setup:rollback',
|
||||
description: 'Roll back the database to the state associated with a certain stable release.',
|
||||
)]
|
||||
final class RollbackDbCommand extends AbstractDatabaseCommand
|
||||
{
|
||||
use ContainerAwareTrait;
|
||||
use EnvironmentAwareTrait;
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->addArgument('version', InputArgument::REQUIRED);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$io->title(__('Roll Back Database'));
|
||||
|
||||
// Pull migration corresponding to the stable version specified.
|
||||
try {
|
||||
$version = $input->getArgument('version');
|
||||
$migrationVersion = $this->findMigration($version);
|
||||
} catch (Throwable $e) {
|
||||
$io->error($e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->runCommand(
|
||||
$output,
|
||||
'migrations:sync-metadata-storage'
|
||||
);
|
||||
|
||||
// Attempt DB migration.
|
||||
$io->section(__('Running database migrations...'));
|
||||
|
||||
// Back up current DB state.
|
||||
try {
|
||||
$dbDumpPath = $this->saveOrRestoreDatabase($io);
|
||||
} catch (Exception $e) {
|
||||
$io->error($e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
$io->info($migrationVersion);
|
||||
|
||||
$this->runCommand(
|
||||
$output,
|
||||
'migrations:migrate',
|
||||
[
|
||||
'--allow-no-migration' => true,
|
||||
'version' => $migrationVersion,
|
||||
]
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
// Rollback to the DB dump from earlier.
|
||||
$io->error(
|
||||
sprintf(
|
||||
__('Database migration failed: %s'),
|
||||
$e->getMessage()
|
||||
)
|
||||
);
|
||||
|
||||
return $this->tryEmergencyRestore($io, $dbDumpPath);
|
||||
} finally {
|
||||
(new Filesystem())->remove($dbDumpPath);
|
||||
}
|
||||
|
||||
$io->newLine();
|
||||
$io->success(
|
||||
sprintf(
|
||||
__('Database rolled back to stable release version "%s".'),
|
||||
$version
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected function findMigration(string $version): string
|
||||
{
|
||||
$version = trim($version);
|
||||
|
||||
if (empty($version)) {
|
||||
throw new InvalidArgumentException('No version specified.');
|
||||
}
|
||||
|
||||
$versionParts = explode('.', $version);
|
||||
if (3 !== count($versionParts)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Invalid version specified. Version must be in the form of x.x.x, i.e. 0.19.0.'
|
||||
);
|
||||
}
|
||||
|
||||
$migrationsDir = $this->environment->getBaseDirectory() . '/src/Entity/Migration';
|
||||
$iterator = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($migrationsDir, FilesystemIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::LEAVES_ONLY
|
||||
);
|
||||
|
||||
$migrationFiles = [];
|
||||
|
||||
/** @var SplFileInfo $file */
|
||||
foreach ($iterator as $file) {
|
||||
// Skip dotfiles
|
||||
$fileName = $file->getBasename('.php');
|
||||
if ($fileName == $file->getBasename()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$className = 'App\\Entity\\Migration\\' . $fileName;
|
||||
$migrationFiles[$fileName] = $className;
|
||||
}
|
||||
|
||||
$migrationFiles = array_reverse($migrationFiles);
|
||||
|
||||
/** @var class-string $migrationClassName */
|
||||
foreach ($migrationFiles as $migrationClassName) {
|
||||
$reflClass = new ReflectionClass($migrationClassName);
|
||||
$reflAttrs = $reflClass->getAttributes(StableMigration::class);
|
||||
|
||||
foreach ($reflAttrs as $reflAttrInfo) {
|
||||
/** @var StableMigration $reflAttr */
|
||||
$reflAttr = $reflAttrInfo->newInstance();
|
||||
|
||||
if ($version === $reflAttr->version) {
|
||||
return $migrationClassName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException(
|
||||
'No migration found for the specified version. Make sure to specify a version after 0.17.0.'
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\Attributes;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* Mark a database migration as the last migration before a stable version was tagged.
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS | ATTRIBUTE::IS_REPEATABLE)]
|
||||
final class StableMigration
|
||||
{
|
||||
public function __construct(
|
||||
public string $version
|
||||
) {
|
||||
}
|
||||
}
|
|
@ -4,9 +4,11 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Entity\Migration;
|
||||
|
||||
use App\Entity\Attributes\StableMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
#[StableMigration('0.17.0')]
|
||||
final class Version20220605052847 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
|
|
|
@ -4,9 +4,11 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Entity\Migration;
|
||||
|
||||
use App\Entity\Attributes\StableMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
#[StableMigration('0.17.1')]
|
||||
final class Version20220611123923 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
|
|
|
@ -4,9 +4,11 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Entity\Migration;
|
||||
|
||||
use App\Entity\Attributes\StableMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
#[StableMigration('0.17.2')]
|
||||
final class Version20220626171758 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
|
|
|
@ -4,9 +4,11 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Entity\Migration;
|
||||
|
||||
use App\Entity\Attributes\StableMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
#[StableMigration('0.17.3')]
|
||||
final class Version20220724223136 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
|
|
|
@ -4,9 +4,11 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Entity\Migration;
|
||||
|
||||
use App\Entity\Attributes\StableMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
#[StableMigration('0.17.4')]
|
||||
final class Version20221008043751 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
|
|
|
@ -4,9 +4,11 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Entity\Migration;
|
||||
|
||||
use App\Entity\Attributes\StableMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
#[StableMigration('0.17.5')]
|
||||
final class Version20221102125558 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
|
|
|
@ -4,9 +4,11 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Entity\Migration;
|
||||
|
||||
use App\Entity\Attributes\StableMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
#[StableMigration('0.17.6')]
|
||||
final class Version20221110212745 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
|
|
|
@ -4,12 +4,11 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Entity\Migration;
|
||||
|
||||
use App\Entity\Attributes\StableMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
#[StableMigration('0.17.7')]
|
||||
final class Version20230102192652 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
|
|
|
@ -4,9 +4,14 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Entity\Migration;
|
||||
|
||||
use App\Entity\Attributes\StableMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
#[
|
||||
StableMigration('0.18.1'),
|
||||
StableMigration('0.18.0')
|
||||
]
|
||||
final class Version20230410210554 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
|
|
|
@ -4,10 +4,16 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Entity\Migration;
|
||||
|
||||
use App\Entity\Attributes\StableMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
use Exception;
|
||||
|
||||
#[
|
||||
StableMigration('0.18.5'),
|
||||
StableMigration('0.18.3'),
|
||||
StableMigration('0.18.2')
|
||||
]
|
||||
final class Version20230602095822 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
|
|
|
@ -4,9 +4,14 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Entity\Migration;
|
||||
|
||||
use App\Entity\Attributes\StableMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
#[
|
||||
StableMigration('0.19.1'),
|
||||
StableMigration('0.19.0')
|
||||
]
|
||||
final class Version20230803181406 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
|
|
Loading…
Reference in New Issue