diff --git a/.gitignore b/.gitignore index a147ed3..b5538e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ vendor mage.phar +bin +!bin/mage # OS generated files # // GitHub Recommendation ###################### @@ -7,3 +9,7 @@ mage.phar ehthumbs.db Icon? Thumbs.db + +# IDE generated files +.idea +nbproject diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..5d2fb0d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: php + +php: + - 5.3 + - 5.4 + - 5.5 + - 5.6 + +install: + - composer install --dev --prefer-source diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7af6191 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,123 @@ +Contributor guidelines for Magallanes +===================================== +Welcome to Magallanes! We are much appreciated you've decided to contribute to this project! +Please read the following guidelines to make your and our work easier and cleaner. + +**TL;DR** + +1. Write clean code with no mess left +2. Contribute the docs when adding configurable new feature +3. Create your pull request from `develop` branch +4. Ensure your code is fully covered by tests + + +---------- + +# Reporting issues +If you have a problem or you've noticed some bug, please feel free to open new issue. However please follow the rules below: +* First, make sure that similar/the same issue doesn't already exist +* If you've already found the solution of the problem you are about to report, please feel free to open a new pull request. Then follow the rules below in **Developing Magallanes** section. +* If you are able to, include some test cases or steps to reproduce the bug for us to examine the problem to reach the problem origin. + +## Opening pull requests +Pull Request are actually some kind of issues with code, so please follow the rules above in **Reporting issues** section before making the pull requests. +Our code isn't so beautiful, tested and testable as we would like it to be but if you're pushing your code please be sure it meets the requirements from **Organization and code quality** chapter. We want to improve the existing code to facilitate extending it and making fixes quicker. So if you are editing some class and you find it ugly, please do not ignore it. Following [The Boy Scout Rule](http://www.informit.com/articles/article.aspx?p=1235624&seqNum=6) - *Leave the campground cleaner than you found it* - we all can improve the existing code. +Keep your git commits as atomic as possible. It brings better history description only by commit messages and allow us to eventually revert the single commits with no affects. Your commit messages should be also descriptive. The first line of commit should be short, try to limit it up to 50 characters. The messages should be written imperatively, like following: +``` +Add MyCustomTask +``` +If you need to write more about your tasks, please enter the description in the next lines. There you can write whatever you want, like why you made this commit and what it consists of. +``` +Add MyCustomTask + +This task has very important role for the project. I found this very useful for all developers. I think the deploy with it should be a lot easier. +``` + +Optionally you can tag your messages in square brackets. It can be issue number or simple flag. Examples: +``` +[#183] Add new CONTRIBUTING document +[FIX] Set correct permissions on deploy stage +[FEATURE] Create new PermissionsTask +``` +Remember of square brackets when adding issue number. If you'd forget adding them, your whole message will be a comment! + +## Contributing the documentation +Magallanes is made to deploy your application quick and with no need to write redudant code. Usage is as simple as writing the configuration for target project in YAML files. In the nearest future we would like to make some Wiki with all available options, tasks and commands. For now, the only "documentation" are example files in `docs` directory. If the code you are going to include in your pull requests adds or changes config options, please make sure that you create a new sample in those files. You should also do the same with commands. + +# Developing Magallanes +## Branches +The flow is pretty simple. +In most common cases we work on `develop` branch. It's a branch with the newest changes which sometimes need more testing. All pull requests are opened to be merged into that branch. That keeps us safe to not deploy unsafe code into production - `master` branch. When we decide that every changeset in `develop` is tested manually and works as it's intented, we merge it to master. +If the change you commited is pretty hot and needs to be released ASAP, you are allowed to make a pull request to `master` branch. But it's the only case, please try to avoid it. All pull request that are not made on `develop` will be rejected. +If you want to use develop branch in your code, simple pass `dev-develop` to dependency version in your `composer.json` file: +```json +{ + "require": { + "andres-montanez/magallanes": "dev-develop" + } +} +``` +## Organization and code quality +We use [PSR2](http://www.php-fig.org/psr/psr-2/) as PHP coding standard. +Some of the rules we follow that are not included in document above: + +* Variables' and properties' names are camelCased (e.g.: `$thisIsMyVariable`) +* Avoid too long or too short variables' and methods' names, like `$thisIsMyAwesomeVariableAndImProudOfIt` +* Names of your properties/methods should be intuitive and self-describing - that means your code should look like a book. Developers who read the code should immediately know what a variable includes or what a method does. +* Let your methods will be verbs. For boolean methods, prefix it with `is`, `has`, and so on. E.g.: `isConfigurable`, `hasChildren`. +* Be [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) and follow [KISS](http://en.wikipedia.org/wiki/KISS_principle) - let the class be responsible only for its tasks. +* Write testable code and if there's a need - easy extendible. +* Avoid duplications + +The rules above have been set a long time after the project has started. If you notice some violations, please open a new issue or even pull request with fixes. It'll be much appreciated. + +### Tools you can use to ensure your code quality + +1. **PHP-CodeSniffer** +2. **PHP Mess Detector** +3. PHP Copy/Paste Detector +4. PHP Dead Code Detector +5. [PHP Coding Standards Fixer](http://cs.sensiolabs.org) + +## Testing and quality +We use PHPUnit to test our code. The whole project is not covered with tests right now but we've been working on it for some time. If you want your code to be merged into Magallanes, we want you to push it with proper tests. We would love to reach and keep at least 90% of line code coverage. In short time we want to configure quality tools to make sure your code is tested properly with minimum coverage. Anyway, try to keep 100% of Code Coverage in your pull requests. To run your tests with code coverage report, you can either run it with: +``` +bin/phpunit --coverage-text +``` +or with more friendly and detailed user graphical representation, into HTML: +``` +bin/phpunit --coverate-html report +``` +where `report` is the directory where html report files shall be stored. +Tests structure follow the same structure as production code with `Test` suffix in class and file name. All tests should go to `tests` directory in project root. So if you've created a class `Mage\Tasks\BuilIn\NewTask` the testing class should be called `MageTest\Tasks\BuiltIn\NewTaskTest`. +To provide more strict tests, point what the method actually test and omit testing some classes indirectly, remember to add annotations to your tests: + +* **`@coversDefaultClass` class annotations** +This prevent to to write full class name each time you write `@covers` for test method (see next point) +```php + +/** + * @coversDefaultClass Mage\Console\Colors + */ +class ColorsTest extends PHPUnit_Framework_TestCase +{ +``` +* **`@covers` methods annotations** +```php +/** + * @covers ::add + */ +public function testAddOnePlusOne() +{ + // ... +} +``` +**Note:** If you omit `coversDefaultClass` for test class, you need to write full class name in `@covers` annotation. + +**Test class musn't test more than one class and any other classes than class being actually tested** + +## Configuration +Magallanes configuration is kept in YAML files. Please follow those rules while adding or changing the configuration: +* Keep 2 spaces indentation in each level +* Multi-word config keys should be joined with dash (`-`), like `my-custom-task` +* If your contribution includes new config key, please be sure that you've documented it in configuration documentation. diff --git a/Mage/Command/BuiltIn/DeployCommand.php b/Mage/Command/BuiltIn/DeployCommand.php index f53eac0..8649ef8 100644 --- a/Mage/Command/BuiltIn/DeployCommand.php +++ b/Mage/Command/BuiltIn/DeployCommand.php @@ -59,6 +59,15 @@ class DeployCommand extends AbstractCommand implements RequiresEnvironment */ const IN_PROGRESS = 'in_progress'; + /** + * Stage where possible throw Rollback Exception + * @var array + */ + public $acceptedStagesToRollback = array( + AbstractTask::STAGE_POST_RELEASE, + AbstractTask::STAGE_POST_DEPLOY + ); + /** * Time the Deployment has Started * @var integer @@ -434,22 +443,46 @@ class DeployCommand extends AbstractCommand implements RequiresEnvironment } } - protected function runRollbackTask(){ + protected function runRollbackTask(AbstractTask $task){ $this->getConfig()->reload(); $hosts = $this->getConfig()->getHosts(); - if (count($hosts) == 0) { + Console::output("",1,2); + Console::output("Starting the rollback",1,1); + + if(!in_array($task->getStage(), $this->acceptedStagesToRollback ) ) { + $stagesString = implode(', ',$this->acceptedStagesToRollback); + Console::output("Warning! Rollback during deployment can be called only at the stages: $stagesString ",1); + Console::output("Rollback: ABORTING",1,3); + + } elseif (count($hosts) == 0) { Console::output('Warning! No hosts defined, unable to get releases.', 1, 3); } else { $result = true; - foreach ($hosts as $host) { - $this->getConfig()->setHost($host); + foreach ($hosts as $hostKey => $host) { + $hostConfig = null; + if (is_array($host)) { + $hostConfig = $host; + $host = $hostKey; + } + // Set Host and Host Specific Config + $this->getConfig()->setHost($host); + $this->getConfig()->setHostConfig($hostConfig); $this->getConfig()->setReleaseId(-1); - $task = Factory::get('releases/rollback', $this->getConfig()); + + $task = Factory::get(array( + 'name'=>'releases/rollback', + 'parameters' => array('inDeploy'=>true) + ), + $this->getConfig(), + false, + $task->getStage() + ); $task->init(); $result = $task->run() && $result; + } return $result; } @@ -490,8 +523,8 @@ class DeployCommand extends AbstractCommand implements RequiresEnvironment $result = false; } } catch (RollbackException $e) { - Console::output('FAIL, Rollback started [Message: ' . $e->getMessage() . ']', 0); - $this->runRollbackTask(); + Console::output('FAIL, Rollback catched [Message: ' . $e->getMessage() . ']', 0); + $this->runRollbackTask($task); $result = false; } catch (ErrorWithMessageException $e) { diff --git a/Mage/Config.php b/Mage/Config.php index e839c22..dd78593 100644 --- a/Mage/Config.php +++ b/Mage/Config.php @@ -391,6 +391,16 @@ class Config return $this->deployment('identity-file') ? ('-i ' . $this->deployment('identity-file') . ' ') : ''; } + /** + * Get the ConnectTimeout option + * + * @return string + */ + public function getConnectTimeoutOption() + { + return $this->environmentConfig('connect-timeout') ? ('-o ConnectTimeout=' . $this->environmentConfig('connect-timeout') . ' ') : ''; + } + /** * Get the current Host * diff --git a/Mage/Console.php b/Mage/Console.php index b9dd97c..8dd7940 100644 --- a/Mage/Console.php +++ b/Mage/Console.php @@ -48,6 +48,12 @@ class Console */ private static $logEnabled = true; + /** + * Enables or disables verbose logging + * @var boolean + */ + private static $verboseLogEnabled = false; + /** * String Buffer for the screen output * @var string @@ -107,6 +113,8 @@ class Console self::$logEnabled = $config->general('logging', false); } + self::$verboseLogEnabled = self::isVerboseLoggingEnabled(); + // Greetings if ($showGreetings) { if (!self::$logEnabled) { @@ -173,15 +181,17 @@ class Console { self::log(strip_tags($message)); - self::$screenBuffer .= str_repeat("\t", $tabs) - . strip_tags($message) - . str_repeat(PHP_EOL, $newLine); + if (!self::$verboseLogEnabled) { + self::$screenBuffer .= str_repeat("\t", $tabs) + . strip_tags($message) + . str_repeat(PHP_EOL, $newLine); - $output = str_repeat("\t", $tabs) - . Colors::color($message, self::$config) - . str_repeat(PHP_EOL, $newLine); + $output = str_repeat("\t", $tabs) + . Colors::color($message, self::$config) + . str_repeat(PHP_EOL, $newLine); - echo $output; + echo $output; + } } /** @@ -227,6 +237,10 @@ class Console $message = date('Y-m-d H:i:s -- ') . $message; fwrite(self::$log, $message . PHP_EOL); + + if (self::$verboseLogEnabled) { + echo $message . PHP_EOL; + } } } @@ -286,4 +300,15 @@ class Console } } + /** + * Check if verbose logging is enabled + * @return boolean + */ + protected static function isVerboseLoggingEnabled() + { + return self::$config->getParameter('verbose', false) + || self::$config->general('verbose_logging') + || self::$config->environmentConfig('verbose_logging', false); + } + } diff --git a/Mage/Task/AbstractTask.php b/Mage/Task/AbstractTask.php index 663b387..491adfb 100644 --- a/Mage/Task/AbstractTask.php +++ b/Mage/Task/AbstractTask.php @@ -201,6 +201,7 @@ abstract class AbstractTask $localCommand = 'ssh ' . $this->getConfig()->getHostIdentityFileOption() . $needs_tty . ' -p ' . $this->getConfig()->getHostPort() . ' ' . '-q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no ' + . $this->getConfig()->getConnectTimeoutOption() . $this->getConfig()->deployment('user') . '@' . $this->getConfig()->getHostName(); $remoteCommand = str_replace('"', '\"', $command); diff --git a/Mage/Task/BuiltIn/Deployment/ReleaseTask.php b/Mage/Task/BuiltIn/Deployment/ReleaseTask.php index e7baf30..4ecf586 100644 --- a/Mage/Task/BuiltIn/Deployment/ReleaseTask.php +++ b/Mage/Task/BuiltIn/Deployment/ReleaseTask.php @@ -74,21 +74,21 @@ class ReleaseTask extends AbstractTask implements IsReleaseAware, SkipOnOverride } } - // Remove symlink if exists; create new symlink and change owners - $tmplink = $currentCopy . '.tmp'; - $command = 'ln -sfn ' . $currentCopy . ' ' . $tmplink - . ' && ' - . 'mv -T ' . $tmplink . ' ' . $symlink; - - if ($resultFetch && $userGroup != '') { - $command .= ' && ' - . 'chown -h ' . $userGroup . ' ' . $symlink + if ($resultFetch && $userGroup != '') { + $command = 'chown -h ' . $userGroup . ' ' . $symlink . ' && ' . 'chown -R ' . $userGroup . ' ' . $currentCopy . ' && ' . 'chown ' . $userGroup . ' ' . $releasesDirectory; + $result = $this->runCommandRemote($command); + if (!$result) { + return $result; + } } + // Remove symlink if exists; create new symlink and change owner + $tmplink = $currentCopy . '.tmp'; + $command = "ln -sfn {$currentCopy} {$tmplink} && mv -fT {$tmplink} {$symlink}"; $result = $this->runCommandRemote($command); if ($result) { diff --git a/Mage/Task/BuiltIn/Deployment/Strategy/TarGzTask.php b/Mage/Task/BuiltIn/Deployment/Strategy/TarGzTask.php index d45ea17..2345860 100644 --- a/Mage/Task/BuiltIn/Deployment/Strategy/TarGzTask.php +++ b/Mage/Task/BuiltIn/Deployment/Strategy/TarGzTask.php @@ -77,7 +77,10 @@ class TarGzTask extends BaseStrategyTaskAbstract implements IsReleaseAware $strategyFlags = ''; } - $command = 'tar cfzh' . $strategyFlags . ' ' . $localTarGz . '.tar.gz ' . $excludeCmd . $excludeFromFileCmd . ' -C ' . $this->getConfig()->deployment('from') . ' .'; + // remove h option only if dump-symlinks is allowed in the release config part + $dumpSymlinks = $this->getConfig()->release('dump-symlinks') ? '' : 'h'; + + $command = 'tar cfz'. $dumpSymlinks . $strategyFlags . ' ' . $localTarGz . '.tar.gz ' . $excludeCmd . $excludeFromFileCmd . ' -C ' . $this->getConfig()->deployment('from') . ' .'; $result = $this->runCommandLocal($command); // Strategy Flags @@ -89,7 +92,7 @@ class TarGzTask extends BaseStrategyTaskAbstract implements IsReleaseAware } // Copy Tar Gz to Remote Host - $command = 'scp ' . $strategyFlags . ' ' . $this->getConfig()->getHostIdentityFileOption() . '-P ' . $this->getConfig()->getHostPort() . ' ' . $localTarGz . '.tar.gz ' + $command = 'scp ' . $strategyFlags . ' ' . $this->getConfig()->getHostIdentityFileOption() . $this->getConfig()->getConnectTimeoutOption() . '-P ' . $this->getConfig()->getHostPort() . ' ' . $localTarGz . '.tar.gz ' . $this->getConfig()->deployment('user') . '@' . $this->getConfig()->getHostName() . ':' . $deployToDirectory; $result = $this->runCommandLocal($command) && $result; @@ -106,11 +109,11 @@ class TarGzTask extends BaseStrategyTaskAbstract implements IsReleaseAware $result = $this->runCommandRemote($command) && $result; // Delete Tar Gz from Remote Host - $command = $this->getReleasesAwareCommand('rm ' . $remoteTarGz . '.tar.gz'); + $command = $this->getReleasesAwareCommand('rm -f ' . $remoteTarGz . '.tar.gz'); $result = $this->runCommandRemote($command) && $result; // Delete Tar Gz from Local - $command = 'rm ' . $localTarGz . ' ' . $localTarGz . '.tar.gz'; + $command = 'rm -f ' . $localTarGz . ' ' . $localTarGz . '.tar.gz'; $result = $this->runCommandLocal($command) && $result; return $result; diff --git a/Mage/Task/BuiltIn/Filesystem/ApplyFaclsTask.php b/Mage/Task/BuiltIn/Filesystem/ApplyFaclsTask.php index 03786ce..3f40ee7 100644 --- a/Mage/Task/BuiltIn/Filesystem/ApplyFaclsTask.php +++ b/Mage/Task/BuiltIn/Filesystem/ApplyFaclsTask.php @@ -25,7 +25,6 @@ class ApplyFaclsTask extends AbstractTask implements IsReleaseAware public function run() { $releasesDirectory = $this->getConfig()->release('directory', 'releases'); - $releasesDirectory = rtrim($this->getConfig()->deployment('to'), '/') . '/' . $releasesDirectory; $currentCopy = $releasesDirectory . '/' . $this->getConfig()->getReleaseId(); diff --git a/Mage/Task/BuiltIn/Filesystem/LinkSharedFilesTask.php b/Mage/Task/BuiltIn/Filesystem/LinkSharedFilesTask.php index 4ed89a1..2682b86 100644 --- a/Mage/Task/BuiltIn/Filesystem/LinkSharedFilesTask.php +++ b/Mage/Task/BuiltIn/Filesystem/LinkSharedFilesTask.php @@ -5,21 +5,47 @@ use Mage\Task\AbstractTask; use Mage\Task\Releases\IsReleaseAware; use Mage\Task\SkipException; +/** + * Class LinkSharedFilesTask + * + * @package Mage\Task\BuiltIn\Filesystem + * @author Andrey Kolchenko + */ class LinkSharedFilesTask extends AbstractTask implements IsReleaseAware { + /** + * Linked folders parameter name + */ + const LINKED_FILES = 'linked_files'; + /** + * Linked folders parameter name + */ + const LINKED_FOLDERS = 'linked_folders'; + /** + * Linking strategy parameter name + */ + const LINKED_STRATEGY = 'linking_strategy'; - const LINKED_FOLDERS = 'linked_folders'; - const LINKED_STRATEGY = 'linking_strategy'; - + /** + * Absolute linked strategy + */ const ABSOLUTE_LINKING = 'absolute'; + /** + * Relative linked strategy + */ const RELATIVE_LINKING = 'relative'; - public $linkingStrategies = array( + /** + * @var array + */ + private static $linkingStrategies = array( self::ABSOLUTE_LINKING, self::RELATIVE_LINKING ); + /** * Returns the Title of the Task + * * @return string */ public function getName() @@ -35,41 +61,92 @@ class LinkSharedFilesTask extends AbstractTask implements IsReleaseAware */ public function run() { - $linkedFiles = $this->getParameter('linked_files', []); - $linkedFolders = $this->getParameter(self::LINKED_FOLDERS, []); - $linkingStrategy = $this->getParameter(self::LINKED_STRATEGY, self::ABSOLUTE_LINKING); - - $linkedEntities = array_merge($linkedFiles,$linkedFolders); + $linkedEntities = array_merge( + $this->getParameter(self::LINKED_FILES, array()), + $this->getParameter(self::LINKED_FOLDERS, array()) + ); - if (sizeof($linkedFiles) == 0 && sizeof($linkedFolders) == 0) { + if (empty($linkedEntities)) { throw new SkipException('No files and folders configured for sym-linking.'); } - $sharedFolderName = $this->getParameter('shared', 'shared'); - $sharedFolderPath = rtrim($this->getConfig()->deployment('to'), '/') . '/' . $sharedFolderName; - $releasesDirectory = $this->getConfig()->release('directory', 'releases'); - $releasesDirectoryPath = rtrim($this->getConfig()->deployment('to'), '/') . '/' . $releasesDirectory; - + $remoteDirectory = rtrim($this->getConfig()->deployment('to'), '/') . '/'; + $sharedFolderPath = $remoteDirectory . $this->getParameter('shared', 'shared'); + $releasesDirectoryPath = $remoteDirectory . $this->getConfig()->release('directory', 'releases'); $currentCopy = $releasesDirectoryPath . '/' . $this->getConfig()->getReleaseId(); - $relativeDiffPath = str_replace($this->getConfig()->deployment('to'),'',$currentCopy) . '/'; foreach ($linkedEntities as $ePath) { - if(is_array($ePath) && in_array($strategy = reset($ePath), $this->linkingStrategies ) ) { - $entityPath = key($ePath); + list($entityPath, $strategy) = $this->getPath($ePath); + if ($strategy === self::RELATIVE_LINKING) { + $dirName = dirname($currentCopy . '/' . $entityPath); + $target = $this->makePathRelative($sharedFolderPath, $dirName) . $entityPath; } else { - $strategy = $linkingStrategy; - $entityPath = $ePath; - } - $sharedEntityLinkedPath = "$sharedFolderPath/$entityPath"; - if($strategy==self::RELATIVE_LINKING) { - $parentFolderPath = dirname($entityPath); - $relativePath = $parentFolderPath=='.'?$relativeDiffPath:$relativeDiffPath.$parentFolderPath.'/'; - $sharedEntityLinkedPath = ltrim(preg_replace('/(\w+\/)/', '../', $relativePath),'/').$sharedFolderName .'/'. $entityPath; + $target = $sharedFolderPath . '/' . $entityPath; } - $command = "ln -nfs $sharedEntityLinkedPath $currentCopy/$entityPath"; + $command = 'mkdir -p ' . escapeshellarg(dirname($target)); + $this->runCommandRemote($command); + $command = 'ln -nfs ' . escapeshellarg($target) . ' ' . escapeshellarg($currentCopy . '/' . $entityPath); $this->runCommandRemote($command); } return true; } -} \ No newline at end of file + + /** + * Given an existing path, convert it to a path relative to a given starting path + * + * @param string $endPath Absolute path of target + * @param string $startPath Absolute path where traversal begins + * + * @return string Path of target relative to starting path + * + * @author Fabien Potencier + * @see https://github.com/symfony/Filesystem/blob/v2.6.1/Filesystem.php#L332 + */ + private function makePathRelative($endPath, $startPath) + { + // Normalize separators on Windows + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + $endPath = strtr($endPath, '\\', '/'); + $startPath = strtr($startPath, '\\', '/'); + } + // Split the paths into arrays + $startPathArr = explode('/', trim($startPath, '/')); + $endPathArr = explode('/', trim($endPath, '/')); + // Find for which directory the common path stops + $index = 0; + while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) { + $index++; + } + // Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels) + $depth = count($startPathArr) - $index; + // Repeated "../" for each level need to reach the common path + $traverser = str_repeat('../', $depth); + $endPathRemainder = implode('/', array_slice($endPathArr, $index)); + // Construct $endPath from traversing to the common path, then to the remaining $endPath + $relativePath = $traverser . (strlen($endPathRemainder) > 0 ? $endPathRemainder . '/' : ''); + + return (strlen($relativePath) === 0) ? './' : $relativePath; + } + + /** + * @param array|string $linkedEntity + * + * @return array [$path, $strategy] + */ + private function getPath($linkedEntity) + { + $linkingStrategy = $this->getParameter(self::LINKED_STRATEGY, self::ABSOLUTE_LINKING); + if (is_array($linkedEntity)) { + list($path, $strategy) = each($linkedEntity); + if (!in_array($strategy, self::$linkingStrategies)) { + $strategy = $linkingStrategy; + } + } else { + $strategy = $linkingStrategy; + $path = $linkedEntity; + } + + return [$path, $strategy]; + } +} diff --git a/Mage/Task/BuiltIn/Filesystem/PermissionsReadableOnlyByWebServerTask.php b/Mage/Task/BuiltIn/Filesystem/PermissionsReadableOnlyByWebServerTask.php new file mode 100644 index 0000000..375489d --- /dev/null +++ b/Mage/Task/BuiltIn/Filesystem/PermissionsReadableOnlyByWebServerTask.php @@ -0,0 +1,60 @@ + + */ +class PermissionsReadableOnlyByWebServerTask extends PermissionsTask +{ + /** + * Set group with web server user and give group write permissions. + */ + public function init() + { + parent::init(); + + $this->setGroup($this->getParameter('group') ? $this->getParameter('group') : $this->getWebServerUser()) + ->setRights('040'); + } + + /** + * @return string + */ + public function getName() + { + return "Giving read permissions only to web server user for given paths [built-in]"; + } + + /** + * Tries to guess the web server user by going thru the running processes. + * + * @return string + * @throws SkipException + */ + protected function getWebServerUser() + { + $this->runCommand("ps aux | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | head -1 | cut -d\ -f1", $webServerUser); + + if (empty($webServerUser)) { + throw new SkipException("Can't guess web server user. Please check if it is running or force it by setting the group parameter"); + } + + return $webServerUser; + } +} diff --git a/Mage/Task/BuiltIn/Filesystem/PermissionsTask.php b/Mage/Task/BuiltIn/Filesystem/PermissionsTask.php new file mode 100644 index 0000000..f5f9505 --- /dev/null +++ b/Mage/Task/BuiltIn/Filesystem/PermissionsTask.php @@ -0,0 +1,324 @@ + + */ +class PermissionsTask extends AbstractTask +{ + /** + * Paths to change of permissions in an array or a string separated by + * PATH_SEPARATOR. + * + * If the stage is on local host you should give full paths. If on remote + * you may give full or relative to the current release directory paths. + * + * @var string + */ + private $paths; + + /** + * If set to true, will check existance of given paths on the host and + * throw SkipException if at least one does not exist. + * + * @var boolean + */ + private $checkPathsExist = true; + + /** + * Owner to set for the given paths (ex : "www-data" or "www-data:www-data" + * to set both owner and group at the same time) + * + * @var string + */ + private $owner; + + /** + * Group to set for the given paths (ex : "www-data") + * + * @var string + */ + private $group; + + /** + * Rights to set for the given paths (ex: "755" or "g+w") + * + * @var string + */ + private $rights; + + /** + * If set to true, will recursively change permissions on given paths. + * + * @var string + */ + private $recursive = false; + + /** + * Initialize parameters. + * + * @throws SkipException + */ + public function init() + { + parent::init(); + + if (! is_null($this->getParameter('checkPathsExist'))) { + $this->setCheckPathsExist($this->getParameter('checkPathsExist')); + } + + if (! $this->getParameter('paths')) { + throw new SkipException('Param paths is mandatory'); + } + $this->setPaths(is_array($this->getParameter('paths')) ? $this->getParameter('paths') : explode(PATH_SEPARATOR, $this->getParameter('paths', ''))); + + if (! is_null($owner = $this->getParameter('owner'))) { + if (strpos($owner, ':') !== false) { + $this->setOwner(array_shift(explode(':', $owner))); + $this->setGroup(array_pop(explode(':', $owner))); + } else { + $this->setOwner($owner); + } + } + + if (! is_null($group = $this->getParameter('group'))) { + $this->setGroup($group); + } + + if (! is_null($rights = $this->getParameter('rights'))) { + $this->setRights($rights); + } + + if (! is_null($recursive = $this->getParameter('recursive'))) { + $this->setRecursive($recursive); + } + } + + /** + * @return string + */ + public function getName() + { + return "Changing rights / owner / group for given paths [built-in]"; + } + + /** + * @return boolean + */ + public function run() + { + $commands = array(); + + if ($this->paths && $this->owner) { + $commands []= 'chown '. $this->getOptionsForCmd() .' ' . $this->owner . ' ' . $this->getPathsForCmd(); + } + + if ($this->paths && $this->group) { + $commands []= 'chgrp '. $this->getOptionsForCmd() .' ' . $this->group . ' ' . $this->getPathsForCmd(); + } + + if ($this->paths && $this->rights) { + $commands []= 'chmod '. $this->getOptionsForCmd() .' ' . $this->rights . ' ' . $this->getPathsForCmd(); + } + + $result = $this->runCommand(implode(' && ', $commands)); + + return $result; + } + + /** + * Returns the options for the commands to run. Only supports -R for now. + * + * @return string + */ + protected function getOptionsForCmd() + { + $optionsForCmd = ''; + $options = array( + 'R' => $this->recursive + ); + + foreach($options as $option => $apply) { + if ($apply === true) { + $optionsForCmd .= $option; + } + } + + return $optionsForCmd ? '-' . $optionsForCmd : ''; + } + + /** + * Transforms paths array to a string separated by 1 space in order to use + * it in a command line. + * + * @return string + */ + protected function getPathsForCmd($paths = null) + { + if (is_null($paths)) { + $paths = $this->paths; + } + + return implode(' ', $paths); + } + + /** + * Set paths. Will check if they exist on the host depending on + * checkPathsExist flag. + * + * @param array $paths + * @return PermissionsTask + * @throws SkipException + */ + protected function setPaths(array $paths) + { + if ($this->checkPathsExist === true) { + $commands = array(); + foreach ($paths as $path) { + $commands[] = '(([ -f ' . $path . ' ]) || ([ -d ' . $path . ' ]))'; + } + + $command = implode(' && ', $commands); + if (! $this->runCommand($command)) { + throw new SkipException('Make sure all paths given exist on the host : ' . $this->getPathsForCmd($paths)); + } + } + + $this->paths = $paths; + + return $this; + } + + /** + * @return string + */ + protected function getPaths() + { + return $this->paths; + } + + /** + * Set checkPathsExist. + * + * @param boolean $checkPathsExist + * @return PermissionsTask + */ + protected function setCheckPathsExist($checkPathsExist) + { + $this->checkPathsExist = (bool) $checkPathsExist; + + return $this; + } + + /** + * @return boolean + */ + protected function getCheckPathsExist() + { + return $this->checkPathsExist; + } + + /** + * Set owner. + * + * @param string $owner + * @return PermissionsTask + */ + protected function setOwner($owner) + { + $this->owner = $owner; + + return $this; + } + + /** + * @return string + */ + protected function getOwner() + { + return $this->owner; + } + + /** + * Set group. + * + * @param string $group + * @return PermissionsTask + */ + protected function setGroup($group) + { + $this->group = $group; + + return $this; + } + + /** + * @return string + */ + protected function getGroup() + { + return $this->group; + } + + /** + * Set rights. + * + * @param string $rights + * @return PermissionsTask + */ + protected function setRights($rights) + { + $this->rights = $rights; + + return $this; + } + + /** + * @return string + */ + protected function getRights() + { + return $this->rights; + } + + /** + * Set recursive. + * + * @param boolean $recursive + * @return PermissionsTask + */ + protected function setRecursive($recursive) + { + $this->recursive = (bool) $recursive; + + return $this; + } + + /** + * @return boolean + */ + protected function getRecursive() + { + return $this->recursive; + } +} diff --git a/Mage/Task/BuiltIn/Filesystem/PermissionsWritableByWebServerTask.php b/Mage/Task/BuiltIn/Filesystem/PermissionsWritableByWebServerTask.php new file mode 100644 index 0000000..6abaf07 --- /dev/null +++ b/Mage/Task/BuiltIn/Filesystem/PermissionsWritableByWebServerTask.php @@ -0,0 +1,60 @@ + + */ +class PermissionsWritableByWebServerTask extends PermissionsTask +{ + /** + * Set group with web server user and give group write permissions. + */ + public function init() + { + parent::init(); + + $this->setGroup($this->getParameter('group') ? $this->getParameter('group') : $this->getWebServerUser()) + ->setRights('g+w'); + } + + /** + * @return string + */ + public function getName() + { + return "Giving write permissions to web server user for given paths [built-in]"; + } + + /** + * Tries to guess the web server user by going thru the running processes. + * + * @return string + * @throws SkipException + */ + protected function getWebServerUser() + { + $this->runCommand("ps aux | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | head -1 | cut -d\ -f1", $webServerUser); + + if (empty($webServerUser)) { + throw new SkipException("Can't guess web server user. Please check if it is running or force it by setting the group parameter"); + } + + return $webServerUser; + } +} diff --git a/Mage/Task/BuiltIn/General/ManuallyTask.php b/Mage/Task/BuiltIn/General/ManuallyTask.php new file mode 100644 index 0000000..aec0279 --- /dev/null +++ b/Mage/Task/BuiltIn/General/ManuallyTask.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Mage\Task\BuiltIn\General; + +use Mage\Task\AbstractTask; + +/** + * Task for running multiple custom commands setting them manually + * + * Example of usage: + * + * tasks: + * on-deploy: + * - scm/force-update + * - general/manually: + * - find . -type d -exec chmod 755 {} \; + * - find . -type f -exec chmod 644 {} \; + * - chmod +x bin/console + * - find var/logs -maxdepth 1 -type f -name '*.log' -exec chown apache:apache {} \; + * - symfony2/cache-clear + * + * @author Samuel Chiriluta + */ +class ManuallyTask extends AbstractTask { + + /** + * (non-PHPdoc) + * @see \Mage\Task\AbstractTask::getName() + */ + public function getName() + { + return 'Manually multiple custom tasks'; + } + + /** + * @see \Mage\Task\AbstractTask::run() + */ + public function run() + { + $result = true; + + $commands = $this->getParameters(); + + foreach ($commands as $command) + { + $result = $result && $this->runCommand($command); + } + + return $result; + } + +} diff --git a/Mage/Task/BuiltIn/Ioncube/EncryptTask.php b/Mage/Task/BuiltIn/Ioncube/EncryptTask.php index f147be8..8565036 100644 --- a/Mage/Task/BuiltIn/Ioncube/EncryptTask.php +++ b/Mage/Task/BuiltIn/Ioncube/EncryptTask.php @@ -33,7 +33,6 @@ use Mage\Task\ErrorWithMessageException; * * Example enviroment.yaml file at end * - * @todo add support for creating license files. * * (c) ActWeb 2013 * (c) Matt Lowe (marl.scot.1@googlemail.com) @@ -202,7 +201,7 @@ class EncryptTask extends AbstractTask /* * Get all our IonCube config options */ - $this->_getAllIonCubeConfigs(); + $this->getAllIonCubeConfigs(); /* * get the source code location */ @@ -279,7 +278,7 @@ class EncryptTask extends AbstractTask * * @return void */ - private function _getAllIonCubeConfigs() + private function getAllIonCubeConfigs() { /* @@ -553,7 +552,6 @@ class EncryptTask extends AbstractTask * D - Default options as stored in script * * more options could be added to make this a bit more flexable - * @todo I'm sure this could be combined into a loop to make it easier and shorter * */ $s = array(); diff --git a/Mage/Task/BuiltIn/Releases/RollbackTask.php b/Mage/Task/BuiltIn/Releases/RollbackTask.php index 1aff8e3..a881ec6 100644 --- a/Mage/Task/BuiltIn/Releases/RollbackTask.php +++ b/Mage/Task/BuiltIn/Releases/RollbackTask.php @@ -51,6 +51,10 @@ class RollbackTask extends AbstractTask implements IsReleaseAware $releasesDirectory = $this->getConfig()->release('directory', 'releases'); $symlink = $this->getConfig()->release('symlink', 'current'); + if (substr($symlink, 0, 1) == '/') { + $releasesDirectory = rtrim($this->getConfig()->deployment('to'), '/') . '/' . $releasesDirectory; + } + $output = ''; $result = $this->runCommandRemote('ls -1 ' . $releasesDirectory, $output); $releases = ($output == '') ? array() : explode(PHP_EOL, $output); @@ -60,7 +64,11 @@ class RollbackTask extends AbstractTask implements IsReleaseAware } else { rsort($releases); - $deleteCurrent = $this->getConfig()->getParameter('deleteCurrent', false); + $deleteCurrent = $this->getConfig()->getParameter('deleteCurrent', + $this->getConfig()->deployment('delete-on-rollback', + $this->getConfig()->general('delete-on-rollback',false) + ) + ); $releaseIsAvailable = false; if ($this->getReleaseId() == '') { diff --git a/Mage/Task/BuiltIn/Scm/ChangeBranchTask.php b/Mage/Task/BuiltIn/Scm/ChangeBranchTask.php index 8c5e33f..20859c5 100644 --- a/Mage/Task/BuiltIn/Scm/ChangeBranchTask.php +++ b/Mage/Task/BuiltIn/Scm/ChangeBranchTask.php @@ -63,14 +63,15 @@ class ChangeBranchTask extends AbstractTask */ public function run() { + $preCommand = 'cd ' . $this->getConfig()->deployment('from', './') . '; '; switch ($this->getConfig()->general('scm')) { case 'git': if ($this->getParameter('_changeBranchRevert', false)) { - $command = 'git checkout ' . self::$startingBranch; + $command = $preCommand . 'git checkout ' . self::$startingBranch; $result = $this->runCommandLocal($command); } else { - $command = 'git branch | grep \'*\' | cut -d\' \' -f 2'; + $command = $preCommand . 'git branch | grep \'*\' | cut -d\' \' -f 2'; $currentBranch = 'master'; $result = $this->runCommandLocal($command, $currentBranch); diff --git a/Mage/Task/BuiltIn/Scm/ForceUpdateTask.php b/Mage/Task/BuiltIn/Scm/ForceUpdateTask.php index 9d68e09..2b1bfe8 100644 --- a/Mage/Task/BuiltIn/Scm/ForceUpdateTask.php +++ b/Mage/Task/BuiltIn/Scm/ForceUpdateTask.php @@ -65,13 +65,13 @@ class ForceUpdateTask extends AbstractTask $remote = $this->getParameter('remote', 'origin'); $command = 'git fetch ' . $remote . ' ' . $branch; - $result = $this->runCommandRemote($command); + $result = $this->runCommand($command); $command = 'git reset --hard ' . $remote . '/' . $branch; - $result = $result && $this->runCommandRemote($command); + $result = $result && $this->runCommand($command); $command = 'git pull ' . $remote . ' ' . $branch; - $result = $result && $this->runCommandRemote($command); + $result = $result && $this->runCommand($command); break; default: @@ -79,7 +79,6 @@ class ForceUpdateTask extends AbstractTask break; } - $result = $this->runCommandLocal($command); $this->getConfig()->reload(); return $result; diff --git a/Mage/Task/BuiltIn/Scm/UpdateTask.php b/Mage/Task/BuiltIn/Scm/UpdateTask.php index 22c496e..3347a97 100644 --- a/Mage/Task/BuiltIn/Scm/UpdateTask.php +++ b/Mage/Task/BuiltIn/Scm/UpdateTask.php @@ -54,9 +54,10 @@ class UpdateTask extends AbstractTask */ public function run() { + $command = 'cd ' . $this->getConfig()->deployment('from', './') . '; '; switch ($this->getConfig()->general('scm')) { case 'git': - $command = 'git pull'; + $command .= 'git pull'; break; default: diff --git a/Mage/Task/BuiltIn/Symfony2/CacheClearTask.php b/Mage/Task/BuiltIn/Symfony2/CacheClearTask.php index 44acc46..d347138 100644 --- a/Mage/Task/BuiltIn/Symfony2/CacheClearTask.php +++ b/Mage/Task/BuiltIn/Symfony2/CacheClearTask.php @@ -14,8 +14,13 @@ use Mage\Task\BuiltIn\Symfony2\SymfonyAbstractTask; /** * Task for Clearing the Cache + * + * Example of usage: + * symfony2/cache-clear: { env: dev } + * symfony2/cache-clear: { env: dev, optional: --no-warmup } * * @author Andrés Montañez + * @author Samuel Chiriluta */ class CacheClearTask extends SymfonyAbstractTask { @@ -36,8 +41,10 @@ class CacheClearTask extends SymfonyAbstractTask { // Options $env = $this->getParameter('env', 'dev'); + $optional = $this->getParameter('optional', ''); + + $command = $this->getAppPath() . ' cache:clear --env=' . $env . ' ' . $optional; - $command = $this->getAppPath() . ' cache:clear --env=' . $env; $result = $this->runCommand($command); return $result; diff --git a/Mage/Task/BuiltIn/Symfony2/DoctrineMigrate.php b/Mage/Task/BuiltIn/Symfony2/DoctrineMigrateTask.php similarity index 93% rename from Mage/Task/BuiltIn/Symfony2/DoctrineMigrate.php rename to Mage/Task/BuiltIn/Symfony2/DoctrineMigrateTask.php index b76633d..3a412e9 100644 --- a/Mage/Task/BuiltIn/Symfony2/DoctrineMigrate.php +++ b/Mage/Task/BuiltIn/Symfony2/DoctrineMigrateTask.php @@ -15,7 +15,7 @@ use Mage\Task\BuiltIn\Symfony2\SymfonyAbstractTask; /** * Task for Doctrine migrations */ -class DoctrineMigrate extends SymfonyAbstractTask +class DoctrineMigrateTask extends SymfonyAbstractTask { /** * (non-PHPdoc) @@ -34,7 +34,9 @@ class DoctrineMigrate extends SymfonyAbstractTask public function run() { $env = $this->getParameter('env', 'dev'); + $command = $this->getAppPath() . ' doctrine:migrations:migrate -n --env=' . $env; + return $this->runCommand($command); } } diff --git a/README.md b/README.md index 99d584b..064c932 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Magallanes # +[![SensioLabsInsight](https://insight.sensiolabs.com/projects/ed0de53a-a12e-459b-9464-34def5907b56/mini.png)](https://insight.sensiolabs.com/projects/ed0de53a-a12e-459b-9464-34def5907b56) + ### What's Magallanes? ### Magallanes is a deployment tool for PHP applications; it's quite simple to use and manage. It will get your application to a safe harbor. @@ -16,7 +18,7 @@ Simply add the following dependency to your project’s composer.json file: ```js "require-dev": { // ... - "andres-montanez/magallanes": "~1.0.3" + "andres-montanez/magallanes": "~1.0.*" // ... } ``` @@ -35,7 +37,7 @@ $ bin/mage version ### System-wide installation with composer ### ```bash -$ composer global require "andres-montanez/magallanes=~1.0.3" +$ composer global require "andres-montanez/magallanes=~1.0.*" ``` Make sure you have ~/.composer/vendor/bin/ in your path. @@ -52,7 +54,6 @@ Like this: $ mage deploy to:production ``` - ### What's this sorcery?! ### Easy boy. It's not sorcery, just some *technomagick*! @@ -69,3 +70,4 @@ Enjoy your magic trip with **Magallanes** to the land of the easily deployable a ### "develop" branch ### Please, all pull request now must be on the develop branch. Thanks! + diff --git a/bin/mage b/bin/mage index 986bafb..80d9b88 100755 --- a/bin/mage +++ b/bin/mage @@ -13,7 +13,7 @@ date_default_timezone_set('UTC'); $baseDir = dirname(dirname(__FILE__)); -define('MAGALLANES_VERSION', '1.0.3'); +define('MAGALLANES_VERSION', '1.0.4'); define('MAGALLANES_DIRECTORY', $baseDir); if (file_exists(__DIR__ . '/../vendor/autoload.php')) { @@ -26,7 +26,6 @@ if (file_exists(__DIR__ . '/../vendor/autoload.php')) { spl_autoload_register(array($loader, 'autoLoad')); } - // Clean arguments array_shift($argv); diff --git a/composer.json b/composer.json index f921960..5295d56 100644 --- a/composer.json +++ b/composer.json @@ -8,6 +8,9 @@ "require": { "php": ">=5.3" }, + "require-dev": { + "phpunit/phpunit": "4.3.5" + }, "autoload": { "psr-4": { "Mage\\": "./Mage", @@ -15,6 +18,9 @@ "Command\\": [".mage/tasks", "../../../.mage/commands"] } }, + "config": { + "bin-dir": "bin" + }, "bin": [ "bin/mage" ] diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..a927b43 --- /dev/null +++ b/composer.lock @@ -0,0 +1,763 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "7a55b88add493fbc9910519e7e9c3a3b", + "packages": [], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "f976e5de371104877ebc89bd8fecb0019ed9c119" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f976e5de371104877ebc89bd8fecb0019ed9c119", + "reference": "f976e5de371104877ebc89bd8fecb0019ed9c119", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "2.0.*@ALPHA" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Instantiator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2014-10-13 12:58:55" + }, + { + "name": "phpunit/php-code-coverage", + "version": "2.0.13", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "0e7d2eec5554f869fa7a4ec2d21e4b37af943ea5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0e7d2eec5554f869fa7a4ec2d21e4b37af943ea5", + "reference": "0e7d2eec5554f869fa7a4ec2d21e4b37af943ea5", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "~1.3", + "sebastian/environment": "~1.0", + "sebastian/version": "~1.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "~4.1" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.2.1", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2014-12-03 06:41:44" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.3.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/acd690379117b042d1c8af1fafd61bde001bf6bb", + "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "File/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2013-10-10 15:34:57" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", + "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "Text/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2014-01-30 17:20:04" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/19689d4354b295ee3d8c54b4f42c3efb69cbc17c", + "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "PHP/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2013-08-02 07:42:54" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "f8d5d08c56de5cfd592b3340424a81733259a876" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/f8d5d08c56de5cfd592b3340424a81733259a876", + "reference": "f8d5d08c56de5cfd592b3340424a81733259a876", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2014-08-31 06:12:13" + }, + { + "name": "phpunit/phpunit", + "version": "4.3.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "2dab9d593997db4abcf58d0daf798eb4e9cecfe1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2dab9d593997db4abcf58d0daf798eb4e9cecfe1", + "reference": "2dab9d593997db4abcf58d0daf798eb4e9cecfe1", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpunit/php-code-coverage": "~2.0", + "phpunit/php-file-iterator": "~1.3.2", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "~1.0.2", + "phpunit/phpunit-mock-objects": "~2.3", + "sebastian/comparator": "~1.0", + "sebastian/diff": "~1.1", + "sebastian/environment": "~1.0", + "sebastian/exporter": "~1.0", + "sebastian/version": "~1.0", + "symfony/yaml": "~2.0" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "", + "../../symfony/yaml/" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "http://www.phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2014-11-11 10:11:09" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "c63d2367247365f688544f0d500af90a11a44c65" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/c63d2367247365f688544f0d500af90a11a44c65", + "reference": "c63d2367247365f688544f0d500af90a11a44c65", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "~1.0,>=1.0.1", + "php": ">=5.3.3", + "phpunit/php-text-template": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.3" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2014-10-03 05:12:11" + }, + { + "name": "sebastian/comparator", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "c484a80f97573ab934e37826dba0135a3301b26a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/c484a80f97573ab934e37826dba0135a3301b26a", + "reference": "c484a80f97573ab934e37826dba0135a3301b26a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.1", + "sebastian/exporter": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2014-11-16 21:32:38" + }, + { + "name": "sebastian/diff", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "5843509fed39dee4b356a306401e9dd1a931fec7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/5843509fed39dee4b356a306401e9dd1a931fec7", + "reference": "5843509fed39dee4b356a306401e9dd1a931fec7", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "http://www.github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2014-08-15 10:29:00" + }, + { + "name": "sebastian/environment", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "6e6c71d918088c251b181ba8b3088af4ac336dd7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6e6c71d918088c251b181ba8b3088af4ac336dd7", + "reference": "6e6c71d918088c251b181ba8b3088af4ac336dd7", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2014-10-25 08:00:45" + }, + { + "name": "sebastian/exporter", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "c7d59948d6e82818e1bdff7cadb6c34710eb7dc0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c7d59948d6e82818e1bdff7cadb6c34710eb7dc0", + "reference": "c7d59948d6e82818e1bdff7cadb6c34710eb7dc0", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2014-09-10 00:51:36" + }, + { + "name": "sebastian/version", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43", + "reference": "b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2014-03-07 15:35:33" + }, + { + "name": "symfony/yaml", + "version": "v2.6.1", + "target-dir": "Symfony/Component/Yaml", + "source": { + "type": "git", + "url": "https://github.com/symfony/Yaml.git", + "reference": "3346fc090a3eb6b53d408db2903b241af51dcb20" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Yaml/zipball/3346fc090a3eb6b53d408db2903b241af51dcb20", + "reference": "3346fc090a3eb6b53d408db2903b241af51dcb20", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Yaml\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony Yaml Component", + "homepage": "http://symfony.com", + "time": "2014-12-02 20:19:20" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.3" + }, + "platform-dev": [] +} diff --git a/docs/commands.txt b/docs/commands.txt index b9674b4..178bad2 100644 --- a/docs/commands.txt +++ b/docs/commands.txt @@ -51,9 +51,11 @@ mage releases rollback --release=-3 to:production # Rollback to a specific Release on the Production environment # mage releases rollback --release=20120101172148 to:production +# Output logs by adding verbose option to ANY command +mage deploy to:production --verbose ### List of UPCOMING Commands ### # mage config add host s05.example.com to:[production] # mage config git git://github.com/andres-montanez/Zend-Framework-Twig-example-app.git # mage config svn svn://example.com/repo -# mage task:deployment/rsync to:production \ No newline at end of file +# mage task:deployment/rsync to:production diff --git a/docs/example-config/.mage/config/environment/production.yml b/docs/example-config/.mage/config/environment/production.yml index 5122d7d..97a3fcc 100644 --- a/docs/example-config/.mage/config/environment/production.yml +++ b/docs/example-config/.mage/config/environment/production.yml @@ -21,3 +21,4 @@ tasks: - privileges - sampleTask - sampleTaskRollbackAware +verbose_logging: true diff --git a/docs/example-config/.mage/config/environment/production.yml.dump-symlinks.txt b/docs/example-config/.mage/config/environment/production.yml.dump-symlinks.txt new file mode 100644 index 0000000..1ff2547 --- /dev/null +++ b/docs/example-config/.mage/config/environment/production.yml.dump-symlinks.txt @@ -0,0 +1,43 @@ +#production +deployment: + user: root + from: ./ +# source: +# type: git +# repository: git://github.com/andres-montanez/Magallanes.git +# from: master +# temporal: /tmp/myAppClone + to: /var/www/vhosts/example.com/www + excludes: + - application/data/cache/twig/* +releases: + enabled: true + max: 5 + symlink: current + directory: releases +# This option allows to dump the symlink with the TarGz strategy and use the symlink on the deployment host. +# This is useful, if the files the symlink point to only exist on the deployment host and not on the host who runs the mage command. +# The default value is false to keep bc. +# See : http://linux.die.net/man/1/tar -h, --dereference + dump-symlinks: true +hosts: + - s01.example.com + - s02.example.com +# s02.example.com: +# deployment: +# user: toor +# to: /home/web/public +# releases: +# max: 10 +# tasks: +# on-deploy: +# - privileges +tasks: + pre-deploy: + - scm/update + on-deploy: + - symfony2/cache-warmup: { env: prod } + - privileges + - sampleTask + - sampleTaskRollbackAware + #post-deploy: diff --git a/docs/example-config/.mage/config/environment/staging.yml b/docs/example-config/.mage/config/environment/staging.yml index 5c1c51d..51f4f06 100644 --- a/docs/example-config/.mage/config/environment/staging.yml +++ b/docs/example-config/.mage/config/environment/staging.yml @@ -29,3 +29,4 @@ tasks: # - sampleTask post-deploy: - sampleTask +verbose_logging: false diff --git a/docs/example-config/.mage/config/general.yml b/docs/example-config/.mage/config/general.yml index 3834ff3..c3a9b6d 100644 --- a/docs/example-config/.mage/config/general.yml +++ b/docs/example-config/.mage/config/general.yml @@ -3,6 +3,7 @@ name: My fantastic App email: andresmontanez@gmail.com notifications: true logging: true +verbose_logging: false scm: type: git url: git://github.com/andres-montanez/Zend-Framework-Twig-example-app.git diff --git a/docs/example-config/.mage/tasks/SampleTaskRollbackAware.php b/docs/example-config/.mage/tasks/SampleTaskRollbackAware.php index d9213f4..c8dadc9 100644 --- a/docs/example-config/.mage/tasks/SampleTaskRollbackAware.php +++ b/docs/example-config/.mage/tasks/SampleTaskRollbackAware.php @@ -1,23 +1,22 @@ -inRollback()) { - return 'A Sample Task aware of rollbacks [in rollback]'; - } else { - return 'A Sample Task aware of rollbacks [not in rollback]'; - } - } - - public function run() - { - return true; - } -} - +inRollback()) { + return 'A Sample Task aware of rollbacks [in rollback]'; + } else { + return 'A Sample Task aware of rollbacks [not in rollback]'; + } + } + + public function run() + { + return true; + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..6fbbd61 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,19 @@ + + + + tests + + + + + src + + + + diff --git a/tests/MageTest/Console/ColorsTest.php b/tests/MageTest/Console/ColorsTest.php new file mode 100644 index 0000000..5038303 --- /dev/null +++ b/tests/MageTest/Console/ColorsTest.php @@ -0,0 +1,76 @@ +getMock('Mage\Config'); + $config->expects($this->once()) + ->method('getParameter') + ->with($this->noColorParameter) + ->will($this->returnValue(false)); + + $string = 'FooBar'; + + // Method need to be non static in the future + $result = Colors::color($string, $config); + $expected = "\033[0;32mFooBar\033[0m"; + + $this->assertSame($expected, $result); + } + + /** + * @group 159 + * @covers ::color + */ + public function testColorNoColor() + { + $config = $this->getMock('Mage\Config'); + $config->expects($this->once()) + ->method('getParameter') + ->with($this->noColorParameter) + ->will($this->returnValue(true)); + + $string = 'FooBar'; + + // Method need to be non static in the future + $result = Colors::color($string, $config); + $expected = 'FooBar'; + + $this->assertSame($expected, $result); + } + + /** + * @group 159 + * @covers ::color + */ + public function testColorUnknownColorName() + { + $config = $this->getMock('Mage\Config'); + $config->expects($this->once()) + ->method('getParameter') + ->with($this->noColorParameter) + ->will($this->returnValue(false)); + + $string = 'FooBar'; + + // Method need to be non static in the future + $result = Colors::color($string, $config); + + $this->assertSame($string, $result); + } +}