recipe/magento2.php
<?php
namespace Deployer;
require_once __DIR__ . '/common.php';
require_once __DIR__ . '/../contrib/cachetool.php';
use Deployer\Exception\ConfigurationException;
use Deployer\Exception\GracefulShutdownException;
use Deployer\Exception\RunException;
use Deployer\Host\Host;
const CONFIG_IMPORT_NEEDED_EXIT_CODE = 2;
const DB_UPDATE_NEEDED_EXIT_CODE = 2;
const MAINTENANCE_MODE_ACTIVE_OUTPUT_MSG = 'maintenance mode is active';
const ENV_CONFIG_FILE_PATH = 'app/etc/env.php';
const TMP_ENV_CONFIG_FILE_PATH = 'app/etc/env_tmp.php';
add('recipes', ['magento2']);
// Configuration
// By default setup:static-content:deploy uses `en_US`.
// To change that, simply put `set('static_content_locales', 'en_US de_DE');`
// in you deployer script.
set('static_content_locales', 'en_US');
// Configuration
// You can also set the themes to run against. By default it'll deploy
// all themes - `add('magento_themes', ['Magento/luma', 'Magento/backend']);`
// If the themes are set as a simple list of strings, then all languages defined in {{static_content_locales}} are
// compiled for the given themes.
// Alternatively The themes can be defined as an associative array, where the key represents the theme name and
// the key contains the languages for the compilation (for this specific theme)
// Example:
// set('magento_themes', ['Magento/luma']); - Will compile this theme with every language from {{static_content_locales}}
// set('magento_themes', [
// 'Magento/luma' => null, - Will compile all languages from {{static_content_locales}} for Magento/luma
// 'Custom/theme' => 'en_US fr_FR' - Will compile only en_US and fr_FR for Custom/theme
// 'Custom/another' => '{{static_content_locales}} it_IT' - Will compile all languages from {{static_content_locales}} + it_IT for Custom/another
// ]); - Will compile this theme with every language
set('magento_themes', [
]);
// Static content deployment options, e.g. '--no-parent'
set('static_deploy_options', '');
// Deploy frontend and adminhtml together as default
set('split_static_deployment', false);
// Use the default languages for the backend as default
set('static_content_locales_backend', '{{static_content_locales}}');
// backend themes to deploy. Only used if split_static_deployment=true
// This setting supports the same options/structure as {{magento_themes}}
set('magento_themes_backend', ['Magento/backend' => null]);
// Configuration
// Also set the number of concurrent jobs to run. The default is 1
// Update using: `set('static_content_jobs', '1');`
set('static_content_jobs', '1');
set('content_version', function () {
return time();
});
// Magento directory relative to repository root. Use "." (default) if it is not located in a subdirectory
set('magento_dir', '.');
set('shared_files', [
'{{magento_dir}}/app/etc/env.php',
'{{magento_dir}}/var/.maintenance.ip',
]);
set('shared_dirs', [
'{{magento_dir}}/var/composer_home',
'{{magento_dir}}/var/log',
'{{magento_dir}}/var/export',
'{{magento_dir}}/var/report',
'{{magento_dir}}/var/import',
'{{magento_dir}}/var/import_history',
'{{magento_dir}}/var/session',
'{{magento_dir}}/var/importexport',
'{{magento_dir}}/var/backups',
'{{magento_dir}}/var/tmp',
'{{magento_dir}}/pub/sitemap',
'{{magento_dir}}/pub/media',
'{{magento_dir}}/pub/static/_cache',
]);
set('writable_dirs', [
'{{magento_dir}}/var',
'{{magento_dir}}/pub/static',
'{{magento_dir}}/pub/media',
'{{magento_dir}}/generated',
'{{magento_dir}}/var/page_cache',
]);
set('clear_paths', [
'{{magento_dir}}/generated/*',
'{{magento_dir}}/pub/static/_cache/*',
'{{magento_dir}}/var/generation/*',
'{{magento_dir}}/var/cache/*',
'{{magento_dir}}/var/page_cache/*',
'{{magento_dir}}/var/view_preprocessed/*',
]);
set('bin/magento', '{{release_or_current_path}}/{{magento_dir}}/bin/magento');
set('magento_version', function () {
// detect version
$versionOutput = run('{{bin/php}} {{bin/magento}} --version');
preg_match('/(\d+\.?)+(-p\d+)?$/', $versionOutput, $matches);
return $matches[0] ?? '2.0';
});
set('config_import_needed', function () {
// detect if app:config:import is needed
try {
run('{{bin/php}} {{bin/magento}} app:config:status');
} catch (RunException $e) {
if ($e->getExitCode() == CONFIG_IMPORT_NEEDED_EXIT_CODE) {
return true;
}
throw $e;
}
return false;
});
set('database_upgrade_needed', function () {
// detect if setup:upgrade is needed
try {
run('{{bin/php}} {{bin/magento}} setup:db:status');
} catch (RunException $e) {
if ($e->getExitCode() == DB_UPDATE_NEEDED_EXIT_CODE) {
return true;
}
throw $e;
}
return false;
});
// Deploy without setting maintenance mode if possible
set('enable_zerodowntime', true);
// Tasks
// To work correctly with artifact deployment, it is necessary to set the MAGE_MODE correctly in `app/etc/config.php`
// e.g.
// ```php
// 'MAGE_MODE' => 'production'
// ```
desc('Compiles magento di');
task('magento:compile', function () {
run("{{bin/php}} {{bin/magento}} setup:di:compile");
run('cd {{release_or_current_path}}/{{magento_dir}} && {{bin/composer}} dump-autoload -o');
});
// To work correctly with artifact deployment it is necessary to set `system/dev/js` , `system/dev/css` and `system/dev/template`
// in `app/etc/config.php`, e.g.:
// ```php
// 'system' => [
// 'default' => [
// 'dev' => [
// 'js' => [
// 'merge_files' => '1',
// 'minify_files' => '1'
// ],
// 'css' => [
// 'merge_files' => '1',
// 'minify_files' => '1'
// ],
// 'template' => [
// 'minify_html' => '1'
// ]
// ]
// ]
// ```
desc('Deploys assets');
task('magento:deploy:assets', function () {
$themesToCompile = '';
if (get('split_static_deployment')) {
invoke('magento:deploy:assets:adminhtml');
invoke('magento:deploy:assets:frontend');
} else {
if (count(get('magento_themes')) > 0) {
$themes = array_is_list(get('magento_themes')) ? get('magento_themes') : array_keys(get('magento_themes'));
foreach ($themes as $theme) {
$themesToCompile .= ' -t ' . $theme;
}
}
run("{{bin/php}} {{release_or_current_path}}/bin/magento setup:static-content:deploy -f --content-version={{content_version}} {{static_deploy_options}} {{static_content_locales}} $themesToCompile -j {{static_content_jobs}}");
}
});
desc('Deploys assets for backend only');
task('magento:deploy:assets:adminhtml', function () {
magentoDeployAssetsSplit('backend');
});
desc('Deploys assets for frontend only');
task('magento:deploy:assets:frontend', function () {
magentoDeployAssetsSplit('frontend');
});
/**
* @phpstan-param 'frontend'|'backend' $area
*
* @throws ConfigurationException
*/
function magentoDeployAssetsSplit(string $area)
{
if (!in_array($area, ['frontend', 'backend'], true)) {
throw new ConfigurationException("\$area must be either 'frontend' or 'backend', '$area' given");
}
$isFrontend = $area === 'frontend';
$suffix = $isFrontend
? ''
: '_backend';
$themesConfig = get("magento_themes$suffix");
$defaultLanguages = get("static_content_locales$suffix");
$useDefaultLanguages = array_is_list($themesConfig);
/** @var list<string> $themes */
$themes = $useDefaultLanguages
? array_values($themesConfig)
: array_keys($themesConfig);
$staticContentArea = $isFrontend
? 'frontend'
: 'adminhtml';
if ($useDefaultLanguages) {
$themes = '-t ' . implode(' -t ', $themes);
run("{{bin/php}} {{bin/magento}} setup:static-content:deploy -f --area=$staticContentArea --content-version={{content_version}} {{static_deploy_options}} $defaultLanguages $themes -j {{static_content_jobs}}");
return;
}
foreach ($themes as $theme) {
$languages = parse($themesConfig[$theme] ?? $defaultLanguages);
run("{{bin/php}} {{bin/magento}} setup:static-content:deploy -f --area=$staticContentArea --content-version={{content_version}} {{static_deploy_options}} $languages -t $theme -j {{static_content_jobs}}");
}
}
desc('Syncs content version');
task('magento:sync:content_version', function () {
$timestamp = time();
on(select('all'), function (Host $host) use ($timestamp) {
$host->set('content_version', $timestamp);
});
})->once();
before('magento:deploy:assets', 'magento:sync:content_version');
desc('Enables maintenance mode');
task('magento:maintenance:enable', function () {
// do not use {{bin/magento}} because it would be in "release" but the maintenance mode must be set in "current"
run("if [ -d $(echo {{current_path}}) ]; then {{bin/php}} {{current_path}}/{{magento_dir}}/bin/magento maintenance:enable; fi");
});
desc('Disables maintenance mode');
task('magento:maintenance:disable', function () {
// do not use {{bin/magento}} because it would be in "release" but the maintenance mode must be set in "current"
run("if [ -d $(echo {{current_path}}) ]; then {{bin/php}} {{current_path}}/{{magento_dir}}/bin/magento maintenance:disable; fi");
});
desc('Set maintenance mode if needed');
task('magento:maintenance:enable-if-needed', function () {
! get('enable_zerodowntime') || get('database_upgrade_needed') || get('config_import_needed') ?
invoke('magento:maintenance:enable') :
writeln('Config and database up to date => no maintenance mode');
});
desc('Config Import');
task('magento:config:import', function () {
if (get('config_import_needed')) {
run('{{bin/php}} {{bin/magento}} app:config:import --no-interaction');
} else {
writeln('App config is up to date => import skipped');
}
});
desc('Upgrades magento database');
task('magento:upgrade:db', function () {
if (get('database_upgrade_needed')) {
run("{{bin/php}} {{bin/magento}} setup:db-schema:upgrade --no-interaction");
run("{{bin/php}} {{bin/magento}} setup:db-data:upgrade --no-interaction");
} else {
writeln('Database schema is up to date => upgrade skipped');
}
})->once();
desc('Flushes Magento Cache');
task('magento:cache:flush', function () {
run("{{bin/php}} {{bin/magento}} cache:flush");
});
desc('Magento2 deployment operations');
task('deploy:magento', [
'magento:build',
'magento:maintenance:enable-if-needed',
'magento:config:import',
'magento:upgrade:db',
'magento:maintenance:disable',
]);
desc('Magento2 build operations');
task('magento:build', [
'magento:compile',
'magento:deploy:assets',
]);
desc('Deploys your project');
task('deploy', [
'deploy:prepare',
'deploy:vendors',
'deploy:clear_paths',
'deploy:magento',
'deploy:publish',
]);
after('deploy:symlink', 'magento:cache:flush');
after('deploy:failed', 'magento:maintenance:disable');
// Artifact deployment section
// The file the artifact is saved to
set('artifact_file', 'artifact.tar.gz');
// The directory the artifact is saved in
set('artifact_dir', 'artifacts');
// Points to a file with a list of files to exclude from packaging.
// The format is as with the `tar --exclude-from=[file]` option
set('artifact_excludes_file', 'artifacts/excludes');
// If set to true, the artifact is built from a clean copy of the project repository instead of the current working directory
set('build_from_repo', false);
// Set this value if "build_from_repo" is set to true. The target to deploy must also be set with "--branch", "--tag" or "--revision"
set('repository', null);
// The relative path to the artifact file. If the directory does not exist, it will be created
set('artifact_path', function () {
if (!testLocally('[ -d {{artifact_dir}} ]')) {
runLocally('mkdir -p {{artifact_dir}}');
}
return get('artifact_dir') . '/' . get('artifact_file');
});
// The location of the tar command. On MacOS you should have installed gtar, as it supports the required settings
set('bin/tar', function () {
if (commandExist('gtar')) {
return which('gtar');
} else {
return which('tar');
}
});
// tasks section
desc('Packages all relevant files in an artifact.');
task('artifact:package', function () {
if (!test('[ -f {{artifact_excludes_file}} ]')) {
throw new GracefulShutdownException(
"No artifact excludes file provided, provide one at artifacts/excludes or change location",
);
}
run('{{bin/tar}} --exclude-from={{artifact_excludes_file}} -czf {{artifact_path}} -C {{release_or_current_path}} .');
});
desc('Uploads artifact in release folder for extraction.');
task('artifact:upload', function () {
upload(get('artifact_path'), '{{release_path}}');
});
desc('Extracts artifact in release path.');
task('artifact:extract', function () {
run('{{bin/tar}} -xzpf {{release_path}}/{{artifact_file}} -C {{release_path}}');
run('rm -rf {{release_path}}/{{artifact_file}}');
});
desc('Clears generated files prior to building.');
task('build:remove-generated', function () {
run('rm -rf generated/*');
});
desc('Prepare local artifact build');
task('build:prepare', function () {
if (!currentHost()->get('local')) {
throw new GracefulShutdownException('Artifact can only be built locally, you provided a non local host');
}
$buildDir = get('build_from_repo') ? get('artifact_dir') . '/repo' : '.';
set('deploy_path', $buildDir);
set('release_path', $buildDir);
set('current_path', $buildDir);
if (!get('build_from_repo')) {
return;
}
$repository = (string) get('repository');
if ($repository === '') {
throw new GracefulShutdownException('You must specify the "repository" option.');
}
run('rm -rf {{release_or_current_path}}');
run('git clone {{repository}} {{release_or_current_path}}');
run('git -C {{release_or_current_path}} checkout --force {{target}}');
});
desc('Builds an artifact.');
task('artifact:build', [
'build:prepare',
'build:remove-generated',
'deploy:vendors',
'magento:compile',
'magento:deploy:assets',
'artifact:package',
]);
// Array of shared files that will be added to the default shared_files without overriding
set('additional_shared_files', []);
// Array of shared directories that will be added to the default shared_dirs without overriding
set('additional_shared_dirs', []);
desc('Adds additional files and dirs to the list of shared files and dirs');
task('deploy:additional-shared', function () {
add('shared_files', get('additional_shared_files'));
add('shared_dirs', get('additional_shared_dirs'));
});
/**
* Update cache id_prefix on deploy so that you are compiling against a fresh cache
* Reference Issue: https://github.com/davidalger/capistrano-magento2/issues/151
* To use this feature, add the following to your deployer scripts:
* ```php
* after('deploy:shared', 'magento:set_cache_prefix');
* after('deploy:magento', 'magento:cleanup_cache_prefix');
* ```
**/
desc('Update cache id_prefix');
task('magento:set_cache_prefix', function () {
//download current env config
$tmpConfigFile = tempnam(sys_get_temp_dir(), 'deployer_config');
download('{{deploy_path}}/shared/' . ENV_CONFIG_FILE_PATH, $tmpConfigFile);
$envConfigArray = include($tmpConfigFile);
//set prefix to `alias_releasename_`
$prefixUpdate = get('alias') . '_' . get('release_name') . '_';
//check for preload keys and update
if (isset($envConfigArray['cache']['frontend']['default']['backend_options']['preload_keys'])) {
$oldPrefix = $envConfigArray['cache']['frontend']['default']['id_prefix'];
$preloadKeys = $envConfigArray['cache']['frontend']['default']['backend_options']['preload_keys'];
$newPreloadKeys = [];
foreach ($preloadKeys as $preloadKey) {
$newPreloadKeys[] = preg_replace('/^' . $oldPrefix . '/', $prefixUpdate, $preloadKey);
}
$envConfigArray['cache']['frontend']['default']['backend_options']['preload_keys'] = $newPreloadKeys;
}
//update id_prefix to include release name
$envConfigArray['cache']['frontend']['default']['id_prefix'] = $prefixUpdate;
$envConfigArray['cache']['frontend']['page_cache']['id_prefix'] = $prefixUpdate;
//Generate configuration array as string
$envConfigStr = '<?php return ' . var_export($envConfigArray, true) . ';';
file_put_contents($tmpConfigFile, $envConfigStr);
//upload updated config to server
upload($tmpConfigFile, '{{deploy_path}}/shared/' . TMP_ENV_CONFIG_FILE_PATH);
//cleanup tmp file
unlink($tmpConfigFile);
//delete the symlink for env.php
run('rm {{release_or_current_path}}/' . ENV_CONFIG_FILE_PATH);
//link the env to the tmp version
run('{{bin/symlink}} {{deploy_path}}/shared/' . TMP_ENV_CONFIG_FILE_PATH . ' {{release_path}}/' . ENV_CONFIG_FILE_PATH);
});
/**
* After successful deployment, move the tmp_env.php file to env.php ready for next deployment
*/
desc('Cleanup cache id_prefix env files');
task('magento:cleanup_cache_prefix', function () {
run('rm {{deploy_path}}/shared/' . ENV_CONFIG_FILE_PATH);
run('rm {{release_or_current_path}}/' . ENV_CONFIG_FILE_PATH);
run('mv {{deploy_path}}/shared/' . TMP_ENV_CONFIG_FILE_PATH . ' {{deploy_path}}/shared/' . ENV_CONFIG_FILE_PATH);
// Symlink shared dir to release dir
run('{{bin/symlink}} {{deploy_path}}/shared/' . ENV_CONFIG_FILE_PATH . ' {{release_path}}/' . ENV_CONFIG_FILE_PATH);
});
/**
* Remove cron from crontab and kill running cron jobs
* To use this feature, add the following to your deployer scripts:
* ```php
* after('magento:maintenance:enable-if-needed', 'magento:cron:stop');
* ```
*/
desc('Remove cron from crontab and kill running cron jobs');
task('magento:cron:stop', function () {
if (has('previous_release')) {
run('{{bin/php}} {{previous_release}}/{{magento_dir}}/bin/magento cron:remove');
}
run('pgrep -U "$(id -u)" -f "bin/magento +(cron:run|queue:consumers:start)" | xargs -r kill');
});
/**
* Install cron in crontab
* To use this feature, add the following to your deployer scripts:
* ```php
* after('magento:upgrade:db', 'magento:cron:install');
* ```
*/
desc('Install cron in crontab');
task('magento:cron:install', function () {
run('cd {{release_or_current_path}}');
run('{{bin/php}} {{bin/magento}} cron:install');
});
desc('Prepares an artifact on the target server');
task('artifact:prepare', [
'deploy:info',
'deploy:setup',
'deploy:lock',
'deploy:release',
'artifact:upload',
'artifact:extract',
'deploy:additional-shared',
'deploy:shared',
'deploy:writable',
]);
desc('Executes the tasks after artifact is released');
task('artifact:finish', [
'magento:cache:flush',
'cachetool:clear:opcache',
'deploy:cleanup',
'deploy:unlock',
'deploy:success',
]);
desc('Actually releases the artifact deployment');
task('artifact:deploy', [
'artifact:prepare',
'magento:maintenance:enable-if-needed',
'magento:config:import',
'magento:upgrade:db',
'magento:maintenance:disable',
'deploy:symlink',
'artifact:finish',
]);
fail('artifact:deploy', 'deploy:failed');