diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fa5587cd..23b838bf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ ### Changelog -### 0.7.0 (stable) +### 0.8.0 + +- **Rocketeer can now have specific configurations for stages and connections** +- **Better handling of multiple connections** +- **Added facade shortcuts `Rocketeer::execute(Task)` and `Rocketeer::on(connection[s], Task)` to execute commands on the remote servers** +- Added the `--list` flag on the `rollback` command to show a list of available releases and pick one to rollback to +- Added the `--on` flag to all commands to specify which connections the task should be executed on (ex. `production`, `staging,production`) +- Added `deploy:flush` to clear Rocketeer's cache of credentials + +### 0.7.0 - **Rocketeer can now work outside of Laravel** - **Better handling of SSH keys** diff --git a/composer.lock b/composer.lock index bc69f1984..37f387879 100644 --- a/composer.lock +++ b/composer.lock @@ -12,12 +12,12 @@ "source": { "type": "git", "url": "https://github.com/illuminate/config.git", - "reference": "70e05b02ed6f1ff0df447a0da0a2421cf95f6fce" + "reference": "a48873b5e777ea09777291b02cb498032924844e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/config/zipball/70e05b02ed6f1ff0df447a0da0a2421cf95f6fce", - "reference": "70e05b02ed6f1ff0df447a0da0a2421cf95f6fce", + "url": "https://api.github.com/repos/illuminate/config/zipball/a48873b5e777ea09777291b02cb498032924844e", + "reference": "a48873b5e777ea09777291b02cb498032924844e", "shasum": "" }, "require": { @@ -51,7 +51,7 @@ "role": "Developer" } ], - "time": "2013-08-10 17:47:18" + "time": "2013-10-01 15:20:00" }, { "name": "illuminate/console", @@ -60,16 +60,16 @@ "source": { "type": "git", "url": "https://github.com/illuminate/console.git", - "reference": "cffaa6e6414828fb7a0a742b5f7e9d0f789e24ae" + "reference": "0b4badcd1aecdb623d3e435d1c6d8797e772e233" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/console/zipball/cffaa6e6414828fb7a0a742b5f7e9d0f789e24ae", - "reference": "cffaa6e6414828fb7a0a742b5f7e9d0f789e24ae", + "url": "https://api.github.com/repos/illuminate/console/zipball/0b4badcd1aecdb623d3e435d1c6d8797e772e233", + "reference": "0b4badcd1aecdb623d3e435d1c6d8797e772e233", "shasum": "" }, "require": { - "symfony/console": "2.3.*" + "symfony/console": "2.4.*" }, "require-dev": { "phpunit/phpunit": "3.7.*" @@ -97,7 +97,7 @@ "role": "Developer" } ], - "time": "2013-07-19 10:47:48" + "time": "2013-10-08 21:46:57" }, { "name": "illuminate/container", @@ -106,12 +106,12 @@ "source": { "type": "git", "url": "https://github.com/illuminate/container.git", - "reference": "b9ecfb12b5d45f06e6eb0c9db82fcde6cb5c705e" + "reference": "1444907a30937bf4b7a910489160ba4576f3afa9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/container/zipball/b9ecfb12b5d45f06e6eb0c9db82fcde6cb5c705e", - "reference": "b9ecfb12b5d45f06e6eb0c9db82fcde6cb5c705e", + "url": "https://api.github.com/repos/illuminate/container/zipball/1444907a30937bf4b7a910489160ba4576f3afa9", + "reference": "1444907a30937bf4b7a910489160ba4576f3afa9", "shasum": "" }, "require": { @@ -143,7 +143,7 @@ "role": "Developer" } ], - "time": "2013-07-31 15:34:38" + "time": "2013-10-02 21:50:28" }, { "name": "illuminate/filesystem", @@ -152,18 +152,18 @@ "source": { "type": "git", "url": "https://github.com/illuminate/filesystem.git", - "reference": "fe9915c329ef869efccbc88334cf3ff89f2ecb03" + "reference": "98c6e3710853f4e0aee7ea14b09194083815bf1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/filesystem/zipball/fe9915c329ef869efccbc88334cf3ff89f2ecb03", - "reference": "fe9915c329ef869efccbc88334cf3ff89f2ecb03", + "url": "https://api.github.com/repos/illuminate/filesystem/zipball/98c6e3710853f4e0aee7ea14b09194083815bf1b", + "reference": "98c6e3710853f4e0aee7ea14b09194083815bf1b", "shasum": "" }, "require": { - "illuminate/support": "4.1.x", + "illuminate/support": "4.1.*", "php": ">=5.3.0", - "symfony/finder": "2.3.x" + "symfony/finder": "2.4.*" }, "require-dev": { "phpunit/phpunit": "3.7.*" @@ -191,7 +191,7 @@ "role": "Developer" } ], - "time": "2013-07-02 17:46:50" + "time": "2013-10-08 21:46:57" }, { "name": "illuminate/support", @@ -200,12 +200,12 @@ "source": { "type": "git", "url": "https://github.com/illuminate/support.git", - "reference": "9507e57aa5b237993e2bdb82d8534fc89d5dac6c" + "reference": "33df9bdf3d9b38353097d2e6cfdfc4ebfe9900c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/support/zipball/9507e57aa5b237993e2bdb82d8534fc89d5dac6c", - "reference": "9507e57aa5b237993e2bdb82d8534fc89d5dac6c", + "url": "https://api.github.com/repos/illuminate/support/zipball/33df9bdf3d9b38353097d2e6cfdfc4ebfe9900c1", + "reference": "33df9bdf3d9b38353097d2e6cfdfc4ebfe9900c1", "shasum": "" }, "require": { @@ -242,21 +242,21 @@ "role": "Developer" } ], - "time": "2013-08-05 13:28:56" + "time": "2013-10-03 21:32:36" }, { "name": "symfony/console", - "version": "2.3.x-dev", + "version": "dev-master", "target-dir": "Symfony/Component/Console", "source": { "type": "git", "url": "https://github.com/symfony/Console.git", - "reference": "v2.3.3" + "reference": "608960cd7f7e906e64d6586b9152e308f7ea0ff2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Console/zipball/v2.3.3", - "reference": "v2.3.3", + "url": "https://api.github.com/repos/symfony/Console/zipball/608960cd7f7e906e64d6586b9152e308f7ea0ff2", + "reference": "608960cd7f7e906e64d6586b9152e308f7ea0ff2", "shasum": "" }, "require": { @@ -271,7 +271,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "2.4-dev" } }, "autoload": { @@ -295,21 +295,21 @@ ], "description": "Symfony Console Component", "homepage": "http://symfony.com", - "time": "2013-07-21 12:12:18" + "time": "2013-10-16 16:16:10" }, { "name": "symfony/finder", - "version": "2.3.x-dev", + "version": "dev-master", "target-dir": "Symfony/Component/Finder", "source": { "type": "git", "url": "https://github.com/symfony/Finder.git", - "reference": "4a0fee5b86f5bbd9dfdc11ec124eba2915737ce1" + "reference": "e2ce3164ab58b4d54612e630571f158035ee8603" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Finder/zipball/4a0fee5b86f5bbd9dfdc11ec124eba2915737ce1", - "reference": "4a0fee5b86f5bbd9dfdc11ec124eba2915737ce1", + "url": "https://api.github.com/repos/symfony/Finder/zipball/e2ce3164ab58b4d54612e630571f158035ee8603", + "reference": "e2ce3164ab58b4d54612e630571f158035ee8603", "shasum": "" }, "require": { @@ -318,7 +318,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "2.4-dev" } }, "autoload": { @@ -342,7 +342,7 @@ ], "description": "Symfony Finder Component", "homepage": "http://symfony.com", - "time": "2013-08-13 20:18:00" + "time": "2013-09-19 09:47:34" } ], "packages-dev": [ @@ -352,12 +352,12 @@ "source": { "type": "git", "url": "https://github.com/padraic/mockery.git", - "reference": "9d2460275665f4506d359f4e2037705d07833173" + "reference": "09ab879a09def2a658d6e8030f88432cc479f5a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/padraic/mockery/zipball/9d2460275665f4506d359f4e2037705d07833173", - "reference": "9d2460275665f4506d359f4e2037705d07833173", + "url": "https://api.github.com/repos/padraic/mockery/zipball/09ab879a09def2a658d6e8030f88432cc479f5a8", + "reference": "09ab879a09def2a658d6e8030f88432cc479f5a8", "shasum": "" }, "require": { @@ -398,7 +398,7 @@ "test double", "testing" ], - "time": "2013-08-16 09:45:12" + "time": "2013-10-18 15:18:30" }, { "name": "nesbot/carbon", @@ -406,12 +406,12 @@ "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "bfabf7c397090f594bf9ae01c7df5d5c9e045c76" + "reference": "06f0b8a99a90c5392ceccb09b75b74ff6c08ec07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/bfabf7c397090f594bf9ae01c7df5d5c9e045c76", - "reference": "bfabf7c397090f594bf9ae01c7df5d5c9e045c76", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/06f0b8a99a90c5392ceccb09b75b74ff6c08ec07", + "reference": "06f0b8a99a90c5392ceccb09b75b74ff6c08ec07", "shasum": "" }, "require": { @@ -441,7 +441,7 @@ "datetime", "time" ], - "time": "2013-08-09 04:19:56" + "time": "2013-09-09 02:39:19" }, { "name": "patchwork/utf8", @@ -449,17 +449,23 @@ "source": { "type": "git", "url": "https://github.com/nicolas-grekas/Patchwork-UTF8.git", - "reference": "6c820a6fa0b4464a3d1ea0514937aae46f2e701c" + "reference": "efd5478a233f6940d3296ab27c2a02ba47831968" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nicolas-grekas/Patchwork-UTF8/zipball/6c820a6fa0b4464a3d1ea0514937aae46f2e701c", - "reference": "6c820a6fa0b4464a3d1ea0514937aae46f2e701c", + "url": "https://api.github.com/repos/nicolas-grekas/Patchwork-UTF8/zipball/efd5478a233f6940d3296ab27c2a02ba47831968", + "reference": "efd5478a233f6940d3296ab27c2a02ba47831968", "shasum": "" }, "require": { + "lib-pcre": "*", "php": ">=5.3.0" }, + "suggest": { + "ext-mbstring": "Use Mbstring for best performance", + "lib-iconv": "Use iconv for best performance", + "lib-icu": "Use Intl for best performance" + }, "type": "library", "autoload": { "psr-0": { @@ -478,7 +484,7 @@ "role": "Developer" } ], - "description": "UTF-8 strings handling for PHP 5.3: portable, performant and extended", + "description": "Extensive, portable and performant handling of UTF-8 and grapheme clusters for PHP", "homepage": "https://github.com/nicolas-grekas/Patchwork-UTF8", "keywords": [ "i18n", @@ -486,7 +492,7 @@ "utf-8", "utf8" ], - "time": "2013-08-16 08:38:10" + "time": "2013-10-15 08:18:30" } ], "aliases": [ diff --git a/docs b/docs index 396795da7..542031cd6 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 396795da7bb89ccef3ddd1ebfa8a1be39059f68c +Subproject commit 542031cd6731e8e4aeb602aee7d694defb3a62ce diff --git a/src/Rocketeer/Commands/BaseDeployCommand.php b/src/Rocketeer/Commands/BaseDeployCommand.php index 1ecdf1a03..a69136318 100644 --- a/src/Rocketeer/Commands/BaseDeployCommand.php +++ b/src/Rocketeer/Commands/BaseDeployCommand.php @@ -25,6 +25,7 @@ protected function getOptions() { return array( array('pretend', 'p', InputOption::VALUE_NONE, 'Returns an array of commands to be executed instead of actually executing them'), + array('on', 'C', InputOption::VALUE_REQUIRED, 'The connection(s) to execute the Task in'), array('stage', 'S', InputOption::VALUE_REQUIRED, 'The stage to execute the Task in') ); } @@ -111,17 +112,16 @@ protected function getRepositoryCredentials() */ protected function getServerCredentials() { - // Check for configured connections - $connections = $this->laravel['rocketeer.rocketeer']->getConnections(); - if (empty($connections)) { - $connectionName = $this->ask('No connections have been set, please create one : (production)', 'production'); - } else { - $connectionName = key($connections); + if ($connections = $this->option('on')) { + $this->laravel['rocketeer.rocketeer']->setConnections($connections); } - // Set the found connection as default if none is specified - if (!isset($this->laravel['config']['remote.default'])) { - $this->laravel['config']->set('remote.default', $connectionName); + // Check for configured connections + $connections = $this->laravel['rocketeer.rocketeer']->getAvailableConnections(); + $connectionName = $this->laravel['rocketeer.rocketeer']->getConnection(); + + if (is_null($connectionName)) { + $connectionName = $this->ask('No connections have been set, please create one : (production)', 'production'); } // Check for server credentials @@ -132,13 +132,13 @@ protected function getServerCredentials() foreach ($credentials as $credential => $required) { ${$credential} = array_get($connection, $credential); if (!${$credential} and $required) { - ${$credential} = $this->ask('No '.$credential. ' is set for current connection, please provide one :'); + ${$credential} = $this->ask('No '.$credential. ' is set for [' .$connectionName. '], please provide one :'); } } // Get password or key if (!$password and !$key) { - $type = $this->ask('No password or SSH key is set for current connection, which would you use ? [key/password]'); + $type = $this->ask('No password or SSH key is set for [' .$connectionName. '], which would you use ? [key/password]'); if ($type == 'key') { $key = $this->ask('Please enter the full path to your key'); } else { diff --git a/src/Rocketeer/Commands/DeployDeployCommand.php b/src/Rocketeer/Commands/DeployDeployCommand.php index de6176e75..5bd49a010 100644 --- a/src/Rocketeer/Commands/DeployDeployCommand.php +++ b/src/Rocketeer/Commands/DeployDeployCommand.php @@ -35,17 +35,17 @@ public function fire() )); } - /** - * Get the console command options. - * - * @return array - */ - protected function getOptions() - { - return array_merge(parent::getOptions(), array( - array('tests', 't', InputOption::VALUE_NONE, 'Runs the tests on deploy'), - array('migrate', 'm', InputOption::VALUE_NONE, 'Run the migrations'), - array('seed', 's', InputOption::VALUE_NONE, 'Seed the database after migrating the database'), - )); - } + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return array_merge(parent::getOptions(), array( + array('tests', 't', InputOption::VALUE_NONE, 'Runs the tests on deploy'), + array('migrate', 'm', InputOption::VALUE_NONE, 'Run the migrations'), + array('seed', 's', InputOption::VALUE_NONE, 'Seed the database after migrating the database'), + )); + } } diff --git a/src/Rocketeer/Commands/DeployFlushCommand.php b/src/Rocketeer/Commands/DeployFlushCommand.php new file mode 100644 index 000000000..49900df71 --- /dev/null +++ b/src/Rocketeer/Commands/DeployFlushCommand.php @@ -0,0 +1,33 @@ +laravel['rocketeer.server']->deleteRepository(); + $this->info("Rocketeer's cache has been properly flushed"); + } +} diff --git a/src/Rocketeer/Commands/DeployRollbackCommand.php b/src/Rocketeer/Commands/DeployRollbackCommand.php index 228031b41..eb7688877 100644 --- a/src/Rocketeer/Commands/DeployRollbackCommand.php +++ b/src/Rocketeer/Commands/DeployRollbackCommand.php @@ -1,6 +1,7 @@ fireTasksQueue('Rocketeer\Tasks\Update'); } - /** - * Get the console command options. - * - * @return array - */ - protected function getOptions() - { - return array_merge(parent::getOptions(), array( - array('migrate', 'm', InputOption::VALUE_NONE, 'Run the migrations'), - array('seed', 's', InputOption::VALUE_NONE, 'Seed the database after migrating the database'), - )); - } + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return array_merge(parent::getOptions(), array( + array('migrate', 'm', InputOption::VALUE_NONE, 'Run the migrations'), + array('seed', 's', InputOption::VALUE_NONE, 'Seed the database after migrating the database'), + )); + } } diff --git a/src/Rocketeer/Rocketeer.php b/src/Rocketeer/Rocketeer.php index 8dcbb0af8..e7920a816 100644 --- a/src/Rocketeer/Rocketeer.php +++ b/src/Rocketeer/Rocketeer.php @@ -24,12 +24,26 @@ class Rocketeer */ protected $stage; + /** + * The connections to use + * + * @var array + */ + protected $connections; + + /** + * The current connection + * + * @var string + */ + protected $connection; + /** * The Rocketeer version * * @var string */ - const VERSION = '0.7.0'; + const VERSION = '0.8.0'; /** * Build a new ReleasesManager @@ -50,9 +64,40 @@ public function __construct(Container $app) */ public function getOption($option) { + if ($contextual = $this->getContextualOption($option, 'stages')) { + return $contextual; + } + + if ($contextual = $this->getContextualOption($option, 'connections')) { + return $contextual; + } + return $this->app['config']->get('rocketeer::'.$option); } + /** + * Get a contextual option + * + * @param string $option + * @param string $type [stage,connection] + * + * @return mixed + */ + protected function getContextualOption($option, $type) + { + switch ($type) { + case 'stages': + $contextual = sprintf('rocketeer::on.stages.%s.%s', $this->stage, $option); + break; + + case 'connections': + $contextual = sprintf('rocketeer::on.connections.%s.%s', $this->getConnection(), $option); + break; + } + + return $this->app['config']->get($contextual); + } + //////////////////////////////////////////////////////////////////// //////////////////////////////// STAGES //////////////////////////// //////////////////////////////////////////////////////////////////// @@ -108,7 +153,7 @@ public function needsCredentials() * * @return array */ - public function getConnections() + public function getAvailableConnections() { $connections = $this->app['rocketeer.server']->getValue('connections'); if (!$connections) { @@ -118,6 +163,109 @@ public function getConnections() return $connections; } + /** + * Check if a connection has credentials related to it + * + * @param string $connection + * + * @return boolean + */ + public function isValidConnection($connection) + { + $available = (array) $this->getAvailableConnections(); + + return array_key_exists($connection, $available); + } + + /** + * Get the connection in use + * + * @return string + */ + public function getConnections() + { + // Get cached resolved connections + if ($this->connections) { + return $this->connections; + } + + // Get all and defaults + $connections = (array) $this->app['config']->get('rocketeer::connections'); + $default = $this->app['config']->get('remote.default'); + + // Remove invalid connections + $instance = $this; + $connections = array_filter($connections, function ($value) use ($instance) { + return $instance->isValidConnection($value); + }); + + // Return default if no active connection(s) set + if (empty($connections)) { + return array($default); + } + + // Set current connection as default + $this->connections = $connections; + + return $connections; + } + + /** + * Get the active connection + * + * @return string + */ + public function getConnection() + { + // Get cached resolved connection + if ($this->connection) { + return $this->connection; + } + + $connection = array_get($this->getConnections(), 0); + $this->connection = $connection; + + return $this->connection; + } + + /** + * Set the active connections + * + * @param string|array $connections + */ + public function setConnections($connections) + { + if (!is_array($connections)) { + $connections = explode(',', $connections); + } + + $this->connections = $connections; + } + + /** + * Set the curent connection + * + * @param string $connection + */ + public function setConnection($connection) + { + if ($this->isValidConnection($connection)) { + $this->connection = $connection; + $this->app['config']->set('remote.default', $connection); + } + } + + /** + * Flush active connection(s) + * + * @return void + */ + public function disconnect() + { + $this->connection = null; + $this->connections = null; + } + /** * Get the name of the application to deploy * @@ -211,7 +359,7 @@ public function replacePatterns($path) return preg_replace_callback('/\{[a-z\.]+\}/', function ($match) use ($app) { $folder = substr($match[0], 1, -1); - if (isset($app[$folder])) { + if ($app->bound($folder)) { return str_replace($app['path.base'].'/', null, $app->make($folder)); } diff --git a/src/Rocketeer/RocketeerServiceProvider.php b/src/Rocketeer/RocketeerServiceProvider.php index 53797c306..1cd251588 100644 --- a/src/Rocketeer/RocketeerServiceProvider.php +++ b/src/Rocketeer/RocketeerServiceProvider.php @@ -18,13 +18,6 @@ */ class RocketeerServiceProvider extends ServiceProvider { - /** - * Indicates if loading of the provider is deferred. - * - * @var bool - */ - protected $defer = false; - /** * The commands to register * @@ -98,26 +91,26 @@ public static function make($app = null) */ public function bindCoreClasses(Container $app) { - $app->bindIf('files', 'Illuminate\Filesystem\Filesystem'); + $app->bindIf('files', 'Illuminate\Filesystem\Filesystem'); - $app->bindIf('request', function ($app) { - return Request::createFromGlobals(); - }, true); + $app->bindIf('request', function ($app) { + return Request::createFromGlobals(); + }, true); - $app->bindIf('config', function ($app) { + $app->bindIf('config', function ($app) { $fileloader = new FileLoader($app['files'], __DIR__.'/../config'); return new Repository($fileloader, 'config'); - }, true); + }, true); - $app->bindIf('remote', function($app) { + $app->bindIf('remote', function ($app) { return new RemoteManager($app); }, true); // Register factory and custom configurations $app = $this->registerConfig($app); - return $app; + return $app; } /** @@ -190,16 +183,17 @@ public function bindCommands(Container $app) // Base commands $tasks = array( '' => '', - 'ignite' => 'Ignite', 'check' => 'Check', - 'setup' => 'Setup', - 'deploy' => 'Deploy', - 'update' => 'Update', - 'rollback' => 'Rollback', 'cleanup' => 'Cleanup', 'current' => 'CurrentRelease', - 'test' => 'Test', + 'deploy' => 'Deploy', + 'flush' => 'Flush', + 'ignite' => 'Ignite', + 'rollback' => 'Rollback', + 'setup' => 'Setup', 'teardown' => 'Teardown', + 'test' => 'Test', + 'update' => 'Update', ); // Add User commands @@ -265,18 +259,17 @@ public function bindCommands(Container $app) protected function registerConfig(Container $app) { // Register paths - if (!isset($app['path.base'])) { - $base = explode('/vendor', __DIR__); - $app['path.base'] = $base[0]; + if (!$app->bound('path.base')) { + $app['path.base'] = realpath(__DIR__.'/../../../'); } - // Register config file + // Register config file $app['config']->package('anahkiasen/rocketeer', __DIR__.'/../config'); // Register custom config $custom = $app['path.base'].'/rocketeer.php'; if (file_exists($custom)) { - $app['config']->afterLoading('rocketeer', function($me, $group, $items) use ($custom) { + $app['config']->afterLoading('rocketeer', function ($me, $group, $items) use ($custom) { $custom = include $custom; return array_replace_recursive($items, $custom); }); diff --git a/src/Rocketeer/Scm/Git.php b/src/Rocketeer/Scm/Git.php index e679ef747..426103e94 100644 --- a/src/Rocketeer/Scm/Git.php +++ b/src/Rocketeer/Scm/Git.php @@ -65,7 +65,7 @@ public function checkout($destination) $branch = $this->app['rocketeer.rocketeer']->getRepositoryBranch(); $repository = $this->app['rocketeer.rocketeer']->getRepository(); - return sprintf($this->getCommand('clone -b %s %s %s'), $branch, $repository, $destination); + return sprintf($this->getCommand('clone --depth 1 -b %s %s %s'), $branch, $repository, $destination); } /** diff --git a/src/Rocketeer/Tasks/Ignite.php b/src/Rocketeer/Tasks/Ignite.php index 99098bb45..951d3dfbe 100644 --- a/src/Rocketeer/Tasks/Ignite.php +++ b/src/Rocketeer/Tasks/Ignite.php @@ -38,4 +38,4 @@ public function execute() return $this->history; } -} \ No newline at end of file +} diff --git a/src/Rocketeer/Tasks/Rollback.php b/src/Rocketeer/Tasks/Rollback.php index d65464048..1a7043aaf 100644 --- a/src/Rocketeer/Tasks/Rollback.php +++ b/src/Rocketeer/Tasks/Rollback.php @@ -1,6 +1,7 @@ getRollbackRelease(); + // If no release specified, display the available ones + if ($this->command->option('list')) { + $releases = $this->releasesManager->getReleases(); + $this->command->info('Here are the available releases :'); + + foreach ($releases as $key => $name) { + $name = DateTime::createFromFormat('YmdHis', $name); + $name = $name->format('Y-m-d H:i:s'); + + $this->command->comment(sprintf('[%d] %s', $key, $name)); + } + + // Get actual release name from date + $rollbackRelease = $this->command->ask('Which one do you want to go back to ? (0)', 0); + $rollbackRelease = $releases[$rollbackRelease]; + } + + // Rollback release $this->command->info('Rolling back to release '.$rollbackRelease); $this->updateSymlink($rollbackRelease); @@ -34,6 +53,11 @@ public function execute() */ protected function getRollbackRelease() { - return array_get($this->command->argument(), 'release', $this->releasesManager->getPreviousRelease()); + $release = $this->command->argument('release'); + if (!$release) { + $release = $this->releasesManager->getPreviousRelease(); + } + + return $release; } } diff --git a/src/Rocketeer/TasksQueue.php b/src/Rocketeer/TasksQueue.php index e00ddd0b3..7565769ca 100644 --- a/src/Rocketeer/TasksQueue.php +++ b/src/Rocketeer/TasksQueue.php @@ -39,6 +39,13 @@ class TasksQueue */ protected $command; + /** + * The output of the queue + * + * @var array + */ + protected $output = array(); + /** * Build a new TasksQueue Instance * @@ -123,6 +130,39 @@ public function getAfter(Task $task) return $this->getSurroundingTasks($task, 'after'); } + /** + * Execute Tasks on the default connection + * + * @param string|array|Closure $task + * + * @return array + */ + public function execute($queue) + { + $queue = (array) $queue; + $queue = $this->buildQueue($queue); + + return $this->run($queue); + } + + /** + * Execute Tasks on various connections + * + * @param string|array $connections + * @param string|array|Closure $queue + * + * @return array + */ + public function on($connections, $queue) + { + $this->app['rocketeer.rocketeer']->setConnections($connections); + + $queue = (array) $queue; + $queue = $this->buildQueue($queue); + + return $this->run($queue); + } + //////////////////////////////////////////////////////////////////// //////////////////////////////// QUEUE ///////////////////////////// //////////////////////////////////////////////////////////////////// @@ -143,29 +183,35 @@ public function run(array $tasks, $command = null) $this->command = $command; $queue = $this->buildQueue($tasks); - // Check if we provided a stage - $stage = $this->getStage(); - $stages = $this->app['rocketeer.rocketeer']->getStages(); - if ($stage and in_array($stage, $stages)) { - $stages = array($stage); - } + // Get the connections to execute the tasks on + $connections = (array) $this->app['rocketeer.rocketeer']->getConnections(); + foreach ($connections as $connection) { + $this->app['rocketeer.rocketeer']->setConnection($connection); - // Run the Tasks on each stage - if (!empty($stages)) { - foreach ($stages as $stage) { - $state = $this->runQueue($queue, $stage); + // Check if we provided a stage + $stage = $this->getStage(); + $stages = $this->app['rocketeer.rocketeer']->getStages(); + if ($stage and in_array($stage, $stages)) { + $stages = array($stage); + } + + // Run the Tasks on each stage + if (!empty($stages)) { + foreach ($stages as $stage) { + $state = $this->runQueue($queue, $stage); + } + } else { + $state = $this->runQueue($queue); } - } else { - $state = $this->runQueue($queue); } - return $state ? $queue : $state; + return $this->output; } /** * Run the queue, taking into account the stage * - * @param array $tasks + * @param array $tasks * @param string $stage * * @return boolean @@ -177,6 +223,7 @@ protected function runQueue($tasks, $stage = null) $this->app['rocketeer.rocketeer']->setStage($currentStage); $state = $task->execute(); + $this->output[] = $state; if ($state === false) { return false; } @@ -349,8 +396,10 @@ protected function getSurroundingTasks(Task $task, $position) */ protected function getStage() { - $defaultStage = $this->app['rocketeer.rocketeer']->getOption('stages.default'); - $stage = $this->command->option('stage') ?: $defaultStage; + $stage = $this->app['rocketeer.rocketeer']->getOption('stages.default'); + if ($this->command) { + $stage = $this->command->option('stage') ?: $stage; + } // Return all stages if "all" if ($stage == 'all') { diff --git a/src/Rocketeer/Traits/Task.php b/src/Rocketeer/Traits/Task.php index 229d884a3..3add14cb6 100644 --- a/src/Rocketeer/Traits/Task.php +++ b/src/Rocketeer/Traits/Task.php @@ -209,27 +209,34 @@ public function share($file) */ public function setPermissions($folder) { + $commands = array(); + // Get path to folder $folder = $this->releasesManager->getCurrentReleasePath($folder); $this->command->comment('Setting permissions for '.$folder); // Get permissions options $options = $this->rocketeer->getOption('remote.permissions'); - $chmod = array_get($options, 'permissions', 775); + $chmod = array_get($options, 'permissions'); $user = array_get($options, 'apache.user'); $group = array_get($options, 'apache.group'); // Add chmod - $commands = array( - sprintf('chmod -R %s %s', $chmod, $folder), - sprintf('chmod -R g+s %s', $folder), - ); + if ($chmod) { + $commands[] = sprintf('chmod -R %s %s', $chmod, $folder); + $commands[] = sprintf('chmod -R g+s %s', $folder); + } // And chown if ($user and $group) { $commands[] = sprintf('chown -R %s:%s %s', $user, $group, $folder); } + // Cancel if setting of permissions is not configured + if (empty($commands)) { + return true; + } + return $this->runForCurrentRelease($commands); } diff --git a/src/config/config.php b/src/config/config.php index 2792ae587..830a5e73e 100644 --- a/src/config/config.php +++ b/src/config/config.php @@ -3,7 +3,7 @@ // Remote access // // You can either use a single connection or an array of connections - // For this configure your app/remote.php file + // If this is null, the "default" entry in remote.php will be used ////////////////////////////////////////////////////////////////////// // The remote connection(s) to deploy to @@ -75,6 +75,7 @@ 'permissions' => array( // The permissions to CHMOD folders to + // Change to null to leave the folders untouched 'permissions' => 755, // The folders and files to set as web writable @@ -128,4 +129,30 @@ 'custom' => array(), ), + // Contextual options + // + // In this section you can fine-tune the above configuration according + // to the stage or connection currently in use. + // Per example : + // 'stages' => array( + // 'staging' => array( + // 'scm' => array('branch' => 'staging'), + // ), + // 'production' => array( + // 'scm' => array('branch' => 'master'), + // ), + // ), + + 'on' => array( + + // Stages configurations + 'stages' => array( + ), + + // Connections configuration + 'connections' => array( + ), + + ), + ); diff --git a/tests/ConsoleTest.php b/tests/ConsoleTest.php index e8d6d8408..9e9e0f7ae 100644 --- a/tests/ConsoleTest.php +++ b/tests/ConsoleTest.php @@ -7,4 +7,4 @@ public function testCanRunStandaloneConsole() $this->assertContains('Rocketeer version 0', $console); } -} \ No newline at end of file +} diff --git a/tests/ReleasesManagerTest.php b/tests/ReleasesManagerTest.php index cdbcb592a..2a08b29f8 100644 --- a/tests/ReleasesManagerTest.php +++ b/tests/ReleasesManagerTest.php @@ -56,6 +56,6 @@ public function testCanGetFolderInRelease() { $folder = $this->app['rocketeer.releases']->getCurrentReleasePath('{path.storage}'); - $this->assertEquals($this->server.'/releases/20000000000000/storage', $folder); + $this->assertEquals($this->server.'/releases/20000000000000/app/storage', $folder); } } diff --git a/tests/RocketeerTest.php b/tests/RocketeerTest.php index 6b6aa9213..51434cbb5 100644 --- a/tests/RocketeerTest.php +++ b/tests/RocketeerTest.php @@ -2,21 +2,43 @@ class RocketeerTest extends RocketeerTests { - //////////////////////////////////////////////////////////////////// //////////////////////////////// TESTS ///////////////////////////// //////////////////////////////////////////////////////////////////// public function testCanGetAvailableConnections() { - $connections = $this->app['rocketeer.rocketeer']->getConnections(); - $this->assertEquals(array('production'), array_keys($connections)); + $connections = $this->app['rocketeer.rocketeer']->getAvailableConnections(); + $this->assertEquals(array('production', 'staging'), array_keys($connections)); $this->app['rocketeer.server']->setValue('connections.custom.username', 'foobar'); - $connections = $this->app['rocketeer.rocketeer']->getConnections(); + $connections = $this->app['rocketeer.rocketeer']->getAvailableConnections(); $this->assertEquals(array('custom'), array_keys($connections)); } + public function testCanGetCurrentConnection() + { + $this->swapConfig(array('rocketeer::connections' => 'foobar')); + $this->assertEquals('production', $this->app['rocketeer.rocketeer']->getConnection()); + + $this->swapConfig(array('rocketeer::connections' => 'production')); + $this->assertEquals('production', $this->app['rocketeer.rocketeer']->getConnection()); + + $this->swapConfig(array('rocketeer::connections' => 'staging')); + $this->assertEquals('staging', $this->app['rocketeer.rocketeer']->getConnection()); + } + + public function testCanChangeConnection() + { + $this->assertEquals('production', $this->app['rocketeer.rocketeer']->getConnection()); + + $this->app['rocketeer.rocketeer']->setConnection('staging'); + $this->assertEquals('staging', $this->app['rocketeer.rocketeer']->getConnection()); + + $this->app['rocketeer.rocketeer']->setConnections('staging,production'); + $this->assertEquals(array('staging', 'production'), $this->app['rocketeer.rocketeer']->getConnections()); + } + public function testCanUseSshRepository() { $repository = 'git@github.com:Anahkiasen/rocketeer.git'; @@ -97,7 +119,7 @@ public function testCanReplacePatternsInFolders() { $folder = $this->app['rocketeer.rocketeer']->getFolder('{path.storage}'); - $this->assertEquals($this->server.'/storage', $folder); + $this->assertEquals($this->server.'/app/storage', $folder); } public function testCannotReplaceUnexistingPatternsInFolders() @@ -107,6 +129,35 @@ public function testCannotReplaceUnexistingPatternsInFolders() $this->assertEquals($this->server.'/', $folder); } + public function testCanUseRecursiveStageConfiguration() + { + $this->swapConfig(array( + 'rocketeer::scm.branch' => 'master', + 'rocketeer::on.stages.staging.scm.branch' => 'staging', + )); + + $this->assertEquals('master', $this->app['rocketeer.rocketeer']->getOption('scm.branch')); + $this->app['rocketeer.rocketeer']->setStage('staging'); + $this->assertEquals('staging', $this->app['rocketeer.rocketeer']->getOption('scm.branch')); + } + + public function testCanUseRecursiveConnectionConfiguration() + { + $this->swapConfig(array( + 'rocketeer::connections' => 'production', + 'rocketeer::scm.branch' => 'master', + 'rocketeer::on.connections.staging.scm.branch' => 'staging', + )); + $this->assertEquals('master', $this->app['rocketeer.rocketeer']->getOption('scm.branch')); + + $this->swapConfig(array( + 'rocketeer::connections' => 'staging', + 'rocketeer::scm.branch' => 'master', + 'rocketeer::on.connections.staging.scm.branch' => 'staging', + )); + $this->assertEquals('staging', $this->app['rocketeer.rocketeer']->getOption('scm.branch')); + } + //////////////////////////////////////////////////////////////////// //////////////////////////////// HELPERS /////////////////////////// //////////////////////////////////////////////////////////////////// diff --git a/tests/Tasks/CheckTest.php b/tests/Tasks/CheckTest.php index e5bdd06ca..d795bb5fe 100644 --- a/tests/Tasks/CheckTest.php +++ b/tests/Tasks/CheckTest.php @@ -7,4 +7,4 @@ public function testCanPretendToCheck() $task = $this->pretendTask('Check'); $task->execute(); } -} \ No newline at end of file +} diff --git a/tests/Tasks/CleanupTest.php b/tests/Tasks/CleanupTest.php index 4a769fa66..966225ff1 100644 --- a/tests/Tasks/CleanupTest.php +++ b/tests/Tasks/CleanupTest.php @@ -13,4 +13,4 @@ public function testCanCleanupServer() $output = $cleanup->execute(); $this->assertEquals('No releases to prune from the server', $output); } -} \ No newline at end of file +} diff --git a/tests/Tasks/CurrentReleaseTest.php b/tests/Tasks/CurrentReleaseTest.php index 39ec1391e..18de26fb8 100644 --- a/tests/Tasks/CurrentReleaseTest.php +++ b/tests/Tasks/CurrentReleaseTest.php @@ -11,4 +11,4 @@ public function testCanGetCurrentRelease() $current = $this->task('CurrentRelease')->execute(); $this->assertEquals('No release has yet been deployed', $current); } -} \ No newline at end of file +} diff --git a/tests/Tasks/DeployTest.php b/tests/Tasks/DeployTest.php index 453eb9d0f..b9805fc35 100644 --- a/tests/Tasks/DeployTest.php +++ b/tests/Tasks/DeployTest.php @@ -21,4 +21,4 @@ public function testCanDeployToServer() $this->recreateVirtualServer(); } -} \ No newline at end of file +} diff --git a/tests/Tasks/IgniteTest.php b/tests/Tasks/IgniteTest.php index 27eb1bbf7..af0dc906c 100644 --- a/tests/Tasks/IgniteTest.php +++ b/tests/Tasks/IgniteTest.php @@ -27,7 +27,7 @@ public function testCanIgniteConfigurationInLaravel() $root = $this->app['path.base'].'/rocketeer.php'; $command = $this->getCommand(); - $command->shouldReceive('call')->with('config:publish', array('package' => 'anahkiasen/rocketeer'))->andReturnUsing(function() use ($root) { + $command->shouldReceive('call')->with('config:publish', array('package' => 'anahkiasen/rocketeer'))->andReturnUsing(function () use ($root) { file_put_contents($root, 'foobar'); }); @@ -37,4 +37,4 @@ public function testCanIgniteConfigurationInLaravel() $contents = file_get_contents($root); $this->assertEquals('foobar', $contents); } -} \ No newline at end of file +} diff --git a/tests/Tasks/RollbackTest.php b/tests/Tasks/RollbackTest.php index 006d7ea80..dc0293e48 100644 --- a/tests/Tasks/RollbackTest.php +++ b/tests/Tasks/RollbackTest.php @@ -8,4 +8,4 @@ public function testCanRollbackRelease() $this->assertEquals(10000000000000, $this->app['rocketeer.releases']->getCurrentRelease()); } -} \ No newline at end of file +} diff --git a/tests/Tasks/SetupTest.php b/tests/Tasks/SetupTest.php index 13707dcfb..5c1f4488b 100644 --- a/tests/Tasks/SetupTest.php +++ b/tests/Tasks/SetupTest.php @@ -11,4 +11,4 @@ public function testCanSetupServer() $this->assertFileExists($this->server.'/current'); $this->assertFileExists($this->server.'/releases'); } -} \ No newline at end of file +} diff --git a/tests/Tasks/TeardownTest.php b/tests/Tasks/TeardownTest.php index cbd89738c..6d896eb20 100644 --- a/tests/Tasks/TeardownTest.php +++ b/tests/Tasks/TeardownTest.php @@ -14,10 +14,10 @@ public function testCanAbortTeardown() { $command = Mockery::mock('Command');; $command->shouldReceive('confirm')->andReturn(false); - $command->shouldReceive('info')->andReturnUsing(function($message) { return $message; }); + $command->shouldReceive('info')->andReturnUsing(function ($message) { return $message; }); $message = $this->task('Teardown', $command)->execute(); $this->assertEquals('Teardown aborted', $message); } -} \ No newline at end of file +} diff --git a/tests/Tasks/TestTest.php b/tests/Tasks/TestTest.php index 2c237d416..382bebb58 100644 --- a/tests/Tasks/TestTest.php +++ b/tests/Tasks/TestTest.php @@ -8,4 +8,4 @@ public function testCanRunTests() $this->assertEquals('cd '.$this->server.'/releases/20000000000000', $tests[0]); $this->assertContains('phpunit --stop-on-failure', $tests[1]); } -} \ No newline at end of file +} diff --git a/tests/Tasks/UpdateTest.php b/tests/Tasks/UpdateTest.php index 67a85a8b9..e2521abb2 100644 --- a/tests/Tasks/UpdateTest.php +++ b/tests/Tasks/UpdateTest.php @@ -41,4 +41,4 @@ public function testCanUpdateRepository() $this->assertEquals($matcher, $update); } -} \ No newline at end of file +} diff --git a/tests/TasksQueueTest.php b/tests/TasksQueueTest.php index 6811773f7..e31962eeb 100644 --- a/tests/TasksQueueTest.php +++ b/tests/TasksQueueTest.php @@ -3,7 +3,6 @@ class TasksQueueTest extends RocketeerTests { - public function testCanUseFacadeOutsideOfLaravel() { Rocketeer::before('deploy', 'ls'); @@ -143,11 +142,77 @@ function ($task) { public function testCanRunQueue() { - $this->expectOutputString('JOEY DOESNT SHARE FOOD'); + $this->swapConfig(array( + 'rocketeer::connections' => 'production', + )); + + $this->expectOutputString('JOEY DOESNT SHARE FOOD'); $this->tasksQueue()->run(array( function ($task) { print 'JOEY DOESNT SHARE FOOD'; } ), $this->getCommand()); } + + public function testCanRunQueueOnDifferentConnectionsAndStages() + { + $this->swapConfig(array( + 'rocketeer::connections' => array('staging', 'production'), + 'rocketeer::stages.stages' => array('first', 'second'), + )); + + $output = array(); + $queue = array( + function ($task) use (&$output) { + $output[] = $task->rocketeer->getConnection(). ' - ' .$task->rocketeer->getStage(); + } + ); + + $queue = $this->tasksQueue()->buildQueue($queue); + $this->tasksQueue()->run($queue, $this->getCommand()); + + $this->assertEquals(array( + 'staging - first', + 'staging - second', + 'production - first', + 'production - second', + ), $output); + } + + public function testCanRunQueueViaExecute() + { + $this->swapConfig(array( + 'rocketeer::connections' => 'production', + )); + + $output = $this->tasksQueue()->execute(array( + 'ls -a', + function ($task) { + return 'JOEY DOESNT SHARE FOOD'; + } + )); + + $this->assertEquals(array( + '.'.PHP_EOL.'..'.PHP_EOL.'.gitkeep', + 'JOEY DOESNT SHARE FOOD', + ), $output); + } + + public function testCanRunOnMultipleConnectionsViaOn() + { + $this->swapConfig(array( + 'rocketeer::stages.stages' => array('first', 'second'), + )); + + $output = $this->tasksQueue()->on(array('staging', 'production'), function ($task) { + return $task->rocketeer->getConnection(). ' - ' .$task->rocketeer->getStage(); + }); + + $this->assertEquals(array( + 'staging - first', + 'staging - second', + 'production - first', + 'production - second', + ), $output); + } } diff --git a/tests/_start.php b/tests/_start.php index 0a9c24eb3..472928d6b 100644 --- a/tests/_start.php +++ b/tests/_start.php @@ -52,10 +52,10 @@ public function setUp() // Laravel classes --------------------------------------------- / - $this->app['path.base'] = '/src'; - $this->app['path'] = '/src/app'; - $this->app['path.public'] = '/src/public'; - $this->app['path.storage'] = '/src/storage'; + $this->app->instance('path.base', '/src'); + $this->app->instance('path', '/src/app'); + $this->app->instance('path.public', '/src/public'); + $this->app->instance('path.storage', '/src/app/storage'); $this->app['files'] = new Filesystem; $this->app['config'] = $this->getConfig(); @@ -110,6 +110,7 @@ protected function recreateVirtualServer() ))); // Recreate altered local server + $this->app['files']->deleteDirectory(__DIR__.'/../storage'); $folders = array('current', 'shared', 'releases', 'releases/10000000000000', 'releases/20000000000000'); foreach ($folders as $folder) { $folder = $this->server.'/'.$folder; @@ -219,18 +220,24 @@ protected function getCommand() * * @return Mockery */ - protected function getConfig() + protected function getConfig($options = array()) { $config = Mockery::mock('Illuminate\Config\Repository'); + $config->shouldIgnoreMissing(); + + foreach ($options as $key => $value) { + $config->shouldReceive('get')->with($key)->andReturn($value); + } // Drivers $config->shouldReceive('get')->with('cache.driver')->andReturn('file'); $config->shouldReceive('get')->with('database.default')->andReturn('mysql'); - $config->shouldReceive('get')->with('remote.connections')->andReturn(array('production' => array())); + $config->shouldReceive('get')->with('remote.default')->andReturn('production'); + $config->shouldReceive('get')->with('remote.connections')->andReturn(array('production' => array(), 'staging' => array())); $config->shouldReceive('get')->with('session.driver')->andReturn('file'); // Rocketeer - $config->shouldReceive('get')->with('rocketeer::connections')->andReturn('production'); + $config->shouldReceive('get')->with('rocketeer::connections')->andReturn(array('production', 'staging')); $config->shouldReceive('get')->with('rocketeer::remote.application_name')->andReturn('foobar'); $config->shouldReceive('get')->with('rocketeer::remote.keep_releases')->andReturn(1); $config->shouldReceive('get')->with('rocketeer::remote.permissions')->andReturn(array( @@ -270,6 +277,19 @@ protected function getConfig() return $config; } + /** + * Swap the current config + * + * @param array $config + * + * @return void + */ + protected function swapConfig($config) + { + $this->app['rocketeer.rocketeer']->disconnect(); + $this->app['config'] = $this->getConfig($config); + } + /** * Mock the Remote component * diff --git a/tests/meta/coverage.txt b/tests/meta/coverage.txt index 6d68366fd..109488dfd 100644 --- a/tests/meta/coverage.txt +++ b/tests/meta/coverage.txt @@ -1,27 +1,27 @@ -Code Coverage Report: - 2013-08-16 13:29:09 - - Summary: - Classes: 63.16% (12/19) - Methods: 87.72% (100/114) - Lines: 90.05% (516/573) +Code Coverage Report + 2013-10-19 14:44:37 + + Summary: + Classes: 60.00% (12/20) + Methods: 86.99% (107/123) + Lines: 90.18% (588/652) \Rocketeer::Bash - Methods: 88.24% (15/17) Lines: 93.07% ( 94/101) + Methods: 100.00% (17/17) Lines: 93.52% (101/108) \Rocketeer::ReleasesManager Methods: 100.00% ( 9/ 9) Lines: 100.00% ( 22/ 22) \Rocketeer::Rocketeer - Methods: 100.00% (15/15) Lines: 100.00% ( 50/ 50) + Methods: 100.00% (22/22) Lines: 100.00% ( 97/ 97) \Rocketeer::Server - Methods: 90.00% ( 9/10) Lines: 90.24% ( 37/ 41) + Methods: 100.00% (10/10) Lines: 91.11% ( 41/ 45) \Rocketeer::TasksQueue - Methods: 80.00% (12/15) Lines: 89.77% ( 79/ 88) + Methods: 100.00% (17/17) Lines: 95.73% (112/117) \Rocketeer\Scm::Git Methods: 100.00% ( 6/ 6) Lines: 100.00% ( 8/ 8) \Rocketeer\Tasks::Check - Methods: 57.14% ( 4/ 7) Lines: 66.67% ( 36/ 54) + Methods: 100.00% ( 7/ 7) Lines: 66.67% ( 36/ 54) \Rocketeer\Tasks::Cleanup Methods: 100.00% ( 1/ 1) Lines: 100.00% ( 9/ 9) \Rocketeer\Tasks::Closure @@ -29,13 +29,13 @@ Code Coverage Report: \Rocketeer\Tasks::CurrentRelease Methods: 100.00% ( 1/ 1) Lines: 100.00% ( 7/ 7) \Rocketeer\Tasks::Deploy - Methods: 50.00% ( 2/ 4) Lines: 62.86% ( 22/ 35) + Methods: 100.00% ( 4/ 4) Lines: 63.89% ( 23/ 36) \Rocketeer\Tasks::Ignite Methods: 100.00% ( 1/ 1) Lines: 100.00% ( 8/ 8) \Rocketeer\Tasks::Rollback - Methods: 100.00% ( 2/ 2) Lines: 100.00% ( 5/ 5) + Methods: 100.00% ( 2/ 2) Lines: 50.00% ( 10/ 20) \Rocketeer\Tasks::Setup - Methods: 50.00% ( 1/ 2) Lines: 88.89% ( 24/ 27) + Methods: 100.00% ( 2/ 2) Lines: 88.89% ( 24/ 27) \Rocketeer\Tasks::Teardown Methods: 100.00% ( 1/ 1) Lines: 100.00% ( 7/ 7) \Rocketeer\Tasks::Test @@ -45,4 +45,4 @@ Code Coverage Report: \Rocketeer\Traits::Scm Methods: 100.00% ( 3/ 3) Lines: 100.00% ( 7/ 7) \Rocketeer\Traits::Task - Methods: 86.67% (13/15) Lines: 95.83% ( 69/ 72) + Methods: 93.75% (15/16) Lines: 94.81% ( 73/ 77)