deployer/recipe/common.php

451 lines
14 KiB
PHP
Raw Normal View History

<?php
2016-01-11 12:51:59 +07:00
/* (c) Anton Medvedev <anton@medv.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
2015-02-15 15:28:44 +03:00
/**
* Common parameters.
*/
2015-01-03 00:04:00 +03:00
set('keep_releases', 3);
2015-02-15 15:28:44 +03:00
set('shared_dirs', []);
set('shared_files', []);
2015-07-05 14:53:49 +02:00
set('copy_dirs', []);
2015-02-15 23:24:08 +03:00
set('writable_dirs', []);
set('writable_use_sudo', true); // Using sudo in writable commands?
set('http_user', null);
set('clear_paths', []); // Relative path from deploy_path
set('clear_use_sudo', true); // Using sudo in clean commands?
/**
* Environment vars
*/
env('timezone', 'UTC');
2015-02-25 13:04:18 +03:00
env('branch', ''); // Branch to deploy.
env('env_vars', ''); // For Composer installation. Like SYMFONY_ENV=prod
2015-06-04 20:00:14 +07:00
env('composer_options', 'install --no-dev --verbose --prefer-dist --optimize-autoloader --no-progress --no-interaction');
2015-06-17 19:30:25 +02:00
env('git_cache', function () { //whether to use git cache - faster cloning by borrowing objects from existing clones.
$gitVersion = run('git version');
$regs = [];
if (preg_match('/((\d+\.?)+)/', $gitVersion, $regs)) {
$version = $regs[1];
} else {
$version = "1.0.0";
}
return version_compare($version, '2.3', '>=');
2015-06-16 11:31:49 +02:00
});
env('release_name', date('YmdHis')); // name of folder in releases
/**
* Custom bins.
*/
env('bin/php', function () {
return run('which php')->toString();
});
env('bin/git', function () {
return run('which git')->toString();
});
env('bin/composer', function () {
if (commandExist('composer')) {
$composer = run('which composer')->toString();
}
if (empty($composer)) {
run("cd {{release_path}} && curl -sS https://getcomposer.org/installer | php");
$composer = '{{bin/php}} composer.phar';
}
return $composer;
});
/**
* Default arguments and options.
*/
argument('stage', \Symfony\Component\Console\Input\InputArgument::OPTIONAL, 'Run tasks only on this server or group of servers.');
option('tag', null, \Symfony\Component\Console\Input\InputOption::VALUE_OPTIONAL, 'Tag to deploy.');
option('revision', null, \Symfony\Component\Console\Input\InputOption::VALUE_OPTIONAL, 'Revision to deploy.');
2015-01-03 00:04:00 +03:00
/**
* Rollback to previous release.
*/
task('rollback', function () {
2015-01-03 14:48:38 +03:00
$releases = env('releases_list');
if (isset($releases[1])) {
2015-03-23 09:19:12 +07:00
$releaseDir = "{{deploy_path}}/releases/{$releases[1]}";
2015-01-03 14:48:38 +03:00
// Symlink to old release.
2015-03-23 09:19:12 +07:00
run("cd {{deploy_path}} && ln -nfs $releaseDir current");
// Remove release
2015-03-23 09:19:12 +07:00
run("rm -rf {{deploy_path}}/releases/{$releases[0]}");
2015-01-03 14:48:38 +03:00
if (isVerbose()) {
writeln("Rollback to `{$releases[1]}` release was successful.");
}
} else {
writeln("<comment>No more releases you can revert to.</comment>");
}
})->desc('Rollback to previous release');
/**
* Preparing server for deployment.
*/
task('deploy:prepare', function () {
\Deployer\Task\Context::get()->getServer()->connect();
2015-05-13 19:00:12 +07:00
// Check if shell is POSIX-compliant
2015-05-12 00:11:24 +07:00
try {
2015-05-19 14:58:32 +07:00
cd(''); // To run command as raw.
2015-09-04 00:10:48 +07:00
$result = run('echo $0')->toString();
if ($result == 'stdin: is not a tty') {
throw new RuntimeException(
"Looks like ssh inside another ssh.\n" .
"Help: http://goo.gl/gsdLt9"
);
}
2015-05-12 00:11:24 +07:00
} catch (\RuntimeException $e) {
2015-05-13 16:52:34 +07:00
$formatter = \Deployer\Deployer::get()->getHelper('formatter');
$errorMessage = [
"Shell on your server is not POSIX-compliant. Please change to sh, bash or similar.",
"Usually, you can change your shell to bash by running: chsh -s /bin/bash",
];
write($formatter->formatBlock($errorMessage, 'error', true));
2015-05-13 13:26:48 +07:00
throw $e;
2015-05-12 00:11:24 +07:00
}
// Set the deployment timezone
if (!date_default_timezone_set(env('timezone'))) {
date_default_timezone_set('UTC');
}
run('if [ ! -d {{deploy_path}} ]; then mkdir -p {{deploy_path}}; fi');
// Check for existing /current directory (not symlink)
$result = run('if [ ! -L {{deploy_path}}/current ] && [ -d {{deploy_path}}/current ]; then echo true; fi')->toBool();
2016-03-14 12:33:59 +01:00
if ($result) {
throw new \RuntimeException('There already is a directory (not symlink) named "current" in ' . env('deploy_path') . '. Remove this directory so it can be replaced with a symlink for atomic deployments.');
}
// Create releases dir.
2015-03-23 09:19:12 +07:00
run("cd {{deploy_path}} && if [ ! -d releases ]; then mkdir releases; fi");
// Create shared dir.
2015-03-23 09:19:12 +07:00
run("cd {{deploy_path}} && if [ ! -d shared ]; then mkdir shared; fi");
})->desc('Preparing server for deploy');
2015-01-03 00:04:00 +03:00
/**
* Return release path.
*/
env('release_path', function () {
2015-03-23 09:19:12 +07:00
return str_replace("\n", '', run("readlink {{deploy_path}}/release"));
2015-01-03 00:04:00 +03:00
});
/**
2015-01-03 00:04:00 +03:00
* Release
*/
2015-01-03 00:04:00 +03:00
task('deploy:release', function () {
$releasePath = "{{deploy_path}}/releases/{{release_name}}";
2015-02-15 23:24:08 +03:00
$i = 0;
while (is_dir(env()->parse($releasePath)) && $i < 42) {
$releasePath .= '.' . ++$i;
}
2015-01-03 00:04:00 +03:00
run("mkdir $releasePath");
run("cd {{deploy_path}} && if [ -h release ]; then rm release; fi");
2015-01-03 00:04:00 +03:00
2015-03-23 09:19:12 +07:00
run("ln -s $releasePath {{deploy_path}}/release");
2015-01-03 00:04:00 +03:00
})->desc('Prepare release');
/**
2015-01-03 00:04:00 +03:00
* Update project code
*/
2015-01-03 00:04:00 +03:00
task('deploy:update_code', function () {
2015-01-03 15:01:48 +03:00
$repository = get('repository');
$branch = env('branch');
$git = env('bin/git');
2015-06-16 11:31:49 +02:00
$gitCache = env('git_cache');
$depth = $gitCache ? '' : '--depth 1';
if (input()->hasOption('tag')) {
$tag = input()->getOption('tag');
2016-03-19 22:29:17 +07:00
} elseif (input()->hasOption('revision')) {
$revision = input()->getOption('revision');
}
$at = '';
if (!empty($tag)) {
$at = "-b $tag";
2015-06-17 19:30:25 +02:00
} elseif (!empty($branch)) {
$at = "-b $branch";
}
2016-03-19 23:38:45 +07:00
$releases = env('releases_list');
if (!empty($revision)) {
// To checkout specified revision we need to clone all tree.
run("$git clone $at --recursive -q $repository {{release_path}} 2>&1");
run("cd {{release_path}} && $git checkout $revision");
2016-03-19 22:29:17 +07:00
} elseif ($gitCache && isset($releases[1])) {
2015-06-17 19:30:25 +02:00
try {
run("$git clone $at --recursive -q --reference {{deploy_path}}/releases/{$releases[1]} --dissociate $repository {{release_path}} 2>&1");
2015-06-17 19:30:25 +02:00
} catch (RuntimeException $exc) {
// If {{deploy_path}}/releases/{$releases[1]} has a failed git clone, is empty, shallow etc, git would throw error and give up. So we're forcing it to act without reference in this situation
run("$git clone $at --recursive -q $repository {{release_path}} 2>&1");
2015-06-17 19:30:25 +02:00
}
} else {
// if we're using git cache this would be identical to above code in catch - full clone. If not, it would create shallow clone.
run("$git clone $at $depth --recursive -q $repository {{release_path}} 2>&1");
2015-06-16 11:31:49 +02:00
}
2015-01-03 00:04:00 +03:00
})->desc('Updating code');
2015-07-05 14:53:49 +02:00
/**
2015-12-03 12:24:04 -07:00
* Copy directories. Useful for vendors directories
2015-07-05 14:53:49 +02:00
*/
task('deploy:copy_dirs', function () {
$dirs = get('copy_dirs');
2015-07-05 14:59:40 +02:00
foreach ($dirs as $dir) {
// Delete directory if exists.
2015-07-05 15:06:00 +02:00
run("if [ -d $(echo {{release_path}}/$dir) ]; then rm -rf {{release_path}}/$dir; fi");
2015-07-05 14:53:49 +02:00
// Copy directory.
2015-07-05 15:06:00 +02:00
run("if [ -d $(echo {{deploy_path}}/current/$dir) ]; then cp -rpf {{deploy_path}}/current/$dir {{release_path}}/$dir; fi");
2015-07-05 14:53:49 +02:00
}
})->desc('Copy directories');
/**
2015-01-03 00:04:00 +03:00
* Create symlinks for shared directories and files.
*/
task('deploy:shared', function () {
2015-03-23 09:19:12 +07:00
$sharedPath = "{{deploy_path}}/shared";
2015-02-15 15:28:44 +03:00
foreach (get('shared_dirs') as $dir) {
// Remove from source.
2015-03-23 09:19:12 +07:00
run("if [ -d $(echo {{release_path}}/$dir) ]; then rm -rf {{release_path}}/$dir; fi");
// Create shared dir if it does not exist.
2015-02-15 15:28:44 +03:00
run("mkdir -p $sharedPath/$dir");
// Create path to shared dir in release dir if it does not exist.
// (symlink will not create the path and will fail otherwise)
2015-03-23 09:19:12 +07:00
run("mkdir -p `dirname {{release_path}}/$dir`");
// Symlink shared dir to release dir
2015-03-23 09:19:12 +07:00
run("ln -nfs $sharedPath/$dir {{release_path}}/$dir");
}
2015-01-03 00:04:00 +03:00
foreach (get('shared_files') as $file) {
$dirname = dirname($file);
// Remove from source.
2015-06-04 14:22:37 +03:00
run("if [ -f $(echo {{release_path}}/$file) ]; then rm -rf {{release_path}}/$file; fi");
// Ensure dir is available in release
run("if [ ! -d $(echo {{release_path}}/$dirname) ]; then mkdir -p {{release_path}}/$dirname;fi");
// Create dir of shared file
run("mkdir -p $sharedPath/" . $dirname);
2015-01-03 00:04:00 +03:00
// Touch shared
run("touch $sharedPath/$file");
2015-01-03 00:04:00 +03:00
// Symlink shared dir to release dir
2015-03-23 09:19:12 +07:00
run("ln -nfs $sharedPath/$file {{release_path}}/$file");
}
})->desc('Creating symlinks for shared files');
/**
2015-02-15 23:24:08 +03:00
* Make writable dirs.
*/
2015-02-15 23:24:08 +03:00
task('deploy:writable', function () {
$dirs = join(' ', get('writable_dirs'));
$sudo = get('writable_use_sudo') ? 'sudo' : '';
$httpUser = get('http_user');
2015-02-15 23:24:08 +03:00
2015-05-05 00:36:28 +07:00
if (!empty($dirs)) {
2015-05-13 16:52:34 +07:00
try {
if (null === $httpUser) {
2016-04-10 20:38:46 -04:30
$httpUser = run("ps axo user,comm | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | head -1 | cut -d\ -f1")->toString();
}
2015-02-15 23:24:08 +03:00
2015-05-13 16:52:34 +07:00
cd('{{release_path}}');
2015-02-15 23:41:52 +03:00
// Try OS-X specific setting of access-rights
2015-05-13 16:52:34 +07:00
if (strpos(run("chmod 2>&1; true"), '+a') !== false) {
if (!empty($httpUser)) {
run("$sudo chmod +a \"$httpUser allow delete,write,append,file_inherit,directory_inherit\" $dirs");
}
2015-05-13 16:52:34 +07:00
run("$sudo chmod +a \"`whoami` allow delete,write,append,file_inherit,directory_inherit\" $dirs");
// Try linux ACL implementation with unsafe fail-fallback to POSIX-way
2015-05-13 16:52:34 +07:00
} elseif (commandExist('setfacl')) {
if (!empty($httpUser)) {
if (!empty($sudo)) {
run("$sudo setfacl -R -m u:\"$httpUser\":rwX -m u:`whoami`:rwX $dirs");
run("$sudo setfacl -dR -m u:\"$httpUser\":rwX -m u:`whoami`:rwX $dirs");
} else {
// When running without sudo, exception may be thrown
// if executing setfacl on files created by http user (in directory that has been setfacl before).
// These directories/files should be skipped.
// Now, we will check each directory for ACL and only setfacl for which has not been set before.
$writeableDirs = get('writable_dirs');
foreach ($writeableDirs as $dir) {
// Check if ACL has been set or not
2015-08-28 11:22:23 +07:00
$hasfacl = run("getfacl -p $dir | grep \"^user:$httpUser:.*w\" | wc -l")->toString();
// Set ACL for directory if it has not been set before
if (!$hasfacl) {
run("setfacl -R -m u:\"$httpUser\":rwX -m u:`whoami`:rwX $dir");
run("setfacl -dR -m u:\"$httpUser\":rwX -m u:`whoami`:rwX $dir");
}
}
}
2015-05-13 16:52:34 +07:00
} else {
run("$sudo chmod 777 -R $dirs");
2015-05-13 16:52:34 +07:00
}
// If we are not on OS-X and have no ACL installed use POSIX
} else {
run("$sudo chmod 777 -R $dirs");
}
2015-05-13 16:52:34 +07:00
} catch (\RuntimeException $e) {
$formatter = \Deployer\Deployer::get()->getHelper('formatter');
$errorMessage = [
"Unable to setup correct permissions for writable dirs. ",
"You need to configure sudo's sudoers files to not prompt for password,",
2015-05-13 16:52:34 +07:00
"or setup correct permissions manually. ",
];
write($formatter->formatBlock($errorMessage, 'error', true));
2015-02-15 23:24:08 +03:00
2015-05-13 16:52:34 +07:00
throw $e;
2015-02-15 23:24:08 +03:00
}
}
2015-02-15 23:24:08 +03:00
})->desc('Make writable dirs');
/**
2015-01-03 00:04:00 +03:00
* Installing vendors tasks.
*/
task('deploy:vendors', function () {
$composer = env('bin/composer');
$envVars = env('env_vars') ? 'export ' . env('env_vars') . ' &&' : '';
run("cd {{release_path}} && $envVars $composer {{composer_options}}");
})->desc('Installing vendors');
/**
2015-01-03 00:04:00 +03:00
* Create symlink to last release.
*/
task('deploy:symlink', function () {
run("cd {{deploy_path}} && ln -sfn {{release_path}} current"); // Atomic override symlink.
run("cd {{deploy_path}} && rm release"); // Remove release link.
})->desc('Creating symlink to release');
/**
2015-01-03 00:04:00 +03:00
* Return list of releases on server.
*/
env('releases_list', function () {
// find will list only dirs in releases/
$list = run('find {{deploy_path}}/releases -maxdepth 1 -mindepth 1 -type d')->toArray();
2015-01-03 00:04:00 +03:00
2016-02-16 16:07:17 +03:00
// filter out anything that does not look like a release
foreach ($list as $key => $item) {
$item = basename($item); // strip path returned from find
// release dir can look like this: 20160216152237 or 20160216152237.1.2.3.4 ...
$name_match = '[0-9]{14}'; // 20160216152237
$extension_match = '\.[0-9]+'; // .1 or .15 etc
if (!preg_match("/^$name_match($extension_match)*$/", $item)) {
2016-02-16 16:07:17 +03:00
unset($list[$key]); // dir name does not match pattern, throw it out
continue;
}
$list[$key] = $item; // $item was changed
}
2015-01-03 00:04:00 +03:00
rsort($list);
return $list;
});
/**
* Return the current release timestamp
*/
env('release', function () {
return basename(env('current'));
});
2015-01-03 00:04:00 +03:00
/**
* Return current release path.
*/
env('current', function () {
2015-03-23 09:19:12 +07:00
return run("readlink {{deploy_path}}/current")->toString();
2015-01-03 00:04:00 +03:00
});
/**
* Show current release number.
*/
task('current', function () {
writeln('Current release: ' . basename(env('current')));
})->desc('Show current release.');
/**
* Cleanup old releases.
*/
task('cleanup', function () {
2015-01-03 00:04:00 +03:00
$releases = env('releases_list');
2015-01-03 00:04:00 +03:00
$keep = get('keep_releases');
while ($keep > 0) {
array_shift($releases);
--$keep;
}
foreach ($releases as $release) {
2015-03-23 09:19:12 +07:00
run("rm -rf {{deploy_path}}/releases/$release");
}
2015-03-23 09:19:12 +07:00
run("cd {{deploy_path}} && if [ -e release ]; then rm release; fi");
run("cd {{deploy_path}} && if [ -h release ]; then rm release; fi");
2015-01-03 14:48:38 +03:00
})->desc('Cleaning up old releases');
2015-02-25 12:55:56 +03:00
/**
* Cleanup files and directories
*/
task('deploy:clean', function () {
$paths = get('clear_paths');
$sudo = get('clear_use_sudo') ? 'sudo' : '';
foreach ($paths as $path) {
run("$sudo rm -rf {{deploy_path}}/$path");
}
})->desc('Cleaning up files and/or directories');
2015-02-25 12:55:56 +03:00
/**
* Success message
*/
task('success', function () {
writeln("<info>Successfully deployed!</info>");
})
->once()
->setPrivate();