[Nostromo] Initial Commit

This commit is contained in:
Andrés Montañez 2016-12-31 03:52:25 -03:00
commit 233bd58d48
39 changed files with 3169 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/vendor/

34
.mage.yml Normal file
View file

@ -0,0 +1,34 @@
magephp:
# composer:
# path: composer
symfony:
# console: bin/console
# log_dir: app/logs
environments:
production:
user: root
# ssh: -p 22 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no
# scp: -P 22 -p -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no
branch: test
host_path: /var/www/test
releases: 4
exclude:
- vendor
- app/cache
- app/log
- web/app_dev.php
hosts:
- webserver
pre-deploy:
- git/update
# - composer/install #: { flags: '--optimize' }
# - composer/generate-autoload
on-deploy:
# - symfony/cache-clear: { env: 'dev' }
# - symfony/cache-warmup: { env: 'dev' }
# - symfony/assets-install: { env: 'dev' }
# - symfony/assetic-dump: { env: 'dev' }
on-release:
post-release:
post-deploy:

23
LICENSE Normal file
View file

@ -0,0 +1,23 @@
Copyright (c) 2011 - 2017 Andrés Montañez
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions of
the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

4
README.md Normal file
View file

@ -0,0 +1,4 @@
# Magallanes #
## Insurrection Version ##
Insurrection: A violent uprising of part or all of a national population against the government or other authority; a mutiny; **a rebellion**.

11
bin/mage Executable file
View file

@ -0,0 +1,11 @@
#!/usr/bin/env php
<?php
date_default_timezone_set('UTC');
require __DIR__ . '/../vendor/autoload.php';
use Mage\MageApplication;
$mage = new MageApplication();
$mage->configure(__DIR__ . '/../.mage.yml');
$mage->run();

32
composer.json Normal file
View file

@ -0,0 +1,32 @@
{
"name": "andres-montanez/magallanes",
"description": "A Deployment Tool for PHP Applications",
"homepage": "http://magephp.com",
"license": "MIT",
"type": "library",
"keywords": ["deployment"],
"authors": [
{
"name": "Andrés Montañez",
"email": "andresmontanez@gmail.com"
}
],
"require": {
"php": ">=5.5.9",
"monolog/monolog": "^1.0",
"symfony/console": "^3.0",
"symfony/filesystem": "^3.0",
"symfony/finder": "^3.0",
"symfony/yaml": "^3.0",
"symfony/process": "^3.0"
},
"autoload": {
"psr-4": {
"Symfony\\Component\\": "src/Symfony/Component/"
},
"psr-0": { "": "src/" },
"exclude-from-classmap": [
"**/Tests/"
]
}
}

527
composer.lock generated Normal file
View file

@ -0,0 +1,527 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "452fb59524b87e73b64df8ae4e513c82",
"content-hash": "9af180a8e49a2a08013cb76345fde19b",
"packages": [
{
"name": "monolog/monolog",
"version": "1.22.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "bad29cb8d18ab0315e6c477751418a82c850d558"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/bad29cb8d18ab0315e6c477751418a82c850d558",
"reference": "bad29cb8d18ab0315e6c477751418a82c850d558",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"psr/log": "~1.0"
},
"provide": {
"psr/log-implementation": "1.0.0"
},
"require-dev": {
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
"doctrine/couchdb": "~1.0@dev",
"graylog2/gelf-php": "~1.0",
"jakub-onderka/php-parallel-lint": "0.9",
"php-amqplib/php-amqplib": "~2.4",
"php-console/php-console": "^3.1.3",
"phpunit/phpunit": "~4.5",
"phpunit/phpunit-mock-objects": "2.3.0",
"ruflin/elastica": ">=0.90 <3.0",
"sentry/sentry": "^0.13",
"swiftmailer/swiftmailer": "~5.3"
},
"suggest": {
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
"ext-mongo": "Allow sending log messages to a MongoDB server",
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
"mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
"php-console/php-console": "Allow sending log messages to Google Chrome",
"rollbar/rollbar": "Allow sending log messages to Rollbar",
"ruflin/elastica": "Allow sending log messages to an Elastic Search server",
"sentry/sentry": "Allow sending log messages to a Sentry server"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Monolog\\": "src/Monolog"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
"homepage": "http://github.com/Seldaek/monolog",
"keywords": [
"log",
"logging",
"psr-3"
],
"time": "2016-11-26 00:15:39"
},
{
"name": "psr/log",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Log\\": "Psr/Log/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
],
"time": "2016-10-10 12:19:37"
},
{
"name": "symfony/console",
"version": "v3.2.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "d12aa9ca20f4db83ec58410978dab6afcb9d6aaa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/d12aa9ca20f4db83ec58410978dab6afcb9d6aaa",
"reference": "d12aa9ca20f4db83ec58410978dab6afcb9d6aaa",
"shasum": ""
},
"require": {
"php": ">=5.5.9",
"symfony/debug": "~2.8|~3.0",
"symfony/polyfill-mbstring": "~1.0"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/event-dispatcher": "~2.8|~3.0",
"symfony/filesystem": "~2.8|~3.0",
"symfony/process": "~2.8|~3.0"
},
"suggest": {
"psr/log": "For using the console logger",
"symfony/event-dispatcher": "",
"symfony/filesystem": "",
"symfony/process": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.2-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Console\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"time": "2016-12-11 14:34:22"
},
{
"name": "symfony/debug",
"version": "v3.2.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug.git",
"reference": "9f923e68d524a3095c5a2ae5fc7220c7cbc12231"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/debug/zipball/9f923e68d524a3095c5a2ae5fc7220c7cbc12231",
"reference": "9f923e68d524a3095c5a2ae5fc7220c7cbc12231",
"shasum": ""
},
"require": {
"php": ">=5.5.9",
"psr/log": "~1.0"
},
"conflict": {
"symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2"
},
"require-dev": {
"symfony/class-loader": "~2.8|~3.0",
"symfony/http-kernel": "~2.8|~3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.2-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Debug\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Debug Component",
"homepage": "https://symfony.com",
"time": "2016-11-16 22:18:16"
},
{
"name": "symfony/filesystem",
"version": "v3.2.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "8d4cf7561a5b17e5eb7a02b80d0b8f014a3796d4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/8d4cf7561a5b17e5eb7a02b80d0b8f014a3796d4",
"reference": "8d4cf7561a5b17e5eb7a02b80d0b8f014a3796d4",
"shasum": ""
},
"require": {
"php": ">=5.5.9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.2-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Filesystem\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
"time": "2016-11-24 00:46:43"
},
{
"name": "symfony/finder",
"version": "v3.2.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "a69cb5d455b4885ca376dc5bb3e1155cc8c08c4b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/a69cb5d455b4885ca376dc5bb3e1155cc8c08c4b",
"reference": "a69cb5d455b4885ca376dc5bb3e1155cc8c08c4b",
"shasum": ""
},
"require": {
"php": ">=5.5.9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.2-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Finder\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
"time": "2016-12-13 09:39:43"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "e79d363049d1c2128f133a2667e4f4190904f7f4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4",
"reference": "e79d363049d1c2128f133a2667e4f4190904f7f4",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.3-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"time": "2016-11-14 01:06:16"
},
{
"name": "symfony/process",
"version": "v3.2.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "02ea84847aad71be7e32056408bb19f3a616cdd3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/02ea84847aad71be7e32056408bb19f3a616cdd3",
"reference": "02ea84847aad71be7e32056408bb19f3a616cdd3",
"shasum": ""
},
"require": {
"php": ">=5.5.9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.2-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Process\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
"time": "2016-11-24 10:40:28"
},
{
"name": "symfony/yaml",
"version": "v3.2.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "a7095af4b97a0955f85c8989106c249fa649011f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/a7095af4b97a0955f85c8989106c249fa649011f",
"reference": "a7095af4b97a0955f85c8989106c249fa649011f",
"shasum": ""
},
"require": {
"php": ">=5.5.9"
},
"require-dev": {
"symfony/console": "~2.8|~3.0"
},
"suggest": {
"symfony/console": "For validating YAML files using the lint command"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.2-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Yaml\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2016-12-10 10:07:06"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=5.5.9"
},
"platform-dev": []
}

View file

@ -0,0 +1,77 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Command;
use Mage\Runtime\Runtime;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Symfony\Component\Console\Command\Command;
/**
* Abstract base class for Magallanes Commands
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
abstract class AbstractCommand extends Command
{
/**
* @var Runtime Current Runtime instance
*/
protected $runtime;
/**
* @var LoggerInterface|null The instance of the logger, it's optional
*/
private $logger = null;
/**
* Configure the Command and create the Runtime configuration
*
* @param array $configuration Magallanes configuration
* @return AbstractCommand
*/
public function setConfiguration($configuration)
{
$this->runtime = new Runtime();
$this->runtime->setConfiguration($configuration);
$this->runtime->setLogger($this->logger);
return $this;
}
/**
* Sets the logger
*
* @param LoggerInterface $logger
* @return AbstractCommand
*/
public function setLogger(LoggerInterface $logger = null)
{
$this->logger = $logger;
return $this;
}
/**
* Logs a message, if logger is valid instance
*
* @param string $message
* @param string $level
* @return AbstractCommand
*/
public function log($message, $level = LogLevel::DEBUG)
{
if ($this->logger instanceof LoggerInterface) {
$this->logger->log($level, $message);
}
return $this;
}
}

View file

@ -0,0 +1,306 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Command\BuiltIn;
use Mage\Runtime\Exception\DeploymentException;
use Mage\Runtime\Exception\InvalidEnvironmentException;
use Mage\Runtime\Exception\RuntimeException;
use Mage\Runtime\Runtime;
use Mage\Task\ErrorException;
use Mage\Task\ExecuteOnRollbackInterface;
use Mage\Task\AbstractTask;
use Mage\Task\SkipException;
use Mage\Task\TaskFactory;
use Mage\Utils;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
use Mage\Command\AbstractCommand;
/**
* The Deployment Command
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class DeployCommand extends AbstractCommand
{
/**
* @var TaskFactory
*/
protected $taskFactory;
/**
* Configure the Command
*/
protected function configure()
{
$this
->setName('deploy')
->setDescription('Deploy code to hosts')
->addArgument('environment', InputArgument::REQUIRED, 'Name of the environment to deploy to')
;
}
/**
* Execute the Command
*
* @param InputInterface $input
* @param OutputInterface $output
* @return int|mixed
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln('Starting <fg=blue>Magallanes</>');
$output->writeln('');
try {
$this->runtime->setEnvironment($input->getArgument('environment'));
} catch (InvalidEnvironmentException $exception) {
$output->writeln(sprintf('<error>%s</error>', $exception->getMessage()));
return $exception->getCode();
}
$output->writeln(sprintf(' Environment: <fg=green>%s</>', $this->runtime->getEnvironment()));
$this->log(sprintf('Environment: %s', $this->runtime->getEnvironment()));
if ($this->runtime->getEnvironmentConfig('releases', false)) {
$this->runtime->setReleaseId(date('YmdHis'));
$output->writeln(sprintf(' Release ID: <fg=green>%s</>', $this->runtime->getReleaseId()));
$this->log(sprintf('Release ID: %s', $this->runtime->getReleaseId()));
}
if ($this->runtime->getConfigOptions('log_file', false)) {
$output->writeln(sprintf(' Logfile: <fg=green>%s</>', $this->runtime->getConfigOptions('log_file')));
}
$output->writeln('');
try {
$this->taskFactory = new TaskFactory($this->runtime);
$this->runDeployment($output);
} catch (DeploymentException $exception) {
$output->writeln(sprintf('<error>%s</error>', $exception->getMessage()));
return $exception->getCode();
}
$output->writeln('Finished <fg=blue>Magallanes</>');
return 0;
}
/**
* Run the Deployment Process
*
* @param OutputInterface $output
* @throws DeploymentException
*/
protected function runDeployment(OutputInterface $output)
{
// Run Pre Deploy Tasks
$this->runtime->setStage(Runtime::PRE_DEPLOY);
$preDeployTasks = $this->runtime->getTasks();
if ($this->runtime->getEnvironmentConfig('branch', false) && !$this->runtime->inRollback()) {
if (!in_array('git/change-branch', $preDeployTasks)) {
array_unshift($preDeployTasks, 'git/change-branch');
}
}
if ($this->runtime->getEnvironmentConfig('releases', false) && !$this->runtime->inRollback()) {
if (!in_array('deploy/targz/prepare', $preDeployTasks)) {
array_push($preDeployTasks, 'deploy/targz/prepare');
}
}
if (!$this->runTasks($output, $preDeployTasks)) {
throw new DeploymentException(sprintf(' Tasks failed on %s stage, halting deployment', $this->getStageName()), 500);
}
// Run On Deploy Tasks
$hosts = $this->runtime->getEnvironmentConfig('hosts');
if (count($hosts) == 0) {
$output->writeln(' No hosts defined, skipping On Deploy tasks');
$output->writeln('');
} else {
$this->runtime->setStage(Runtime::ON_DEPLOY);
$onDeployTasks = $this->runtime->getTasks();
if ($this->runtime->getEnvironmentConfig('releases', false) && !$this->runtime->inRollback()) {
if (!in_array('deploy/targz/copy', $onDeployTasks)) {
array_unshift($onDeployTasks, 'deploy/targz/copy');
}
} else {
if (!in_array('deploy/rsync', $onDeployTasks) && !$this->runtime->inRollback()) {
array_unshift($onDeployTasks, 'deploy/rsync');
}
}
if ($this->runtime->getEnvironmentConfig('releases', false) && !$this->runtime->inRollback()) {
if (!in_array('deploy/release/prepare', $onDeployTasks)) {
array_unshift($onDeployTasks, 'deploy/release/prepare');
}
}
foreach ($hosts as $host) {
$this->runtime->setWorkingHost($host);
if (!$this->runTasks($output, $onDeployTasks)) {
throw new DeploymentException(sprintf(' Tasks failed on <fg=black;options=bold>%s</> stage, halting deployment', $this->getStageName()), 500);
}
$this->runtime->setWorkingHost(null);
}
}
// Run On Release Tasks
$hosts = $this->runtime->getEnvironmentConfig('hosts');
if (count($hosts) == 0) {
$output->writeln(' No hosts defined, skipping On Release tasks');
$output->writeln('');
} else {
$this->runtime->setStage(Runtime::ON_RELEASE);
$onReleaseTasks = $this->runtime->getTasks();
if ($this->runtime->getEnvironmentConfig('releases', false)) {
if (!in_array('deploy/release', $onReleaseTasks)) {
array_unshift($onReleaseTasks, 'deploy/release');
}
}
foreach ($hosts as $host) {
$this->runtime->setWorkingHost($host);
if (!$this->runTasks($output, $onReleaseTasks)) {
throw new DeploymentException(sprintf(' Tasks failed on <fg=black;options=bold>%s</> stage, halting deployment', $this->getStageName()), 500);
}
$this->runtime->setWorkingHost(null);
}
}
// Run Post Release Tasks
$hosts = $this->runtime->getEnvironmentConfig('hosts');
if (count($hosts) == 0) {
$output->writeln(' No hosts defined, skipping Post Release tasks');
$output->writeln('');
} else {
$this->runtime->setStage(Runtime::POST_RELEASE);
$postReleaseTasks = $this->runtime->getTasks();
if ($this->runtime->getEnvironmentConfig('releases', false) && !$this->runtime->inRollback()) {
if (!in_array('deploy/release/cleanup', $postReleaseTasks)) {
array_unshift($postReleaseTasks, 'deploy/release/cleanup');
}
}
foreach ($hosts as $host) {
$this->runtime->setWorkingHost($host);
if (!$this->runTasks($output, $postReleaseTasks)) {
throw new DeploymentException(sprintf(' Tasks failed on <fg=black;options=bold>%s</> stage, halting deployment', $this->getStageName()), 500);
}
$this->runtime->setWorkingHost(null);
}
}
// Run Post Deploy Tasks
$this->runtime->setStage(Runtime::POST_DEPLOY);
$postDeployTasks = $this->runtime->getTasks();
if ($this->runtime->getEnvironmentConfig('releases', false) && !$this->runtime->inRollback()) {
if (!in_array('deploy/targz/cleanup', $postDeployTasks)) {
array_unshift($postDeployTasks, 'deploy/targz/cleanup');
}
}
if ($this->runtime->getEnvironmentConfig('branch', false) && !$this->runtime->inRollback()) {
if (!in_array('git/change-branch', $postDeployTasks)) {
array_push($postDeployTasks, 'git/change-branch');
}
}
if (!$this->runTasks($output, $postDeployTasks)) {
throw new DeploymentException(sprintf(' Tasks failed on <fg=black;options=bold>%s</> stage, halting deployment', $this->getStageName()), 500);
}
}
/**
* Runs all the tasks
*
* @param OutputInterface $output
* @param $tasks
* @return bool
* @throws RuntimeException
*/
protected function runTasks(OutputInterface $output, $tasks)
{
if (count($tasks) == 0) {
$output->writeln(sprintf(' No tasks defined for <fg=black;options=bold>%s</>', $this->getStageName()));
$output->writeln('');
return true;
}
if ($this->runtime->getWorkingHost() != null) {
$output->writeln(sprintf(' Starting <fg=black;options=bold>%s</> tasks on host <fg=black;options=bold>%s</>:', $this->getStageName(), $this->runtime->getWorkingHost()));
} else {
$output->writeln(sprintf(' Starting <fg=black;options=bold>%s</> tasks:', $this->getStageName()));
}
$totalTasks = count($tasks);
$succeededTasks = 0;
foreach ($tasks as $taskName) {
/** @var AbstractTask $task */
$task = $this->taskFactory->get($taskName);
$output->write(sprintf(' Running <fg=magenta>%s</> ... ', $task->getDescription()));
$this->log(sprintf('Running task %s (%s)', $task->getDescription(), $task->getName()));
if ($this->runtime->inRollback() && !$task instanceof ExecuteOnRollbackInterface) {
$succeededTasks++;
$output->writeln('<fg=yellow>SKIPPED</>');
$this->log(sprintf('Task %s (%s) finished with SKIPPED, it was in a Rollback', $task->getDescription(), $task->getName()));
} else {
try {
if ($task->execute()) {
$succeededTasks++;
$output->writeln('<fg=green>OK</>');
$this->log(sprintf('Task %s (%s) finished with OK', $task->getDescription(), $task->getName()));
} else {
$output->writeln('<fg=red>FAIL</>');
$this->log(sprintf('Task %s (%s) finished with FAIL', $task->getDescription(), $task->getName()));
}
} catch (SkipException $exception) {
$succeededTasks++;
$output->writeln('<fg=yellow>SKIPPED</>');
$this->log(sprintf('Task %s (%s) finished with SKIPPED, thrown SkipException', $task->getDescription(), $task->getName()));
} catch (ErrorException $exception) {
$output->writeln(sprintf('<fg=red>FAIL</> [%s]', $exception->getTrimmedMessage()));
$this->log(sprintf('Task %s (%s) finished with FAIL, with Error "%s"', $task->getDescription(), $task->getName(), $exception->getMessage()));
}
}
}
if ($succeededTasks != $totalTasks) {
$alertColor = 'red';
} else {
$alertColor = 'green';
}
$output->writeln(sprintf(' Finished <fg=black;options=bold>%s</> tasks: <fg=%s>%d/%d</> done.', $this->getStageName(), $alertColor, $succeededTasks, $totalTasks));
$output->writeln('');
return ($succeededTasks == $totalTasks);
}
/**
* Get the Human friendly Stage name
*
* @return string
*/
protected function getStageName()
{
return Utils::getStageName($this->runtime->getStage());
}
}

View file

@ -0,0 +1,141 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Command\BuiltIn\Releases;
use Mage\Utils;
use Mage\Runtime\Exception\InvalidEnvironmentException;
use Mage\Runtime\Exception\DeploymentException;
use Mage\Runtime\Exception\RuntimeException;
use Symfony\Component\Process\Process;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
use Mage\Command\AbstractCommand;
/**
* Command for Listing all Releases
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class ListCommand extends AbstractCommand
{
/**
* Configure the Command
*/
protected function configure()
{
$this
->setName('releases:list')
->setDescription('List the releases on an environment')
->addArgument('environment', InputArgument::REQUIRED, 'Name of the environment to deploy to')
;
}
/**
* Execute the Command
*
* @param InputInterface $input
* @param OutputInterface $output
* @return int|mixed
* @throws DeploymentException
* @throws RuntimeException
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln('Starting <fg=blue>Magallanes</>');
$output->writeln('');
try {
$this->runtime->setEnvironment($input->getArgument('environment'));
} catch (InvalidEnvironmentException $exception) {
$output->writeln(sprintf('<error>%s</error>', $exception->getMessage()));
return $exception->getCode();
}
if (!$this->runtime->getEnvironmentConfig('releases', false)) {
throw new DeploymentException('Releases are not enabled', 700);
}
$output->writeln(sprintf(' Environment: <fg=green>%s</>', $this->runtime->getEnvironment()));
$this->log(sprintf('Environment: %s', $this->runtime->getEnvironment()));
if ($this->runtime->getConfigOptions('log_file', false)) {
$output->writeln(sprintf(' Logfile: <fg=green>%s</>', $this->runtime->getConfigOptions('log_file')));
}
$output->writeln('');
$hosts = $this->runtime->getEnvironmentConfig('hosts');
if (count($hosts) == 0) {
$output->writeln('No hosts defined');
$output->writeln('');
} else {
$hostPath = rtrim($this->runtime->getEnvironmentConfig('host_path'), '/');
foreach ($hosts as $host) {
$this->runtime->setWorkingHost($host);
// Get List of Releases
$cmdListReleases = sprintf('ls -1 %s/releases', $hostPath);
/** @var Process $process */
$process = $this->runtime->runRemoteCommand($cmdListReleases, true);
if (!$process->isSuccessful()) {
throw new RuntimeException(sprintf('Unable to retrieve releases from host %s', $host), 800);
}
$releases = explode(PHP_EOL, trim($process->getOutput()));
rsort($releases);
if (count($releases) == 0) {
$output->writeln(sprintf(' No releases available on host <fg=black;options=bold>%s</>:', $host));
} else {
// Get Current Release
$cmdCurrentRelease = sprintf('readlink -f %s/current', $hostPath);
/** @var Process $process */
$process = $this->runtime->runRemoteCommand($cmdCurrentRelease, true);
if (!$process->isSuccessful()) {
throw new RuntimeException(sprintf('Unable to retrieve current release from host %s', $host), 850);
}
$currentReleaseId = explode('/', trim($process->getOutput()));
$currentReleaseId = $currentReleaseId[count($currentReleaseId) - 1];
$output->writeln(sprintf(' Releases on host <fg=black;options=bold>%s</>:', $host));
foreach ($releases as $releaseId) {
$releaseDate = Utils::getReleaseDate($releaseId);
$output->write(sprintf(' Release ID: <fg=magenta>%s</> - Date: <fg=black;options=bold>%s</> [%s]',
$releaseId,
$releaseDate->format('Y-m-d H:i:s'),
Utils::getTimeDiff($releaseDate)
));
if ($releaseId == $currentReleaseId) {
$output->writeln(' <fg=red;options=bold>[current]</>');
} else {
$output->writeln('');
}
}
}
$this->runtime->setWorkingHost(null);
$output->writeln('');
}
}
$output->writeln('Finished <fg=blue>Magallanes</>');
return 0;
}
}

View file

@ -0,0 +1,148 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Command\BuiltIn\Releases;
use Mage\Task\TaskFactory;
use Mage\Runtime\Exception\InvalidEnvironmentException;
use Mage\Runtime\Exception\DeploymentException;
use Symfony\Component\Process\Process;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
use Mage\Command\BuiltIn\DeployCommand;
/**
* Command for Rolling Back a Releases
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class RollbackCommand extends DeployCommand
{
/**
* Configure the Command
*/
protected function configure()
{
$this
->setName('releases:rollback')
->setDescription('Rollback to a release on an environment')
->addArgument('environment', InputArgument::REQUIRED, 'Name of the environment to deploy to')
->addArgument('release', InputArgument::REQUIRED, 'The ID or the Index of the release to rollback to')
;
}
/**
* Execute the Command
*
* @param InputInterface $input
* @param OutputInterface $output
* @return int|mixed
* @throws DeploymentException
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln('Starting <fg=blue>Magallanes</>');
$output->writeln('');
try {
$this->runtime->setEnvironment($input->getArgument('environment'));
} catch (InvalidEnvironmentException $exception) {
$output->writeln(sprintf('<error>%s</error>', $exception->getMessage()));
return $exception->getCode();
}
if (!$this->runtime->getEnvironmentConfig('releases', false)) {
throw new DeploymentException('Releases are not enabled', 700);
}
// Check if the Release exists in all hosts
$releaseToRollback = $input->getArgument('release');
if ($releaseId = $this->checkReleaseAvailability($releaseToRollback)) {
$this->runtime->setReleaseId($releaseId)->setRollback(true);
$output->writeln(sprintf(' Environment: <fg=green>%s</>', $this->runtime->getEnvironment()));
$this->log(sprintf('Environment: %s', $this->runtime->getEnvironment()));
$output->writeln(sprintf(' Rollback to Release ID: <fg=green>%s</>', $this->runtime->getReleaseId()));
$this->log(sprintf('Release ID: %s', $this->runtime->getReleaseId()));
if ($this->runtime->getConfigOptions('log_file', false)) {
$output->writeln(sprintf(' Logfile: <fg=green>%s</>', $this->runtime->getConfigOptions('log_file')));
}
$output->writeln('');
// Get the Task Factory
$this->taskFactory = new TaskFactory($this->runtime);
try {
$this->runDeployment($output);
} catch (DeploymentException $exception) {
$output->writeln(sprintf('<error>%s</error>', $exception->getMessage()));
return $exception->getCode();
}
} else {
throw new DeploymentException(sprintf('Release %s is not available on all hosts', $releaseToRollback), 720);
}
$output->writeln('Finished <fg=blue>Magallanes</>');
return 0;
}
/**
* Check if the provided Release ID is available in all hosts
*
* @param string $releaseToRollback Release ID
* @return bool
*/
protected function checkReleaseAvailability($releaseToRollback)
{
$releaseIdCandidate = false;
$hosts = $this->runtime->getEnvironmentConfig('hosts');
$hostPath = rtrim($this->runtime->getEnvironmentConfig('host_path'), '/');
$releaseAvailableInAllHosts = true;
foreach ($hosts as $host) {
$this->runtime->setWorkingHost($host);
// Get List of Releases
$cmdListReleases = sprintf('ls -1 %s/releases', $hostPath);
/** @var Process $process */
$process = $this->runtime->runRemoteCommand($cmdListReleases, true);
if (!$process->isSuccessful()) {
$releases = [];
} else {
$releases = explode(PHP_EOL, trim($process->getOutput()));
rsort($releases);
}
if (in_array($releaseToRollback, $releases)) {
if ($releaseIdCandidate === false) {
$releaseIdCandidate = $releaseToRollback;
} else {
if ($releaseIdCandidate != $releaseToRollback) {
$releaseAvailableInAllHosts = false;
}
}
}
$this->runtime->setWorkingHost(null);
}
if ($releaseAvailableInAllHosts) {
return $releaseIdCandidate;
}
return false;
}
}

View file

@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Command\BuiltIn;
use Mage\Mage;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Mage\Command\AbstractCommand;
/**
* Version Command, return the current version of Magallanes
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class VersionCommand extends AbstractCommand
{
/**
* Configure the Command
*/
protected function configure()
{
$this
->setName('version')
->setDescription('Get the version of Magallanes')
;
}
/**
* Executes the Command
*
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln(sprintf('Magallanes v%s [%s]', Mage::VERSION, Mage::CODENAME));
return 0;
}
}

22
src/Mage/Mage.php Normal file
View file

@ -0,0 +1,22 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage;
/**
* Mallaganes base class
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class Mage
{
const VERSION = '3.0.0-alpha1';
const CODENAME = 'Nostromo';
}

View file

@ -0,0 +1,95 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage;
use Mage\Command\AbstractCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Symfony\Component\Console\Application;
use Symfony\Component\Yaml\Yaml;
use Mage\Runtime\Exception\RuntimeException;
/**
* The Console Application for launching the Mage command in a standalone instance
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class MageApplication extends Application
{
private $configuration;
protected $logger;
/**
* Configure the Magallanes Application
*
* @param $file string The YAML file from which to read the configuration
*
* @throws RuntimeException
*/
public function configure($file)
{
$config = Yaml::parse(file_get_contents($file));
if (array_key_exists('magephp', $config)) {
$this->configuration = $config['magephp'];
if (array_key_exists('log_dir', $this->configuration)) {
$logfile = sprintf('%s/%s.log', $this->configuration['log_dir'], date('Ymd_His'));
$this->configuration['log_file'] = $logfile;
$this->logger = new Logger('magephp');
$this->logger->pushHandler(new StreamHandler($logfile));
}
} else {
throw new RuntimeException(sprintf('The file "%s" does not have a valid Magallanes configuration.', $file));
}
}
/**
* Run the Application
*
* @param InputInterface $input
* @param OutputInterface $output
* @throws Exception
*/
public function run(InputInterface $input = null, OutputInterface $output = null)
{
$this->loadBuiltInCommands();
parent::run();
}
/**
* Loads the BuiltIn Commands
*/
protected function loadBuiltInCommands()
{
$finder = new Finder();
$finder->files()->in(__DIR__ . '/Command/BuiltIn')->name('*Command.php');
/** @var SplFileInfo $file */
foreach ($finder as $file) {
$class = substr('\\Mage\\Command\\BuiltIn\\' . str_replace('/', '\\', $file->getRelativePathname()), 0, -4);
if (class_exists($class)) {
$command = new $class();
if ($command instanceof AbstractCommand) {
$command->setLogger($this->logger);
$command->setConfiguration($this->configuration);
$this->add($command);
}
}
}
}
}

View file

@ -0,0 +1,20 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Runtime\Exception;
/**
* An Error occurred while Deploying
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class DeploymentException extends RuntimeException
{
}

View file

@ -0,0 +1,20 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Runtime\Exception;
/**
* The provided Environment is invalid
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class InvalidEnvironmentException extends RuntimeException
{
}

View file

@ -0,0 +1,22 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Runtime\Exception;
use Exception;
/**
* An Error occurred while running
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class RuntimeException extends Exception
{
}

View file

@ -0,0 +1,398 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Runtime;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Symfony\Component\Process\Process;
use Mage\Runtime\Exception\InvalidEnvironmentException;
/**
* Runtime is a container of all run in time configuration, stages of progress, hosts being deployed, etc.
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class Runtime
{
const PRE_DEPLOY = 'pre-deploy';
const ON_DEPLOY = 'on-deploy';
const POST_DEPLOY = 'post-deploy';
const ON_RELEASE = 'on-release';
const POST_RELEASE = 'post-release';
/**
* @var array Magallanes configuration
*/
protected $configuration;
/**
* @var string|null Environment being deployed
*/
protected $environment;
/**
* @var string Stage of Deployment
*/
protected $stage;
/**
* @var string|null The host being deployed to
*/
protected $workingHost;
/**
* @var string|null The Release ID
*/
protected $releaseId = null;
/**
* @var array Hold a bag of variables for sharing information between tasks, if needed
*/
protected $vars = [];
/**
* @var LoggerInterface|null The logger instance
*/
protected $logger;
/**
* @var bool Indicates if a Rollback operation is in progress
*/
protected $rollback = false;
/**
* Sets the Release ID
*
* @param string $releaseId Release ID
* @return Runtime
*/
public function setReleaseId($releaseId)
{
$this->releaseId = $releaseId;
return $this;
}
/**
* Retrieve the current Release ID
*
* @return null|string Release ID
*/
public function getReleaseId()
{
return $this->releaseId;
}
/**
* Sets the Runtime in Rollback mode On or Off
*
* @param bool $inRollback
* @return Runtime
*/
public function setRollback($inRollback)
{
$this->rollback = $inRollback;
return $this;
}
/**
* Indicates if Runtime is in rollback
*
* @return bool
*/
public function inRollback()
{
return $this->rollback;
}
/**
* Sets a value in the Vars bag
*
* @param mixed $key Variable name
* @param mixed $value Variable value
* @return Runtime
*/
public function setVar($key, $value)
{
$this->vars[$key] = $value;
return $this;
}
/**
* Retrieve a value from the Vars bag
*
* @param mixed $key Variable name
* @param mixed $default Variable default value, returned if not found
* @return mixed
*/
public function getVar($key, $default = null)
{
if (array_key_exists($key, $this->vars)) {
return $this->vars[$key];
}
return $default;
}
/**
* Sets the Logger instance
*
* @param LoggerInterface $logger Logger instance
* @return Runtime
*/
public function setLogger(LoggerInterface $logger = null)
{
$this->logger = $logger;
return $this;
}
/**
* Sets the Magallanes Configuration to the Runtime
*
* @param array $configuration Configuration
* @return Runtime
*/
public function setConfiguration($configuration)
{
$this->configuration = $configuration;
return $this;
}
/**
* Retrieve the Configuration
*
* @return array
*/
public function getConfiguration()
{
return $this->configuration;
}
/**
* Retrieves the Configuration options for a specific section in the configuration
*
* @param mixed $key Section name
* @param mixed $default Default value
* @return mixed
*/
public function getConfigOptions($key, $default = null)
{
if (array_key_exists($key, $this->configuration)) {
return $this->configuration[$key];
}
return $default;
}
/**
* Returns the configuration for the current Environment
* If $key is provided, it will be returned only that section, if not found the default value will be returned,
* if $key is not provided, the whole Environment's configuration will be returned
*
* @param mixed $key Section name
* @param mixed $default Default value
* @return mixed
* @throws InvalidEnvironmentException
*/
public function getEnvironmentConfig($key = null, $default = null)
{
if (!array_key_exists($this->environment, $this->configuration['environments'])) {
return [];
}
$config = $this->configuration['environments'][$this->environment];
if ($key !== null) {
if (array_key_exists($key, $config)) {
return $config[$key];
} else {
return $default;
}
}
return $config;
}
/**
* Sets the working Environment
*
* @param string $environment Environment name
* @return Runtime
* @throws InvalidEnvironmentException
*/
public function setEnvironment($environment)
{
if (array_key_exists('environments', $this->configuration) && array_key_exists($environment, $this->configuration['environments'])) {
$this->environment = $environment;
return $this;
}
throw new InvalidEnvironmentException(sprintf('The environment "%s" does not exists.', $environment), 1000);
}
/**
* Returns the current working Environment
*
* @return null|string
*/
public function getEnvironment()
{
return $this->environment;
}
/**
* Sets the working stage
*
* @param string $stage Stage code
* @return Runtime
*/
public function setStage($stage)
{
$this->stage = $stage;
return $this;
}
/**
* Retrieve the current wokring Stage
*
* @return string
*/
public function getStage()
{
return $this->stage;
}
/**
* Retrieve the defined Tasks for the current Environment and Stage
*
* @return array
* @throws InvalidEnvironmentException
*/
public function getTasks()
{
$config = $this->getEnvironmentConfig();
if (array_key_exists($this->stage, $config)) {
if (is_array($config[$this->stage])) {
return $config[$this->stage];
}
}
return [];
}
/**
* Sets the working Host
*
* @param string $host Host name
* @return Runtime
*/
public function setWorkingHost($host)
{
$this->workingHost = $host;
return $this;
}
/**
* Retrieve the working Host
*
* @return null|string
*/
public function getWorkingHost()
{
return $this->workingHost;
}
/**
* Logs a Message into the Logger
*
* @param string $message Log message
* @param string $level Log Level
*/
public function log($message, $level = LogLevel::DEBUG)
{
if ($this->logger instanceof LoggerInterface) {
$this->logger->log($level, $message);
}
}
/**
* Executes a command, it will be run Locally or Remotely based on the working Stage
*
* @param string $cmd Command to execute
* @param int $timeout Seconds to wait
* @return Process
*/
public function runCommand($cmd, $timeout = 120)
{
switch ($this->getStage()) {
case self::ON_DEPLOY:
case self::ON_RELEASE:
case self::POST_RELEASE:
return $this->runRemoteCommand($cmd, true, $timeout);
break;
default:
return $this->runLocalCommand($cmd, $timeout);
break;
}
}
/**
* Execute a command locally
*
* @param string $cmd Command to execute
* @param int $timeout Seconds to wait
* @return Process
*/
public function runLocalCommand($cmd, $timeout = 120)
{
$this->log($cmd, LogLevel::INFO);
$process = new Process($cmd);
$process->setTimeout($timeout);
$process->run();
$this->log($process->getOutput(), LogLevel::DEBUG);
if (!$process->isSuccessful()) {
$this->log($process->getErrorOutput(), LogLevel::ERROR);
}
return $process;
}
/**
* Executes a command remotely, if jail is true, it will run inside the Host Path and the Release (if available)
*
* @param string $cmd Command to execute
* @param bool $jail Jail the command
* @param int $timeout Seconds to wait
* @return Process
* @throws InvalidEnvironmentException
*/
public function runRemoteCommand($cmd, $jail = true, $timeout = 120)
{
$user = $this->getEnvironmentConfig('user');
$host = $this->getWorkingHost();
$sshFlags = $this->getEnvironmentConfig('ssh', '-p 22 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no');
$cmdDelegate = $cmd;
if ($jail) {
$hostPath = rtrim($this->getEnvironmentConfig('host_path'), '/');
if ($this->getReleaseId()) {
$cmdDelegate = sprintf('cd %s/releases/%s && %s', $hostPath, $this->getReleaseId(), $cmdDelegate);
} else {
$cmdDelegate = sprintf('cd %s && %s', $hostPath, $cmdDelegate);
}
}
$cmdRemote = str_replace(['"', '&', ';'], ['\"', '\&', '\;'], $cmdDelegate);
$cmdLocal = sprintf('ssh %s %s@%s sh -c \"%s\"', $sshFlags, $user, $host, $cmdRemote);
return $this->runLocalCommand($cmdLocal, $timeout);
}
}

View file

@ -0,0 +1,76 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Task;
use Mage\Runtime\Runtime;
/**
* Abstract base class for Magallanes Tasks
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
abstract class AbstractTask
{
/**
* @var array Task custom options
*/
protected $options = [];
/**
* @var Runtime
*/
protected $runtime;
/**
* Get the Name/Code of the Task
*
* @return string
*/
abstract public function getName();
/**
* Get a short Description of the Task
*
* @return string
*/
abstract public function getDescription();
/**
* Executes the Command
*
* @return bool
*/
abstract public function execute();
/**
* Set additional Options for the Task
*
* @param array $options Options
* @return AbstractTask
*/
public function setOptions($options = [])
{
$this->options = $options;
return $this;
}
/**
* Set the Runtime instance
*
* @param Runtime $runtime
* @return AbstractTask
*/
public function setRuntime(Runtime $runtime)
{
$this->runtime = $runtime;
return $this;
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace Mage\Task\BuiltIn\Composer;
use Symfony\Component\Process\Process;
use Mage\Task\AbstractTask;
/**
* Composer Task - Generate Autoload
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class GenerateAutoloadTask extends AbstractTask
{
public function getName()
{
return 'composer/generate-autoload';
}
public function getDescription()
{
return '[Composer] Generate Autoload';
}
public function execute()
{
$options = $this->getOptions();
$command = $options['path'] . ' dumpautoload ' . $options['flags'];
/** @var Process $process */
$process = $this->runtime->runCommand($command);
return $process->isSuccessful();
}
protected function getOptions()
{
$options = array_merge(
['path' => 'composer', 'flags' => '--optimize'],
$this->runtime->getConfigOptions('composer', []),
$this->options
);
return $options;
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace Mage\Task\BuiltIn\Composer;
use Symfony\Component\Process\Process;
use Mage\Task\AbstractTask;
/**
* Composer Task - Install Vendors
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class InstallTask extends AbstractTask
{
public function getName()
{
return 'composer/install';
}
public function getDescription()
{
return '[Composer] Install';
}
public function execute()
{
$options = $this->getOptions();
$command = $options['path'] . ' install ' . $options['flags'];
/** @var Process $process */
$process = $this->runtime->runCommand($command);
return $process->isSuccessful();
}
protected function getOptions()
{
$options = array_merge(
['path' => 'composer', 'flags' => '--dev'],
$this->runtime->getConfigOptions('composer', []),
$this->options
);
return $options;
}
}

View file

@ -0,0 +1,67 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Task\BuiltIn\Deploy\Release;
use Symfony\Component\Process\Process;
use Mage\Task\AbstractTask;
/**
* Release Task - Cleanup old releases
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class CleanupTask extends AbstractTask
{
public function getName()
{
return 'deploy/release/cleanup';
}
public function getDescription()
{
return '[Release] Cleaning up old Releases';
}
public function execute()
{
$hostPath = rtrim($this->runtime->getEnvironmentConfig('host_path'), '/');
$currentReleaseId = $this->runtime->getReleaseId();
$maxReleases = $this->runtime->getEnvironmentConfig('releases');
$cmdListReleases = sprintf('ls -1 %s/releases', $hostPath);
/** @var Process $process */
$process = $this->runtime->runRemoteCommand($cmdListReleases, false);
if ($process->isSuccessful()) {
$releases = $process->getOutput();
$releases = explode(PHP_EOL, trim($releases));
if (count($releases) > $maxReleases) {
sort($releases);
$releasesToDelete = array_slice($releases, 0, count($releases) - $maxReleases);
foreach ($releasesToDelete as $releaseId) {
if ($releaseId != $currentReleaseId) {
$cmdDeleteRelease = sprintf('rm -rf %s/releases/%s', $hostPath, $releaseId);
/** @var Process $process */
$process = $this->runtime->runRemoteCommand($cmdDeleteRelease, false);
if (!$process->isSuccessful()) {
return false;
}
}
}
}
return true;
}
return false;
}
}

View file

@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Task\BuiltIn\Deploy\Release;
use Symfony\Component\Process\Process;
use Mage\Task\AbstractTask;
/**
* Release Task - Create the Release Directory
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class PrepareTask extends AbstractTask
{
public function getName()
{
return 'deploy/release/prepare';
}
public function getDescription()
{
return '[Release] Preparing Release';
}
public function execute()
{
$hostPath = rtrim($this->runtime->getEnvironmentConfig('host_path'), '/');
$cmdMakeDir = sprintf('mkdir -p %s/releases/%s', $hostPath, $this->runtime->getReleaseId());
/** @var Process $process */
$process = $this->runtime->runRemoteCommand($cmdMakeDir, false);
return $process->isSuccessful();
}
}

View file

@ -0,0 +1,45 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Task\BuiltIn\Deploy;
use Mage\Task\ExecuteOnRollbackInterface;
use Symfony\Component\Process\Process;
use Mage\Task\AbstractTask;
/**
* Release Task - Create the Symlink
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class ReleaseTask extends AbstractTask implements ExecuteOnRollbackInterface
{
public function getName()
{
return 'deploy/release';
}
public function getDescription()
{
return '[Release] Creating Symlink';
}
public function execute()
{
$hostPath = rtrim($this->runtime->getEnvironmentConfig('host_path'), '/');
$releaseId = $this->runtime->getReleaseId();
$cmdLinkRelease = sprintf('cd %s && ln -snf releases/%s current', $hostPath, $releaseId);
/** @var Process $process */
$process = $this->runtime->runRemoteCommand($cmdLinkRelease, false);
return $process->isSuccessful();
}
}

View file

@ -0,0 +1,63 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Task\BuiltIn\Deploy;
use Symfony\Component\Process\Process;
use Mage\Task\AbstractTask;
/**
* Rsync Task - Copy files with Rsync
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class RsyncTask extends AbstractTask
{
public function getName()
{
return 'deploy/rsync';
}
public function getDescription()
{
return '[Deploy] Copying files with Rsync';
}
public function execute()
{
$user = $this->runtime->getEnvironmentConfig('user');
$host = $this->runtime->getWorkingHost();
$hostPath = rtrim($this->runtime->getEnvironmentConfig('host_path'), '/');
$targetDir = rtrim($hostPath, '/');
if ($this->runtime->getEnvironmentConfig('releases', false)) {
$targetDir = sprintf('%s/releases/%s', $hostPath, $this->runtime->getReleaseId());
}
$excludes = $this->getExcludes();
$cmdRsync = sprintf('rsync -avz %s ./ %s@%s:%s', $excludes, $user, $host, $targetDir);
/** @var Process $process */
$process = $this->runtime->runLocalCommand($cmdRsync, 600);
return $process->isSuccessful();
}
protected function getExcludes()
{
$excludes = $this->runtime->getEnvironmentConfig('exclude', []);
$excludes = array_merge(['.git'], $excludes);
foreach ($excludes as &$exclude) {
$exclude = '--exclude=' . $exclude;
}
return implode(' ', $excludes);
}
}

View file

@ -0,0 +1,52 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Task\BuiltIn\Deploy\TarGz;
use Mage\Runtime\Exception\DeploymentException;
use Symfony\Component\Process\Process;
use Mage\Task\AbstractTask;
/**
* TarGz Task - Delete temporal Tar
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class CleanupTask extends AbstractTask
{
public function getName()
{
return 'deploy/targz/cleanup';
}
public function getDescription()
{
return '[Deploy] Cleanup TarGZ file';
}
public function execute()
{
if (!$this->runtime->getEnvironmentConfig('releases', false)) {
throw new DeploymentException('This task is only available with releases enabled', 400);
}
$tarGzLocal = $this->runtime->getVar('targz_local');
$cmdDeleteTarGz = sprintf('rm %s', $tarGzLocal);
/** @var Process $process */
$process = $this->runtime->runLocalCommand($cmdDeleteTarGz);
if ($process->isSuccessful()) {
return true;
}
return false;
}
}

View file

@ -0,0 +1,69 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Task\BuiltIn\Deploy\TarGz;
use Mage\Runtime\Exception\DeploymentException;
use Symfony\Component\Process\Process;
use Mage\Task\AbstractTask;
/**
* TarGz Task - Copy Tar
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class CopyTask extends AbstractTask
{
public function getName()
{
return 'deploy/targz/copy';
}
public function getDescription()
{
return '[Deploy] Copying files with TarGZ';
}
public function execute()
{
if (!$this->runtime->getEnvironmentConfig('releases', false)) {
throw new DeploymentException('This task is only available with releases enabled', 400);
}
$user = $this->runtime->getEnvironmentConfig('user');
$host = $this->runtime->getWorkingHost();
$scpFlags = $this->runtime->getEnvironmentConfig('scp', '-P 22 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no');
$hostPath = rtrim($this->runtime->getEnvironmentConfig('host_path'), '/');
$currentReleaseId = $this->runtime->getReleaseId();
$targetDir = sprintf('%s/releases/%s', $hostPath, $currentReleaseId);
$tarGzLocal = $this->runtime->getVar('targz_local');
$tarGzRemote = basename($tarGzLocal);
$cmdCopy = sprintf('scp %s %s %s@%s:%s/%s', $scpFlags, $tarGzLocal, $user, $host, $targetDir, $tarGzRemote);
/** @var Process $process */
$process = $this->runtime->runLocalCommand($cmdCopy, 300);
if ($process->isSuccessful()) {
$cmdUntar = sprintf('cd %s && tar xfz %s', $targetDir, $tarGzRemote);
$process = $this->runtime->runRemoteCommand($cmdUntar, false, 600);
if ($process->isSuccessful()) {
$cmdDelete = sprintf('rm %s/%s', $targetDir, $tarGzRemote);
$process = $this->runtime->runRemoteCommand($cmdDelete, false);
if ($process->isSuccessful()) {
return true;
}
}
}
return false;
}
}

View file

@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Task\BuiltIn\Deploy\TarGz;
use Mage\Runtime\Exception\DeploymentException;
use Symfony\Component\Process\Process;
use Mage\Task\AbstractTask;
/**
* TarGz Task - Create temporal Tar
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class PrepareTask extends AbstractTask
{
public function getName()
{
return 'deploy/targz/prepare';
}
public function getDescription()
{
return '[Deploy] Preparing TarGz file';
}
public function execute()
{
if (!$this->runtime->getEnvironmentConfig('releases', false)) {
throw new DeploymentException('This task is only available with releases enabled', 400);
}
$tarGzLocal = tempnam(sys_get_temp_dir(), 'mage');
$this->runtime->setVar('targz_local', $tarGzLocal);
$excludes = $this->getExcludes();
$cmdTarGz = sprintf('tar cfz %s %s ./', $tarGzLocal, $excludes);
/** @var Process $process */
$process = $this->runtime->runLocalCommand($cmdTarGz, 300);
return $process->isSuccessful();
}
protected function getExcludes()
{
$excludes = $this->runtime->getEnvironmentConfig('exclude', []);
$excludes = array_merge(['.git'], $excludes);
foreach ($excludes as &$exclude) {
$exclude = '--exclude=' . $exclude;
}
return implode(' ', $excludes);
}
}

View file

@ -0,0 +1,88 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Task\BuiltIn\Git;
use Mage\Task\SkipException;
use Symfony\Component\Process\Process;
use Mage\Task\AbstractTask;
/**
* Git Task - Checkout Branch
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class ChangeBranchTask extends AbstractTask
{
public function getName()
{
return 'git/change-branch';
}
public function getDescription()
{
$options = $this->getOptions();
$branch = $options['branch'];
if ($this->runtime->getVar('git_revert_branch', false)) {
$branch = $this->runtime->getVar('git_revert_branch');
}
return sprintf('[Git] Change Branch (%s)', $branch);
}
public function execute()
{
$options = $this->getOptions();
$branch = $options['branch'];
if (!$this->runtime->getVar('git_revert_branch', false)) {
$cmdGetCurrent = sprintf('%s branch | grep "*"', $options['path']);
/** @var Process $process */
$process = $this->runtime->runLocalCommand($cmdGetCurrent);
if ($process->isSuccessful()) {
$initialBranch = str_replace('* ', '', trim($process->getOutput()));
if ($initialBranch == $branch) {
throw new SkipException();
} else {
$this->runtime->setVar('git_revert_branch', $initialBranch);
}
} else {
return false;
}
} else {
$branch = $this->runtime->getVar('git_revert_branch');
}
$cmdChange = sprintf('%s checkout %s', $options['path'], $branch);
/** @var Process $process */
$process = $this->runtime->runLocalCommand($cmdChange);
return $process->isSuccessful();
}
protected function getOptions()
{
$config = $this->runtime->getEnvironmentConfig();
$branch = 'master';
if (array_key_exists('branch', $config)) {
$branch = $config['branch'];
}
$options = array_merge(
['path' => 'git', 'branch' => $branch],
$this->options
);
return $options;
}
}

View file

@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Task\BuiltIn\Git;
use Symfony\Component\Process\Process;
use Mage\Task\AbstractTask;
/**
* Git Task - Pull
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class UpdateTask extends AbstractTask
{
public function getName()
{
return 'git/update';
}
public function getDescription()
{
return '[Git] Update';
}
public function execute()
{
$options = $this->getOptions();
$command = $options['path'] . ' pull';
/** @var Process $process */
$process = $this->runtime->runLocalCommand($command);
return $process->isSuccessful();
}
protected function getOptions()
{
$config = $this->runtime->getEnvironmentConfig();
$branch = 'master';
if (array_key_exists('branch', $config)) {
$branch = $config['branch'];
}
$options = array_merge(
['path' => 'git', 'branch' => $branch],
$this->options
);
return $options;
}
}

View file

@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Task\BuiltIn\Symfony;
use Symfony\Component\Process\Process;
use Mage\Task\AbstractTask;
/**
* Symfony Task - Dump Assetics
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class AsseticDumpTask extends AbstractTask
{
public function getName()
{
return 'symfony/assetic-dump';
}
public function getDescription()
{
return '[Symfony] Assetic Dump';
}
public function execute()
{
$options = $this->getOptions();
$command = $options['console'] . ' assetic:dump --env=' . $options['env'] . ' ' . $options['flags'];
/** @var Process $process */
$process = $this->runtime->runCommand($command);
return $process->isSuccessful();
}
protected function getOptions()
{
$options = array_merge(
['path' => 'bin/console', 'env' => 'dev', 'flags' => ''],
$this->runtime->getConfigOptions('symfony', []),
$this->options
);
return $options;
}
}

View file

@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Task\BuiltIn\Symfony;
use Symfony\Component\Process\Process;
use Mage\Task\AbstractTask;
/**
* Symfony Task - Install Assets
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class AssetsInstallTask extends AbstractTask
{
public function getName()
{
return 'symfony/assets-install';
}
public function getDescription()
{
return '[Symfony] Assets Install';
}
public function execute()
{
$options = $this->getOptions();
$command = $options['console'] . ' assets:install --env=' . $options['env'] . ' ' . $options['flags'] . ' ' . $options['target'];
/** @var Process $process */
$process = $this->runtime->runCommand($command);
return $process->isSuccessful();
}
protected function getOptions()
{
$options = array_merge(
['path' => 'bin/console', 'env' => 'dev', 'target' => 'web', 'flags' => '--symlink --relative'],
$this->runtime->getConfigOptions('symfony', []),
$this->options
);
return $options;
}
}

View file

@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Task\BuiltIn\Symfony;
use Symfony\Component\Process\Process;
use Mage\Task\AbstractTask;
/**
* Symfony Task - Clear Cache
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class CacheClearTask extends AbstractTask
{
public function getName()
{
return 'symfony/cache-clear';
}
public function getDescription()
{
return '[Symfony] Cache Clear';
}
public function execute()
{
$options = $this->getOptions();
$command = $options['console'] . ' cache:clear --env=' . $options['env'] . ' ' . $options['flags'];
/** @var Process $process */
$process = $this->runtime->runCommand($command);
return $process->isSuccessful();
}
protected function getOptions()
{
$options = array_merge(
['path' => 'bin/console', 'env' => 'dev', 'flags' => ''],
$this->runtime->getConfigOptions('symfony', []),
$this->options
);
return $options;
}
}

View file

@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Task\BuiltIn\Symfony;
use Symfony\Component\Process\Process;
use Mage\Task\AbstractTask;
/**
* Symfony Task - Cache Warmup
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class CacheWarmupTask extends AbstractTask
{
public function getName()
{
return 'symfony/cache-warmup';
}
public function getDescription()
{
return '[Symfony] Cache Warmup';
}
public function execute()
{
$options = $this->getOptions();
$command = $options['console'] . ' cache:warmup --env=' . $options['env'] . ' ' . $options['flags'];
/** @var Process $process */
$process = $this->runtime->runCommand($command);
return $process->isSuccessful();
}
protected function getOptions()
{
$options = array_merge(
['path' => 'bin/console', 'env' => 'dev', 'flags' => ''],
$this->runtime->getConfigOptions('symfony', []),
$this->options
);
return $options;
}
}

View file

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Task;
use Exception;
/**
* The Task Failed, and it has a Custom Message
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class ErrorException extends Exception
{
public function getTrimmedMessage($maxLength = 20)
{
$message = $this->getMessage();
if (strlen($message) > $maxLength) {
$message = substr($message, 0, $maxLength) . '...';
}
return $message;
}
}

View file

@ -0,0 +1,20 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Task;
/**
* The Task will be Executed in a Rollback
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
interface ExecuteOnRollbackInterface
{
}

View file

@ -0,0 +1,22 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Task;
use Exception;
/**
* The Task will be Skipped
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class SkipException extends Exception
{
}

View file

@ -0,0 +1,112 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Task;
use Mage\Runtime\Runtime;
use Mage\Runtime\Exception\RuntimeException;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
/**
* Task Factory
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class TaskFactory
{
/**
* @var Runtime
*/
protected $runtime;
/**
* @var array Registered Tasks
*/
protected $registeredTasks = [];
/**
* Constructor
*
* @param Runtime $runtime
*/
public function __construct(Runtime $runtime)
{
$this->runtime = $runtime;
$this->loadBuiltInTasks();
}
/**
* Add a Task
*
* @param AbstractTask $task
*/
public function add(AbstractTask $task)
{
$task->setRuntime($this->runtime);
$this->registeredTasks[$task->getName()] = $task;
}
/**
* Get a Task by it's registered Name/Code, or it can be a Class Name,
* in that case the class will be instantiated
*
* @param string $name Name/Code or Class of the Task
* @return AbstractTask
* @throws RuntimeException
*/
public function get($name)
{
if (is_array($name)) {
$options = $name;
list($name) = array_keys($name);
$options = $options[$name];
} else {
$options = [];
}
if (array_key_exists($name, $this->registeredTasks)) {
/** @var AbstractTask $task */
$task = $this->registeredTasks[$name];
$task->setOptions($options);
return $task;
} elseif (class_exists($name)) {
$task = new $name();
if ($task instanceof AbstractTask) {
$task->setOptions($options);
$this->add($task);
return $task;
}
}
throw new RuntimeException(sprintf('Invalid task name "%s"', $name));
}
/**
* Load BuiltIn Tasks
*/
protected function loadBuiltInTasks()
{
$finder = new Finder();
$finder->files()->in(__DIR__ . '/BuiltIn')->name('*Task.php');
/** @var SplFileInfo $file */
foreach ($finder as $file) {
$class = substr('\\Mage\\Task\\BuiltIn\\' . str_replace('/', '\\', $file->getRelativePathname()), 0, -4);
if (class_exists($class)) {
$task = new $class();
if ($task instanceof AbstractTask) {
$this->add($task);
}
}
}
}
}

121
src/Mage/Utils.php Normal file
View file

@ -0,0 +1,121 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage;
use Mage\Runtime\Runtime;
use DateTime;
/**
* Utility class for resolving trivial operations
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class Utils
{
/**
* Given a stage code it will resolve a human friendly name
*
* @param string $stage
* @return string
*/
public static function getStageName($stage)
{
switch ($stage) {
case Runtime::PRE_DEPLOY:
return 'Pre Deployment';
break;
case Runtime::ON_DEPLOY:
return 'On Deployment';
break;
case Runtime::POST_DEPLOY:
return 'Post Deployment';
break;
case Runtime::ON_RELEASE:
return 'On Release';
break;
case Runtime::POST_RELEASE:
return 'Post Release';
break;
}
return $stage;
}
/**
* Given a Release ID, convert it to a DateTime instance
*
* @param string $releaseId The Release ID
* @return DateTime
*/
public static function getReleaseDate($releaseId)
{
$formatted = sprintf('%d%d%d%d-%d%d-%d%d %d%d:%d%d:%d%d',
$releaseId[0], $releaseId[1], $releaseId[2], $releaseId[3],
$releaseId[4], $releaseId[5],
$releaseId[6], $releaseId[7],
$releaseId[8], $releaseId[9],
$releaseId[10], $releaseId[11],
$releaseId[12], $releaseId[13]);
return new DateTime($formatted);
}
/**
* Given a Date, calculate friendly how much time has passed
*
* @param DateTime $releaseDate
* @return string
*/
public static function getTimeDiff(DateTime $releaseDate)
{
$textDiff = '';
$now = new DateTime();
$diff = $now->diff($releaseDate);
if ($diff->format('%a') <= 7) {
if ($diff->format('%d') == 7) {
$textDiff = 'a week ago';
} elseif ($diff->format('%d') > 0 && $diff->format('%d') < 7) {
$days = $diff->format('%d');
if ($days <= 1) {
$textDiff = 'one day ago';
} else {
$textDiff = $days . ' days ago';
}
} elseif ($diff->format('%d') == 0 && $diff->format('%h') > 0) {
$hours = $diff->format('%h');
if ($hours <= 1) {
$textDiff = 'one hour ago';
} else {
$textDiff = $hours . ' hours ago';
}
} elseif ($diff->format('%d') == 0 && $diff->format('%h') == 0) {
$minutes = $diff->format('%i');
if ($minutes <= 1) {
$textDiff = 'one minute ago';
} else {
$textDiff = $minutes . ' minutes ago';
}
} elseif ($diff->format('%d') == 0 && $diff->format('%h') == 0 && $diff->format('%i') == 0) {
$seconds = $diff->format('%s');
if ($seconds < 10) {
$textDiff = 'just now!';
} else {
$textDiff = $seconds . ' seconds ago';
}
}
}
return $textDiff;
}
}