[TASK] Add base files
This commit is contained in:
commit
b5ad2bb9c8
51
.editorconfig
Normal file
51
.editorconfig
Normal file
@ -0,0 +1,51 @@
|
||||
# EditorConfig is awesome: http://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# TS/JS-Files
|
||||
[*.{ts,js,es6}]
|
||||
indent_size = 2
|
||||
|
||||
# JSON-Files
|
||||
[*.json]
|
||||
indent_style = tab
|
||||
|
||||
# ReST-Files
|
||||
[*.rst]
|
||||
indent_size = 3
|
||||
max_line_length = 80
|
||||
|
||||
# YAML-Files
|
||||
[*.{yaml,yml}]
|
||||
indent_size = 2
|
||||
|
||||
# package.json
|
||||
[package.json]
|
||||
indent_size = 2
|
||||
|
||||
# TypoScript
|
||||
[*.{typoscript,tsconfig}]
|
||||
indent_size = 2
|
||||
|
||||
# XLF-Files
|
||||
[*.xlf]
|
||||
indent_style = tab
|
||||
|
||||
# SQL-Files
|
||||
[*.sql]
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
|
||||
# .htaccess
|
||||
[{_.htaccess,.htaccess}]
|
||||
indent_style = tab
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.cache/
|
||||
.idea/
|
||||
vendor/
|
||||
composer.lock
|
||||
26
.gitlab-ci.yml
Normal file
26
.gitlab-ci.yml
Normal file
@ -0,0 +1,26 @@
|
||||
# To contribute improvements to CI/CD templates, please follow the Development guide at:
|
||||
# https://docs.gitlab.com/ee/development/cicd/templates.html
|
||||
# This specific template is located at:
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Composer.gitlab-ci.yml
|
||||
|
||||
# Publishes a tag/branch to Composer Packages of the current project
|
||||
publish:
|
||||
image: curlimages/curl:latest
|
||||
stage: build
|
||||
rules:
|
||||
- if: '$CI_COMMIT_REF_NAME == "develop" || $CI_COMMIT_REF_NAME == "main" || $CI_COMMIT_TAG =~ /^\d+.\d+.\d+/'
|
||||
variables:
|
||||
URL: "$CI_SERVER_PROTOCOL://$CI_SERVER_HOST:$CI_SERVER_PORT/api/v4/projects/$CI_PROJECT_ID/packages/composer?job_token=$CI_JOB_TOKEN"
|
||||
script:
|
||||
- version=$([[ -z "$CI_COMMIT_TAG" ]] && echo "branch=$CI_COMMIT_REF_NAME" || echo "tag=$CI_COMMIT_TAG")
|
||||
- insecure=$([ "$CI_SERVER_PROTOCOL" = "http" ] && echo "--insecure" || echo "")
|
||||
- response=$(curl -s -w "\n%{http_code}" $insecure --data $version $URL)
|
||||
- code=$(echo "$response" | tail -n 1)
|
||||
- body=$(echo "$response" | head -n 1)
|
||||
# Output state information
|
||||
- if [ $code -eq 201 ]; then
|
||||
echo "Package created - Code $code - $body";
|
||||
else
|
||||
echo "Could not create package - Code $code - $body";
|
||||
exit 1;
|
||||
fi
|
||||
91
Classes/Config/AbstractConfiguration.php
Normal file
91
Classes/Config/AbstractConfiguration.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace Evoweb\DeployerConfig\Config;
|
||||
|
||||
use Deployer\Deployer;
|
||||
use Deployer\Task\Task;
|
||||
use phpDocumentor\Reflection\DocBlockFactory;
|
||||
use ReflectionMethod;
|
||||
|
||||
use function Deployer\get;
|
||||
use function Deployer\has;
|
||||
use function Deployer\set;
|
||||
|
||||
abstract class AbstractConfiguration
|
||||
{
|
||||
protected array $tasks = [];
|
||||
|
||||
protected function initializeTasks(): void
|
||||
{
|
||||
foreach ($this->tasks as $name => $config) {
|
||||
$this->registerTask($name, $config);
|
||||
}
|
||||
}
|
||||
|
||||
protected function registerTask(string $name, array $config): void
|
||||
{
|
||||
$methodName = $config['methodName'] ?? '';
|
||||
|
||||
if ($methodName === '' || !method_exists(static::class, $methodName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$task = new Task($name, $this->$methodName(...));
|
||||
|
||||
$description = $this->getFunctionSummary($methodName);
|
||||
if ($description) {
|
||||
$task->desc($description);
|
||||
}
|
||||
|
||||
$stages = $config['stages'] ?? [];
|
||||
if (count($stages)) {
|
||||
$task->onStage(...$stages);
|
||||
}
|
||||
|
||||
$deployer = Deployer::get();
|
||||
$deployer->tasks->set($name, $task);
|
||||
}
|
||||
|
||||
protected function getFunctionSummary(string $function): string
|
||||
{
|
||||
$summary = '';
|
||||
try {
|
||||
$reflector = new ReflectionMethod(Deployment::class, $function);
|
||||
$comment = $reflector->getDocComment();
|
||||
if ($comment) {
|
||||
$summary = DocBlockFactory::createInstance()->create($comment)->getSummary();
|
||||
}
|
||||
} catch (\Exception) {
|
||||
}
|
||||
return $summary;
|
||||
}
|
||||
|
||||
protected function getTasks(): array
|
||||
{
|
||||
return array_keys($this->tasks);
|
||||
}
|
||||
|
||||
protected function get(string $name, $default = null)
|
||||
{
|
||||
return get($name, $default) ?? (
|
||||
isset(Deployer::get()->config[$name])
|
||||
? Deployer::get()->config->get($name)
|
||||
: $default
|
||||
);
|
||||
}
|
||||
|
||||
protected function set(string $name, $value): void
|
||||
{
|
||||
set($name, $value);
|
||||
}
|
||||
|
||||
protected function has(string $name): bool
|
||||
{
|
||||
return has($name) || Deployer::get()->config->has($name);
|
||||
}
|
||||
|
||||
protected function hasArray(string $name): bool
|
||||
{
|
||||
return $this->has($name) && is_array($this->get($name));
|
||||
}
|
||||
}
|
||||
660
Classes/Config/Deployment.php
Normal file
660
Classes/Config/Deployment.php
Normal file
@ -0,0 +1,660 @@
|
||||
<?php
|
||||
|
||||
namespace Evoweb\DeployerConfig\Config;
|
||||
|
||||
use Deployer\Exception\Exception;
|
||||
use Deployer\Host\Host;
|
||||
use Deployer\Task\Context;
|
||||
use Deployer\Type\Csv;
|
||||
|
||||
use function Deployer\after;
|
||||
use function Deployer\cd;
|
||||
use function Deployer\commandExist;
|
||||
use function Deployer\get;
|
||||
use function Deployer\inventory;
|
||||
use function Deployer\parse;
|
||||
use function Deployer\run;
|
||||
use function Deployer\task;
|
||||
use function Deployer\test;
|
||||
use function Deployer\writeln;
|
||||
|
||||
class Deployment extends AbstractConfiguration
|
||||
{
|
||||
protected array $tasks = [
|
||||
'remote:prepare' => ['methodName' => 'remotePrepare'],
|
||||
'rsync:warmup' => [],
|
||||
'deploy:prepare' => [],
|
||||
'deploy:lock' => [],
|
||||
'deploy:release' => [],
|
||||
'deploy:update_code' => [],
|
||||
//'rsync:update_code' => [],
|
||||
|
||||
'local:vendors' => ['methodName' => 'localVendors'],
|
||||
'local:create_folder' => ['methodName' => 'localCreateFolder'],
|
||||
'local:write_release' => ['methodName' => 'localWriteRelease'],
|
||||
'local:shared' => ['methodName' => 'localShared'],
|
||||
'local:writable_files' => ['methodName' => 'localWritableFiles'],
|
||||
'local:executable_files' => ['methodName' => 'localExecutableFiles'],
|
||||
'deploy:clear_paths' => [],
|
||||
'local:symlink' => ['methodName' => 'localSymlink'],
|
||||
'local:cleanup' => ['methodName' => 'localCleanup'],
|
||||
|
||||
'rsync:remote' => [],
|
||||
'remote:change_group' => ['methodName' => 'remoteChangeGroup'],
|
||||
'remote:writable' => ['methodName' => 'remoteWritable'],
|
||||
'remote:db_compare' => ['methodName' => 'remoteDbCompare'],
|
||||
'remote:fix_folder_structure' => ['methodName' => 'remoteFixFolderStructure'],
|
||||
'rsync:switch_current' => [],
|
||||
'remote:clear_cache' => ['methodName' => 'remoteClearCache'],
|
||||
'remote:clear_opcache' => ['methodName' => 'remoteClearOpcache'],
|
||||
'rsync:remote_additional_targets' => [],
|
||||
'rsync:switch_current_additional_targets' => [],
|
||||
'remote:hetzner_shared_hosting' => ['methodName' => 'remoteHetznerSharedHosting'],
|
||||
|
||||
'local:echo_release_number' => ['methodName' => 'echoReleaseNumber'],
|
||||
'deploy:unlock' => [],
|
||||
];
|
||||
|
||||
public function __construct(array $tasks = [])
|
||||
{
|
||||
if (count($tasks)) {
|
||||
$this->tasks = $tasks;
|
||||
}
|
||||
$this->loadBasicDeployerFiles();
|
||||
$this->setDefaultConfiguration();
|
||||
$this->loadHostConfiguration();
|
||||
$this->initializeTasks();
|
||||
$this->initializeMainTask();
|
||||
}
|
||||
|
||||
protected function loadBasicDeployerFiles(): void
|
||||
{
|
||||
$commonFile = realpath(__DIR__ . '/../../../../Build/vendor/deployer/deployer/recipe/common.php');
|
||||
if (file_exists($commonFile)) {
|
||||
require $commonFile;
|
||||
}
|
||||
|
||||
new Rsync();
|
||||
}
|
||||
|
||||
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',
|
||||
]);
|
||||
}
|
||||
|
||||
protected function loadHostConfiguration(): void
|
||||
{
|
||||
// read configuration
|
||||
inventory(__DIR__ . '/../../../../Build/hosts.yaml');
|
||||
}
|
||||
|
||||
protected function initializeMainTask(): void
|
||||
{
|
||||
// Main deployment task
|
||||
task('deploy', $this->getTasks())->desc('Deploy your project');
|
||||
|
||||
// Display success message on completion
|
||||
after('deploy', 'success');
|
||||
|
||||
// [Optional] if deploy fails automatically unlock.
|
||||
after('deploy:failed', 'deploy:unlock');
|
||||
}
|
||||
|
||||
protected function getApplicationContext(): string
|
||||
{
|
||||
return $this->has('application_context') ?
|
||||
'TYPO3_CONTEXT="' . $this->get('application_context') . '" ' :
|
||||
'';
|
||||
}
|
||||
|
||||
protected function getSudo(): string
|
||||
{
|
||||
return $this->get('clear_use_sudo') ? 'sudo ' : '';
|
||||
}
|
||||
|
||||
protected function processRemoteTask(callable $callback): void
|
||||
{
|
||||
$config = Context::get()->getConfig();
|
||||
if ($config->get('local') && $config->get('remote_path')) {
|
||||
// backup
|
||||
$contextBackup = Context::pop();
|
||||
$config->set('local', false);
|
||||
|
||||
// create temporary host
|
||||
$hostname = $contextBackup->getHost()->getHostname();
|
||||
print_r($hostname);
|
||||
$host = new Host($hostname);
|
||||
$methods = [
|
||||
'hostname',
|
||||
'user',
|
||||
'port',
|
||||
'configFile',
|
||||
'identityFile',
|
||||
'forwardAgent',
|
||||
'multiplexing',
|
||||
'sshOptions',
|
||||
'sshFlags',
|
||||
'shellCommand',
|
||||
];
|
||||
foreach ($methods as $method) {
|
||||
if ($config->has($method)) {
|
||||
$host->$method($config->get($method));
|
||||
}
|
||||
}
|
||||
foreach ($config->getCollection()->getIterator() as $name => $value) {
|
||||
$host->set($name, $value);
|
||||
}
|
||||
|
||||
// create new context with host
|
||||
$context = new Context($host, $contextBackup->getInput(), $contextBackup->getOutput());
|
||||
Context::push($context);
|
||||
|
||||
// execute the task
|
||||
\Closure::bind($callback, $this)();
|
||||
|
||||
// restore
|
||||
Context::pop();
|
||||
$config->set('local', true);
|
||||
Context::push($contextBackup);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare remote path exists
|
||||
*/
|
||||
public function remotePrepare(): void
|
||||
{
|
||||
if ($this->hasArray('prepare_dirs')) {
|
||||
$callback = function () {
|
||||
$prepareDirs = $this->get('prepare_dirs');
|
||||
foreach ($prepareDirs as $dir) {
|
||||
if (test('[ ! -e "{{remote_path}}/' . $dir . '" ]')) {
|
||||
run($this->getSudo() . ' mkdir -p {{remote_path}}/' . $dir);
|
||||
}
|
||||
}
|
||||
};
|
||||
$this->processRemoteTask($callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installing vendors
|
||||
*/
|
||||
public function localVendors(): void
|
||||
{
|
||||
if (!commandExist('unzip')) {
|
||||
writeln(
|
||||
'<comment>To speed up composer installation setup "unzip" command' .
|
||||
' with PHP zip extension https://goo.gl/sxzFcD</comment>'
|
||||
);
|
||||
}
|
||||
|
||||
// if composer.json exists
|
||||
if (test('[ -d $(echo {{release_path}}) ] && [ -e "{{release_path}}/composer.json" ]')) {
|
||||
run('{{bin/composer}} --working-dir={{release_path}} {{composer_options}};');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creating necessary folders
|
||||
*/
|
||||
public function localCreateFolder(): void
|
||||
{
|
||||
$sudo = $this->getSudo();
|
||||
|
||||
if (test('[ ! -d {{release_path}}/var/cache ]')) {
|
||||
run('mkdir -p {{release_path}}/var/cache');
|
||||
}
|
||||
run($sudo . ' chmod -R 775 {{release_path}}/var/cache');
|
||||
|
||||
if (test('[ ! -d {{release_path}}/var/log ]')) {
|
||||
run('mkdir -p {{release_path}}/var/log');
|
||||
}
|
||||
run($sudo . ' chmod -R 775 {{release_path}}/var/log');
|
||||
}
|
||||
|
||||
public function localWriteRelease(): void
|
||||
{
|
||||
run('echo ' . getEnv('CI_COMMIT_REF_NAME') . ' > {{release_path}}/release');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creating symlinks for shared files and dirs in cms private folder
|
||||
*/
|
||||
public function localShared(): void
|
||||
{
|
||||
if ($this->hasArray('shared_dirs_private')) {
|
||||
$this->sharedFolders($this->get('shared_dirs_private'), 'private');
|
||||
}
|
||||
|
||||
if ($this->hasArray('shared_dirs_public')) {
|
||||
$this->sharedFolders($this->get('shared_dirs_public'), 'public');
|
||||
}
|
||||
|
||||
if ($this->hasArray('shared_files_private')) {
|
||||
$this->sharedFiles($this->get('shared_files_private'), 'private');
|
||||
}
|
||||
|
||||
if ($this->hasArray('shared_files_public')) {
|
||||
$this->sharedFiles($this->get('shared_files_public'), 'public');
|
||||
}
|
||||
}
|
||||
|
||||
protected function sharedFolders(array $sharedDirs, string $folder): void
|
||||
{
|
||||
// Validate shared_dir, find duplicates
|
||||
foreach ($sharedDirs as $a) {
|
||||
foreach ($sharedDirs as $b) {
|
||||
if (
|
||||
$a !== $b
|
||||
&& str_starts_with(rtrim($a, '/') . 'Deployment.php/', rtrim($b, '/') . '/')
|
||||
) {
|
||||
throw new Exception("Can not share same dirs `$a` and `$b`.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($sharedDirs as $dir) {
|
||||
// Check if shared dir does not exist.
|
||||
if (test('[ ! -d {{deploy_path}}/shared/' . $dir . ' ]')) {
|
||||
// Create shared dir if it does not exist.
|
||||
run('mkdir -p {{deploy_path}}/shared/' . $dir);
|
||||
|
||||
// If release contains shared dir, copy that dir from release to shared.
|
||||
if (test('[ -d $(echo {{release_path}}/' . $folder . '/' . $dir . ') ]')) {
|
||||
run('cp -rv {{release_path}}/' . $folder . '/$dir {{deploy_path}}/shared/' . dirname($dir));
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from source.
|
||||
run('rm -rf {{release_path}}/' . $folder . '/' . $dir);
|
||||
|
||||
// Create path to shared dir in release dir if it does not exist.
|
||||
// Symlink will not create the path and will fail otherwise.
|
||||
run('mkdir -p `dirname {{release_path}}/' . $folder . '/' . $dir . '`');
|
||||
|
||||
// Symlink shared dir to release dir
|
||||
run('cd {{deploy_path}} && {{bin/symlink}} ../../../shared/'
|
||||
. $dir . ' {{release_path}}/' . $folder . '/' . $dir);
|
||||
}
|
||||
}
|
||||
|
||||
protected function sharedFiles(array $sharedFiles, string $folder): void
|
||||
{
|
||||
foreach ($sharedFiles as $file) {
|
||||
$dirname = dirname($file);
|
||||
|
||||
// Create dir of shared file
|
||||
run('mkdir -p {{deploy_path}}/shared/' . $dirname);
|
||||
|
||||
// Check if shared file does not exist in shared and file exist in release
|
||||
if (
|
||||
test('[ ! -f {{deploy_path}}/shared/' . $file
|
||||
. ' ] && [ -f {{release_path}}/' . $folder . '/' . $file . ' ]')
|
||||
) {
|
||||
// Copy file in shared dir
|
||||
run('cp -rv {{release_path}}/' . $folder . '/' . $file . ' {{deploy_path}}/shared/' . $file);
|
||||
}
|
||||
|
||||
// Remove from source.
|
||||
if (test('[ -f $(echo {{release_path}}/' . $folder . '/' . $file . ') ]')) {
|
||||
run('rm -rf {{release_path}}/' . $folder . '/' . $file);
|
||||
}
|
||||
|
||||
// Ensure dir is available in release
|
||||
if (test('[ ! -d $(echo {{release_path}}/' . $folder . '/' . $dirname . ') ]')) {
|
||||
run('mkdir -p {{release_path}}/' . $folder . '/' . $dirname);
|
||||
}
|
||||
|
||||
// Touch shared
|
||||
run('touch {{deploy_path}}/shared/' . $file);
|
||||
|
||||
// Symlink shared dir to release dir
|
||||
run('cd {{deploy_path}} && {{bin/symlink}} ../../../shared/'
|
||||
. $file . ' {{release_path}}/' . $folder . '/' . $file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set writable bit on files in "writable_files"
|
||||
*/
|
||||
public function localWritableFiles(): void
|
||||
{
|
||||
if ($this->hasArray('writable_files')) {
|
||||
$paths = $this->get('writable_files');
|
||||
foreach ($paths as $path) {
|
||||
if (test('[ -e "{{release_path}}/' . $path . '" ]')) {
|
||||
run($this->getSudo() . ' chmod g+w {{release_path}}/' . $path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set executable bit on files in "executable_files"
|
||||
*/
|
||||
public function localExecutableFiles(): void
|
||||
{
|
||||
if ($this->hasArray('executable_files')) {
|
||||
$sudo = $this->getSudo();
|
||||
$paths = $this->get('executable_files');
|
||||
foreach ($paths as $path) {
|
||||
if (test('[ -e "{{release_path}}/' . $path . '" ]')) {
|
||||
run($sudo . ' chmod g+x {{release_path}}/' . $path);
|
||||
run($sudo . ' chmod u+x {{release_path}}/' . $path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creating symlink to release
|
||||
*/
|
||||
public function localSymlink(): void
|
||||
{
|
||||
$sudo = $this->getSudo();
|
||||
|
||||
if (test('[ -L "{{deploy_path}}/current" ] || [ -D "{{deploy_path}}/current" ]')) {
|
||||
// Remove previous current
|
||||
run($sudo . ' rm {{deploy_path}}/current');
|
||||
}
|
||||
|
||||
// Atomic override symlink.
|
||||
run($sudo . ' cd {{deploy_path}} && {{bin/symlink}} releases/{{release_name}} current');
|
||||
// Remove release link.
|
||||
run($sudo . ' cd {{deploy_path}} && rm release');
|
||||
}
|
||||
|
||||
/**
|
||||
* Change usergroup for the hole project
|
||||
*/
|
||||
public function remoteChangeGroup(): void
|
||||
{
|
||||
if ($this->get('http_group')) {
|
||||
$callback = function () {
|
||||
$sudo = $this->getSudo();
|
||||
$runOpts = [];
|
||||
if ($sudo) {
|
||||
$runOpts['tty'] = $this->get('writable_tty', false);
|
||||
}
|
||||
|
||||
$path = parse('{{remote_path}}/releases/{{release_name}}/');
|
||||
run($sudo . ' chgrp -H -R ' . $this->get('http_group') . ' ' . $path, $runOpts);
|
||||
run($sudo . ' chmod -R 2755 ' . $path, $runOpts);
|
||||
};
|
||||
$this->processRemoteTask($callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set files and/or folders writable
|
||||
*/
|
||||
public function remoteWritable(): void
|
||||
{
|
||||
if ($this->hasArray('writable_dirs') || $this->hasArray('writable_files')) {
|
||||
$callback = function () {
|
||||
$sudo = $this->getSudo();
|
||||
$runOpts = [];
|
||||
if ($sudo) {
|
||||
$runOpts['tty'] = get('writable_tty', false);
|
||||
}
|
||||
$recursive = $this->get('writable_recursive') ? '-R' : '';
|
||||
$httpGroup = $this->get('http_group', false);
|
||||
$path = parse('{{remote_path}}/releases/{{release_name}}/');
|
||||
|
||||
if ($this->hasArray('writable_dirs')) {
|
||||
foreach ($this->get('writable_dirs') as $dir) {
|
||||
if (test('[ ! -d "' . $path . $dir . '" ]')) {
|
||||
run('mkdir -p ' . $path . $dir);
|
||||
}
|
||||
run($sudo . ' chmod -R 2775 ' . $path . $dir);
|
||||
if ($httpGroup) {
|
||||
run(
|
||||
$sudo . ' chgrp -H ' . $recursive . ' ' . $httpGroup . ' ' . $path . $dir,
|
||||
$runOpts
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->hasArray('writable_files')) {
|
||||
foreach ($this->get('writable_files') as $file) {
|
||||
if (test('[ -e "' . $path . $file . '" ]')) {
|
||||
run($sudo . ' chmod g+w ' . $path . $file);
|
||||
if ($httpGroup) {
|
||||
run($sudo . ' chgrp ' . $httpGroup . ' ' . $path . $file, $runOpts);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
$this->processRemoteTask($callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Database migration
|
||||
*/
|
||||
public function remoteDbCompare(): void
|
||||
{
|
||||
$callback = function () {
|
||||
$path = parse('{{app_container_path}}') ?: parse('{{remote_path}}');
|
||||
|
||||
$php = $this->getSudo() . parse('{{bin/php}} ');
|
||||
if (!str_contains($php, 'TYPO3_CONTEXT')) {
|
||||
$php = $this->getApplicationContext() . $php;
|
||||
}
|
||||
|
||||
$result = run(
|
||||
$php . $this->getTypo3Bin($path, parse('{{remote_path}}')) . ' database:updateschema "*.add,*.change"'
|
||||
);
|
||||
writeln($result);
|
||||
};
|
||||
$this->processRemoteTask($callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix folder structure
|
||||
*/
|
||||
public function remoteFixFolderStructure(): void
|
||||
{
|
||||
$callback = function () {
|
||||
$path = parse('{{app_container_path}}') ?: parse('{{remote_path}}');
|
||||
|
||||
$php = $this->getSudo() . parse('{{bin/php}} ');
|
||||
if (!str_contains($php, 'TYPO3_CONTEXT')) {
|
||||
$php = $this->getApplicationContext() . $php;
|
||||
}
|
||||
|
||||
$result = run(
|
||||
$php . $this->getTypo3Bin($path, parse('{{remote_path}}')) . ' install:fixfolderstructure'
|
||||
);
|
||||
writeln($result);
|
||||
};
|
||||
$this->processRemoteTask($callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cache via typo3 cache:flush
|
||||
*/
|
||||
public function remoteClearCache(): void
|
||||
{
|
||||
$callback = function () {
|
||||
$path = parse('{{app_container_path}}') ?: parse('{{remote_path}}');
|
||||
|
||||
$php = $this->getSudo() . parse('{{bin/php}} ');
|
||||
if (!str_contains($php, 'TYPO3_CONTEXT')) {
|
||||
$php = $this->getApplicationContext() . $php;
|
||||
}
|
||||
|
||||
$result = run($php . $this->getTypo3Bin($path, parse('{{remote_path}}')) . ' cache:flush');
|
||||
writeln($result);
|
||||
};
|
||||
$this->processRemoteTask($callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear opcache via curl on {CI_HOST}/cache.php
|
||||
*/
|
||||
public function remoteClearOpcache(): void
|
||||
{
|
||||
$webDomain = rtrim($this->get('web_domain'), '/');
|
||||
$htaccess = $this->has('htaccess') ? '--user ' . $this->get('htaccess') : '';
|
||||
run('curl ' . $htaccess . ' -sk ' . $webDomain . '/cache.php');
|
||||
}
|
||||
|
||||
public function remoteHetznerSharedHosting(): void
|
||||
{
|
||||
if (
|
||||
($this->has('hetzner_shared_hosting') && $this->get('hetzner_shared_hosting'))
|
||||
&& ($this->has('hetzner_cache_command') && $this->get('hetzner_cache_command'))
|
||||
) {
|
||||
$callback = function () {
|
||||
$result = run($this->get('hetzner_cache_command'));
|
||||
writeln($result);
|
||||
};
|
||||
$this->processRemoteTask($callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleaning up old releases
|
||||
*/
|
||||
public function localCleanup(): void
|
||||
{
|
||||
[$list, $releases] = $this->releasesList();
|
||||
$sudo = $this->get('cleanup_use_sudo') ? 'sudo' : '';
|
||||
$runOpts = [];
|
||||
|
||||
if ($sudo) {
|
||||
$runOpts['tty'] = $this->get('cleanup_tty', false);
|
||||
}
|
||||
|
||||
foreach ($list as $release) {
|
||||
run($sudo . ' rm -rf {{deploy_path}}/releases/' . $release, $runOpts);
|
||||
}
|
||||
|
||||
run('cd {{deploy_path}} && if [ -e release ]; then ' . $sudo . ' rm release; fi', $runOpts);
|
||||
run('cd {{deploy_path}} && if [ -h release ]; then ' . $sudo . ' rm release; fi', $runOpts);
|
||||
|
||||
$this->updateReleaseFile($releases);
|
||||
}
|
||||
|
||||
public function echoReleaseNumber(): void
|
||||
{
|
||||
writeln('This release folder {{release_path}}');
|
||||
}
|
||||
|
||||
/**
|
||||
* Override deployer releases_list
|
||||
*/
|
||||
protected function releasesList(): array
|
||||
{
|
||||
cd('{{deploy_path}}');
|
||||
$keep = (int)$this->get('keep_releases');
|
||||
|
||||
// If there is no releases return empty list.
|
||||
if (!test('[ -d releases ] && [ "$(ls -A releases)" ]')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Will list only dirs in releases.
|
||||
$list = explode("\n", run('cd releases && ls -t -1 --directory */'));
|
||||
|
||||
// Prepare list.
|
||||
$list = array_map(function ($release) {
|
||||
return basename(rtrim(trim($release), '/'));
|
||||
}, $list);
|
||||
|
||||
// Releases list.
|
||||
$releases = [];
|
||||
// Other will be ignored.
|
||||
$metaInfo = $this->getReleasesInformation();
|
||||
$i = 0;
|
||||
foreach ($metaInfo as $release) {
|
||||
if ($i++ < $keep && is_array($release) && count($release) > 1) {
|
||||
$index = array_search($release[1], $list, true);
|
||||
if ($index !== false) {
|
||||
$releases[] = $release;
|
||||
unset($list[$index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [$list, array_reverse($releases)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect releases based on .dep/releases info.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getReleasesInformation(): array
|
||||
{
|
||||
if (test('[ -f .dep/releases ]')) {
|
||||
$csv = run('cat .dep/releases');
|
||||
$releaseInformation = Csv::parse($csv);
|
||||
|
||||
usort($releaseInformation, function ($a, $b) {
|
||||
return strcmp($b[0], $a[0]);
|
||||
});
|
||||
}
|
||||
return $releaseInformation ?? [];
|
||||
}
|
||||
|
||||
protected function getTypo3Bin(string $path, string $testPath): string
|
||||
{
|
||||
if (test('[ -f ' . $testPath . '/releases/{{release_name}}/vendor/bin/typo3cms ]')) {
|
||||
$bin = $path . '/releases/{{release_name}}/vendor/bin/typo3cms';
|
||||
} else {
|
||||
$bin = $path . '/releases/{{release_name}}/vendor/bin/typo3';
|
||||
}
|
||||
return $bin;
|
||||
}
|
||||
|
||||
protected function updateReleaseFile(array $releases): void
|
||||
{
|
||||
cd('{{deploy_path}}');
|
||||
$first = true;
|
||||
foreach ($releases as $release) {
|
||||
if ($first) {
|
||||
$first = false;
|
||||
run('echo "' . implode(',', $release) . '" > .dep/releases');
|
||||
} else {
|
||||
run('echo "' . implode(',', $release) . '" >> .dep/releases');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
457
Classes/Config/Rsync.php
Normal file
457
Classes/Config/Rsync.php
Normal file
@ -0,0 +1,457 @@
|
||||
<?php
|
||||
|
||||
namespace Evoweb\DeployerConfig\Config;
|
||||
|
||||
use Deployer\Host\Localhost;
|
||||
use Deployer\Task\Context;
|
||||
use RuntimeException;
|
||||
|
||||
use function Deployer\run;
|
||||
use function Deployer\runLocally;
|
||||
use function Deployer\test;
|
||||
use function Deployer\writeln;
|
||||
|
||||
class Rsync extends AbstractConfiguration
|
||||
{
|
||||
protected array $defaultConfig = [
|
||||
'exclude' => [
|
||||
'.git',
|
||||
'deploy.php',
|
||||
'deploy.lock',
|
||||
'current',
|
||||
'shared/',
|
||||
'var/*',
|
||||
],
|
||||
'exclude-file' => false,
|
||||
'include' => [
|
||||
'var/',
|
||||
],
|
||||
'include-file' => false,
|
||||
'filter' => [],
|
||||
'filter-file' => false,
|
||||
'filter-perdir' => false,
|
||||
'flags' => 'rzlc',
|
||||
'options' => ['delete', 'delete-after'],
|
||||
'timeout' => 600,
|
||||
];
|
||||
|
||||
protected array $tasks = [
|
||||
'rsync:warmup' => ['methodName' => 'warmup'],
|
||||
'rsync:update_code' => ['methodName' => 'updateCode'],
|
||||
'rsync:remote' => ['methodName' => 'remote'],
|
||||
'rsync:remote_additional_targets' => ['methodName' => 'remoteAdditionalTargets'],
|
||||
'rsync:switch_current' => ['methodName' => 'switchCurrent'],
|
||||
'rsync:switch_current_additional_targets' => ['methodName' => 'switchCurrentAdditionalTargets'],
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->setDefaultConfiguration();
|
||||
$this->initializeTasks();
|
||||
}
|
||||
|
||||
protected function setDefaultConfiguration(): void
|
||||
{
|
||||
$this->set('port', 22);
|
||||
|
||||
$this->set('rsync', []);
|
||||
|
||||
$this->set('rsync_default', $this->defaultConfig);
|
||||
|
||||
$this->set('rsync_src', '{{deploy_path}}');
|
||||
|
||||
$this->set('rsync_dest', '{{user}}@{{hostname}}:\'{{remote_path}}/\'');
|
||||
|
||||
$this->set('rsync_config', $this->rsyncConfig(...));
|
||||
|
||||
$this->set('rsync_flags', $this->rsyncFlags(...));
|
||||
|
||||
$this->set('rsync_excludes', $this->rsyncExcludes(...));
|
||||
$this->set('rsync_excludes_download', $this->rsyncExcludesDownload(...));
|
||||
$this->set('rsync_excludes_upload', $this->rsyncExcludesUpload(...));
|
||||
|
||||
$this->set('rsync_includes', $this->rsyncIncludes(...));
|
||||
|
||||
$this->set('rsync_filter', $this->rsyncFilter(...));
|
||||
|
||||
$this->set('rsync_options', $this->rsyncOptions(...));
|
||||
|
||||
$this->set('rsync_timeout', $this->rsyncTimeout(...));
|
||||
}
|
||||
|
||||
/**
|
||||
* Warmup remote Rsync target
|
||||
*/
|
||||
public function warmup(): void
|
||||
{
|
||||
if (!test('[ -d "{{deploy_path}}" ]')) {
|
||||
runLocally('mkdir -p {{deploy_path}}');
|
||||
}
|
||||
|
||||
if (test('[ -d "{{deploy_path}}" ]')) {
|
||||
$identityFile = $this->get('identityFile') ? ' -i ' . $this->get('identityFile') : '';
|
||||
run(
|
||||
'rsync \
|
||||
-e \'ssh -p {{port}}' . $identityFile . '\' \
|
||||
{{rsync_flags}} \
|
||||
{{rsync_options}} \
|
||||
{{rsync_timeout}} \
|
||||
--exclude=' . escapeshellarg('shared') . ' \
|
||||
{{rsync_excludes_download}} \
|
||||
{{rsync_includes}} \
|
||||
{{rsync_filter}} \
|
||||
{{rsync_dest}} {{deploy_path}}'
|
||||
);
|
||||
} else {
|
||||
writeln('<comment>No destination folder found.</comment>');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy repository content to release folder
|
||||
*/
|
||||
public function updateCode(): void
|
||||
{
|
||||
$pathConfigName = 'ignore_update_code_paths';
|
||||
if ($this->hasArray($pathConfigName)) {
|
||||
$excludes = '';
|
||||
$ignoreUpdateCodePaths = $this->get($pathConfigName);
|
||||
foreach ($ignoreUpdateCodePaths as $path) {
|
||||
$excludes .= ' --exclude=\'' . $path . '\'';
|
||||
}
|
||||
$identityFile = $this->get('identityFile') ? ' -i ' . $this->get('identityFile') : '';
|
||||
run(
|
||||
'rsync \
|
||||
-e \'ssh -p {{port}}' . $identityFile . '\' \
|
||||
{{rsync_flags}} \
|
||||
{{rsync_options}} \
|
||||
{{rsync_timeout}} \
|
||||
--exclude=' . escapeshellarg('shared') . ' \
|
||||
' . $excludes . ' \
|
||||
{{rsync_includes}} \
|
||||
{{rsync_filter}} \
|
||||
{{CI_PROJECT_DIR}}/ \
|
||||
{{release_path}}'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rsync local->remote
|
||||
*/
|
||||
public function remote(): void
|
||||
{
|
||||
$config = $this->get('rsync_config');
|
||||
$server = Context::get()->getHost();
|
||||
$identityFile = $this->get('identityFile') ? ' -i ' . $this->get('identityFile') : '';
|
||||
if ($server instanceof Localhost) {
|
||||
runLocally(
|
||||
'rsync \
|
||||
-e \'ssh -p {{port}}' . $identityFile . '\' \
|
||||
{{rsync_flags}} \
|
||||
{{rsync_options}} \
|
||||
{{rsync_timeout}} \
|
||||
{{rsync_includes}} \
|
||||
--exclude=' . escapeshellarg('shared/') . ' \
|
||||
{{rsync_excludes_upload}} \
|
||||
{{rsync_filter}} \
|
||||
\'{{rsync_src}}/\' {{rsync_dest}}',
|
||||
$config
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
runLocally(
|
||||
'rsync \
|
||||
-e \'ssh -p {{port}}' . $identityFile . '\' \
|
||||
{{rsync_flags}} \
|
||||
{{rsync_options}} \
|
||||
{{rsync_timeout}} \
|
||||
{{rsync_includes}} \
|
||||
--exclude=' . escapeshellarg('shared/') . ' \
|
||||
{{rsync_excludes_upload}} \
|
||||
{{rsync_filter}} \
|
||||
\'{{rsync_src}}/\' {{user}}@{{hostname}}:\'{{remote_path}}/\'',
|
||||
$config
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rsync local->additional targets
|
||||
*/
|
||||
public function remoteAdditionalTargets(): void
|
||||
{
|
||||
$configName = 'additional_remote_targets';
|
||||
if ($this->hasArray($configName)) {
|
||||
$config = $this->get('rsync_config');
|
||||
$identityFile = $this->get('identityFile') ? ' -i ' . $this->get('identityFile') : '';
|
||||
$targets = $this->get($configName);
|
||||
foreach ($targets as $target) {
|
||||
$port = $target['port'] ?? '{{port}}';
|
||||
$user = $target['user'] ?? '{{user}}';
|
||||
$remotePath = $target['remote_path'] ?: '{{remote_path}}';
|
||||
runLocally(
|
||||
'rsync \
|
||||
-e \'ssh -p ' . $port . $identityFile . '\' \
|
||||
{{rsync_flags}} \
|
||||
{{rsync_options}} \
|
||||
{{rsync_timeout}} \
|
||||
{{rsync_includes}} \
|
||||
--exclude=' . escapeshellarg('shared/') . ' \
|
||||
{{rsync_excludes_upload}} \
|
||||
{{rsync_filter}} \
|
||||
\'{{rsync_src}}/\' ' . $user . '@' . $target['hostname'] . ':\'' . $remotePath . '/\'',
|
||||
$config
|
||||
);
|
||||
}
|
||||
} else {
|
||||
writeln('No additional remote target configured');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync current after release was uploaded
|
||||
*/
|
||||
public function switchCurrent(): void
|
||||
{
|
||||
$config = $this->get('rsync_config');
|
||||
$server = Context::get()->getHost();
|
||||
$identityFile = $this->get('identityFile') ? ' -i ' . $this->get('identityFile') : '';
|
||||
if ($server instanceof Localhost) {
|
||||
runLocally(
|
||||
'rsync \
|
||||
-e \'ssh -p {{port}}' . $identityFile . '\' \
|
||||
{{rsync_flags}} \
|
||||
{{rsync_options}} \
|
||||
\'{{rsync_src}}/current\' {{rsync_dest}}',
|
||||
$config
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
runLocally(
|
||||
'rsync \
|
||||
-e \'ssh -p {{port}}' . $identityFile . '\' \
|
||||
{{rsync_flags}} \
|
||||
{{rsync_options}} \
|
||||
\'{{rsync_src}}/current\' {{user}}@{{hostname}}:\'{{remote_path}}/\'',
|
||||
$config
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync current after release was uploaded
|
||||
*/
|
||||
public function switchCurrentAdditionalTargets(): void
|
||||
{
|
||||
$configName = 'additional_remote_targets';
|
||||
if ($this->hasArray($configName)) {
|
||||
$config = $this->get('rsync_config');
|
||||
$identityFile = $this->get('identityFile') ? ' -i ' . $this->get('identityFile') : '';
|
||||
$targets = $this->get($configName);
|
||||
foreach ($targets as $target) {
|
||||
$port = $target['port'] ?? '{{port}}';
|
||||
$user = $target['user'] ?? '{{user}}';
|
||||
$remotePath = $target['remote_path'] ?: '{{remote_path}}';
|
||||
runLocally(
|
||||
'rsync \
|
||||
-e \'ssh -p ' . $port . $identityFile . '\' \
|
||||
{{rsync_flags}} \
|
||||
{{rsync_options}} \
|
||||
\'{{rsync_src}}/current\'' . ' ' . $user . '@' . $target['hostname'] . ':\'' . $remotePath . '/\'',
|
||||
$config
|
||||
);
|
||||
}
|
||||
} else {
|
||||
writeln('No additional remote target configured');
|
||||
}
|
||||
}
|
||||
|
||||
protected function getSource(): string
|
||||
{
|
||||
$source = $this->get('rsync_src');
|
||||
while (is_callable($source)) {
|
||||
$source = $source();
|
||||
}
|
||||
|
||||
if (!trim($source)) {
|
||||
// if $src is not set here rsync is going to do a directory listing
|
||||
// exiting with code 0, since only doing a directory listing clearly
|
||||
// is not what we want to achieve we need to throw an exception
|
||||
throw new RuntimeException('You need to specify a source path.');
|
||||
}
|
||||
|
||||
return $source;
|
||||
}
|
||||
|
||||
protected function getDestination(): string
|
||||
{
|
||||
$destination = $this->get('rsync_dest');
|
||||
while (is_callable($destination)) {
|
||||
$destination = $destination();
|
||||
}
|
||||
|
||||
if (!trim($destination)) {
|
||||
// if $dst is not set here we are going to sync to root
|
||||
// and even worse - depending on rsync flags and permission -
|
||||
// might end up deleting everything we have write permission to
|
||||
throw new RuntimeException('You need to specify a destination path.');
|
||||
}
|
||||
|
||||
return $destination;
|
||||
}
|
||||
|
||||
public function rsyncConfig(): array
|
||||
{
|
||||
$default = $this->get('rsync_default');
|
||||
$config = $this->get('rsync');
|
||||
return Rsync::arrayMergeRecursiveDistinct($default, $config);
|
||||
}
|
||||
|
||||
public function rsyncFlags(): string
|
||||
{
|
||||
$config = $this->get('rsync_config');
|
||||
$flags = $config['flags'] ?? '';
|
||||
return $flags ? ' -' . $flags : '';
|
||||
}
|
||||
|
||||
public function rsyncExcludes(): string
|
||||
{
|
||||
$config = $this->get('rsync_config');
|
||||
$excludes = $config['exclude'] ?? [];
|
||||
$excludeFile = $config['exclude-file'] ?? '';
|
||||
$excludesRsync = '';
|
||||
foreach ($excludes as $exclude) {
|
||||
$excludesRsync .= ' --exclude=' . escapeshellarg($exclude);
|
||||
}
|
||||
|
||||
if (
|
||||
!empty($excludeFile)
|
||||
&& file_exists($excludeFile)
|
||||
&& is_file($excludeFile)
|
||||
&& is_readable($excludeFile)
|
||||
) {
|
||||
$excludesRsync .= ' --exclude-from=' . escapeshellarg($excludeFile);
|
||||
}
|
||||
|
||||
return $excludesRsync;
|
||||
}
|
||||
|
||||
public function rsyncExcludesDownload(): string
|
||||
{
|
||||
$config = $this->get('rsync_config');
|
||||
$excludes = $config['exclude_download'] ?? $config['exclude'] ?? [];
|
||||
$excludeFile = $config['exclude-file'] ?? '';
|
||||
$excludesRsync = '';
|
||||
foreach ($excludes as $exclude) {
|
||||
$excludesRsync .= ' --exclude=' . escapeshellarg($exclude);
|
||||
}
|
||||
|
||||
if (
|
||||
!empty($excludeFile)
|
||||
&& file_exists($excludeFile)
|
||||
&& is_file($excludeFile)
|
||||
&& is_readable($excludeFile)
|
||||
) {
|
||||
$excludesRsync .= ' --exclude-from=' . escapeshellarg($excludeFile);
|
||||
}
|
||||
|
||||
return $excludesRsync;
|
||||
}
|
||||
|
||||
public function rsyncExcludesUpload(): string
|
||||
{
|
||||
$config = $this->get('rsync_config');
|
||||
$excludes = $config['exclude_upload'] ?? $config['exclude'] ?? [];
|
||||
$excludeFile = $config['exclude-file'] ?? '';
|
||||
$excludesRsync = '';
|
||||
foreach ($excludes as $exclude) {
|
||||
$excludesRsync .= ' --exclude=' . escapeshellarg($exclude);
|
||||
}
|
||||
|
||||
if (
|
||||
!empty($excludeFile)
|
||||
&& file_exists($excludeFile)
|
||||
&& is_file($excludeFile)
|
||||
&& is_readable($excludeFile)
|
||||
) {
|
||||
$excludesRsync .= ' --exclude-from=' . escapeshellarg($excludeFile);
|
||||
}
|
||||
|
||||
return $excludesRsync;
|
||||
}
|
||||
|
||||
public function rsyncIncludes(): string
|
||||
{
|
||||
$config = $this->get('rsync_config');
|
||||
$includes = $config['include'] ?? [];
|
||||
$includeFile = $config['include-file'] ?? '';
|
||||
$includesRsync = '';
|
||||
foreach ($includes as $include) {
|
||||
$includesRsync .= ' --include=' . escapeshellarg($include);
|
||||
}
|
||||
if (
|
||||
!empty($includeFile)
|
||||
&& file_exists($includeFile)
|
||||
&& is_file($includeFile)
|
||||
&& is_readable($includeFile)
|
||||
) {
|
||||
$includesRsync .= ' --include-from=' . escapeshellarg($includeFile);
|
||||
}
|
||||
|
||||
return $includesRsync;
|
||||
}
|
||||
|
||||
public function rsyncFilter(): string
|
||||
{
|
||||
$config = $this->get('rsync_config');
|
||||
$filters = $config['filter'] ?? [];
|
||||
$filterFile = $config['filter-file'] ?? '';
|
||||
$filterPerDir = $config['filter-perdir'] ?? '';
|
||||
$filtersRsync = '';
|
||||
foreach ($filters as $filter) {
|
||||
$filtersRsync .= " --filter='$filter'";
|
||||
}
|
||||
if (!empty($filterFile)) {
|
||||
$filtersRsync .= " --filter='merge $filterFile'";
|
||||
}
|
||||
if (!empty($filterPerDir)) {
|
||||
$filtersRsync .= " --filter='dir-merge $filterPerDir'";
|
||||
}
|
||||
return $filtersRsync;
|
||||
}
|
||||
|
||||
public function rsyncOptions(): string
|
||||
{
|
||||
$config = $this->get('rsync_config');
|
||||
$options = $config['options'] ?? [];
|
||||
$optionsRsync = [];
|
||||
foreach ($options as $option) {
|
||||
$optionsRsync[] = '--' . $option;
|
||||
}
|
||||
return ' ' . implode(' ', $optionsRsync);
|
||||
}
|
||||
|
||||
public function rsyncTimeout(): string
|
||||
{
|
||||
$config = $this->get('rsync_config');
|
||||
$timeout = $config['timeout'] ?? 0;
|
||||
|
||||
return $timeout ? ' --timeout=' . $timeout : '';
|
||||
}
|
||||
|
||||
public static function arrayMergeRecursiveDistinct(array $array1, array &$array2): array
|
||||
{
|
||||
$merged = $array1;
|
||||
|
||||
foreach ($array2 as $key => &$value) {
|
||||
if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
|
||||
$merged[$key] = self::arrayMergeRecursiveDistinct($merged[$key], $value);
|
||||
} else {
|
||||
$merged[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $merged;
|
||||
}
|
||||
}
|
||||
18
Classes/Services/Environment.php
Normal file
18
Classes/Services/Environment.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Evoweb\DeployerConfig\Services;
|
||||
|
||||
use Composer\Script\Event;
|
||||
|
||||
class Environment
|
||||
{
|
||||
public static function setContext(Event $event)
|
||||
{
|
||||
putenv('CI_PROJECT_DIR=' . dirname(getcwd()));
|
||||
putenv('ENVIRONMENT_NAME=' . strtoupper(end($_SERVER['argv'])));
|
||||
$extras = $event->getComposer()->getPackage()->getExtra();
|
||||
foreach (($extras['deployer'] ?? []) as $key => $value) {
|
||||
putenv(strtoupper($key) . '=' . $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
85
README.md
Normal file
85
README.md
Normal file
@ -0,0 +1,85 @@
|
||||
## To install add Build/composer.json to the project
|
||||
```json
|
||||
{
|
||||
"name": "cp/build",
|
||||
"description": "Project deployment with support for deployphp",
|
||||
"repositories": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://gitlab.cp-compartner.de/cpcompartner/build-config.git"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"cp/build-config": "^1.7.3"
|
||||
},
|
||||
"scripts": {
|
||||
"staging": "dep -vv --file=vendor/cp/build-config/deploy.php deploy staging --branch",
|
||||
"production": "dep --file=vendor/cp/build-config/deploy.php deploy production --tag",
|
||||
|
||||
"phpcs-fixer": "php-cs-fixer fix --config=vendor/cp/build-config/php_cs.php",
|
||||
"phpstan": "phpstan analyse --configuration=vendor/cp/build-config/phpstan.neon",
|
||||
"rector": "rector process --config=vendor/cp/build-config/rector.php",
|
||||
"php72-lint": "find ../packages ../private/typo3conf -name '*.php' -exec php7.2 -l {} 1> /dev/null \\;",
|
||||
"php74-lint": "find ../packages ../private/typo3conf -name '*.php' -exec php7.4 -l {} 1> /dev/null \\;"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage per composer
|
||||
|
||||
Install it with:
|
||||
```bash
|
||||
./composer update
|
||||
```
|
||||
|
||||
Deploy branch develop to staging
|
||||
```bash
|
||||
./composer staging develop
|
||||
```
|
||||
|
||||
Deploy tag 1.0.0 to production
|
||||
```bash
|
||||
./composer production 1.0.0
|
||||
```
|
||||
|
||||
PHPCS fixer usage:
|
||||
```bash
|
||||
./composer phpcs-fixer ../package/cp_sitepackage
|
||||
```
|
||||
|
||||
PHPStan usage:
|
||||
```bash
|
||||
./composer phpstan ../package/cp_sitepackage
|
||||
```
|
||||
|
||||
TYPO3 Rector usage:
|
||||
```bash
|
||||
./composer rector ../package/cp_sitepackage
|
||||
```
|
||||
|
||||
## Overriding default values per host in hosts.yaml
|
||||
```yaml
|
||||
.base: &base
|
||||
local: true
|
||||
alias: '{{CI_HOST}}'
|
||||
deploy_path: '{{CI_PROJECT_DIR}}/cache/{{ENVIRONMENT_NAME}}'
|
||||
composer_options: '{{composer_action}} --verbose --prefer-dist --no-progress --no-interaction --no-dev --optimize-autoloader --no-suggest --ignore-platform-reqs'
|
||||
|
||||
prepare_dirs:
|
||||
- .dep
|
||||
- releases
|
||||
- shared/fileadmin
|
||||
|
||||
shared_dirs_private:
|
||||
- fileadmin
|
||||
|
||||
writable_dirs:
|
||||
- private/typo3temp
|
||||
- var
|
||||
writable_files:
|
||||
- private/typo3conf/PackageStates.php
|
||||
|
||||
rsync:
|
||||
exclude:
|
||||
- '-,p private/typo3temp/var/Cache'
|
||||
```
|
||||
27
alias.sh
Normal file
27
alias.sh
Normal file
@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
function composer() {
|
||||
mkdir -p "${HOME}/.config/composer"
|
||||
mkdir -p "${HOME}/.cache/composer"
|
||||
docker run -t \
|
||||
--user $(id -u):33 \
|
||||
--env COMPOSER_CACHE_DIR=/cache \
|
||||
--env SSH_AUTH_SOCK=/ssh-agent \
|
||||
--env CI_HOST \
|
||||
--env CI_PROJECT_DIR \
|
||||
--env ENVIRONMENT_NAME \
|
||||
--env INSTANCE_ID \
|
||||
--env ADDITIONAL_CONFIG_FILE \
|
||||
--env TYPO3_CONTEXT \
|
||||
--env STAGE \
|
||||
--network db \
|
||||
--volume "$(readlink -f ${SSH_AUTH_SOCK})":/ssh-agent \
|
||||
--volume /etc/passwd:/etc/passwd:ro \
|
||||
--volume "${HOME}":"${HOME}" \
|
||||
--volume "${HOME}/.config/composer":/tmp \
|
||||
--volume "${HOME}/.cache/composer":/cache \
|
||||
--volume "${CI_PROJECT_DIR}":"${CI_PROJECT_DIR}" \
|
||||
--volume "${PWD%}":/app \
|
||||
evoweb/php:composer $@
|
||||
}
|
||||
alias composer=composer
|
||||
16
composer.json
Normal file
16
composer.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "evoweb/deployer-config",
|
||||
"description": "TYPO3 CMS Distribution with utilization of deployphp",
|
||||
"type": "project",
|
||||
|
||||
"require": {
|
||||
"deployer/deployer": "^v6.9 || dev-master",
|
||||
"phpdocumentor/reflection-docblock": "^5.3"
|
||||
},
|
||||
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Evoweb\\DeployerConfig\\": "Classes/"
|
||||
}
|
||||
}
|
||||
}
|
||||
3
deploy.php
Normal file
3
deploy.php
Normal file
@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
new \Evoweb\DeployerConfig\Config\Deployment();
|
||||
Loading…
Reference in New Issue
Block a user