Skip to content

Commit

Permalink
Merge pull request #1804 from acelaya-forks/feature/release-3.6.1
Browse files Browse the repository at this point in the history
Feature/release 3.6.1
  • Loading branch information
acelaya authored Jun 4, 2023
2 parents b6792d3 + 575e6bf commit 56d299a
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 58 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org).

## [3.6.1] - 2023-05-04
### Added
* *Nothing*

### Changed
* *Nothing*

### Deprecated
* *Nothing*

### Removed
* *Nothing*

### Fixed
* [#1413](https://github.com/shlinkio/shlink/issues/1413) Fix error when creating initial DB in Postgres in a cluster where a default `postgres` db does not exist or the credentials do not grant permissions to connect.
* [#1803](https://github.com/shlinkio/shlink/issues/1803) Fix default RoadRunner port when not using docker image.


## [3.6.0] - 2023-05-24
### Added
* [#1148](https://github.com/shlinkio/shlink/issues/1148) Add support to delete short URL visits.
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
"phpstan/phpstan-phpunit": "^1.3",
"phpstan/phpstan-symfony": "^1.2",
"phpunit/php-code-coverage": "^10.0",
"phpunit/phpunit": "^10.1",
"phpunit/phpunit": "~10.1.0",
"roave/security-advisories": "dev-master",
"shlinkio/php-coding-standard": "~2.3.0",
"shlinkio/shlink-test-utils": "^3.6",
Expand Down
6 changes: 3 additions & 3 deletions config/roadrunner/.rr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ server:
command: 'php -dopcache.enable_cli=1 -dopcache.validate_timestamps=0 ../../bin/roadrunner-worker.php'

http:
address: '0.0.0.0:${PORT}'
address: '0.0.0.0:${PORT:-8080}'
middleware: ['static']
static:
dir: '../../public'
forbid: ['.php', '.htaccess']
pool:
num_workers: ${WEB_WORKER_NUM}
num_workers: ${WEB_WORKER_NUM:-0}

jobs:
timeout: 300 # 5 minutes
pool:
num_workers: ${TASK_WORKER_NUM}
num_workers: ${TASK_WORKER_NUM:-0}
consume: ['shlink']
pipelines:
shlink:
Expand Down
8 changes: 0 additions & 8 deletions docker/docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,6 @@ if [ "${ENABLE_PERIODIC_VISIT_LOCATE}" = "true" ] && [ "${SHLINK_USER_ID}" = "ro
/usr/sbin/crond &
fi

# RoadRunner config needs these to have been set, so falling back to default values if not set yet
if [ "$SHLINK_RUNTIME" == 'rr' ]; then
export PORT="${PORT:-"8080"}"
# Default to 0 so that RoadRunner decides the number of workers based on the amount of logical CPUs
export WEB_WORKER_NUM="${WEB_WORKER_NUM:-"0"}"
export TASK_WORKER_NUM="${TASK_WORKER_NUM:-"0"}"
fi

if [ "$SHLINK_RUNTIME" == 'openswoole' ]; then
# When restarting the container, openswoole might think it is already in execution
# This forces the app to be started every second until the exit code is 0
Expand Down
51 changes: 25 additions & 26 deletions module/CLI/src/Command/Db/CreateDatabaseCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Process\PhpExecutableFinder;
use Throwable;

use function Functional\contains;
use function Functional\map;
Expand Down Expand Up @@ -53,9 +54,7 @@ protected function lockedExecute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);

$this->checkDbExists();

if ($this->schemaExists()) {
if ($this->databaseTablesExist()) {
$io->success('Database already exists. Run "db:migrate" command to make sure it is up to date.');
return ExitCode::EXIT_SUCCESS;
}
Expand All @@ -68,35 +67,35 @@ protected function lockedExecute(InputInterface $input, OutputInterface $output)
return ExitCode::EXIT_SUCCESS;
}

private function checkDbExists(): void
{
if ($this->regularConn->getDriver()->getDatabasePlatform() instanceof SqlitePlatform) {
return;
}

// In order to create the new database, we have to use a connection where the dbname was not set.
// Otherwise, it will fail to connect and will not be able to create the new database
$schemaManager = $this->noDbNameConn->createSchemaManager();
$databases = $schemaManager->listDatabases();
// We cannot use getDatabase() to get the database name here, because then the driver will try to connect, and
// it does not exist yet. We need to read from the raw params instead.
$shlinkDatabase = $this->regularConn->getParams()['dbname'] ?? null;

if ($shlinkDatabase !== null && ! contains($databases, $shlinkDatabase)) {
$schemaManager->createDatabase($shlinkDatabase);
}
}

private function schemaExists(): bool
private function databaseTablesExist(): bool
{
$schemaManager = $this->regularConn->createSchemaManager();
$existingTables = $schemaManager->listTableNames();

$existingTables = $this->ensureDatabaseExistsAndGetTables();
$allMetadata = $this->em->getMetadataFactory()->getAllMetadata();
$shlinkTables = map($allMetadata, static fn (ClassMetadata $metadata) => $metadata->getTableName());

// If at least one of the shlink tables exist, we will consider the database exists somehow.
// Any other inconsistency will be taken care of by the migrations.
return some($shlinkTables, static fn (string $shlinkTable) => contains($existingTables, $shlinkTable));
}

private function ensureDatabaseExistsAndGetTables(): array
{
if ($this->regularConn->getDriver()->getDatabasePlatform() instanceof SqlitePlatform) {
return [];
}

try {
// Trying to list tables requires opening a connection to configured database.
// If it fails, it means it does not exist yet.
return $this->regularConn->createSchemaManager()->listTableNames();
} catch (Throwable) {
// We cannot use getDatabase() to get the database name here, because then the driver will try to connect.
// Instead, we read from the raw params.
$shlinkDatabase = $this->regularConn->getParams()['dbname'] ?? '';
// Create the database using a connection where the dbname was not set.
$this->noDbNameConn->createSchemaManager()->createDatabase($shlinkDatabase);

return [];
}
}
}
33 changes: 13 additions & 20 deletions module/CLI/test/Command/Db/CreateDatabaseCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\Persistence\Mapping\ClassMetadataFactory;
use Exception;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\MockObject\MockObject;
Expand Down Expand Up @@ -69,17 +70,14 @@ protected function setUp(): void
#[Test]
public function successMessageIsPrintedIfDatabaseAlreadyExists(): void
{
$shlinkDatabase = 'shlink_database';
$this->regularConn->expects($this->once())->method('getParams')->willReturn(['dbname' => $shlinkDatabase]);
$this->regularConn->expects($this->never())->method('getParams');
$this->driver->method('getDatabasePlatform')->willReturn($this->createMock(AbstractPlatform::class));

$metadataMock = $this->createMock(ClassMetadata::class);
$metadataMock->expects($this->once())->method('getTableName')->willReturn('foo_table');
$this->metadataFactory->method('getAllMetadata')->willReturn([$metadataMock]);
$this->schemaManager->expects($this->once())->method('listDatabases')->willReturn(
['foo', $shlinkDatabase, 'bar'],
);
$this->schemaManager->expects($this->never())->method('createDatabase');
$this->schemaManager->expects($this->once())->method('listTableNames')->willReturn(['foo_table', 'bar_table']);
$this->driver->method('getDatabasePlatform')->willReturn($this->createMock(AbstractPlatform::class));

$this->commandTester->execute([]);
$output = $this->commandTester->getDisplay();
Expand All @@ -90,30 +88,26 @@ public function successMessageIsPrintedIfDatabaseAlreadyExists(): void
#[Test]
public function databaseIsCreatedIfItDoesNotExist(): void
{
$this->driver->method('getDatabasePlatform')->willReturn($this->createMock(AbstractPlatform::class));

$shlinkDatabase = 'shlink_database';
$this->regularConn->expects($this->once())->method('getParams')->willReturn(['dbname' => $shlinkDatabase]);
$this->metadataFactory->method('getAllMetadata')->willReturn([]);
$this->schemaManager->expects($this->once())->method('listDatabases')->willReturn(['foo', 'bar']);
$this->schemaManager->expects($this->once())->method('createDatabase')->with($shlinkDatabase);
$this->schemaManager->expects($this->once())->method('listTableNames')->willReturn(
['foo_table', 'bar_table'],
);
$this->driver->method('getDatabasePlatform')->willReturn($this->createMock(AbstractPlatform::class));
$this->schemaManager->expects($this->once())->method('listTableNames')->willThrowException(new Exception(''));

$this->commandTester->execute([]);
}

#[Test, DataProvider('provideEmptyDatabase')]
public function tablesAreCreatedIfDatabaseIsEmpty(array $tables): void
{
$shlinkDatabase = 'shlink_database';
$this->regularConn->expects($this->once())->method('getParams')->willReturn(['dbname' => $shlinkDatabase]);
$this->regularConn->expects($this->never())->method('getParams');
$this->driver->method('getDatabasePlatform')->willReturn($this->createMock(AbstractPlatform::class));

$metadata = $this->createMock(ClassMetadata::class);
$metadata->method('getTableName')->willReturn('shlink_table');
$this->metadataFactory->method('getAllMetadata')->willReturn([$metadata]);
$this->schemaManager->expects($this->once())->method('listDatabases')->willReturn(
['foo', $shlinkDatabase, 'bar'],
);
$this->schemaManager->expects($this->never())->method('createDatabase');
$this->schemaManager->expects($this->once())->method('listTableNames')->willReturn($tables);
$this->processHelper->expects($this->once())->method('run')->with($this->isInstanceOf(OutputInterface::class), [
Expand All @@ -122,7 +116,6 @@ public function tablesAreCreatedIfDatabaseIsEmpty(array $tables): void
CreateDatabaseCommand::DOCTRINE_CREATE_SCHEMA_COMMAND,
'--no-interaction',
]);
$this->driver->method('getDatabasePlatform')->willReturn($this->createMock(AbstractPlatform::class));

$this->commandTester->execute([]);
$output = $this->commandTester->getDisplay();
Expand All @@ -141,12 +134,12 @@ public static function provideEmptyDatabase(): iterable
public function databaseCheckIsSkippedForSqlite(): void
{
$this->driver->method('getDatabasePlatform')->willReturn($this->createMock(SqlitePlatform::class));

$this->regularConn->expects($this->never())->method('getParams');
$this->metadataFactory->expects($this->once())->method('getAllMetadata')->willReturn([]);
$this->schemaManager->expects($this->never())->method('listDatabases');
$this->schemaManager->expects($this->never())->method('createDatabase');
$this->schemaManager->expects($this->once())->method('listTableNames')->willReturn(['foo_table', 'bar_table']);
$this->schemaManager->expects($this->never())->method('listTableNames');

$this->metadataFactory->expects($this->once())->method('getAllMetadata')->willReturn([]);

$this->commandTester->execute([]);
}
Expand Down

0 comments on commit 56d299a

Please sign in to comment.