2014-07-12 14:23:22 +04:00
< ? php
2016-01-11 12:51:59 +07:00
/* ( c ) Anton Medvedev < anton @ medv . io >
2014-07-12 14:23:22 +04:00
*
* 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' , []);
2015-02-20 15:26:31 +03:00
set ( 'writable_use_sudo' , true ); // Using sudo in writable commands?
2015-08-30 10:18:38 +02:00
set ( 'http_user' , null );
2016-01-05 10:48:08 +01:00
set ( 'composer_command' , 'composer' ); // Path to composer
2015-02-25 12:49:25 +03:00
/**
* Environment vars
*/
2015-08-24 00:31:29 +07:00
env ( 'timezone' , 'UTC' );
2015-02-25 13:04:18 +03:00
env ( 'branch' , '' ); // Branch to deploy.
2015-02-25 12:49:25 +03:00
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.
2015-08-24 08:37:18 +07:00
$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
});
2015-02-24 17:13:10 +03:00
/**
2015-02-24 18:47:48 +03:00
* Default arguments and options .
2015-02-24 17:13:10 +03:00
*/
argument ( 'stage' , \Symfony\Component\Console\Input\InputArgument :: OPTIONAL , 'Run tasks only on this server or group of servers.' );
2015-02-24 18:47:48 +03:00
option ( 'tag' , null , \Symfony\Component\Console\Input\InputOption :: VALUE_OPTIONAL , 'Tag to deploy.' );
2015-01-03 00:04:00 +03:00
2014-07-12 14:23:22 +04:00
/**
* Rollback to previous release .
*/
task ( 'rollback' , function () {
2015-01-03 14:48:38 +03:00
$releases = env ( 'releases_list' );
2014-07-12 14:23:22 +04:00
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
2014-07-12 14:23:22 +04:00
// Symlink to old release.
2015-03-23 09:19:12 +07:00
run ( " cd { { deploy_path}} && ln -nfs $releaseDir current " );
2014-07-12 14:23:22 +04:00
// 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. " );
}
2014-07-12 14:23:22 +04:00
} else {
writeln ( " <comment>No more releases you can revert to.</comment> " );
}
}) -> desc ( 'Rollback to previous release' );
/**
* Preparing server for deployment .
*/
task ( 'deploy:prepare' , function () {
2015-05-13 16:26:26 +07:00
\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
}
2015-08-24 00:31:29 +07:00
// Set the deployment timezone
if ( ! date_default_timezone_set ( env ( 'timezone' ))) {
date_default_timezone_set ( 'UTC' );
}
2015-09-07 11:50:48 +02:00
2015-07-27 14:58:47 +07:00
run ( 'if [ ! -d {{deploy_path}} ]; then mkdir -p {{deploy_path}}; fi' );
2015-05-11 14:04:14 +01:00
2014-07-12 14:23:22 +04:00
// Create releases dir.
2015-03-23 09:19:12 +07:00
run ( " cd { { deploy_path}} && if [ ! -d releases ]; then mkdir releases; fi " );
2014-07-12 14:23:22 +04:00
// Create shared dir.
2015-03-23 09:19:12 +07:00
run ( " cd { { deploy_path}} && if [ ! -d shared ]; then mkdir shared; fi " );
2014-07-12 14:23:22 +04:00
}) -> 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
});
2014-07-12 14:23:22 +04:00
/**
2015-01-03 00:04:00 +03:00
* Release
2014-07-12 14:23:22 +04:00
*/
2015-01-03 00:04:00 +03:00
task ( 'deploy:release' , function () {
2015-08-24 00:31:29 +07:00
$release = date ( 'YmdHis' );
2014-07-12 14:23:22 +04:00
2015-03-23 09:19:12 +07:00
$releasePath = " { { deploy_path}}/releases/ $release " ;
2014-07-12 14:23:22 +04:00
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 " );
2014-07-12 14:23:22 +04:00
2015-05-08 13:03:22 +03:00
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' );
2014-07-12 14:23:22 +04:00
/**
2015-01-03 00:04:00 +03:00
* Update project code
2014-07-12 14:23:22 +04:00
*/
2015-01-03 00:04:00 +03:00
task ( 'deploy:update_code' , function () {
2015-01-03 15:01:48 +03:00
$repository = get ( 'repository' );
2015-02-25 12:49:25 +03:00
$branch = env ( 'branch' );
2015-06-16 11:31:49 +02:00
$gitCache = env ( 'git_cache' );
2015-06-16 13:15:36 +02:00
$depth = $gitCache ? '' : '--depth 1' ;
2015-09-07 11:50:48 +02:00
2015-02-24 18:47:48 +03:00
if ( input () -> hasOption ( 'tag' )) {
$tag = input () -> getOption ( 'tag' );
2015-02-25 12:20:04 +03:00
}
$at = '' ;
if ( ! empty ( $tag )) {
$at = " -b $tag " ;
2015-06-17 19:30:25 +02:00
} elseif ( ! empty ( $branch )) {
2015-02-25 12:20:04 +03:00
$at = " -b $branch " ;
}
2015-02-25 12:36:25 -05:00
2015-06-16 11:31:49 +02:00
$releases = env ( 'releases_list' );
2015-09-07 11:50:48 +02:00
2015-06-17 19:30:25 +02:00
if ( $gitCache && isset ( $releases [ 1 ])) {
try {
run ( " git clone $at --recursive -q --reference { { deploy_path}}/releases/ { $releases [ 1 ] } --dissociate $repository { { release_path}} 2>&1 " );
} 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
2015-07-05 15:10:08 +02:00
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.
2015-07-05 15:10:08 +02:00
run ( " git clone $at $depth --recursive -q $repository { { release_path}} 2>&1 " );
2015-06-16 11:31:49 +02:00
}
2015-02-25 12:36:25 -05:00
2015-01-03 00:04:00 +03:00
}) -> desc ( 'Updating code' );
2014-07-12 14:23:22 +04:00
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 ) {
2015-07-05 14:53:49 +02:00
//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
2015-07-05 15:06:00 +02:00
//Copy directory
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' );
2014-07-12 14:23:22 +04:00
/**
2015-01-03 00:04:00 +03:00
* Create symlinks for shared directories and files .
2014-07-12 14:23:22 +04:00
*/
task ( 'deploy:shared' , function () {
2015-03-23 09:19:12 +07:00
$sharedPath = " { { deploy_path}}/shared " ;
2014-07-12 14:23:22 +04:00
2015-02-15 15:28:44 +03:00
foreach ( get ( 'shared_dirs' ) as $dir ) {
2015-01-03 00:04:00 +03:00
// 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 " );
2014-07-12 14:23:22 +04:00
2015-02-25 12:36:25 -05:00
// Create shared dir if it does not exist
2015-02-15 15:28:44 +03:00
run ( " mkdir -p $sharedPath / $dir " );
2014-07-12 14:23:22 +04:00
2015-02-25 12:36:25 -05:00
// 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 ` " );
2015-02-25 12:36:25 -05:00
2014-07-12 14:23:22 +04:00
// Symlink shared dir to release dir
2015-03-23 09:19:12 +07:00
run ( " ln -nfs $sharedPath / $dir { { release_path}}/ $dir " );
2014-07-12 14:23:22 +04:00
}
2015-01-03 00:04:00 +03:00
foreach ( get ( 'shared_files' ) as $file ) {
2016-01-20 10:23:47 +01:00
$dirname = dirname ( $file );
2015-01-03 00:04:00 +03:00
// 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 " );
2016-01-20 10:23:47 +01:00
// Ensure dir is available in release
run ( " if [ ! -d $ (echo { { release_path}}/ $dirname ) ]; then mkdir -p { { release_path}}/ $dirname ;fi " );
2014-07-12 14:23:22 +04:00
// Create dir of shared file
2016-01-20 10:23:47 +01:00
run ( " mkdir -p $sharedPath / " . $dirname );
2014-07-12 14:23:22 +04:00
2015-01-03 00:04:00 +03:00
// Touch shared
2014-07-12 14:23:22 +04:00
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 " );
2014-07-12 14:23:22 +04:00
}
}) -> desc ( 'Creating symlinks for shared files' );
/**
2015-02-15 23:24:08 +03:00
* Make writable dirs .
2014-07-12 14:23:22 +04:00
*/
2015-02-15 23:24:08 +03:00
task ( 'deploy:writable' , function () {
$dirs = join ( ' ' , get ( 'writable_dirs' ));
2015-02-20 15:26:31 +03:00
$sudo = get ( 'writable_use_sudo' ) ? 'sudo' : '' ;
2015-08-30 10:18:38 +02:00
$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 {
2015-08-30 10:18:38 +02:00
if ( null === $httpUser ) {
$httpUser = run ( " ps aux | 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
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-02-25 12:36:25 -05:00
2015-05-13 16:52:34 +07:00
run ( " $sudo chmod +a \" `whoami` allow delete,write,append,file_inherit,directory_inherit \" $dirs " );
} elseif ( commandExist ( 'setfacl' )) {
if ( ! empty ( $httpUser )) {
2015-08-24 11:37:26 +07:00
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 {
2015-08-24 17:34:29 +07:00
// 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.
2015-08-24 11:37:26 +07:00
$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 ();
2015-08-24 17:34:29 +07:00
// Set ACL for directory if it has not been set before
2015-08-24 11:37:26 +07:00
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 {
2015-06-11 23:00:14 +07:00
run ( " $sudo chmod 777 -R $dirs " );
2015-05-13 16:52:34 +07:00
}
2015-02-20 15:26:31 +03:00
} else {
2015-06-11 23:00:14 +07:00
run ( " $sudo chmod 777 -R $dirs " );
2015-02-20 15:26:31 +03:00
}
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. " ,
2015-12-17 05:24:23 -07:00
" 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
}
2014-07-12 14:23:22 +04:00
}
2015-02-15 23:24:08 +03:00
2015-02-20 15:26:31 +03:00
}) -> desc ( 'Make writable dirs' );
2014-07-12 14:23:22 +04:00
/**
2015-01-03 00:04:00 +03:00
* Installing vendors tasks .
2014-07-12 14:23:22 +04:00
*/
task ( 'deploy:vendors' , function () {
2016-01-05 10:31:00 +01:00
$composer = get ( 'composer_command' );
if ( ! commandExist ( $composer )) {
2015-05-05 00:09:48 +07:00
run ( " cd { { release_path}} && curl -sS https://getcomposer.org/installer | php " );
2015-02-15 15:28:44 +03:00
$composer = 'php composer.phar' ;
2014-07-12 14:23:22 +04:00
}
2015-07-24 17:38:48 +02:00
$composerEnvVars = env ( 'env_vars' ) ? 'export ' . env ( 'env_vars' ) . ' &&' : '' ;
run ( " cd { { release_path}} && $composerEnvVars $composer { { composer_options}} " );
2014-07-12 14:23:22 +04:00
}) -> desc ( 'Installing vendors' );
/**
2015-01-03 00:04:00 +03:00
* Create symlink to last release .
2014-07-12 14:23:22 +04:00
*/
task ( 'deploy:symlink' , function () {
2015-05-12 13:15:51 +07:00
run ( " cd { { deploy_path}} && ln -sfn { { release_path}} current " ); // Atomic override symlink.
run ( " cd { { deploy_path}} && rm release " ); // Remove release link.
2014-07-12 14:23:22 +04:00
}) -> desc ( 'Creating symlink to release' );
/**
2015-01-03 00:04:00 +03:00
* Return list of releases on server .
*/
env ( 'releases_list' , function () {
2016-02-16 15:06:06 +03:00
// 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
2016-02-16 14:45:34 +03:00
foreach ( $list as $key => $item ) {
2016-02-16 15:06:06 +03:00
$item = basename ( $item ); // strip path returned from find
2016-02-16 15:33:33 +03:00
// 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
2016-02-16 15:06:06 +03:00
continue ;
2016-02-16 14:45:34 +03:00
}
2016-02-16 15:06:06 +03:00
$list [ $key ] = $item ; // $item was changed
2016-02-16 14:45:34 +03:00
}
2015-01-03 00:04:00 +03:00
rsort ( $list );
return $list ;
});
2015-09-07 11:50:48 +02:00
/**
* 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 .
2014-07-12 14:23:22 +04:00
*/
task ( 'cleanup' , function () {
2015-01-03 00:04:00 +03:00
$releases = env ( 'releases_list' );
2014-07-12 14:23:22 +04:00
2015-01-03 00:04:00 +03:00
$keep = get ( 'keep_releases' );
2014-07-12 14:23:22 +04:00
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 " );
2014-07-12 14:23:22 +04:00
}
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
2014-07-12 14:23:22 +04:00
}) -> desc ( 'Cleaning up old releases' );
2015-02-25 12:55:56 +03:00
/**
* Success message
*/
task ( 'success' , function () {
writeln ( " <info>Successfully deployed!</info> " );
})
-> once ()
-> setPrivate ();