mirror of
https://github.com/shlinkio/shlink.git
synced 2025-03-14 04:00:57 +03:00
Merge pull request #442 from acelaya/feature/locked-migrations-command
Feature/locked migrations command
This commit is contained in:
commit
acf2961f9e
9 changed files with 139 additions and 7 deletions
|
@ -43,10 +43,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
|
|||
|
||||
* [#440](https://github.com/shlinkio/shlink/pull/440) Created `db:create` command, which improves how the shlink database is created, with these benefits:
|
||||
|
||||
* It sets up a lock which prevents the command to be run multiple times.
|
||||
* It sets up a lock which prevents the command to be run concurrently.
|
||||
* It checks of the database does not exist, and creates it in that case.
|
||||
* It checks if the database tables already exist, exiting gracefully in that case.
|
||||
|
||||
* [#442](https://github.com/shlinkio/shlink/pull/442) Created `db:migrate` command, which improves doctrine's migrations command by generating a lock, preventing it to be run concurrently.
|
||||
|
||||
#### Changed
|
||||
|
||||
* [#430](https://github.com/shlinkio/shlink/issues/430) Updated to [shlinkio/php-coding-standard](https://github.com/shlinkio/php-coding-standard) 1.2.2
|
||||
|
|
|
@ -270,6 +270,7 @@ Available commands:
|
|||
config:generate-secret [DEPRECATED] Generates a random secret string that can be used for JWT token encryption
|
||||
db
|
||||
db:create Creates the database needed for shlink to work. It will do nothing if the database already exists
|
||||
db:migrate Runs database migrations, which will ensure the shlink database is up to date.
|
||||
short-url
|
||||
short-url:delete [short-code:delete] Deletes a short URL
|
||||
short-url:generate [shortcode:generate|short-code:generate] Generates a short URL for provided long URL and returns it
|
||||
|
|
|
@ -40,9 +40,9 @@ return [
|
|||
'db_create_schema' => [
|
||||
'command' => 'bin/cli db:create',
|
||||
],
|
||||
// 'db_migrate' => [
|
||||
// 'command' => 'bin/cli db:migrate',
|
||||
// ],
|
||||
'db_migrate' => [
|
||||
'command' => 'bin/cli db:migrate',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
@ -30,6 +30,7 @@ return [
|
|||
Command\Tag\DeleteTagsCommand::NAME => Command\Tag\DeleteTagsCommand::class,
|
||||
|
||||
Command\Db\CreateDatabaseCommand::NAME => Command\Db\CreateDatabaseCommand::class,
|
||||
Command\Db\MigrateDatabaseCommand::NAME => Command\Db\MigrateDatabaseCommand::class,
|
||||
],
|
||||
],
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ return [
|
|||
Command\Tag\DeleteTagsCommand::class => ConfigAbstractFactory::class,
|
||||
|
||||
Command\Db\CreateDatabaseCommand::class => ConfigAbstractFactory::class,
|
||||
Command\Db\MigrateDatabaseCommand::class => ConfigAbstractFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
|
@ -88,6 +89,11 @@ return [
|
|||
Connection::class,
|
||||
NoDbNameConnectionFactory::SERVICE_NAME,
|
||||
],
|
||||
Command\Db\MigrateDatabaseCommand::class => [
|
||||
Locker::class,
|
||||
SymfonyCli\Helper\ProcessHelper::class,
|
||||
PhpExecutableFinder::class,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
@ -28,6 +28,6 @@ abstract class AbstractDatabaseCommand extends AbstractLockedCommand
|
|||
protected function runPhpCommand(OutputInterface $output, array $command): void
|
||||
{
|
||||
array_unshift($command, $this->phpBinary);
|
||||
$this->processHelper->run($output, $command);
|
||||
$this->processHelper->run($output, $command, null, null, $output->getVerbosity());
|
||||
}
|
||||
}
|
||||
|
|
40
module/CLI/src/Command/Db/MigrateDatabaseCommand.php
Normal file
40
module/CLI/src/Command/Db/MigrateDatabaseCommand.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\CLI\Command\Db;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Command\Util\LockedCommandConfig;
|
||||
use Shlinkio\Shlink\CLI\Util\ExitCodes;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class MigrateDatabaseCommand extends AbstractDatabaseCommand
|
||||
{
|
||||
public const NAME = 'db:migrate';
|
||||
public const DOCTRINE_HELPER_SCRIPT = 'vendor/doctrine/migrations/bin/doctrine-migrations.php';
|
||||
public const DOCTRINE_HELPER_COMMAND = 'migrations:migrate';
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setName(self::NAME)
|
||||
->setDescription('Runs database migrations, which will ensure the shlink database is up to date.');
|
||||
}
|
||||
|
||||
protected function lockedExecute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$io->writeln('<fg=blue>Migrating database...</>');
|
||||
$this->runPhpCommand($output, [self::DOCTRINE_HELPER_SCRIPT, self::DOCTRINE_HELPER_COMMAND]);
|
||||
$io->success('Database properly migrated!');
|
||||
|
||||
return ExitCodes::EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
protected function getLockConfig(): LockedCommandConfig
|
||||
{
|
||||
return new LockedCommandConfig($this->getName(), true);
|
||||
}
|
||||
}
|
|
@ -107,7 +107,7 @@ class CreateDatabaseCommandTest extends TestCase
|
|||
}
|
||||
|
||||
/** @test */
|
||||
public function tablesAreCreatedIfDatabaseIsEMpty(): void
|
||||
public function tablesAreCreatedIfDatabaseIsEmpty(): void
|
||||
{
|
||||
$shlinkDatabase = 'shlink_database';
|
||||
$getDatabase = $this->regularConn->getDatabase()->willReturn($shlinkDatabase);
|
||||
|
@ -119,7 +119,7 @@ class CreateDatabaseCommandTest extends TestCase
|
|||
'/usr/local/bin/php',
|
||||
CreateDatabaseCommand::DOCTRINE_HELPER_SCRIPT,
|
||||
CreateDatabaseCommand::DOCTRINE_HELPER_COMMAND,
|
||||
]);
|
||||
], Argument::cetera());
|
||||
|
||||
$this->commandTester->execute([]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
|
|
82
module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php
Normal file
82
module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\CLI\Command\Db;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\Db\MigrateDatabaseCommand;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Helper\ProcessHelper;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use Symfony\Component\Lock\Factory as Locker;
|
||||
use Symfony\Component\Lock\LockInterface;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
|
||||
class MigrateDatabaseCommandTest extends TestCase
|
||||
{
|
||||
/** @var CommandTester */
|
||||
private $commandTester;
|
||||
/** @var ObjectProphecy */
|
||||
private $processHelper;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$locker = $this->prophesize(Locker::class);
|
||||
$lock = $this->prophesize(LockInterface::class);
|
||||
$lock->acquire(Argument::any())->willReturn(true);
|
||||
$lock->release()->will(function () {
|
||||
});
|
||||
$locker->createLock(Argument::cetera())->willReturn($lock->reveal());
|
||||
|
||||
$phpExecutableFinder = $this->prophesize(PhpExecutableFinder::class);
|
||||
$phpExecutableFinder->find(false)->willReturn('/usr/local/bin/php');
|
||||
|
||||
$this->processHelper = $this->prophesize(ProcessHelper::class);
|
||||
|
||||
$command = new MigrateDatabaseCommand(
|
||||
$locker->reveal(),
|
||||
$this->processHelper->reveal(),
|
||||
$phpExecutableFinder->reveal()
|
||||
);
|
||||
$app = new Application();
|
||||
$app->add($command);
|
||||
|
||||
$this->commandTester = new CommandTester($command);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideVerbosities
|
||||
*/
|
||||
public function migrationsCommandIsRunWithProperVerbosity(int $verbosity): void
|
||||
{
|
||||
$runCommand = $this->processHelper->run(Argument::type(OutputInterface::class), [
|
||||
'/usr/local/bin/php',
|
||||
MigrateDatabaseCommand::DOCTRINE_HELPER_SCRIPT,
|
||||
MigrateDatabaseCommand::DOCTRINE_HELPER_COMMAND,
|
||||
], null, null, $verbosity);
|
||||
|
||||
$this->commandTester->execute([], [
|
||||
'verbosity' => $verbosity,
|
||||
]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
|
||||
if ($verbosity >= OutputInterface::VERBOSITY_VERBOSE) {
|
||||
$this->assertStringContainsString('Migrating database...', $output);
|
||||
$this->assertStringContainsString('Database properly migrated!', $output);
|
||||
}
|
||||
$runCommand->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
|
||||
public function provideVerbosities(): iterable
|
||||
{
|
||||
yield 'debug' => [OutputInterface::VERBOSITY_DEBUG];
|
||||
yield 'normal' => [OutputInterface::VERBOSITY_NORMAL];
|
||||
yield 'quiet' => [OutputInterface::VERBOSITY_QUIET];
|
||||
yield 'verbose' => [OutputInterface::VERBOSITY_VERBOSE];
|
||||
yield 'very verbose' => [OutputInterface::VERBOSITY_VERY_VERBOSE];
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue