ew_deployer_config/Classes/Config/Deployment.php
2025-04-17 20:53:18 +02:00

251 lines
8.2 KiB
PHP

<?php
declare(strict_types=1);
namespace Evoweb\DeployerConfig\Config;
use Deployer\Deployer;
use Deployer\Exception\Exception;
use Evoweb\DeployerConfig\Tasks\Deploy;
use Evoweb\DeployerConfig\Tasks\Local;
use Evoweb\DeployerConfig\Tasks\Remote;
use Evoweb\DeployerConfig\Tasks\Rsync;
use RuntimeException;
use function Deployer\get;
use function Deployer\import;
use function Deployer\parse;
use function Deployer\runLocally;
use function Deployer\set;
use function Deployer\Support\str_contains;
use function Deployer\testLocally;
use function Deployer\warning;
class Deployment
{
public function __construct()
{
$this->loadDeployerCommon();
$this->setDefaultConfiguration();
$this->loadHostConfiguration();
$this->registerTasks();
}
protected function loadDeployerCommon(): void
{
foreach (['/../../', '/../../../', '/../../../../'] as $path) {
$file = realpath(__DIR__ . $path . 'vendor/deployer/deployer/recipe/common.php');
if ($file && file_exists($file)) {
require $file;
break;
}
}
}
protected function setDefaultConfiguration(): void
{
$this->set('CI_PROJECT_DIR', getEnv('CI_PROJECT_DIR'));
$this->set('CI_HOST', getEnv('CI_HOST'));
$this->set('ENVIRONMENT_NAME', getEnv('ENVIRONMENT_NAME'));
$this->set('INSTANCE_ID', getEnv('INSTANCE_ID'));
$this->set('app_container_path', '');
$this->set('prepare_dirs', [
'.dep',
'releases',
'shared',
]);
$this->set('ignore_update_code_paths', [
'var/cache',
'var/log/*',
'Build',
'resources',
'.idea',
'.editorconfig',
'.git',
'.gitignore',
'.gitlab-ci.yml',
]);
$this->set('writable_dirs', [
'var',
]);
$this->set('clear_paths', [
'composer.json',
'composer.lock',
]);
$this->set('bin/git', function () {
return $this->which('git');
});
$this->set('bin/composer', function () {
if (testLocally('[ -f {{deploy_path}}/.dep/composer.phar ]')) {
return '{{bin/php}} {{deploy_path}}/.dep/composer.phar';
}
if ($this->commandExist('composer')) {
return $this->which('composer');
}
warning("Composer binary wasn't found. Installing latest composer to \"{{deploy_path}}/.dep/composer.phar\".");
runLocally("cd {{deploy_path}} && curl -sS https://getcomposer.org/installer | {{bin/php}}");
runLocally('mv {{deploy_path}}/composer.phar {{deploy_path}}/.dep/composer.phar');
return '{{bin/php}} {{deploy_path}}/.dep/composer.phar';
});
$this->set('use_relative_symlink', function () {
return $this->commandSupportsOption('ln', '--relative');
});
$this->set('use_atomic_symlink', function () {
return $this->commandSupportsOption('mv', '--no-target-directory');
});
$this->set('release_name', function () {
$latest = runLocally('cat {{deploy_path}}/.dep/latest_release || echo 0');
return strval(intval($latest) + 1);
});
$this->set('releases_log', function () {
if (!testLocally('[ -f {{deploy_path}}/.dep/releases_log ]')) {
return [];
}
$releaseLogs = array_map(function ($line) {
return json_decode($line, true);
}, explode("\n", runLocally('tail -n 300 {{deploy_path}}/.dep/releases_log')));
return array_filter($releaseLogs); // Return all non-empty lines.
});
$this->set('releases_list', function () {
// If there is no releases return empty list.
if (!testLocally('[ -d {{deploy_path}}/releases ] && [ "$(ls -A {{deploy_path}}/releases)" ]')) {
return [];
}
// Will list only dirs in releases.
$ll = explode("\n", runLocally('cd {{deploy_path}}/releases && ls -t -1 -d */'));
$ll = array_map(function ($release) {
return basename(rtrim(trim($release), '/'));
}, $ll);
// Return releases from newest to oldest.
$releasesLog = array_reverse(get('releases_log'));
$releases = [];
foreach ($releasesLog as $release) {
if (in_array($release['release_name'], $ll, true)) {
$releases[] = $release['release_name'];
}
}
return $releases;
});
$this->set('release_path', function () {
$releaseExists = testLocally('[ -h {{deploy_path}}/release ]');
if ($releaseExists) {
$link = runLocally("readlink {{deploy_path}}/release");
return str_starts_with($link, '/') ? $link : get('deploy_path') . '/' . $link;
} else {
throw new Exception(parse('The "release_path" ({{deploy_path}}/release) does not exist.'));
}
});
$this->set('release_or_current_path', function () {
$releaseExists = testLocally('[ -h {{deploy_path}}/release ]');
return $releaseExists ? get('release_path') : get('current_path');
});
$this->set('http_user', function () {
$candidates = explode("\n", runLocally("ps axo comm,user | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | sort | awk '{print \$NF}' | uniq"));
$httpUser = array_shift($candidates);
if (empty($httpUser)) {
throw new RuntimeException(
"Can't detect http user name.\n" .
"Please setup `http_user` config parameter."
);
}
return $httpUser;
});
$this->set('http_group', function () {
$candidates = explode("\n", runLocally("ps axo comm,group | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | sort | awk '{print \$NF}' | uniq"));
$httpGroup = array_shift($candidates);
if (empty($httpGroup)) {
throw new RuntimeException(
"Can't detect http user name.\n" .
"Please setup `http_group` config parameter."
);
}
return $httpGroup;
});
}
protected function loadHostConfiguration(): void
{
// read configuration
foreach (['/../../../../', '/../../../../../'] as $path) {
$file = realpath(__DIR__ . $path . $_ENV['ENVIRONMENT_NAME'] . '.yaml');
if (file_exists($file)) {
import($file);
break;
}
}
}
protected function registerTasks(): void
{
new Rsync();
new Local();
new Remote();
new Deploy();
}
protected function commandSupportsOption(string $command, string $option): bool
{
$man = runLocally("(man $command 2>&1 || $command -h 2>&1 || $command --help 2>&1) | grep -- $option || true");
if (empty($man)) {
return false;
}
return str_contains($man, $option);
}
protected function commandExist(string $command): bool
{
return testLocally("hash $command 2>/dev/null");
}
protected function which(string $name): string
{
$nameEscaped = escapeshellarg($name);
// Try `command`, should cover all Bourne-like shells
// Try `which`, should cover most other cases
// Fallback to `type` command, if the rest fails
$path = runLocally("command -v $nameEscaped || which $nameEscaped || type -p $nameEscaped");
if (empty($path)) {
throw new RuntimeException("Can't locate [$nameEscaped] - neither of [command|which|type] commands are available");
}
// Deal with issue when `type -p` outputs something like `type -ap` in some implementations
return trim(str_replace("$name is", "", $path));
}
protected function set(string $name, $value): void
{
set($name, $value);
}
}