Skip to content

Commit

Permalink
Merge pull request #196 from shlinkio/develop
Browse files Browse the repository at this point in the history
Release 8.5.0
  • Loading branch information
acelaya authored Sep 22, 2023
2 parents 25cb9c3 + a10261d commit aee6e98
Show file tree
Hide file tree
Showing 19 changed files with 249 additions and 97 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).

## [8.5.0] - 2023-09-22
### Added
* Improve `init` command's `--initial-api-key` flag, so that it can receive an optional value which will be used as the initial API key.

### Changed
* [#193](https://github.com/shlinkio/shlink-installer/issues/193) Display improved verbosity hint for installation commands based on `interactive` flag, suggesting `-vvv` for interactive executions, and `SHELL_VERBOSITY=3` for non-interactive ones.
* Display warning next to SQLite when selecting database, informing it is not supported for production setups.

### Deprecated
* *Nothing*

### Removed
* *Nothing*

### Fixed
* *Nothing*


## [8.4.2] - 2023-06-15
### Added
* *Nothing*
Expand Down
7 changes: 7 additions & 0 deletions config/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,13 @@
'failOnError' => false,
'printOutput' => true,
],
InstallationCommand::API_KEY_CREATE->value => [
'command' => null, // Disabled by default, to avoid dependency on consumer (Shlink)
'initMessage' => 'Creating first API key...',
'errorMessage' => 'Error creating first API key.',
'failOnError' => false,
'printOutput' => true,
],
InstallationCommand::ROAD_RUNNER_BINARY_DOWNLOAD->value => [
'command' => 'vendor/bin/rr get --no-interaction --no-config --location bin/',
'initMessage' => 'Downloading RoadRunner binary...',
Expand Down
2 changes: 1 addition & 1 deletion indocker
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/usr/bin/env bash

docker-compose run --rm shlink_installer_php /bin/sh -c "$*"
docker compose run --rm shlink_installer_php /bin/sh -c "$*"
1 change: 1 addition & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
bootstrap="./vendor/autoload.php"
colors="true"
cacheDirectory="build/.phpunit.cache"
displayDetailsOnTestsThatTriggerWarnings="true"
>
<testsuites>
<testsuite name="Installer">
Expand Down
5 changes: 4 additions & 1 deletion src/Command/AbstractInstallCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,14 @@ private function execInitCommand(SymfonyStyle $io, ImportedConfig $importedConfi
$input = [
InitOption::SKIP_INITIALIZE_DB->asCliFlag() => $isUpdate,
InitOption::CLEAR_DB_CACHE->asCliFlag() => $isUpdate,
InitOption::INITIAL_API_KEY->asCliFlag() => ! $isUpdate,
InitOption::DOWNLOAD_RR_BINARY->asCliFlag() =>
$isUpdate && $this->assetsHandler->roadRunnerBinaryExistsInPath($importedConfig->importPath),
];

if (! $isUpdate) {
$input[InitOption::INITIAL_API_KEY->asCliFlag()] = null;
}

$command = $this->getApplication()?->find(InitCommand::NAME);
$exitCode = $command?->run(new ArrayInput($input), $io);

Expand Down
37 changes: 22 additions & 15 deletions src/Command/InitCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Shlinkio\Shlink\Installer\Command;

use Shlinkio\Shlink\Installer\Command\Model\InitOption;
use Shlinkio\Shlink\Installer\Model\FlagOption;
use Shlinkio\Shlink\Installer\Model\CLIOption;
use Shlinkio\Shlink\Installer\Model\ShlinkInitConfig;
use Shlinkio\Shlink\Installer\Service\InstallationCommandsRunnerInterface;
use Shlinkio\Shlink\Installer\Util\InstallationCommand;
Expand All @@ -20,21 +20,21 @@ class InitCommand extends Command
{
public const NAME = 'init';

private readonly FlagOption $skipInitDb;
private readonly FlagOption $clearDbCache;
private readonly FlagOption $initialApiKey;
private readonly FlagOption $downloadRoadRunnerBin;
private readonly FlagOption $skipDownloadGeoLiteDb;
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->skipInitDb = InitOption::SKIP_INITIALIZE_DB->toFlagOption($this);
$this->clearDbCache = InitOption::CLEAR_DB_CACHE->toFlagOption($this);
$this->initialApiKey = InitOption::INITIAL_API_KEY->toFlagOption($this);
$this->downloadRoadRunnerBin = InitOption::DOWNLOAD_RR_BINARY->toFlagOption($this);
$this->skipDownloadGeoLiteDb = InitOption::SKIP_DOWNLOAD_GEOLITE->toFlagOption($this);
$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
Expand All @@ -59,9 +59,16 @@ protected function execute(InputInterface $input, OutputInterface $output): ?int
$commands = InstallationCommand::resolveCommandsForConfig($config);
$io = new SymfonyStyle($input, $output);

return every(
$commands,
fn (InstallationCommand $command) => $this->commandsRunner->execPhpCommand($command->value, $io),
) ? 0 : -1;
return every($commands, function (array $commandInfo) use ($input, $io): bool {
/** @var array{InstallationCommand, string | null} $commandInfo */
[$command, $arg] = $commandInfo;

return $this->commandsRunner->execPhpCommand(
name: $command->value,
io: $io,
interactive: $input->isInteractive(),
args: $arg !== null ? [$arg] : [],
);
}) ? 0 : -1;
}
}
42 changes: 32 additions & 10 deletions src/Command/Model/InitOption.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

namespace Shlinkio\Shlink\Installer\Command\Model;

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

enum InitOption: string
{
Expand All @@ -18,20 +19,41 @@ public function asCliFlag(): string
return '--' . $this->value;
}

public function toFlagOption(Command $command): FlagOption
public function description(): string
{
$description = match ($this) {
self:: SKIP_INITIALIZE_DB =>
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 and print initial admin API key.',
self:: DOWNLOAD_RR_BINARY =>
'Download a RoadRunner binary. Useful only if you plan to serve Shlink with Roadrunner.',
self:: SKIP_DOWNLOAD_GEOLITE =>
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.',
};
return new FlagOption($command, $this->value, $description);
}

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);
}
}
2 changes: 1 addition & 1 deletion src/Config/Option/Database/DatabaseDriverConfigOption.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class DatabaseDriverConfigOption extends BaseConfigOption
'MariaDB' => DatabaseDriver::MYSQL,
'PostgreSQL' => DatabaseDriver::POSTGRES,
'MicrosoftSQL' => DatabaseDriver::MSSQL,
'SQLite' => DatabaseDriver::SQLITE,
'SQLite [<options=bold;fg=yellow>Not supported for production</>]' => DatabaseDriver::SQLITE,
];

public function getEnvVar(): string
Expand Down
28 changes: 28 additions & 0 deletions src/Model/CLIOption.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Shlinkio\Shlink\Installer\Model;

use Shlinkio\Shlink\Installer\Command\Model\InitOption;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;

class CLIOption
{
public function __construct(Command $command, private readonly InitOption $initOption)
{
$command->addOption(
$initOption->value,
null,
$initOption->valueType(),
$initOption->description(),
$this->initOption->defaultValue(),
);
}

public function get(InputInterface $input): mixed
{
return $input->getOption($this->initOption->value);
}
}
22 changes: 0 additions & 22 deletions src/Model/FlagOption.php

This file was deleted.

8 changes: 7 additions & 1 deletion src/Model/ShlinkInitConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ public function __construct(
public readonly bool $initializeDb,
public readonly bool $clearDbCache,
public readonly bool $downloadRoadrunnerBinary,
public readonly bool $generateApiKey,
/**
* False: Do not generate an initial API key.
* String: Use provided value as the initial API key.
* Null: Auto-generate a random initial API key.
* @todo Change to string|false|null once PHP 8.1 is no longer supported
*/
public readonly string|bool|null $generateApiKey,
public readonly bool $downloadGeoLiteDb,
) {
}
Expand Down
11 changes: 7 additions & 4 deletions src/Service/InstallationCommandsRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public function __construct(
$this->phpBinary = $phpFinder->find(false) ?: 'php';
}

public function execPhpCommand(string $name, SymfonyStyle $io): bool
public function execPhpCommand(string $name, SymfonyStyle $io, bool $interactive, array $args): bool
{
$commandConfig = $this->commandsMapping[$name] ?? null;
if ($commandConfig === null) {
Expand All @@ -50,7 +50,7 @@ public function execPhpCommand(string $name, SymfonyStyle $io): bool
return true;
}

$command = [$this->phpBinary, ...$this->commandToArray($command)];
$command = [$this->phpBinary, ...$this->commandToArray($command), ...$args];
$io->write(
sprintf(' <options=bold>[Running "%s"]</> ', implode(' ', $command)),
false,
Expand All @@ -61,14 +61,17 @@ public function execPhpCommand(string $name, SymfonyStyle $io): bool
$isSuccess = $process->isSuccessful();
$isWarning = ! $isSuccess && ! $failOnError;
$isVerbose = $io->isVerbose();
$verbosityIndicator = $interactive ? 'Run with -vvv' : 'Set SHELL_VERBOSITY=3';

if ($isSuccess) {
$io->writeln(' <info>Success!</info>');
} elseif ($isWarning) {
$io->write(' <comment>Warning!</comment>');
$io->writeln($isVerbose ? '' : ' Run with -vvv to see error.');
$io->writeln($isVerbose ? '' : sprintf(' %s to see error.', $verbosityIndicator));
} elseif (! $isVerbose) {
$io->error(sprintf('%s. Run this command with -vvv to see specific error info.', $errorMessage));
$io->error(
sprintf('%s. %s to see specific error info.', $errorMessage, $verbosityIndicator),
);
}

if ($printOutput) {
Expand Down
2 changes: 1 addition & 1 deletion src/Service/InstallationCommandsRunnerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@

interface InstallationCommandsRunnerInterface
{
public function execPhpCommand(string $name, SymfonyStyle $io): bool;
public function execPhpCommand(string $name, SymfonyStyle $io, bool $interactive, array $args): bool;
}
23 changes: 14 additions & 9 deletions src/Util/InstallationCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use Shlinkio\Shlink\Installer\Model\ShlinkInitConfig;

use function is_string;

enum InstallationCommand: string
{
case DB_CREATE_SCHEMA = 'db_create_schema';
Expand All @@ -14,34 +16,37 @@ enum InstallationCommand: string
case ORM_CLEAR_CACHE = 'orm_clear_cache';
case GEOLITE_DOWNLOAD_DB = 'geolite_download_db';
case API_KEY_GENERATE = 'api_key_generate';
case API_KEY_CREATE = 'api_key_create';
case ROAD_RUNNER_BINARY_DOWNLOAD = 'road_runner_update';

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

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

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

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

if ($config->generateApiKey) {
yield self::API_KEY_GENERATE;
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;
yield [self::ROAD_RUNNER_BINARY_DOWNLOAD, null];
}
}
}
Loading

0 comments on commit aee6e98

Please sign in to comment.