246 lines
7.9 KiB
PHP
246 lines
7.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Evoweb\DeployerConfig\Config;
|
|
|
|
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\testLocally;
|
|
use function Deployer\warning;
|
|
|
|
class Deployment
|
|
{
|
|
protected array $environmentVariables = [
|
|
'CI_PROJECT_DIR',
|
|
'CI_HOST',
|
|
'ENVIRONMENT',
|
|
'INSTANCE_ID'
|
|
];
|
|
|
|
public function __construct()
|
|
{
|
|
$this->loadDeployerCommon();
|
|
$this->setDefaultConfiguration();
|
|
$this->loadHostConfiguration();
|
|
$this->registerTasks();
|
|
}
|
|
|
|
protected function loadDeployerCommon(): void
|
|
{
|
|
$file = realpath(__DIR__ . '/../../../../deployer/deployer/recipe/common.php');
|
|
if ($file && file_exists($file)) {
|
|
require $file;
|
|
}
|
|
}
|
|
|
|
protected function setDefaultConfiguration(): void
|
|
{
|
|
array_walk($this->environmentVariables, fn ($variable) => $this->set($variable, getEnv($variable)));
|
|
|
|
$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
|
|
$file = realpath(__DIR__ . '/../../../../../hosts.yaml');
|
|
if ($file && file_exists($file)) {
|
|
import($file);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|