diff --git a/.gitignore b/.gitignore index 263233c8..11aaa0c5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /vendor/ /build composer.lock +.mage.yml diff --git a/.travis.yml b/.travis.yml index 3241d8ca..51d78aa1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ php: - '5.6' - '7.0' - '7.1' + - '7.2' install: - composer install diff --git a/CHANGELOG.md b/CHANGELOG.md index d40ac366..785091b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG for 3.X ================= +* 3.4.0 (2018-03-29) + * [Issue#380] Throw exception if log_dir is defined but directory doesn't exists + * [BUGFIX] [Issue#405] Malformed ssh command when defining host:port notation + * [Issue#415] Remove timeout on Deploy with Tar or Rsync tasks + + * 3.3.0 (2017-07-22) * [PR#386] Allow to define timeout (default 120s) for symfony/assetic-dump task. * [PR#392] Allow to define Host Port in Host configuration. diff --git a/docs/dockers/docker-compose.yml b/docs/dockers/docker-compose.yml new file mode 100644 index 00000000..9bd212f7 --- /dev/null +++ b/docs/dockers/docker-compose.yml @@ -0,0 +1,19 @@ +version: '2' +services: + php5: + container_name: mage-php5 + build: ./php5 + volumes: + - ../../:/home/magephp + + php7.0: + container_name: mage-php7.0 + build: ./php7.0 + volumes: + - ../../:/home/magephp + + php7.1: + container_name: mage-php7.1 + build: ./php7.1 + volumes: + - ../../:/home/magephp diff --git a/docs/dockers/php5/Dockerfile b/docs/dockers/php5/Dockerfile new file mode 100644 index 00000000..c7918b43 --- /dev/null +++ b/docs/dockers/php5/Dockerfile @@ -0,0 +1,9 @@ +FROM ubuntu:14.04 + +RUN apt-get update && apt-get upgrade -y +RUN apt-get install -y vim curl git unzip +RUN apt-get install -y php5-cli php5-curl + +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer + +WORKDIR /home/magephp diff --git a/docs/dockers/php7.0/Dockerfile b/docs/dockers/php7.0/Dockerfile new file mode 100644 index 00000000..f1da4b49 --- /dev/null +++ b/docs/dockers/php7.0/Dockerfile @@ -0,0 +1,9 @@ +FROM ubuntu:16.04 + +RUN apt-get update && apt-get upgrade -y +RUN apt-get install -y vim curl git unzip +RUN apt-get install -y php7.0-cli php-zip php7.0-curl php7.0-xml + +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer + +WORKDIR /home/magephp diff --git a/docs/dockers/php7.1/Dockerfile b/docs/dockers/php7.1/Dockerfile new file mode 100644 index 00000000..cb63a2ba --- /dev/null +++ b/docs/dockers/php7.1/Dockerfile @@ -0,0 +1,9 @@ +FROM ubuntu:17.10 + +RUN apt-get update && apt-get upgrade -y +RUN apt-get install -y vim curl git unzip +RUN apt-get install -y php7.1-cli php-zip php7.1-curl php7.1-xml + +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer + +WORKDIR /home/magephp diff --git a/src/Command/BuiltIn/DeployCommand.php b/src/Command/BuiltIn/DeployCommand.php index dd86e4db..bb2cb5c4 100644 --- a/src/Command/BuiltIn/DeployCommand.php +++ b/src/Command/BuiltIn/DeployCommand.php @@ -143,6 +143,9 @@ protected function runDeployment(OutputInterface $output, StrategyInterface $str protected function runOnHosts(OutputInterface $output, $tasks) { $hosts = $this->runtime->getEnvOption('hosts'); + if (!is_array($hosts) && !$hosts instanceof \Countable) { + $hosts = []; + } if (count($hosts) == 0) { $output->writeln(sprintf(' No hosts defined, skipping %s tasks', $this->getStageName())); $output->writeln(''); @@ -175,8 +178,8 @@ protected function runTasks(OutputInterface $output, $tasks) return true; } - if ($this->runtime->getWorkingHost() !== null) { - $output->writeln(sprintf(' Starting %s tasks on host %s:', $this->getStageName(), $this->runtime->getWorkingHost())); + if ($this->runtime->getHostName() !== null) { + $output->writeln(sprintf(' Starting %s tasks on host %s:', $this->getStageName(), $this->runtime->getHostName())); } else { $output->writeln(sprintf(' Starting %s tasks:', $this->getStageName())); } diff --git a/src/Command/BuiltIn/Releases/ListCommand.php b/src/Command/BuiltIn/Releases/ListCommand.php index 4bee6900..5d49ce20 100644 --- a/src/Command/BuiltIn/Releases/ListCommand.php +++ b/src/Command/BuiltIn/Releases/ListCommand.php @@ -74,6 +74,9 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln(''); $hosts = $this->runtime->getEnvOption('hosts'); + if (!is_array($hosts) && !$hosts instanceof \Countable) { + $hosts = []; + } if (count($hosts) == 0) { $output->writeln('No hosts defined'); $output->writeln(''); diff --git a/src/Mage.php b/src/Mage.php index 435b2c14..4944c52d 100644 --- a/src/Mage.php +++ b/src/Mage.php @@ -17,6 +17,6 @@ */ class Mage { - const VERSION = '3.3.0'; + const VERSION = '3.x-dev'; const CODENAME = 'Nostromo'; } diff --git a/src/MageApplication.php b/src/MageApplication.php index cb20d30e..77fe4e21 100644 --- a/src/MageApplication.php +++ b/src/MageApplication.php @@ -84,6 +84,9 @@ public function configure() $logger = new Logger('magephp'); $logger->pushHandler(new StreamHandler($logfile)); + + } elseif (array_key_exists('log_dir', $config['magephp']) && !is_dir($config['magephp']['log_dir'])) { + throw new RuntimeException(sprintf('The configured log_dir "%s" does not exists or is not a directory.', $config['magephp']['log_dir'])); } $this->runtime->setConfiguration($config['magephp']); diff --git a/src/Runtime/Runtime.php b/src/Runtime/Runtime.php index f28e03fe..5b7368df 100644 --- a/src/Runtime/Runtime.php +++ b/src/Runtime/Runtime.php @@ -429,7 +429,7 @@ public function runRemoteCommand($cmd, $jail, $timeout = 120) { $user = $this->getEnvOption('user', $this->getCurrentUser()); $sudo = $this->getEnvOption('sudo', false); - $host = $this->getWorkingHost(); + $host = $this->getHostName(); $sshConfig = $this->getSSHConfig(); $cmdDelegate = $cmd; @@ -485,6 +485,21 @@ public function getHostPort() return isset($info[1]) ? $info[1] : null; } + /** + * Get the current Host Name + * + * @return string + */ + public function getHostName() + { + if (strpos($this->getWorkingHost(), ':') === false) { + return $this->getWorkingHost(); + } + + $info = explode(':', $this->getWorkingHost()); + return $info[0]; + } + /** * Gets a Temporal File name * diff --git a/src/Task/BuiltIn/Deploy/ReleaseTask.php b/src/Task/BuiltIn/Deploy/ReleaseTask.php index ad0dd001..ab767f01 100644 --- a/src/Task/BuiltIn/Deploy/ReleaseTask.php +++ b/src/Task/BuiltIn/Deploy/ReleaseTask.php @@ -44,7 +44,7 @@ public function execute() $cmdLinkRelease = sprintf('cd %s && ln -snf releases/%s current', $hostPath, $releaseId); /** @var Process $process */ - $process = $this->runtime->runRemoteCommand($cmdLinkRelease, false); + $process = $this->runtime->runRemoteCommand($cmdLinkRelease, false, null); return $process->isSuccessful(); } } diff --git a/src/Task/BuiltIn/Deploy/RsyncTask.php b/src/Task/BuiltIn/Deploy/RsyncTask.php index b1ac64bf..1310a5a8 100644 --- a/src/Task/BuiltIn/Deploy/RsyncTask.php +++ b/src/Task/BuiltIn/Deploy/RsyncTask.php @@ -36,7 +36,7 @@ public function execute() $flags = $this->runtime->getEnvOption('rsync', '-avz'); $sshConfig = $this->runtime->getSSHConfig(); $user = $this->runtime->getEnvOption('user', $this->runtime->getCurrentUser()); - $host = $this->runtime->getWorkingHost(); + $host = $this->runtime->getHostName(); $hostPath = rtrim($this->runtime->getEnvOption('host_path'), '/'); $targetDir = rtrim($hostPath, '/'); @@ -49,7 +49,7 @@ public function execute() $cmdRsync = sprintf('rsync -e "ssh -p %d %s" %s %s %s %s@%s:%s', $sshConfig['port'], $sshConfig['flags'], $flags, $excludes, $from, $user, $host, $targetDir); /** @var Process $process */ - $process = $this->runtime->runLocalCommand($cmdRsync, 600); + $process = $this->runtime->runLocalCommand($cmdRsync, null); return $process->isSuccessful(); } diff --git a/src/Task/BuiltIn/Deploy/Tar/CopyTask.php b/src/Task/BuiltIn/Deploy/Tar/CopyTask.php index 294d2bc6..997023d7 100644 --- a/src/Task/BuiltIn/Deploy/Tar/CopyTask.php +++ b/src/Task/BuiltIn/Deploy/Tar/CopyTask.php @@ -38,7 +38,7 @@ public function execute() } $user = $this->runtime->getEnvOption('user', $this->runtime->getCurrentUser()); - $host = $this->runtime->getWorkingHost(); + $host = $this->runtime->getHostName(); $sshConfig = $sshConfig = $this->runtime->getSSHConfig(); $hostPath = rtrim($this->runtime->getEnvOption('host_path'), '/'); $currentReleaseId = $this->runtime->getReleaseId(); diff --git a/src/Task/BuiltIn/FS/AbstractFileTask.php b/src/Task/BuiltIn/FS/AbstractFileTask.php index eb782b27..6243946c 100644 --- a/src/Task/BuiltIn/FS/AbstractFileTask.php +++ b/src/Task/BuiltIn/FS/AbstractFileTask.php @@ -61,8 +61,8 @@ protected function getFile($file) '%environment%' => $this->runtime->getEnvironment(), ]; - if ($this->runtime->getWorkingHost() !== null) { - $mapping['%host%'] = $this->runtime->getWorkingHost(); + if ($this->runtime->getHostName() !== null) { + $mapping['%host%'] = $this->runtime->getHostName(); } if ($this->runtime->getReleaseId() !== null) { diff --git a/tests/Command/BuiltIn/DeployCommandMiscTest.php b/tests/Command/BuiltIn/DeployCommandMiscTest.php index 9ffa09df..8df52ef5 100644 --- a/tests/Command/BuiltIn/DeployCommandMiscTest.php +++ b/tests/Command/BuiltIn/DeployCommandMiscTest.php @@ -11,6 +11,7 @@ namespace Mage\Tests\Command\BuiltIn; use Mage\Command\BuiltIn\DeployCommand; +use Mage\Runtime\Exception\RuntimeException; use Mage\Tests\MageApplicationMockup; use Mage\Command\AbstractCommand; use Symfony\Component\Console\Tester\CommandTester; @@ -36,6 +37,24 @@ public function testDeploymentWithNoHosts() $this->assertEquals(0, $tester->getStatusCode()); } + public function testInvalidLog() + { + $application = new MageApplicationMockup(__DIR__ . '/../../Resources/invalid-log.yml'); + + /** @var AbstractCommand $command */ + $command = $application->find('deploy'); + $this->assertTrue($command instanceof DeployCommand); + + try { + $tester = new CommandTester($command); + $tester->execute(['command' => $command->getName(), 'environment' => 'test']); + } catch (RuntimeException $exception) { + $this->assertEquals('The configured log_dir "/no-temp" does not exists or is not a directory.', $exception->getMessage()); + } catch (\Exception $exception) { + $this->assertFalse(true, sprintf('Exception "%s" catched, message: "%s"', get_class($exception), $exception->getMessage())); + } + } + public function testDeploymentWithSudo() { $application = new MageApplicationMockup(__DIR__ . '/../../Resources/testhost-sudo.yml'); diff --git a/tests/Command/BuiltIn/DeployCommandWithReleasesTest.php b/tests/Command/BuiltIn/DeployCommandWithReleasesTest.php index ead00533..de3e2010 100644 --- a/tests/Command/BuiltIn/DeployCommandWithReleasesTest.php +++ b/tests/Command/BuiltIn/DeployCommandWithReleasesTest.php @@ -68,6 +68,56 @@ public function testDeploymentWithReleasesCommands() $this->assertEquals(0, $tester->getStatusCode()); } + public function testDeploymentWithReleasesWithPortCommands() + { + $application = new MageApplicationMockup(__DIR__ . '/../../Resources/testhost-with-port.yml'); + + $application->getRuntime()->setReleaseId('20170101015120'); + + /** @var AbstractCommand $command */ + $command = $application->find('deploy'); + $this->assertTrue($command instanceof DeployCommand); + + $tester = new CommandTester($command); + $tester->execute(['command' => $command->getName(), 'environment' => 'test']); + + $ranCommands = $application->getRuntime()->getRanCommands(); + + $testCase = array( + 0 => 'git branch | grep "*"', + 1 => 'git checkout test', + 2 => 'git pull', + 3 => 'composer install --optimize-autoloader', + 4 => 'composer dump-autoload --optimize', + 5 => 'tar cfzp /tmp/mageXYZ --exclude=".git" --exclude="./var/cache/*" --exclude="./var/log/*" --exclude="./web/app_dev.php" ./', + 6 => 'ssh -p 202 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no tester@testhost "mkdir -p /var/www/test/releases/1234567890"', + 7 => 'scp -P 202 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no /tmp/mageXYZ tester@testhost:/var/www/test/releases/1234567890/mageXYZ', + 8 => 'ssh -p 202 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no tester@testhost "cd /var/www/test/releases/1234567890 && tar xfzop mageXYZ"', + 9 => 'ssh -p 202 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no tester@testhost "rm /var/www/test/releases/1234567890/mageXYZ"', + 10 => 'ssh -p 202 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no tester@testhost "cd /var/www/test/releases/1234567890 && bin/console cache:warmup --env=prod"', + 11 => 'ssh -p 202 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no tester@testhost "cd /var/www/test/releases/1234567890 && bin/console assets:install web --env=prod --symlink --relative"', + 12 => 'ssh -p 202 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no tester@testhost "cd /var/www/test/releases/1234567890 && bin/console assetic:dump --env=prod"', + 13 => 'ssh -p 202 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no tester@testhost "cd /var/www/test && ln -snf releases/1234567890 current"', + 14 => 'ssh -p 202 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no tester@testhost "ls -1 /var/www/test/releases"', + 15 => 'ssh -p 202 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no tester@testhost "rm -rf /var/www/test/releases/20170101015110"', + 16 => 'ssh -p 202 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no tester@testhost "rm -rf /var/www/test/releases/20170101015111"', + 17 => 'ssh -p 202 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no tester@testhost "rm -rf /var/www/test/releases/20170101015112"', + 18 => 'ssh -p 202 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no tester@testhost "rm -rf /var/www/test/releases/20170101015113"', + 19 => 'rm /tmp/mageXYZ', + 20 => 'git checkout master', + ); + + // Check total of Executed Commands + $this->assertEquals(count($testCase), count($ranCommands)); + + // Check Generated Commands + foreach ($testCase as $index => $command) { + $this->assertEquals($command, $ranCommands[$index]); + } + + $this->assertEquals(0, $tester->getStatusCode()); + } + public function testDeploymentWithReleasesWithFromCommands() { $application = new MageApplicationMockup(__DIR__ . '/../../Resources/testhost-with-from.yml'); diff --git a/tests/Command/BuiltIn/DeployCommandWithoutReleasesTest.php b/tests/Command/BuiltIn/DeployCommandWithoutReleasesTest.php index 0ba490b3..1e585317 100644 --- a/tests/Command/BuiltIn/DeployCommandWithoutReleasesTest.php +++ b/tests/Command/BuiltIn/DeployCommandWithoutReleasesTest.php @@ -55,6 +55,43 @@ public function testDeploymentWithoutReleasesCommands() $this->assertEquals(0, $tester->getStatusCode()); } + public function testDeploymentWithoutReleasesWithPortCommands() + { + $application = new MageApplicationMockup(__DIR__ . '/../../Resources/testhost-without-releases-with-port.yml'); + + /** @var AbstractCommand $command */ + $command = $application->find('deploy'); + $this->assertTrue($command instanceof DeployCommand); + + $tester = new CommandTester($command); + $tester->execute(['command' => $command->getName(), 'environment' => 'test']); + + $ranCommands = $application->getRuntime()->getRanCommands(); + + $testCase = array( + 0 => 'git branch | grep "*"', + 1 => 'git checkout test', + 2 => 'git pull', + 3 => 'composer install --optimize-autoloader', + 4 => 'composer dump-autoload --optimize', + 5 => 'rsync -e "ssh -p 202 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" -avz --exclude=.git --exclude=./var/cache/* --exclude=./var/log/* --exclude=./web/app_dev.php ./ tester@testhost:/var/www/test', + 6 => 'ssh -p 202 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no tester@testhost "cd /var/www/test && bin/console cache:warmup --env=dev"', + 7 => 'ssh -p 202 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no tester@testhost "cd /var/www/test && bin/console assets:install web --env=dev --symlink --relative"', + 8 => 'ssh -p 202 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no tester@testhost "cd /var/www/test && bin/console assetic:dump --env=dev"', + 9 => 'git checkout master', + ); + + // Check total of Executed Commands + $this->assertEquals(count($testCase), count($ranCommands)); + + // Check Generated Commands + foreach ($testCase as $index => $command) { + $this->assertEquals($command, $ranCommands[$index]); + } + + $this->assertEquals(0, $tester->getStatusCode()); + } + public function testDeploymentWithoutReleasesWithFromCommands() { $application = new MageApplicationMockup(__DIR__ . '/../../Resources/testhost-without-releases-with-from.yml'); diff --git a/tests/Resources/invalid-log.yml b/tests/Resources/invalid-log.yml new file mode 100644 index 00000000..d6321c59 --- /dev/null +++ b/tests/Resources/invalid-log.yml @@ -0,0 +1,27 @@ +magephp: + log_dir: /no-temp + environments: + production: + user: app + branch: master + host_path: /var/www/myapp + releases: 4 + exclude: + - ./var/cache/* + - ./var/log/* + - ./web/app_dev.php + hosts: + - webserver1 + - webserver2 + - webserver3 + pre-deploy: + - git/update + - composer/install + - composer/dump-autoload + on-deploy: + - symfony/cache-warmup: { env: 'dev' } + - symfony/assets-install: { env: 'dev' } + - symfony/assetic-dump: { env: 'dev' } + on-release: + post-release: + post-deploy: \ No newline at end of file diff --git a/tests/Resources/testhost-with-port.yml b/tests/Resources/testhost-with-port.yml new file mode 100644 index 00000000..c6e5ff66 --- /dev/null +++ b/tests/Resources/testhost-with-port.yml @@ -0,0 +1,27 @@ +magephp: + log_dir: /tmp + environments: + test: + user: tester + branch: test + host_path: /var/www/test + releases: 4 + exclude: + - ./var/cache/* + - ./var/log/* + - ./web/app_dev.php + - + - + hosts: + - testhost:202 + pre-deploy: + - git/update + - composer/install + - composer/dump-autoload + on-deploy: + - symfony/cache-warmup: { env: 'prod' } + - symfony/assets-install: { env: 'prod' } + - symfony/assetic-dump: { env: 'prod' } + on-release: + post-release: + post-deploy: \ No newline at end of file diff --git a/tests/Resources/testhost-without-releases-with-port.yml b/tests/Resources/testhost-without-releases-with-port.yml new file mode 100644 index 00000000..9f4f184d --- /dev/null +++ b/tests/Resources/testhost-without-releases-with-port.yml @@ -0,0 +1,24 @@ +magephp: + log_dir: /tmp + environments: + test: + user: tester + branch: test + host_path: /var/www/test + exclude: + - ./var/cache/* + - ./var/log/* + - ./web/app_dev.php + hosts: + - testhost:202 + pre-deploy: + - git/update + - composer/install + - composer/dump-autoload + on-deploy: + - symfony/cache-warmup: { env: 'dev' } + - symfony/assets-install: { env: 'dev' } + - symfony/assetic-dump: { env: 'dev' } + on-release: + post-release: + post-deploy: \ No newline at end of file diff --git a/tests/Runtime/ProcessMockup.php b/tests/Runtime/ProcessMockup.php index 918a127e..f5b862ba 100644 --- a/tests/Runtime/ProcessMockup.php +++ b/tests/Runtime/ProcessMockup.php @@ -99,6 +99,14 @@ public function getOutput() return '/var/www/test/releases/20170101015117'; } + if ($this->commandline == 'ssh -p 202 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no tester@testhost "ls -1 /var/www/test/releases"') { + return implode(PHP_EOL, ['20170101015110', '20170101015111', '20170101015112', '20170101015113', '20170101015114', '20170101015115', '20170101015116', '20170101015117']); + } + + if ($this->commandline == 'ssh -p 202 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no tester@testhost "readlink -f /var/www/test/current"') { + return '/var/www/test/releases/20170101015117'; + } + if ($this->commandline == 'ssh -p 22 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no tester@host1 "ls -1 /var/www/test/releases"') { return implode(PHP_EOL, ['20170101015110', '20170101015111', '20170101015112', '20170101015113', '20170101015114', '20170101015115', '20170101015116', '20170101015117']); }