Skip to content

Commit

Permalink
Merge pull request #187 from shlinkio/develop
Browse files Browse the repository at this point in the history
Release 8.4
  • Loading branch information
acelaya authored May 23, 2023
2 parents 1ead941 + fccdda4 commit faf84bc
Show file tree
Hide file tree
Showing 87 changed files with 868 additions and 480 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ test-resources export-ignore
CHANGELOG.md export-ignore
docker-compose.override.yml.dist export-ignore
docker-compose.yml export-ignore
Dockerfile export-ignore
indocker export-ignore
infection.json export-ignore
phpcs.xml export-ignore
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ build
composer.lock
vendor/
docker-compose.override.yml
.phpunit.result.cache
config/*.local.php
data
bin/rr
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,30 @@ 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.4.0] - 2023-05-23
### Added
* [#183](https://github.com/shlinkio/shlink-installer/issues/183) Create new `init` command that can be used to set up and initialize the environment for Shlink.

This command makes sure the database is created, caches are cleared, etc., and can be used by those who wish to automate Shlink installations with env vars instead of the interactive `install`/`update`.

The existing `install` and `update` commands use this one internally, and it is also suitable for the docker image entry point.

* [#184](https://github.com/shlinkio/shlink-installer/issues/184) During updates, the installer can now detect if the RoadRunner binary exists in the "old" installation folder, in which case it downloads a new instance as part of the process.

### Changed
* Changed `loosely` by `loose` for the short URL mode, ensuring a migration for those who previously set `loosely`.
* Updated to PHPUnit 10 and migrated config to PHPUnit 10.1 format.

### Deprecated
* *Nothing*

### Removed
* *Nothing*

### Fixed
* *Nothing*


## [8.3.0] - 2023-01-28
### Added
* [#174](https://github.com/shlinkio/shlink-installer/issues/174) Added support for redirect status codes 308 and 307.
Expand Down
6 changes: 6 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FROM composer:2

RUN apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} && \
pecl install pcov && \
docker-php-ext-enable pcov && \
apk del .phpize-deps
32 changes: 16 additions & 16 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,25 @@
],
"require": {
"php": "^8.1",
"laminas/laminas-config": "^3.7",
"laminas/laminas-config-aggregator": "^1.8",
"laminas/laminas-servicemanager": "^3.16",
"laminas/laminas-stdlib": "^3.11",
"laminas/laminas-config": "^3.8",
"laminas/laminas-config-aggregator": "^1.13",
"laminas/laminas-servicemanager": "^3.21",
"laminas/laminas-stdlib": "^3.17",
"lstrojny/functional-php": "^1.17",
"shlinkio/shlink-config": "^2.0",
"symfony/console": "^6.1",
"symfony/filesystem": "^6.1",
"symfony/process": "^6.1"
"shlinkio/shlink-config": "^2.4",
"symfony/console": "^6.2",
"symfony/filesystem": "^6.2",
"symfony/process": "^6.2"
},
"require-dev": {
"devster/ubench": "^2.0",
"infection/infection": "^0.26.15",
"phpstan/phpstan": "^1.8",
"phpstan/phpstan-phpunit": "^1.2",
"phpunit/phpunit": "^9.5",
"devster/ubench": "^2.1",
"infection/infection": "^0.27",
"phpstan/phpstan": "^1.10",
"phpstan/phpstan-phpunit": "^1.3",
"phpunit/phpunit": "^10.1",
"roave/security-advisories": "dev-master",
"shlinkio/php-coding-standard": "~2.3.0",
"symfony/var-dumper": "^6.1"
"symfony/var-dumper": "^6.2"
},
"autoload": {
"psr-4": {
Expand All @@ -53,10 +53,10 @@
"cs": "phpcs",
"cs:fix": "phpcbf",
"stan": "phpstan analyse src test test-resources config --level=8",
"test": "phpdbg -qrr vendor/bin/phpunit --order-by=random --testdox --colors=always",
"test": "phpunit --order-by=random --testdox --colors=always",
"test:ci": "@test --coverage-clover=build/clover.xml --coverage-xml=build/coverage-xml --log-junit=build/junit.xml",
"test:pretty": "@test --coverage-html build/coverage-html",
"infect": "infection --threads=4 --min-msi=85 --log-verbosity=default --only-covered --only-covering-test-cases",
"infect": "infection --threads=4 --min-msi=85 --log-verbosity=default --only-covered",
"infect:ci": "@infect --coverage=build --skip-initial-tests",
"infect:show": "@infect --show-mutations",
"infect:show:ci": "@infect --show-mutations --coverage=build",
Expand Down
14 changes: 11 additions & 3 deletions config/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
Command\InstallCommand::class => ConfigAbstractFactory::class,
Command\UpdateCommand::class => ConfigAbstractFactory::class,
Command\SetOptionCommand::class => ConfigAbstractFactory::class,
Command\InitCommand::class => ConfigAbstractFactory::class,
],
],

Expand Down Expand Up @@ -201,13 +202,11 @@
PhpArrayConfigWriter::class,
Service\ShlinkAssetsHandler::class,
Config\ConfigGenerator::class,
Service\InstallationCommandsRunner::class,
],
Command\UpdateCommand::class => [
PhpArrayConfigWriter::class,
Service\ShlinkAssetsHandler::class,
Config\ConfigGenerator::class,
Service\InstallationCommandsRunner::class,
],
Command\SetOptionCommand::class => [
PhpArrayConfigWriter::class,
Expand All @@ -217,21 +216,23 @@
'config.config_options.groups',
'config.installer.enabled_options',
],
Command\InitCommand::class => [Service\InstallationCommandsRunner::class],
],

'installer' => [
'commands' => [
Command\InstallCommand::NAME => Command\InstallCommand::class,
Command\UpdateCommand::NAME => Command\UpdateCommand::class,
Command\SetOptionCommand::NAME => Command\SetOptionCommand::class,
Command\InitCommand::NAME => Command\InitCommand::class,
],

'enabled_options' => null,

'installation_commands' => [
InstallationCommand::DB_CREATE_SCHEMA->value => [
'command' => 'vendor/doctrine/orm/bin/doctrine.php orm:schema-tool:create',
'initMessage' => 'Initializing database...',
'initMessage' => 'Initializing database if needed...',
'errorMessage' => 'Error generating database.',
'failOnError' => true,
'printOutput' => false,
Expand Down Expand Up @@ -271,6 +272,13 @@
'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...',
'errorMessage' => 'Error downloading RoadRunner binary.',
'failOnError' => false,
'printOutput' => false,
],
],
],

Expand Down
3 changes: 2 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ version: '3'
services:
shlink_installer_php:
container_name: shlink_installer_php
image: composer:2
build:
context: .
volumes:
- ./:/app
13 changes: 7 additions & 6 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
<?xml version="1.0"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/6.5/phpunit.xsd"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="./vendor/autoload.php"
colors="true"
cacheDirectory="build/.phpunit.cache"
>
<testsuites>
<testsuite name="Installer">
<directory>./test</directory>
</testsuite>
</testsuites>

<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
<source>
<include>
<directory>./src</directory>
</include>
</source>
</phpunit>
38 changes: 20 additions & 18 deletions src/Command/AbstractInstallCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,24 @@
namespace Shlinkio\Shlink\Installer\Command;

use Laminas\Config\Writer\WriterInterface;
use Shlinkio\Shlink\Installer\Command\Model\InitOption;
use Shlinkio\Shlink\Installer\Config\ConfigGeneratorInterface;
use Shlinkio\Shlink\Installer\Model\ImportedConfig;
use Shlinkio\Shlink\Installer\Service\InstallationCommandsRunnerInterface;
use Shlinkio\Shlink\Installer\Service\ShlinkAssetsHandler;
use Shlinkio\Shlink\Installer\Service\ShlinkAssetsHandlerInterface;
use Shlinkio\Shlink\Installer\Util\InstallationCommand;
use Shlinkio\Shlink\Installer\Util\Utils;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

use function Functional\every;

abstract class AbstractInstallCommand extends Command
{
public function __construct(
private WriterInterface $configWriter,
private ShlinkAssetsHandlerInterface $assetsHandler,
private ConfigGeneratorInterface $configGenerator,
private InstallationCommandsRunnerInterface $commandsRunner,
private readonly WriterInterface $configWriter,
private readonly ShlinkAssetsHandlerInterface $assetsHandler,
private readonly ConfigGeneratorInterface $configGenerator,
) {
parent::__construct();
}
Expand Down Expand Up @@ -54,7 +51,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$io->text('<info>Custom configuration properly generated!</info>');
$io->newLine();

if (! $this->execPostInstallCommands($io)) {
if (! $this->execInitCommand($io, $importedConfig)) {
return -1;
}

Expand All @@ -71,16 +68,21 @@ private function resolvePreviousConfig(SymfonyStyle $io): ImportedConfig
return ImportedConfig::notImported();
}

private function execPostInstallCommands(SymfonyStyle $io): bool
private function execInitCommand(SymfonyStyle $io, ImportedConfig $importedConfig): bool
{
$commands = $this->isUpdate()
? InstallationCommand::POST_UPDATE_COMMANDS
: InstallationCommand::POST_INSTALL_COMMANDS;

return every(
$commands,
fn (InstallationCommand $command) => $this->commandsRunner->execPhpCommand($command->value, $io),
);
$isUpdate = $this->isUpdate();
$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),
];

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

return $exitCode === 0;
}

abstract protected function isUpdate(): bool;
Expand Down
67 changes: 67 additions & 0 deletions src/Command/InitCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

declare(strict_types=1);

namespace Shlinkio\Shlink\Installer\Command;

use Shlinkio\Shlink\Installer\Command\Model\InitOption;
use Shlinkio\Shlink\Installer\Model\FlagOption;
use Shlinkio\Shlink\Installer\Model\ShlinkInitConfig;
use Shlinkio\Shlink\Installer\Service\InstallationCommandsRunnerInterface;
use Shlinkio\Shlink\Installer\Util\InstallationCommand;
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 Functional\every;

class InitCommand extends Command
{
public const NAME = 'init';

private readonly FlagOption $skipInitDb;
private readonly FlagOption $clearDbCache;
private readonly FlagOption $initialApiKey;
private readonly FlagOption $updateRoadRunnerBin;
private readonly FlagOption $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->updateRoadRunnerBin = InitOption::DOWNLOAD_RR_BINARY->toFlagOption($this);
$this->skipDownloadGeoLiteDb = InitOption::SKIP_DOWNLOAD_GEOLITE->toFlagOption($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
{
$config = new ShlinkInitConfig(
initializeDb: ! $this->skipInitDb->get($input),
clearDbCache: $this->clearDbCache->get($input),
updateRoadrunnerBinary: $this->updateRoadRunnerBin->get($input),
generateApiKey: $this->initialApiKey->get($input),
downloadGeoLiteDb: ! $this->skipDownloadGeoLiteDb->get($input),
);
$commands = InstallationCommand::resolveCommandsForConfig($config);
$io = new SymfonyStyle($input, $output);

return every(
$commands,
fn (InstallationCommand $command) => $this->commandsRunner->execPhpCommand($command->value, $io),
) ? 0 : -1;
}
}
37 changes: 37 additions & 0 deletions src/Command/Model/InitOption.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Shlinkio\Shlink\Installer\Command\Model;

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

enum InitOption: string
{
case SKIP_INITIALIZE_DB = 'skip-initialize-db';
case CLEAR_DB_CACHE = 'clear-db-cache';
case INITIAL_API_KEY = 'initial-api-key';
case DOWNLOAD_RR_BINARY = 'download-rr-binary';
case SKIP_DOWNLOAD_GEOLITE = 'skip-download-geolite';

public function asCliFlag(): string
{
return '--' . $this->value;
}

public function toFlagOption(Command $command): FlagOption
{
$description = 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 =>
'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);
}
}
Loading

0 comments on commit faf84bc

Please sign in to comment.