Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 12 additions & 38 deletions src/Command/InitCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,63 +4,37 @@

namespace Shlinkio\Shlink\Installer\Command;

use Shlinkio\Shlink\Installer\Command\Model\InitOption;
use Shlinkio\Shlink\Installer\Model\CLIOption;
use Shlinkio\Shlink\Installer\Model\ShlinkInitConfig;
use Shlinkio\Shlink\Installer\Command\Model\InitCommandInput;
use Shlinkio\Shlink\Installer\Service\InstallationCommandsRunnerInterface;
use Shlinkio\Shlink\Installer\Util\InstallationCommand;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Attribute\MapInput;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

use function array_reduce;

#[AsCommand(
name: InitCommand::NAME,
description: 'Initializes external dependencies required for Shlink to properly work, like DB, cache warmup, '
. 'initial GeoLite DB download, etc',
)]
class InitCommand extends Command
{
public const string NAME = 'init';

private readonly CLIOption $skipInitDb;
private readonly CLIOption $clearDbCache;
private readonly CLIOption $initialApiKey;
private readonly CLIOption $downloadRoadRunnerBin;
private readonly CLIOption $skipDownloadGeoLiteDb;

public function __construct(private readonly InstallationCommandsRunnerInterface $commandsRunner)
{
parent::__construct();

$this->initialApiKey = InitOption::INITIAL_API_KEY->toCLIOption($this);
$this->skipInitDb = InitOption::SKIP_INITIALIZE_DB->toCLIOption($this);
$this->clearDbCache = InitOption::CLEAR_DB_CACHE->toCLIOption($this);
$this->downloadRoadRunnerBin = InitOption::DOWNLOAD_RR_BINARY->toCLIOption($this);
$this->skipDownloadGeoLiteDb = InitOption::SKIP_DOWNLOAD_GEOLITE->toCLIOption($this);
}

protected function configure(): void
{
$this
->setName(self::NAME)
->setDescription(
'Initializes external dependencies required for Shlink to properly work, like DB, cache warmup, '
. 'initial GeoLite DB download, etc',
);
}

protected function execute(InputInterface $input, OutputInterface $output): int
public function __invoke(SymfonyStyle $io, InputInterface $input, #[MapInput] InitCommandInput $inputData): int
{
$config = new ShlinkInitConfig(
initializeDb: ! $this->skipInitDb->get($input),
clearDbCache: $this->clearDbCache->get($input),
downloadRoadrunnerBinary: $this->downloadRoadRunnerBin->get($input),
generateApiKey: $this->initialApiKey->get($input),
downloadGeoLiteDb: ! $this->skipDownloadGeoLiteDb->get($input),
);
$commands = [...InstallationCommand::resolveCommandsForConfig($config)];
$io = new SymfonyStyle($input, $output);
$commands = [...$inputData->resolveCommands()];

return array_reduce($commands, function (bool $carry, array $commandInfo) use ($input, $io): bool {
/** @var array{InstallationCommand, string | null} $commandInfo */
/** @var array{InstallationCommand, string|null} $commandInfo */
[$command, $arg] = $commandInfo;

return $this->commandsRunner->execPhpCommand(
Expand All @@ -69,6 +43,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
interactive: $input->isInteractive(),
args: $arg !== null ? [$arg] : [],
) && $carry;
}, initial: true) ? 0 : -1;
}, initial: true) ? self::SUCCESS : self::FAILURE;
}
}
70 changes: 70 additions & 0 deletions src/Command/Model/InitCommandInput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

namespace Shlinkio\Shlink\Installer\Command\Model;

use Shlinkio\Shlink\Installer\Util\InstallationCommand;
use Symfony\Component\Console\Attribute\Option;

use function is_string;

final class InitCommandInput
{
#[Option(
'Skip the initial empty database creation. It will make this command fail on a later stage if the '
. 'database was not created manually',
)]
public bool $skipInitializeDb = false;

#[Option('Clear the database metadata cache')]
public bool $clearDbCache = false;

/**
* False: Do not generate an initial API key.
* True: Auto-generate a random initial API key.
* String: Use provided value as the initial API key.
*/
#[Option('Create an initial API key. A random one will be generated and printed if no value is provided')]
public string|bool $initialApiKey = false;

#[Option('Download a RoadRunner binary. Useful only if you plan to serve Shlink with Roadrunner')]
public bool $downloadRrBinary = false;

#[Option(
'Skip downloading the initial GeoLite DB file. Shlink will try to download it the first time it needs '
. 'to geolocate visits',
)]
public bool $skipDownloadGeolite = false;

/**
* @return iterable<array{InstallationCommand, string|null}>
*/
public function resolveCommands(): iterable
{
if (! $this->skipInitializeDb) {
yield [InstallationCommand::DB_CREATE_SCHEMA, null];
}

yield [InstallationCommand::DB_MIGRATE, null];
yield [InstallationCommand::ORM_PROXIES, null];

if ($this->clearDbCache) {
yield [InstallationCommand::ORM_CLEAR_CACHE, null];
}

if (! $this->skipDownloadGeolite) {
yield [InstallationCommand::GEOLITE_DOWNLOAD_DB, null];
}

if ($this->initialApiKey === true) {
yield [InstallationCommand::API_KEY_GENERATE, null];
} elseif (is_string($this->initialApiKey)) {
yield [InstallationCommand::API_KEY_CREATE, $this->initialApiKey];
}

if ($this->downloadRrBinary) {
yield [InstallationCommand::ROAD_RUNNER_BINARY_DOWNLOAD, null];
}
}
}
43 changes: 1 addition & 42 deletions src/Command/Model/InitOption.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

namespace Shlinkio\Shlink\Installer\Command\Model;

use Shlinkio\Shlink\Installer\Model\CLIOption;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputOption;

/** @deprecated */
enum InitOption: string
{
case SKIP_INITIALIZE_DB = 'skip-initialize-db';
Expand All @@ -18,42 +15,4 @@ public function asCliFlag(): string
{
return '--' . $this->value;
}

public function description(): string
{
return match ($this) {
self::SKIP_INITIALIZE_DB =>
'Skip the initial empty database creation. It will make this command fail on a later stage if the '
. 'database was not created manually.',
self::CLEAR_DB_CACHE => 'Clear the database metadata cache.',
self::INITIAL_API_KEY =>
'Create an initial admin API key. A random one will be generated and printed if no value is provided.',
self::DOWNLOAD_RR_BINARY =>
'Download a RoadRunner binary. Useful only if you plan to serve Shlink with Roadrunner.',
self::SKIP_DOWNLOAD_GEOLITE =>
'Skip downloading the initial GeoLite DB file. Shlink will try to download it the first time it needs '
. 'to geolocate visits.',
};
}

public function valueType(): int
{
return match ($this) {
self::INITIAL_API_KEY => InputOption::VALUE_OPTIONAL,
default => InputOption::VALUE_NONE,
};
}

public function defaultValue(): bool|null
{
return match ($this) {
self::INITIAL_API_KEY => false,
default => null,
};
}

public function toCLIOption(Command $command): CLIOption
{
return new CLIOption($command, $this);
}
}
28 changes: 0 additions & 28 deletions src/Model/CLIOption.php

This file was deleted.

22 changes: 0 additions & 22 deletions src/Model/ShlinkInitConfig.php

This file was deleted.

35 changes: 0 additions & 35 deletions src/Util/InstallationCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@

namespace Shlinkio\Shlink\Installer\Util;

use Shlinkio\Shlink\Installer\Model\ShlinkInitConfig;

use function is_string;

enum InstallationCommand: string
{
case DB_CREATE_SCHEMA = 'db_create_schema';
Expand All @@ -18,35 +14,4 @@ enum InstallationCommand: string
case API_KEY_GENERATE = 'api_key_generate';
case API_KEY_CREATE = 'api_key_create';
case ROAD_RUNNER_BINARY_DOWNLOAD = 'road_runner_update';

/**
* @return iterable<array{self, string | null}>
*/
public static function resolveCommandsForConfig(ShlinkInitConfig $config): iterable
{
if ($config->initializeDb) {
yield [self::DB_CREATE_SCHEMA, null];
}

yield [self::DB_MIGRATE, null];
yield [self::ORM_PROXIES, null];

if ($config->clearDbCache) {
yield [self::ORM_CLEAR_CACHE, null];
}

if ($config->downloadGeoLiteDb) {
yield [self::GEOLITE_DOWNLOAD_DB, null];
}

if ($config->generateApiKey === null) {
yield [self::API_KEY_GENERATE, null];
} elseif (is_string($config->generateApiKey)) {
yield [self::API_KEY_CREATE, $config->generateApiKey];
}

if ($config->downloadRoadrunnerBinary) {
yield [self::ROAD_RUNNER_BINARY_DOWNLOAD, null];
}
}
}
5 changes: 3 additions & 2 deletions test/Command/InitCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Shlinkio\Shlink\Installer\Service\InstallationCommandsRunnerInterface;
use Shlinkio\Shlink\Installer\Util\InstallationCommand;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Tester\CommandTester;

use function count;
Expand Down Expand Up @@ -111,7 +112,7 @@ public function properExitCodeIsReturnedBasedOnCommandsExecution(bool $result, i

public static function provideExitCodes(): iterable
{
yield 'success' => [true, 0];
yield 'error' => [false, -1];
yield 'success' => [true, Command::SUCCESS];
yield 'error' => [false, Command::FAILURE];
}
}