diff --git a/.gitignore b/.gitignore
index a68b5f971..51072d51f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,14 +1,11 @@
# Cache and compiled
-bin/rocketeer.phar
-bin/phar
-storage
.rocketeer
-
-# Plugins
-rocketeer-campfire
+bin/phar
+bin/rocketeer.phar
+composer.lock
# Tests
-tests/_meta/coverage
+tests/_server/foobar
# Dependencies
-vendor
\ No newline at end of file
+vendor
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index 91e45b800..000000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "docs"]
- path = docs
- url = https://github.com/Anahkiasen/rocketeer.wiki.git
diff --git a/.travis.yml b/.travis.yml
index cca97f3fa..dad16d49b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,19 +1,18 @@
-language: php
-
-php:
- - 5.3
- - 5.4
- - 5.5
- - 5.6
- - hhvm
-
-before_script:
- - composer self-update
- - composer install --dev --prefer-dist
-
-matrix:
- allow_failures:
- - php: hhvm
- fast_finish: true
-
-script: phpunit
\ No newline at end of file
+language: php
+
+php:
+ - 5.4
+ - 5.5
+ - 5.6
+ - hhvm-nightly
+
+before_script:
+ - travis_retry composer self-update
+ - travis_retry composer install --no-interaction --prefer-source --dev
+
+matrix:
+ allow_failures:
+ - php: hhvm-nightly
+ fast_finish: true
+
+script: phpunit --debug
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4b62dbe94..88222a515 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,191 +1,303 @@
-### Changelog
-
-### 1.2.2
-
-- **The Notifier plugin module not has a hook for before and after deployment**
+# Changelog
+
+2.0.0
+-----
+
+### Added
+- Added ability to run tasks in parallel via the `--parallel` flag (or `-P`)
+- Added ability to have multiple servers for one connection, just define them in a `servers` array in your connection, each entry being an usual connection credentials array
+- Added support for defining contextual configurations in files (`.rocketeer/connections/{connection}/scm.php`, same for stages)
+- Core tasks (Deploy, Check, Test, Migrate) now use a module system called Strategies
+- Added a `Sync` DeployStrategy in addition to `Clone` and `Copy` that uses rsync to create a new release
+- Added static helper `Rocketeer::getDetectedStage` to get the stage Rocketeer think's he's in on the server (for environment mappings)
+- Added support for checking of HHVM extensions
+- Added `Task::upload(file, destination)` to upload files to remote, destination can be null and the basename of the file would then be used
+
+### Changed
+- Output now lists which tasks were fired by which task/events, how long they should take, in a tree-like format that clarifies tasks and subtasks
+- For breaking changes, see the [Upgrade Path](http://rocketeer.autopergamene.eu/#/docs/III-Further/Upgrade-Path)
+
+### Fixed
+- Fixed the `Copy` strategy
+- Fixed a bug where registered events in `hooks` would make the notifier plugins fail
+- Fixed a bug where `rocketeer current` would fail to find the related task
+- Fixed a bug where Artisan wouldn't be found even if at the default location
+- Fixed a bug where ignition would fail when the default connection isn't `production`
+- Fixed a bug where logs would be misplaced
+- Fixed a bug where tasks and events weren't properly loaded in Laravel
+- Fixed a bug where releases would be asked to the server at each command, slowing down deployments
+- Fixed a bug where events wouldn't be properly rebooted when using connections other than the default ones
+- Fixed a bug where Rocketeer would ask for credentials again after switching connection
+
+1.2.2 - 2014-06-05
+------------------
+
+### Added
- Add ability to disable composer completely
- Add support for ssh-agent for secure connections
+
+### Changed
+- The Notifier plugin module now has a hook for before and after deployment
+
+### Fixed
- Fixed a bug that prevented the `--seed` option from working
- Fixed a bug when getting the user's home folder on Windows
- Fixed a bug where Composer-related tasks would be run even without a `composer.json` is found
- Fixed some compatibility issue with Laravel 4.2
-### 1.2.1
+1.2.1 - 2014-03-31
+------------------
+### Changed
+- Split `remote/application_name` in `config/application_name` and `remote/app_directory` to allow contextual application folder name
+- The `composer self-update` command is now commented out by default
+
+### Fixed
- Fixed a bug where `composer install` wouldn't return the proper status code and would cancel deployment
- Fixed a bug where empty arrays wouldn't override defaults in the configuration
- Fixed path to home folder not being properly found in Windows environment
-- Split `remote/application_name` in `config/application_name` and `remote/app_directory` to allow contextual application folder name
-- The `composer self-update` command is now commented out by default
-### 1.2.0
+1.2.0 - 2014-03-08
+------------------
-- **Added various SSH task-running helpers such as `Rocketeer::task(taskname, task)`**
-- **Rocketeer now has a `copy` strategy that copies the previous release instead of cloning a new one on deploy**
-- **Composer execution is now configurable via a callback**
+### Added
+- Added various SSH task-running helpers such as `Rocketeer::task(taskname, task)`
+- Rocketeer now has a `copy` strategy that copies the previous release instead of cloning a new one on deploy
+- Composer execution is now configurable via a callback
- Added an option to disable recursive git clone (submodules)
- Releases are now sorted by date when printed out in `rollback` and `current`
+
+### Fixed
- Fixed a bug when running Setup would cancel the `--stage` option
- Fixed a bug where contextual options weren't properly merged with default ones
-### 1.1.2
+1.1.2 - 2014-02-12
+------------------
+### Added
- Added a `Rocketeer\Plugins\Notifier` class to easily add third-party deployment notification plugins
+
+### Fixed
- Fixed a bug where the custom tasks/events file/folders might not exist
-### 1.1.1
+1.1.1 - 2014-02-08
+------------------
+### Fixed
- Fixed a bug where the `before` event if halting wouldn't cancel the Task firing
- Fixed a bug where some calls to the facade would crash in `tasks.php`
-### 1.1.0
+1.1.0 - 2014-02-08
+------------------
-- **Events can now cancel the queue by returning false or returning `$task->halt(error)`**
-- **Rocketeer now logs its output and commands**
-- **Releases are now marked as completed or halted to avoid rollback to releases that errored**
+### Added
+- Events can now cancel the queue by returning false or returning `$task->halt(error)`
+- Rocketeer now logs its output and commands
+- Releases are now marked as completed or halted to avoid rollback to releases that errored
- Rocketeer will now automatically load `.rocketeer/tasks.php`/`.rocketeer/events.php` _or_ the contents of `.rocketeer/tasks`/`.rocketeer/events` if they're folders
- Hash is now computed with the actual configuration instead of the modification times to avoid unecessary reflushes
- Check task now uses the PHP version required in your `composer.json` file if the latter exists
+
+### Fixed
- Use the server's time to timestamp releases instead of the local time
- Fixed a bug where incorrect current release would be returned for multi-servers setups
-### 1.0.0
-
-**Note : Configuration is now split in multiple files, you'll need to redeploy the configuration files**
+1.0.0 - 2014-01-13
+------------------
-- **Rocketeer is now available as a [standalone PHAR](http://rocketeer.autopergamene.eu/versions/rocketeer.phar)**
-- **Revamped plugin system**
-- **Rocketeer hooks now use `illuminate/event` system, and can fire events during tasks (instead of just before and after)**
-- **Permissions setting is now set in a callback to allow custom permissions routines**
+### Added
+- Rocketeer is now available as a [standalone PHAR](http://rocketeer.autopergamene.eu/versions/rocketeer.phar)
+- Revamped plugin system
+- Rocketeer hooks now use `illuminate/event` system, and can fire events during tasks (instead of just before and after)
+- Permissions setting is now set in a callback to allow custom permissions routines
- Rocketeer now looks into `~/.ssh` by default for keys instead of asking
- Added the `--clean-all` flag to the `Cleanup` task to prune all but the latest release
- Deployments file is now cleared when the config files are changed
- Added an option to disable shallow clone as it caused some problems on some servers
+
+### Deprecated
+- Configuration is now split in multiple files, you'll need to redeploy the configuration files
+
+### Fixed
- Fixed a bug where `CurrentRelease` wouldn't show any release with an empty/fresh deployments file
- Fix some multiconnections related bugs
- Fixed some minor behaviors that were causing `--pretend` and/or `--verbose` to not output SCM commands
-### 0.9.0
+0.9.0 - 2013-11-15
+------------------
-- **Rocketeer now supports SVN**
-- **Rocketeer now has a [Campfire plugin](https://github.com/Anahkiasen/rocketeer-campfire)**
+### Added
+- Rocketeer now supports SVN
+- Rocketeer now has a [Campfire plugin](https://github.com/Anahkiasen/rocketeer-campfire)
- Add option to manually set remote variables when encountering problems
- Add keyphrase support
-### 0.8.0
+0.8.0 - 2013-10-19
+------------------
-- **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
+- 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
+0.7.0 - 2013-08-16
+------------------
-- **Rocketeer can now work outside of Laravel**
-- **Better handling of SSH keys**
+### Added
+- Rocketeer can now work outside of Laravel
+- Better handling of SSH keys
- Permissions are now entirely configurable
- Rocketeer now prompts for confirmation before executing the Teardown task
- Allow the use of patterns in shared folders
-- Share `sessions` folder by default
- Rocketeer now prompts for binaries it can't find (composer, phpunit, etc)
-### 0.6.5
+### Changed
+- Share `sessions` folder by default
-- **Make Rocketeer prompt for both server and SCM credentials if they're not stored**
-- **`artisan deploy` now deploys the project if the `--version` flat is not passed**
+0.6.5 - 2013-07-29
+------------------
+
+### Added
+- Make Rocketeer prompt for both server and SCM credentials if they're not stored
+- `artisan deploy` now deploys the project if the `--version` flat is not passed
- Make Rocketeer forget invalid credentials provided by prompt
+
+### Fixed
- Fix a bug where incorrect SCM urls would be generated
-### 0.6.4
+0.6.4 - 2013-07-16
+------------------
+### Added
- Make the output of commands in realtime when `--verbose` instead of when the command is done
+
+### Changed
+- Reverse sluggification of application name
+
+### Fixed
- Fix a bug where custom Task classes would be analyzed as string commands
- Fix Rocketeeer not taking into account custom paths to **app/**, **storage/**, **public/** etc.
-- Reverse sluggification of application name
-### 0.6.3
+0.6.3 - 2013-07-11
+------------------
+### Changed
- Application name is now always sluggified as a security
+
+### Fixed
- Fix a bug where the Check task would fail on pretend mode
- Fix a bug where invalid directory separators would get cached and used
-### 0.6.2
+0.6.2 - 2013-07-11
+------------------
+### Added
- Make the Check task check for the remote presence of the configured SCM
+
+### Fixed
- Fix Rocketeer not being able to use a `composer.phar` on the server
-### 0.6.1
+0.6.1 - 2013-07-10
+------------------
-- Fix a bug where the configured user would not have the rights to set permissions
+### Fixed
+- Fixed a bug where the configured user would not have the rights to set permissions
-### 0.6.0
+0.6.0 - 2013-07-06
+------------------
-- **Add multistage strategy**
-- **Add compatibility to Laravel 4.0**
-- Migrations are now under a `--migrate` flag
+### Added
+- Add multistage strategy
+- Add compatibility to Laravel 4.0
- Split Git from the SCM implementation (**requires a config update**)
+
+### Changed
+- Migrations are now under a `--migrate` flag
- Releases are now named as `YmdHis` instead of `time()`
- If the `scm.branch` option is empty, Rocketeer will now use the current Git branch
-- Fix a delay where the `current` symlink would get updated before the complete end of the deploy
-- Fix errors with Git and Composer not canceling deploy
-- Fix some compatibility problems with Windows
-- Fix a bug where string tasks would not be run in latest release folder
-- Fix Apache username and group using `www-data` by default
-### 0.5.0
+### Fixed
+- Fixed a delay where the `current` symlink would get updated before the complete end of the deploy
+- Fixed errors with Git and Composer not canceling deploy
+- Fixed some compatibility problems with Windows
+- Fixed a bug where string tasks would not be run in latest release folder
+- Fixed Apache username and group using `www-data` by default
+
+0.5.0 - 2013-07-01
+------------------
-- **Add a `deploy:update` task that updates the remote server without doing a new release**
-- **Add a `deploy:test` to run the tests on the server**
-- **Rocketeer can now prompt for Git credentials if you don't want to store them in the config**
+### Added
+- Added a `deploy:update` task that updates the remote server without doing a new release
+- Added a `deploy:test` to run the tests on the server
+- Rocketeer can now prompt for Git credentials if you don't want to store them in the config
- The `deploy:check` command now checks PHP extensions for the cache/database/session drivers you set
- Rocketeer now share logs by default between releases
- Add ability to specify an array of Tasks in Rocketeer::before|after
- Added a `$silent` flag to make a `Task::run` call silent no matter what
- Rocketeer now displays how long the task took
-### 0.4.0
+0.4.0 - 2013-06-26
+------------------
-- **Add ability to share files and folders between releases**
-- **Add ability to create custom tasks integrated in the CLI**
-- **Add a `deploy:check` Task that checks if the server is ready to receive a Laravel app**
-- Add `Task::listContents` and `Task::fileExists` helpers
-- Add Task helper to run outstanding migrations
-- Add `Rocketeer::add` method on the facade to register custom Tasks
-- Fix `Task::runComposer` not taking into account a local `composer.phar`
+### Added
+- Added ability to share files and folders between releases
+- Added ability to create custom tasks integrated in the CLI
+- Added a `deploy:check` Task that checks if the server is ready to receive a Laravel app
+- Added `Task::listContents` and `Task::fileExists` helpers
+- Added Task helper to run outstanding migrations
+- Added `Rocketeer::add` method on the facade to register custom Tasks
-### 0.3.2
+### Fixed
+- Fixed `Task::runComposer` not taking into account a local `composer.phar`
+0.3.2 - 2013-06-25
+------------------
+
+### Fixed
- Fixed wrong tag used in `deploy:cleanup`
-### 0.3.1
+0.3.1 - 2013-06-24
+------------------
+### Added
- Added `--pretend` flag on all commands to print out a list of the commands that would have been executed instead of running them
-### 0.3.0
+0.3.0 - 2013-06-24
+------------------
+### Added
- Added `Task::runInFolder` to run tasks in a specific folder
- Added `Task::runForCurrentRelease` Task helper
-- Fixed a bug where `Task::run` would only return the last line of the command's output
- Added `Task::runTests` methods to run the PHPUnit tests of the application
- Integrated `Task::runTests` in the `Deploy` task under the `--tests` flag ; failing tests will cancel deploy and rollback
-### 0.2.0
+### Fixed
+- Fixed a bug where `Task::run` would only return the last line of the command's output
+
+0.2.0 - 2013-06-24
+------------------
+### Added
- The core of Rocketeer's actions is now split into a system of Tasks for flexibility
- Added a `Rocketeer` facade to easily add tasks via `before` and `after` (see Tasks docs)
-### 0.1.1
+0.1.1 - 2013-06-23
+------------------
+### Fixed
- Fixed a bug where the commands would try to connect to the remote hosts on construct
- Fixed `ReleasesManager::getPreviousRelease` returning the wrong release
-### 0.1.0
+0.1.0 - 2013-06-23
+------------------
-- Add `deploy:teardown` to remove the application from remote servers
-- Add support for the connections defined in the remote config file
-- Add `deploy:rollback` and `deploy:current` commands
-- Add `deploy:cleanup` command
-- Add config file
-- Add `deploy:setup` and `deploy:deploy` commands
+### Added
+- Added `deploy:teardown` to remove the application from remote servers
+- Added support for the connections defined in the remote config file
+- Added `deploy:rollback` and `deploy:current` commands
+- Added `deploy:cleanup` command
+- Added config file
+- Added `deploy:setup` and `deploy:deploy` commands
diff --git a/README.md b/README.md
index e8584502d..fa3d25553 100644
--- a/README.md
+++ b/README.md
@@ -8,13 +8,27 @@
[](https://www.versioneye.com/user/projects/53f1c65f13bb0677b1000744)
[](https://www.gittip.com/Anahkiasen/)
-**Rocketeer** is a task runner and deployment package for the PHP world. It is inspired by the [Laravel Framework](http://laravel.com/) philosophy and thus aims to be fast, elegant, and more importantly easy to use.
+**Rocketeer** is a modern PHP task runner and deployment package. It is inspired by the [Laravel Framework](http://laravel.com/) philosophy and thus aims to be fast, elegant, and more importantly easy to use.
+
+Like the latter, emphasis is put on smart defaults and modern development. While it is coded in PHP, it can deploy any project from small HTML/CSS websites to large Rails applications.
+
+## Main features
+
+- **Versatile**, support for multiple connections, multiserver connections, multiple stages per server, etc.
+- **Fast**, queue tasks and run them in parallel across all your servers and stages
+- **Modulable**, not only can you add custom tasks and components, every core part of Rocketeer can be hot swapped, extended, hacked to bits, etc.
+- **Preconfigured**, tired of defining the same routines again and again ? Rocketeer is made for modern development and comes with smart defaults and built-in tasks such as installing your application's dependencies
+- **Powerful**, releases management, server checks, rollbacks, etc. Every feature you'd expect from a deployment tool is there
## Installation
-The easiest way is to get the latest compiled version [from the website](http://rocketeer.autopergamene.eu/versions/rocketeer.phar), put it at the root of the project you want to deploy, and hit `php rocketeer.phar ignite`. You'll get asked a series of questions that should get you up and running in no time.
+The fastest way is to grab the binary:
+
+```bash
+curl http://rocketeer.autopergamene.eu/versions/rocketeer.phar > /usr/local/bin/rocketeer && chmod 755 /usr/local/bin/rocketeer
+```
-Rocketeer also integrates nicely with the Laravel framework, for that refer to the [Getting Started](https://github.com/Anahkiasen/rocketeer/wiki/Getting-started) pages of the documentation.
+More ways to setup Rocketeer can be found in the [official documentation](http://rocketeer.autopergamene.eu/#/docs/I-Introduction/Getting-started).
## Usage
@@ -22,21 +36,24 @@ The available commands in Rocketeer are :
```
$ php rocketeer
- check Check if the server is ready to receive the application
- cleanup Clean up old releases from the server
- current Display what the current release is
- deploy Deploy the website.
- flush Flushes Rocketeer's cache of credentials
- help Displays help for a command
- ignite Creates Rocketeer's configuration
- list Lists commands
- rollback Rollback to the previous release, or to a specific one
- setup Set up the remote server for deployment
- teardown Remove the remote applications and existing caches
- test Run the tests on the server and displays the output
- update Update the remote server without doing a new release.
+ check Check if the server is ready to receive the application
+ cleanup Clean up old releases from the server
+ current Display what the current release is
+ deploy Deploys the website
+ flush Flushes Rocketeer's cache of credentials
+ help Displays help for a command
+ ignite Creates Rocketeer's configuration
+ list Lists commands
+ rollback Rollback to the previous release, or to a specific one
+ setup Set up the remote server for deployment
+ strategies Lists the available options for each strategy
+ teardown Remove the remote applications and existing caches
+ test Run the tests on the server and displays the output
+ update Update the remote server without doing a new release
```
+Documentation can be [found here](https://github.com/rocketeers/docs)
+
## Testing
``` bash
@@ -45,23 +62,25 @@ $ phpunit
## Contributing
-Please see [CONTRIBUTING](https://github.com/anahkiasen/rocketeer/blob/master/CONTRIBUTING.md) for details.
+Please see [CONTRIBUTING](https://github.com/rocketeers/rocketeer/blob/master/CONTRIBUTING.md) for details.
## Credits
- [Anahkiasen](https://github.com/Anahkiasen)
-- [All Contributors](https://github.com/anahkiasen/rocketeer/contributors)
+- [All Contributors](https://github.com/rocketeers/rocketeer/contributors)
## License
-The MIT License (MIT). Please see [License File](https://github.com/anahkiasen/rocketeer/blob/master/LICENSE) for more information.
+The MIT License (MIT). Please see [License File](https://github.com/rocketeers/rocketeer/blob/master/LICENSE) for more information.
-----
-## Available plugins
+## Available plugins and integrations
-- [Campfire](https://github.com/Anahkiasen/rocketeer-campfire)
-- [Slack](https://github.com/Anahkiasen/rocketeer-slack)
+- [Campfire](https://github.com/rocketeers/rocketeer-campfire)
+- [Slack](https://github.com/rocketeers/rocketeer-slack)
+- [HipChat](https://github.com/hannesvdvreken/rocketeer-hipchat)
+- [Wordpress](https://github.com/mykebates/wp-rocketeer)
## Why not Capistrano ?
@@ -71,7 +90,3 @@ But, it remains a Ruby package and one that's tightly coupled to Rails in some w
It's also meant to be a lot easier to comprehend, for first-time users or novices, Capistrano is a lot to take at once – Rocketeer aims to be as simple as possible by providing smart defaults and speeding up the time between installing it and first hitting `deploy`.
It's also more thought out for the PHP world – although you can configure Capistrano to run Composer and PHPUnit, that's not something it expects from the get go, while those tasks that are a part of every PHP developer are integrated in Rocketeer's core deploy process.
-
-## Documentation
-
-Documentation can be [found here](https://github.com/rocketeers/docs)
diff --git a/bin/compile b/bin/compile
index b67c97911..f88350e72 100755
--- a/bin/compile
+++ b/bin/compile
@@ -4,7 +4,7 @@ require __DIR__.'/../vendor/autoload.php';
// Create Phar
$compiler = new Rocketeer\Console\Compiler;
-$phar = $compiler->compile();
+$phar = $compiler->compile();
// Set permissions
-chmod($phar, 0755);
\ No newline at end of file
+chmod($phar, 0755);
diff --git a/bin/rocketeer b/bin/rocketeer
old mode 100644
new mode 100755
index 832787794..b86ca0ca8
--- a/bin/rocketeer
+++ b/bin/rocketeer
@@ -2,17 +2,26 @@
boot();
+
+$app = Rocketeer::getFacadeApplication();
+
+// Compile new phar and extract it
+$boris = new Boris('rocketeer> ');
+$boris->setLocal(array(
+ 'app' => $app,
+));
+
+$boris->start();
diff --git a/composer.json b/composer.json
index 880561fb1..1c88b92e1 100644
--- a/composer.json
+++ b/composer.json
@@ -4,6 +4,7 @@
"license": "MIT",
"keywords": [
"laravel",
+ "deploy",
"deployment",
"ssh"
],
@@ -14,26 +15,32 @@
}
],
"require": {
- "php": ">=5.3.0",
- "illuminate/support": "~4.1",
- "illuminate/config": "~4.1",
- "illuminate/console": "~4.1",
- "illuminate/container": "~4.1",
- "illuminate/filesystem": "~4.1",
- "illuminate/events": "~4.1",
- "illuminate/remote": "~4.1",
- "illuminate/log": "~4.1"
+ "php": ">=5.4.0",
+ "illuminate/support": "~4.2",
+ "illuminate/config": "~4.2",
+ "illuminate/console": "~4.2",
+ "illuminate/container": "~4.2",
+ "illuminate/filesystem": "~4.2",
+ "illuminate/events": "~4.2",
+ "illuminate/remote": "~4.2",
+ "illuminate/log": "~4.2",
+ "kzykhys/parallel": "~0.1.0"
},
"require-dev": {
+ "phpunit/phpunit": "~4.0",
"mockery/mockery": "~0.9",
"nesbot/carbon": "~1.4",
- "patchwork/utf8": "~1.1.18",
+ "patchwork/utf8": "~1.1",
"herrera-io/box": "~1.5.3",
- "phpseclib/phpseclib": "~0.3.5"
+ "phpseclib/phpseclib": "~0.3.5",
+ "d11wtq/boris": "~1.0.8",
+ "raveren/kint": "~0.9.1",
+ "johnkary/phpunit-speedtrap": "dev-master"
},
"suggest": {
"anahkiasen/rocketeer-campfire": "Campfire plugin to create deployments notifications",
- "anahkiasen/rocketeer-slack": "Slack plugin to create deployments notifications"
+ "anahkiasen/rocketeer-slack": "Slack plugin to create deployments notifications",
+ "ext-pcntl": "Allow parallel deployments"
},
"bin": [
"bin/rocketeer"
@@ -45,7 +52,7 @@
},
"extra": {
"branch-alias": {
- "dev-master": "1.2-dev"
+ "dev-develop": "2.0-dev"
}
}
}
diff --git a/composer.lock b/composer.lock
deleted file mode 100644
index 034d7f4ca..000000000
--- a/composer.lock
+++ /dev/null
@@ -1,1019 +0,0 @@
-{
- "_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": "b3b332169ded14fb990f977dd08b7734",
- "packages": [
- {
- "name": "illuminate/config",
- "version": "v4.2.1",
- "target-dir": "Illuminate/Config",
- "source": {
- "type": "git",
- "url": "https://github.com/illuminate/config.git",
- "reference": "cd99d3dc2ca192b7b4003b5f6b31a4b1dd3c8167"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/illuminate/config/zipball/cd99d3dc2ca192b7b4003b5f6b31a4b1dd3c8167",
- "reference": "cd99d3dc2ca192b7b4003b5f6b31a4b1dd3c8167",
- "shasum": ""
- },
- "require": {
- "illuminate/filesystem": "4.2.*",
- "illuminate/support": "4.2.*",
- "php": ">=5.4.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.2-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "Illuminate\\Config": ""
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Taylor Otwell",
- "email": "taylorotwell@gmail.com",
- "homepage": "https://github.com/taylorotwell",
- "role": "Creator of Laravel"
- }
- ],
- "time": "2014-05-23 16:40:19"
- },
- {
- "name": "illuminate/console",
- "version": "v4.2.1",
- "target-dir": "Illuminate/Console",
- "source": {
- "type": "git",
- "url": "https://github.com/illuminate/console.git",
- "reference": "de40c341eeccb1619106e8e8b954eea239aceb68"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/illuminate/console/zipball/de40c341eeccb1619106e8e8b954eea239aceb68",
- "reference": "de40c341eeccb1619106e8e8b954eea239aceb68",
- "shasum": ""
- },
- "require": {
- "php": ">=5.4.0",
- "symfony/console": "2.5.*"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.2-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "Illuminate\\Console": ""
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Taylor Otwell",
- "email": "taylorotwell@gmail.com",
- "homepage": "https://github.com/taylorotwell",
- "role": "Creator of Laravel"
- }
- ],
- "time": "2014-05-23 16:40:19"
- },
- {
- "name": "illuminate/container",
- "version": "v4.2.1",
- "target-dir": "Illuminate/Container",
- "source": {
- "type": "git",
- "url": "https://github.com/illuminate/container.git",
- "reference": "15d85ddb8b56fef54db5941c9b112cae069606ae"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/illuminate/container/zipball/15d85ddb8b56fef54db5941c9b112cae069606ae",
- "reference": "15d85ddb8b56fef54db5941c9b112cae069606ae",
- "shasum": ""
- },
- "require": {
- "php": ">=5.4.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.2-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "Illuminate\\Container": ""
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Taylor Otwell",
- "email": "taylorotwell@gmail.com",
- "homepage": "https://github.com/taylorotwell",
- "role": "Creator of Laravel"
- }
- ],
- "time": "2014-05-23 16:40:19"
- },
- {
- "name": "illuminate/events",
- "version": "v4.2.1",
- "target-dir": "Illuminate/Events",
- "source": {
- "type": "git",
- "url": "https://github.com/illuminate/events.git",
- "reference": "eb23d436806893b6b05d7ad3b6f52212830c7e4e"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/illuminate/events/zipball/eb23d436806893b6b05d7ad3b6f52212830c7e4e",
- "reference": "eb23d436806893b6b05d7ad3b6f52212830c7e4e",
- "shasum": ""
- },
- "require": {
- "illuminate/container": "4.2.*",
- "illuminate/support": "4.2.*",
- "php": ">=5.4.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.2-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "Illuminate\\Events": ""
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Taylor Otwell",
- "email": "taylorotwell@gmail.com",
- "homepage": "https://github.com/taylorotwell",
- "role": "Creator of Laravel"
- }
- ],
- "time": "2014-05-23 16:40:19"
- },
- {
- "name": "illuminate/filesystem",
- "version": "v4.2.1",
- "target-dir": "Illuminate/Filesystem",
- "source": {
- "type": "git",
- "url": "https://github.com/illuminate/filesystem.git",
- "reference": "568f829aebe886bbe84bcd726e1509f830d5a1b0"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/illuminate/filesystem/zipball/568f829aebe886bbe84bcd726e1509f830d5a1b0",
- "reference": "568f829aebe886bbe84bcd726e1509f830d5a1b0",
- "shasum": ""
- },
- "require": {
- "illuminate/support": "4.2.*",
- "php": ">=5.4.0",
- "symfony/finder": "2.5.*"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.2-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "Illuminate\\Filesystem": ""
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Taylor Otwell",
- "email": "taylorotwell@gmail.com",
- "homepage": "https://github.com/taylorotwell",
- "role": "Creator of Laravel"
- }
- ],
- "time": "2014-05-23 16:40:19"
- },
- {
- "name": "illuminate/log",
- "version": "v4.2.1",
- "target-dir": "Illuminate/Log",
- "source": {
- "type": "git",
- "url": "https://github.com/illuminate/log.git",
- "reference": "9b3ae8d7159ae45f3db03bf2a83684c7f2b88474"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/illuminate/log/zipball/9b3ae8d7159ae45f3db03bf2a83684c7f2b88474",
- "reference": "9b3ae8d7159ae45f3db03bf2a83684c7f2b88474",
- "shasum": ""
- },
- "require": {
- "illuminate/support": "4.2.*",
- "monolog/monolog": "~1.6",
- "php": ">=5.4.0"
- },
- "require-dev": {
- "illuminate/events": "4.2.*"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.2-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "Illuminate\\Log": ""
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Taylor Otwell",
- "email": "taylorotwell@gmail.com",
- "homepage": "https://github.com/taylorotwell",
- "role": "Creator of Laravel"
- }
- ],
- "time": "2014-05-23 16:40:19"
- },
- {
- "name": "illuminate/remote",
- "version": "v4.2.1",
- "target-dir": "Illuminate/Remote",
- "source": {
- "type": "git",
- "url": "https://github.com/illuminate/remote.git",
- "reference": "7ba42ea5f526e665062b889632195b947da03d32"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/illuminate/remote/zipball/7ba42ea5f526e665062b889632195b947da03d32",
- "reference": "7ba42ea5f526e665062b889632195b947da03d32",
- "shasum": ""
- },
- "require": {
- "illuminate/filesystem": "4.2.*",
- "illuminate/support": "4.2.*",
- "php": ">=5.4.0",
- "phpseclib/phpseclib": "0.3.*"
- },
- "require-dev": {
- "illuminate/console": "4.2.*"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.2-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "Illuminate\\Remote": ""
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Taylor Otwell",
- "email": "taylorotwell@gmail.com",
- "homepage": "https://github.com/taylorotwell",
- "role": "Creator of Laravel"
- }
- ],
- "time": "2014-05-25 01:46:11"
- },
- {
- "name": "illuminate/support",
- "version": "v4.2.1",
- "target-dir": "Illuminate/Support",
- "source": {
- "type": "git",
- "url": "https://github.com/illuminate/support.git",
- "reference": "f1dd716598f99ff18b455da3e073a8324d65252e"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/illuminate/support/zipball/f1dd716598f99ff18b455da3e073a8324d65252e",
- "reference": "f1dd716598f99ff18b455da3e073a8324d65252e",
- "shasum": ""
- },
- "require": {
- "php": ">=5.4.0"
- },
- "require-dev": {
- "jeremeamia/superclosure": "~1.0",
- "patchwork/utf8": "1.1.*"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.2-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "Illuminate\\Support": ""
- },
- "files": [
- "Illuminate/Support/helpers.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Taylor Otwell",
- "email": "taylorotwell@gmail.com",
- "homepage": "https://github.com/taylorotwell",
- "role": "Creator of Laravel"
- }
- ],
- "time": "2014-05-30 16:22:28"
- },
- {
- "name": "monolog/monolog",
- "version": "1.10.0",
- "source": {
- "type": "git",
- "url": "https://github.com/Seldaek/monolog.git",
- "reference": "25b16e801979098cb2f120e697bfce454b18bf23"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/Seldaek/monolog/zipball/25b16e801979098cb2f120e697bfce454b18bf23",
- "reference": "25b16e801979098cb2f120e697bfce454b18bf23",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.0",
- "psr/log": "~1.0"
- },
- "require-dev": {
- "aws/aws-sdk-php": "~2.4, >2.4.8",
- "doctrine/couchdb": "~1.0@dev",
- "graylog2/gelf-php": "~1.0",
- "phpunit/phpunit": "~3.7.0",
- "raven/raven": "~0.5",
- "ruflin/elastica": "0.90.*"
- },
- "suggest": {
- "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
- "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
- "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
- "ext-mongo": "Allow sending log messages to a MongoDB server",
- "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
- "raven/raven": "Allow sending log messages to a Sentry server",
- "rollbar/rollbar": "Allow sending log messages to Rollbar",
- "ruflin/elastica": "Allow sending log messages to an Elastic Search server"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.10.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Monolog\\": "src/Monolog"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Jordi Boggiano",
- "email": "j.boggiano@seld.be",
- "homepage": "http://seld.be",
- "role": "Developer"
- }
- ],
- "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
- "homepage": "http://github.com/Seldaek/monolog",
- "keywords": [
- "log",
- "logging",
- "psr-3"
- ],
- "time": "2014-06-04 16:30:04"
- },
- {
- "name": "phpseclib/phpseclib",
- "version": "0.3.6",
- "source": {
- "type": "git",
- "url": "https://github.com/phpseclib/phpseclib.git",
- "reference": "0ea31d9b65d49a8661e93bec19f44e989bd34c69"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/0ea31d9b65d49a8661e93bec19f44e989bd34c69",
- "reference": "0ea31d9b65d49a8661e93bec19f44e989bd34c69",
- "shasum": ""
- },
- "require": {
- "php": ">=5.0.0"
- },
- "require-dev": {
- "squizlabs/php_codesniffer": "1.*"
- },
- "suggest": {
- "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
- "ext-mcrypt": "Install the Mcrypt extension in order to speed up a wide variety of cryptographic operations.",
- "pear-pear/PHP_Compat": "Install PHP_Compat to get phpseclib working on PHP < 4.3.3."
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "0.3-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "Crypt": "phpseclib/",
- "File": "phpseclib/",
- "Math": "phpseclib/",
- "Net": "phpseclib/",
- "System": "phpseclib/"
- },
- "files": [
- "phpseclib/Crypt/Random.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "include-path": [
- "phpseclib/"
- ],
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Jim Wigginton",
- "email": "terrafrost@php.net",
- "role": "Lead Developer"
- },
- {
- "name": "Patrick Monnerat",
- "email": "pm@datasphere.ch",
- "role": "Developer"
- },
- {
- "name": "Andreas Fischer",
- "email": "bantu@phpbb.com",
- "role": "Developer"
- },
- {
- "name": "Hans-Jürgen Petrich",
- "email": "petrich@tronic-media.com",
- "role": "Developer"
- }
- ],
- "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
- "homepage": "http://phpseclib.sourceforge.net",
- "keywords": [
- "BigInteger",
- "aes",
- "asn.1",
- "asn1",
- "blowfish",
- "crypto",
- "cryptography",
- "encryption",
- "rsa",
- "security",
- "sftp",
- "signature",
- "signing",
- "ssh",
- "twofish",
- "x.509",
- "x509"
- ],
- "time": "2014-02-28 16:05:05"
- },
- {
- "name": "psr/log",
- "version": "1.0.0",
- "source": {
- "type": "git",
- "url": "https://github.com/php-fig/log.git",
- "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b",
- "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b",
- "shasum": ""
- },
- "type": "library",
- "autoload": {
- "psr-0": {
- "Psr\\Log\\": ""
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "PHP-FIG",
- "homepage": "http://www.php-fig.org/"
- }
- ],
- "description": "Common interface for logging libraries",
- "keywords": [
- "log",
- "psr",
- "psr-3"
- ],
- "time": "2012-12-21 11:40:51"
- },
- {
- "name": "symfony/console",
- "version": "v2.5.0",
- "target-dir": "Symfony/Component/Console",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/Console.git",
- "reference": "ef4ca73b0b3a10cbac653d3ca482d0cdd4502b2c"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/Console/zipball/ef4ca73b0b3a10cbac653d3ca482d0cdd4502b2c",
- "reference": "ef4ca73b0b3a10cbac653d3ca482d0cdd4502b2c",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.3"
- },
- "require-dev": {
- "psr/log": "~1.0",
- "symfony/event-dispatcher": "~2.1"
- },
- "suggest": {
- "psr/log": "For using the console logger",
- "symfony/event-dispatcher": ""
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.5-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "Symfony\\Component\\Console\\": ""
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com",
- "homepage": "http://fabien.potencier.org",
- "role": "Lead Developer"
- },
- {
- "name": "Symfony Community",
- "homepage": "http://symfony.com/contributors"
- }
- ],
- "description": "Symfony Console Component",
- "homepage": "http://symfony.com",
- "time": "2014-05-22 08:54:24"
- },
- {
- "name": "symfony/finder",
- "version": "v2.5.0",
- "target-dir": "Symfony/Component/Finder",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/Finder.git",
- "reference": "307aad2c541bbdf43183043645e186ef2cd6b973"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/Finder/zipball/307aad2c541bbdf43183043645e186ef2cd6b973",
- "reference": "307aad2c541bbdf43183043645e186ef2cd6b973",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.3"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.5-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "Symfony\\Component\\Finder\\": ""
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com",
- "homepage": "http://fabien.potencier.org",
- "role": "Lead Developer"
- },
- {
- "name": "Symfony Community",
- "homepage": "http://symfony.com/contributors"
- }
- ],
- "description": "Symfony Finder Component",
- "homepage": "http://symfony.com",
- "time": "2014-05-22 13:47:45"
- }
- ],
- "packages-dev": [
- {
- "name": "herrera-io/box",
- "version": "1.5.3",
- "source": {
- "type": "git",
- "url": "https://github.com/herrera-io/php-box.git",
- "reference": "b5432951f85a56df6012f503881174e7aeec6611"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/herrera-io/php-box/zipball/b5432951f85a56df6012f503881174e7aeec6611",
- "reference": "b5432951f85a56df6012f503881174e7aeec6611",
- "shasum": ""
- },
- "require": {
- "ext-phar": "*",
- "phine/path": "~1.0",
- "php": ">=5.3.3"
- },
- "require-dev": {
- "herrera-io/annotations": "~1.0",
- "herrera-io/phpunit-test-case": "1.*",
- "mikey179/vfsstream": "1.1.0",
- "phpseclib/phpseclib": "~0.3",
- "phpunit/phpunit": "3.7.*"
- },
- "suggest": {
- "herrera-io/annotations": "For compacting annotated docblocks.",
- "phpseclib/phpseclib": "For verifying OpenSSL signed phars without the phar extension."
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "Herrera\\Box": "src/lib"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Kevin Herrera",
- "email": "kevin@herrera.io",
- "homepage": "http://kevin.herrera.io",
- "role": "Developer"
- }
- ],
- "description": "A library for simplifying the PHAR build process.",
- "homepage": "http://herrera-io.github.com/php-box",
- "keywords": [
- "phar"
- ],
- "time": "2014-02-07 15:48:46"
- },
- {
- "name": "mockery/mockery",
- "version": "0.9.1",
- "source": {
- "type": "git",
- "url": "https://github.com/padraic/mockery.git",
- "reference": "17f63ee40ed14a8afb7ba1f0ae15cc4491d719d1"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/padraic/mockery/zipball/17f63ee40ed14a8afb7ba1f0ae15cc4491d719d1",
- "reference": "17f63ee40ed14a8afb7ba1f0ae15cc4491d719d1",
- "shasum": ""
- },
- "require": {
- "lib-pcre": ">=7.0",
- "php": ">=5.3.2"
- },
- "require-dev": {
- "hamcrest/hamcrest-php": "~1.1",
- "phpunit/phpunit": "~4.0",
- "satooshi/php-coveralls": "~0.7@dev"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "0.9.x-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "Mockery": "library/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Pádraic Brady",
- "email": "padraic.brady@gmail.com",
- "homepage": "http://blog.astrumfutura.com"
- },
- {
- "name": "Dave Marshall",
- "email": "dave.marshall@atstsolutions.co.uk",
- "homepage": "http://davedevelopment.co.uk"
- }
- ],
- "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succint API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.",
- "homepage": "http://github.com/padraic/mockery",
- "keywords": [
- "BDD",
- "TDD",
- "library",
- "mock",
- "mock objects",
- "mockery",
- "stub",
- "test",
- "test double",
- "testing"
- ],
- "time": "2014-05-02 12:16:45"
- },
- {
- "name": "nesbot/carbon",
- "version": "1.9.0",
- "source": {
- "type": "git",
- "url": "https://github.com/briannesbitt/Carbon.git",
- "reference": "b94de7192b01d0e80794eae984dcc773220ab0dc"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/b94de7192b01d0e80794eae984dcc773220ab0dc",
- "reference": "b94de7192b01d0e80794eae984dcc773220ab0dc",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.0"
- },
- "require-dev": {
- "phpunit/phpunit": "3.7.*"
- },
- "type": "library",
- "autoload": {
- "psr-0": {
- "Carbon": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Brian Nesbitt",
- "email": "brian@nesbot.com",
- "homepage": "http://nesbot.com"
- }
- ],
- "description": "A simple API extension for DateTime.",
- "homepage": "https://github.com/briannesbitt/Carbon",
- "keywords": [
- "date",
- "datetime",
- "time"
- ],
- "time": "2014-05-13 02:29:30"
- },
- {
- "name": "patchwork/utf8",
- "version": "v1.1.23",
- "source": {
- "type": "git",
- "url": "https://github.com/nicolas-grekas/Patchwork-UTF8.git",
- "reference": "c1edbc82aed49ff2bdef0a0f659bf69e3f7b14cc"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/nicolas-grekas/Patchwork-UTF8/zipball/c1edbc82aed49ff2bdef0a0f659bf69e3f7b14cc",
- "reference": "c1edbc82aed49ff2bdef0a0f659bf69e3f7b14cc",
- "shasum": ""
- },
- "require": {
- "lib-pcre": ">=7.3",
- "php": ">=5.3.0"
- },
- "suggest": {
- "ext-iconv": "Use iconv for best performance",
- "ext-intl": "Use Intl for best performance",
- "ext-mbstring": "Use Mbstring for best performance"
- },
- "type": "library",
- "autoload": {
- "psr-0": {
- "Patchwork": "class/",
- "Normalizer": "class/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "(Apache-2.0 or GPL-2.0)"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com",
- "role": "Developer"
- }
- ],
- "description": "Extensive, portable and performant handling of UTF-8 and grapheme clusters for PHP",
- "homepage": "https://github.com/nicolas-grekas/Patchwork-UTF8",
- "keywords": [
- "i18n",
- "unicode",
- "utf-8",
- "utf8"
- ],
- "time": "2014-05-22 13:59:09"
- },
- {
- "name": "phine/exception",
- "version": "1.0.0",
- "source": {
- "type": "git",
- "url": "https://github.com/phine/lib-exception.git",
- "reference": "150c6b6090b2ebc53c60e87cb20c7f1287b7b68a"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phine/lib-exception/zipball/150c6b6090b2ebc53c60e87cb20c7f1287b7b68a",
- "reference": "150c6b6090b2ebc53c60e87cb20c7f1287b7b68a",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.3"
- },
- "require-dev": {
- "league/phpunit-coverage-listener": "~1.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "Phine\\Exception": "src/lib"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Kevin Herrera",
- "email": "kevin@herrera.io",
- "homepage": "http://kevin.herrera.io"
- }
- ],
- "description": "A PHP library for improving the use of exceptions.",
- "homepage": "https://github.com/phine/lib-exception",
- "keywords": [
- "exception"
- ],
- "time": "2013-08-27 17:43:25"
- },
- {
- "name": "phine/path",
- "version": "1.1.0",
- "source": {
- "type": "git",
- "url": "https://github.com/phine/lib-path.git",
- "reference": "cbe1a5eb6cf22958394db2469af9b773508abddd"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phine/lib-path/zipball/cbe1a5eb6cf22958394db2469af9b773508abddd",
- "reference": "cbe1a5eb6cf22958394db2469af9b773508abddd",
- "shasum": ""
- },
- "require": {
- "phine/exception": "~1.0",
- "php": ">=5.3.3"
- },
- "require-dev": {
- "league/phpunit-coverage-listener": "~1.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "Phine\\Path": "src/lib"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Kevin Herrera",
- "email": "kevin@herrera.io",
- "homepage": "http://kevin.herrera.io"
- }
- ],
- "description": "A PHP library for improving the use of file system paths.",
- "homepage": "https://github.com/phine/lib-path",
- "keywords": [
- "file",
- "path",
- "system"
- ],
- "time": "2013-10-15 22:58:04"
- }
- ],
- "aliases": [],
- "minimum-stability": "stable",
- "stability-flags": [],
- "platform": {
- "php": ">=5.3.0"
- },
- "platform-dev": []
-}
diff --git a/docs b/docs
deleted file mode 160000
index cf947b11c..000000000
--- a/docs
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit cf947b11c636abca7956b350f3fee9f09837cc34
diff --git a/phpunit.xml b/phpunit.xml
index 18ef14b9f..5ff0a69fa 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,39 +1,47 @@
-
-
-
-
-
-
- src/Rocketeer
-
- src/Rocketeer/RocketeerServiceProvider.php
- src/Rocketeer/Console/WhitespaceCompactor.php
- src/Rocketeer/Console/Compiler.php
- src/Rocketeer/Console
- src/Rocketeer/Commands
- src/Rocketeer/Facades
-
-
-
-
-
-
- tests
-
-
-
\ No newline at end of file
+
+
+
+
+
+ src/Rocketeer
+
+ src/Rocketeer/Abstracts/AbstractCommand.php
+ src/Rocketeer/RocketeerServiceProvider.php
+ src/Rocketeer/Console
+ src/Rocketeer/Interfaces
+ src/Rocketeer/Facades
+
+
+
+
+
+
+
+
+
+ 500
+
+
+ 10
+
+
+
+
+
+
+
+
+ tests
+
+
+
diff --git a/src/Rocketeer/Abstracts/AbstractBinary.php b/src/Rocketeer/Abstracts/AbstractBinary.php
new file mode 100644
index 000000000..73acb1236
--- /dev/null
+++ b/src/Rocketeer/Abstracts/AbstractBinary.php
@@ -0,0 +1,221 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Abstracts;
+
+use Illuminate\Container\Container;
+use Illuminate\Support\Arr;
+use Illuminate\Support\Str;
+use Rocketeer\Traits\HasLocator;
+
+/**
+ * A generic class to represent a binary as a class
+ *
+ * @author Maxime Fabre
+ */
+abstract class AbstractBinary
+{
+ use HasLocator;
+
+ /**
+ * The core binary
+ *
+ * @var string
+ */
+ protected $binary;
+
+ /**
+ * A parent binary to call this one with
+ *
+ * @type AbstractBinary|string
+ */
+ protected $parent;
+
+ /**
+ * @param Container $app
+ */
+ public function __construct(Container $app)
+ {
+ $this->app = $app;
+
+ // Assign default paths
+ $paths = $this->getKnownPaths();
+ if ($this->connections->getConnection() && $paths) {
+ $binary = Arr::get($paths, 0);
+ $fallback = Arr::get($paths, 1);
+ $binary = $this->bash->which($binary, $fallback, false);
+
+ $this->setBinary($binary);
+ } elseif ($paths) {
+ $this->setBinary($paths[0]);
+ }
+ }
+
+ /**
+ * Get an array of default paths to look for
+ *
+ * @return array
+ */
+ protected function getKnownPaths()
+ {
+ return [];
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ///////////////////////////// PROPERTIES /////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * @param AbstractBinary|string $parent
+ */
+ public function setParent($parent)
+ {
+ $this->parent = $parent;
+ }
+
+ /**
+ * @param string $binary
+ */
+ public function setBinary($binary)
+ {
+ $this->binary = $binary;
+ }
+
+ /**
+ * Get the current binary name
+ *
+ * @return string
+ */
+ public function getBinary()
+ {
+ return $this->binary;
+ }
+
+ /**
+ * Call or execute a command on the Binary
+ *
+ * @param string $name
+ * @param array $arguments
+ *
+ * @return string|null
+ */
+ public function __call($name, $arguments)
+ {
+ // Execution aliases
+ if (Str::startsWith($name, 'run')) {
+ $command = array_shift($arguments);
+ $command = call_user_func_array([$this, $command], $arguments);
+
+ return $this->bash->$name($command);
+ }
+
+ // Format name
+ $name = Str::snake($name, '-');
+
+ // Prepend command name to arguments and call
+ array_unshift($arguments, $name);
+ $command = call_user_func_array([$this, 'getCommand'], $arguments);
+
+ return $command;
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ //////////////////////////////// HELPERS ///////////////////////////
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Returns a command with the SCM's binary
+ *
+ * @param string|null $command
+ * @param string|string[] $arguments
+ * @param string|string[] $flags
+ *
+ * @return string
+ */
+ public function getCommand($command = null, $arguments = array(), $flags = array())
+ {
+ // Format arguments
+ $arguments = $this->buildArguments($arguments);
+ $options = $this->buildOptions($flags);
+
+ // Build command
+ $binary = $this->binary;
+ $components = [$command, $arguments, $options];
+ foreach ($components as $component) {
+ if ($component) {
+ $binary .= ' '.$component;
+ }
+ }
+
+ // If the binary has a parent, wrap the call with it
+ $parent = $this->parent instanceof AbstractBinary ? $this->parent->getBinary() : $this->parent;
+ $command = $parent.' '.$binary;
+
+ return trim($command);
+ }
+
+ /**
+ * @param string|string[] $flags
+ *
+ * @return string
+ */
+ protected function buildOptions($flags)
+ {
+ // Return if already builts
+ if (is_string($flags)) {
+ return $flags;
+ }
+
+ $options = [];
+ $flags = (array) $flags;
+
+ // Flip array if necessary
+ $firstKey = Arr::get(array_keys($flags), 0);
+ if (!is_null($firstKey) && is_int($firstKey)) {
+ $flags = array_combine(
+ array_values($flags),
+ array_fill(0, count($flags), null)
+ );
+ }
+
+ // Build flags
+ foreach ($flags as $flag => $value) {
+ $options[] = $value ? $flag.'="'.$value.'"' : $flag;
+ }
+
+ return implode(' ', $options);
+ }
+
+ /**
+ * @param string|string[] $arguments
+ *
+ * @return string
+ */
+ protected function buildArguments($arguments)
+ {
+ if (!is_string($arguments)) {
+ $arguments = (array) $arguments;
+ $arguments = implode(' ', $arguments);
+ }
+
+ return $arguments;
+ }
+
+ /**
+ * Quote a string
+ *
+ * @param string $string
+ *
+ * @return string
+ */
+ protected function quote($string)
+ {
+ return '"'.$string.'"';
+ }
+}
diff --git a/src/Rocketeer/Abstracts/AbstractCommand.php b/src/Rocketeer/Abstracts/AbstractCommand.php
new file mode 100644
index 000000000..aedd97866
--- /dev/null
+++ b/src/Rocketeer/Abstracts/AbstractCommand.php
@@ -0,0 +1,261 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Abstracts;
+
+use Closure;
+use Illuminate\Console\Command;
+use Symfony\Component\Console\Helper\Table;
+use Symfony\Component\Console\Input\InputOption;
+
+/**
+ * An abstract command with various helpers for all
+ * subcommands to inherit
+ *
+ * @author Maxime Fabre
+ */
+abstract class AbstractCommand extends Command
+{
+ /**
+ * Whether the command's task should be built
+ * into a pipeline or run straight
+ *
+ * @type boolean
+ */
+ protected $straight = false;
+
+ /**
+ * the task to execute on fire
+ *
+ * @var AbstractTask
+ */
+ protected $task;
+
+ /**
+ * @param AbstractTask|null $task
+ */
+ public function __construct(AbstractTask $task = null)
+ {
+ parent::__construct();
+
+ // If we passed a Task, bind its properties
+ // to the command
+ if ($task) {
+ $this->task = $task;
+ $this->task->command = $this;
+
+ if (!$this->description && $description = $task->getDescription()) {
+ $this->setDescription($description);
+ }
+ }
+ }
+
+ /**
+ * Get the task this command executes
+ *
+ * @return AbstractTask
+ */
+ public function getTask()
+ {
+ return $this->task;
+ }
+
+ /**
+ * Returns the command name.
+ *
+ * @return string The command name
+ */
+ public function getName()
+ {
+ // Return commands as is in Laravel
+ if ($this->isInsideLaravel()) {
+ return $this->name;
+ }
+
+ $name = str_replace('deploy:', null, $this->name);
+ $name = str_replace('-', ':', $name);
+
+ return $name;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// EXECUTION /////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Run the tasks
+ *
+ * @return void
+ */
+ abstract public function fire();
+
+ /**
+ * Get the console command options.
+ *
+ * @return array>
+ */
+ protected function getOptions()
+ {
+ return array(
+ // Options
+ ['parallel', 'P', InputOption::VALUE_NONE, 'Run the tasks asynchronously instead of sequentially'],
+ ['pretend', 'p', InputOption::VALUE_NONE, 'Shows which command would execute without actually doing anything'],
+ ['on', 'C', InputOption::VALUE_REQUIRED, 'The connection(s) to execute the Task in'],
+ ['stage', 'S', InputOption::VALUE_REQUIRED, 'The stage to execute the Task in'],
+ // Credentials
+ ['host', null, InputOption::VALUE_REQUIRED, 'The host to use if asked'],
+ ['username', null, InputOption::VALUE_REQUIRED, 'The username to use if asked'],
+ ['password', null, InputOption::VALUE_REQUIRED, 'The password to use if asked'],
+ ['key', null, InputOption::VALUE_REQUIRED, 'The key to use if asked'],
+ ['keyphrase', null, InputOption::VALUE_REQUIRED, 'The keyphrase to use if asked'],
+ ['agent', null, InputOption::VALUE_REQUIRED, 'The agent to use if asked'],
+ ['repository', null, InputOption::VALUE_REQUIRED, 'The repository to use if asked'],
+ );
+ }
+
+ /**
+ * Check if the current command is run in the scope of
+ * Laravel or standalone
+ *
+ * @return boolean
+ */
+ public function isInsideLaravel()
+ {
+ return $this->laravel->bound('artisan');
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ ///////////////////////////// CORE METHODS /////////////////////////
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Fire a Tasks Queue
+ *
+ * @param string|string[]|\Rocketeer\Abstracts\AbstractTask[] $tasks
+ *
+ * @return integer
+ */
+ protected function fireTasksQueue($tasks)
+ {
+ // Bind command to container
+ $this->laravel->instance('rocketeer.command', $this);
+
+ // Check for credentials
+ $this->laravel['rocketeer.credentials']->getServerCredentials();
+ $this->laravel['rocketeer.credentials']->getRepositoryCredentials();
+
+ if ($this->straight) {
+ // If we only have a single task, run it
+ $status = $this->laravel['rocketeer.builder']->buildTask($tasks)->fire();
+ } else {
+ // Run tasks and display timer
+ $status = $this->time(function () use ($tasks) {
+ return $this->laravel['rocketeer.queue']->run($tasks);
+ });
+ }
+
+ // Remove command instance
+ unset($this->laravel['rocketeer.command']);
+
+ // Save history to logs
+ $logs = $this->laravel['rocketeer.logs']->write();
+ foreach ($logs as $log) {
+ $this->info('Saved logs to '.$log);
+ }
+
+ return $status ? 0 : 1;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ /////////////////////////////// INPUT ////////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Ask a question to the user, with default and/or multiple choices
+ *
+ * @param string $question
+ * @param string|null $default
+ * @param string[] $choices
+ *
+ * @return string
+ */
+ public function askWith($question, $default = null, $choices = array())
+ {
+ $question = $this->formatQuestion($question, $default, $choices);
+
+ // If we provided choices, autocomplete
+ if ($choices) {
+ return $this->askWithCompletion($question, $choices, $default);
+ }
+
+ return $this->ask($question, $default);
+ }
+
+ /**
+ * Ask a question to the user, hiding the input
+ *
+ * @param string $question
+ * @param string|null $default
+ *
+ * @return string|null
+ */
+ public function askSecretly($question, $default = null)
+ {
+ $question = $this->formatQuestion($question, $default);
+
+ return $this->secret($question) ?: $default;
+ }
+
+ /**
+ * Adds additional information to a question
+ *
+ * @param string $question
+ * @param string $default
+ * @param array $choices
+ *
+ * @return string
+ */
+ protected function formatQuestion($question, $default, $choices = array())
+ {
+ // If default, show it in the question
+ if (!is_null($default)) {
+ $question .= ' ('.$default.')';
+ }
+
+ // If multiple choices, show them
+ if ($choices) {
+ $question .= ' ['.implode('/', $choices).']';
+ }
+
+ return $question;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// HELPERS ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Time an operation and display it afterwards
+ *
+ * @param Closure $callback
+ *
+ * @return boolean
+ */
+ public function time(Closure $callback)
+ {
+ // Start timer, execute callback, close timer
+ $timerStart = microtime(true);
+ $results = $callback();
+ $time = round(microtime(true) - $timerStart, 4);
+
+ $this->line('Execution time: '.$time.'s');
+
+ return $results;
+ }
+}
diff --git a/src/Rocketeer/Abstracts/AbstractPackageManager.php b/src/Rocketeer/Abstracts/AbstractPackageManager.php
new file mode 100644
index 000000000..ae83e8d2f
--- /dev/null
+++ b/src/Rocketeer/Abstracts/AbstractPackageManager.php
@@ -0,0 +1,62 @@
+getBinary() && $this->hasManifest();
+ }
+
+ /**
+ * Check if the manifest file exists, locally or on server
+ *
+ * @return bool
+ */
+ public function hasManifest()
+ {
+ $server = $this->paths->getFolder('current/'.$this->manifest);
+ $server = $this->bash->fileExists($server);
+
+ $local = $this->app['path.base'].DS.$this->manifest;
+ $local = $this->files->exists($local);
+
+ return $local || $server;
+ }
+
+ /**
+ * Get the contents of the manifest file
+ *
+ * @return string|null
+ * @throws \Illuminate\Filesystem\FileNotFoundException
+ */
+ public function getManifestContents()
+ {
+ $manifest = $this->app['path.base'].DS.$this->manifest;
+ if ($this->files->exists($manifest)) {
+ return $this->files->get($manifest);
+ }
+
+ return null;
+ }
+
+ /**
+ * @return string
+ */
+ public function getManifest()
+ {
+ return $this->manifest;
+ }
+}
diff --git a/src/Rocketeer/Traits/Plugin.php b/src/Rocketeer/Abstracts/AbstractPlugin.php
similarity index 84%
rename from src/Rocketeer/Traits/Plugin.php
rename to src/Rocketeer/Abstracts/AbstractPlugin.php
index dc7e31be0..bbe0b1e2f 100644
--- a/src/Rocketeer/Traits/Plugin.php
+++ b/src/Rocketeer/Abstracts/AbstractPlugin.php
@@ -7,19 +7,22 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
-namespace Rocketeer\Traits;
+namespace Rocketeer\Abstracts;
use Illuminate\Container\Container;
use Illuminate\Support\Str;
-use Rocketeer\TasksHandler;
+use Rocketeer\Services\TasksHandler;
+use Rocketeer\Traits\HasLocator;
/**
* A basic abstract class for Rocketeer plugins to extend
*
* @author Maxime Fabre
*/
-abstract class Plugin extends AbstractLocatorClass
+abstract class AbstractPlugin
{
+ use HasLocator;
+
/**
* The path to the configuration folder
*
@@ -56,7 +59,7 @@ public function register(Container $app)
/**
* Register Tasks with Rocketeer
*
- * @param TasksHandler $queue
+ * @param \Rocketeer\Services\TasksHandler $queue
*
* @return void
*/
diff --git a/src/Rocketeer/Abstracts/AbstractStorage.php b/src/Rocketeer/Abstracts/AbstractStorage.php
new file mode 100644
index 000000000..1d510920e
--- /dev/null
+++ b/src/Rocketeer/Abstracts/AbstractStorage.php
@@ -0,0 +1,132 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Abstracts;
+
+use Closure;
+use Illuminate\Container\Container;
+use Illuminate\Support\Arr;
+use Rocketeer\Traits\HasLocator;
+
+/**
+ * Abstract class for storage implementations
+ *
+ * @author Maxime Fabre
+ */
+abstract class AbstractStorage
+{
+ use HasLocator;
+
+ /**
+ * The file to act on
+ *
+ * @type string
+ */
+ protected $file;
+
+ /**
+ * Build a new ServerStorage
+ *
+ * @param Container $app
+ * @param string $file
+ */
+ public function __construct(Container $app, $file)
+ {
+ $this->app = $app;
+ $this->file = $file;
+ }
+
+ /**
+ * Change the file in use
+ *
+ * @param string $file
+ */
+ public function setFile($file)
+ {
+ $this->file = $file;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ /////////////////////////////// VALUES ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get a value on the server
+ *
+ * @param string|null $key
+ * @param array|string|Closure|null $fallback
+ *
+ * @return string|integer|array
+ */
+ public function get($key = null, $fallback = null)
+ {
+ $contents = $this->getContents();
+
+ return Arr::get($contents, $key, $fallback);
+ }
+
+ /**
+ * Set a value on the server
+ *
+ * @param string|array $key
+ * @param mixed|null $value
+ */
+ public function set($key, $value = null)
+ {
+ // Set the value on the contents
+ $contents = (array) $this->getContents();
+ if (is_array($key)) {
+ $contents = $key;
+ } else {
+ Arr::set($contents, $key, $value);
+ }
+
+ $this->saveContents($contents);
+ }
+
+ /**
+ * Forget a value from the repository file
+ *
+ * @param string $key
+ */
+ public function forget($key)
+ {
+ $contents = $this->getContents();
+ Arr::forget($contents, $key);
+
+ $this->saveContents($contents);
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// HELPERS ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get the full path to the file
+ *
+ * @return string
+ */
+ abstract public function getFilepath();
+
+ /**
+ * Get the contents of the file
+ *
+ * @return array
+ */
+ abstract protected function getContents();
+
+ /**
+ * Save the contents of the file
+ *
+ * @param array $contents
+ *
+ * @return void
+ */
+ abstract protected function saveContents($contents);
+}
diff --git a/src/Rocketeer/Traits/Task.php b/src/Rocketeer/Abstracts/AbstractTask.php
similarity index 55%
rename from src/Rocketeer/Traits/Task.php
rename to src/Rocketeer/Abstracts/AbstractTask.php
index 055a94949..36abe2f55 100644
--- a/src/Rocketeer/Traits/Task.php
+++ b/src/Rocketeer/Abstracts/AbstractTask.php
@@ -7,18 +7,22 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
-namespace Rocketeer\Traits;
+namespace Rocketeer\Abstracts;
use DateTime;
+use Illuminate\Support\Str;
use Rocketeer\Bash;
+use Rocketeer\Traits\StepsRunner;
/**
- * An abstract Task with common helpers, from which all Tasks derive
+ * An abstract AbstractTask with common helpers, from which all Tasks derive
*
* @author Maxime Fabre
*/
-abstract class Task extends Bash
+abstract class AbstractTask extends Bash
{
+ use StepsRunner;
+
/**
* The name of the task
*
@@ -27,32 +31,32 @@ abstract class Task extends Bash
protected $name;
/**
- * A description of what the Task does
+ * A description of what the task does
*
* @var string
*/
protected $description;
/**
- * Whether the task was halted mid-course
+ * The event this task is answering to
*
- * @var boolean
+ * @type string
*/
- protected $halted = false;
+ protected $event;
/**
- * Whether the Task needs to be run on each stage or globally
+ * Whether the task was halted mid-course
*
* @var boolean
*/
- public $usesStages = true;
+ protected $halted = false;
////////////////////////////////////////////////////////////////////
////////////////////////////// REFLECTION //////////////////////////
////////////////////////////////////////////////////////////////////
/**
- * Get the name of the Task
+ * Get the name of the task
*
* @return string
*/
@@ -62,33 +66,52 @@ public function getName()
}
/**
- * Change the Task's name
+ * Get the basic name of the task
*
- * @param string $name
+ * @return string
*/
- public function setName($name)
+ public function getSlug()
{
- $this->name = $name;
+ $slug = Str::snake($this->getName(), '-');
+ $slug = Str::slug($slug);
+
+ return $slug;
}
/**
- * Get the basic name of the Task
+ * Get what the task does
*
* @return string
*/
- public function getSlug()
+ public function getDescription()
{
- return strtolower($this->getName());
+ return $this->description;
}
/**
- * Get what the Task does
+ * Change the task's name
*
- * @return string
+ * @param string $name
*/
- public function getDescription()
+ public function setName($name)
{
- return $this->description;
+ $this->name = ucfirst($name) ?: $this->name;
+ }
+
+ /**
+ * @param string $event
+ */
+ public function setEvent($event)
+ {
+ $this->event = $event;
+ }
+
+ /**
+ * @param string $description
+ */
+ public function setDescription($description)
+ {
+ $this->description = $description ?: $this->description;
}
////////////////////////////////////////////////////////////////////
@@ -96,34 +119,38 @@ public function getDescription()
////////////////////////////////////////////////////////////////////
/**
- * Run the Task
+ * Run the task
*
- * @return void
+ * @return string
*/
abstract public function execute();
/**
* Fire the command
*
- * @return array
+ * @return boolean
*/
public function fire()
{
- // Fire the Task if the before event passes
+ // Print status
+ $results = false;
+ $this->displayStatus();
+
+ // Fire the task if the before event passes
if ($this->fireEvent('before')) {
- $results = $this->execute();
+ $this->timer->time($this, function () use (&$results) {
+ $results = $this->execute();
+ });
$this->fireEvent('after');
-
- return $results;
}
- return false;
+ return $results;
}
/**
* Cancel the task
*
- * @param string $errors Potential errors to display
+ * @param string|null $errors Potential errors to display
*
* @return boolean
*/
@@ -134,13 +161,14 @@ public function halt($errors = null)
$this->command->error($errors);
}
+ $this->fireEvent('halt');
$this->halted = true;
return false;
}
/**
- * Whether the Task was halted mid-course
+ * Whether the task was halted mid-course
*
* @return boolean
*/
@@ -158,13 +186,24 @@ public function wasHalted()
*
* @param string $event
*
- * @return array|null
+ * @return boolean
*/
public function fireEvent($event)
{
+ $event = $this->getQualifiedEvent($event);
+ $listeners = $this->events->getListeners($event);
+
// Fire the event
- $event = $this->getQualifiedEvent($event);
- $result = $this->app['events']->fire($event, array($this), true);
+ $result = $this->explainer->displayBelow(function () use ($listeners) {
+ foreach ($listeners as $listener) {
+ $response = call_user_func_array($listener, [$this]);
+ if ($response === false) {
+ return false;
+ }
+ }
+
+ return true;
+ });
// If the event returned a strict false, halt the task
if ($result === false) {
@@ -193,34 +232,49 @@ public function getQualifiedEvent($event)
/**
* Display a list of releases and their status
*
- * @return void
+ * @codeCoverageIgnore
*/
protected function displayReleases()
{
+ if (!$this->command) {
+ return;
+ }
+
+ $key = 0;
+ $rows = [];
$releases = $this->releasesManager->getValidationFile();
- $this->command->comment('Here are the available releases :');
- $key = 0;
+ // Append the rows
foreach ($releases as $name => $state) {
- $name = DateTime::createFromFormat('YmdHis', $name);
- $name = $name->format('Y-m-d H:i:s');
- $method = $state ? 'info' : 'error';
- $state = $state ? '✓' : '✘';
+ $icon = $state ? '✓' : '✘';
+ $color = $state ? 'green' : 'red';
+ $date = DateTime::createFromFormat('YmdHis', $name)->format('Y-m-d H:i:s');
+ $date = sprintf('%s', $color, $date, $color);
+ // Add color to row
+ $rows[] = [$key, $name, $date, $icon];
$key++;
- $this->command->$method(sprintf('[%d] %s %s', $key, $name, $state));
}
+
+ // Render table
+ $this->command->comment('Here are the available releases :');
+ $this->command->table(
+ ['#', 'Path', 'Deployed at', 'Status'],
+ $rows
+ );
+
+ return $rows;
}
/**
- * Execute another Task by name
- *
- * @param string $task
- *
- * @return string The Task's output
+ * Display what the command is and does
*/
- public function executeTask($task)
+ protected function displayStatus()
{
- return $this->app['rocketeer.tasks']->buildTaskFromClass($task)->fire();
+ $name = $this->getName();
+ $description = $this->getDescription();
+ $time = $this->timer->getTaskTime($this);
+
+ $this->explainer->display($name, $description, $this->event, $time);
}
}
diff --git a/src/Rocketeer/Abstracts/Strategies/AbstractCheckStrategy.php b/src/Rocketeer/Abstracts/Strategies/AbstractCheckStrategy.php
new file mode 100644
index 000000000..fc5f43823
--- /dev/null
+++ b/src/Rocketeer/Abstracts/Strategies/AbstractCheckStrategy.php
@@ -0,0 +1,122 @@
+manager;
+ }
+
+ /**
+ * @param \Rocketeer\Abstracts\AbstractPackageManager $manager
+ */
+ public function setManager($manager)
+ {
+ $this->manager = $manager;
+ }
+
+ /**
+ * @return string
+ */
+ public function getLanguage()
+ {
+ return $this->language;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ /////////////////////////////// CHECKS ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Check that the PM that'll install
+ * the app's dependencies is present
+ *
+ * @return boolean
+ */
+ public function manager()
+ {
+ return $this->manager->isExecutable();
+ }
+
+ /**
+ * Check that the language used by the
+ * application is at the required version
+ *
+ * @return boolean
+ */
+ public function language()
+ {
+ $required = null;
+
+ // Get the minimum version of the application
+ if ($manifest = $this->manager->getManifestContents()) {
+ $required = $this->getLanguageConstraint($manifest);
+ }
+
+ // Cancel if no version constraint
+ if (!$required) {
+ return true;
+ }
+
+ $version = $this->getCurrentVersion();
+
+ return version_compare($version, $required, '>=');
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// LANGUAGE //////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get the version constraint which should be checked against
+ *
+ * @param string $manifest
+ *
+ * @return string
+ */
+ abstract protected function getLanguageConstraint($manifest);
+
+ /**
+ * Get the current version in use
+ *
+ * @return string
+ */
+ abstract protected function getCurrentVersion();
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// HELPERS ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * @param string $manifest
+ * @param string $handle
+ *
+ * @return string
+ */
+ protected function getLanguageConstraintFromJson($manifest, $handle)
+ {
+ $manifest = json_decode($manifest, true);
+ $constraint = (string) Arr::get($manifest, $handle);
+ $constraint = preg_replace('/[~>= ]+ ?(.+)/', '$1', $constraint);
+
+ return $constraint;
+ }
+}
diff --git a/src/Rocketeer/Abstracts/Strategies/AbstractDependenciesStrategy.php b/src/Rocketeer/Abstracts/Strategies/AbstractDependenciesStrategy.php
new file mode 100644
index 000000000..ee9927140
--- /dev/null
+++ b/src/Rocketeer/Abstracts/Strategies/AbstractDependenciesStrategy.php
@@ -0,0 +1,96 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Abstracts\Strategies;
+
+use Illuminate\Container\Container;
+use Rocketeer\Abstracts\AbstractPackageManager;
+
+/**
+ * Abstract class for Dependencies strategies
+ *
+ * @author Maxime Fabre
+ */
+abstract class AbstractDependenciesStrategy extends AbstractStrategy
+{
+ /**
+ * The name of the binary
+ *
+ * @type string
+ */
+ protected $binary;
+
+ /**
+ * The package manager instance
+ *
+ * @type AbstractPackageManager
+ */
+ protected $manager;
+
+ /**
+ * @param Container $app
+ */
+ public function __construct(Container $app)
+ {
+ $this->app = $app;
+ $this->manager = $this->binary($this->binary);
+ }
+
+ /**
+ * @param AbstractPackageManager $manager
+ */
+ public function setManager($manager)
+ {
+ $this->manager = $manager;
+ }
+
+ /**
+ * Get an instance of the Binary
+ *
+ * @return AbstractPackageManager
+ */
+ protected function getManager()
+ {
+ return $this->manager;
+ }
+
+ /**
+ * Whether this particular strategy is runnable or not
+ *
+ * @return boolean
+ */
+ public function isExecutable()
+ {
+ return $this->manager->isExecutable();
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// COMMANDS //////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Install the dependencies
+ *
+ * @return bool
+ */
+ public function install()
+ {
+ return $this->manager->runForCurrentRelease('install');
+ }
+
+ /**
+ * Update the dependencies
+ *
+ * @return boolean
+ */
+ public function update()
+ {
+ return $this->manager->runForCurrentRelease('update');
+ }
+}
diff --git a/src/Rocketeer/Abstracts/Strategies/AbstractPolyglotStrategy.php b/src/Rocketeer/Abstracts/Strategies/AbstractPolyglotStrategy.php
new file mode 100644
index 000000000..d91b5e84f
--- /dev/null
+++ b/src/Rocketeer/Abstracts/Strategies/AbstractPolyglotStrategy.php
@@ -0,0 +1,70 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Abstracts\Strategies;
+
+use Closure;
+
+abstract class AbstractPolyglotStrategy extends AbstractStrategy
+{
+ /**
+ * The various strategies to call
+ *
+ * @type array
+ */
+ protected $strategies = [];
+
+ /**
+ * Execute a method on all sub-strategies
+ *
+ * @param string $method
+ *
+ * @return boolean[]
+ */
+ protected function executeStrategiesMethod($method)
+ {
+ return $this->onStrategies(function (AbstractStrategy $strategy) use ($method) {
+ return $strategy->$method();
+ });
+ }
+
+ /**
+ * Assert that the results of a command are all true
+ *
+ * @param boolean[] $results
+ *
+ * @return boolean
+ */
+ protected function checkStrategiesResults($results)
+ {
+ $results = array_filter($results);
+
+ return count($results) == count($this->strategies);
+ }
+
+ /**
+ * @param Closure $callback
+ *
+ * @return array
+ */
+ protected function onStrategies(Closure $callback)
+ {
+ return $this->explainer->displayBelow(function () use ($callback) {
+ $results = [];
+ foreach ($this->strategies as $strategy) {
+ $instance = $this->getStrategy('Dependencies', $strategy);
+ if ($instance) {
+ $results[$strategy] = $callback($instance);
+ }
+ }
+
+ return $results;
+ });
+ }
+}
diff --git a/src/Rocketeer/Abstracts/Strategies/AbstractStrategy.php b/src/Rocketeer/Abstracts/Strategies/AbstractStrategy.php
new file mode 100644
index 000000000..996f81a49
--- /dev/null
+++ b/src/Rocketeer/Abstracts/Strategies/AbstractStrategy.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Abstracts\Strategies;
+
+use Illuminate\Support\Arr;
+use Rocketeer\Bash;
+
+/**
+ * Core class for strategies
+ *
+ * @author Maxime Fabre
+ */
+abstract class AbstractStrategy extends Bash
+{
+ /**
+ * @type string
+ */
+ protected $description;
+
+ /**
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ /**
+ * Whether this particular strategy is runnable or not
+ *
+ * @return boolean
+ */
+ public function isExecutable()
+ {
+ return true;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// HELPERS ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Display what the command is and does
+ *
+ * @return $this
+ */
+ public function displayStatus()
+ {
+ // Recompose strategy and implementation from
+ // the class name
+ $components = get_class($this);
+ $components = explode('\\', $components);
+
+ $name = Arr::get($components, count($components) - 1);
+ $strategy = Arr::get($components, count($components) - 2);
+
+ $parent = ucfirst($strategy);
+ $concrete = str_replace('Strategy', null, $name);
+ $details = $this->getDescription();
+
+ $this->explainer->display($parent.'/'.$concrete, $details);
+
+ return $this;
+ }
+}
diff --git a/src/Rocketeer/Bash.php b/src/Rocketeer/Bash.php
index 5cab728ed..5d660bdc4 100644
--- a/src/Rocketeer/Bash.php
+++ b/src/Rocketeer/Bash.php
@@ -9,13 +9,83 @@
*/
namespace Rocketeer;
+use Rocketeer\Traits\BashModules\Binaries;
+use Rocketeer\Traits\BashModules\Core;
+use Rocketeer\Traits\BashModules\Filesystem;
+use Rocketeer\Traits\BashModules\Flow;
+
/**
* An helper to execute low-level commands on the remote server
*
* @author Maxime Fabre
*/
-class Bash extends Traits\BashModules\Flow
+class Bash
{
- // Composite class
- // Don't ask.
+ use Core;
+ use Binaries;
+ use Filesystem;
+ use Flow;
+
+ /**
+ * @param string $hook
+ * @param array $arguments
+ *
+ * @return string|array|null
+ */
+ protected function getHookedTasks($hook, array $arguments)
+ {
+ $tasks = $this->rocketeer->getOption('strategies.'.$hook);
+ if (!is_callable($tasks)) {
+ return;
+ }
+
+ // Cancel if no tasks to execute
+ $tasks = (array) call_user_func_array($tasks, $arguments);
+ if (empty($tasks)) {
+ return;
+ }
+
+ // Run commands
+ return $tasks;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// RUNNERS ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get the implementation behind a strategy
+ *
+ * @param string $strategy
+ * @param string|null $concrete
+ *
+ * @return \Rocketeer\Abstracts\Strategies\AbstractStrategy
+ */
+ public function getStrategy($strategy, $concrete = null)
+ {
+ $strategy = $this->builder->buildStrategy($strategy, $concrete);
+ if (!$strategy || !$strategy->isExecutable()) {
+ return;
+ }
+
+ return $this->explainer->displayBelow(function () use ($strategy) {
+ return $strategy->displayStatus();
+ });
+ }
+
+ /**
+ * Execute another task by name
+ *
+ * @param string $tasks
+ *
+ * @return string|false
+ */
+ public function executeTask($tasks)
+ {
+ $results = $this->explainer->displayBelow(function () use ($tasks) {
+ return $this->builder->buildTask($tasks)->fire();
+ });
+
+ return $results;
+ }
}
diff --git a/src/Rocketeer/Binaries/AnonymousBinary.php b/src/Rocketeer/Binaries/AnonymousBinary.php
new file mode 100644
index 000000000..d601e039d
--- /dev/null
+++ b/src/Rocketeer/Binaries/AnonymousBinary.php
@@ -0,0 +1,20 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Binaries;
+
+use Rocketeer\Abstracts\AbstractBinary;
+
+/**
+ * An wrapper to execute random binaries commands
+ */
+class AnonymousBinary extends AbstractBinary
+{
+ // ...
+}
diff --git a/src/Rocketeer/Binaries/Artisan.php b/src/Rocketeer/Binaries/Artisan.php
new file mode 100644
index 000000000..5c2efdfe6
--- /dev/null
+++ b/src/Rocketeer/Binaries/Artisan.php
@@ -0,0 +1,71 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Binaries;
+
+use Illuminate\Container\Container;
+use Rocketeer\Abstracts\AbstractBinary;
+
+class Artisan extends AbstractBinary
+{
+ /**
+ * @param Container $app
+ */
+ public function __construct(Container $app)
+ {
+ parent::__construct($app);
+
+ // Set PHP as parent
+ $php = new Php($this->app);
+ $this->setParent($php);
+ }
+
+ /**
+ * Get an array of default paths to look for
+ *
+ * @return string[]
+ */
+ protected function getKnownPaths()
+ {
+ return array(
+ 'artisan',
+ $this->releasesManager->getCurrentReleasePath().'/artisan',
+ );
+ }
+
+ /**
+ * Run outstranding migrations
+ *
+ * @return string
+ */
+ public function migrate()
+ {
+ return $this->getCommand('migrate');
+ }
+
+ /**
+ * Seed the database
+ *
+ * @return string
+ */
+ public function seed()
+ {
+ return $this->getCommand('db:seed');
+ }
+
+ /**
+ * Clear the cache
+ *
+ * @return string
+ */
+ public function clearCache()
+ {
+ return $this->getCommand('cache:clear');
+ }
+}
diff --git a/src/Rocketeer/Binaries/PackageManagers/Bower.php b/src/Rocketeer/Binaries/PackageManagers/Bower.php
new file mode 100644
index 000000000..3037b9f98
--- /dev/null
+++ b/src/Rocketeer/Binaries/PackageManagers/Bower.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Binaries\PackageManagers;
+
+use Rocketeer\Abstracts\AbstractPackageManager;
+
+class Bower extends AbstractPackageManager
+{
+ /**
+ * The name of the manifest file to look for
+ *
+ * @type string
+ */
+ protected $manifest = 'bower.json';
+
+ /**
+ * Get an array of default paths to look for
+ *
+ * @return string[]
+ */
+ protected function getKnownPaths()
+ {
+ return array(
+ 'bower',
+ $this->releasesManager->getCurrentReleasePath().'/node_modules/.bin/bower',
+ );
+ }
+}
diff --git a/src/Rocketeer/Binaries/PackageManagers/Bundler.php b/src/Rocketeer/Binaries/PackageManagers/Bundler.php
new file mode 100644
index 000000000..c1875d5c6
--- /dev/null
+++ b/src/Rocketeer/Binaries/PackageManagers/Bundler.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Binaries\PackageManagers;
+
+use Rocketeer\Abstracts\AbstractPackageManager;
+
+class Bundler extends AbstractPackageManager
+{
+ /**
+ * The name of the manifest file to look for
+ *
+ * @type string
+ */
+ protected $manifest = 'Gemfile';
+
+ /**
+ * Get an array of default paths to look for
+ *
+ * @return string[]
+ */
+ protected function getKnownPaths()
+ {
+ return array(
+ 'bundle',
+ );
+ }
+}
diff --git a/src/Rocketeer/Binaries/PackageManagers/Composer.php b/src/Rocketeer/Binaries/PackageManagers/Composer.php
new file mode 100644
index 000000000..bd8bc9f67
--- /dev/null
+++ b/src/Rocketeer/Binaries/PackageManagers/Composer.php
@@ -0,0 +1,52 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Binaries\PackageManagers;
+
+use Rocketeer\Abstracts\AbstractPackageManager;
+use Rocketeer\Binaries\Php;
+
+class Composer extends AbstractPackageManager
+{
+ /**
+ * The name of the manifest file to look for
+ *
+ * @type string
+ */
+ protected $manifest = 'composer.json';
+
+ /**
+ * Get an array of default paths to look for
+ *
+ * @return string[]
+ */
+ protected function getKnownPaths()
+ {
+ return array(
+ 'composer',
+ $this->releasesManager->getCurrentReleasePath().'/composer.phar',
+ );
+ }
+
+ /**
+ * Change Composer's binary
+ *
+ * @param string $binary
+ */
+ public function setBinary($binary)
+ {
+ parent::setBinary($binary);
+
+ // Prepend PHP command if executing from archive
+ if (strpos($this->getBinary(), 'composer.phar') !== false) {
+ $php = new Php($this->app);
+ $this->setParent($php);
+ }
+ }
+}
diff --git a/src/Rocketeer/Binaries/PackageManagers/Npm.php b/src/Rocketeer/Binaries/PackageManagers/Npm.php
new file mode 100644
index 000000000..c52f647a4
--- /dev/null
+++ b/src/Rocketeer/Binaries/PackageManagers/Npm.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Binaries\PackageManagers;
+
+use Rocketeer\Abstracts\AbstractPackageManager;
+
+class Npm extends AbstractPackageManager
+{
+ /**
+ * The name of the manifest file to look for
+ *
+ * @type string
+ */
+ protected $manifest = 'package.json';
+
+ /**
+ * Get an array of default paths to look for
+ *
+ * @return string[]
+ */
+ protected function getKnownPaths()
+ {
+ return array(
+ 'npm',
+ );
+ }
+}
diff --git a/src/Rocketeer/Binaries/Php.php b/src/Rocketeer/Binaries/Php.php
new file mode 100644
index 000000000..fe78eb9a5
--- /dev/null
+++ b/src/Rocketeer/Binaries/Php.php
@@ -0,0 +1,59 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Binaries;
+
+use Rocketeer\Abstracts\AbstractBinary;
+
+class Php extends AbstractBinary
+{
+ /**
+ * Get an array of default paths to look for
+ *
+ * @return string[]
+ */
+ protected function getKnownPaths()
+ {
+ return ['php'];
+ }
+
+ /**
+ * Get the running version of PHP
+ *
+ * @return string
+ */
+ public function version()
+ {
+ return $this->getCommand(null, null, ['-r' => "print defined('HHVM_VERSION') ? HHVM_VERSION : PHP_VERSION;"]);
+ }
+
+ /**
+ * Get the installed extensions
+ *
+ * @return string
+ */
+ public function extensions()
+ {
+ return $this->getCommand(null, null, ['-m' => null]);
+ }
+
+ /**
+ * Whether this PHP installation is an HHVM one or not
+ *
+ * @return bool
+ */
+ public function isHhvm()
+ {
+ $version = $this->bash->runRaw($this->version(), true);
+ $version = head($version);
+ $version = strtolower($version);
+
+ return strpos($version, 'hiphop') !== false;
+ }
+}
diff --git a/src/Rocketeer/Binaries/Phpunit.php b/src/Rocketeer/Binaries/Phpunit.php
new file mode 100644
index 000000000..baa0adc81
--- /dev/null
+++ b/src/Rocketeer/Binaries/Phpunit.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Binaries;
+
+use Rocketeer\Abstracts\AbstractBinary;
+
+class Phpunit extends AbstractBinary
+{
+ /**
+ * Get an array of default paths to look for
+ *
+ * @return string[]
+ */
+ protected function getKnownPaths()
+ {
+ return array(
+ 'phpunit',
+ $this->releasesManager->getCurrentReleasePath().'/vendor/bin/phpunit',
+ );
+ }
+}
diff --git a/src/Rocketeer/Commands/AbstractDeployCommand.php b/src/Rocketeer/Commands/AbstractDeployCommand.php
deleted file mode 100644
index 7ee250e88..000000000
--- a/src/Rocketeer/Commands/AbstractDeployCommand.php
+++ /dev/null
@@ -1,228 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace Rocketeer\Commands;
-
-use Illuminate\Console\Command;
-use Symfony\Component\Console\Input\InputOption;
-
-/**
- * An abstract command with various helpers for all
- * subcommands to inherit
- *
- * @author Maxime Fabre
- */
-abstract class AbstractDeployCommand extends Command
-{
- /**
- * Run the tasks
- *
- * @return void
- */
- abstract public function fire();
-
- /**
- * Get the console command options.
- *
- * @return array
- */
- 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')
- );
- }
-
- /**
- * Returns the command name.
- *
- * @return string The command name
- */
- public function getName()
- {
- // Return commands without namespace if standalone
- if (!$this->isInsideLaravel()) {
- return str_replace('deploy:', null, $this->name);
- }
-
- return $this->name;
- }
-
- /**
- * Check if the current command is run in the scope of
- * Laravel or standalone
- *
- * @return boolean
- */
- public function isInsideLaravel()
- {
- return $this->laravel->bound('artisan');
- }
-
- ////////////////////////////////////////////////////////////////////
- ///////////////////////////// CORE METHODS /////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Fire a Tasks Queue
- *
- * @param string|array $tasks
- *
- * @return mixed
- */
- protected function fireTasksQueue($tasks)
- {
- // Check for credentials
- $this->getServerCredentials();
- $this->getRepositoryCredentials();
-
- // Start timer
- $timerStart = microtime(true);
-
- // Convert tasks to array if necessary
- if (!is_array($tasks)) {
- $tasks = array($tasks);
- }
-
- // Bind command to container
- $this->laravel->instance('rocketeer.command', $this);
-
- // Run tasks and display timer
- $this->laravel['rocketeer.tasks']->run($tasks, $this);
- $this->line('Execution time: '.round(microtime(true) - $timerStart, 4). 's');
-
- // Remove commmand instance
- unset($this->laravel['rocketeer.command']);
- }
-
- ////////////////////////////////////////////////////////////////////
- ///////////////////////////// CREDENTIALS //////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Get the Repository's credentials
- *
- * @return void
- */
- protected function getRepositoryCredentials()
- {
- // Check for repository credentials
- $repositoryInfos = $this->laravel['rocketeer.rocketeer']->getCredentials();
- $credentials = array('repository');
- if (!array_get($repositoryInfos, 'repository') or $this->laravel['rocketeer.rocketeer']->needsCredentials()) {
- $credentials = array('repository', 'username', 'password');
- }
-
- // Gather credentials
- foreach ($credentials as $credential) {
- ${$credential} = $this->getCredential($repositoryInfos, $credential);
- if (!${$credential}) {
- ${$credential} = $this->ask('No '.$credential. ' is set for the repository, please provide one :');
- }
- }
-
- // Save them
- $credentials = compact($credentials);
- $this->laravel['rocketeer.server']->setValue('credentials', $credentials);
- foreach ($credentials as $key => $credential) {
- $this->laravel['config']->set('rocketeer::scm.'.$key, $credential);
- }
- }
-
- /**
- * Get the Server's credentials
- *
- * @return void
- */
- protected function getServerCredentials()
- {
- if ($connections = $this->option('on')) {
- $this->laravel['rocketeer.rocketeer']->setConnections($connections);
- }
-
- // Check for configured connections
- $availableConnections = $this->laravel['rocketeer.rocketeer']->getAvailableConnections();
- $activeConnections = $this->laravel['rocketeer.rocketeer']->getConnections();
-
- if (count($activeConnections) <= 0) {
- $connectionName = $this->ask('No connections have been set, please create one : (production)', 'production');
- $this->storeServerCredentials($availableConnections, $connectionName);
- } else {
- foreach ($activeConnections as $connectionName) {
- $this->storeServerCredentials($availableConnections, $connectionName);
- }
- }
- }
-
- /**
- * Verifies and stores credentials for the given connection name
- *
- * @param string $connections
- * @param string $connectionName
- *
- * @return void
- */
- protected function storeServerCredentials($connections, $connectionName)
- {
- // Check for server credentials
- $connection = array_get($connections, $connectionName, array());
- $credentials = array(
- 'host' => true,
- 'username' => true,
- 'password' => false,
- 'keyphrase' => null,
- 'key' => false,
- 'agent' => false
- );
-
- // Gather credentials
- foreach ($credentials as $credential => $required) {
- ${$credential} = $this->getCredential($connection, $credential);
- if ($required and !${$credential}) {
- ${$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 [' .$connectionName. '], which would you use ? [key/password]', 'key');
- if ($type == 'key') {
- $default = $this->laravel['rocketeer.rocketeer']->getUserHomeFolder().'/.ssh/id_rsa';
- $key = $this->ask('Please enter the full path to your key (' .$default. ')', $default);
- $keyphrase = $this->ask('If a keyphrase is required, provide it');
- } else {
- $password = $this->ask('Please enter your password');
- }
- }
-
- // Save credentials
- $credentials = compact(array_keys($credentials));
- $this->laravel['rocketeer.rocketeer']->syncConnectionCredentials($connectionName, $credentials);
- }
-
- /**
- * Check if a credential needs to be filled
- *
- * @param array $credentials
- * @param string $credential
- *
- * @return string
- */
- protected function getCredential($credentials, $credential)
- {
- $credential = array_get($credentials, $credential);
- if (substr($credential, 0, 1) === '{') {
- return;
- }
-
- return $credential;
- }
-}
diff --git a/src/Rocketeer/Commands/BaseTaskCommand.php b/src/Rocketeer/Commands/BaseTaskCommand.php
deleted file mode 100644
index 573458729..000000000
--- a/src/Rocketeer/Commands/BaseTaskCommand.php
+++ /dev/null
@@ -1,77 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace Rocketeer\Commands;
-
-use Rocketeer\Traits\Task;
-
-/**
- * A command that wraps around a Task class and runs
- * its execute method on fire
- *
- * @author Maxime Fabre
- */
-class BaseTaskCommand extends AbstractDeployCommand
-{
- /**
- * The default name
- *
- * @var string
- */
- protected $name = 'deploy:custom';
-
- /**
- * The Task to execute on fire
- *
- * @var Task
- */
- protected $task;
-
- /**
- * Build a new custom command
- *
- * @param Task $task
- * @param string $name A name for the command
- */
- public function __construct(Task $task, $name = null)
- {
- parent::__construct();
-
- // Set Task
- $this->task = $task;
- $this->task->command = $this;
-
- // Set name
- $this->name = $name ?: $task->getSlug();
- $this->name = 'deploy:'.$this->name;
-
- // Set description
- $this->setDescription($task->getDescription());
- }
-
- /**
- * Fire the custom Task
- *
- * @return string
- */
- public function fire()
- {
- return $this->fireTasksQueue($this->task->getSlug());
- }
-
- /**
- * Get the Task this command executes
- *
- * @return Task
- */
- public function getTask()
- {
- return $this->task;
- }
-}
diff --git a/src/Rocketeer/Commands/CleanupCommand.php b/src/Rocketeer/Commands/CleanupCommand.php
deleted file mode 100644
index c098cc88b..000000000
--- a/src/Rocketeer/Commands/CleanupCommand.php
+++ /dev/null
@@ -1,56 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace Rocketeer\Commands;
-
-use Symfony\Component\Console\Input\InputOption;
-
-/**
- * Runs the Cleanup task to prune deprecated releases
- *
- * @author Maxime Fabre
- */
-class CleanupCommand extends AbstractDeployCommand
-{
- /**
- * The console command name.
- *
- * @var string
- */
- protected $name = 'deploy:cleanup';
-
- /**
- * The console command description.
- *
- * @var string
- */
- protected $description = 'Clean up old releases from the server.';
-
- /**
- * Execute the tasks
- *
- * @return array
- */
- public function fire()
- {
- return $this->fireTasksQueue('cleanup');
- }
-
- /**
- * Get the console command options.
- *
- * @return array
- */
- protected function getOptions()
- {
- return array_merge(parent::getOptions(), array(
- array('clean-all', null, InputOption::VALUE_NONE, 'Cleans up all non-current releases'),
- ));
- }
-}
diff --git a/src/Rocketeer/Commands/TestCommand.php b/src/Rocketeer/Commands/TestCommand.php
deleted file mode 100644
index 8c1edfeac..000000000
--- a/src/Rocketeer/Commands/TestCommand.php
+++ /dev/null
@@ -1,44 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace Rocketeer\Commands;
-
-/**
- * Run the tests on the server and displays the ouput
- *
- * @author Maxime Fabre
- */
-class TestCommand extends AbstractDeployCommand
-{
- /**
- * The console command name.
- *
- * @var string
- */
- protected $name = 'deploy:test';
-
- /**
- * The console command description.
- *
- * @var string
- */
- protected $description = 'Run the tests on the server and displays the output';
-
- /**
- * The tasks to execute
- *
- * @return array
- */
- public function fire()
- {
- $this->input->setOption('verbose', true);
-
- return $this->fireTasksQueue('test');
- }
-}
diff --git a/src/Rocketeer/Commands/UpdateCommand.php b/src/Rocketeer/Commands/UpdateCommand.php
deleted file mode 100644
index 0aeb9ec6e..000000000
--- a/src/Rocketeer/Commands/UpdateCommand.php
+++ /dev/null
@@ -1,57 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace Rocketeer\Commands;
-
-use Symfony\Component\Console\Input\InputOption;
-
-/**
- * Update the remote server without doing a new release
- *
- * @author Maxime Fabre
- */
-class UpdateCommand extends AbstractDeployCommand
-{
- /**
- * The console command name.
- *
- * @var string
- */
- protected $name = 'deploy:update';
-
- /**
- * The console command description.
- *
- * @var string
- */
- protected $description = 'Update the remote server without doing a new release.';
-
- /**
- * Execute the tasks
- *
- * @return array
- */
- public function fire()
- {
- return $this->fireTasksQueue('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'),
- ));
- }
-}
diff --git a/src/Rocketeer/Console/Commands/BaseTaskCommand.php b/src/Rocketeer/Console/Commands/BaseTaskCommand.php
new file mode 100644
index 000000000..1013711b6
--- /dev/null
+++ b/src/Rocketeer/Console/Commands/BaseTaskCommand.php
@@ -0,0 +1,56 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Console\Commands;
+
+use Rocketeer\Abstracts\AbstractCommand;
+use Rocketeer\Abstracts\AbstractTask;
+
+/**
+ * A command that wraps around a task class and runs
+ * its execute method on fire
+ *
+ * @author Maxime Fabre
+ */
+class BaseTaskCommand extends AbstractCommand
+{
+ /**
+ * The default name
+ *
+ * @var string
+ */
+ protected $name = 'deploy:custom';
+
+ /**
+ * Build a new custom command
+ *
+ * @param AbstractTask|null $task
+ * @param string|null $name A name for the command
+ */
+ public function __construct(AbstractTask $task = null, $name = null)
+ {
+ parent::__construct($task);
+
+ // Set name
+ if ($this->name == 'deploy:custom' && $task) {
+ $this->name = $name ?: $task->getSlug();
+ $this->name = 'deploy:'.$this->name;
+ }
+ }
+
+ /**
+ * Fire the custom Task
+ *
+ * @return integer
+ */
+ public function fire()
+ {
+ return $this->fireTasksQueue($this->task->getSlug());
+ }
+}
diff --git a/src/Rocketeer/Console/Commands/CleanupCommand.php b/src/Rocketeer/Console/Commands/CleanupCommand.php
new file mode 100644
index 000000000..03d8a064a
--- /dev/null
+++ b/src/Rocketeer/Console/Commands/CleanupCommand.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Console\Commands;
+
+use Symfony\Component\Console\Input\InputOption;
+
+/**
+ * Runs the Cleanup task to prune deprecated releases
+ *
+ * @author Maxime Fabre
+ */
+class CleanupCommand extends BaseTaskCommand
+{
+ /**
+ * Get the console command options.
+ *
+ * @return array>
+ */
+ protected function getOptions()
+ {
+ return array_merge(parent::getOptions(), array(
+ ['clean-all', null, InputOption::VALUE_NONE, 'Cleans up all non-current releases'],
+ ));
+ }
+}
diff --git a/src/Rocketeer/Commands/DeployCommand.php b/src/Rocketeer/Console/Commands/DeployCommand.php
similarity index 53%
rename from src/Rocketeer/Commands/DeployCommand.php
rename to src/Rocketeer/Console/Commands/DeployCommand.php
index b0157cad4..76575d5fb 100644
--- a/src/Rocketeer/Commands/DeployCommand.php
+++ b/src/Rocketeer/Console/Commands/DeployCommand.php
@@ -7,7 +7,7 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
-namespace Rocketeer\Commands;
+namespace Rocketeer\Console\Commands;
use Symfony\Component\Console\Input\InputOption;
@@ -16,26 +16,19 @@
*
* @author Maxime Fabre
*/
-class DeployCommand extends AbstractDeployCommand
+class DeployCommand extends BaseTaskCommand
{
/**
- * The console command name.
+ * The default name
*
* @var string
*/
protected $name = 'deploy:deploy';
- /**
- * The console command description.
- *
- * @var string
- */
- protected $description = 'Deploy the website.';
-
/**
* Execute the tasks
*
- * @return array
+ * @return integer
*/
public function fire()
{
@@ -48,15 +41,15 @@ public function fire()
/**
* Get the console command options.
*
- * @return array
+ * @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 it if --migrate)'),
- array('clean-all', null, InputOption::VALUE_NONE, 'Cleanup all but the current release on deploy'),
+ ['tests', 't', InputOption::VALUE_NONE, 'Runs the tests on deploy'],
+ ['migrate', 'm', InputOption::VALUE_NONE, 'Run the migrations'],
+ ['seed', 's', InputOption::VALUE_NONE, 'Seed the database (after migrating it if --migrate)'],
+ ['clean-all', null, InputOption::VALUE_NONE, 'Cleanup all but the current release on deploy'],
));
}
}
diff --git a/src/Rocketeer/Commands/FlushCommand.php b/src/Rocketeer/Console/Commands/FlushCommand.php
similarity index 73%
rename from src/Rocketeer/Commands/FlushCommand.php
rename to src/Rocketeer/Console/Commands/FlushCommand.php
index 7339c7255..83cc3b902 100644
--- a/src/Rocketeer/Commands/FlushCommand.php
+++ b/src/Rocketeer/Console/Commands/FlushCommand.php
@@ -7,14 +7,16 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
-namespace Rocketeer\Commands;
+namespace Rocketeer\Console\Commands;
+
+use Rocketeer\Abstracts\AbstractCommand;
/**
* Flushes any custom storage Rocketeer has created
*
* @author Maxime Fabre
*/
-class FlushCommand extends AbstractDeployCommand
+class FlushCommand extends AbstractCommand
{
/**
* The console command name.
@@ -33,11 +35,15 @@ class FlushCommand extends AbstractDeployCommand
/**
* Execute the tasks
*
- * @return array
+ * @return integer
*/
public function fire()
{
- $this->laravel['rocketeer.server']->deleteRepository();
+ // Clear the cache of credentials
+ $this->laravel['rocketeer.storage.local']->destroy();
+
$this->info("Rocketeer's cache has been properly flushed");
+
+ return 0;
}
}
diff --git a/src/Rocketeer/Console/Commands/IgniteCommand.php b/src/Rocketeer/Console/Commands/IgniteCommand.php
new file mode 100644
index 000000000..2ec3f99c4
--- /dev/null
+++ b/src/Rocketeer/Console/Commands/IgniteCommand.php
@@ -0,0 +1,13 @@
+fireTasksQueue('Plugins\Installer');
+ }
+
+ /**
+ * Get the console command arguments.
+ *
+ * @return string[][]
+ */
+ protected function getArguments()
+ {
+ return array(
+ ['package', InputArgument::REQUIRED, 'The package to publish the configuration for'],
+ );
+ }
+}
diff --git a/src/Rocketeer/Console/Commands/Plugins/ListCommand.php b/src/Rocketeer/Console/Commands/Plugins/ListCommand.php
new file mode 100644
index 000000000..14c96891f
--- /dev/null
+++ b/src/Rocketeer/Console/Commands/Plugins/ListCommand.php
@@ -0,0 +1,46 @@
+laravel['rocketeer.tasks']->getRegisteredPlugins();
+ foreach ($plugins as $plugin => $instance) {
+ $rows[] = [$plugin];
+ }
+
+ $this->table(['Plugin'], $rows);
+ }
+}
diff --git a/src/Rocketeer/Console/Commands/Plugins/PublishCommand.php b/src/Rocketeer/Console/Commands/Plugins/PublishCommand.php
new file mode 100644
index 000000000..e321ac6ef
--- /dev/null
+++ b/src/Rocketeer/Console/Commands/Plugins/PublishCommand.php
@@ -0,0 +1,54 @@
+laravel);
+ $publisher->publish($this->argument('package'));
+ }
+
+ /**
+ * Get the console command arguments.
+ *
+ * @return string[][]
+ */
+ protected function getArguments()
+ {
+ return array(
+ ['package', InputArgument::REQUIRED, 'The package to publish the configuration for'],
+ );
+ }
+}
diff --git a/src/Rocketeer/Commands/RocketeerCommand.php b/src/Rocketeer/Console/Commands/RocketeerCommand.php
similarity index 93%
rename from src/Rocketeer/Commands/RocketeerCommand.php
rename to src/Rocketeer/Console/Commands/RocketeerCommand.php
index 42eade36d..ae7daf1b5 100644
--- a/src/Rocketeer/Commands/RocketeerCommand.php
+++ b/src/Rocketeer/Console/Commands/RocketeerCommand.php
@@ -7,7 +7,7 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
-namespace Rocketeer\Commands;
+namespace Rocketeer\Console\Commands;
use Rocketeer\Rocketeer;
@@ -27,8 +27,6 @@ class RocketeerCommand extends DeployCommand
/**
* Displays the current version
- *
- * @return string
*/
public function fire()
{
diff --git a/src/Rocketeer/Commands/RollbackCommand.php b/src/Rocketeer/Console/Commands/RollbackCommand.php
similarity index 51%
rename from src/Rocketeer/Commands/RollbackCommand.php
rename to src/Rocketeer/Console/Commands/RollbackCommand.php
index c3ae0ad40..145e42568 100644
--- a/src/Rocketeer/Commands/RollbackCommand.php
+++ b/src/Rocketeer/Console/Commands/RollbackCommand.php
@@ -7,63 +7,39 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
-namespace Rocketeer\Commands;
+namespace Rocketeer\Console\Commands;
-use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputOption;
/**
* Rollback to the previous release, or to a specific one
*
* @author Maxime Fabre
*/
-class RollbackCommand extends AbstractDeployCommand
+class RollbackCommand extends BaseTaskCommand
{
- /**
- * The console command name.
- *
- * @var string
- */
- protected $name = 'deploy:rollback';
-
- /**
- * The console command description.
- *
- * @var string
- */
- protected $description = 'Rollback to the previous release, or to a specific one';
-
- /**
- * The tasks to execute
- *
- * @return array
- */
- public function fire()
- {
- return $this->fireTasksQueue('rollback');
- }
-
/**
* Get the console command arguments.
*
- * @return array
+ * @return string[][]
*/
protected function getArguments()
{
return array(
- array('release', InputArgument::OPTIONAL, 'The release to rollback to'),
+ ['release', InputArgument::OPTIONAL, 'The release to rollback to'],
);
}
/**
* Get the console command options.
*
- * @return array
+ * @return array>
*/
protected function getOptions()
{
return array_merge(parent::getOptions(), array(
- array('list', 'L', InputOption::VALUE_NONE, 'Shows the available releases to rollback to'),
+ ['list', 'L', InputOption::VALUE_NONE, 'Shows the available releases to rollback to'],
));
}
}
diff --git a/src/Rocketeer/Console/Commands/StrategiesCommand.php b/src/Rocketeer/Console/Commands/StrategiesCommand.php
new file mode 100644
index 000000000..f332ce1fb
--- /dev/null
+++ b/src/Rocketeer/Console/Commands/StrategiesCommand.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 Rocketeer\Console\Commands;
+
+use Rocketeer\Abstracts\AbstractCommand;
+use Symfony\Component\Console\Helper\Table;
+
+/**
+ * Lists the available options for each strategy
+ *
+ * @author Maxime Fabre
+ */
+class StrategiesCommand extends AbstractCommand
+{
+ /**
+ * The console command name.
+ *
+ * @var string
+ */
+ protected $name = 'deploy:strategies';
+
+ /**
+ * The console command description.
+ *
+ * @var string
+ */
+ protected $description = 'Lists the available options for each strategy';
+
+ /**
+ * Run the tasks
+ *
+ * @return void
+ */
+ public function fire()
+ {
+ $strategies = array(
+ 'check' => ['Php', 'Ruby', 'Node'],
+ 'deploy' => ['Clone', 'Copy', 'Sync'],
+ 'test' => ['Phpunit'],
+ 'migrate' => ['Artisan'],
+ 'dependencies' => ['Composer', 'Bundler', 'Npm', 'Bower', 'Polyglot'],
+ );
+
+ $rows = [];
+ foreach ($strategies as $strategy => $implementations) {
+ foreach ($implementations as $implementation) {
+ $instance = $this->laravel['rocketeer.builder']->buildStrategy($strategy, $implementation);
+ $rows[] = [$strategy, $implementation, $instance->getDescription()];
+ }
+ }
+
+ $this->table(['Strategy', 'Implementation', 'Description'], $rows);
+ }
+}
diff --git a/src/Rocketeer/Console/Commands/UpdateCommand.php b/src/Rocketeer/Console/Commands/UpdateCommand.php
new file mode 100644
index 000000000..e29bb1993
--- /dev/null
+++ b/src/Rocketeer/Console/Commands/UpdateCommand.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Console\Commands;
+
+use Symfony\Component\Console\Input\InputOption;
+
+/**
+ * Update the remote server without doing a new release
+ *
+ * @author Maxime Fabre
+ */
+class UpdateCommand extends BaseTaskCommand
+{
+ /**
+ * Get the console command options.
+ *
+ * @return array>
+ */
+ protected function getOptions()
+ {
+ return array_merge(parent::getOptions(), array(
+ ['migrate', 'm', InputOption::VALUE_NONE, 'Run the migrations'],
+ ['seed', 's', InputOption::VALUE_NONE, 'Seed the database after migrating the database'],
+ ));
+ }
+}
diff --git a/src/Rocketeer/Console/Compiler.php b/src/Rocketeer/Console/Compiler.php
index 815a54f71..1fd267068 100644
--- a/src/Rocketeer/Console/Compiler.php
+++ b/src/Rocketeer/Console/Compiler.php
@@ -56,8 +56,6 @@ public function __construct()
* Extract an existing Phar
*
* @param string $destination
- *
- * @return void
*/
public function extract($destination)
{
@@ -99,9 +97,10 @@ public function compile()
// Add core files and dependencies
$this->addFolder($src);
$this->addFolder($vendor, array(
- 'mockery',
- 'patchwork',
+ 'd11wtq',
'herrera-io',
+ 'johnkary',
+ 'mockery',
'nesbot',
'phine',
));
@@ -123,33 +122,31 @@ public function compile()
/**
* Set the stub to use
- *
- * @return string
*/
protected function setStub()
{
- $this->box->getPhar()->setStub(
- StubGenerator::create()
- ->index('bin/rocketeer')
- ->generate()
- );
+ $stub = StubGenerator::create()
+ ->index('bin/rocketeer')
+ ->generate();
+
+ $this->box->getPhar()->setStub($stub);
}
/**
* Add a folder to the PHAR
*
- * @param string $folder
- * @param array $ignore
+ * @param string $folder
+ * @param string[] $ignore
*
- * @return array
+ * @return string[]
*/
protected function addFolder($folder, array $ignore = array())
{
$finder = new Finder();
$finder = $finder->files()
- ->ignoreVCS(true)
- ->name('*.php')
- ->in($folder);
+ ->ignoreVCS(true)
+ ->name('*.php')
+ ->in($folder);
// Ignore some files or folders
if ($ignore) {
diff --git a/src/Rocketeer/Console/Console.php b/src/Rocketeer/Console/Console.php
index 2e17e9f6a..830259645 100644
--- a/src/Rocketeer/Console/Console.php
+++ b/src/Rocketeer/Console/Console.php
@@ -25,26 +25,24 @@ class Console extends Application
*/
public function getHelp()
{
- $help = str_replace($this->getLongVersion(), null, parent::getHelp());
+ $help = str_replace($this->getLongVersion(), null, parent::getHelp());
+ $state = $this->buildBlock('Current state', $this->getCurrentState());
+ $help = sprintf('%s'.PHP_EOL.PHP_EOL.'%s%s', $this->getLongVersion(), $state, $help);
- return
- $this->getLongVersion().
- PHP_EOL.PHP_EOL.
- $this->buildBlock('Current state', $this->getCurrentState()).
- $help;
+ return $help;
}
/**
* Build an help block
*
- * @param string $title
- * @param array $informations
+ * @param string $title
+ * @param string[] $informations
*
* @return string
*/
protected function buildBlock($title, $informations)
{
- $message = '' .$title. '';
+ $message = ''.$title.'';
foreach ($informations as $name => $info) {
$message .= PHP_EOL.sprintf(' %-15s %s', $name, $info);
}
@@ -55,7 +53,7 @@ protected function buildBlock($title, $informations)
/**
* Get current state of the CLI
*
- * @return array
+ * @return string[]
*/
protected function getCurrentState()
{
diff --git a/src/Rocketeer/Console/WhitespaceCompactor.php b/src/Rocketeer/Console/WhitespaceCompactor.php
index 418c054cb..b264c11df 100644
--- a/src/Rocketeer/Console/WhitespaceCompactor.php
+++ b/src/Rocketeer/Console/WhitespaceCompactor.php
@@ -29,6 +29,6 @@ class WhitespaceCompactor extends Php
*/
public function supports($file)
{
- return dirname($file) !== 'src/config' and parent::supports($file);
+ return dirname($file) !== 'src/config' && parent::supports($file);
}
}
diff --git a/src/Rocketeer/Exceptions/ConnectionException.php b/src/Rocketeer/Exceptions/ConnectionException.php
new file mode 100644
index 000000000..7302cb210
--- /dev/null
+++ b/src/Rocketeer/Exceptions/ConnectionException.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Exceptions;
+
+use Exception;
+
+/**
+ * Exception when Rocketeer can't connect to a server
+ *
+ * @author Maxime Fabre
+ */
+class ConnectionException extends Exception
+{
+ /**
+ * Set the credentials that failed to connect
+ *
+ * @param array $credentials
+ */
+ public function setCredentials(array $credentials)
+ {
+ $this->message .= PHP_EOL.'With credentials:'.PHP_EOL.json_encode($credentials);
+ }
+}
diff --git a/src/Rocketeer/Exceptions/MissingCredentialsException.php b/src/Rocketeer/Exceptions/MissingCredentialsException.php
new file mode 100644
index 000000000..52b8f5b6b
--- /dev/null
+++ b/src/Rocketeer/Exceptions/MissingCredentialsException.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Exceptions;
+
+use InvalidArgumentException;
+
+class MissingCredentialsException extends InvalidArgumentException
+{
+ // ...
+}
diff --git a/src/Rocketeer/Exceptions/TaskCompositionException.php b/src/Rocketeer/Exceptions/TaskCompositionException.php
new file mode 100644
index 000000000..6729f2d22
--- /dev/null
+++ b/src/Rocketeer/Exceptions/TaskCompositionException.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Exceptions;
+
+use Exception;
+
+class TaskCompositionException extends Exception
+{
+ // ...
+}
diff --git a/src/Rocketeer/Facades/Console.php b/src/Rocketeer/Facades/Console.php
index 3a5ba9927..38f1979a8 100644
--- a/src/Rocketeer/Facades/Console.php
+++ b/src/Rocketeer/Facades/Console.php
@@ -13,8 +13,7 @@
* Facade for Rocketeer's CLI
*
* @author Maxime Fabre
- *
- * @see Rocketeer\Console\Console
+ * @see Rocketeer\Console\Console
*/
class Console extends StandaloneFacade
{
diff --git a/src/Rocketeer/Facades/Rocketeer.php b/src/Rocketeer/Facades/Rocketeer.php
index aaa672ebd..02c19b782 100644
--- a/src/Rocketeer/Facades/Rocketeer.php
+++ b/src/Rocketeer/Facades/Rocketeer.php
@@ -13,8 +13,7 @@
* Facade for Rocketeer's CLI
*
* @author Maxime Fabre
- *
- * @see Rocketeer\TasksQueue
+ * @see Rocketeer\TasksQueue
*/
class Rocketeer extends StandaloneFacade
{
diff --git a/src/Rocketeer/Facades/StandaloneFacade.php b/src/Rocketeer/Facades/StandaloneFacade.php
index 84a476ce4..347cdb137 100644
--- a/src/Rocketeer/Facades/StandaloneFacade.php
+++ b/src/Rocketeer/Facades/StandaloneFacade.php
@@ -9,6 +9,7 @@
*/
namespace Rocketeer\Facades;
+use Illuminate\Container\Container;
use Illuminate\Support\Facades\Facade;
use Rocketeer\RocketeerServiceProvider;
@@ -16,8 +17,7 @@
* Facade for Rocketeer's CLI
*
* @author Maxime Fabre
- *
- * @see Rocketeer\Console\Console
+ * @see Rocketeer\Console\Console
*/
abstract class StandaloneFacade extends Facade
{
@@ -36,7 +36,11 @@ abstract class StandaloneFacade extends Facade
protected static function getFacadeAccessor()
{
if (!static::$app) {
- static::$app = RocketeerServiceProvider::make();
+ $container = new Container();
+ $provider = new RocketeerServiceProvider($container);
+ $provider->boot();
+
+ static::$app = $container;
}
return static::$accessor;
diff --git a/src/Rocketeer/Igniter.php b/src/Rocketeer/Igniter.php
deleted file mode 100644
index ba245bce2..000000000
--- a/src/Rocketeer/Igniter.php
+++ /dev/null
@@ -1,213 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace Rocketeer;
-
-use Illuminate\Container\Container;
-
-/**
- * Finds configurations and paths
- *
- * @author Maxime Fabre
- */
-class Igniter
-{
- /**
- * The Container
- *
- * @var Container
- */
- protected $app;
-
- /**
- * Build a new Igniter
- *
- * @param Container $app
- */
- public function __construct(Container $app)
- {
- $this->app = $app;
- }
-
- /**
- * Bind paths to the container
- *
- * @return void
- */
- public function bindPaths()
- {
- $this->bindBase();
- $this->bindConfiguration();
- }
-
- ////////////////////////////////////////////////////////////////////
- /////////////////////////////// IGNITION ///////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Get the path to the configuration folder
- *
- * @return string
- */
- public function getConfigurationPath()
- {
- // Return path to Laravel configuration
- if ($this->app->bound('path')) {
- $laravel = $this->app['path'].'/config/packages/anahkiasen/rocketeer';
- if (file_exists($laravel)) {
- return $laravel;
- }
- }
-
- return $this->app['path.rocketeer.config'];
- }
-
- /**
- * Export the configuration files
- *
- * @return void
- */
- public function exportConfiguration()
- {
- $source = __DIR__.'/../config';
- $destination = $this->getConfigurationPath();
-
- // Unzip configuration files
- $this->app['files']->copyDirectory($source, $destination);
-
- return $destination;
- }
-
- /**
- * Replace placeholders in configuration
- *
- * @param string $folder
- * @param array $values
- *
- * @return void
- */
- public function updateConfiguration($folder, array $values = array())
- {
- // Replace stub values in files
- $files = $this->app['files']->files($folder);
- foreach ($files as $file) {
- foreach ($values as $name => $value) {
- $contents = str_replace('{' .$name. '}', $value, file_get_contents($file));
- $this->app['files']->put($file, $contents);
- }
- }
-
- // Change repository in use
- $application = array_get($values, 'application_name');
- $this->app['rocketeer.server']->setRepository($application);
- }
-
- ////////////////////////////////////////////////////////////////////
- //////////////////////////////// PATHS /////////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Bind the base path to the Container
- *
- * @return void
- */
- protected function bindBase()
- {
- if ($this->app->bound('path.base')) {
- return;
- }
-
- $this->app->instance('path.base', getcwd());
- }
-
- /**
- * Bind paths to the configuration files
- *
- * @return void
- */
- protected function bindConfiguration()
- {
- $path = $this->getBasePath();
- $logs = $this->getStoragePath();
-
- // Prepare the paths to bind
- $paths = array(
- 'config' => '.rocketeer',
- 'events' => '.rocketeer/events',
- 'tasks' => '.rocketeer/tasks',
- 'logs' => $logs.'/logs',
- );
-
- foreach ($paths as $key => $file) {
- $filename = $path.$file;
-
- // Check whether we provided a file or folder
- if (!is_dir($filename) and file_exists($filename.'.php')) {
- $filename .= '.php';
- }
-
- // Use configuration in current folder if none found
- $realpath = realpath('.').'/'.$file;
- if (!file_exists($filename) and file_exists($realpath)) {
- $filename = $realpath;
- }
-
- $this->app->instance('path.rocketeer.'.$key, $filename);
- }
- }
-
- ////////////////////////////////////////////////////////////////////
- /////////////////////////////// HELPERS ////////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Get the base path
- *
- * @return string
- */
- protected function getBasePath()
- {
- $base = $this->app['path.base'] ? $this->app['path.base'].'/' : '';
- $base = $this->unifySlashes($base);
-
- return $base;
- }
-
- /**
- * Get path to the storage folder
- *
- * @return string
- */
- protected function getStoragePath()
- {
- // If no path is bound, default to the Rocketeer folder
- if (!$this->app->bound('path.storage')) {
- return '.rocketeer';
- }
-
- // Unify slashes
- $storage = $this->app['path.storage'];
- $storage = $this->unifySlashes($storage);
- $storage = str_replace($this->getBasePath(), null, $storage);
-
- return $storage;
- }
-
- /**
- * Unify the slashes to the UNIX mode (forward slashes)
- *
- * @param string $path
- *
- * @return string
- */
- protected function unifySlashes($path)
- {
- return str_replace('\\', '/', $path);
- }
-}
diff --git a/src/Rocketeer/Scm/ScmInterface.php b/src/Rocketeer/Interfaces/ScmInterface.php
similarity index 86%
rename from src/Rocketeer/Scm/ScmInterface.php
rename to src/Rocketeer/Interfaces/ScmInterface.php
index 72ad1fe15..22a97996b 100644
--- a/src/Rocketeer/Scm/ScmInterface.php
+++ b/src/Rocketeer/Interfaces/ScmInterface.php
@@ -7,7 +7,7 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
-namespace Rocketeer\Scm;
+namespace Rocketeer\Interfaces;
/**
* The interface for all SCM implementations
@@ -16,6 +16,13 @@
*/
interface ScmInterface
{
+ /**
+ * Get the current binary name
+ *
+ * @return string
+ */
+ public function getBinary();
+
/**
* Check if the SCM is available
*
@@ -40,7 +47,7 @@ public function currentBranch();
/**
* Clone a repository
*
- * @param string $destination
+ * @param string $destination
*
* @return string
*/
diff --git a/src/Rocketeer/Interfaces/StorageInterface.php b/src/Rocketeer/Interfaces/StorageInterface.php
new file mode 100644
index 000000000..f926a30a7
--- /dev/null
+++ b/src/Rocketeer/Interfaces/StorageInterface.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Interfaces;
+
+interface StorageInterface
+{
+ /**
+ * Get a value
+ *
+ * @param string|null $key
+ * @param string|null $fallback
+ *
+ * @return mixed
+ */
+ public function get($key = null, $fallback = null);
+
+ /**
+ * Set a value
+ *
+ * @param string|array $key
+ * @param mixed|null $value
+ *
+ * @return void
+ */
+ public function set($key, $value = null);
+
+ /**
+ * Forget a value
+ *
+ * @param string $key
+ *
+ * @return void
+ */
+ public function forget($key);
+
+ /**
+ * Destroy the file
+ *
+ * @return boolean
+ */
+ public function destroy();
+}
diff --git a/src/Rocketeer/Interfaces/Strategies/CheckStrategyInterface.php b/src/Rocketeer/Interfaces/Strategies/CheckStrategyInterface.php
new file mode 100644
index 000000000..6e297ac8c
--- /dev/null
+++ b/src/Rocketeer/Interfaces/Strategies/CheckStrategyInterface.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Interfaces\Strategies;
+
+interface DependenciesStrategyInterface
+{
+ /**
+ * Install the dependencies
+ *
+ * @return boolean
+ */
+ public function install();
+
+ /**
+ * Update the dependencies
+ *
+ * @return boolean
+ */
+ public function update();
+}
diff --git a/src/Rocketeer/Interfaces/Strategies/DeployStrategyInterface.php b/src/Rocketeer/Interfaces/Strategies/DeployStrategyInterface.php
new file mode 100644
index 000000000..573e507b2
--- /dev/null
+++ b/src/Rocketeer/Interfaces/Strategies/DeployStrategyInterface.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Interfaces\Strategies;
+
+/**
+ * Interface for the various deployment strategies
+ *
+ * @author Maxime Fabre
+ */
+interface DeployStrategyInterface
+{
+ /**
+ * Deploy a new clean copy of the application
+ *
+ * @param string|null $destination
+ *
+ * @return boolean
+ */
+ public function deploy($destination = null);
+
+ /**
+ * Update the latest version of the application
+ *
+ * @param boolean $reset
+ *
+ * @return boolean
+ */
+ public function update($reset = true);
+}
diff --git a/src/Rocketeer/Interfaces/Strategies/MigrateStrategyInterface.php b/src/Rocketeer/Interfaces/Strategies/MigrateStrategyInterface.php
new file mode 100644
index 000000000..dfef20b24
--- /dev/null
+++ b/src/Rocketeer/Interfaces/Strategies/MigrateStrategyInterface.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Interfaces\Strategies;
+
+/**
+ * Interface for the various migration strategies
+ *
+ * @author Maxime Fabre
+ */
+interface MigrateStrategyInterface
+{
+ /**
+ * Run outstanding migrations
+ *
+ * @return boolean
+ */
+ public function migrate();
+
+ /**
+ * Seed the database
+ *
+ * @return boolean
+ */
+ public function seed();
+}
diff --git a/src/Rocketeer/Interfaces/Strategies/TestStrategyInterface.php b/src/Rocketeer/Interfaces/Strategies/TestStrategyInterface.php
new file mode 100644
index 000000000..2d46a5b39
--- /dev/null
+++ b/src/Rocketeer/Interfaces/Strategies/TestStrategyInterface.php
@@ -0,0 +1,20 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Interfaces\Strategies;
+
+interface TestStrategyInterface
+{
+ /**
+ * Run the tests
+ *
+ * @return boolean
+ */
+ public function test();
+}
diff --git a/src/Rocketeer/LogsHandler.php b/src/Rocketeer/LogsHandler.php
deleted file mode 100644
index 65766c2c8..000000000
--- a/src/Rocketeer/LogsHandler.php
+++ /dev/null
@@ -1,132 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace Rocketeer;
-
-use Illuminate\Container\Container;
-
-/**
- * Handles rotation of logs
- */
-class LogsHandler
-{
- /**
- * The loggers instances
- *
- * @var array
- */
- protected $loggers = array();
-
- /**
- * The Container
- *
- * @var Container
- */
- protected $app;
-
- /**
- * Build a new LogsHandler instance
- *
- * @param Container $app
- */
- public function __construct(Container $app)
- {
- $this->app = $app;
- }
-
- /**
- * Log by level
- *
- * @param string $method
- * @param array $parameters
- *
- * @return void
- */
- public function __call($method, $parameters)
- {
- return $this->log($parameters[0], $method);
- }
-
- /**
- * Log a piece of text
- *
- * @param string $informations
- * @param string $level
- *
- * @return void
- */
- public function log($informations, $level = 'info')
- {
- if ($file = $this->getCurrentLogsFile()) {
- return $this->getLogger($file)->$level($informations);
- }
- }
-
- /**
- * Get the logs file being currently used
- *
- * @return string
- */
- public function getCurrentLogsFile()
- {
- $logs = $this->app['config']->get('rocketeer::logs');
- if (!$logs) {
- return;
- }
-
- $file = $logs($this->app['rocketeer.rocketeer']);
- $file = $this->app['path.rocketeer.logs'].'/'.$file;
-
- return $file;
- }
-
- /**
- * Get a logger instance by context
- *
- * @param string $file
- *
- * @return Illuminate\Log\Writer
- */
- protected function getLogger($file)
- {
- // Create logger instance if necessary
- if (!array_key_exists($file, $this->loggers)) {
- $this->createLogsFile($file);
-
- // Store specific logger instance
- $logger = clone $this->app['log'];
- $logger->useFiles($file);
- $this->loggers[$file] = $logger;
- }
-
- return $this->loggers[$file];
- }
-
- /**
- * Create a logs file if it doesn't exist
- *
- * @param string $file
- *
- * @return void
- */
- protected function createLogsFile($file)
- {
- $directory = dirname($file);
-
- // Create directory
- if (!is_dir($directory)) {
- $this->app['files']->makeDirectory($directory, 0777, true);
- }
-
- // Create file
- if (!file_exists($file)) {
- $this->app['files']->put($file, '');
- }
- }
-}
diff --git a/src/Rocketeer/Plugins/AbstractNotifier.php b/src/Rocketeer/Plugins/AbstractNotifier.php
new file mode 100644
index 000000000..af12d5241
--- /dev/null
+++ b/src/Rocketeer/Plugins/AbstractNotifier.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Plugins;
+
+use Rocketeer\Abstracts\AbstractPlugin;
+use Rocketeer\Services\TasksHandler;
+use Rocketeer\Tasks\Subtasks\Notify;
+
+/**
+ * A base class for notification services to extends
+ */
+abstract class AbstractNotifier extends AbstractPlugin
+{
+ /**
+ * Register Tasks with Rocketeer
+ *
+ * @param \Rocketeer\Services\TasksHandler $queue
+ *
+ * @return void
+ */
+ public function onQueue(TasksHandler $queue)
+ {
+ // Create the task instance
+ $notify = new Notify($this->app);
+ $notify->setNotifier($this);
+
+ $queue->addTaskListeners('deploy', 'before', [clone $notify], -10, true);
+ $queue->addTaskListeners('deploy', 'after', [clone $notify], -10, true);
+ }
+
+ /**
+ * Send a given message
+ *
+ * @param string $message
+ *
+ * @return void
+ */
+ abstract public function send($message);
+
+ /**
+ * Get the default message format
+ *
+ * @param string $message The message handle
+ *
+ * @return string
+ */
+ abstract public function getMessageFormat($message);
+}
diff --git a/src/Rocketeer/Plugins/Notifier.php b/src/Rocketeer/Plugins/Notifier.php
deleted file mode 100644
index 39dcc1777..000000000
--- a/src/Rocketeer/Plugins/Notifier.php
+++ /dev/null
@@ -1,115 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace Rocketeer\Plugins;
-
-use Rocketeer\Traits\Plugin;
-use Rocketeer\TasksHandler;
-
-/**
- * A base class for notification services to extends
- */
-abstract class Notifier extends Plugin
-{
- /**
- * Register Tasks with Rocketeer
- *
- * @param TasksHandler $queue
- *
- * @return void
- */
- public function onQueue(TasksHandler $queue)
- {
- $me = $this;
-
- $queue->before('deploy', function ($task) use ($me) {
- $me->prepareThenSend($task, 'before_deploy');
- }, -10);
-
- $queue->after('deploy', function ($task) use ($me) {
- $me->prepareThenSend($task, 'after_deploy');
- }, -10);
- }
-
- /**
- * Send a given message
- *
- * @param Task $task
- * @param string $message
- *
- * @return void
- */
- abstract public function send($message);
-
- ////////////////////////////////////////////////////////////////////
- /////////////////////////////// MESSAGE ////////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Get the message's components
- *
- * @return array
- */
- protected function getComponents()
- {
- // Get user name
- $user = $this->server->getValue('notifier.name');
- if (!$user) {
- $user = $this->command->ask('Who is deploying ?');
- $this->server->setValue('notifier.name', $user);
- }
-
- // Get what was deployed
- $branch = $this->rocketeer->getRepositoryBranch();
- $stage = $this->rocketeer->getStage();
- $connection = $this->rocketeer->getConnection();
-
- // Get hostname
- $credentials = array_get($this->rocketeer->getAvailableConnections(), $connection);
- $host = array_get($credentials, 'host');
- if ($stage) {
- $connection = $stage.'@'.$connection;
- }
-
- return compact('user', 'branch', 'connection', 'host');
- }
-
- /**
- * Get the default message format
- *
- * @param string $message The message handle
- *
- * @return string
- */
- abstract protected function getMessageFormat($message);
-
- /**
- * Prepare and send a message
- *
- * @param Task $task
- * @param string $message
- *
- * @return void
- */
- public function prepareThenSend($task, $message)
- {
- // Don't send a notification if pretending to deploy
- if ($task->command->option('pretend')) {
- return;
- }
-
- // Build message
- $message = $this->getMessageFormat($message);
- $message = preg_replace('#\{([0-9])\}#', '%$1\$s', $message);
- $message = vsprintf($message, $this->getComponents());
-
- // Send it
- $this->send($message);
- }
-}
diff --git a/src/Rocketeer/Rocketeer.php b/src/Rocketeer/Rocketeer.php
index 48fc5f6f9..5cf042732 100644
--- a/src/Rocketeer/Rocketeer.php
+++ b/src/Rocketeer/Rocketeer.php
@@ -9,9 +9,7 @@
*/
namespace Rocketeer;
-use Exception;
-use Illuminate\Container\Container;
-use Illuminate\Support\Str;
+use Rocketeer\Traits\HasLocator;
/**
* Handles interaction between the User provided informations
@@ -21,61 +19,55 @@
*/
class Rocketeer
{
- /**
- * The IoC Container
- *
- * @var Container
- */
- protected $app;
+ use HasLocator;
/**
- * The current stage
+ * The Rocketeer version
*
* @var string
*/
- protected $stage;
+ const VERSION = '2.0.0';
/**
- * The connections to use
+ * Returns what stage Rocketeer thinks he's in
*
- * @var array
- */
- protected $connections;
-
- /**
- * The current connection
+ * @param string $application
+ * @param string|null $path
*
- * @var string
+ * @return string|false
*/
- protected $connection;
+ public static function getDetectedStage($application = 'application', $path = null)
+ {
+ $current = $path ?: realpath(__DIR__);
+ preg_match('/'.$application.'\/([a-zA-Z0-9_-]+)\/releases\/([0-9]{14})/', $current, $matches);
- /**
- * The Rocketeer version
- *
- * @var string
- */
- const VERSION = '1.2.2';
+ return isset($matches[1]) ? $matches[1] : false;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ //////////////////////////// CONFIGURATION ///////////////////////////
+ //////////////////////////////////////////////////////////////////////
/**
- * Build a new ReleasesManager
+ * Get the name of the application to deploy
*
- * @param Container $app
+ * @return string
*/
- public function __construct(Container $app)
+ public function getApplicationName()
{
- $this->app = $app;
+ return $this->config->get('rocketeer::application_name');
}
/**
* Get an option from Rocketeer's config file
*
- * @param string $option
+ * @param string $option
*
- * @return mixed
+ * @return string
*/
public function getOption($option)
{
- $original = $this->app['config']->get('rocketeer::'.$option);
+ $original = $this->config->get('rocketeer::'.$option);
if ($contextual = $this->getContextualOption($option, 'stages', $original)) {
return $contextual;
@@ -91,22 +83,22 @@ public function getOption($option)
/**
* Get a contextual option
*
- * @param string $option
- * @param string $type [stage,connection]
- * @param string|array $original
+ * @param string $option
+ * @param string $type [stage,connection]
+ * @param string|array|null $original
*
- * @return mixed
+ * @return string|array|\Closure
*/
protected function getContextualOption($option, $type, $original = null)
{
// Switch context
switch ($type) {
case 'stages':
- $contextual = sprintf('rocketeer::on.stages.%s.%s', $this->stage, $option);
+ $contextual = sprintf('rocketeer::on.stages.%s.%s', $this->connections->getStage(), $option);
break;
case 'connections':
- $contextual = sprintf('rocketeer::on.connections.%s.%s', $this->getConnection(), $option);
+ $contextual = sprintf('rocketeer::on.connections.%s.%s', $this->connections->getConnection(), $option);
break;
default:
@@ -115,411 +107,11 @@ protected function getContextualOption($option, $type, $original = null)
}
// Merge with defaults
- $value = $this->app['config']->get($contextual);
- if (is_array($value) and $original) {
+ $value = $this->config->get($contextual);
+ if (is_array($value) && $original) {
$value = array_replace($original, $value);
}
return $value;
}
-
- ////////////////////////////////////////////////////////////////////
- //////////////////////////////// STAGES ////////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Set the stage Tasks will execute on
- *
- * @param string $stage
- *
- * @return void
- */
- public function setStage($stage)
- {
- $this->stage = $stage;
-
- // If we do have a stage, cleanup previous events
- if ($stage) {
- $this->app['rocketeer.tasks']->registerConfiguredEvents();
- }
- }
-
- /**
- * Get the current stage
- *
- * @return string
- */
- public function getStage()
- {
- return $this->stage;
- }
-
- /**
- * Get the various stages provided by the User
- *
- * @return array
- */
- public function getStages()
- {
- return $this->getOption('stages.stages');
- }
-
- ////////////////////////////////////////////////////////////////////
- ///////////////////////////// APPLICATION //////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Whether the repository used is using SSH or HTTPS
- *
- * @return boolean
- */
- public function needsCredentials()
- {
- return Str::contains($this->getRepository(), 'https://');
- }
-
- /**
- * Get the available connections
- *
- * @return array
- */
- public function getAvailableConnections()
- {
- $connections = $this->app['rocketeer.server']->getValue('connections');
-
- // Fetch from config file
- if (!$connections) {
- $connections = $this->app['config']->get('rocketeer::connections');
- }
-
- // Fetch from remote file
- if (!$connections or array_get($connections, 'production.host') == '{host}') {
- $connections = $this->app['config']->get('remote.connections');
- }
-
- 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::default');
- $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) and $default) {
- 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;
- }
-
- /**
- * Get the credentials for a particular connection
- *
- * @param string $connection
- *
- * @return array
- */
- public function getConnectionCredentials($connection = null)
- {
- $connection = $connection ?: $this->getConnection();
-
- return array_get($this->getAvailableConnections(), $connection, array());
- }
-
- /**
- * Sync Rocketeer's credentials with Laravel's
- *
- * @param string $connection
- * @param array $credentials
- *
- * @return void
- */
- public function syncConnectionCredentials($connection = null, array $credentials = array())
- {
- // Store credentials if any
- if ($credentials) {
- $this->app['rocketeer.server']->setValue('connections.'.$connection, $credentials);
- }
-
- // Get connection
- $connection = $connection ?: $this->getConnection();
- $credentials = $this->getConnectionCredentials($connection);
-
- $this->app['config']->set('remote.connections.'.$connection, $credentials);
- }
-
- /**
- * 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
- *
- * @return string
- */
- public function getApplicationName()
- {
- return $this->app['config']->get('rocketeer::application_name');
- }
-
- ////////////////////////////////////////////////////////////////////
- /////////////////////////// GIT REPOSITORY /////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Get the credentials for the repository
- *
- * @return array
- */
- public function getCredentials()
- {
- $credentials = $this->app['rocketeer.server']->getValue('credentials');
- if (!$credentials) {
- $credentials = $this->getOption('scm');
- }
-
- // Cast to array
- $credentials = (array) $credentials;
-
- return array_merge(array(
- 'repository' => '',
- 'username' => '',
- 'password' => '',
- ), $credentials);
- }
-
- /**
- * Get the URL to the Git repository
- *
- * @param string $username
- * @param string $password
- *
- * @return string
- */
- public function getRepository()
- {
- // Get credentials
- $repository = $this->getCredentials();
- $username = array_get($repository, 'username');
- $password = array_get($repository, 'password');
- $repository = array_get($repository, 'repository');
-
- // Add credentials if possible
- if ($username or $password) {
-
- // Build credentials chain
- $credentials = $password ? $username.':'.$password : $username;
- $credentials .= '@';
-
- // Add them in chain
- $repository = preg_replace('#https://(.+)@#', 'https://', $repository);
- $repository = str_replace('https://', 'https://'.$credentials, $repository);
- }
-
- return $repository;
- }
-
- /**
- * Get the Git branch
- *
- * @return string
- */
- public function getRepositoryBranch()
- {
- exec($this->app['rocketeer.scm']->currentBranch(), $fallback);
- $fallback = trim($fallback[0]) ?: 'master';
- $branch = $this->getOption('scm.branch') ?: $fallback;
-
- return $branch;
- }
-
- ////////////////////////////////////////////////////////////////////
- //////////////////////////////// PATHS /////////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Get a configured path
- *
- * @param string $path
- *
- * @return string
- */
- public function getPath($path)
- {
- return $this->getOption('paths.'.$path);
- }
-
- /**
- * Replace patterns in a folder path
- *
- * @param string $path
- *
- * @return string
- */
- public function replacePatterns($path)
- {
- $app = $this->app;
-
- // Replace folder patterns
- return preg_replace_callback('/\{[a-z\.]+\}/', function ($match) use ($app) {
- $folder = substr($match[0], 1, -1);
-
- if ($app->bound($folder)) {
- return str_replace($app['path.base'].'/', null, $app->make($folder));
- }
-
- return false;
- }, $path);
- }
-
- /**
- * Get the path to a folder, taking into account application name and stage
- *
- * @param string $folder
- *
- * @return string
- */
- public function getFolder($folder = null)
- {
- $folder = $this->replacePatterns($folder);
-
- $base = $this->getHomeFolder().'/';
- if ($folder and $this->stage) {
- $base .= $this->stage.'/';
- }
- $folder = str_replace($base, null, $folder);
-
- return $base.$folder;
- }
-
- /**
- * Get the path to the root folder of the application
- *
- * @return string
- */
- public function getHomeFolder()
- {
- $rootDirectory = $this->getOption('remote.root_directory');
- $rootDirectory = Str::finish($rootDirectory, '/');
- $appDirectory = $this->getOption('remote.app_directory') ?: $this->getApplicationName();
-
- return $rootDirectory.$appDirectory;
- }
-
- /**
- * Get the path to the Rocketeer config folder in the users home
- *
- * @return string
- */
- public function getRocketeerConfigFolder()
- {
- return $this->getUserHomeFolder() . '/.rocketeer';
- }
-
- /**
- * Get the path to the users home folder
- *
- * @return string
- */
- public function getUserHomeFolder()
- {
- // Get home folder if available (Unix)
- if (!empty($_SERVER['HOME'])) {
- return $_SERVER['HOME'];
-
- // Else use the homedrive (Windows)
- } elseif (!empty($_SERVER['HOMEDRIVE']) && !empty($_SERVER['HOMEPATH'])) {
- return $_SERVER['HOMEDRIVE'] . $_SERVER['HOMEPATH'];
-
- } else {
- throw new Exception('Cannot determine user home directory.');
- }
- }
}
diff --git a/src/Rocketeer/RocketeerServiceProvider.php b/src/Rocketeer/RocketeerServiceProvider.php
index 807dfc4ce..2051236a9 100644
--- a/src/Rocketeer/RocketeerServiceProvider.php
+++ b/src/Rocketeer/RocketeerServiceProvider.php
@@ -11,13 +11,26 @@
use Illuminate\Config\FileLoader;
use Illuminate\Config\Repository;
-use Illuminate\Container\Container;
use Illuminate\Events\Dispatcher;
use Illuminate\Http\Request;
use Illuminate\Log\Writer;
-use Illuminate\Remote\RemoteManager;
use Illuminate\Support\ServiceProvider;
use Monolog\Logger;
+use Rocketeer\Services\Connections\ConnectionsHandler;
+use Rocketeer\Services\Connections\LocalConnection;
+use Rocketeer\Services\Connections\RemoteHandler;
+use Rocketeer\Services\CredentialsGatherer;
+use Rocketeer\Services\Display\QueueExplainer;
+use Rocketeer\Services\Display\QueueTimer;
+use Rocketeer\Services\History\History;
+use Rocketeer\Services\History\LogsHandler;
+use Rocketeer\Services\Ignition\Configuration;
+use Rocketeer\Services\Pathfinder;
+use Rocketeer\Services\ReleasesManager;
+use Rocketeer\Services\Storages\LocalStorage;
+use Rocketeer\Services\Tasks\TasksBuilder;
+use Rocketeer\Services\Tasks\TasksQueue;
+use Rocketeer\Services\TasksHandler;
// Define DS
if (!defined('DS')) {
@@ -55,208 +68,212 @@ public function register()
*/
public function boot()
{
- // Register classes and commands
- $this->app = static::make($this->app);
+ $this->bindPaths();
+ $this->bindThirdPartyServices();
+
+ // Bind Rocketeer's classes
+ $this->bindCoreClasses();
+ $this->bindConsoleClasses();
+ $this->bindStrategies();
+
+ // Load the user's events, tasks, plugins, and configurations
+ $this->app['rocketeer.igniter']->loadUserConfiguration();
+
+ // Bind commands
+ $this->bindCommands();
}
/**
* Get the services provided by the provider.
*
- * @return array
+ * @return string[]
*/
public function provides()
{
- return array('rocketeer');
+ return ['rocketeer'];
}
////////////////////////////////////////////////////////////////////
/////////////////////////// CLASS BINDINGS /////////////////////////
////////////////////////////////////////////////////////////////////
- /**
- * Make a Rocketeer container
- *
- * @param Container $app
- *
- * @return Container
- */
- public static function make($app = null)
- {
- if (!$app) {
- $app = new Container;
- }
-
- $serviceProvider = new static($app);
-
- // Bind core paths and classes
- $app = $serviceProvider->bindPaths($app);
- $app = $serviceProvider->bindCoreClasses($app);
-
- // Bind Rocketeer's classes
- $app = $serviceProvider->bindClasses($app);
- $app = $serviceProvider->bindScm($app);
-
- // Load the user's events and tasks
- $app = $serviceProvider->loadFileOrFolder($app, 'tasks');
- $app = $serviceProvider->loadFileOrFolder($app, 'events');
-
- // Bind commands
- $app = $serviceProvider->bindCommands($app);
-
- return $app;
- }
-
/**
* Bind the Rocketeer paths
- *
- * @param Container $app
- *
- * @return Container
*/
- public function bindPaths(Container $app)
+ public function bindPaths()
{
- $app->bind('rocketeer.igniter', function ($app) {
- return new Igniter($app);
+ $this->app->singleton('rocketeer.paths', function ($app) {
+ return new Pathfinder($app);
});
- // Bind paths
- $app['rocketeer.igniter']->bindPaths();
+ $this->app->bind('rocketeer.igniter', function ($app) {
+ return new Configuration($app);
+ });
- return $app;
+ // Bind paths
+ $this->app['rocketeer.igniter']->bindPaths();
}
/**
* Bind the core classes
- *
- * @param Container $app
- *
- * @return Container
*/
- public function bindCoreClasses(Container $app)
+ public function bindThirdPartyServices()
{
- $app->bindIf('files', 'Illuminate\Filesystem\Filesystem');
+ $this->app->bindIf('files', 'Illuminate\Filesystem\Filesystem');
- $app->bindIf('request', function () {
+ $this->app->bindIf('request', function () {
return Request::createFromGlobals();
}, true);
- $app->bindIf('config', function ($app) {
+ $this->app->bindIf('config', function ($app) {
$fileloader = new FileLoader($app['files'], __DIR__.'/../config');
return new Repository($fileloader, 'config');
}, true);
- $app->bindIf('remote', function ($app) {
- return new RemoteManager($app);
+ $this->app->bindIf('rocketeer.remote', function ($app) {
+ return new RemoteHandler($app);
}, true);
- $app->bindIf('events', function ($app) {
+ $this->app->singleton('remote.local', function ($app) {
+ return new LocalConnection($app);
+ });
+
+ $this->app->bindIf('events', function ($app) {
return new Dispatcher($app);
}, true);
- $app->bindIf('log', function () {
+ $this->app->bindIf('log', function () {
return new Writer(new Logger('rocketeer'));
}, true);
// Register factory and custom configurations
- $app = $this->registerConfig($app);
-
- return $app;
+ $this->registerConfig();
}
/**
* Bind the Rocketeer classes to the Container
- *
- * @param Container $app
- *
- * @return Container
*/
- public function bindClasses(Container $app)
+ public function bindCoreClasses()
{
- $app->singleton('rocketeer.rocketeer', function ($app) {
+ $this->app->singleton('rocketeer.rocketeer', function ($app) {
return new Rocketeer($app);
});
- $app->bind('rocketeer.releases', function ($app) {
+ $this->app->singleton('rocketeer.connections', function ($app) {
+ return new ConnectionsHandler($app);
+ });
+
+ $this->app->singleton('rocketeer.explainer', function ($app) {
+ return new QueueExplainer($app);
+ });
+
+ $this->app->bind('rocketeer.timer', function ($app) {
+ return new QueueTimer($app);
+ });
+
+ $this->app->singleton('rocketeer.releases', function ($app) {
return new ReleasesManager($app);
});
- $app->bind('rocketeer.server', function ($app) {
+ $this->app->singleton('rocketeer.storage.local', function ($app) {
$filename = $app['rocketeer.rocketeer']->getApplicationName();
$filename = $filename === '{application_name}' ? 'deployments' : $filename;
- return new Server($app, $filename);
+ return new LocalStorage($app, $filename);
});
- $app->bind('rocketeer.bash', function ($app) {
+ $this->app->bind('rocketeer.bash', function ($app) {
return new Bash($app);
});
- $app->singleton('rocketeer.queue', function ($app) {
+ $this->app->singleton('rocketeer.queue', function ($app) {
return new TasksQueue($app);
});
- $app->singleton('rocketeer.tasks', function ($app) {
+ $this->app->bind('rocketeer.builder', function ($app) {
+ return new TasksBuilder($app);
+ });
+
+ $this->app->singleton('rocketeer.tasks', function ($app) {
return new TasksHandler($app);
});
- $app->singleton('rocketeer.logs', function ($app) {
+ $this->app->singleton('rocketeer.history', function () {
+ return new History;
+ });
+
+ $this->app->singleton('rocketeer.logs', function ($app) {
return new LogsHandler($app);
});
+ }
- $app->singleton('rocketeer.console', function () {
- return new Console\Console('Rocketeer', Rocketeer::VERSION);
+ /**
+ * Bind the CredentialsGatherer and Console application
+ */
+ public function bindConsoleClasses()
+ {
+ $this->app->singleton('rocketeer.credentials', function ($app) {
+ return new CredentialsGatherer($app);
});
- $app['rocketeer.console']->setLaravel($app);
- $app['rocketeer.rocketeer']->syncConnectionCredentials();
+ $this->app->singleton('rocketeer.console', function () {
+ return new Console\Console('Rocketeer', Rocketeer::VERSION);
+ });
- return $app;
+ $this->app['rocketeer.console']->setLaravel($this->app);
+ $this->app['rocketeer.connections']->syncConnectionCredentials();
}
/**
* Bind the SCM instance
- *
- * @param Container $app
- *
- * @return Container
*/
- public function bindScm(Container $app)
+ public function bindStrategies()
{
- // Currently only one
+ // Bind SCM class
$scm = $this->app['rocketeer.rocketeer']->getOption('scm.scm');
$scm = 'Rocketeer\Scm\\'.ucfirst($scm);
- $app->bind('rocketeer.scm', function ($app) use ($scm) {
+ $this->app->bind('rocketeer.scm', function ($app) use ($scm) {
return new $scm($app);
});
- return $app;
+ // Bind strategies
+ $strategies = $this->app['rocketeer.rocketeer']->getOption('strategies');
+ foreach ($strategies as $strategy => $concrete) {
+ if (!is_string($concrete)) {
+ continue;
+ }
+
+ $this->app->bind('rocketeer.strategies.'.$strategy, function ($app) use ($strategy, $concrete) {
+ return $app['rocketeer.builder']->buildStrategy($strategy, $concrete);
+ });
+ }
}
/**
* Bind the commands to the Container
- *
- * @param Container $app
- *
- * @return Container
*/
- public function bindCommands(Container $app)
+ public function bindCommands()
{
// Base commands
$tasks = array(
- '' => 'Rocketeer',
- 'check' => 'Check',
- 'cleanup' => 'Cleanup',
- 'current' => 'CurrentRelease',
- 'deploy' => 'Deploy',
- 'flush' => 'Flush',
- 'ignite' => 'Ignite',
- 'rollback' => 'Rollback',
- 'setup' => 'Setup',
- 'teardown' => 'Teardown',
- 'test' => 'Test',
- 'update' => 'Update',
+ '' => 'Rocketeer',
+ 'check' => 'Check',
+ 'cleanup' => 'Cleanup',
+ 'current' => 'CurrentRelease',
+ 'deploy' => 'Deploy',
+ 'flush' => 'Flush',
+ 'ignite' => 'Ignite',
+ 'rollback' => 'Rollback',
+ 'setup' => 'Setup',
+ 'strategies' => 'Strategies',
+ 'teardown' => 'Teardown',
+ 'test' => 'Test',
+ 'update' => 'Update',
+ 'plugin-publish' => 'Plugins\Publish',
+ 'plugin-list' => 'Plugins\List',
+ 'plugin-install' => 'Plugins\Install',
);
// Add User commands
@@ -265,51 +282,31 @@ public function bindCommands(Container $app)
// Bind the commands
foreach ($tasks as $slug => $task) {
-
- // Check if we have an actual command to use
- $commandClass = 'Rocketeer\Commands\\'.$task.'Command';
- $fakeCommand = !class_exists($commandClass);
-
- // Build command slug
- $taskInstance = $this->app['rocketeer.tasks']->buildTaskFromClass($task);
- if (is_numeric($slug)) {
- $slug = $taskInstance->getSlug();
- }
+ $command = $this->app['rocketeer.builder']->buildCommand($task, $slug);
// Bind Task to container
$handle = 'rocketeer.tasks.'.$slug;
- $this->app->bind($handle, function () use ($taskInstance) {
- return $taskInstance;
+ $this->app->bind($handle, function () use ($command) {
+ return $command->getTask();
});
// Add command to array
- $command = trim('deploy.'.$slug, '.');
- $this->commands[] = $command;
-
- // Look for an existing command
- if (!$fakeCommand) {
- $this->app->singleton($command, function () use ($commandClass) {
- return new $commandClass;
- });
-
- // Else create a fake one
- } else {
- $this->app->bind($command, function () use ($taskInstance, $slug) {
- return new Commands\BaseTaskCommand($taskInstance, $slug);
- });
- }
+ $commandHandle = trim('deploy.'.$slug, '.');
+ $this->commands[] = $commandHandle;
+ // Register command with the container
+ $this->app->singleton($commandHandle, function () use ($command) {
+ return $command;
+ });
}
// Add commands to Artisan
foreach ($this->commands as $command) {
- $app['rocketeer.console']->add($app[$command]);
- if (isset($app['events'])) {
+ $this->app['rocketeer.console']->add($this->app[$command]);
+ if (isset($this->app['events'])) {
$this->commands($command);
}
}
-
- return $app;
}
////////////////////////////////////////////////////////////////////
@@ -318,62 +315,28 @@ public function bindCommands(Container $app)
/**
* Register factory and custom configurations
- *
- * @param Container $app
- *
- * @return Container
*/
- protected function registerConfig(Container $app)
+ protected function registerConfig()
{
// Register config file
- $app['config']->package('anahkiasen/rocketeer', __DIR__.'/../config');
- $app['config']->getLoader();
+ $this->app['config']->package('anahkiasen/rocketeer', __DIR__.'/../config');
+ $this->app['config']->getLoader();
// Register custom config
- $custom = $app['path.rocketeer.config'];
- if (file_exists($custom)) {
- $app['config']->afterLoading('rocketeer', function ($me, $group, $items) use ($custom) {
- $customItems = $custom.'/'.$group.'.php';
- if (!file_exists($customItems)) {
- return $items;
- }
-
- $customItems = include $customItems;
-
- return array_replace($items, $customItems);
- });
- }
-
- return $app;
- }
-
- /**
- * Load a file or its contents if a folder
- *
- * @param Container $app
- * @param string $handle
- *
- * @return Container
- */
- protected function loadFileOrFolder(Container $app, $handle)
- {
- // Bind ourselves into the facade to avoid automatic resolution
- Facades\Rocketeer::setFacadeApplication($app);
-
- // If we have one unified tasks file, include it
- $file = $app['path.rocketeer.'.$handle];
- if (!is_dir($file) and file_exists($file)) {
- include $file;
+ $set = $this->app['path.rocketeer.config'];
+ if (!file_exists($set)) {
+ return;
}
- // Else include its contents
- elseif (is_dir($file)) {
- $folder = glob($file.'/*.php');
- foreach ($folder as $file) {
- include $file;
+ $this->app['config']->afterLoading('rocketeer', function ($me, $group, $items) use ($set) {
+ $customItems = $set.'/'.$group.'.php';
+ if (!file_exists($customItems)) {
+ return $items;
}
- }
- return $app;
+ $customItems = include $customItems;
+
+ return array_replace($items, $customItems);
+ });
}
}
diff --git a/src/Rocketeer/Scm/Git.php b/src/Rocketeer/Scm/Git.php
index b90ab7f82..32718b41c 100644
--- a/src/Rocketeer/Scm/Git.php
+++ b/src/Rocketeer/Scm/Git.php
@@ -9,21 +9,22 @@
*/
namespace Rocketeer\Scm;
-use Rocketeer\Traits\Scm;
+use Rocketeer\Abstracts\AbstractBinary;
+use Rocketeer\Interfaces\ScmInterface;
/**
* The Git implementation of the ScmInterface
*
* @author Maxime Fabre
*/
-class Git extends Scm implements ScmInterface
+class Git extends AbstractBinary implements ScmInterface
{
/**
* The core binary
*
* @var string
*/
- public $binary = 'git';
+ protected $binary = 'git';
////////////////////////////////////////////////////////////////////
///////////////////////////// INFORMATIONS /////////////////////////
@@ -46,7 +47,7 @@ public function check()
*/
public function currentState()
{
- return $this->getCommand('rev-parse HEAD');
+ return $this->revParse('HEAD');
}
/**
@@ -56,7 +57,7 @@ public function currentState()
*/
public function currentBranch()
{
- return $this->getCommand('rev-parse --abbrev-ref HEAD');
+ return $this->revParse('--abbrev-ref HEAD');
}
////////////////////////////////////////////////////////////////////
@@ -66,17 +67,24 @@ public function currentBranch()
/**
* Clone a repository
*
- * @param string $destination
+ * @param string $destination
*
* @return string
*/
public function checkout($destination)
{
- $branch = $this->app['rocketeer.rocketeer']->getRepositoryBranch();
- $repository = $this->app['rocketeer.rocketeer']->getRepository();
- $shallow = $this->app['rocketeer.rocketeer']->getOption('scm.shallow') ? ' --depth 1' : '';
+ $arguments = array_map([$this, 'quote'], array(
+ $this->connections->getRepositoryEndpoint(),
+ $destination,
+ ));
- return $this->getCommand('clone%s -b %s "%s" %s', $shallow, $branch, $repository, $destination);
+ // Build flags
+ $flags = ['--branch' => $this->connections->getRepositoryBranch()];
+ if ($this->rocketeer->getOption('scm.shallow')) {
+ $flags['--depth'] = 1;
+ }
+
+ return $this->clone($arguments, $flags);
}
/**
@@ -86,7 +94,7 @@ public function checkout($destination)
*/
public function reset()
{
- return $this->getCommand('reset --hard');
+ return $this->getCommand('reset', [], ['--hard']);
}
/**
@@ -96,7 +104,7 @@ public function reset()
*/
public function update()
{
- return $this->getCommand('pull');
+ return $this->pull();
}
/**
@@ -106,6 +114,6 @@ public function update()
*/
public function submodules()
{
- return $this->getCommand('submodule update --init --recursive');
+ return $this->submodule('update', ['--init', '--recursive']);
}
}
diff --git a/src/Rocketeer/Scm/Svn.php b/src/Rocketeer/Scm/Svn.php
index a8bf092f3..ed9b621f7 100644
--- a/src/Rocketeer/Scm/Svn.php
+++ b/src/Rocketeer/Scm/Svn.php
@@ -9,7 +9,9 @@
*/
namespace Rocketeer\Scm;
-use Rocketeer\Traits\Scm;
+use Illuminate\Support\Arr;
+use Rocketeer\Abstracts\AbstractBinary;
+use Rocketeer\Interfaces\ScmInterface;
/**
* The Svn implementation of the ScmInterface
@@ -17,7 +19,7 @@
* @author Maxime Fabre
* @author Gasillo
*/
-class Svn extends Scm implements ScmInterface
+class Svn extends AbstractBinary implements ScmInterface
{
/**
* The core binary
@@ -67,17 +69,17 @@ public function currentBranch()
/**
* Clone a repository
*
- * @param string $destination
+ * @param string $destination
*
* @return string
*/
public function checkout($destination)
{
- $branch = $this->app['rocketeer.rocketeer']->getRepositoryBranch();
- $repository = $this->app['rocketeer.rocketeer']->getRepository();
- $repository = rtrim($repository, '/') . '/' . ltrim($branch, '/');
+ $branch = $this->connections->getRepositoryBranch();
+ $repository = $this->connections->getRepositoryEndpoint();
+ $repository = rtrim($repository, '/').'/'.ltrim($branch, '/');
- return $this->getCommand('co %s %s %s', $this->getCredentials(), $repository, $destination);
+ return $this->co([$repository, $destination], $this->getCredentials());
}
/**
@@ -87,7 +89,9 @@ public function checkout($destination)
*/
public function reset()
{
- return $this->getCommand('status -q | grep -v \'^[~XI ]\' | awk \'{print $2;}\' | xargs %s revert', $this->binary);
+ $command = sprintf('status -q | grep -v \'^[~XI ]\' | awk \'{print $2;}\' | xargs %s revert', $this->binary);
+
+ return $this->getCommand($command);
}
/**
@@ -97,28 +101,28 @@ public function reset()
*/
public function update()
{
- return $this->getCommand('up %s', $this->getCredentials());
+ return $this->up([], $this->getCredentials());
}
/**
* Return credential options
*
- * @return string
+ * @return array|array
*/
protected function getCredentials()
{
- $options = array('--non-interactive');
- $credentials = $this->app['rocketeer.rocketeer']->getCredentials();
+ $options = ['--non-interactive' => null];
+ $credentials = $this->connections->getRepositoryCredentials();
// Build command
- if ($user = array_get($credentials, 'username')) {
- $options[] = '--username=' . $user;
+ if ($user = Arr::get($credentials, 'username')) {
+ $options['--username'] = $user;
}
- if ($pass = array_get($credentials, 'password')) {
- $options[] = '--password=' . $pass;
+ if ($pass = Arr::get($credentials, 'password')) {
+ $options['--password'] = $pass;
}
- return implode(' ', $options);
+ return $options;
}
/**
diff --git a/src/Rocketeer/Server.php b/src/Rocketeer/Server.php
deleted file mode 100644
index dd62e82e4..000000000
--- a/src/Rocketeer/Server.php
+++ /dev/null
@@ -1,315 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace Rocketeer;
-
-use Exception;
-use Illuminate\Container\Container;
-
-/**
- * Provides and persists informations about the remote server
- *
- * @author Maxime Fabre
- */
-class Server
-{
- /**
- * The IoC Container
- *
- * @var Container
- */
- protected $app;
-
- /**
- * The path to the storage file
- *
- * @var string
- */
- protected $repository;
-
- /**
- * The current hash in use
- *
- * @var string
- */
- protected $hash;
-
- /**
- * Build a new ReleasesManager
- *
- * @param Container $app
- * @param string $filename
- * @param string $storage
- */
- public function __construct(Container $app, $filename = 'deployments', $storage = null)
- {
- $this->app = $app;
-
- // Create repository and update it if necessary
- $this->setRepository($filename, $storage);
- if ($this->shouldFlush()) {
- $this->deleteRepository();
- }
-
- // Add salt to current repository
- $this->setValue('hash', $this->getHash());
- }
-
- ////////////////////////////////////////////////////////////////////
- //////////////////////////////// SALTS /////////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Get the current salt in use
- *
- * @return string
- */
- public function getHash()
- {
- // Return cached hash if any
- if ($this->hash) {
- return $this->hash;
- }
-
- // Get the contents of the configuration folder
- $salt = '';
- $folder = $this->app['rocketeer.igniter']->getConfigurationPath();
- $files = $this->app['files']->glob($folder.'/*.php');
-
- // Remove custom files and folders
- $handles = array('events', 'tasks');
- foreach ($handles as $handle) {
- $path = $this->app['path.rocketeer.'.$handle];
- $index = array_search($path, $files);
- if ($index !== false) {
- unset($files[$index]);
- }
- }
-
- // Compute the salts
- foreach ($files as $file) {
- $file = $this->app['files']->getRequire($file);
- $salt .= json_encode($file);
- }
-
- // Cache it
- $this->hash = md5($salt);
-
- return $this->hash;
- }
-
- ////////////////////////////////////////////////////////////////////
- ///////////////////////////// REPOSITORY ///////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Flushes the repository if required
- *
- * @return void
- */
- public function shouldFlush()
- {
- $currentHash = $this->getValue('hash');
-
- return $currentHash and $currentHash !== $this->getHash();
- }
-
- /**
- * Change the repository in use
- *
- * @param string $filename
- * @param string $storage
- */
- public function setRepository($filename, $storage = null)
- {
- // Create personal storage if necessary
- if (!$this->app->bound('path.storage')) {
- $storage = $this->app['rocketeer.rocketeer']->getRocketeerConfigFolder();
- $this->app['files']->makeDirectory($storage, 0755, false, true);
- }
-
- // Get path to storage
- $storage = $storage ?: $this->app['path.storage'].DS.'meta';
-
- $this->repository = $storage.DS.$filename.'.json';
- }
-
- ////////////////////////////////////////////////////////////////////
- /////////////////////////// REMOTE VARIABLES ///////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Get the directory separators on the remove server
- *
- * @return string
- */
- public function getSeparator()
- {
- // If manually set by the user, return it
- $user = $this->app['rocketeer.rocketeer']->getOption('remote.variables.directory_separator');
- if ($user) {
- return $user;
- }
-
- $bash = $this->app['rocketeer.bash'];
- return $this->getValue('directory_separator', function ($server) use ($bash) {
- $separator = $bash->runLast('php -r "echo DIRECTORY_SEPARATOR;"');
-
- // Throw an Exception if we receive invalid output
- if (strlen($separator) > 1) {
- throw new Exception(
- 'An error occured while fetching the directory separators used on the server.'.PHP_EOL.
- 'Output received was : '.$separator
- );
- }
-
- // Cache separator
- $server->setValue('directory_separator', $separator);
-
- return $separator;
- });
- }
-
- /**
- * Get the remote line endings on the remove server
- *
- * @return string
- */
- public function getLineEndings()
- {
- // If manually set by the user, return it
- $user = $this->app['rocketeer.rocketeer']->getOption('remote.variables.line_endings');
- if ($user) {
- return $user;
- }
-
- $bash = $this->app['rocketeer.bash'];
- return $this->getValue('line_endings', function ($server) use ($bash) {
- $endings = $bash->runRaw('php -r "echo PHP_EOL;"');
- $server->setValue('line_endings', $endings);
-
- return $endings;
- });
- }
-
- /**
- * Check if the current project uses Composer
- *
- * @return boolean
- */
- public function usesComposer()
- {
- $path = $this->app['path.base'].DIRECTORY_SEPARATOR.'composer.json';
-
- return $this->app['files']->exists($path);
- }
-
- ////////////////////////////////////////////////////////////////////
- /////////////////////////////// KEYSTORE ///////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Get a value from the repository file
- *
- * @param string $key
- * @param \Closure|string $fallback
- *
- * @return mixed
- */
- public function getValue($key, $fallback = null)
- {
- $value = array_get($this->getRepository(), $key, null);
-
- // Get fallback value
- if (is_null($value)) {
- return is_callable($fallback) ? $fallback($this) : $fallback;
- }
-
- return $value;
- }
-
- /**
- * Set a value from the repository file
- *
- * @param string $key
- * @param mixed $value
- *
- * @return array
- */
- public function setValue($key, $value)
- {
- $repository = $this->getRepository();
- array_set($repository, $key, $value);
-
- return $this->updateRepository($repository);
- }
-
- /**
- * Forget a value from the repository file
- *
- * @param string $key
- *
- * @return array
- */
- public function forgetValue($key)
- {
- $repository = $this->getRepository();
- array_forget($repository, $key);
-
- return $this->updateRepository($repository);
- }
-
- ////////////////////////////////////////////////////////////////////
- ////////////////////////// REPOSITORY FILE /////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Replace the contents of the deployments file
- *
- * @param array $data
- *
- * @return array
- */
- public function updateRepository($data)
- {
- // Yup. Don't look at me like that.
- @$this->app['files']->put($this->repository, json_encode($data));
-
- return $data;
- }
-
- /**
- * Get the contents of the deployments file
- *
- * @return array
- */
- public function getRepository()
- {
- // Cancel if the file doesn't exist
- if (!$this->app['files']->exists($this->repository)) {
- return array();
- }
-
- // Get and parse file
- $repository = $this->app['files']->get($this->repository);
- $repository = json_decode($repository, true);
-
- return $repository;
- }
-
- /**
- * Deletes the deployments file
- *
- * @return boolean
- */
- public function deleteRepository()
- {
- return $this->app['files']->delete($this->repository);
- }
-}
diff --git a/src/Rocketeer/Services/Connections/Connection.php b/src/Rocketeer/Services/Connections/Connection.php
new file mode 100644
index 000000000..4c53d32bf
--- /dev/null
+++ b/src/Rocketeer/Services/Connections/Connection.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Services\Connections;
+
+/**
+ * Base connection class with additional setters
+ *
+ * @author Maxime Fabre
+ */
+class Connection extends \Illuminate\Remote\Connection
+{
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUsername()
+ {
+ return $this->username;
+ }
+}
diff --git a/src/Rocketeer/Services/Connections/ConnectionsHandler.php b/src/Rocketeer/Services/Connections/ConnectionsHandler.php
new file mode 100644
index 000000000..5caffcea0
--- /dev/null
+++ b/src/Rocketeer/Services/Connections/ConnectionsHandler.php
@@ -0,0 +1,428 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Services\Connections;
+
+use Illuminate\Support\Arr;
+use Illuminate\Support\Str;
+use Rocketeer\Traits\HasLocator;
+
+/**
+ * Handles, get and return, the various connections/stages
+ * and their credentials
+ *
+ * @author Maxime Fabre
+ */
+class ConnectionsHandler
+{
+ use HasLocator;
+
+ /**
+ * The current handle
+ *
+ * @type string
+ */
+ protected $handle;
+
+ /**
+ * The current stage
+ *
+ * @var string
+ */
+ protected $stage;
+
+ /**
+ * The current server
+ *
+ * @type integer
+ */
+ protected $currentServer = 0;
+
+ /**
+ * The connections to use
+ *
+ * @var array|null
+ */
+ protected $connections;
+
+ /**
+ * The current connection
+ *
+ * @var string|null
+ */
+ protected $connection;
+
+ /**
+ * Build the current connection's handle
+ *
+ * @param string|null $connection
+ * @param integer|null $server
+ * @param string|null $stage
+ *
+ * @return string
+ */
+ public function getHandle($connection = null, $server = null, $stage = null)
+ {
+ if ($this->handle) {
+ return $this->handle;
+ }
+
+ // Get identifiers
+ $connection = $connection ?: $this->getConnection();
+ $server = $server ?: $this->getServer();
+ $stage = $stage ?: $this->getStage();
+
+ // Filter values
+ $handle = [$connection, $server, $stage];
+ if ($this->isMultiserver($connection)) {
+ $handle = array_filter($handle, function ($value) {
+ return !is_null($value);
+ });
+ } else {
+ $handle = array_filter($handle);
+ }
+
+ // Concatenate
+ $handle = implode('/', $handle);
+ $this->handle = $handle;
+
+ return $handle;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// SERVERS ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * @return int
+ */
+ public function getServer()
+ {
+ return $this->currentServer;
+ }
+
+ /**
+ * Check if a connection is multiserver or not
+ *
+ * @param string $connection
+ *
+ * @return boolean
+ */
+ public function isMultiserver($connection)
+ {
+ return (bool) count($this->getConnectionCredentials($connection));
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ //////////////////////////////// STAGES ////////////////////////////
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get the current stage
+ *
+ * @return string
+ */
+ public function getStage()
+ {
+ return $this->stage;
+ }
+
+ /**
+ * Set the stage Tasks will execute on
+ *
+ * @param string|null $stage
+ */
+ public function setStage($stage)
+ {
+ if ($stage == $this->stage) {
+ return;
+ }
+
+ $this->stage = $stage;
+ $this->handle = null;
+
+ // If we do have a stage, cleanup previous events
+ if ($stage) {
+ $this->tasks->registerConfiguredEvents();
+ }
+ }
+
+ /**
+ * Get the various stages provided by the User
+ *
+ * @return array
+ */
+ public function getStages()
+ {
+ return (array) $this->rocketeer->getOption('stages.stages');
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ ///////////////////////////// APPLICATION //////////////////////////
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Whether the repository used is using SSH or HTTPS
+ *
+ * @return boolean
+ */
+ public function needsCredentials()
+ {
+ return Str::contains($this->getRepositoryEndpoint(), 'https://');
+ }
+
+ /**
+ * Get the available connections
+ *
+ * @return string[][]|string[]
+ */
+ public function getAvailableConnections()
+ {
+ // Fetch stored credentials
+ $storage = (array) $this->localStorage->get('connections');
+
+ // Merge with defaults from config file
+ $configuration = (array) $this->config->get('rocketeer::connections');
+
+ // Fetch from remote file
+ $remote = (array) $this->config->get('remote.connections');
+
+ // Merge configurations
+ $connections = array_replace_recursive($remote, $configuration, $storage);
+
+ // Unify multiservers
+ foreach ($connections as $key => $servers) {
+ $servers = Arr::get($servers, 'servers', [$servers]);
+ $connections[$key] = ['servers' => array_values($servers)];
+ }
+
+ 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 (bool) Arr::get($available, $connection.'.servers');
+ }
+
+ /**
+ * 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->config->get('rocketeer::default');
+ $default = $this->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) && $default) {
+ return array($default);
+ }
+
+ // Set current connection as default
+ $this->connections = $connections;
+
+ return $connections;
+ }
+
+ /**
+ * Set the active connections
+ *
+ * @param string|string[] $connections
+ */
+ public function setConnections($connections)
+ {
+ if (!is_array($connections)) {
+ $connections = explode(',', $connections);
+ }
+
+ $this->connections = $connections;
+ $this->handle = null;
+ }
+
+ /**
+ * Get the active connection
+ *
+ * @return string
+ */
+ public function getConnection()
+ {
+ // Get cached resolved connection
+ if ($this->connection) {
+ return $this->connection;
+ }
+
+ $connection = Arr::get($this->getConnections(), 0);
+ $this->connection = $connection;
+
+ return $this->connection;
+ }
+
+ /**
+ * Set the current connection
+ *
+ * @param string $connection
+ * @param int $server
+ */
+ public function setConnection($connection, $server = 0)
+ {
+ if (!$this->isValidConnection($connection) || $this->connection == $connection) {
+ return;
+ }
+
+ // Set the connection
+ $this->handle = null;
+ $this->connection = $connection;
+ $this->localStorage = $server;
+
+ // Update events
+ $this->tasks->registerConfiguredEvents();
+ }
+
+ /**
+ * Get the credentials for a particular connection
+ *
+ * @param string|null $connection
+ *
+ * @return string[][]
+ */
+ public function getConnectionCredentials($connection = null)
+ {
+ $connection = $connection ?: $this->getConnection();
+ $available = $this->getAvailableConnections();
+
+ return Arr::get($available, $connection.'.servers');
+ }
+
+ /**
+ * Get thecredentials for as server
+ *
+ * @param string|null $connection
+ * @param int $server
+ *
+ * @return mixed
+ */
+ public function getServerCredentials($connection = null, $server = 0)
+ {
+ $connection = $this->getConnectionCredentials($connection);
+
+ return Arr::get($connection, $server);
+ }
+
+ /**
+ * Sync Rocketeer's credentials with Laravel's
+ *
+ * @param string|null $connection
+ * @param string[]|null $credentials
+ * @param int $server
+ */
+ public function syncConnectionCredentials($connection = null, array $credentials = array(), $server = 0)
+ {
+ // Store credentials if any
+ if ($credentials) {
+ $this->localStorage->set('connections.'.$connection.'.servers.'.$server, $credentials);
+ }
+
+ // Get connection
+ $connection = $connection ?: $this->getConnection();
+ $credentials = $this->getConnectionCredentials($connection);
+
+ $this->config->set('remote.connections.'.$connection, $credentials);
+ }
+
+ /**
+ * Flush active connection(s)
+ */
+ public function disconnect()
+ {
+ $this->connection = null;
+ $this->connections = null;
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ /////////////////////////// GIT REPOSITORY /////////////////////////
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get the credentials for the repository
+ *
+ * @return string[]
+ */
+ public function getRepositoryCredentials()
+ {
+ $config = (array) $this->rocketeer->getOption('scm');
+ $credentials = (array) $this->localStorage->get('credentials');
+
+ return array_merge($config, $credentials);
+ }
+
+ /**
+ * Get the URL to the Git repository
+ *
+ * @return string
+ */
+ public function getRepositoryEndpoint()
+ {
+ // Get credentials
+ $repository = $this->getRepositoryCredentials();
+ $username = Arr::get($repository, 'username');
+ $password = Arr::get($repository, 'password');
+ $repository = Arr::get($repository, 'repository');
+
+ // Add credentials if possible
+ if ($username || $password) {
+
+ // Build credentials chain
+ $credentials = $password ? $username.':'.$password : $username;
+ $credentials .= '@';
+
+ // Add them in chain
+ $repository = preg_replace('#https://(.+)@#', 'https://', $repository);
+ $repository = str_replace('https://', 'https://'.$credentials, $repository);
+ }
+
+ return $repository;
+ }
+
+ /**
+ * Get the Git branch
+ *
+ * @return string
+ */
+ public function getRepositoryBranch()
+ {
+ exec($this->scm->currentBranch(), $fallback);
+ $fallback = Arr::get($fallback, 0, 'master');
+ $fallback = trim($fallback);
+ $branch = $this->rocketeer->getOption('scm.branch') ?: $fallback;
+
+ return $branch;
+ }
+}
diff --git a/src/Rocketeer/Services/Connections/LocalConnection.php b/src/Rocketeer/Services/Connections/LocalConnection.php
new file mode 100644
index 000000000..ec1e5c38c
--- /dev/null
+++ b/src/Rocketeer/Services/Connections/LocalConnection.php
@@ -0,0 +1,152 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Services\Connections;
+
+use Closure;
+use Illuminate\Remote\ConnectionInterface;
+use Rocketeer\Traits\HasLocator;
+
+/**
+ * Stub of local connections to make Rocketeer work
+ * locally when necessary
+ *
+ * @author Maxime Fabre
+ */
+class LocalConnection implements ConnectionInterface
+{
+ use HasLocator;
+
+ /**
+ * Return status of the last command
+ *
+ * @type integer
+ */
+ protected $previousStatus;
+
+ /**
+ * Define a set of commands as a task.
+ *
+ * @param string $task
+ * @param string|array $commands
+ *
+ * @codeCoverageIgnore
+ * @return void
+ */
+ public function define($task, $commands)
+ {
+ // ...
+ }
+
+ /**
+ * Run a task against the connection.
+ *
+ * @param string $task
+ * @param Closure|null $callback
+ *
+ * @codeCoverageIgnore
+ * @return void
+ */
+ public function task($task, Closure $callback = null)
+ {
+ // ...
+ }
+
+ /**
+ * Run a set of commands against the connection.
+ *
+ * @param string|array $commands
+ * @param Closure|null $callback
+ *
+ * @return void
+ */
+ public function run($commands, Closure $callback = null)
+ {
+ $commands = (array) $commands;
+ foreach ($commands as $command) {
+ exec($command, $output, $status);
+
+ $this->previousStatus = $status;
+ if ($callback) {
+ $output = (array) $output;
+ foreach ($output as $line) {
+ $callback($line.PHP_EOL);
+ }
+ }
+ }
+ }
+
+ /**
+ * Get the exit status of the last command.
+ *
+ * @return integer|null
+ */
+ public function status()
+ {
+ return $this->previousStatus;
+ }
+
+ /**
+ * Upload a local file to the server.
+ *
+ * @param string $local
+ * @param string $remote
+ *
+ * @codeCoverageIgnore
+ * @return integer
+ */
+ public function put($local, $remote)
+ {
+ $local = $this->files->get($local);
+
+ return $this->putString($local, $remote);
+ }
+
+ /**
+ * Get the contents of a remote file.
+ *
+ * @param string $remote
+ *
+ * @codeCoverageIgnore
+ * @return string
+ */
+ public function getString($remote)
+ {
+ return $this->files->get($remote);
+ }
+
+ /**
+ * Display the given line using the default output.
+ *
+ * @param string $line
+ *
+ * @codeCoverageIgnore
+ * @return void
+ */
+ public function display($line)
+ {
+ $lead = '[local]';
+
+ $this->command->line($lead.' '.$line);
+ }
+
+ /**
+ * Upload a string to to the given file on the server.
+ *
+ * @param string $remote
+ * @param string $contents
+ *
+ * @codeCoverageIgnore
+ * @return integer
+ */
+ public function putString($remote, $contents)
+ {
+ return $this->files->put($remote, $contents);
+ }
+}
diff --git a/src/Rocketeer/Services/Connections/RemoteHandler.php b/src/Rocketeer/Services/Connections/RemoteHandler.php
new file mode 100644
index 000000000..e379fd7d5
--- /dev/null
+++ b/src/Rocketeer/Services/Connections/RemoteHandler.php
@@ -0,0 +1,145 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Services\Connections;
+
+use Exception;
+use InvalidArgumentException;
+use Rocketeer\Exceptions\ConnectionException;
+use Rocketeer\Exceptions\MissingCredentialsException;
+use Rocketeer\Traits\HasLocator;
+use Symfony\Component\Console\Output\NullOutput;
+
+/**
+ * Handle creationg and caching of connections
+ *
+ * @author Maxime Fabre
+ * @author Taylor Otwell
+ */
+class RemoteHandler
+{
+ use HasLocator;
+
+ /**
+ * A storage of active connections
+ *
+ * @type Connection[]
+ */
+ protected $active = [];
+
+ /**
+ * Whether the handler is currently connected to any server
+ *
+ * @return boolean
+ */
+ public function connected()
+ {
+ return (bool) $this->active;
+ }
+
+ /**
+ * Create a specific connection or the default one
+ *
+ * @param string|null $connection
+ * @param integer $server
+ *
+ * @return Connection
+ */
+ public function connection($connection = null, $server = 0)
+ {
+ $name = $connection ?: $this->connections->getConnection();
+ $server = $server ?: $this->connections->getServer();
+ $handle = $this->connections->getHandle($name, $server);
+
+ // Check the cache
+ if (isset($this->active[$handle])) {
+ return $this->active[$handle];
+ }
+
+ // Create connection
+ $credentials = $this->connections->getServerCredentials();
+ $connection = $this->makeConnection($name, $credentials);
+
+ // Save to cache
+ $this->active[$handle] = $connection;
+
+ return $connection;
+ }
+
+ /**
+ * @param string $name
+ * @param array $credentials
+ *
+ * @throws MissingCredentialsException
+ * @return Connection
+ */
+ protected function makeConnection($name, array $credentials)
+ {
+ if (!isset($credentials['host']) || !isset($credentials['username'])) {
+ throw new MissingCredentialsException('Host and/or username is required for '.$name);
+ }
+
+ $connection = new Connection(
+ $name,
+ $credentials['host'],
+ $credentials['username'],
+ $this->getAuth($credentials)
+ );
+
+ // Set output on connection
+ $output = $this->hasCommand() ? $this->command->getOutput() : new NullOutput();
+ $connection->setOutput($output);
+
+ return $connection;
+ }
+
+ /**
+ * Format the appropriate authentication array payload.
+ *
+ * @param array $config
+ *
+ * @return array
+ * @throws InvalidArgumentException
+ */
+ protected function getAuth(array $config)
+ {
+ if (isset($config['agent']) && $config['agent'] === true) {
+ return ['agent' => true];
+ } elseif (isset($config['key']) && trim($config['key']) != '') {
+ return ['key' => $config['key'], 'keyphrase' => $config['keyphrase']];
+ } elseif (isset($config['keytext']) && trim($config['keytext']) != '') {
+ return ['keytext' => $config['keytext']];
+ } elseif (isset($config['password'])) {
+ return ['password' => $config['password']];
+ }
+
+ throw new MissingCredentialsException('Password / key is required.');
+ }
+
+ /**
+ * Dynamically pass methods to the default connection.
+ *
+ * @param string $method
+ * @param array $parameters
+ *
+ * @throws \Rocketeer\Exceptions\ConnectionException
+ * @return mixed
+ */
+ public function __call($method, $parameters)
+ {
+ try {
+ return call_user_func_array([$this->connection(), $method], $parameters);
+ } catch (Exception $exception) {
+ $exception = new ConnectionException($exception->getMessage());
+ $exception->setCredentials($this->connections->getServerCredentials());
+
+ throw $exception;
+ }
+ }
+}
diff --git a/src/Rocketeer/Services/CredentialsGatherer.php b/src/Rocketeer/Services/CredentialsGatherer.php
new file mode 100644
index 000000000..429f463f1
--- /dev/null
+++ b/src/Rocketeer/Services/CredentialsGatherer.php
@@ -0,0 +1,218 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Services;
+
+use Illuminate\Support\Arr;
+use Rocketeer\Traits\HasLocator;
+
+class CredentialsGatherer
+{
+ use HasLocator;
+
+ /**
+ * Get the Repository's credentials
+ */
+ public function getRepositoryCredentials()
+ {
+ // Check for repository credentials
+ $repositoryCredentials = $this->connections->getRepositoryCredentials();
+
+ // Build credentials array
+ // null values are considered non required
+ $credentials = array(
+ 'repository' => true,
+ 'username' => !is_null(Arr::get($repositoryCredentials, 'username', '')),
+ 'password' => !is_null(Arr::get($repositoryCredentials, 'password', '')),
+ );
+
+ // If we didn't specify a login/password ask for both the first time
+ if ($this->connections->needsCredentials()) {
+ // Else assume the repository is passwordless and only ask again for username
+ $credentials += ['username' => true, 'password' => true];
+ }
+
+ // Gather credentials
+ $credentials = $this->gatherCredentials($credentials, $repositoryCredentials, 'repository');
+
+ // Save them to local storage and runtime configuration
+ $this->localStorage->set('credentials', $credentials);
+ foreach ($credentials as $key => $credential) {
+ $this->config->set('rocketeer::scm.'.$key, $credential);
+ }
+ }
+
+ /**
+ * Get the LocalStorage's credentials
+ */
+ public function getServerCredentials()
+ {
+ if ($connections = $this->command->option('on')) {
+ $this->connections->setConnections($connections);
+ }
+
+ // Check for configured connections
+ $availableConnections = $this->connections->getAvailableConnections();
+ $activeConnections = $this->connections->getConnections();
+
+ // If we didn't set any connection, ask for them
+ if (!$activeConnections || empty($availableConnections)) {
+ $connectionName = $this->command->askWith('No connections have been set, please create one:', 'production');
+ $this->getConnectionCredentials($connectionName);
+
+ return;
+ }
+
+ // Else loop through the connections and fill in credentials
+ foreach ($activeConnections as $connectionName) {
+ $servers = Arr::get($availableConnections, $connectionName.'.servers');
+ $servers = array_keys($servers);
+ foreach ($servers as $server) {
+ $this->getConnectionCredentials($connectionName, $server);
+ }
+ }
+ }
+
+ /**
+ * Verifies and stores credentials for the given connection name
+ *
+ * @param string $connectionName
+ * @param integer|null $server
+ */
+ protected function getConnectionCredentials($connectionName, $server = null)
+ {
+ // Get the available connections
+ $connections = $this->connections->getAvailableConnections();
+
+ // Get the credentials for the asked connection
+ $connection = $connectionName.'.servers';
+ $connection = !is_null($server) ? $connection.'.'.$server : $connection;
+ $connection = Arr::get($connections, $connection, []);
+
+ // Update connection name
+ $handle = $this->connections->getHandle($connectionName, $server);
+
+ // Gather common credentials
+ $credentials = $this->gatherCredentials(array(
+ 'host' => true,
+ 'username' => true,
+ 'password' => false,
+ 'keyphrase' => null,
+ 'key' => false,
+ 'agent' => false,
+ ), $connection, $handle);
+
+ // Get password or key
+ $credentials = $this->getConnectionAuthentication($credentials, $handle);
+
+ // Save credentials
+ $this->connections->syncConnectionCredentials($connectionName, $credentials, $server);
+ $this->connections->setConnection($connectionName);
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// HELPERS ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Smart fill-in of the key/password of a connection
+ *
+ * @param string[] $credentials
+ * @param string $handle
+ *
+ * @return string[]
+ */
+ protected function getConnectionAuthentication(array $credentials, $handle)
+ {
+ // Cancel if already provided
+ if ($credentials['password'] || $credentials['key']) {
+ return $credentials;
+ }
+
+ // Get which type of authentication to use
+ $types = ['key', 'password'];
+ $keyPath = $this->paths->getDefaultKeyPath();
+ $type = $this->command->askWith('No password or SSH key is set for ['.$handle.'], which would you use?', 'key', $types);
+
+ // Gather the credentials for each
+ switch ($type) {
+ case 'key':
+ $credentials['key'] = $this->command->option('key') ?: $this->command->askWith('Please enter the full path to your key', $keyPath);
+ $credentials['keyphrase'] = $this->gatherCredential($handle, 'keyphrase', 'If a keyphrase is required, provide it');
+ break;
+
+ case 'password':
+ $credentials['password'] = $this->gatherCredential($handle, 'password');
+ break;
+ }
+
+ return $credentials;
+ }
+
+ /**
+ * Loop through credentials and store the missing ones
+ *
+ * @param boolean[] $credentials
+ * @param string[] $current
+ * @param string $handle
+ *
+ * @return string[]
+ */
+ protected function gatherCredentials($credentials, $current, $handle)
+ {
+ // Loop through credentials and ask missing ones
+ foreach ($credentials as $credential => $required) {
+ $$credential = $this->getCredential($current, $credential);
+ if ($required && !$$credential) {
+ $$credential = $this->gatherCredential($handle, $credential);
+ }
+ }
+
+ // Reform array
+ $credentials = compact(array_keys($credentials));
+
+ return $credentials;
+ }
+
+ /**
+ * Look for a credential in the flags or ask for it
+ *
+ * @param string $handle
+ * @param string $credential
+ * @param string|null $question
+ *
+ * @return string
+ */
+ protected function gatherCredential($handle, $credential, $question = null)
+ {
+ $question = $question ?: 'No '.$credential.' is set for ['.$handle.'], please provide one:';
+ $option = $this->command->option($credential);
+ $method = $credential == 'password' ? 'askSecretly' : 'askWith';
+
+ return $option ?: $this->command->$method($question);
+ }
+
+ /**
+ * Check if a credential needs to be filled
+ *
+ * @param string[] $credentials
+ * @param string $credential
+ *
+ * @return string
+ */
+ protected function getCredential($credentials, $credential)
+ {
+ $value = Arr::get($credentials, $credential);
+ if (substr($value, 0, 1) === '{') {
+ return;
+ }
+
+ return $value ?: $this->command->option($credential);
+ }
+}
diff --git a/src/Rocketeer/Services/Display/QueueExplainer.php b/src/Rocketeer/Services/Display/QueueExplainer.php
new file mode 100644
index 000000000..e548a9567
--- /dev/null
+++ b/src/Rocketeer/Services/Display/QueueExplainer.php
@@ -0,0 +1,206 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Services\Display;
+
+use Closure;
+use Rocketeer\Traits\HasLocator;
+
+/**
+ * Gives some insight into what task is executing,
+ * what it's doing, what its parent is, etc.
+ *
+ * @author Maxime Fabre
+ */
+class QueueExplainer
+{
+ use HasLocator;
+
+ /**
+ * The level at which to display statuses
+ *
+ * @type integer
+ */
+ public $level = 0;
+
+ /**
+ * Length of the longest handle to display
+ *
+ * @type integer
+ */
+ protected $longest;
+
+ //////////////////////////////////////////////////////////////////////
+ /////////////////////////////// STATUS ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Execute a task in a level below
+ *
+ * @param Closure $callback
+ *
+ * @return mixed
+ */
+ public function displayBelow(Closure $callback)
+ {
+ if (!$this->hasCommand()) {
+ return $callback();
+ }
+
+ $this->level++;
+ $results = $callback();
+ $this->level--;
+
+ return $results;
+ }
+
+ /**
+ * Display a status
+ *
+ * @param string|null $info
+ * @param string|null $details
+ * @param string|null $origin
+ * @param float|null $time
+ */
+ public function display($info = null, $details = null, $origin = null, $time = null)
+ {
+ if (!$this->hasCommand()) {
+ return;
+ }
+
+ // Build handle
+ $comment = $this->getTree();
+
+ // Add details
+ if ($info) {
+ $comment .= ' '.$info.'';
+ }
+ if ($details) {
+ $comment .= ' ('.$details.')';
+ }
+ if ($origin) {
+ $comment .= ' fired by '.$origin.'';
+ }
+ if ($time) {
+ $comment .= ' [~'.$time.'s]';
+ }
+
+ $this->command->line($comment);
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// PROGRESS //////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Format and send a message by the command
+ *
+ * @param string $message
+ * @param string|null $color
+ *
+ * @return string|null
+ */
+ public function line($message, $color = null)
+ {
+ if (!$this->hasCommand()) {
+ return;
+ }
+
+ // Format and pass to Command
+ $message = $color ? sprintf('%s', $color, $message, $color) : $message;
+ $message = $this->getTree('==').'=> '.$message;
+ $this->command->line($message);
+
+ return $message;
+ }
+
+ /**
+ * @param string $message
+ *
+ * @return string|null
+ */
+ public function success($message)
+ {
+ return $this->line($message, 'green');
+ }
+
+ /**
+ * @param string $message
+ *
+ * @return string|null
+ */
+ public function error($message)
+ {
+ return $this->line($message, 'red');
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// HELPERS ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get the longest size an handle can have
+ *
+ * @return integer
+ */
+ protected function getLongestSize()
+ {
+ if ($this->longest) {
+ return $this->longest;
+ }
+
+ // Build possible handles
+ $strings = [];
+ $connections = (array) $this->connections->getAvailableConnections();
+ $stages = (array) $this->connections->getStages();
+ foreach ($connections as $connection => $servers) {
+ foreach ($stages as $stage) {
+ $strings[] = $connection.'/'.count($servers).'/'.$stage;
+ }
+ }
+
+ // Get longest string
+ $strings = array_map('strlen', $strings);
+ $strings = $strings ? max($strings) : 0;
+
+ // Cache value
+ $this->longest = $strings + 1;
+
+ return $this->longest;
+ }
+
+ /**
+ * @param string $dashes
+ *
+ * @return string
+ */
+ protected function getTree($dashes = '--')
+ {
+ // Build handle
+ $numberConnections = count($this->connections->getAvailableConnections());
+ $numberStages = count($this->connections->getStages());
+
+ $tree = null;
+ if ($numberConnections > 1 || $numberStages > 1) {
+ $handle = $this->connections->getHandle();
+ $spacing = $this->getLongestSize() - strlen($handle);
+ $spacing = $spacing < 1 ? 1 : $spacing;
+ $spacing = str_repeat(' ', $spacing);
+
+ // Build tree and command
+ $tree .= sprintf('%s%s', $handle, $spacing);
+ }
+
+ // Add tree
+ $dashes = $this->level ? str_repeat($dashes, $this->level) : null;
+ $tree .= '|'.$dashes;
+
+ return $tree;
+ }
+}
diff --git a/src/Rocketeer/Services/Display/QueueTimer.php b/src/Rocketeer/Services/Display/QueueTimer.php
new file mode 100644
index 000000000..f3f2f82b3
--- /dev/null
+++ b/src/Rocketeer/Services/Display/QueueTimer.php
@@ -0,0 +1,111 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Services\Display;
+
+use Closure;
+use Rocketeer\Abstracts\AbstractTask;
+use Rocketeer\Traits\HasLocator;
+
+/**
+ * Saves the execution time of tasks and
+ * predicts their future ones
+ *
+ * @author Maxime Fabre
+ */
+class QueueTimer
+{
+ use HasLocator;
+
+ /**
+ * Time a task operation
+ *
+ * @param AbstractTask $task
+ * @param Closure $callback
+ *
+ * @return boolean|null
+ */
+ public function time(AbstractTask $task, Closure $callback)
+ {
+ // Start timer, execute callback, close timer
+ $timerStart = microtime(true);
+ $callback();
+ $time = round(microtime(true) - $timerStart, 4);
+
+ $this->saveTaskTime($task, $time);
+ }
+
+ /**
+ * Save the execution time of a task for future reference
+ *
+ * @param AbstractTask $task
+ * @param double $time
+ */
+ public function saveTaskTime(AbstractTask $task, $time)
+ {
+ // Don't save times in pretend mode
+ if ($this->getOption('pretend')) {
+ return;
+ }
+
+ // Append the new time to past ones
+ $past = $this->getTaskTimes($task);
+ $past[] = $time;
+
+ $this->saveTaskTimes($task, $past);
+ }
+
+ /**
+ * Compute the predicted execution time of a task
+ *
+ * @param AbstractTask $task
+ *
+ * @return double|null
+ */
+ public function getTaskTime(AbstractTask $task)
+ {
+ $past = $this->getTaskTimes($task);
+ if (!$past) {
+ return;
+ }
+
+ // Compute average time
+ $average = array_sum($past) / count($past);
+ $average = round($average, 2);
+
+ return $average;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////// SETTERS/GETTERS ///////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * @param AbstractTask $task
+ *
+ * @return array
+ */
+ protected function getTaskTimes(AbstractTask $task)
+ {
+ $handle = sprintf('times.%s', $task->getSlug());
+ $past = $this->localStorage->get($handle, []);
+
+ return $past;
+ }
+
+ /**
+ * @param AbstractTask $task
+ * @param double[] $past
+ */
+ protected function saveTaskTimes(AbstractTask $task, array $past)
+ {
+ $handle = sprintf('times.%s', $task->getSlug());
+ $this->localStorage->set($handle, $past);
+ }
+}
diff --git a/src/Rocketeer/Services/History/History.php b/src/Rocketeer/Services/History/History.php
new file mode 100644
index 000000000..4ec34f490
--- /dev/null
+++ b/src/Rocketeer/Services/History/History.php
@@ -0,0 +1,58 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Services\History;
+
+use Illuminate\Support\Collection;
+
+class History extends Collection
+{
+ /**
+ * Get the history, flattened
+ *
+ * @return string[]|string[][]
+ */
+ public function getFlattenedHistory()
+ {
+ return $this->getFlattened('history');
+ }
+
+ /**
+ * Get the output, flattened
+ *
+ * @return string[]|string[][]
+ */
+ public function getFlattenedOutput()
+ {
+ return $this->getFlattened('output');
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// HELPERS ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get a flattened list of a certain type
+ *
+ * @param string $type
+ *
+ * @return string[]|string[][]
+ */
+ protected function getFlattened($type)
+ {
+ $history = [];
+ foreach ($this->items as $class => $entries) {
+ $history = array_merge($history, $entries[$type]);
+ }
+
+ ksort($history);
+
+ return array_values($history);
+ }
+}
diff --git a/src/Rocketeer/Services/History/LogsHandler.php b/src/Rocketeer/Services/History/LogsHandler.php
new file mode 100644
index 000000000..e2ffcafd0
--- /dev/null
+++ b/src/Rocketeer/Services/History/LogsHandler.php
@@ -0,0 +1,113 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Services\History;
+
+use Illuminate\Support\Arr;
+use Rocketeer\Traits\HasLocator;
+
+/**
+ * Handles rotation of logs
+ */
+class LogsHandler
+{
+ use HasLocator;
+
+ /**
+ * Cache of the logs file to be written
+ *
+ * @type array
+ */
+ protected $logs = [];
+
+ /**
+ * The closure used to name logs
+ *
+ * @type \Closure
+ */
+ protected $namer;
+
+ /**
+ * Save something for the logs
+ *
+ * @param string $string
+ */
+ public function log($string)
+ {
+ // Create entry in the logs
+ $file = $this->getCurrentLogsFile();
+ if (!isset($this->logs[$file])) {
+ $this->logs[$file] = [];
+ }
+
+ $this->logs[$file][] = $string;
+ }
+
+ /**
+ * Write the stored logs
+ *
+ * @return array
+ */
+ public function write()
+ {
+ foreach ($this->logs as $file => $entries) {
+ $entries = Arr::flatten($entries);
+ if (!$this->files->exists($file)) {
+ $this->createLogsFile($file);
+ }
+
+ $this->files->put($file, implode(PHP_EOL, $entries));
+ }
+
+ return array_keys($this->logs);
+ }
+
+ /**
+ * Get the logs file being currently used
+ *
+ * @return string|false
+ */
+ public function getCurrentLogsFile()
+ {
+ if (!$this->namer) {
+ $this->namer = $this->config->get('rocketeer::logs');
+ }
+
+ // Cancel if invalid namer
+ if (!$this->namer || !is_callable($this->namer)) {
+ return false;
+ }
+
+ $namer = $this->namer;
+ $file = $namer($this->connections);
+ $file = $this->app['path.rocketeer.logs'].'/'.$file;
+
+ return $file;
+ }
+
+ /**
+ * Create a logs file if it doesn't exist
+ *
+ * @param string $file
+ */
+ protected function createLogsFile($file)
+ {
+ $directory = dirname($file);
+
+ // Create directory
+ if (!is_dir($directory)) {
+ $this->files->makeDirectory($directory, 0777, true);
+ }
+
+ // Create file
+ if (!file_exists($file)) {
+ $this->files->put($file, '');
+ }
+ }
+}
diff --git a/src/Rocketeer/Services/Ignition/Configuration.php b/src/Rocketeer/Services/Ignition/Configuration.php
new file mode 100644
index 000000000..be2d8fd28
--- /dev/null
+++ b/src/Rocketeer/Services/Ignition/Configuration.php
@@ -0,0 +1,285 @@
+
+*
+* For the full copyright and license information, please view the LICENSE
+* file that was distributed with this source code.
+*/
+namespace Rocketeer\Services\Ignition;
+
+use Closure;
+use Illuminate\Support\Arr;
+use Rocketeer\Facades;
+use Rocketeer\Traits\HasLocator;
+use Symfony\Component\Finder\Finder;
+use Symfony\Component\Finder\SplFileInfo;
+
+/**
+ * Ignites Rocketeer's custom configuration, tasks, events and paths
+ * depending on what Rocketeer is used on
+ *
+ * @author Maxime Fabre
+ */
+class Configuration
+{
+ use HasLocator;
+
+ /**
+ * Bind paths to the container
+ *
+ * @return void
+ */
+ public function bindPaths()
+ {
+ $this->bindBase();
+ $this->bindConfiguration();
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ///////////////////////// USER CONFIGURATION /////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Load the custom files (tasks, events, ...)
+ */
+ public function loadUserConfiguration()
+ {
+ $fileLoaders = function () {
+ $this->loadFileOrFolder('tasks');
+ $this->loadFileOrFolder('events');
+ };
+
+ // Defer loading of tasks and events or not
+ if (is_a($this->app, 'Illuminate\Foundation\Application')) {
+ $this->app->booted($fileLoaders);
+ } else {
+ $fileLoaders();
+ }
+
+ // Load plugins
+ $plugins = (array) $this->config->get('rocketeer::plugins');
+ $plugins = array_filter($plugins, 'class_exists');
+ foreach ($plugins as $plugin) {
+ $this->tasks->plugin($plugin);
+ }
+
+ // Merge contextual configurations
+ $this->mergeContextualConfigurations();
+ $this->mergePluginsConfiguration();
+ }
+
+ /**
+ * Merge the various contextual configurations defined in userland
+ */
+ public function mergeContextualConfigurations()
+ {
+ $this->mergeConfigurationFolders(['stages', 'connections'], function (SplFileInfo $file) {
+ return $this->computeHandleFromPath($file);
+ }, 'config.php');
+ }
+
+ /**
+ * Merge the plugin configurations defined in userland
+ */
+ public function mergePluginsConfiguration()
+ {
+ $this->mergeConfigurationFolders(['plugins'], function (SplFileInfo $file) {
+ $handle = basename(dirname($file->getPathname()));
+ $handle .= '::'.$file->getBasename('.php');
+
+ return $handle;
+ });
+ }
+
+ /**
+ * Export the configuration files
+ *
+ * @return string
+ */
+ public function exportConfiguration()
+ {
+ $source = $this->paths->unifyLocalSlashes(__DIR__.'/../../../config');
+ $source = realpath($source);
+ $destination = $this->paths->getConfigurationPath();
+
+ // Unzip configuration files
+ $this->files->copyDirectory($source, $destination);
+
+ return $destination;
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ ///////////////////////////// CONFIGURATION ////////////////////////
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Replace placeholders in configuration
+ *
+ * @param string $folder
+ * @param string[] $values
+ */
+ public function updateConfiguration($folder, array $values = array())
+ {
+ // Replace stub values in files
+ $files = $this->files->files($folder);
+ foreach ($files as $file) {
+ foreach ($values as $name => $value) {
+ $contents = str_replace('{'.$name.'}', $value, file_get_contents($file));
+ $this->files->put($file, $contents);
+ }
+ }
+
+ // Change repository in use
+ $application = Arr::get($values, 'application_name');
+ $this->localStorage->setFile($application);
+ }
+
+ /**
+ * Merge configuration files from userland
+ *
+ * @param array $folders
+ * @param callable $computeHandle
+ * @param string|null $exclude
+ */
+ protected function mergeConfigurationFolders(array $folders, Closure $computeHandle, $exclude = null)
+ {
+ // Cancel if not ignited yet
+ $configuration = $this->app['path.rocketeer.config'];
+ if (!is_dir($configuration)) {
+ return;
+ }
+
+ // Cancel if the subfolders don't exist
+ $existing = array_filter($folders, function ($path) use ($configuration) {
+ return is_dir($configuration.DS.$path);
+ });
+ if (!$existing) {
+ return;
+ }
+
+ // Get folders to glob
+ $folders = $this->paths->unifyLocalSlashes($configuration.'/{'.implode(',', $folders).'}/*');
+
+ // Gather custom files
+ $finder = new Finder();
+ $finder = $finder->in($folders);
+ if ($exclude) {
+ $finder = $finder->notName($exclude);
+ }
+
+ // Bind their contents to the "on" array
+ $files = $finder->files();
+ foreach ($files as $file) {
+ $contents = include $file->getPathname();
+ $handle = $computeHandle($file);
+
+ $this->config->set($handle, $contents);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ //////////////////////////////// PATHS /////////////////////////////
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Bind the base path to the Container
+ */
+ protected function bindBase()
+ {
+ if ($this->app->bound('path.base')) {
+ return;
+ }
+
+ $this->app->instance('path.base', getcwd());
+ }
+
+ /**
+ * Bind paths to the configuration files
+ */
+ protected function bindConfiguration()
+ {
+ // Bind path to the configuration directory
+ if ($this->isInsideLaravel()) {
+ $path = $this->paths->getConfigurationPath();
+ $storage = $this->paths->getStoragePath();
+ } else {
+ $path = $this->paths->getBasePath().'.rocketeer';
+
+ $storage = $path;
+ }
+
+ // Build paths
+ $paths = array(
+ 'config' => $path.'',
+ 'events' => $path.DS.'events',
+ 'plugins' => $path.DS.'plugins',
+ 'tasks' => $path.DS.'tasks',
+ 'logs' => $storage.DS.'logs',
+ );
+
+ foreach ($paths as $key => $file) {
+
+ // Check whether we provided a file or folder
+ if (!is_dir($file) && file_exists($file.'.php')) {
+ $file .= '.php';
+ }
+
+ // Use configuration in current folder if none found
+ $realpath = realpath('.').DS.basename($file);
+ if (!file_exists($file) && file_exists($realpath)) {
+ $file = $realpath;
+ }
+
+ $this->app->instance('path.rocketeer.'.$key, $file);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ /////////////////////////////// HELPERS ////////////////////////////
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Computes which configuration handle a config file should bind to
+ *
+ * @param SplFileInfo $file
+ *
+ * @return string
+ */
+ protected function computeHandleFromPath(SplFileInfo $file)
+ {
+ // Get realpath
+ $handle = $file->getRealpath();
+
+ // Format appropriately
+ $handle = str_replace($this->app['path.rocketeer.config'].DS, null, $handle);
+ $handle = str_replace('.php', null, $handle);
+ $handle = str_replace(DS, '.', $handle);
+
+ return sprintf('rocketeer::on.%s', $handle);
+ }
+
+ /**
+ * Load a file or its contents if a folder
+ *
+ * @param string $handle
+ */
+ protected function loadFileOrFolder($handle)
+ {
+ // Bind ourselves into the facade to avoid automatic resolution
+ Facades\Rocketeer::setFacadeApplication($this->app);
+
+ // If we have one unified tasks file, include it
+ $file = $this->app['path.rocketeer.'.$handle];
+ if (!is_dir($file) && file_exists($file)) {
+ include $file;
+ } // Else include its contents
+ elseif (is_dir($file)) {
+ $folder = glob($file.DS.'*.php');
+ foreach ($folder as $file) {
+ include $file;
+ }
+ }
+ }
+}
diff --git a/src/Rocketeer/Services/Ignition/Plugins.php b/src/Rocketeer/Services/Ignition/Plugins.php
new file mode 100644
index 000000000..e81d352b1
--- /dev/null
+++ b/src/Rocketeer/Services/Ignition/Plugins.php
@@ -0,0 +1,101 @@
+
+*
+* For the full copyright and license information, please view the LICENSE
+* file that was distributed with this source code.
+*/
+namespace Rocketeer\Services\Ignition;
+
+use Illuminate\Support\Arr;
+use Rocketeer\Traits\HasLocator;
+
+/**
+ * Publishes the plugin's configurations in user-land
+ *
+ * @author Maxime Fabre
+ */
+class Plugins
+{
+ use HasLocator;
+
+ /**
+ * Publishes a package's configuration
+ *
+ * @param string $package
+ *
+ * @return boolean|null
+ */
+ public function publish($package)
+ {
+ if ($this->isInsideLaravel()) {
+ return $this->publishLaravelConfiguration($package);
+ }
+
+ // Find the plugin's configuration
+ $paths = array(
+ $this->app['path.base'].'/vendor/%s/src/config',
+ $this->app['path.base'].'/vendor/%s/config',
+ $this->paths->getHomeFolder().'/.composer/vendor/%s/src/config',
+ $this->paths->getHomeFolder().'/.composer/vendor/%s/config',
+ );
+
+ // Check for the first configuration path that exists
+ $paths = array_filter($paths, function ($path) use ($package) {
+ return $this->files->isDirectory(sprintf($path, $package));
+ });
+ $paths = array_values($paths);
+
+ // Cancel if no valid paths
+ if (empty($paths)) {
+ return $this->command->error('No configuration found for '.$package);
+ }
+
+ return $this->publishConfiguration($paths[0]);
+ }
+
+ /**
+ * Publishes a configuration within a Laravel application
+ *
+ * @param string $package
+ *
+ * @return boolean
+ */
+ protected function publishLaravelConfiguration($package)
+ {
+ // Publish initial configuration
+ $this->artisan->call('config:publish', ['package' => $package]);
+
+ // Move under Rocketeer namespace
+ $path = $this->app['path'].'/config/packages/'.$package;
+ $destination = preg_replace('/packages\/([^\/]+)/', 'packages/rocketeers', $path);
+
+ return $this->files->move($path, $destination);
+ }
+
+ /**
+ * Publishes a configuration within a classic application
+ *
+ * @param string $path
+ *
+ * @return boolean
+ */
+ protected function publishConfiguration($path)
+ {
+ // Get the vendor and package
+ preg_match('/vendor\/([^\/]+)\/([^\/]+)/', $path, $handle);
+ $handle = (array) $handle;
+ $package = Arr::get($handle, 2);
+
+ // Compute and create the destination foldser
+ $destination = $this->app['path.rocketeer.config'];
+ $destination = $destination.'/plugins/rocketeers/'.$package;
+ if (!$this->files->isDirectory($destination)) {
+ $this->files->makeDirectory($destination, 0755, true);
+ }
+
+ return $this->files->copyDirectory($path, $destination);
+ }
+}
diff --git a/src/Rocketeer/Services/Pathfinder.php b/src/Rocketeer/Services/Pathfinder.php
new file mode 100644
index 000000000..03b1b26c7
--- /dev/null
+++ b/src/Rocketeer/Services/Pathfinder.php
@@ -0,0 +1,223 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Services;
+
+use Exception;
+use Illuminate\Support\Str;
+use Rocketeer\Traits\HasLocator;
+
+/**
+ * Locates folders and paths on the server and locally
+ *
+ * @author Maxime Fabre
+ */
+class Pathfinder
+{
+ use HasLocator;
+
+ //////////////////////////////////////////////////////////////////////
+ //////////////////////////////// LOCAL ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get a configured path
+ *
+ * @param string $path
+ *
+ * @return string
+ */
+ public function getPath($path)
+ {
+ return $this->rocketeer->getOption('paths.'.$path);
+ }
+
+ /**
+ * Get the path to the root folder of the application
+ *
+ * @return string
+ */
+ public function getHomeFolder()
+ {
+ $rootDirectory = $this->rocketeer->getOption('remote.root_directory');
+ $rootDirectory = Str::finish($rootDirectory, '/');
+ $appDirectory = $this->rocketeer->getOption('remote.app_directory') ?: $this->rocketeer->getApplicationName();
+
+ return $rootDirectory.$appDirectory;
+ }
+
+ /**
+ * Get the default path for the SSH key
+ *
+ * @return string
+ * @throws Exception
+ */
+ public function getDefaultKeyPath()
+ {
+ return $this->getUserHomeFolder().'/.ssh/id_rsa';
+ }
+
+ /**
+ * Get the path to the Rocketeer config folder in the users home
+ *
+ * @return string
+ */
+ public function getRocketeerConfigFolder()
+ {
+ return $this->getUserHomeFolder().'/.rocketeer';
+ }
+
+ /**
+ * Get the path to the users home folder
+ *
+ * @throws Exception
+ * @return string
+ */
+ public static function getUserHomeFolder()
+ {
+ // Get home folder if available (Unix)
+ if (!empty($_SERVER['HOME'])) {
+ return $_SERVER['HOME'];
+ // Else use the home drive (Windows)
+ } elseif (!empty($_SERVER['HOMEDRIVE']) && !empty($_SERVER['HOMEPATH'])) {
+ return $_SERVER['HOMEDRIVE'].$_SERVER['HOMEPATH'];
+ } else {
+ throw new Exception('Cannot determine user home directory.');
+ }
+ }
+
+ /**
+ * Get the base path
+ *
+ * @return string
+ */
+ public function getBasePath()
+ {
+ $base = $this->app['path.base'] ? $this->app['path.base'].'/' : '';
+ $base = $this->unifySlashes($base);
+
+ return $base;
+ }
+
+ /**
+ * Get the path to the configuration folder
+ *
+ * @return string
+ */
+ public function getConfigurationPath()
+ {
+ // Return path to Laravel configuration
+ if ($this->isInsideLaravel()) {
+ $configuration = $this->app['path'].'/config/packages/anahkiasen/rocketeer';
+ } else {
+ $configuration = $this->app['path.rocketeer.config'];
+ }
+
+ return $this->unifyLocalSlashes($configuration);
+ }
+
+ /**
+ * Get path to the storage folder
+ *
+ * @return string
+ */
+ public function getStoragePath()
+ {
+ // If no path is bound, default to the Rocketeer folder
+ if (!$this->app->bound('path.storage')) {
+ return '.rocketeer';
+ }
+
+ // Unify slashes
+ $storage = $this->app['path.storage'];
+ $storage = $this->unifySlashes($storage);
+ $storage = str_replace($this->getBasePath(), null, $storage);
+
+ return $storage;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ /////////////////////////////// SERVER ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get the path to a folder, taking into account application name and stage
+ *
+ * @param string|null $folder
+ *
+ * @return string
+ */
+ public function getFolder($folder = null)
+ {
+ $folder = $this->replacePatterns($folder);
+
+ $base = $this->getHomeFolder().'/';
+ $stage = $this->connections->getStage();
+ if ($folder && $stage) {
+ $base .= $stage.'/';
+ }
+ $folder = str_replace($base, null, $folder);
+
+ return $base.$folder;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// HELPERS ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Unify the slashes to the UNIX mode (forward slashes)
+ *
+ * @param string $path
+ *
+ * @return string
+ */
+ public function unifySlashes($path)
+ {
+ return str_replace('\\', '/', $path);
+ }
+
+ /**
+ * Unify paths to the local DS
+ *
+ * @param string $path
+ *
+ * @return string
+ */
+ public function unifyLocalSlashes($path)
+ {
+ return preg_replace('#(/|\\\)#', DS, $path);
+ }
+
+ /**
+ * Replace patterns in a folder path
+ *
+ * @param string $path
+ *
+ * @return string
+ */
+ public function replacePatterns($path)
+ {
+ $base = $this->getBasePath();
+
+ // Replace folder patterns
+ return preg_replace_callback('/\{[a-z\.]+\}/', function ($match) use ($base) {
+ $folder = substr($match[0], 1, -1);
+
+ // Replace paths from the container
+ if ($this->app->bound($folder)) {
+ $path = $this->app->make($folder);
+
+ return str_replace($base, null, $this->unifySlashes($path));
+ }
+
+ return false;
+ }, $path);
+ }
+}
diff --git a/src/Rocketeer/ReleasesManager.php b/src/Rocketeer/Services/ReleasesManager.php
similarity index 55%
rename from src/Rocketeer/ReleasesManager.php
rename to src/Rocketeer/Services/ReleasesManager.php
index aa34406c0..322237d49 100644
--- a/src/Rocketeer/ReleasesManager.php
+++ b/src/Rocketeer/Services/ReleasesManager.php
@@ -7,9 +7,12 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
-namespace Rocketeer;
+namespace Rocketeer\Services;
use Illuminate\Container\Container;
+use Illuminate\Support\Arr;
+use Rocketeer\Services\Storages\ServerStorage;
+use Rocketeer\Traits\HasLocator;
/**
* Provides informations and actions around releases
@@ -18,12 +21,7 @@
*/
class ReleasesManager
{
- /**
- * The IoC Container
- *
- * @var Container
- */
- protected $app;
+ use HasLocator;
/**
* Cache of the validation file
@@ -32,6 +30,27 @@ class ReleasesManager
*/
protected $state = array();
+ /**
+ * Cache of the releases
+ *
+ * @type array
+ */
+ public $releases;
+
+ /**
+ * The next release to come
+ *
+ * @type string
+ */
+ protected $nextRelease;
+
+ /**
+ * The storage
+ *
+ * @type ServerStorage
+ */
+ protected $storage;
+
/**
* Build a new ReleasesManager
*
@@ -39,8 +58,9 @@ class ReleasesManager
*/
public function __construct(Container $app)
{
- $this->app = $app;
- $this->state = $this->getValidationFile();
+ $this->app = $app;
+ $this->storage = new ServerStorage($app, 'state');
+ $this->state = $this->getValidationFile();
}
////////////////////////////////////////////////////////////////////
@@ -50,56 +70,65 @@ public function __construct(Container $app)
/**
* Get all the releases on the server
*
- * @return array
+ * @return integer[]
*/
public function getReleases()
{
// Get releases on server
- $releases = $this->app['rocketeer.bash']->listContents($this->getReleasesPath());
- if (is_array($releases)) {
+ if (is_null($this->releases)) {
+ $releases = $this->getReleasesPath();
+ $releases = (array) $this->bash->listContents($releases);
+
+ // Filter and sort releases
+ $releases = array_filter($releases, function ($release) {
+ return $this->isRelease($release);
+ });
+
rsort($releases);
+
+ $this->releases = (array) $releases;
}
- return $releases;
+ return $this->releases;
}
/**
- * Get an array of deprecated releases
+ * Get an array of non-current releases
*
- * @return array
+ * @return integer[]
*/
- public function getDeprecatedReleases()
+ public function getNonCurrentReleases()
{
- $releases = (array) $this->getReleases();
- $maxReleases = $this->app['config']->get('rocketeer::remote.keep_releases');
-
- return array_slice($releases, $maxReleases);
+ return $this->getDeprecatedReleases(1);
}
/**
- * Get an array of invalid releases
+ * Get an array of deprecated releases
*
- * @return array
+ * @param integer|null $treshold
+ *
+ * @return integer[]
*/
- public function getInvalidReleases()
+ public function getDeprecatedReleases($treshold = null)
{
- $releases = (array) $this->getReleases();
- $invalid = array_diff($this->state, array_filter($this->state));
- $invalid = array_keys($invalid);
+ $releases = $this->getReleases();
+ $treshold = $treshold ?: $this->config->get('rocketeer::remote.keep_releases');
- return array_intersect($releases, $invalid);
+ return array_slice($releases, $treshold);
}
/**
- * Get an array of non-current releases
+ * Get an array of invalid releases
*
- * @return array
+ * @return integer[]
*/
- public function getNonCurrentReleases()
+ public function getInvalidReleases()
{
- $releases = (array) $this->getReleases();
+ $releases = $this->getReleases();
+ $invalid = array_diff($this->state, array_filter($this->state));
+ $invalid = array_keys($invalid);
- return array_slice($releases, 1);
+ return array_intersect($releases, $invalid);
}
////////////////////////////////////////////////////////////////////
@@ -113,25 +142,25 @@ public function getNonCurrentReleases()
*/
public function getReleasesPath()
{
- return $this->app['rocketeer.rocketeer']->getFolder('releases');
+ return $this->paths->getFolder('releases');
}
/**
* Get the path to a release
*
- * @param integer $release
+ * @param string $release
*
* @return string
*/
public function getPathToRelease($release)
{
- return $this->app['rocketeer.rocketeer']->getFolder('releases/'.$release);
+ return $this->paths->getFolder('releases/'.$release);
}
/**
* Get the path to the current release
*
- * @param string $folder A folder in the release
+ * @param string|null $folder A folder in the release
*
* @return string
*/
@@ -155,13 +184,10 @@ public function getCurrentReleasePath($folder = null)
*/
public function getValidationFile()
{
- // Get the contents of the validation file
- $file = $this->app['rocketeer.rocketeer']->getFolder('state.json');
- $file = $this->app['rocketeer.bash']->getFile($file) ?: '{}';
- $file = (array) json_decode($file, true);
+ $file = $this->storage->get();
// Fill the missing releases
- $releases = (array) $this->getReleases();
+ $releases = $this->getReleases();
$releases = array_fill_keys($releases, false);
// Sort entries
@@ -175,34 +201,20 @@ public function getValidationFile()
return $releases;
}
- /**
- * Update the contents of the validation file
- *
- * @param array $validation
- *
- * @return void
- */
- public function saveValidationFile(array $validation)
- {
- $file = $this->app['rocketeer.rocketeer']->getFolder('state.json');
- $this->app['rocketeer.bash']->putFile($file, json_encode($validation));
-
- $this->state = $validation;
- }
-
/**
* Mark a release as valid
*
- * @param integer $release
- *
- * @return void
+ * @param string|null $release
*/
- public function markReleaseAsValid($release)
+ public function markReleaseAsValid($release = null)
{
- $validation = $this->getValidationFile();
- $validation[$release] = true;
+ $release = $release ?: $this->getCurrentRelease();
- return $this->saveValidationFile($validation);
+ // If the release is not null, mark it as valid
+ if ($release) {
+ $this->state[$release] = true;
+ $this->storage->set($release, true);
+ }
}
/**
@@ -214,69 +226,30 @@ public function markReleaseAsValid($release)
*/
public function checkReleaseState($release)
{
- return array_get($this->state, $release, true);
+ return Arr::get($this->state, $release, true);
}
////////////////////////////////////////////////////////////////////
/////////////////////////// CURRENT RELEASE ////////////////////////
////////////////////////////////////////////////////////////////////
- /**
- * Sanitize a possible release
- *
- * @param string $release
- *
- * @return string
- */
- protected function sanitizeRelease($release)
- {
- return strlen($release) === 14 ? $release : null;
- }
-
- /**
- * Get where to store the current release
- *
- * @return string
- */
- protected function getCurrentReleaseKey()
- {
- $key = 'current_release';
-
- // Get the scopes
- $connection = $this->app['rocketeer.rocketeer']->getConnection();
- $stage = $this->app['rocketeer.rocketeer']->getStage();
- $scopes = array($connection, $stage);
- foreach ($scopes as $scope) {
- $key .= $scope ? '.'.$scope : '';
- }
-
- return $key;
- }
-
/**
* Get the current release
*
- * @return string
+ * @return string|integer|null
*/
public function getCurrentRelease()
{
- // If we have saved the last deployed release, return that
- $cached = $this->app['rocketeer.server']->getValue($this->getCurrentReleaseKey());
- if ($cached) {
- return $this->sanitizeRelease($cached);
- }
-
- // Else get and save last deployed release
- $lastDeployed = array_get($this->getReleases(), 0);
- $this->updateCurrentRelease($lastDeployed);
+ $current = Arr::get($this->getReleases(), 0);
+ $current = $this->sanitizeRelease($current);
- return $this->sanitizeRelease($lastDeployed);
+ return $this->nextRelease ?: $current;
}
/**
* Get the release before the current one
*
- * @param string $release A release name
+ * @param string|null $release A release name
*
* @return string
*/
@@ -288,30 +261,67 @@ public function getPreviousRelease($release = null)
// Get the one before that, or default to current
$key = array_search($current, $releases);
+ $key = !is_int($key) ? -1 : $key;
$next = 1;
do {
- $release = array_get($releases, $key + $next);
+ $release = Arr::get($releases, $key + $next);
$next++;
- } while (!$this->checkReleaseState($release));
+ } while (!$this->checkReleaseState($release) && isset($this->state[$release]));
return $release ?: $current;
}
/**
- * Update the current release
+ * Get the next release to come
*
- * @param string $release A release name
- *
- * @return void
+ * @return string
*/
- public function updateCurrentRelease($release = null)
+ public function getNextRelease()
{
- if (!$release) {
- $release = $this->app['rocketeer.bash']->getTimestamp();
+ if (!$this->nextRelease) {
+ $this->nextRelease = $this->bash->getTimestamp();
}
- $this->app['rocketeer.server']->setValue($this->getCurrentReleaseKey(), $release);
+ return $this->nextRelease;
+ }
+
+ /**
+ * Change the release to come
+ *
+ * @param string $release
+ */
+ public function setNextRelease($release)
+ {
+ $this->nextRelease = $release;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// HELPERS ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Sanitize a possible release
+ *
+ * @param string|integer $release
+ *
+ * @return string|integer|null
+ */
+ protected function sanitizeRelease($release)
+ {
+ return $this->isRelease($release) ? $release : null;
+ }
+
+ /**
+ * Check if it quacks like a duck
+ *
+ * @param string|integer $release
+ *
+ * @return bool
+ */
+ protected function isRelease($release)
+ {
+ $release = (string) $release;
- return $release;
+ return (bool) preg_match('#[0-9]{14}#', $release);
}
}
diff --git a/src/Rocketeer/Services/StepsBuilder.php b/src/Rocketeer/Services/StepsBuilder.php
new file mode 100644
index 000000000..e670505bb
--- /dev/null
+++ b/src/Rocketeer/Services/StepsBuilder.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 Rocketeer\Services;
+
+/**
+ * Saves in an array methods the call signatures
+ * of the methods called on it
+ *
+ * @author Maxime Fabre
+ */
+class StepsBuilder
+{
+ /**
+ * The extisting steps
+ *
+ * @type array
+ */
+ protected $steps = [];
+
+ /**
+ * Add a step
+ *
+ * @param string $name
+ * @param array $arguments
+ */
+ public function __call($name, $arguments)
+ {
+ $this->steps[] = [$name, $arguments];
+ }
+
+ /**
+ * Get and clear the steps
+ *
+ * @return array
+ */
+ public function pullSteps()
+ {
+ $steps = $this->steps;
+
+ $this->steps = [];
+
+ return $steps;
+ }
+
+ /**
+ * Get the steps to execute
+ *
+ * @return array
+ */
+ public function getSteps()
+ {
+ return $this->steps;
+ }
+}
diff --git a/src/Rocketeer/Services/Storages/LocalStorage.php b/src/Rocketeer/Services/Storages/LocalStorage.php
new file mode 100644
index 000000000..cfeb4ff8b
--- /dev/null
+++ b/src/Rocketeer/Services/Storages/LocalStorage.php
@@ -0,0 +1,248 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Services\Storages;
+
+use Exception;
+use Illuminate\Container\Container;
+use Rocketeer\Abstracts\AbstractStorage;
+use Rocketeer\Interfaces\StorageInterface;
+
+/**
+ * Provides and persists informations in local
+ *
+ * @author Maxime Fabre
+ */
+class LocalStorage extends AbstractStorage implements StorageInterface
+{
+ /**
+ * The current hash in use
+ *
+ * @var string
+ */
+ protected $hash;
+
+ /**
+ * The folder where file resides
+ *
+ * @type string
+ */
+ protected $folder;
+
+ /**
+ * Build a new LocalStorage
+ *
+ * @param Container $app
+ * @param string $file
+ * @param string|null $folder
+ */
+ public function __construct(Container $app, $file = 'deployments', $folder = null)
+ {
+ parent::__construct($app, $file);
+
+ // Create personal storage if necessary
+ if (!$this->app->bound('path.storage')) {
+ $folder = $this->paths->getRocketeerConfigFolder();
+ $this->files->makeDirectory($folder, 0755, false, true);
+ }
+
+ // Set path to storage folder
+ $this->folder = $folder ?: $this->app['path.storage'].DS.'meta';
+
+ // Flush if necessary
+ if ($this->shouldFlush()) {
+ $this->destroy();
+ }
+
+ $this->set('hash', $this->getHash());
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ //////////////////////////////// SALTS /////////////////////////////
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get the current salt in use
+ *
+ * @return string
+ */
+ public function getHash()
+ {
+ // Return cached hash if any
+ if ($this->hash) {
+ return $this->hash;
+ }
+
+ // Get the contents of the configuration folder
+ $salt = '';
+ $folder = $this->paths->getConfigurationPath();
+ $files = $this->files->glob($folder.'/*.php');
+
+ // Remove custom files and folders
+ $handles = array('events', 'tasks');
+ foreach ($handles as $handle) {
+ $path = $this->app['path.rocketeer.'.$handle];
+ $index = array_search($path, $files);
+ if ($index !== false) {
+ unset($files[$index]);
+ }
+ }
+
+ // Compute the salts
+ foreach ($files as $file) {
+ $file = $this->files->getRequire($file);
+ $salt .= json_encode($file);
+ }
+
+ // Cache it
+ $this->hash = md5($salt);
+
+ return $this->hash;
+ }
+
+ /**
+ * Flushes the repository if required
+ *
+ * @return boolean
+ */
+ public function shouldFlush()
+ {
+ $currentHash = $this->get('hash');
+
+ return $currentHash && $currentHash !== $this->getHash();
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ /////////////////////////// REMOTE VARIABLES ///////////////////////
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get the directory separators on the remove server
+ *
+ * @return string
+ */
+ public function getSeparator()
+ {
+ // If manually set by the user, return it
+ $user = $this->rocketeer->getOption('remote.variables.directory_separator');
+ if ($user) {
+ return $user;
+ }
+
+ return $this->get('directory_separator', function () {
+ $separator = $this->bash->runLast('php -r "echo DIRECTORY_SEPARATOR;"');
+
+ // Throw an Exception if we receive invalid output
+ if (strlen($separator) > 1) {
+ throw new Exception(
+ 'An error occured while fetching the directory separators used on the server.'.PHP_EOL.
+ 'Output received was : '.$separator
+ );
+ }
+
+ // Cache separator
+ $this->set('directory_separator', $separator);
+
+ return $separator;
+ });
+ }
+
+ /**
+ * Get the remote line endings on the remove server
+ *
+ * @return string
+ */
+ public function getLineEndings()
+ {
+ // If manually set by the user, return it
+ $user = $this->rocketeer->getOption('remote.variables.line_endings');
+ if ($user) {
+ return $user;
+ }
+
+ return $this->get('line_endings', function () {
+ $endings = $this->bash->runRaw('php -r "echo PHP_EOL;"');
+ $this->set('line_endings', $endings);
+
+ return $endings ?: PHP_EOL;
+ });
+ }
+
+ /**
+ * Change the folder in use
+ *
+ * @param string $folder
+ */
+ public function setFolder($folder)
+ {
+ $this->folder = $folder;
+ }
+
+ /**
+ * @return string
+ */
+ public function getFolder()
+ {
+ return $this->folder;
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ ////////////////////////// REPOSITORY FILE /////////////////////////
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get the full path to the file
+ *
+ * @return string
+ */
+ public function getFilepath()
+ {
+ return $this->folder.'/'.$this->file.'.json';
+ }
+
+ /**
+ * Get the contents of a file
+ *
+ * @return array
+ */
+ protected function getContents()
+ {
+ // Cancel if the file doesn't exist
+ if (!$this->files->exists($this->getFilepath())) {
+ return [];
+ }
+
+ // Get and parse file
+ $contents = $this->files->get($this->getFilepath());
+ $contents = json_decode($contents, true);
+
+ return $contents;
+ }
+
+ /**
+ * Save the contents of a file
+ *
+ * @param array $contents
+ */
+ protected function saveContents($contents)
+ {
+ // Yup. Don't look at me like that.
+ @$this->files->put($this->getFilepath(), json_encode($contents));
+ }
+
+ /**
+ * Destroy the file
+ *
+ * @return boolean
+ */
+ public function destroy()
+ {
+ return $this->files->delete($this->getFilepath());
+ }
+}
diff --git a/src/Rocketeer/Services/Storages/ServerStorage.php b/src/Rocketeer/Services/Storages/ServerStorage.php
new file mode 100644
index 000000000..632044fb6
--- /dev/null
+++ b/src/Rocketeer/Services/Storages/ServerStorage.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Services\Storages;
+
+use Rocketeer\Abstracts\AbstractStorage;
+use Rocketeer\Interfaces\StorageInterface;
+
+/**
+ * Provides and persists informations on the server
+ *
+ * @author Maxime Fabre
+ */
+class ServerStorage extends AbstractStorage implements StorageInterface
+{
+ /**
+ * Destroy the file
+ *
+ * @return boolean
+ */
+ public function destroy()
+ {
+ $this->bash->removeFolder($this->getFilepath());
+
+ return true;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// HELPERS ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get the full path to the file
+ *
+ * @return string
+ */
+ public function getFilepath()
+ {
+ return $this->paths->getFolder($this->file.'.json');
+ }
+
+ /**
+ * Get the contents of the file
+ *
+ * @return array
+ */
+ protected function getContents()
+ {
+ $file = $this->getFilepath();
+ $file = $this->bash->getFile($file) ?: '{}';
+ $file = (array) json_decode($file, true);
+
+ return $file;
+ }
+
+ /**
+ * Save the contents of the file
+ *
+ * @param array $contents
+ */
+ protected function saveContents($contents)
+ {
+ $file = $this->getFilepath();
+ $this->bash->putFile($file, json_encode($contents));
+ }
+}
diff --git a/src/Rocketeer/Services/Tasks/Job.php b/src/Rocketeer/Services/Tasks/Job.php
new file mode 100644
index 000000000..488bf9e21
--- /dev/null
+++ b/src/Rocketeer/Services/Tasks/Job.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Services\Tasks;
+
+use Illuminate\Support\Fluent;
+
+/**
+ * A job storing where a task/multiple tasks need to be executed
+ *
+ * @property string connection
+ * @property integer server
+ * @property string|null stage
+ * @property \Rocketeer\Abstracts\AbstractTask[] queue
+ * @author Maxime Fabre
+ */
+class Job extends Fluent
+{
+ // ...
+}
diff --git a/src/Rocketeer/Services/Tasks/Pipeline.php b/src/Rocketeer/Services/Tasks/Pipeline.php
new file mode 100644
index 000000000..0176a9d89
--- /dev/null
+++ b/src/Rocketeer/Services/Tasks/Pipeline.php
@@ -0,0 +1,70 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Services\Tasks;
+
+use Illuminate\Support\Collection;
+
+/**
+ * A class representing a pipeline of jobs
+ * to be executed
+ *
+ * @author Maxime Fabre
+ */
+class Pipeline extends Collection
+{
+ /**
+ * The stored results of each task
+ *
+ * @type array
+ */
+ protected $results = [];
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// RESULTS ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Check if the pipeline failed
+ *
+ * @return boolean
+ */
+ public function failed()
+ {
+ $succeeded = count(array_filter($this->results));
+
+ return $succeeded != $this->count();
+ }
+
+ /**
+ * Check if the pipeline ran its course
+ *
+ * @return boolean
+ */
+ public function succeeded()
+ {
+ return !$this->failed();
+ }
+
+ /**
+ * @return array
+ */
+ public function getResults()
+ {
+ return $this->results;
+ }
+
+ /**
+ * @param array $results
+ */
+ public function setResults($results)
+ {
+ $this->results = $results;
+ }
+}
diff --git a/src/Rocketeer/Services/Tasks/TasksBuilder.php b/src/Rocketeer/Services/Tasks/TasksBuilder.php
new file mode 100644
index 000000000..66bf9a826
--- /dev/null
+++ b/src/Rocketeer/Services/Tasks/TasksBuilder.php
@@ -0,0 +1,339 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Services\Tasks;
+
+use Closure;
+use Illuminate\Support\Str;
+use Rocketeer\Abstracts\AbstractTask;
+use Rocketeer\Binaries\AnonymousBinary;
+use Rocketeer\Exceptions\TaskCompositionException;
+use Rocketeer\Traits\HasLocator;
+
+/**
+ * Handles creating tasks from strings, closures, AbstractTask children, etc.
+ *
+ * @author Maxime Fabre
+ */
+class TasksBuilder
+{
+ use HasLocator;
+
+ /**
+ * Build a binary
+ *
+ * @param string $binary
+ *
+ * @return \Rocketeer\Abstracts\AbstractBinary|\Rocketeer\Abstracts\AbstractPackageManager
+ */
+ public function buildBinary($binary)
+ {
+ $class = $this->findQualifiedName($binary, array(
+ 'Rocketeer\Binaries\PackageManagers\%s',
+ 'Rocketeer\Binaries\%s',
+ ));
+
+ // If there is a class by that name
+ if ($class) {
+ return new $class($this->app);
+ }
+
+ // Else wrap the command in an AnonymousBinary
+ $anonymous = new AnonymousBinary($this->app);
+ $anonymous->setBinary($binary);
+
+ return $anonymous;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// COMMANDS //////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Build the command bound to a task
+ *
+ * @param string|AbstractTask $task
+ * @param string|null $slug
+ *
+ * @return \Rocketeer\Abstracts\AbstractCommand
+ */
+ public function buildCommand($task, $slug = null)
+ {
+ // Build the task instance
+ try {
+ $instance = $this->buildTask($task);
+ } catch (TaskCompositionException $exception) {
+ $instance = null;
+ }
+
+ // Get the command name
+ $name = $instance ? $instance->getName() : null;
+ $name = is_string($task) ? $task : $name;
+ $command = $this->findQualifiedName($name, array(
+ 'Rocketeer\Console\Commands\%sCommand',
+ 'Rocketeer\Console\Commands\BaseTaskCommand',
+ ));
+
+ $command = new $command($instance, $slug);
+ $command->setLaravel($this->app);
+
+ return $command;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ///////////////////////////// STRATEGIES /////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Build a strategy
+ *
+ * @param string $strategy
+ * @param string|null $concrete
+ *
+ * @return \Rocketeer\Abstracts\Strategies\AbstractStrategy
+ */
+ public function buildStrategy($strategy, $concrete = null)
+ {
+ // If we passed a concrete implementation
+ // build it, otherwise get the bound one
+ $handle = strtolower($strategy);
+ if ($concrete) {
+ $concrete = $this->findQualifiedName($concrete, array(
+ 'Rocketeer\Strategies\\'.ucfirst($strategy).'\%sStrategy',
+ ));
+ if (!$concrete) {
+ return false;
+ }
+
+ return new $concrete($this->app);
+ }
+
+ return $this->app['rocketeer.strategies.'.$handle];
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ //////////////////////////////// TASKS /////////////////////////////
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Build an array of tasks
+ *
+ * @param array $tasks
+ *
+ * @return array
+ */
+ public function buildTasks(array $tasks)
+ {
+ return array_map([$this, 'buildTask'], $tasks);
+ }
+
+ /**
+ * Build a task from anything
+ *
+ * @param string|Closure|AbstractTask $task
+ * @param string|null $name
+ * @param string|null $description
+ *
+ * @throws \Rocketeer\Exceptions\TaskCompositionException
+ * @return AbstractTask
+ */
+ public function buildTask($task, $name = null, $description = null)
+ {
+ // Compose the task from their various types
+ $task = $this->composeTask($task);
+
+ // If the built class is invalid, cancel
+ if (!$task instanceof AbstractTask) {
+ throw new TaskCompositionException('Class '.get_class($task).' is not a valid task');
+ }
+
+ // Set task properties
+ $task->setName($name);
+ $task->setDescription($description);
+
+ return $task;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// COMPOSING /////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Compose a Task from its various types
+ *
+ * @param string|Closure|AbstractTask $task
+ *
+ * @return mixed|AbstractTask
+ * @throws \Rocketeer\Exceptions\TaskCompositionException
+ */
+ protected function composeTask($task)
+ {
+ // If already built, return it
+ if ($task instanceof AbstractTask) {
+ return $task;
+ }
+
+ // If we provided a Closure, build a ClosureTask
+ if ($task instanceof Closure) {
+ return $this->buildTaskFromClosure($task);
+ }
+
+ // If we passed a task handle, return it
+ if ($handle = $this->getTaskHandle($task)) {
+ return $this->app[$handle];
+ }
+
+ // If we passed a command, build a ClosureTask
+ if (is_array($task) || $this->isStringCommand($task)) {
+ return $this->buildTaskFromString($task);
+ }
+
+ // Else it's a class name, get the appropriated task
+ if (!$task instanceof AbstractTask) {
+ return $this->buildTaskFromClass($task);
+ }
+ }
+
+ /**
+ * Build a task from a string
+ *
+ * @param string|string[] $task
+ *
+ * @return AbstractTask
+ */
+ public function buildTaskFromString($task)
+ {
+ $stringTask = $task;
+ $closure = function (AbstractTask $task) use ($stringTask) {
+ return $task->runForCurrentRelease($stringTask);
+ };
+
+ return $this->buildTaskFromClosure($closure, $stringTask);
+ }
+
+ /**
+ * Build a task from a Closure or a string command
+ *
+ * @param Closure $callback
+ * @param string|null $stringTask
+ *
+ * @return AbstractTask
+ */
+ public function buildTaskFromClosure(Closure $callback, $stringTask = null)
+ {
+ /** @type \Rocketeer\Tasks\Closure $task */
+ $task = $this->buildTaskFromClass('Rocketeer\Tasks\Closure');
+ $task->setClosure($callback);
+
+ // If we had an original string used, store it on
+ // the task for easier reflection
+ if ($stringTask) {
+ $task->setStringTask($stringTask);
+ }
+
+ return $task;
+ }
+
+ /**
+ * Build a task from its name
+ *
+ * @param string|AbstractTask $task
+ *
+ * @throws TaskCompositionException
+ * @return AbstractTask
+ */
+ public function buildTaskFromClass($task)
+ {
+ if (is_object($task) && $task instanceof AbstractTask) {
+ return $task;
+ }
+
+ // Cancel if class doesn't exist
+ if (!$class = $this->taskClassExists($task)) {
+ throw new TaskCompositionException('Impossible to build task: '.$task);
+ }
+
+ return new $class($this->app);
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ /////////////////////////////// HELPERS ////////////////////////////
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get the handle of a task from its name
+ *
+ * @param string|AbstractTask $task
+ *
+ * @return string|null
+ */
+ protected function getTaskHandle($task)
+ {
+ // Check the handle if possible
+ if (!is_string($task)) {
+ return;
+ }
+
+ // Compute the handle and check it's bound
+ $handle = 'rocketeer.tasks.'.Str::snake($task, '-');
+ $task = $this->app->bound($handle) ? $handle : null;
+
+ return $task;
+ }
+
+ /**
+ * Check if a string is a command or a task
+ *
+ * @param string|Closure|AbstractTask $string
+ *
+ * @return boolean
+ */
+ protected function isStringCommand($string)
+ {
+ return is_string($string) && !$this->taskClassExists($string) && !$this->app->bound('rocketeer.tasks.'.$string);
+ }
+
+ /**
+ * Check if a class with the given task name exists
+ *
+ * @param string $task
+ *
+ * @return string|false
+ */
+ protected function taskClassExists($task)
+ {
+ return $this->findQualifiedName($task, array(
+ 'Rocketeer\Tasks\%s',
+ 'Rocketeer\Tasks\Subtasks\%s',
+ ));
+ }
+
+ /**
+ * Find a class in various predefined namespaces
+ *
+ * @param string $class
+ * @param string[] $paths
+ *
+ * @return string|false
+ */
+ protected function findQualifiedName($class, $paths = array())
+ {
+ $paths[] = '%s';
+
+ $class = ucfirst($class);
+ foreach ($paths as $path) {
+ $path = sprintf($path, $class);
+ if (class_exists($path)) {
+ return $path;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/src/Rocketeer/Services/Tasks/TasksQueue.php b/src/Rocketeer/Services/Tasks/TasksQueue.php
new file mode 100644
index 000000000..75ceb3a40
--- /dev/null
+++ b/src/Rocketeer/Services/Tasks/TasksQueue.php
@@ -0,0 +1,303 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Services\Tasks;
+
+use Closure;
+use Exception;
+use KzykHys\Parallel\Parallel;
+use LogicException;
+use Rocketeer\Connection;
+use Rocketeer\Traits\HasHistory;
+use Rocketeer\Traits\HasLocator;
+
+/**
+ * Handles running an array of tasks sequentially
+ * or in parallel
+ *
+ * @author Maxime Fabre
+ */
+class TasksQueue
+{
+ use HasLocator;
+ use HasHistory;
+
+ /**
+ * @type Parallel
+ */
+ protected $parallel;
+
+ /**
+ * A list of Tasks to execute
+ *
+ * @var array
+ */
+ protected $tasks;
+
+ /**
+ * The Remote connection
+ *
+ * @var Connection
+ */
+ protected $remote;
+
+ /**
+ * @param Parallel $parallel
+ */
+ public function setParallel($parallel)
+ {
+ $this->parallel = $parallel;
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ ////////////////////////////// SHORTCUTS ///////////////////////////
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Execute Tasks on the default connection and
+ * return their output
+ *
+ * @param string|array|Closure $queue
+ * @param string|string[]|null $connections
+ *
+ * @return boolean
+ */
+ public function execute($queue, $connections = null)
+ {
+ if ($connections) {
+ $this->connections->setConnections($connections);
+ }
+
+ // Run tasks
+ $this->run($queue);
+ $history = $this->history->getFlattenedOutput();
+
+ return end($history);
+ }
+
+ /**
+ * Execute Tasks on various connections
+ *
+ * @param string|string[] $connections
+ * @param string|array|Closure $queue
+ *
+ * @return boolean
+ */
+ public function on($connections, $queue)
+ {
+ return $this->execute($queue, $connections);
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ //////////////////////////////// QUEUE /////////////////////////////
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Run the queue
+ * Run an array of Tasks instances on the various
+ * connections and stages provided
+ *
+ * @param string|array $tasks An array of tasks
+ *
+ * @throws Exception
+ * @return Pipeline
+ */
+ public function run($tasks)
+ {
+ $tasks = (array) $tasks;
+ $queue = $this->builder->buildTasks($tasks);
+ $pipeline = $this->buildPipeline($queue);
+
+ // Wrap job in closure pipeline
+ foreach ($pipeline as $key => $job) {
+ $pipeline[$key] = function () use ($job) {
+ return $this->executeJob($job);
+ };
+ }
+
+ // Run the tasks and store the results
+ if ($this->getOption('parallel')) {
+ $pipeline = $this->runAsynchronously($pipeline);
+ } else {
+ $pipeline = $this->runSynchronously($pipeline);
+ }
+
+ return $pipeline;
+ }
+
+ /**
+ * Build a pipeline of jobs for Parallel to execute
+ *
+ * @param array $queue
+ *
+ * @return Pipeline
+ */
+ public function buildPipeline(array $queue)
+ {
+ // First we'll build the queue
+ $pipeline = new Pipeline();
+
+ // Get the connections to execute the tasks on
+ $connections = (array) $this->connections->getConnections();
+ foreach ($connections as $connection) {
+ $servers = $this->connections->getConnectionCredentials($connection);
+ $stages = $this->getStages($connection);
+
+ // Add job to pipeline
+ foreach ($servers as $server => $credentials) {
+ foreach ($stages as $stage) {
+ $pipeline[] = new Job(array(
+ 'connection' => $connection,
+ 'server' => $server,
+ 'stage' => $stage,
+ 'queue' => $queue,
+ ));
+ }
+ }
+ }
+
+ return $pipeline;
+ }
+
+ /**
+ * Run the queue, taking into account the stage
+ *
+ * @param Job $job
+ *
+ * @return boolean
+ */
+ protected function executeJob(Job $job)
+ {
+ // Set proper server
+ $this->connections->setConnection($job->connection, $job->server);
+
+ foreach ($job->queue as $key => $task) {
+ if ($task->usesStages()) {
+ $stage = $task->usesStages() ? $job->stage : null;
+ $this->connections->setStage($stage);
+ }
+
+ // Here we fire the task, save its
+ // output and return its status
+ $state = $task->fire();
+ $this->toOutput($state);
+
+ // If the task didn't finish, display what the error was
+ if ($task->wasHalted() || $state === false) {
+ $this->command->error('The tasks queue was canceled by task "'.$task->getName().'"');
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// RUNNERS ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Run the pipeline in order.
+ * As long as the previous entry didn't fail, continue
+ *
+ * @param Pipeline $pipeline
+ *
+ * @return Pipeline
+ */
+ protected function runSynchronously(Pipeline $pipeline)
+ {
+ $results = [];
+
+ /** @type Closure $task */
+ foreach ($pipeline as $key => $task) {
+ $results[$key] = $task();
+ if (!$results[$key]) {
+ break;
+ }
+ }
+
+ // Update Pipeline results
+ $pipeline->setResults($results);
+
+ return $pipeline;
+ }
+
+ /**
+ * Run the pipeline in parallel order
+ *
+ * @param Pipeline $pipeline
+ *
+ * @return Pipeline
+ * @throws \Exception
+ */
+ protected function runAsynchronously(Pipeline $pipeline)
+ {
+ $this->parallel = $this->parallel ?: new Parallel();
+
+ // Check if supported
+ if (!$this->parallel->isSupported()) {
+ throw new Exception('Parallel jobs require the PCNTL extension');
+ }
+
+ try {
+ $this->parallel = $this->parallel ?: new Parallel();
+ $results = $this->parallel->values($pipeline->all());
+ $pipeline->setResults($results);
+ } catch (LogicException $exception) {
+ return $this->runSynchronously($pipeline);
+ }
+
+ return $pipeline;
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ //////////////////////////////// STAGES ////////////////////////////
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get the stages of a connection
+ *
+ * @param string $connection
+ *
+ * @return array
+ */
+ public function getStages($connection)
+ {
+ $this->connections->setConnection($connection);
+
+ $stage = $this->rocketeer->getOption('stages.default');
+ if ($this->hasCommand()) {
+ $stage = $this->getOption('stage') ?: $stage;
+ }
+
+ // Return all stages if "all"
+ if ($stage == 'all' || !$stage) {
+ $stage = $this->connections->getStages();
+ }
+
+ // Sanitize and filter
+ $stages = (array) $stage;
+ $stages = array_filter($stages, [$this, 'isValidStage']);
+
+ return $stages ?: [null];
+ }
+
+ /**
+ * Check if a stage is valid
+ *
+ * @param string $stage
+ *
+ * @return boolean
+ */
+ public function isValidStage($stage)
+ {
+ return in_array($stage, $this->connections->getStages());
+ }
+}
diff --git a/src/Rocketeer/TasksHandler.php b/src/Rocketeer/Services/TasksHandler.php
similarity index 54%
rename from src/Rocketeer/TasksHandler.php
rename to src/Rocketeer/Services/TasksHandler.php
index 4637e4d38..8d1a55e16 100644
--- a/src/Rocketeer/TasksHandler.php
+++ b/src/Rocketeer/Services/TasksHandler.php
@@ -7,19 +7,24 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
-namespace Rocketeer;
+namespace Rocketeer\Services;
+use Closure;
use Illuminate\Container\Container;
-use Rocketeer\Commands\BaseTaskCommand;
-use Rocketeer\Traits\AbstractLocatorClass;
+use Rocketeer\Abstracts\AbstractTask;
+use Rocketeer\Console\Commands\BaseTaskCommand;
+use Rocketeer\Tasks;
+use Rocketeer\Traits\HasLocator;
/**
- * Handles the registering and relating of tasks
+ * Handles the registering and firing of tasks and their events
*
* @author Maxime Fabre
*/
-class TasksHandler extends AbstractLocatorClass
+class TasksHandler
{
+ use HasLocator;
+
/**
* The registered events
*
@@ -27,10 +32,17 @@ class TasksHandler extends AbstractLocatorClass
*/
protected $registeredEvents = array();
+ /**
+ * The registered plugins
+ *
+ * @type array
+ */
+ protected $registeredPlugins = array();
+
/**
* Build a new TasksQueue Instance
*
- * @param Container $app
+ * @param Container $app
*/
public function __construct(Container $app)
{
@@ -57,17 +69,18 @@ public function __call($method, $parameters)
////////////////////////////////////////////////////////////////////
/**
- * Register a custom Task with Rocketeer
+ * Register a custom task with Rocketeer
*
- * @param Task|string $task
- * @param string $name
+ * @param string|Closure|AbstractTask $task
+ * @param string|null $name
+ * @param string|null $description
*
- * @return Container
+ * @return BaseTaskCommand
*/
- public function add($task, $name = null)
+ public function add($task, $name = null, $description = null)
{
- // Build Task if necessary
- $task = $this->buildTask($task, $name);
+ // Build task if necessary
+ $task = $this->builder->buildTask($task, $name, $description);
$slug = 'rocketeer.tasks.'.$task->getSlug();
// Add the task to Rocketeer
@@ -75,8 +88,9 @@ public function add($task, $name = null)
$bound = $this->console->add(new BaseTaskCommand($this->app[$slug]));
// Bind to Artisan too
- if ($this->app->bound('artisan')) {
- $this->app['artisan']->add(new BaseTaskCommand($task));
+ if ($this->app->bound('artisan') && $this->app->resolved('artisan')) {
+ $command = $this->builder->buildCommand($task);
+ $this->app['artisan']->add($command);
}
return $bound;
@@ -85,14 +99,15 @@ public function add($task, $name = null)
/**
* Register a task with Rocketeer
*
- * @param string $name
- * @param mixed $task
+ * @param string $name
+ * @param string|Closure|AbstractTask $task
+ * @param string|null $description
*
- * @return void
+ * @return BaseTaskCommand
*/
- public function task($name, $task)
+ public function task($name, $task, $description = null)
{
- return $this->add($task, $name);
+ return $this->add($task, $name, $description);
}
////////////////////////////////////////////////////////////////////
@@ -100,11 +115,11 @@ public function task($name, $task)
////////////////////////////////////////////////////////////////////
/**
- * Execute a Task before another one
+ * Execute a task before another one
*
- * @param string $task
- * @param string|Closure|Task $listeners
- * @param integer $priority
+ * @param string $task
+ * @param Closure $listeners
+ * @param integer $priority
*
* @return void
*/
@@ -114,11 +129,11 @@ public function before($task, $listeners, $priority = 0)
}
/**
- * Execute a Task after another one
+ * Execute a task after another one
*
- * @param string $task
- * @param string|Closure|Task $listeners
- * @param integer $priority
+ * @param string $task
+ * @param Closure $listeners
+ * @param integer $priority
*
* @return void
*/
@@ -139,6 +154,15 @@ public function registerConfiguredEvents()
$this->events->forget('rocketeer.'.$event);
}
+ // Clean previously registered plugins
+ $plugins = $this->registeredPlugins;
+ $this->registeredPlugins = [];
+
+ // Register plugins again
+ foreach ($plugins as $plugin) {
+ $this->plugin($plugin['plugin'], $plugin['configuration']);
+ }
+
// Get the registered events
$hooks = (array) $this->rocketeer->getOption('hooks');
unset($hooks['custom']);
@@ -146,7 +170,7 @@ public function registerConfiguredEvents()
// Bind events
foreach ($hooks as $event => $tasks) {
foreach ($tasks as $task => $listeners) {
- $this->registeredEvents[] = $this->addTaskListeners($task, $event, $listeners);
+ $this->addTaskListeners($task, $event, $listeners, 0, true);
}
}
}
@@ -154,20 +178,21 @@ public function registerConfiguredEvents()
/**
* Register listeners for a particular event
*
- * @param string $event
- * @param array $listeners
- * @param integer $priority
+ * @param string $event
+ * @param array|callable $listeners
+ * @param integer $priority
*
* @return string
*/
public function listenTo($event, $listeners, $priority = 0)
{
- // Create array if it doesn't exist
- $listeners = $this->buildQueue((array) $listeners);
+ /** @type AbstractTask[] $listeners */
+ $listeners = $this->builder->buildTasks((array) $listeners);
// Register events
foreach ($listeners as $listener) {
- $this->events->listen('rocketeer.'.$event, array($listener, 'execute'), $priority);
+ $listener->setEvent($event);
+ $this->events->listen('rocketeer.'.$event, [$listener, 'fire'], $priority);
}
return $event;
@@ -176,49 +201,66 @@ public function listenTo($event, $listeners, $priority = 0)
/**
* Bind a listener to a task
*
- * @param string $task
- * @param string $event
- * @param mixed $listeners
- * @param integer $priority
+ * @param string|array $task
+ * @param string $event
+ * @param array|callable $listeners
+ * @param integer $priority
+ * @param boolean $register
+ *
+ * @throws \Rocketeer\Exceptions\TaskCompositionException
+ * @return string|null
*/
- public function addTaskListeners($task, $event, $listeners, $priority = 0)
+ public function addTaskListeners($task, $event, $listeners, $priority = 0, $register = false)
{
// Recursive call
if (is_array($task)) {
foreach ($task as $t) {
- $this->addTaskListeners($t, $event, $listeners, $priority);
+ $this->addTaskListeners($t, $event, $listeners, $priority, $register);
}
return;
}
+ // Prevent events on anonymous tasks
+ $slug = $this->builder->buildTask($task)->getSlug();
+ if ($slug == 'closure') {
+ return;
+ }
+
// Get event name and register listeners
- $event = $this->buildTaskFromClass($task)->getSlug().'.'.$event;
+ $event = $slug.'.'.$event;
$event = $this->listenTo($event, $listeners, $priority);
+ // Store registered event
+ if ($register) {
+ $this->registeredEvents[] = $event;
+ }
+
return $event;
}
/**
* Get all of a task's listeners
*
- * @param Task $task
- * @param string $event
- * @param boolean $flatten
+ * @param string|AbstractTask $task
+ * @param string $event
+ * @param boolean $flatten
*
* @return array
*/
public function getTasksListeners($task, $event, $flatten = false)
{
// Get events
- $task = $this->buildTaskFromClass($task)->getSlug();
+ $task = $this->builder->buildTaskFromClass($task)->getSlug();
$events = $this->events->getListeners('rocketeer.'.$task.'.'.$event);
// Flatten the queue if requested
foreach ($events as $key => $event) {
$task = $event[0];
- if ($flatten and $task instanceof Tasks\Closure and $stringTask = $task->getStringTask()) {
+ if ($flatten && $task instanceof Tasks\Closure && $stringTask = $task->getStringTask()) {
$events[$key] = $stringTask;
+ } elseif ($flatten && $task instanceof AbstractTask) {
+ $events[$key] = $task->getSlug();
}
}
@@ -229,6 +271,14 @@ public function getTasksListeners($task, $event, $flatten = false)
/////////////////////////////// PLUGINS ////////////////////////////
////////////////////////////////////////////////////////////////////
+ /**
+ * @return array
+ */
+ public function getRegisteredPlugins()
+ {
+ return $this->registeredPlugins;
+ }
+
/**
* Register a Rocketeer plugin with Rocketeer
*
@@ -241,12 +291,23 @@ public function plugin($plugin, array $configuration = array())
{
// Build plugin
if (is_string($plugin)) {
- $plugin = $this->app->make($plugin, array($this->app));
+ $plugin = $this->app->make($plugin, [$this->app]);
}
+ // Store registration of plugin
+ $identifier = get_class($plugin);
+ if (isset($this->registeredPlugins[$identifier])) {
+ return;
+ }
+
+ $this->registeredPlugins[$identifier] = array(
+ 'plugin' => $plugin,
+ 'configuration' => $configuration,
+ );
+
// Register configuration
$vendor = $plugin->getNamespace();
- $this->config->package('rocketeer/'.$vendor, $plugin->configurationFolder);
+ $this->config->package('rocketeers/'.$vendor, $plugin->configurationFolder);
if ($configuration) {
$this->config->set($vendor.'::config', $configuration);
}
diff --git a/src/Rocketeer/Strategies/Check/NodeStrategy.php b/src/Rocketeer/Strategies/Check/NodeStrategy.php
new file mode 100644
index 000000000..6730e8079
--- /dev/null
+++ b/src/Rocketeer/Strategies/Check/NodeStrategy.php
@@ -0,0 +1,70 @@
+
+ */
+class NodeStrategy extends AbstractCheckStrategy implements CheckStrategyInterface
+{
+ /**
+ * @type string
+ */
+ protected $description = 'Checks if the server is ready to receive a Node application';
+
+ /**
+ * The language of the strategy
+ *
+ * @type string
+ */
+ protected $language = 'Node';
+
+ /**
+ * Get the version constraint which should be checked against
+ *
+ * @param string $manifest
+ *
+ * @return string
+ */
+ protected function getLanguageConstraint($manifest)
+ {
+ return $this->getLanguageConstraintFromJson($manifest, 'engines.node');
+ }
+
+ /**
+ * Get the current version in use
+ *
+ * @return string
+ */
+ protected function getCurrentVersion()
+ {
+ $version = $this->binary('node')->run('--version');
+ $version = str_replace('v', null, $version);
+
+ return $version;
+ }
+
+ /**
+ * Check for the required extensions
+ *
+ * @return array
+ */
+ public function extensions()
+ {
+ return [];
+ }
+
+ /**
+ * Check for the required drivers
+ *
+ * @return array
+ */
+ public function drivers()
+ {
+ return [];
+ }
+}
diff --git a/src/Rocketeer/Strategies/Check/PhpStrategy.php b/src/Rocketeer/Strategies/Check/PhpStrategy.php
new file mode 100644
index 000000000..27fa37813
--- /dev/null
+++ b/src/Rocketeer/Strategies/Check/PhpStrategy.php
@@ -0,0 +1,208 @@
+app = $app;
+ $this->manager = $this->binary('composer');
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ /////////////////////////////// CHECKS ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get the version constraint which should be checked against
+ *
+ * @param string $manifest
+ *
+ * @return string
+ */
+ protected function getLanguageConstraint($manifest)
+ {
+ return $this->getLanguageConstraintFromJson($manifest, 'require.php');
+ }
+
+ /**
+ * Get the current version in use
+ *
+ * @return string
+ */
+ protected function getCurrentVersion()
+ {
+ return $this->php()->runLast('version');
+ }
+
+ /**
+ * Check for the required extensions
+ *
+ * @return array
+ */
+ public function extensions()
+ {
+ $extensions = array(
+ 'mcrypt' => ['checkPhpExtension', 'mcrypt'],
+ 'database' => ['checkDatabaseDriver', $this->app['config']->get('database.default')],
+ 'cache' => ['checkCacheDriver', $this->app['config']->get('cache.driver')],
+ 'session' => ['checkCacheDriver', $this->app['config']->get('session.driver')],
+ );
+
+ // Check PHP extensions
+ $errors = [];
+ foreach ($extensions as $check) {
+ list ($method, $extension) = $check;
+
+ if (!$this->$method($extension)) {
+ $errors[] = $extension;
+ }
+ }
+
+ return $errors;
+ }
+
+ /**
+ * Check for the required drivers
+ *
+ * @return array
+ */
+ public function drivers()
+ {
+ return [];
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// HELPERS ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Check the presence of the correct database PHP extension
+ *
+ * @param string $database
+ *
+ * @return boolean
+ */
+ public function checkDatabaseDriver($database)
+ {
+ switch ($database) {
+ case 'sqlite':
+ return $this->checkPhpExtension('pdo_sqlite');
+
+ case 'mysql':
+ return $this->checkPhpExtension('mysql') && $this->checkPhpExtension('pdo_mysql');
+
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Check the presence of the correct cache PHP extension
+ *
+ * @param string $cache
+ *
+ * @return boolean|string
+ */
+ public function checkCacheDriver($cache)
+ {
+ switch ($cache) {
+ case 'memcached':
+ case 'apc':
+ return $this->checkPhpExtension($cache);
+
+ case 'redis':
+ return $this->which('redis-server');
+
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Check the presence of a PHP extension
+ *
+ * @param string $extension The extension
+ *
+ * @return boolean
+ */
+ public function checkPhpExtension($extension)
+ {
+ // Check for HHVM and built-in extensions
+ if ($this->php()->isHhvm()) {
+ $this->extensions = array(
+ '_hhvm',
+ 'apache',
+ 'asio',
+ 'bcmath',
+ 'bz2',
+ 'ctype',
+ 'curl',
+ 'debugger',
+ 'fileinfo',
+ 'filter',
+ 'gd',
+ 'hash',
+ 'hh',
+ 'iconv',
+ 'icu',
+ 'imagick',
+ 'imap',
+ 'json',
+ 'mailparse',
+ 'mcrypt',
+ 'memcache',
+ 'memcached',
+ 'mysql',
+ 'odbc',
+ 'openssl',
+ 'pcre',
+ 'phar',
+ 'reflection',
+ 'session',
+ 'soap',
+ 'std',
+ 'stream',
+ 'thrift',
+ 'url',
+ 'wddx',
+ 'xdebug',
+ 'zip',
+ 'zlib',
+ );
+ }
+
+ // Get the PHP extensions available
+ if (!$this->extensions) {
+ $this->extensions = (array) $this->bash->run($this->php()->extensions(), false, true);
+ }
+
+ return in_array($extension, $this->extensions);
+ }
+}
diff --git a/src/Rocketeer/Strategies/Check/PolyglotStrategy.php b/src/Rocketeer/Strategies/Check/PolyglotStrategy.php
new file mode 100644
index 000000000..97bf30331
--- /dev/null
+++ b/src/Rocketeer/Strategies/Check/PolyglotStrategy.php
@@ -0,0 +1,66 @@
+executeStrategiesMethod('manager');
+
+ return $this->checkStrategiesResults($results);
+ }
+
+ /**
+ * Check that the language used by the
+ * application is at the required version
+ *
+ * @return boolean
+ */
+ public function language()
+ {
+ $results = $this->executeStrategiesMethod('language');
+
+ return $this->checkStrategiesResults($results);
+ }
+
+ /**
+ * Check for the required extensions
+ *
+ * @return array
+ */
+ public function extensions()
+ {
+ $missing = [];
+ $extensions = $this->executeStrategiesMethod('extensions');
+ foreach ($extensions as $extension) {
+ $missing = array_merge($missing, $extension);
+ }
+
+ return $missing;
+ }
+
+ /**
+ * Check for the required drivers
+ *
+ * @return array
+ */
+ public function drivers()
+ {
+ $missing = [];
+ $drivers = $this->executeStrategiesMethod('drivers');
+ foreach ($drivers as $driver) {
+ $missing = array_merge($missing, $driver);
+ }
+
+ return $missing;
+ }
+}
diff --git a/src/Rocketeer/Strategies/Check/RubyStrategy.php b/src/Rocketeer/Strategies/Check/RubyStrategy.php
new file mode 100644
index 000000000..733eddb36
--- /dev/null
+++ b/src/Rocketeer/Strategies/Check/RubyStrategy.php
@@ -0,0 +1,83 @@
+app = $app;
+ $this->manager = $this->binary('bundler');
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ /////////////////////////////// CHECKS ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get the version constraint which should be checked against
+ *
+ * @param string $manifest
+ *
+ * @return string
+ */
+ protected function getLanguageConstraint($manifest)
+ {
+ preg_match('/ruby \'(.+)\'/', $manifest, $matches);
+ $required = Arr::get((array) $matches, 1);
+
+ return $required;
+ }
+
+ /**
+ * Get the current version in use
+ *
+ * @return string
+ */
+ protected function getCurrentVersion()
+ {
+ $version = $this->binary('ruby')->run('--version');
+ $version = preg_replace('/ruby ([0-9\.]+)p?.+/', '$1', $version);
+
+ return $version;
+ }
+
+ /**
+ * Check for the required extensions
+ *
+ * @return array
+ */
+ public function extensions()
+ {
+ return [];
+ }
+
+ /**
+ * Check for the required drivers
+ *
+ * @return array
+ */
+ public function drivers()
+ {
+ return [];
+ }
+}
diff --git a/src/Rocketeer/Strategies/Dependencies/BowerStrategy.php b/src/Rocketeer/Strategies/Dependencies/BowerStrategy.php
new file mode 100644
index 000000000..ff623a100
--- /dev/null
+++ b/src/Rocketeer/Strategies/Dependencies/BowerStrategy.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Strategies\Dependencies;
+
+use Illuminate\Support\Arr;
+use Rocketeer\Abstracts\Strategies\AbstractDependenciesStrategy;
+use Rocketeer\Interfaces\Strategies\DependenciesStrategyInterface;
+
+class BowerStrategy extends AbstractDependenciesStrategy implements DependenciesStrategyInterface
+{
+ /**
+ * @type string
+ */
+ protected $description = 'Installs dependencies with Bower';
+
+ /**
+ * The name of the binary
+ *
+ * @type string
+ */
+ protected $binary = 'bower';
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// COMMANDS //////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Install the dependencies
+ *
+ * @return bool
+ */
+ public function install()
+ {
+ return $this->manager->runForCurrentRelease('install', [], $this->getInstallationOptions());
+ }
+
+ /**
+ * Update the dependencies
+ *
+ * @return boolean
+ */
+ public function update()
+ {
+ return $this->manager->runForCurrentRelease('update', [], $this->getInstallationOptions());
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// HELPERS ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get the options to run Bower with
+ *
+ * @return array
+ */
+ protected function getInstallationOptions()
+ {
+ $credentials = $this->connections->getServerCredentials();
+ if (Arr::get($credentials, 'username') == 'root') {
+ return ['--allow-root' => null];
+ }
+
+ return [];
+ }
+}
diff --git a/src/Rocketeer/Strategies/Dependencies/BundlerStrategy.php b/src/Rocketeer/Strategies/Dependencies/BundlerStrategy.php
new file mode 100644
index 000000000..76b2b7c1e
--- /dev/null
+++ b/src/Rocketeer/Strategies/Dependencies/BundlerStrategy.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Strategies\Dependencies;
+
+use Rocketeer\Abstracts\Strategies\AbstractDependenciesStrategy;
+use Rocketeer\Interfaces\Strategies\DependenciesStrategyInterface;
+
+class BundlerStrategy extends AbstractDependenciesStrategy implements DependenciesStrategyInterface
+{
+ /**
+ * @type string
+ */
+ protected $description = 'Installs dependencies with Bundler';
+
+ /**
+ * The name of the binary
+ *
+ * @type string
+ */
+ protected $binary = 'bundler';
+}
diff --git a/src/Rocketeer/Strategies/Dependencies/ComposerStrategy.php b/src/Rocketeer/Strategies/Dependencies/ComposerStrategy.php
new file mode 100644
index 000000000..e67c82383
--- /dev/null
+++ b/src/Rocketeer/Strategies/Dependencies/ComposerStrategy.php
@@ -0,0 +1,65 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Strategies\Dependencies;
+
+use Rocketeer\Abstracts\Strategies\AbstractDependenciesStrategy;
+use Rocketeer\Interfaces\Strategies\DependenciesStrategyInterface;
+
+class ComposerStrategy extends AbstractDependenciesStrategy implements DependenciesStrategyInterface
+{
+ /**
+ * @type string
+ */
+ protected $description = 'Installs dependencies with Composer';
+
+ /**
+ * The name of the binary
+ *
+ * @type string
+ */
+ protected $binary = 'composer';
+
+ /**
+ * Install the dependencies
+ *
+ * @return bool
+ */
+ public function install()
+ {
+ return $this->executeHook('install');
+ }
+
+ /**
+ * Update the dependencies
+ *
+ * @return boolean
+ */
+ public function update()
+ {
+ return $this->executeHook('update');
+ }
+
+ /**
+ * @param string $hook
+ *
+ * @return bool
+ */
+ protected function executeHook($hook)
+ {
+ $tasks = $this->getHookedTasks('composer.'.$hook, [$this->manager, $this]);
+ if (!$tasks) {
+ return true;
+ }
+
+ $this->runForCurrentRelease($tasks);
+
+ return $this->checkStatus('Composer could not install dependencies');
+ }
+}
diff --git a/src/Rocketeer/Strategies/Dependencies/NpmStrategy.php b/src/Rocketeer/Strategies/Dependencies/NpmStrategy.php
new file mode 100644
index 000000000..b3dd47865
--- /dev/null
+++ b/src/Rocketeer/Strategies/Dependencies/NpmStrategy.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Strategies\Dependencies;
+
+use Rocketeer\Abstracts\Strategies\AbstractDependenciesStrategy;
+use Rocketeer\Interfaces\Strategies\DependenciesStrategyInterface;
+
+class NpmStrategy extends AbstractDependenciesStrategy implements DependenciesStrategyInterface
+{
+ /**
+ * @type string
+ */
+ protected $description = 'Installs dependencies with NPM';
+
+ /**
+ * The name of the binary
+ *
+ * @type string
+ */
+ protected $binary = 'npm';
+}
diff --git a/src/Rocketeer/Strategies/Dependencies/PolyglotStrategy.php b/src/Rocketeer/Strategies/Dependencies/PolyglotStrategy.php
new file mode 100644
index 000000000..a3650a0ad
--- /dev/null
+++ b/src/Rocketeer/Strategies/Dependencies/PolyglotStrategy.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Strategies\Dependencies;
+
+use Rocketeer\Abstracts\Strategies\AbstractPolyglotStrategy;
+use Rocketeer\Interfaces\Strategies\DependenciesStrategyInterface;
+
+class PolyglotStrategy extends AbstractPolyglotStrategy implements DependenciesStrategyInterface
+{
+ /**
+ * @type string
+ */
+ protected $description = 'Runs all of the above package managers if necessary';
+
+ /**
+ * The various strategies to call
+ *
+ * @type array
+ */
+ protected $strategies = ['Bundler', 'Composer', 'Npm', 'Bower'];
+
+ /**
+ * Install the dependencies
+ *
+ * @return boolean[]
+ */
+ public function install()
+ {
+ return $this->executeStrategiesMethod('install');
+ }
+
+ /**
+ * Update the dependencies
+ *
+ * @return boolean[]
+ */
+ public function update()
+ {
+ return $this->executeStrategiesMethod('update');
+ }
+}
diff --git a/src/Rocketeer/Strategies/Deploy/CloneStrategy.php b/src/Rocketeer/Strategies/Deploy/CloneStrategy.php
new file mode 100644
index 000000000..650d2e183
--- /dev/null
+++ b/src/Rocketeer/Strategies/Deploy/CloneStrategy.php
@@ -0,0 +1,75 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Strategies\Deploy;
+
+use Rocketeer\Abstracts\Strategies\AbstractStrategy;
+use Rocketeer\Interfaces\Strategies\DeployStrategyInterface;
+
+class CloneStrategy extends AbstractStrategy implements DeployStrategyInterface
+{
+ /**
+ * @type string
+ */
+ protected $description = 'Clones a fresh instance of the repository by SCM';
+
+ /**
+ * Deploy a new clean copy of the application
+ *
+ * @param string|null $destination
+ *
+ * @return boolean
+ */
+ public function deploy($destination = null)
+ {
+ if (!$destination) {
+ $destination = $this->releasesManager->getCurrentReleasePath();
+ }
+
+ // Executing checkout
+ $this->explainer->line('Cloning repository in "'.$destination.'"');
+ $output = $this->scm->run('checkout', $destination);
+
+ // Cancel if failed and forget credentials
+ $success = $this->bash->checkStatus('Unable to clone the repository', $output) !== false;
+ if (!$success) {
+ $this->localStorage->forget('credentials');
+
+ return false;
+ }
+
+ // Deploy submodules
+ if ($this->rocketeer->getOption('scm.submodules')) {
+ $this->explainer->line('Initializing submodules if any');
+ $this->scm->runForCurrentRelease('submodules');
+ }
+
+ return $success;
+ }
+
+ /**
+ * Update the latest version of the application
+ *
+ * @param boolean $reset
+ *
+ * @return string
+ */
+ public function update($reset = true)
+ {
+ $this->command->info('Pulling changes');
+ $tasks = [$this->scm->update()];
+
+ // Reset if requested
+ if ($reset) {
+ array_unshift($tasks, $this->scm->reset());
+ }
+
+ return $this->bash->runForCurrentRelease($tasks);
+ }
+}
diff --git a/src/Rocketeer/Strategies/Deploy/CopyStrategy.php b/src/Rocketeer/Strategies/Deploy/CopyStrategy.php
new file mode 100644
index 000000000..1e8fe2587
--- /dev/null
+++ b/src/Rocketeer/Strategies/Deploy/CopyStrategy.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Strategies\Deploy;
+
+use Rocketeer\Interfaces\Strategies\DeployStrategyInterface;
+
+class CopyStrategy extends CloneStrategy implements DeployStrategyInterface
+{
+ /**
+ * @type string
+ */
+ protected $description = 'Copies the previously cloned instance of the repository and update it';
+
+ /**
+ * Deploy a new clean copy of the application
+ *
+ * @param string|null $destination
+ *
+ * @return boolean|string
+ */
+ public function deploy($destination = null)
+ {
+ // Get the previous release, if none clone from scratch
+ $previous = $this->releasesManager->getReleases();
+ if (!$previous) {
+ return parent::deploy($destination);
+ }
+
+ // If we have a previous release, check its validity
+ $previous = $this->releasesManager->getPreviousRelease();
+ $previous = $this->releasesManager->getPathToRelease($previous);
+ if (!$previous) {
+ return parent::deploy($destination);
+ }
+
+ // Recompute destination
+ if (!$destination) {
+ $destination = $this->releasesManager->getCurrentReleasePath();
+ }
+
+ // Copy old release into new one
+ $this->explainer->success('Copying previous release "'.$previous.'" in "'.$destination.'"');
+ $this->bash->copy($previous, $destination);
+
+ // Update repository
+ return $this->update();
+ }
+}
diff --git a/src/Rocketeer/Strategies/Deploy/SyncStrategy.php b/src/Rocketeer/Strategies/Deploy/SyncStrategy.php
new file mode 100644
index 000000000..7fa03d763
--- /dev/null
+++ b/src/Rocketeer/Strategies/Deploy/SyncStrategy.php
@@ -0,0 +1,87 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Strategies\Deploy;
+
+use Rocketeer\Abstracts\Strategies\AbstractStrategy;
+use Rocketeer\Bash;
+use Rocketeer\Interfaces\Strategies\DeployStrategyInterface;
+
+class SyncStrategy extends AbstractStrategy implements DeployStrategyInterface
+{
+ /**
+ * @type string
+ */
+ protected $description = 'Uses rsync to create or update a release from the local files';
+
+ /**
+ * Deploy a new clean copy of the application
+ *
+ * @param string|null $destination
+ *
+ * @return boolean
+ */
+ public function deploy($destination = null)
+ {
+ if (!$destination) {
+ $destination = $this->releasesManager->getCurrentReleasePath();
+ }
+
+ // Create receiveing folder
+ $this->createFolder($destination);
+
+ return $this->rsyncTo($destination);
+ }
+
+ /**
+ * Update the latest version of the application
+ *
+ * @param boolean $reset
+ *
+ * @return boolean
+ */
+ public function update($reset = true)
+ {
+ $release = $this->releasesManager->getCurrentReleasePath();
+
+ return $this->rsyncTo($release);
+ }
+
+ /**
+ * Rsyncs the local folder to a remote one
+ *
+ * @param string $destination
+ *
+ * @return boolean
+ */
+ protected function rsyncTo($destination)
+ {
+ // Build host handle
+ $credentials = $this->connections->getServerCredentials();
+ $handle = array_get($credentials, 'host');
+ if ($user = array_get($credentials, 'username')) {
+ $handle = $user.'@'.$handle;
+ }
+
+ // Create options
+ $options = '--verbose --recursive --rsh="ssh"';
+ $excludes = ['.git', 'vendor'];
+ foreach ($excludes as $exclude) {
+ $options .= ' --exclude="'.$exclude.'"';
+ }
+
+ // Create binary and command
+ $rsync = $this->binary('rsync');
+ $rsync = $rsync->getCommand(null, ['./', $handle.':'.$destination], $options);
+
+ return $this->bash->onLocal(function (Bash $bash) use ($rsync) {
+ return $bash->run($rsync);
+ });
+ }
+}
diff --git a/src/Rocketeer/Strategies/Migrate/ArtisanStrategy.php b/src/Rocketeer/Strategies/Migrate/ArtisanStrategy.php
new file mode 100644
index 000000000..e3ae7d883
--- /dev/null
+++ b/src/Rocketeer/Strategies/Migrate/ArtisanStrategy.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Strategies\Migrate;
+
+use Rocketeer\Abstracts\Strategies\AbstractStrategy;
+use Rocketeer\Interfaces\Strategies\MigrateStrategyInterface;
+
+class ArtisanStrategy extends AbstractStrategy implements MigrateStrategyInterface
+{
+ /**
+ * @type string
+ */
+ protected $description = 'Migrates your database with Laravel\'s Artisan CLI';
+
+ /**
+ * Whether this particular strategy is runnable or not
+ *
+ * @return boolean
+ */
+ public function isExecutable()
+ {
+ return (bool) $this->artisan()->getBinary();
+ }
+
+ /**
+ * Run outstanding migrations
+ *
+ * @return boolean|null
+ */
+ public function migrate()
+ {
+ return $this->artisan()->runForCurrentRelease('migrate');
+ }
+
+ /**
+ * Seed the database
+ *
+ * @return boolean|null
+ */
+ public function seed()
+ {
+ return $this->artisan()->runForCurrentRelease('seed');
+ }
+}
diff --git a/src/Rocketeer/Strategies/Test/PhpunitStrategy.php b/src/Rocketeer/Strategies/Test/PhpunitStrategy.php
new file mode 100644
index 000000000..e7218d2b9
--- /dev/null
+++ b/src/Rocketeer/Strategies/Test/PhpunitStrategy.php
@@ -0,0 +1,52 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Strategies\Test;
+
+use Rocketeer\Abstracts\Strategies\AbstractStrategy;
+use Rocketeer\Interfaces\Strategies\TestStrategyInterface;
+
+class PhpunitStrategy extends AbstractStrategy implements TestStrategyInterface
+{
+ /**
+ * @type string
+ */
+ protected $description = 'Run the tests with PHPUnit';
+
+ /**
+ * Whether this particular strategy is runnable or not
+ *
+ * @return boolean
+ */
+ public function isExecutable()
+ {
+ return (bool) $this->phpunit()->getBinary();
+ }
+
+ /**
+ * Run the task
+ *
+ * @return boolean
+ */
+ public function test()
+ {
+ // Run PHPUnit
+ $arguments = ['--stop-on-failure' => null];
+ $output = $this->runForCurrentRelease(array(
+ $this->phpunit()->getCommand(null, [], $arguments),
+ ));
+
+ $status = $this->checkStatus('Tests failed', $output, 'Tests passed successfully');
+ if (!$status) {
+ $this->explainer->error('Tests failed');
+ }
+
+ return $status;
+ }
+}
diff --git a/src/Rocketeer/Tasks/Check.php b/src/Rocketeer/Tasks/Check.php
index 7d1baf657..bc2260ca7 100644
--- a/src/Rocketeer/Tasks/Check.php
+++ b/src/Rocketeer/Tasks/Check.php
@@ -9,59 +9,72 @@
*/
namespace Rocketeer\Tasks;
-use Rocketeer\Traits\Task;
+use Rocketeer\Abstracts\AbstractTask;
/**
* Check if the server is ready to receive the application
*
* @author Maxime Fabre
*/
-class Check extends Task
+class Check extends AbstractTask
{
- /**
- * The PHP extensions loaded on server
- *
- * @var array
- */
- protected $extensions = array();
- /**
- * A description of what the Task does
+ /**
+ * A description of what the task does
*
* @var string
*/
protected $description = 'Check if the server is ready to receive the application';
/**
- * Whether the Task needs to be run on each stage or globally
+ * Whether the task needs to be run on each stage or globally
*
* @var boolean
*/
public $usesStages = false;
/**
- * Run the Task
+ * Run the task
*
- * @return void
+ * @return boolean|null
*/
public function execute()
{
- $errors = array();
- $checks = $this->getChecks();
+ $check = $this->getStrategy('Check');
+ $errors = [];
+
+ // Check the depoy strategy
+ if ($this->rocketeer->getOption('strategies.deploy') !== 'sync' && !$this->checkScm()) {
+ $errors[] = $this->scm->getBinary().' could not be found';
+ }
- foreach ($checks as $check) {
- list($check, $error) = $check;
+ // Check package manager
+ $manager = class_basename($check->getManager());
+ $manager = str_replace('Strategy', null, $manager);
+ $this->explainer->line('Checking presence of '.$manager);
+ if (!$check->manager()) {
+ $errors[] = sprintf('The %s package manager could not be found', $manager);
+ }
- $argument = null;
- if (is_array($error)) {
- $argument = $error[0];
- $error = $error[1];
- }
+ // Check language
+ $language = $check->getLanguage();
+ $this->explainer->line('Checking '.$language.' version');
+ if (!$check->language()) {
+ $errors[] = $language.' is not at the required version';
+ }
+
+ // Check extensions
+ $this->explainer->line('Checking presence of required extensions');
+ $extensions = $check->extensions();
+ if (!empty($extensions)) {
+ $errors[] = 'The following extensions could not be found: '.implode(', ', $extensions);
+ }
- // If the check fail, print an error message
- if (!$this->$check($argument)) {
- $errors[] = $error;
- }
+ // Check drivers
+ $this->explainer->line('Checking presence of required drivers');
+ $drivers = $check->drivers();
+ if (!empty($drivers)) {
+ $errors[] = 'The following drivers could not be found: '.implode(', ', $drivers);
}
// Return false if any error
@@ -70,30 +83,7 @@ public function execute()
}
// Display confirmation message
- $this->command->info('Your server is ready to deploy');
- }
-
- /**
- * Get the checks to execute
- *
- * @return array
- */
- protected function getChecks()
- {
- $extension = 'The %s extension does not seem to be loaded on the server';
- $database = $this->app['config']->get('database.default');
- $cache = $this->app['config']->get('cache.driver');
- $session = $this->app['config']->get('session.driver');
-
- return array(
- array('checkScm', $this->scm->binary. ' could not be found'),
- array('checkPhpVersion', 'The version of PHP on the server does not match Laravel\'s requirements'),
- array('checkComposer', 'Composer does not seem to be present on the server'),
- array('checkPhpExtension', array('mcrypt', sprintf($extension, 'mcrypt'))),
- array('checkDatabaseDriver', array($database, sprintf($extension, $database))),
- array('checkCacheDriver', array($cache, sprintf($extension, $cache))),
- array('checkCacheDriver', array($session, sprintf($extension, $session))),
- );
+ $this->explainer->line('Your server is ready to deploy');
}
////////////////////////////////////////////////////////////////////
@@ -107,122 +97,10 @@ protected function getChecks()
*/
public function checkScm()
{
- $this->command->comment('Checking presence of '.$this->scm->binary);
- $this->history[] = $this->scm->execute('check');
-
- return $this->remote->status() == 0;
- }
-
- /**
- * Check if Composer is on the server
- *
- * @return boolean
- */
- public function checkComposer()
- {
- if (!$this->server->usesComposer()) {
- return true;
- }
-
- $this->command->comment('Checking presence of Composer');
-
- return $this->composer();
- }
-
- /**
- * Check if the server is ready to support PHP
- *
- * @return boolean
- */
- public function checkPhpVersion()
- {
- $required = null;
-
- // Get the minimum PHP version of the application
- $composer = $this->app['path.base'].'/composer.json';
- if ($this->app['files']->exists($composer)) {
- $composer = $this->app['files']->get($composer);
- $composer = json_decode($composer, true);
-
- // Strip versions of constraints
- $required = array_get($composer, 'require.php');
- $required = preg_replace('/>=/', '', $required);
- }
-
- // Cancel if no PHP version found
- if (!$required) {
- return true;
- }
-
- $this->command->comment('Checking PHP version');
- $version = $this->runLast($this->php('-r "print PHP_VERSION;"'));
-
- return version_compare($version, $required, '>=');
- }
-
- ////////////////////////////////////////////////////////////////////
- /////////////////////////////// HELPERS ////////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Check the presence of the correct database PHP extension
- *
- * @param string $database
- *
- * @return boolean
- */
- public function checkDatabaseDriver($database)
- {
- switch ($database) {
- case 'sqlite':
- return $this->checkPhpExtension('pdo_sqlite');
-
- case 'mysql':
- return $this->checkPhpExtension('mysql') and $this->checkPhpExtension('pdo_mysql');
-
- default:
- return true;
- }
- }
-
- /**
- * Check the presence of the correct cache PHP extension
- *
- * @param string $cache
- *
- * @return boolean
- */
- public function checkCacheDriver($cache)
- {
- switch ($cache) {
- case 'memcached':
- case 'apc':
- return $this->checkPhpExtension($cache);
-
- case 'redis':
- return $this->which('redis-server');
-
- default:
- return true;
- }
- }
-
- /**
- * Check the presence of a PHP extension
- *
- * @param string $extension The extension
- *
- * @return boolean
- */
- public function checkPhpExtension($extension)
- {
- $this->command->comment('Checking presence of '.$extension. ' extension');
-
- // Get the PHP extensions available
- if (!$this->extensions) {
- $this->extensions = (array) $this->run($this->php('-m'), false, true);
- }
+ $this->explainer->line('Checking presence of '.$this->scm->getBinary());
+ $results = $this->scm->run('check');
+ $this->toOutput($results);
- return in_array($extension, $this->extensions);
+ return $this->getConnection()->status() == 0;
}
}
diff --git a/src/Rocketeer/Tasks/Cleanup.php b/src/Rocketeer/Tasks/Cleanup.php
index a7e42896a..026638660 100644
--- a/src/Rocketeer/Tasks/Cleanup.php
+++ b/src/Rocketeer/Tasks/Cleanup.php
@@ -10,50 +10,55 @@
namespace Rocketeer\Tasks;
use Illuminate\Support\Str;
-use Rocketeer\Traits\Task;
+use Rocketeer\Abstracts\AbstractTask;
+use Rocketeer\Services\Storages\ServerStorage;
/**
* Clean up old releases from the server
*
* @author Maxime Fabre
*/
-class Cleanup extends Task
+class Cleanup extends AbstractTask
{
- /**
- * A description of what the Task does
+ /**
+ * A description of what the task does
*
* @var string
*/
protected $description = 'Clean up old releases from the server';
/**
- * Run the Task
- *
- * @return void
+ * Run the task
*/
public function execute()
{
// If no releases to prune
if (!$trash = $this->getReleasesToCleanup()) {
- return $this->command->comment('No releases to prune from the server');
+ return $this->explainer->line('No releases to prune from the server');
}
// Prune releases
- foreach ($trash as $release) {
- $this->removeFolder($this->releasesManager->getPathToRelease($release));
- }
+ $trash = array_map([$this->releasesManager, 'getPathToRelease'], $trash);
+ $this->removeFolder($trash);
// Create final message
- $trash = sizeof($trash);
+ $trash = count($trash);
$message = sprintf('Removing %d %s from the server', $trash, Str::plural('release', $trash));
- return $this->command->line($message);
+ // Delete state file
+ if ($this->getOption('clean-all')) {
+ $state = new ServerStorage($this->app, 'state');
+ $state->destroy();
+ $this->releasesManager->markReleaseAsValid();
+ }
+
+ return $this->explainer->line($message);
}
/**
* Get an array of releases to prune
*
- * @return array
+ * @return integer[]
*/
protected function getReleasesToCleanup()
{
diff --git a/src/Rocketeer/Tasks/Closure.php b/src/Rocketeer/Tasks/Closure.php
index a181dd784..8c56c69ee 100644
--- a/src/Rocketeer/Tasks/Closure.php
+++ b/src/Rocketeer/Tasks/Closure.php
@@ -10,19 +10,19 @@
namespace Rocketeer\Tasks;
use Closure as AnonymousFunction;
-use Rocketeer\Traits\Task;
+use Rocketeer\Abstracts\AbstractTask;
/**
- * A Task that wraps around a closure and execute it
+ * a task that wraps around a closure and execute it
*
* @author Maxime Fabre
*/
-class Closure extends Task
+class Closure extends AbstractTask
{
/**
* A Closure to execute at runtime
*
- * @var Closure
+ * @var AnonymousFunction
*/
protected $closure;
@@ -34,9 +34,32 @@ class Closure extends Task
protected $stringTask;
/**
- * Create a Task from a Closure
+ * Get the name of the task
*
- * @param AnonymousFunction $closure
+ * @return string
+ */
+ public function getName()
+ {
+ return parent::getName() ?: 'Arbitrary task';
+ }
+
+ /**
+ * Get what the task does
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ $flattened = (array) $this->getStringTask();
+ $flattened = implode('/', $flattened);
+
+ return parent::getDescription() ?: $flattened;
+ }
+
+ /**
+ * Create a task from a Closure
+ *
+ * @param AnonymousFunction $closure
*/
public function setClosure(AnonymousFunction $closure)
{
@@ -44,9 +67,9 @@ public function setClosure(AnonymousFunction $closure)
}
/**
- * Get the Task's Closure
+ * Get the task's Closure
*
- * @return Closure
+ * @return AnonymousFunction
*/
public function getClosure()
{
@@ -74,7 +97,7 @@ public function setStringTask($task)
}
/**
- * Run the Task
+ * Run the task
*
* @return void
*/
diff --git a/src/Rocketeer/Tasks/CurrentRelease.php b/src/Rocketeer/Tasks/CurrentRelease.php
index bd81e3a70..e7fb21055 100644
--- a/src/Rocketeer/Tasks/CurrentRelease.php
+++ b/src/Rocketeer/Tasks/CurrentRelease.php
@@ -10,46 +10,56 @@
namespace Rocketeer\Tasks;
use DateTime;
-use Rocketeer\Traits\Task;
+use Rocketeer\Abstracts\AbstractTask;
/**
* Display what the current release is
*
* @author Maxime Fabre
*/
-class CurrentRelease extends Task
+class CurrentRelease extends AbstractTask
{
- /**
- * A description of what the Task does
+ /**
+ * The slug of the task
+ *
+ * @var string
+ */
+ protected $name = 'Current';
+
+ /**
+ * A description of what the task does
*
* @var string
*/
protected $description = 'Display what the current release is';
/**
- * Run the Task
+ * Run the task
*
- * @return void
+ * @return string|null
*/
public function execute()
{
// Get the current stage
- $stage = $this->rocketeer->getStage();
+ $stage = $this->connections->getStage();
$stage = $stage ? ' for stage '.$stage : '';
// Check if a release has been deployed already
$currentRelease = $this->releasesManager->getCurrentRelease();
if (!$currentRelease) {
- return $this->command->error('No release has yet been deployed'.$stage);
+ return $this->explainer->error('No release has yet been deployed'.$stage);
}
// Create state message
$date = DateTime::createFromFormat('YmdHis', $currentRelease)->format('Y-m-d H:i:s');
$state = $this->runForCurrentRelease($this->scm->currentState());
- $message = sprintf('The current release' .$stage. ' is %s (%s deployed at %s)', $currentRelease, $state, $date);
+ $message = sprintf(
+ 'The current release'.$stage.' is %s (%s deployed at %s)',
+ $currentRelease, $state, $date
+ );
// Display current and past releases
- $this->command->line($message);
+ $this->explainer->line($message);
$this->displayReleases();
return $message;
diff --git a/src/Rocketeer/Tasks/Dependencies.php b/src/Rocketeer/Tasks/Dependencies.php
new file mode 100644
index 000000000..5d3f69189
--- /dev/null
+++ b/src/Rocketeer/Tasks/Dependencies.php
@@ -0,0 +1,38 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Tasks;
+
+use Rocketeer\Abstracts\AbstractTask;
+
+class Dependencies extends AbstractTask
+{
+ /**
+ * A description of what the task does
+ *
+ * @var string
+ */
+ protected $description = 'Installs or update the dependencies on server';
+
+ /**
+ * Run the task
+ *
+ * @return boolean
+ */
+ public function execute()
+ {
+ $method = $this->getOption('update', true) ? 'update' : 'install';
+ $dependencies = $this->getStrategy('Dependencies');
+ if (!$dependencies) {
+ return true;
+ }
+
+ return $dependencies->$method();
+ }
+}
diff --git a/src/Rocketeer/Tasks/Deploy.php b/src/Rocketeer/Tasks/Deploy.php
index b7a4e20e7..f87998b3a 100644
--- a/src/Rocketeer/Tasks/Deploy.php
+++ b/src/Rocketeer/Tasks/Deploy.php
@@ -9,138 +9,94 @@
*/
namespace Rocketeer\Tasks;
-use Rocketeer\Traits\Task;
+use Rocketeer\Abstracts\AbstractTask;
/**
* Deploy the website
*
* @author Maxime Fabre
*/
-class Deploy extends Task
+class Deploy extends AbstractTask
{
/**
- * Methods that can halt deployment
+ * The console command description.
*
- * @var array
+ * @var string
*/
- protected $halting = array();
+ protected $description = 'Deploys the website';
/**
- * Run the Task
+ * Run the task
*
- * @return void
+ * @return boolean|null
*/
public function execute()
{
- // Create halting events
- $this->createEvents();
-
- // Setup if necessary
+ // Check if server is ready for deployment
if (!$this->isSetup()) {
- $this->command->error('Server is not ready, running Setup task');
+ $this->explainer->error('Server is not ready, running Setup task');
$this->executeTask('Setup');
}
- // Update current release
- $release = $this->releasesManager->updateCurrentRelease();
+ // Check if local is ready for deployment
+ if (!$this->executeTask('Primer')) {
+ return $this->halt('Project is not ready for deploy. You were almost fired.');
+ }
+
+ // Setup the new release
+ $release = $this->releasesManager->getNextRelease();
- // Run halting methods
- foreach ($this->halting as $method) {
- if (!$this->fireEvent($method)) {
- return false;
- }
+ // Create release and set it up
+ $this->steps()->executeTask('CreateRelease');
+ $this->steps()->executeTask('Dependencies');
- if (!$this->$method()) {
- return $this->halt();
- }
+ if ($this->getOption('tests')) {
+ $this->steps()->executeTask('Test');
}
- // Set permissions
- $this->setApplicationPermissions();
+ // Create release and set permissions
+ $this->steps()->setApplicationPermissions();
// Run migrations
- $this->runMigrationsAndSeed();
+ if ($this->getOption('migrate') || $this->getOption('seed')) {
+ $this->steps()->executeTask('Migrate');
+ }
// Synchronize shared folders and files
- $this->syncSharedFolders();
+ $this->steps()->syncSharedFolders();
// Run before-symlink events
- if (!$this->fireEvent('before-symlink')) {
- return $this->halt();
- }
-
- // Update symlink and mark release as valid
- $this->updateSymlink();
- $this->releasesManager->markReleaseAsValid($release);
-
- $this->command->info('Successfully deployed release '.$release);
- }
+ $this->steps()->fireEvent('before-symlink');
- ////////////////////////////////////////////////////////////////////
- /////////////////////////////// SUBTASKS ///////////////////////////
- ////////////////////////////////////////////////////////////////////
+ // Update symlink
+ $this->steps()->updateSymlink();
- /**
- * Run PHPUnit tests
- *
- * @return void
- */
- protected function checkTestsResults()
- {
- if ($this->getOption('tests') and !$this->runTests()) {
- $this->command->error('Tests failed');
-
- return false;
+ // Run the steps until one fails
+ if (!$this->runSteps()) {
+ return $this->halt();
}
- return true;
- }
-
- /**
- * Run migrations and seed database
- *
- * @return void
- */
- protected function runMigrationsAndSeed()
- {
- $seed = $this->getOption('seed');
+ $this->releasesManager->markReleaseAsValid($release);
- if ($this->getOption('migrate')) {
- return $this->runMigrations($seed);
- } elseif ($seed) {
- return $this->runSeed();
- }
+ $this->explainer->line('Successfully deployed release '.$release);
}
////////////////////////////////////////////////////////////////////
/////////////////////////////// HELPERS ////////////////////////////
////////////////////////////////////////////////////////////////////
- /**
- * Create the events Deploy will run
- *
- * @return void
- */
- protected function createEvents()
- {
- $strategy = $this->rocketeer->getOption('remote.strategy');
- $this->halting = array(
- $strategy.'Repository',
- 'runComposer',
- 'checkTestsResults',
- );
- }
-
/**
* Set permissions for the folders used by the application
*
- * @return void
+ * @return boolean
*/
protected function setApplicationPermissions()
{
$files = (array) $this->rocketeer->getOption('remote.permissions.files');
- foreach ($files as $file) {
+ foreach ($files as &$file) {
$this->setPermissions($file);
}
+
+ return true;
}
}
diff --git a/src/Rocketeer/Tasks/Ignite.php b/src/Rocketeer/Tasks/Ignite.php
index 52481ac51..0371473d8 100644
--- a/src/Rocketeer/Tasks/Ignite.php
+++ b/src/Rocketeer/Tasks/Ignite.php
@@ -9,17 +9,18 @@
*/
namespace Rocketeer\Tasks;
-use Rocketeer\Traits\Task;
+use Illuminate\Support\Arr;
+use Rocketeer\Abstracts\AbstractTask;
/**
* A task to ignite Rocketeer
*
* @author Maxime Fabre
*/
-class Ignite extends Task
+class Ignite extends AbstractTask
{
- /**
- * A description of what the Task does
+ /**
+ * A description of what the task does
*
* @var string
*/
@@ -78,16 +79,17 @@ protected function createOutsideConfiguration()
protected function getConfigurationInformations()
{
// Replace credentials
- $repositoryCredentials = $this->rocketeer->getCredentials();
- $name = basename($this->app['path.base']);
+ $repositoryCredentials = $this->connections->getRepositoryCredentials();
+ $name = basename($this->app['path.base']);
return array_merge(
- $this->rocketeer->getConnectionCredentials(),
+ $this->connections->getServerCredentials(),
array(
- 'scm_repository' => $repositoryCredentials['repository'],
- 'scm_username' => $repositoryCredentials['username'],
- 'scm_password' => $repositoryCredentials['password'],
- 'application_name' => $this->command->ask("What is your application's name ? (" .$name. ")", $name),
+ 'connection' => preg_replace('/#[0-9]+/', null, $this->connections->getConnection()),
+ 'scm_repository' => Arr::get($repositoryCredentials, 'repository'),
+ 'scm_username' => Arr::get($repositoryCredentials, 'username'),
+ 'scm_password' => Arr::get($repositoryCredentials, 'password'),
+ 'application_name' => $this->command->ask('What is your application\'s name ? ('.$name.')', $name),
)
);
}
diff --git a/src/Rocketeer/Tasks/Migrate.php b/src/Rocketeer/Tasks/Migrate.php
new file mode 100644
index 000000000..5db14f074
--- /dev/null
+++ b/src/Rocketeer/Tasks/Migrate.php
@@ -0,0 +1,56 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Tasks;
+
+use Rocketeer\Abstracts\AbstractTask;
+
+class Migrate extends AbstractTask
+{
+ /**
+ * The console command description.
+ *
+ * @var string
+ */
+ protected $description = 'Migrates and/or seed the database';
+
+ /**
+ * Run the task
+ *
+ * @return boolean|boolean[]
+ */
+ public function execute()
+ {
+ $results = [];
+
+ // Get strategy and options
+ $migrate = $this->getOption('migrate');
+ $seed = $this->getOption('seed');
+ $strategy = $this->getStrategy('Migrate');
+
+ // Cancel if nothing to run
+ if (!$strategy || (!$migrate && !$seed)) {
+ return true;
+ }
+
+ // Migrate the database
+ if ($migrate) {
+ $this->explainer->line('Running outstanding migrations');
+ $results[] = $strategy->migrate();
+ }
+
+ // Seed it
+ if ($seed) {
+ $this->explainer->line('Seeding database');
+ $results[] = $strategy->seed();
+ }
+
+ return $results;
+ }
+}
diff --git a/src/Rocketeer/Tasks/Plugins/Installer.php b/src/Rocketeer/Tasks/Plugins/Installer.php
new file mode 100644
index 000000000..5c67a0a9a
--- /dev/null
+++ b/src/Rocketeer/Tasks/Plugins/Installer.php
@@ -0,0 +1,50 @@
+command->argument('package');
+ $folder = $this->paths->getRocketeerConfigFolder();
+
+ // Add version if necessary
+ if (strpos($package, ':') === false) {
+ $package .= ':dev-master';
+ }
+
+ $command = $this->composer()->require($package, array(
+ '--working-dir' => $folder,
+ ));
+
+ // Install plugin
+ $this->explainer->line('Installing '.$package);
+ $this->run($this->shellCommand($command));
+
+ // Prune duplicate Rocketeer
+ $this->files->deleteDirectory($folder.'/vendor/anahkiasen/rocketeer');
+ }
+}
diff --git a/src/Rocketeer/Tasks/Rollback.php b/src/Rocketeer/Tasks/Rollback.php
index 6f870e582..3d9ed0cee 100644
--- a/src/Rocketeer/Tasks/Rollback.php
+++ b/src/Rocketeer/Tasks/Rollback.php
@@ -9,40 +9,47 @@
*/
namespace Rocketeer\Tasks;
-use Rocketeer\Traits\Task;
+use Rocketeer\Abstracts\AbstractTask;
/**
* Rollback to the previous release, or to a specific one
*
* @author Maxime Fabre
*/
-class Rollback extends Task
+class Rollback extends AbstractTask
{
/**
- * Run the Task
+ * The console command description.
*
- * @return void
+ * @var string
+ */
+ protected $description = 'Rollback to the previous release, or to a specific one';
+
+ /**
+ * Run the task
+ *
+ * @return string|null
*/
public function execute()
{
// Get previous release
$rollbackRelease = $this->getRollbackRelease();
if (!$rollbackRelease) {
- $this->command->error('Rocketeer could not rollback as no releases have yet been deployed');
+ return $this->explainer->error('Rocketeer could not rollback as no releases have yet been deployed');
}
// If no release specified, display the available ones
- if (array_get($this->command->option(), 'list')) {
+ if ($this->command->option('list')) {
$releases = $this->releasesManager->getReleases();
$this->displayReleases();
// Get actual release name from date
- $rollbackRelease = $this->command->ask('Which one do you want to go back to ? (0)', 0);
+ $rollbackRelease = $this->command->askWith('Which one do you want to go back to ?', 0);
$rollbackRelease = $releases[$rollbackRelease];
}
// Rollback release
- $this->command->info('Rolling back to release '.$rollbackRelease);
+ $this->explainer->success('Rolling back to release '.$rollbackRelease);
$this->updateSymlink($rollbackRelease);
}
@@ -53,11 +60,11 @@ public function execute()
/**
* Get the release to rollback to
*
- * @return integer
+ * @return integer|null
*/
protected function getRollbackRelease()
{
- $release = array_get($this->command->argument(), 'release');
+ $release = $this->command->argument('release');
if (!$release) {
$release = $this->releasesManager->getPreviousRelease();
}
diff --git a/src/Rocketeer/Tasks/Setup.php b/src/Rocketeer/Tasks/Setup.php
index e2b1ed9ec..b1cafc236 100644
--- a/src/Rocketeer/Tasks/Setup.php
+++ b/src/Rocketeer/Tasks/Setup.php
@@ -9,38 +9,38 @@
*/
namespace Rocketeer\Tasks;
-use Rocketeer\Traits\Task;
+use Rocketeer\Abstracts\AbstractTask;
/**
* Set up the remote server for deployment
*
* @author Maxime Fabre
*/
-class Setup extends Task
+class Setup extends AbstractTask
{
- /**
- * A description of what the Task does
+ /**
+ * A description of what the task does
*
* @var string
*/
protected $description = 'Set up the remote server for deployment';
/**
- * Whether the Task needs to be run on each stage or globally
+ * Whether the task needs to be run on each stage or globally
*
* @var boolean
*/
public $usesStages = false;
/**
- * Run the Task
+ * Run the task
*
- * @return void
+ * @return string|false|null
*/
public function execute()
{
- // Check if requirments are met
- if ($this->executeTask('Check') === false and !$this->getOption('pretend')) {
+ // Check if requirements are met
+ if ($this->executeTask('Check') === false && !$this->getOption('pretend')) {
return false;
}
@@ -49,19 +49,19 @@ public function execute()
$this->createStages();
// Set setup to true
- $this->server->setValue('is_setup', true);
+ $this->localStorage->set('is_setup', true);
// Get server informations
- $this->command->comment('Getting some informations about the server');
- $this->server->getSeparator();
- $this->server->getLineEndings();
+ $this->explainer->line('Getting some informations about the server');
+ $this->localStorage->getSeparator();
+ $this->localStorage->getLineEndings();
// Create confirmation message
$application = $this->rocketeer->getApplicationName();
- $homeFolder = $this->rocketeer->getHomeFolder();
+ $homeFolder = $this->paths->getHomeFolder();
$message = sprintf('Successfully setup "%s" at "%s"', $application, $homeFolder);
- return $this->command->info($message);
+ return $this->explainer->success($message);
}
////////////////////////////////////////////////////////////////////
@@ -76,22 +76,22 @@ public function execute()
protected function createStages()
{
// Get stages
- $availableStages = $this->rocketeer->getStages();
- $originalStage = $this->rocketeer->getStage();
+ $availableStages = $this->connections->getStages();
+ $originalStage = $this->connections->getStage();
if (empty($availableStages)) {
- $availableStages = array(null);
+ $availableStages = [null];
}
// Create folders
foreach ($availableStages as $stage) {
- $this->rocketeer->setStage($stage);
+ $this->connections->setStage($stage);
$this->createFolder('releases', true);
$this->createFolder('current', true);
$this->createFolder('shared', true);
}
if ($originalStage) {
- $this->rocketeer->setStage($originalStage);
+ $this->connections->setStage($originalStage);
}
}
}
diff --git a/src/Rocketeer/Tasks/Subtasks/CreateRelease.php b/src/Rocketeer/Tasks/Subtasks/CreateRelease.php
new file mode 100644
index 000000000..a07d297e8
--- /dev/null
+++ b/src/Rocketeer/Tasks/Subtasks/CreateRelease.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Tasks\Subtasks;
+
+use Rocketeer\Abstracts\AbstractTask;
+
+/**
+ * Creates a new release on the server
+ *
+ * @author Maxime Fabre
+ */
+class CreateRelease extends AbstractTask
+{
+ /**
+ * A description of what the task does
+ *
+ * @var string
+ */
+ protected $description = 'Creates a new release on the server';
+
+ /**
+ * Run the task
+ *
+ * @return string
+ */
+ public function execute()
+ {
+ return $this->getStrategy('Deploy')->deploy();
+ }
+}
diff --git a/src/Rocketeer/Tasks/Subtasks/Notify.php b/src/Rocketeer/Tasks/Subtasks/Notify.php
new file mode 100644
index 000000000..2c6bbba98
--- /dev/null
+++ b/src/Rocketeer/Tasks/Subtasks/Notify.php
@@ -0,0 +1,113 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Tasks\Subtasks;
+
+use Illuminate\Support\Arr;
+use Rocketeer\Abstracts\AbstractTask;
+use Rocketeer\Plugins\AbstractNotifier;
+
+/**
+ * Notify a third-party service
+ *
+ * @author Maxime Fabre
+ */
+class Notify extends AbstractTask
+{
+ /**
+ * The message format
+ *
+ * @type AbstractNotifier
+ */
+ protected $notifier;
+
+ /**
+ * A description of what the task does
+ *
+ * @var string
+ */
+ protected $description = 'Notify a third-party service';
+
+ /**
+ * Run the task
+ *
+ * @return string|null
+ */
+ public function execute()
+ {
+ $hook = str_replace('deploy.', null, $this->event).'_deploy';
+
+ $this->prepareThenSend($hook);
+ }
+
+ /**
+ * @param AbstractNotifier $notifier
+ */
+ public function setNotifier($notifier)
+ {
+ $this->notifier = $notifier;
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ /////////////////////////////// MESSAGE ////////////////////////////
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get the message's components
+ *
+ * @return string[]
+ */
+ protected function getComponents()
+ {
+ // Get user name
+ $user = $this->localStorage->get('notifier.name');
+ if (!$user) {
+ $user = $this->command->ask('Who is deploying ?');
+ $this->localStorage->set('notifier.name', $user);
+ }
+
+ // Get what was deployed
+ $branch = $this->connections->getRepositoryBranch();
+ $stage = $this->connections->getStage();
+ $connection = $this->connections->getConnection();
+ $server = $this->connections->getServer();
+
+ // Get hostname
+ $credentials = $this->connections->getServerCredentials($connection, $server);
+ $host = Arr::get($credentials, 'host');
+ if ($stage) {
+ $connection = $stage.'@'.$connection;
+ }
+
+ return compact('user', 'branch', 'connection', 'host');
+ }
+
+ /**
+ * Prepare and send a message
+ *
+ * @param string $message
+ *
+ * @return void
+ */
+ public function prepareThenSend($message)
+ {
+ // Don't send a notification if pretending to deploy
+ if ($this->command->option('pretend')) {
+ return;
+ }
+
+ // Build message
+ $message = $this->notifier->getMessageFormat($message);
+ $message = preg_replace('#\{([0-9])\}#', '%$1\$s', $message);
+ $message = vsprintf($message, $this->getComponents());
+
+ // Send it
+ $this->notifier->send($message);
+ }
+}
diff --git a/src/Rocketeer/Tasks/Subtasks/Primer.php b/src/Rocketeer/Tasks/Subtasks/Primer.php
new file mode 100644
index 000000000..691913a12
--- /dev/null
+++ b/src/Rocketeer/Tasks/Subtasks/Primer.php
@@ -0,0 +1,59 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Tasks\Subtasks;
+
+use Rocketeer\Abstracts\AbstractTask;
+
+/**
+ * Executes some sanity-check commands before deploy
+ *
+ * @author Maxime Fabre
+ */
+class Primer extends AbstractTask
+{
+ /**
+ * A description of what the task does
+ *
+ * @var string
+ */
+ protected $description = 'Run local checks to ensure deploy can proceed';
+
+ /**
+ * Whether to run the commands locally
+ * or on the server
+ *
+ * @type boolean
+ */
+ protected $local = true;
+
+ /**
+ * Whether the task needs to be run on each stage or globally
+ *
+ * @var boolean
+ */
+ public $usesStages = false;
+
+ /**
+ * Run the task
+ *
+ * @return boolean
+ */
+ public function execute()
+ {
+ $tasks = $this->getHookedTasks('primer', [$this]);
+ if (!$tasks) {
+ return true;
+ }
+
+ $this->run($tasks);
+
+ return $this->status();
+ }
+}
diff --git a/src/Rocketeer/Tasks/Teardown.php b/src/Rocketeer/Tasks/Teardown.php
index 870221123..6146dcf5c 100644
--- a/src/Rocketeer/Tasks/Teardown.php
+++ b/src/Rocketeer/Tasks/Teardown.php
@@ -9,47 +9,50 @@
*/
namespace Rocketeer\Tasks;
-use Rocketeer\Traits\Task;
+use Rocketeer\Abstracts\AbstractTask;
/**
* Remove the remote applications and existing caches
*
* @author Maxime Fabre
*/
-class Teardown extends Task
+class Teardown extends AbstractTask
{
- /**
- * A description of what the Task does
+ /**
+ * A description of what the task does
*
* @var string
*/
protected $description = 'Remove the remote applications and existing caches';
/**
- * Whether the Task needs to be run on each stage or globally
+ * Whether the task needs to be run on each stage or globally
*
* @var boolean
*/
public $usesStages = false;
/**
- * Run the Task
+ * Run the task
*
* @return void
*/
public function execute()
{
// Ask confirmation
- $confirm = $this->command->confirm('This will remove all folders on the server, not just releases. Do you want to proceed ?');
+ $confirm = $this->command->confirm(
+ 'This will remove all folders on the server, not just releases. Do you want to proceed ?'
+ );
+
if (!$confirm) {
return $this->command->info('Teardown aborted');
}
// Remove remote folders
- $this->removeFolder();
+ $this->removeFolder($this->paths->getFolder());
// Remove deployments file
- $this->server->deleteRepository();
+ $this->localStorage->destroy();
$this->command->info('The application was successfully removed from the remote servers');
}
diff --git a/src/Rocketeer/Tasks/Test.php b/src/Rocketeer/Tasks/Test.php
index 7d8f632b7..af0445101 100644
--- a/src/Rocketeer/Tasks/Test.php
+++ b/src/Rocketeer/Tasks/Test.php
@@ -9,32 +9,34 @@
*/
namespace Rocketeer\Tasks;
-use Rocketeer\Traits\Task;
+use Rocketeer\Abstracts\AbstractTask;
/**
* Run the tests on the server and displays the output
*
* @author Maxime Fabre
*/
-class Test extends Task
+class Test extends AbstractTask
{
- /**
- * A description of what the Task does
+ /**
+ * A description of what the task does
*
* @var string
*/
protected $description = 'Run the tests on the server and displays the output';
/**
- * Run the Task
+ * Run the task
*
- * @return void
+ * @return boolean
*/
public function execute()
{
- // Update repository
- $this->command->info('Testing the application');
+ $tester = $this->getStrategy('Test');
+ if (!$tester) {
+ return true;
+ }
- return $this->runTests();
+ return $tester->test();
}
}
diff --git a/src/Rocketeer/Tasks/Update.php b/src/Rocketeer/Tasks/Update.php
index 3242322e2..8b736d833 100644
--- a/src/Rocketeer/Tasks/Update.php
+++ b/src/Rocketeer/Tasks/Update.php
@@ -16,39 +16,51 @@
*/
class Update extends Deploy
{
- /**
- * A description of what the Task does
+ /**
+ * A description of what the task does
*
* @var string
*/
protected $description = 'Update the remote server without doing a new release';
/**
- * Run the Task
+ * Run the task
*
- * @return void
+ * @return boolean|null
*/
public function execute()
{
+ // Check if local is ready for deployment
+ if (!$this->executeTask('Primer')) {
+ return $this->halt('Project is not ready for deploy. You were almost fired.');
+ }
+
// Update repository
- $this->updateRepository();
+ if (!$this->getStrategy('Deploy')->update()) {
+ return $this->halt();
+ }
// Recreate symlinks if necessary
- $this->syncSharedFolders();
+ $this->steps()->syncSharedFolders();
// Recompile dependencies and stuff
- $this->runComposer();
+ $this->steps()->executeTask('Dependencies');
// Set permissions
- $this->setApplicationPermissions();
+ $this->steps()->setApplicationPermissions();
// Run migrations
- if ($this->getOption('migrate')) {
- $this->runMigrations($this->getOption('seed'));
+ if ($this->getOption('migrate') || $this->getOption('seed')) {
+ $this->steps()->executeTask('Migrate');
+ }
+
+ // Run the steps
+ if (!$this->runSteps()) {
+ return $this->halt();
}
// Clear cache
- $this->runForCurrentRelease($this->artisan('cache:clear'));
+ $this->artisan()->runForCurrentRelease('clearCache');
$this->command->info('Successfully updated application');
}
diff --git a/src/Rocketeer/TasksQueue.php b/src/Rocketeer/TasksQueue.php
deleted file mode 100644
index 0217a1184..000000000
--- a/src/Rocketeer/TasksQueue.php
+++ /dev/null
@@ -1,315 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace Rocketeer;
-
-use Closure;
-use Rocketeer\Traits\AbstractLocatorClass;
-use Rocketeer\Traits\Task;
-
-/**
- * Handles the building and execution of tasks
- *
- * @author Maxime Fabre
- */
-class TasksQueue extends AbstractLocatorClass
-{
- /**
- * A list of Tasks to execute
- *
- * @var array
- */
- protected $tasks;
-
- /**
- * The Remote connection
- *
- * @var Connection
- */
- protected $remote;
-
- /**
- * The output of the queue
- *
- * @var array
- */
- protected $output = array();
-
- ////////////////////////////////////////////////////////////////////
- ////////////////////////////// SHORTCUTS ///////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Execute Tasks on the default connection
- *
- * @param string|array|Closure $queue
- * @param string|array $connections
- *
- * @return array
- */
- public function execute($queue, $connections = null)
- {
- if ($connections) {
- $this->rocketeer->setConnections($connections);
- }
-
- $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)
- {
- return $this->execute($queue, $connections);
- }
-
- ////////////////////////////////////////////////////////////////////
- //////////////////////////////// QUEUE /////////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Run the queue
- *
- * Run an array of Tasks instances on the various
- * connections and stages provided
- *
- * @param array $tasks An array of tasks
- *
- * @return array An array of output
- */
- public function run(array $tasks)
- {
- // First we'll build the queue
- $queue = $this->buildQueue($tasks);
-
- // Get the connections to execute the tasks on
- $connections = (array) $this->rocketeer->getConnections();
- foreach ($connections as $connection) {
- $this->rocketeer->setConnection($connection);
-
- // Check if we provided a stage
- $stage = $this->getStage();
- $stages = $this->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) {
- $this->runQueue($queue, $stage);
- }
- } else {
- $this->runQueue($queue);
- }
- }
-
- return $this->output;
- }
-
- /**
- * Run the queue, taking into account the stage
- *
- * @param array $tasks
- * @param string $stage
- *
- * @return boolean
- */
- protected function runQueue($tasks, $stage = null)
- {
- foreach ($tasks as $task) {
- $currentStage = $task->usesStages() ? $stage : null;
- $this->rocketeer->setStage($currentStage);
-
- // Here we fire the task and if it was halted
- // at any point, we cancel the whole queue
- $state = $task->fire();
- $this->output[] = $state;
- if ($task->wasHalted() or $state === false) {
- $this->command->error('Deployment was canceled by task "'.$task->getName(). '"');
- return false;
- }
- }
-
- return true;
- }
-
- ////////////////////////////////////////////////////////////////////
- /////////////////////////////// BUILDING ///////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Build a queue from a list of tasks
- *
- * Here we will take the various Tasks names, closures and string tasks
- * and unify all of those to actual Task instances
- *
- * @param array $tasks
- *
- * @return array
- */
- public function buildQueue(array $tasks)
- {
- foreach ($tasks as &$task) {
- $task = $this->buildTask($task);
- }
-
- return $tasks;
- }
-
- /**
- * Build a task from anything
- *
- * @param mixed $task
- * @param string $name
- *
- * @return Task
- */
- public function buildTask($task, $name = null)
- {
- // Check the handle if possible
- if (is_string($task)) {
- $handle = 'rocketeer.tasks.'.$task;
- }
-
- // If we provided a Closure or a string command, build it
- if ($task instanceof Closure or $this->isStringCommand($task)) {
- $task = $this->buildTaskFromClosure($task);
- }
-
- // Check for an existing container binding
- elseif (isset($handle) and $this->app->bound($handle)) {
- return $this->app[$handle];
- }
-
- // Build remaining tasks
- if (!$task instanceof Task) {
- $task = $this->buildTaskFromClass($task);
- }
-
- // Set the task's name
- if ($name) {
- $task->setName($name);
- }
-
- return $task;
- }
-
- /**
- * Build a Task from a Closure or a string command
- *
- * @param Closure|string $task
- *
- * @return Task
- */
- public function buildTaskFromClosure($task)
- {
- // If the User provided a string to execute
- // We'll build a closure from it
- if ($this->isStringCommand($task)) {
- $stringTask = $task;
- $closure = function ($task) use ($stringTask) {
- return $task->runForCurrentRelease($stringTask);
- };
-
- // If the User provided a Closure
- } elseif ($task instanceof Closure) {
- $closure = $task;
- }
-
- // Now that we unified it all to a Closure, we build
- // a Closure Task from there
- $task = $this->buildTaskFromClass('Rocketeer\Tasks\Closure');
- $task->setClosure($closure);
-
- // If we had an original string used, store it on
- // the task for easier reflection
- if (isset($stringTask)) {
- $task->setStringTask($stringTask);
- }
-
- return $task;
- }
-
- /**
- * Build a Task from its name
- *
- * @param string $task
- *
- * @return Task
- */
- public function buildTaskFromClass($task)
- {
- if (is_object($task) and $task instanceof Task) {
- return $task;
- }
-
- // Shortcut for calling Rocketeer Tasks
- if (class_exists('Rocketeer\Tasks\\'.ucfirst($task))) {
- $task = 'Rocketeer\Tasks\\'.ucfirst($task);
- }
-
- // Cancel if class doesn't exist
- if (!class_exists($task)) {
- return $task;
- }
-
- return new $task($this->app);
- }
-
- ////////////////////////////////////////////////////////////////////
- //////////////////////////////// STAGES ////////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Get the stage to execute Tasks in
- * If null, execute on all stages
- *
- * @return string
- */
- protected function getStage()
- {
- $stage = $this->rocketeer->getOption('stages.default');
- if ($this->hasCommand()) {
- $stage = $this->command->option('stage') ?: $stage;
- }
-
- // Return all stages if "all"
- if ($stage == 'all') {
- $stage = null;
- }
-
- return $stage;
- }
-
- ////////////////////////////////////////////////////////////////////
- /////////////////////////////// HELPERS ////////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Check if a string is a command or a task
- *
- * @param string $string
- *
- * @return boolean
- */
- protected function isStringCommand($string)
- {
- return is_string($string) and !class_exists($string) and !$this->app->bound('rocketeer.tasks.'.$string);
- }
-}
diff --git a/src/Rocketeer/Traits/AbstractLocatorClass.php b/src/Rocketeer/Traits/AbstractLocatorClass.php
deleted file mode 100644
index 7e85d5a7f..000000000
--- a/src/Rocketeer/Traits/AbstractLocatorClass.php
+++ /dev/null
@@ -1,95 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace Rocketeer\Traits;
-
-use Illuminate\Container\Container;
-
-/**
- * An abstract for Service Locator-based classes with adds
- * a few shortcuts to Rocketeer classes
- *
- * @property ReleasesManager $releasesManager
- * @property Rocketeer $rocketeer
- * @property Server $server
- * @property Illuminate\Remote\Connection $remote
- * @property Traits\Scm $scm
- *
- * @author Maxime Fabre
- */
-abstract class AbstractLocatorClass
-{
- /**
- * The IoC Container
- *
- * @var Container
- */
- protected $app;
-
- /**
- * Build a new Task
- *
- * @param Container $app
- * @param Command|null $command
- */
- public function __construct(Container $app)
- {
- $this->app = $app;
- }
-
- /**
- * Get an instance from the Container
- *
- * @param string $key
- *
- * @return object
- */
- public function __get($key)
- {
- $shortcuts = array(
- 'command' => 'rocketeer.command',
- 'console' => 'rocketeer.console',
- 'logs' => 'rocketeer.logs',
- 'queue' => 'rocketeer.queue',
- 'releasesManager' => 'rocketeer.releases',
- 'rocketeer' => 'rocketeer.rocketeer',
- 'scm' => 'rocketeer.scm',
- 'server' => 'rocketeer.server',
- 'tasks' => 'rocketeer.tasks',
- );
-
- // Replace shortcuts
- if (array_key_exists($key, $shortcuts)) {
- $key = $shortcuts[$key];
- }
-
- return $this->app[$key];
- }
-
- /**
- * Set an instance on the Container
- *
- * @param string $key
- * @param object $value
- */
- public function __set($key, $value)
- {
- $this->app[$key] = $value;
- }
-
- /**
- * Check if the current instance has a Command bound
- *
- * @return boolean
- */
- protected function hasCommand()
- {
- return $this->app->bound('rocketeer.command');
- }
-}
diff --git a/src/Rocketeer/Traits/BashModules/Binaries.php b/src/Rocketeer/Traits/BashModules/Binaries.php
index ebaa31f6c..9924727da 100644
--- a/src/Rocketeer/Traits/BashModules/Binaries.php
+++ b/src/Rocketeer/Traits/BashModules/Binaries.php
@@ -10,243 +10,144 @@
namespace Rocketeer\Traits\BashModules;
/**
- * Handles findingand calling binaries
+ * Handles finding and calling binaries
*
* @author Maxime Fabre
*/
-class Binaries extends Filesystem
+trait Binaries
{
////////////////////////////////////////////////////////////////////
/////////////////////////////// BINARIES ///////////////////////////
////////////////////////////////////////////////////////////////////
/**
- * Prefix a command with the right path to PHP
+ * Get an AnonymousBinary instance
*
- * @param string $command
+ * @param string $binary
*
- * @return string
+ * @return \Rocketeer\Abstracts\AbstractBinary|\Rocketeer\Abstracts\AbstractPackageManager
*/
- public function php($command = null)
+ public function binary($binary)
{
- $php = $this->which('php');
-
- return trim($php. ' ' .$command);
+ return $this->builder->buildBinary($binary);
}
- // Artisan
- ////////////////////////////////////////////////////////////////////
-
/**
- * Prefix a command with the right path to Artisan
- *
- * @param string $command
- * @param array $flags
+ * Prefix a command with the right path to PHP
*
- * @return string
+ * @return \Rocketeer\Binaries\Php
*/
- public function artisan($command = null, $flags = array())
+ public function php()
{
- $artisan = $this->which('artisan') ?: 'artisan';
- foreach ($flags as $name => $value) {
- $command .= ' --'.$name;
- $command .= $value ? '="' .$value. '"' : '';
- }
-
- return $this->php($artisan. ' ' .$command);
+ return $this->binary('php');
}
/**
- * Run an artisan command
- *
- * @param string $command
- * @param array $flags
+ * Prefix a command with the right path to Composer
*
- * @return string
+ * @return \Rocketeer\Binaries\Composer
*/
- public function runArtisan($command = null, $flags = array())
+ public function composer()
{
- // Check if the seeds/migration need to be forced
- $forced = array('migrate', 'db:seed');
- if (in_array($command, $forced) && $this->versionCheck('4.2.0')) {
- $flags['force'] = '';
- }
-
- // Create full command
- $command = $this->artisan($command, $flags);
-
- return $this->runForCurrentRelease($command);
+ return $this->binary('composer');
}
/**
- * Run any outstanding migrations
- *
- * @param boolean $seed Whether the database should also be seeded
- *
- * @return string
+ * @return \Rocketeer\Binaries\Phpunit
*/
- public function runMigrations($seed = false)
+ public function phpunit()
{
- $this->command->comment('Running outstanding migrations');
- $flags = $seed ? array('seed' => '') : array();
-
- return $this->runArtisan('migrate', $flags);
+ return $this->binary('phpunit');
}
/**
- * Seed the database
- *
- * @param string $class A class to seed
- *
- * @return string
+ * @return \Rocketeer\Binaries\Artisan
*/
- public function runSeed($class = null)
+ public function artisan()
{
- $this->command->comment('Seeding database');
- $flags = $class ? array('class' => $class) : array();
-
- return $this->runArtisan('db:seed', $flags);
+ return $this->binary('artisan');
}
- // PHPUnit
////////////////////////////////////////////////////////////////////
-
- /**
- * Run the application's tests
- *
- * @param string $arguments Additional arguments to pass to PHPUnit
- *
- * @return boolean
- */
- public function runTests($arguments = null)
- {
- // Look for PHPUnit
- $phpunit = $this->which('phpunit', $this->releasesManager->getCurrentReleasePath().'/vendor/bin/phpunit');
- if (!$phpunit) {
- return true;
- }
-
- // Run PHPUnit
- $this->command->info('Running tests...');
- $output = $this->runForCurrentRelease(array(
- $phpunit. ' --stop-on-failure '.$arguments,
- ));
-
- return $this->checkStatus('Tests failed', $output, 'Tests passed successfully');
- }
-
- // Composer
+ /////////////////////////////// HELPERS ////////////////////////////
////////////////////////////////////////////////////////////////////
/**
- * Prefix a command with the right path to Composer
+ * Get the path to a binary
*
- * @param string $command
+ * @param string $binary The name of the binary
+ * @param string|null $fallback A fallback location
+ * @param boolean $prompt
*
* @return string
*/
- public function composer($command = null)
+ public function which($binary, $fallback = null, $prompt = true)
{
- $composer = $this->which('composer', $this->releasesManager->getCurrentReleasePath().'/composer.phar');
-
- // Prepend PHP command
- if (strpos($composer, 'composer.phar') !== false) {
- $composer = $this->php($composer);
- }
-
- return trim($composer. ' ' .$command);
- }
-
- /**
- * Run Composer on the folder
- *
- * @param boolean $force
- *
- * @return string
- */
- public function runComposer($force = false)
- {
- if (!$this->server->usesComposer() and !$force) {
- return true;
- }
-
- // Find Composer
- $composer = $this->composer();
- if (!$composer) {
- return true;
- }
+ $locations = array(
+ [$this->localStorage, 'get', 'paths.'.$binary],
+ [$this->paths, 'getPath', $binary],
+ [$this, 'runSilently', 'which '.$binary],
+ );
- // Get the Composer commands to run
- $tasks = $this->rocketeer->getOption('remote.composer');
- if (!is_callable($tasks)) {
- return true;
+ // Add fallback if provided
+ if ($fallback) {
+ $locations[] = [$this, 'runSilently', 'which '.$fallback];
}
- // Cancel if no tasks to execute
- $tasks = (array) $tasks($this);
- if (empty($tasks)) {
- return true;
+ // Add command prompt if possible
+ if ($this->hasCommand() && $prompt) {
+ $prompt = $binary.' could not be found, please enter the path to it';
+ $locations[] = [$this->command, 'ask', $prompt];
}
- // Run commands
- $this->command->comment('Installing Composer dependencies');
- $this->runForCurrentRelease($tasks);
-
- return $this->checkStatus('Composer could not install dependencies');
+ return $this->whichFrom($binary, $locations);
}
- ////////////////////////////////////////////////////////////////////
- /////////////////////////////// HELPERS ////////////////////////////
- ////////////////////////////////////////////////////////////////////
-
/**
- * Get a binary
+ * Scan an array of locations for a binary
*
- * @param string $binary The name of the binary
- * @param string $fallback A fallback location
+ * @param string $binary
+ * @param array $locations
*
* @return string
*/
- public function which($binary, $fallback = null)
+ protected function whichFrom($binary, array $locations)
{
- $location = false;
- $locations = array(
- array($this->server, 'getValue', 'paths.'.$binary),
- array($this->rocketeer, 'getPath', $binary),
- array($this, 'runSilently', 'which '.$binary),
- );
-
- // Add fallback if provided
- if ($fallback) {
- $locations[] = array($this, 'runSilently', 'which '.$fallback);
- }
-
- // Add command prompt if possible
- if ($this->hasCommand()) {
- $prompt = $binary. ' could not be found, please enter the path to it';
- $locations[] = array($this->command, 'ask', $prompt);
- }
+ $location = false;
// Look in all the locations
$tryout = 0;
- while (!$location and array_key_exists($tryout, $locations)) {
+ while (!$location && array_key_exists($tryout, $locations)) {
list($object, $method, $argument) = $locations[$tryout];
+ // Execute method
$location = $object->$method($argument);
+
+ // Verify existence of returned path
+ if (strpos($location, 'not found') !== false || !$this->fileExists($location)) {
+ $location = null;
+ }
+
$tryout++;
}
- // Store found location
- $this->server->setValue('paths.'.$binary, $location);
+ // Store found location or remove it if invalid
+ if (!$this->local) {
+ if ($location) {
+ $this->localStorage->set('paths.'.$binary, $location);
+ } else {
+ $this->localStorage->forget('paths.'.$binary);
+ }
+ }
- return $location ?: false;
+ return $location ?: $binary;
}
/**
* Check the Laravel version
*
- * @param string $version The version to check against
- * @param string $operator The operator (default: '>=')
+ * @param string $version The version to check against
+ * @param string $operator The operator (default: '>=')
*
* @return bool
*/
diff --git a/src/Rocketeer/Traits/BashModules/Core.php b/src/Rocketeer/Traits/BashModules/Core.php
index 639952f51..707bb7d62 100644
--- a/src/Rocketeer/Traits/BashModules/Core.php
+++ b/src/Rocketeer/Traits/BashModules/Core.php
@@ -9,35 +9,61 @@
*/
namespace Rocketeer\Traits\BashModules;
+use Closure;
use Illuminate\Support\Str;
-use Rocketeer\Traits\AbstractLocatorClass;
+use Rocketeer\Traits\HasHistory;
+use Rocketeer\Traits\HasLocator;
/**
* Core handling of running commands and returning output
*
* @author Maxime Fabre
*/
-class Core extends AbstractLocatorClass
+trait Core
{
+ use HasLocator;
+ use HasHistory;
+
/**
- * An history of executed commands
+ * Whether to run the commands locally
+ * or on the server
*
- * @var array
+ * @type boolean
*/
- protected $history = array();
+ protected $local = false;
- ////////////////////////////////////////////////////////////////////
- /////////////////////////////// HISTORY ////////////////////////////
- ////////////////////////////////////////////////////////////////////
+ /**
+ * @param boolean $local
+ */
+ public function setLocal($local)
+ {
+ $this->local = $local;
+ }
/**
- * Get the Task's history
+ * Get which Connection to call commands with
*
- * @return array
+ * @return \Illuminate\Remote\ConnectionInterface
*/
- public function getHistory()
+ public function getConnection()
{
- return $this->history;
+ return $this->local ? $this->app['remote.local'] : $this->remote;
+ }
+
+ /**
+ * Run a series of commands in local
+ *
+ * @param Closure $callback
+ *
+ * @return boolean
+ */
+ public function onLocal(Closure $callback)
+ {
+ $this->local = true;
+ $results = $callback($this);
+ $this->local = false;
+
+ return $results;
}
////////////////////////////////////////////////////////////////////
@@ -47,42 +73,48 @@ public function getHistory()
/**
* Run actions on the remote server and gather the ouput
*
- * @param string|array $commands One or more commands
- * @param boolean $silent Whether the command should stay silent no matter what
- * @param boolean $array Whether the output should be returned as an array
+ * @param string|array $commands One or more commands
+ * @param boolean $silent Whether the command should stay silent no matter what
+ * @param boolean $array Whether the output should be returned as an array
*
- * @return string|array
+ * @return string|null
*/
public function run($commands, $silent = false, $array = false)
{
$commands = $this->processCommands($commands);
$verbose = $this->getOption('verbose') && !$silent;
+ $pretend = $this->getOption('pretend');
- // Log the commands for pretend
- if ($this->getOption('pretend') and !$silent) {
- return $this->addCommandsToHistory($commands);
+ // Log the commands
+ if (!$silent) {
+ $this->toHistory($commands);
+ }
+
+ // Display for pretend mode
+ if ($verbose || ($pretend && !$silent)) {
+ $this->toOutput($commands);
+ $flattened = implode(PHP_EOL.'$ ', $commands);
+ $this->command->line('$ '.$flattened.'');
+
+ if ($pretend) {
+ return count($commands) == 1 ? $commands[0] : $commands;
+ }
}
// Run commands
- $me = $this;
$output = null;
- $this->remote->run($commands, function ($results) use (&$output, $verbose, $me) {
+ $this->getConnection()->run($commands, function ($results) use (&$output, $verbose) {
$output .= $results;
if ($verbose) {
- $me->remote->display(trim($results));
+ $display = $this->cleanOutput($results);
+ $this->getConnection()->display(trim($display));
}
});
// Process and log the output and commands
$output = $this->processOutput($output, $array, true);
- $this->logs->log($commands);
- $this->logs->log($output);
-
- // Append output
- if (!$silent) {
- $this->history[] = $output;
- }
+ $this->toOutput($output);
return $output;
}
@@ -91,7 +123,7 @@ public function run($commands, $silent = false, $array = false)
* Run a command get the last line output to
* prevent noise
*
- * @param string|array $commands
+ * @param string $commands
*
* @return string
*/
@@ -107,17 +139,17 @@ public function runLast($commands)
* Run a raw command, without any processing, and
* get its output as a string or array
*
- * @param string|array $commands
- * @param boolean $array Whether the output should be returned as an array
- * @param boolean $trim Whether the output should be trimmed
+ * @param string $commands
+ * @param boolean $array Whether the output should be returned as an array
+ * @param boolean $trim Whether the output should be trimmed
*
- * @return string
+ * @return string|string[]
*/
public function runRaw($commands, $array = false, $trim = false)
{
// Run commands
$output = null;
- $this->remote->run($commands, function ($results) use (&$output) {
+ $this->getConnection()->run($commands, function ($results) use (&$output) {
$output .= $results;
});
@@ -130,10 +162,10 @@ public function runRaw($commands, $array = false, $trim = false)
/**
* Run commands silently
*
- * @param string|array $commands
- * @param boolean $array
+ * @param string|array $commands
+ * @param boolean $array
*
- * @return string
+ * @return string|null
*/
public function runSilently($commands, $array = false)
{
@@ -143,10 +175,10 @@ public function runSilently($commands, $array = false)
/**
* Run commands in a folder
*
- * @param string $folder
- * @param string|array $tasks
+ * @param string|null $folder
+ * @param string|array $tasks
*
- * @return string
+ * @return string|null
*/
public function runInFolder($folder = null, $tasks = array())
{
@@ -156,34 +188,48 @@ public function runInFolder($folder = null, $tasks = array())
}
// Prepend folder
- array_unshift($tasks, 'cd '.$this->rocketeer->getFolder($folder));
+ array_unshift($tasks, 'cd '.$this->paths->getFolder($folder));
return $this->run($tasks);
}
+ /**
+ * Check the status of the last command
+ *
+ * @return bool
+ */
+ public function status()
+ {
+ return $this->getOption('pretend') ? true : $this->getConnection()->status() == 0;
+ }
+
/**
* Check the status of the last run command, return an error if any
*
- * @param string $error The message to display on error
- * @param string $output The command's output
- * @param string $success The message to display on success
+ * @param string $error The message to display on error
+ * @param string|null $output The command's output
+ * @param string|null $success The message to display on success
*
- * @return boolean|string
+ * @return boolean
*/
public function checkStatus($error, $output = null, $success = null)
{
// If all went well
- if ($this->remote->status() == 0) {
+ if ($this->status()) {
if ($success) {
- $this->command->comment($success);
+ $this->explainer->success($success);
}
return $output || true;
}
- // Else
- $this->command->error($error);
- print $output.PHP_EOL;
+ // Else display the error
+ $error = sprintf('An error occured: "%s"', $error);
+ if ($output) {
+ $error .= ', while running:'.PHP_EOL.$output;
+ }
+
+ $this->explainer->error($error);
return false;
}
@@ -206,32 +252,6 @@ public function getTimestamp()
return $timestamp;
}
- /**
- * Get an option from the Command
- *
- * @param string $option
- *
- * @return string
- */
- protected function getOption($option)
- {
- return $this->hasCommand() ? $this->command->option($option) : null;
- }
-
- /**
- * Add an array/command to the history
- *
- * @param string|array $commands
- */
- protected function addCommandsToHistory($commands)
- {
- $this->command->line(implode(PHP_EOL, $commands));
- $commands = (sizeof($commands) == 1) ? $commands[0] : $commands;
- $this->history[] = $commands;
-
- return $commands;
- }
-
////////////////////////////////////////////////////////////////////
///////////////////////////// PROCESSORS ///////////////////////////
////////////////////////////////////////////////////////////////////
@@ -239,14 +259,16 @@ protected function addCommandsToHistory($commands)
/**
* Process an array of commands
*
- * @param string|array $commands
+ * @param string|array $commands
*
* @return array
*/
- protected function processCommands($commands)
+ public function processCommands($commands)
{
- $stage = $this->rocketeer->getStage();
- $separator = $this->server->getSeparator();
+ $stage = $this->connections->getStage();
+ $separator = $this->localStorage->getSeparator();
+ $shell = $this->rocketeer->getOption('remote.shell');
+ $shelled = $this->rocketeer->getOption('remote.shelled');
// Cast commands to array
if (!is_array($commands)) {
@@ -262,29 +284,63 @@ protected function processCommands($commands)
}
// Add stage flag to Artisan commands
- if (Str::contains($command, 'artisan') and $stage) {
- $command .= ' --env='.$stage;
+ if (Str::contains($command, 'artisan') && $stage) {
+ $command .= ' --env="'.$stage.'"';
}
+ // Create shell if asked
+ if ($shell && Str::contains($command, $shelled)) {
+ $command = $this->shellCommand($command);
+ }
}
return $commands;
}
+ /**
+ * Clean the output of various intruding bits
+ *
+ * @param string $output
+ *
+ * @return string
+ */
+ protected function cleanOutput($output)
+ {
+ return strtr($output, array(
+ 'stdin: is not a tty' => null,
+ ));
+ }
+
+ /**
+ * Pass a command through shell execution
+ *
+ * @param string $command
+ *
+ * @return string
+ */
+ protected function shellCommand($command)
+ {
+ return "bash --login -c '".$command."'";
+ }
+
/**
* Process the output of a command
*
- * @param string|array $output
- * @param boolean $array Whether to return an array or a string
- * @param boolean $trim Whether to trim the output or not
+ * @param string $output
+ * @param boolean $array Whether to return an array or a string
+ * @param boolean $trim Whether to trim the output or not
*
* @return string|array
*/
protected function processOutput($output, $array = false, $trim = true)
{
+ // Remove polluting strings
+ $output = $this->cleanOutput($output);
+
// Explode output if necessary
if ($array) {
- $output = explode($this->server->getLineEndings(), $output);
+ $delimiter = $this->localStorage->getLineEndings() ?: PHP_EOL;
+ $output = explode($delimiter, $output);
}
// Trim output
diff --git a/src/Rocketeer/Traits/BashModules/Filesystem.php b/src/Rocketeer/Traits/BashModules/Filesystem.php
index e5f176222..a0b67c912 100644
--- a/src/Rocketeer/Traits/BashModules/Filesystem.php
+++ b/src/Rocketeer/Traits/BashModules/Filesystem.php
@@ -14,7 +14,7 @@
*
* @author Maxime Fabre
*/
-class Filesystem extends Core
+trait Filesystem
{
////////////////////////////////////////////////////////////////////
/////////////////////////////// COMMON /////////////////////////////
@@ -23,8 +23,8 @@ class Filesystem extends Core
/**
* Symlinks two folders
*
- * @param string $folder The folder in shared/
- * @param string $symlink The folder that will symlink to it
+ * @param string $folder The folder in shared/
+ * @param string $symlink The folder that will symlink to it
*
* @return string
*/
@@ -47,13 +47,17 @@ public function symlink($folder, $symlink)
/**
* Move a file
*
- * @param string $origin
- * @param string $destination
+ * @param string $origin
+ * @param string $destination
*
- * @return string
+ * @return string|null
*/
public function move($origin, $destination)
{
+ if (!$this->fileExists($origin)) {
+ return;
+ }
+
return $this->fromTo('mv', $origin, $destination);
}
@@ -67,13 +71,13 @@ public function move($origin, $destination)
*/
public function copy($origin, $destination)
{
- return $this->fromTo('cp', $origin, $destination);
+ return $this->fromTo('cp -r', $origin, $destination);
}
/**
* Get the contents of a directory
*
- * @param string $directory
+ * @param string $directory
*
* @return array
*/
@@ -85,13 +89,13 @@ public function listContents($directory)
/**
* Check if a file exists
*
- * @param string $file Path to the file
+ * @param string $file Path to the file
*
* @return boolean
*/
public function fileExists($file)
{
- $exists = $this->runRaw('[ -e ' .$file. ' ] && echo "true"');
+ $exists = $this->runRaw('[ -e '.$file.' ] && echo "true"');
return trim($exists) == 'true';
}
@@ -107,7 +111,7 @@ public function setPermissions($folder)
{
// Get path to folder
$folder = $this->releasesManager->getCurrentReleasePath($folder);
- $this->command->comment('Setting permissions for '.$folder);
+ $this->explainer->line('Setting permissions for '.$folder);
// Get permissions options
$callback = $this->rocketeer->getOption('remote.permissions.callback');
@@ -134,7 +138,7 @@ public function setPermissions($folder)
*/
public function getFile($file)
{
- return $this->remote->getString($file);
+ return $this->getConnection()->getString($file);
}
/**
@@ -147,7 +151,25 @@ public function getFile($file)
*/
public function putFile($file, $contents)
{
- $this->remote->putString($file, $contents);
+ $this->getConnection()->putString($file, $contents);
+ }
+
+ /**
+ * Upload a local file to remote
+ *
+ * @param string $file
+ * @param string|null $destination
+ */
+ public function upload($file, $destination = null)
+ {
+ if (!file_exists($file)) {
+ return;
+ }
+
+ // Get contents and destination
+ $destination = $destination ?: basename($file);
+
+ $this->getConnection()->put($file, $destination);
}
////////////////////////////////////////////////////////////////////
@@ -157,8 +179,8 @@ public function putFile($file, $contents)
/**
* Create a folder in the application's folder
*
- * @param string $folder The folder to create
- * @param boolean $recursive
+ * @param string|null $folder The folder to create
+ * @param boolean $recursive
*
* @return string The task
*/
@@ -166,19 +188,23 @@ public function createFolder($folder = null, $recursive = false)
{
$recursive = $recursive ? '-p ' : null;
- return $this->run('mkdir '.$recursive.$this->rocketeer->getFolder($folder));
+ return $this->run('mkdir '.$recursive.$this->paths->getFolder($folder));
}
/**
* Remove a folder in the application's folder
*
- * @param string $folder The folder to remove
+ * @param array|string|null $folders The folder to remove
*
* @return string The task
*/
- public function removeFolder($folder = null)
+ public function removeFolder($folders = null)
{
- return $this->run('rm -rf '.$this->rocketeer->getFolder($folder));
+ $folders = (array) $folders;
+ $folders = array_map([$this->paths, 'getFolder'], $folders);
+ $folders = implode(' ', $folders);
+
+ return $this->run('rm -rf '.$folders);
}
////////////////////////////////////////////////////////////////////
diff --git a/src/Rocketeer/Traits/BashModules/Flow.php b/src/Rocketeer/Traits/BashModules/Flow.php
index d1cee0c67..1b3a0f60c 100644
--- a/src/Rocketeer/Traits/BashModules/Flow.php
+++ b/src/Rocketeer/Traits/BashModules/Flow.php
@@ -14,8 +14,15 @@
*
* @author Maxime Fabre
*/
-class Flow extends Scm
+trait Flow
{
+ /**
+ * Whether the task needs to be run on each stage or globally
+ *
+ * @var boolean
+ */
+ public $usesStages = true;
+
/**
* Check if the remote server is setup
*
@@ -23,25 +30,25 @@ class Flow extends Scm
*/
public function isSetup()
{
- return $this->fileExists($this->rocketeer->getFolder('current'));
+ return $this->fileExists($this->paths->getFolder('current'));
}
/**
- * Check if the Task uses stages
+ * Check if the task uses stages
*
* @return boolean
*/
public function usesStages()
{
- $stages = $this->rocketeer->getStages();
+ $stages = $this->connections->getStages();
- return $this->usesStages and !empty($stages);
+ return $this->usesStages && !empty($stages);
}
/**
* Run actions in the current release's folder
*
- * @param string|array $tasks One or more tasks
+ * @param string|array $tasks One or more tasks
*
* @return string
*/
@@ -57,20 +64,22 @@ public function runForCurrentRelease($tasks)
/**
* Sync the requested folders and files
*
- * @return void
+ * @return boolean
*/
protected function syncSharedFolders()
{
$shared = (array) $this->rocketeer->getOption('remote.shared');
- foreach ($shared as $file) {
+ foreach ($shared as &$file) {
$this->share($file);
}
+
+ return true;
}
/**
* Update the current symlink
*
- * @param integer $release A release to mark as current
+ * @param integer|null $release A release to mark as current
*
* @return string
*/
@@ -78,12 +87,12 @@ public function updateSymlink($release = null)
{
// If the release is specified, update to make it the current one
if ($release) {
- $this->releasesManager->updateCurrentRelease($release);
+ $this->releasesManager->setNextRelease($release);
}
// Get path to current/ folder and latest release
$currentReleasePath = $this->releasesManager->getCurrentReleasePath();
- $currentFolder = $this->rocketeer->getFolder('current');
+ $currentFolder = $this->paths->getFolder('current');
return $this->symlink($currentReleasePath, $currentFolder);
}
@@ -91,7 +100,7 @@ public function updateSymlink($release = null)
/**
* Share a file or folder between releases
*
- * @param string $file Path to the file in a release folder
+ * @param string $file Path to the file in a release folder
*
* @return string
*/
@@ -106,7 +115,7 @@ public function share($file)
$this->move($currentFile, $sharedFile);
}
- $this->command->comment('Sharing file '.$currentFile);
+ $this->explainer->line('Sharing file '.$currentFile);
return $this->symlink($sharedFile, $currentFile);
}
diff --git a/src/Rocketeer/Traits/BashModules/Scm.php b/src/Rocketeer/Traits/BashModules/Scm.php
deleted file mode 100644
index 04ce66994..000000000
--- a/src/Rocketeer/Traits/BashModules/Scm.php
+++ /dev/null
@@ -1,102 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace Rocketeer\Traits\BashModules;
-
-/**
- * Repository handling
- *
- * @author Maxime Fabre
- */
-class Scm extends Binaries
-{
- /**
- * Copies the repository into a release folder and update it
- *
- * @param string $destination
- *
- * @return string
- */
- public function copyRepository($destination = null)
- {
- // Get the previous release, if none clone from scratch
- $previous = $this->releasesManager->getPreviousRelease();
- $previous = $this->releasesManager->getPathToRelease($previous);
- if (!$previous) {
- return $this->cloneRepository($destination);
- }
-
- // Recompute destination
- if (!$destination) {
- $destination = $this->releasesManager->getCurrentReleasePath();
- }
-
- // Copy old release into new one
- $this->command->info('Copying previous release "' .$previous. '" in "' .$destination. '"');
- $this->copy($previous, $destination);
-
- // Update repository
- return $this->updateRepository();
- }
-
- /**
- * Clone the repo into a release folder
- *
- * @param string $destination Where to clone to
- *
- * @return string
- */
- public function cloneRepository($destination = null)
- {
- if (!$destination) {
- $destination = $this->releasesManager->getCurrentReleasePath();
- }
-
- // Executing checkout
- $this->command->info('Cloning repository in "' .$destination. '"');
- $output = $this->scm->execute('checkout', $destination);
- $this->history[] = $output;
-
- // Cancel if failed and forget credentials
- $success = $this->checkStatus('Unable to clone the repository', $output) !== false;
- if (!$success) {
- $this->server->forgetValue('credentials');
-
- return false;
- }
-
- // Deploy submodules
- if ($this->rocketeer->getOption('scm.submodules')) {
- $this->command->info('Initializing submodules if any');
- $this->runForCurrentRelease($this->scm->submodules());
- }
-
- return $success;
- }
-
- /**
- * Update the current release
- *
- * @param boolean $reset Whether the repository should be reset first
- *
- * @return string
- */
- public function updateRepository($reset = true)
- {
- $this->command->info('Pulling changes');
- $tasks = array($this->scm->update());
-
- // Reset if requested
- if ($reset) {
- array_unshift($tasks, $this->scm->reset());
- }
-
- return $this->runForCurrentRelease($tasks);
- }
-}
diff --git a/src/Rocketeer/Traits/HasHistory.php b/src/Rocketeer/Traits/HasHistory.php
new file mode 100644
index 000000000..cb452da65
--- /dev/null
+++ b/src/Rocketeer/Traits/HasHistory.php
@@ -0,0 +1,110 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Traits;
+
+use Illuminate\Support\Arr;
+
+/**
+ * A class that maintains an history of results/commands
+ *
+ * @property \Rocketeer\Services\History\History history
+ * @author Maxime Fabre
+ */
+trait HasHistory
+{
+ /**
+ * Get the class's history
+ *
+ * @param string|null $type
+ *
+ * @return array
+ */
+ public function getHistory($type = null)
+ {
+ $handle = $this->getHistoryHandle();
+ $history = $this->history[$handle];
+ $history = Arr::get($history, $type);
+
+ return $history;
+ }
+
+ /**
+ * Append an entry to the history
+ *
+ * @param array|string|boolean $command
+ */
+ public function toHistory($command)
+ {
+ $this->appendTo('history', $command);
+ }
+
+ /**
+ * Append an entry to the output
+ *
+ * @param array|string|boolean $output
+ */
+ public function toOutput($output)
+ {
+ $this->appendTo('output', $output);
+ }
+
+ /**
+ * Get the class's handle in the history
+ *
+ * @return string
+ */
+ protected function getHistoryHandle()
+ {
+ $handle = get_called_class();
+
+ // Create entry if it doesn't exist yet
+ if (!isset($this->history[$handle])) {
+ $this->history[$handle] = array(
+ 'history' => [],
+ 'output' => [],
+ );
+ }
+
+ return $handle;
+ }
+
+ /**
+ * Append something to the history
+ *
+ * @param string $type
+ * @param string|array|boolean $command
+ */
+ protected function appendTo($type, $command)
+ {
+ // Flatten one-liners
+ $command = (array) $command;
+ $command = array_values($command);
+ $flattened = count($command) == 1 ? $command[0] : $command;
+
+ // Save to logs
+ if ($type == 'history') {
+ $command = array_map(function ($command) {
+ return '$ '.$command;
+ }, $command);
+ }
+
+ $this->logs->log($command);
+
+ // Get the various handles
+ $handle = $this->getHistoryHandle();
+ $history = $this->getHistory();
+ $timestamp = (string) microtime(true);
+
+ // Set new history on correct handle
+ $history[$type][$timestamp] = $flattened;
+
+ $this->history[$handle] = $history;
+ }
+}
diff --git a/src/Rocketeer/Traits/HasLocator.php b/src/Rocketeer/Traits/HasLocator.php
new file mode 100644
index 000000000..5ada3039a
--- /dev/null
+++ b/src/Rocketeer/Traits/HasLocator.php
@@ -0,0 +1,154 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Traits;
+
+use Illuminate\Container\Container;
+use Illuminate\Support\Arr;
+
+/**
+ * A trait for Service Locator-based classes wich adds
+ * a few shortcuts to Rocketeer classes
+ *
+ * @property \Illuminate\Config\Repository config
+ * @property \Illuminate\Events\Dispatcher events
+ * @property \Illuminate\Filesystem\Filesystem files
+ * @property \Illuminate\Log\Writer log
+ * @property \Rocketeer\Abstracts\AbstractCommand command
+ * @property \Rocketeer\Bash bash
+ * @property \Rocketeer\Console\Console console
+ * @property \Rocketeer\Interfaces\ScmInterface scm
+ * @property \Rocketeer\Rocketeer rocketeer
+ * @property \Rocketeer\Services\Connections\ConnectionsHandler connections
+ * @property \Rocketeer\Services\Connections\RemoteHandler remote
+ * @property \Rocketeer\Services\CredentialsGatherer credentials
+ * @property \Rocketeer\Services\Display\QueueExplainer explainer
+ * @property \Rocketeer\Services\Display\QueueTimer timer
+ * @property \Rocketeer\Services\History\History history
+ * @property \Rocketeer\Services\Pathfinder paths
+ * @property \Rocketeer\Services\ReleasesManager releasesManager
+ * @property \Rocketeer\Services\Storages\LocalStorage localStorage
+ * @property \Rocketeer\Services\Tasks\TasksBuilder builder
+ * @property \Rocketeer\Services\Tasks\TasksQueue queue
+ * @property \Rocketeer\Services\TasksHandler tasks
+ * @author Maxime Fabre
+ */
+trait HasLocator
+{
+ /**
+ * The IoC Container
+ *
+ * @var Container
+ */
+ protected $app;
+
+ /**
+ * Build a new AbstractTask
+ *
+ * @param Container $app
+ */
+ public function __construct(Container $app)
+ {
+ $this->app = $app;
+ }
+
+ /**
+ * Get an instance from the Container
+ *
+ * @param string $key
+ *
+ * @return object
+ */
+ public function __get($key)
+ {
+ $shortcuts = array(
+ 'bash' => 'rocketeer.bash',
+ 'builder' => 'rocketeer.builder',
+ 'command' => 'rocketeer.command',
+ 'connections' => 'rocketeer.connections',
+ 'console' => 'rocketeer.console',
+ 'credentials' => 'rocketeer.credentials',
+ 'explainer' => 'rocketeer.explainer',
+ 'history' => 'rocketeer.history',
+ 'localStorage' => 'rocketeer.storage.local',
+ 'logs' => 'rocketeer.logs',
+ 'paths' => 'rocketeer.paths',
+ 'queue' => 'rocketeer.queue',
+ 'releasesManager' => 'rocketeer.releases',
+ 'remote' => 'rocketeer.remote',
+ 'rocketeer' => 'rocketeer.rocketeer',
+ 'scm' => 'rocketeer.scm',
+ 'tasks' => 'rocketeer.tasks',
+ 'timer' => 'rocketeer.timer',
+ );
+
+ // Replace shortcuts
+ if (isset($shortcuts[$key])) {
+ $key = $shortcuts[$key];
+ }
+
+ return $this->app[$key];
+ }
+
+ /**
+ * Set an instance on the Container
+ *
+ * @param string $key
+ * @param object $value
+ */
+ public function __set($key, $value)
+ {
+ $this->app[$key] = $value;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// COMMAND ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Check if the current instance has a Command bound
+ *
+ * @return boolean
+ */
+ protected function hasCommand()
+ {
+ return $this->app->bound('rocketeer.command');
+ }
+
+ /**
+ * Get an option from the Command
+ *
+ * @param string $option
+ * @param bool $loose
+ *
+ * @return string
+ */
+ public function getOption($option, $loose = false)
+ {
+ if (!$this->hasCommand()) {
+ return null;
+ }
+
+ return $loose ? Arr::get($this->command->option(), $option) : $this->command->option($option);
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// CONTEXT ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Check if the class is executed inside a Laravel application
+ *
+ * @return boolean
+ */
+ public function isInsideLaravel()
+ {
+ return $this->app->bound('path');
+ }
+}
diff --git a/src/Rocketeer/Traits/Scm.php b/src/Rocketeer/Traits/Scm.php
deleted file mode 100644
index 649538ab2..000000000
--- a/src/Rocketeer/Traits/Scm.php
+++ /dev/null
@@ -1,71 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace Rocketeer\Traits;
-
-/**
- * An abstract class with helpers for SCM implementations
- *
- * @author Maxime Fabre
- */
-abstract class Scm
-{
- /**
- * The IoC Container
- *
- * @var Container
- */
- protected $app;
-
- /**
- * Build a new Git instance
- *
- * @param Container $app
- */
- public function __construct($app)
- {
- $this->app = $app;
- }
-
- ////////////////////////////////////////////////////////////////////
- //////////////////////////////// HELPERS ///////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Returns a command with the SCM's binary
- *
- * @param string $commands...
- *
- * @return string
- */
- public function getCommand()
- {
- $arguments = func_get_args();
- $arguments[0] = $this->binary. ' ' .$arguments[0];
-
- return call_user_func_array('sprintf', $arguments);
- }
-
- /**
- * Execute one of the commands
- *
- * @param string $command
- * @param string $arguments,...
- *
- * @return mixed
- */
- public function execute()
- {
- $arguments = func_get_args();
- $command = array_shift($arguments);
- $command = call_user_func_array(array($this, $command), $arguments);
-
- return $this->app['rocketeer.bash']->run($command);
- }
-}
diff --git a/src/Rocketeer/Traits/StepsRunner.php b/src/Rocketeer/Traits/StepsRunner.php
new file mode 100644
index 000000000..75c829ff5
--- /dev/null
+++ b/src/Rocketeer/Traits/StepsRunner.php
@@ -0,0 +1,59 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Rocketeer\Traits;
+
+use Rocketeer\Services\StepsBuilder;
+
+/**
+ * Gives a class the ability to prepare steps to run and
+ * loop over them
+ *
+ * @author Maxime Fabre
+ */
+trait StepsRunner
+{
+ /**
+ * @type StepsBuilder
+ */
+ protected $steps;
+
+ /**
+ * @return StepsBuilder
+ */
+ public function steps()
+ {
+ if (!$this->steps) {
+ $this->steps = new StepsBuilder;
+ }
+
+ return $this->steps;
+ }
+
+ /**
+ * Execute an array of calls until one halts
+ *
+ * @return boolean
+ */
+ public function runSteps()
+ {
+ foreach ($this->steps()->pullSteps() as $step) {
+ list($method, $arguments) = $step;
+ $arguments = (array) $arguments;
+
+ $results = call_user_func_array([$this, $method], $arguments);
+ $results = $results ?: $this->status();
+ if (!$results) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/src/config/config.php b/src/config/config.php
index a9642da73..0765eebdd 100644
--- a/src/config/config.php
+++ b/src/config/config.php
@@ -1,16 +1,27 @@
- '{application_name}',
+ // Plugins
+ ////////////////////////////////////////////////////////////////////
+
+ // The plugins to load
+ 'plugins' => array(
+ // 'Rocketeer\Plugins\Slack\RocketeerSlack',
+ ),
+
// Logging
////////////////////////////////////////////////////////////////////
// The schema to use to name log files
- 'logs' => function ($rocketeer) {
- return sprintf('%s-%s-%s.log', $rocketeer->getConnection(), $rocketeer->getStage(), date('Ymd'));
+ 'logs' => function (ConnectionsHandler $connections) {
+ return sprintf('%s-%s-%s.log', $connections->getConnection(), $connections->getStage(), date('Ymd'));
},
// Remote access
@@ -19,13 +30,13 @@
////////////////////////////////////////////////////////////////////
// The default remote connection(s) to execute tasks on
- 'default' => array('production'),
+ 'default' => array('{connection}'),
// The various connections you defined
// You can leave all of this empty or remove it entirely if you don't want
// to track files with credentials : Rocketeer will prompt you for your credentials
// and store them locally
- 'connections' => array(
+ 'connections' => array(
'production' => array(
'host' => '{host}',
'username' => '{username}',
@@ -54,12 +65,10 @@
'on' => array(
// Stages configurations
- 'stages' => array(
- ),
+ 'stages' => array(),
// Connections configuration
- 'connections' => array(
- ),
+ 'connections' => array(),
),
diff --git a/src/config/hooks.php b/src/config/hooks.php
index a0a07fb2a..05b52ab30 100644
--- a/src/config/hooks.php
+++ b/src/config/hooks.php
@@ -5,7 +5,7 @@
// Here you can define in the `before` and `after` array, Tasks to execute
// before or after the core Rocketeer Tasks. You can either put a simple command,
// a closure which receives a $task object, or the name of a class extending
- // the Rocketeer\Traits\Task class
+ // the Rocketeer\Abstracts\AbstractTask class
//
// In the `custom` array you can list custom Tasks classes to be added
// to Rocketeer. Those will then be available in the command line
@@ -20,12 +20,12 @@
),
// Tasks to execute after the core Rocketeer Tasks
- 'after' => array(
+ 'after' => array(
'setup' => array(),
'deploy' => array(),
'cleanup' => array(),
),
-
+
// Custom Tasks to register with Rocketeer
'custom' => array(),
diff --git a/src/config/paths.php b/src/config/paths.php
index f366b416a..a5cbcf753 100644
--- a/src/config/paths.php
+++ b/src/config/paths.php
@@ -17,6 +17,6 @@
'composer' => '',
// Path to the Artisan CLI
- 'artisan' => '',
+ 'artisan' => 'artisan',
);
diff --git a/src/config/remote.php b/src/config/remote.php
index 0e4c61756..340558715 100644
--- a/src/config/remote.php
+++ b/src/config/remote.php
@@ -1,59 +1,58 @@
- 'clone',
+return array(
// Remote server
//////////////////////////////////////////////////////////////////////
// Variables about the servers. Those can be guessed but in
// case of problem it's best to input those manually
- 'variables' => array(
+ 'variables' => array(
'directory_separator' => '/',
'line_endings' => "\n",
),
- // The process that will be executed by Composer
- 'composer' => function ($task) {
- return array(
- // $task->composer('self-update'),
- $task->composer('install --no-interaction --no-dev --prefer-dist'),
- );
- },
-
// The number of releases to keep at all times
- 'keep_releases' => 4,
+ 'keep_releases' => 4,
// Folders
////////////////////////////////////////////////////////////////////
// The root directory where your applications will be deployed
- 'root_directory' => '/home/www/',
+ // This path *needs* to start at the root, ie. start with a /
+ 'root_directory' => '/home/www/',
// The folder the application will be cloned in
// Leave empty to use `application_name` as your folder name
- 'app_directory' => '',
+ 'app_directory' => '',
// A list of folders/file to be shared between releases
// Use this to list folders that need to keep their state, like
// user uploaded data, file-based databases, etc.
- 'shared' => array(
+ 'shared' => array(
'{path.storage}/logs',
'{path.storage}/sessions',
),
- // Permissions
+ // Execution
+ //////////////////////////////////////////////////////////////////////
+
+ // If enabled will force a shell to be created
+ // which is required for some tools like RVM or NVM
+ 'shell' => false,
+
+ // An array of commands to run under shell
+ 'shelled' => ['which', 'ruby', 'npm', 'bower', 'bundle', 'grunt'],
+
+ // Permissions$
////////////////////////////////////////////////////////////////////
- 'permissions' => array(
+ 'permissions' => array(
// The folders and files to set as web writable
// You can pass paths in brackets, so {path.public} will return
// the correct path to the public folder
- 'files' => array(
+ 'files' => array(
'app/database/production.sqlite',
'{path.storage}',
'{path.public}',
diff --git a/src/config/scm.php b/src/config/scm.php
index 1a87d66b4..0cd2f404d 100644
--- a/src/config/scm.php
+++ b/src/config/scm.php
@@ -4,7 +4,7 @@
//////////////////////////////////////////////////////////////////////
// The SCM used (supported: "git", "svn")
- 'scm' => 'git',
+ 'scm' => 'git',
// The SSH/HTTPS address to your repository
// Example: https://github.com/vendor/website.git
@@ -24,7 +24,7 @@
// or not – this means a clone with just the latest state of your
// application (no history)
// If you're having problems cloning, try setting this to false
- 'shallow' => true,
+ 'shallow' => true,
// Recursively pull in submodules. Works only with GIT.
'submodules' => true,
diff --git a/src/config/stages.php b/src/config/stages.php
index 20ffb77d4..b581e142c 100644
--- a/src/config/stages.php
+++ b/src/config/stages.php
@@ -8,9 +8,10 @@
// Adding entries to this array will split the remote folder in stages
// Like /var/www/yourapp/staging and /var/www/yourapp/production
- 'stages' => array(),
+ 'stages' => array(),
// The default stage to execute tasks on when --stage is not provided
+ // Falsey means all of them
'default' => '',
);
diff --git a/src/config/strategies.php b/src/config/strategies.php
new file mode 100644
index 000000000..82205d883
--- /dev/null
+++ b/src/config/strategies.php
@@ -0,0 +1,51 @@
+ 'Php',
+
+ // Which strategy to use to create a new release
+ 'deploy' => 'Clone',
+
+ // Which strategy to use to test your application
+ 'test' => 'Phpunit',
+
+ // Which strategy to use to migrate your database
+ 'migrate' => 'Artisan',
+
+ // Which strategy to use to install your application's dependencies
+ 'dependencies' => 'Polyglot',
+
+ // Execution hooks
+ //////////////////////////////////////////////////////////////////////
+
+ 'composer' => array(
+ 'install' => function (Composer $composer, $task) {
+ return $composer->install([], ['--no-interaction' => null, '--no-dev' => null, '--prefer-dist' => null]);
+ },
+ 'update' => function (Composer $composer) {
+ return $composer->update();
+ },
+ ),
+
+ // Here you can configure the Primer tasks
+ // which will run a set of commands on the local
+ // machine, determining whether the deploy can proceed
+ // or not
+ 'primer' => function (Primer $task) {
+ return array(
+ // $task->executeTask('Test'),
+ // $task->binary('grunt')->execute('lint'),
+ );
+ },
+
+);
diff --git a/tests/Abstracts/AbstractBinaryTest.php b/tests/Abstracts/AbstractBinaryTest.php
new file mode 100644
index 000000000..19d08fca6
--- /dev/null
+++ b/tests/Abstracts/AbstractBinaryTest.php
@@ -0,0 +1,23 @@
+mock('rocketeer.bash', 'Bash', function ($mock) {
+ return $mock->shouldReceive('run')->once()->withAnyArgs()->andReturnUsing(function ($arguments) {
+ return $arguments;
+ });
+ });
+
+ $scm = new Git($this->app);
+ $command = $scm->run('checkout', $this->server);
+ $expected = $this->replaceHistoryPlaceholders(['git clone "{repository}" "{server}" --branch="master" --depth="1"']);
+
+ $this->assertEquals($expected[0], $command);
+ }
+}
diff --git a/tests/Abstracts/AbstractStorageTest.php b/tests/Abstracts/AbstractStorageTest.php
new file mode 100644
index 000000000..ed3e58c2f
--- /dev/null
+++ b/tests/Abstracts/AbstractStorageTest.php
@@ -0,0 +1,43 @@
+ 'caca'];
+ $this->localStorage->set($matcher);
+ $contents = $this->localStorage->get();
+ unset($contents['hash']);
+
+ $this->assertEquals($matcher, $contents);
+ }
+
+ public function testCanGetValue()
+ {
+ $this->assertEquals('bar', $this->localStorage->get('foo'));
+ }
+
+ public function testCanSetValue()
+ {
+ $this->localStorage->set('foo', 'baz');
+
+ $this->assertEquals('baz', $this->localStorage->get('foo'));
+ }
+
+ public function testCanDestroy()
+ {
+ $this->localStorage->destroy();
+
+ $this->assertFalse($this->files->exists(__DIR__.'/_meta/deployments.json'));
+ }
+
+ public function testCanFallbackIfFileDoesntExist()
+ {
+ $this->localStorage->destroy();
+
+ $this->assertEquals(null, $this->localStorage->get('foo'));
+ }
+}
diff --git a/tests/Abstracts/AbstractTaskTest.php b/tests/Abstracts/AbstractTaskTest.php
new file mode 100644
index 000000000..2236b65c6
--- /dev/null
+++ b/tests/Abstracts/AbstractTaskTest.php
@@ -0,0 +1,141 @@
+task('Check', array(
+ 'verbose' => true,
+ ));
+
+ ob_start();
+ $task->run('ls');
+ $output = ob_get_clean();
+
+ $this->assertContains('tests', $output);
+ }
+
+ public function testCanPretendToRunTasks()
+ {
+ $task = $this->pretendTask();
+ $commands = $task->run('ls');
+
+ $this->assertEquals('ls', $commands);
+ }
+
+ public function testCanGetDescription()
+ {
+ $task = $this->task('Setup');
+
+ $this->assertNotNull($task->getDescription());
+ }
+
+ public function testCanFireEventsDuringTasks()
+ {
+ $this->expectOutputString('foobar');
+ $this->swapConfig(['rocketeer::hooks' => []]);
+
+ $this->tasks->listenTo('closure.test.foobar', function () {
+ echo 'foobar';
+ });
+
+ $this->queue->execute(function ($task) {
+ $task->fireEvent('test.foobar');
+ }, 'staging');
+ }
+
+ public function testTaskCancelsIfEventHalts()
+ {
+ $this->expectOutputString('abc');
+
+ $this->swapConfig(array(
+ 'rocketeer::hooks' => [],
+ ));
+
+ $this->tasks->registerConfiguredEvents();
+ $this->tasks->listenTo('deploy.before', array(
+ function () {
+ echo 'a';
+
+ return true;
+ },
+ function () {
+ echo 'b';
+
+ return 'lol';
+ },
+ function () {
+ echo 'c';
+
+ return false;
+ },
+ function () {
+ echo 'd';
+ },
+ ));
+
+ $task = $this->pretendTask('Deploy');
+ $task->fire();
+ }
+
+ public function testCanListenToSubtasks()
+ {
+ $this->swapConfig(array(
+ 'rocketeer::hooks' => [],
+ ));
+
+ $this->tasks->listenTo('dependencies.before', ['ls']);
+
+ $this->pretendTask('Deploy')->fire();
+
+ $history = $this->history->getFlattenedOutput();
+ $this->assertHistory(array(
+ 'cd {server}/releases/{release}',
+ 'ls',
+ ), array_get($history, 3));
+ }
+
+ public function testDoesntDuplicateQueuesOnSubtasks()
+ {
+ $this->swapConfig(array(
+ 'rocketeer::default' => ['staging', 'production'],
+ ));
+
+ $this->pretend();
+ $this->queue->run('Deploy');
+
+ $this->assertCount(20, $this->history->getFlattenedHistory());
+ }
+
+ public function testCanHookIntoHaltingEvent()
+ {
+ $this->expectOutputString('halted');
+
+ $this->tasks->before('deploy', 'Rocketeer\Dummies\MyCustomHaltingTask');
+
+ $this->tasks->listenTo('deploy.halt', function () {
+ echo 'halted';
+ });
+
+ $this->pretendTask('Deploy')->fire();
+ }
+
+ public function testCanDisplayReleasesTable()
+ {
+ $headers = ['#', 'Path', 'Deployed at', 'Status'];
+ $releases = array(
+ [0, 20000000000000, '1999-11-30 00:00:00', '✓'],
+ [1, 15000000000000, '1499-11-30 00:00:00', '✘'],
+ [2, 10000000000000, '0999-11-30 00:00:00', '✓'],
+ );
+
+ $this->app['rocketeer.command'] = $this->getCommand()
+ ->shouldReceive('table')->with($headers, $releases)->andReturn(null)->once()
+ ->mock();
+
+ $this->task('CurrentRelease')->execute();
+ }
+}
diff --git a/tests/Abstracts/Strategies/AbstractStrategyTest.php b/tests/Abstracts/Strategies/AbstractStrategyTest.php
new file mode 100644
index 000000000..06cbc7131
--- /dev/null
+++ b/tests/Abstracts/Strategies/AbstractStrategyTest.php
@@ -0,0 +1,16 @@
+app['path.base'] = realpath(__DIR__.'/../../..');
+
+ $this->usesComposer(false);
+ $strategy = $this->builder->buildStrategy('Dependencies', 'Composer');
+ $this->assertTrue($strategy->isExecutable());
+ }
+}
diff --git a/tests/BashTest.php b/tests/BashTest.php
index 9f2bb9082..1a4031ed7 100644
--- a/tests/BashTest.php
+++ b/tests/BashTest.php
@@ -8,7 +8,10 @@ class BashTest extends RocketeerTestCase
public function testBashIsCorrectlyComposed()
{
$contents = $this->task->runRaw('ls', true, true);
+ if (count($contents) !== 11) {
+ var_dump($contents);
+ }
- $this->assertCount(12, $contents);
+ $this->assertCount(11, $contents);
}
}
diff --git a/tests/Binaries/AnonymousBinaryTest.php b/tests/Binaries/AnonymousBinaryTest.php
new file mode 100644
index 000000000..de432180e
--- /dev/null
+++ b/tests/Binaries/AnonymousBinaryTest.php
@@ -0,0 +1,15 @@
+app);
+ $anonymous->setBinary('foobar');
+
+ $this->assertEquals('foobar foo bar --lol', $anonymous->foo('bar', '--lol'));
+ }
+}
diff --git a/tests/Binaries/ArtisanTest.php b/tests/Binaries/ArtisanTest.php
new file mode 100644
index 000000000..b278dc5a8
--- /dev/null
+++ b/tests/Binaries/ArtisanTest.php
@@ -0,0 +1,16 @@
+binaries['php'];
+ $artisan = new Artisan($this->app);
+
+ $commands = $artisan->migrate();
+ $this->assertEquals($php.' artisan migrate', $commands);
+ }
+}
diff --git a/tests/Binaries/ComposerTest.php b/tests/Binaries/ComposerTest.php
new file mode 100644
index 000000000..0766f7dd0
--- /dev/null
+++ b/tests/Binaries/ComposerTest.php
@@ -0,0 +1,16 @@
+app);
+ $composer->setBinary('composer.phar');
+
+ $this->assertEquals($this->binaries['php'].' composer.phar install', $composer->install());
+ }
+}
diff --git a/tests/Binaries/PhpTest.php b/tests/Binaries/PhpTest.php
new file mode 100644
index 000000000..8b5b46007
--- /dev/null
+++ b/tests/Binaries/PhpTest.php
@@ -0,0 +1,16 @@
+app);
+ $hhvm = $php->isHhvm();
+ $defined = defined('HHVM_VERSION');
+
+ $this->assertEquals($defined, $hhvm);
+ }
+}
diff --git a/tests/Console/ConsoleTest.php b/tests/Console/ConsoleTest.php
index 60f6d2d18..d818f5fab 100644
--- a/tests/Console/ConsoleTest.php
+++ b/tests/Console/ConsoleTest.php
@@ -7,7 +7,7 @@ class ConsoleTest extends RocketeerTestCase
{
public function testCanRunStandaloneConsole()
{
- $console = exec('php bin/rocketeer --version');
+ $console = exec('php bin/rocketeer --version --no-ansi');
$this->assertContains('Rocketeer version', $console);
}
diff --git a/tests/Dummies/DummyBeforeAfterNotifier.php b/tests/Dummies/DummyBeforeAfterNotifier.php
new file mode 100644
index 000000000..b607851a0
--- /dev/null
+++ b/tests/Dummies/DummyBeforeAfterNotifier.php
@@ -0,0 +1,33 @@
+halt();
+ }
+}
diff --git a/tests/Dummies/MyCustomTask.php b/tests/Dummies/MyCustomTask.php
index 393cf8d4e..bff08b8f6 100644
--- a/tests/Dummies/MyCustomTask.php
+++ b/tests/Dummies/MyCustomTask.php
@@ -1,9 +1,9 @@
igniter = new Igniter($this->app);
- unset($this->app['path.base']);
- }
-
- ////////////////////////////////////////////////////////////////////
- //////////////////////////////// TESTS /////////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- public function testDoesntRebindBasePath()
- {
- $base = 'src';
- $this->app->instance('path.base', $base);
- $this->igniter->bindPaths();
-
- $this->assertEquals($base, $this->app['path.base']);
- }
-
- public function testCanBindBasePath()
- {
- $this->igniter->bindPaths();
-
- $this->assertEquals(realpath(__DIR__.'/..'), $this->app['path.base']);
- }
-
- public function testCanBindConfigurationPaths()
- {
- $this->igniter->bindPaths();
-
- $root = realpath(__DIR__.'/..');
- $this->assertEquals($root.'/.rocketeer', $this->app['path.rocketeer.config']);
- }
-
- public function testCanBindTasksAndEventsPaths()
- {
- $this->igniter->bindPaths();
- $this->igniter->exportConfiguration();
-
- // Create some fake files
- $root = realpath(__DIR__.'/../.rocketeer');
- $this->app['files']->put($root.'/events.php', '');
- $this->app['files']->makeDirectory($root.'/tasks');
-
- $this->igniter->bindPaths();
-
- $this->assertEquals($root.'/tasks', $this->app['path.rocketeer.tasks']);
- $this->assertEquals($root.'/events.php', $this->app['path.rocketeer.events']);
- }
-
- public function testCanExportConfiguration()
- {
- $this->igniter->bindPaths();
- $this->igniter->exportConfiguration();
-
- $this->assertFileExists(__DIR__.'/../.rocketeer');
- }
-
- public function testCanReplaceStubsInConfigurationFile()
- {
- $this->igniter->bindPaths();
- $path = $this->igniter->exportConfiguration();
- $this->igniter->updateConfiguration($path, array('scm_username' => 'foobar'));
-
- $this->assertFileExists(__DIR__.'/../.rocketeer');
- $this->assertContains('foobar', file_get_contents(__DIR__.'/../.rocketeer/scm.php'));
- }
-
- public function testCanSetCurrentApplication()
- {
- $this->mock('rocketeer.server', 'Server', function ($mock) {
- return $mock->shouldReceive('setRepository')->once()->with('foobar');
- });
-
- $this->igniter->bindPaths();
- $path = $this->igniter->exportConfiguration();
- $this->igniter->updateConfiguration($path, array('application_name' => 'foobar', 'scm_username' => 'foobar'));
-
- $this->assertFileExists(__DIR__.'/../.rocketeer');
- $this->assertContains('foobar', file_get_contents(__DIR__.'/../.rocketeer/config.php'));
- }
-}
diff --git a/tests/MetaTest.php b/tests/MetaTest.php
index 313d1f5aa..95c3336ad 100644
--- a/tests/MetaTest.php
+++ b/tests/MetaTest.php
@@ -12,7 +12,7 @@ public function testCanOverwriteTasksViaContainer()
return new MyCustomTask($app);
});
- $queue = $this->app['rocketeer.tasks']->on('production', array('cleanup'), $this->getCommand());
- $this->assertEquals(array('foobar'), $queue);
+ $this->queue->on('production', ['cleanup'], $this->getCommand());
+ $this->assertEquals(['foobar'], $this->history->getFlattenedOutput());
}
}
diff --git a/tests/Plugins/AbstractNotifierTest.php b/tests/Plugins/AbstractNotifierTest.php
new file mode 100644
index 000000000..43367f32d
--- /dev/null
+++ b/tests/Plugins/AbstractNotifierTest.php
@@ -0,0 +1,77 @@
+swapConfig(array(
+ 'rocketeer::stages.stages' => array('staging', 'production'),
+ 'rocketeer::hooks' => array(),
+ 'rocketeer::connections' => array(
+ 'production' => array(
+ 'host' => 'foo.bar.com',
+ ),
+ ),
+ ));
+ $this->tasks->registerConfiguredEvents();
+
+ $this->notifier = new DummyNotifier($this->app);
+ $this->tasks->plugin($this->notifier);
+ }
+
+ public function testCanAskForNameIfNoneProvided()
+ {
+ $this->expectOutputString('foobar finished deploying branch "master" on "staging@production" (foo.bar.com)');
+
+ $this->mockCommand([], ['ask' => 'foobar']);
+ $this->mock('rocketeer.storage.local', 'LocalStorage', function ($mock) {
+ return $mock
+ ->shouldIgnoreMissing()
+ ->shouldReceive('get')->with('connections')
+ ->shouldReceive('get')->with('notifier.name')->andReturn(null)
+ ->shouldReceive('set')->once()->with('notifier.name', 'foobar');
+ });
+ $this->mock('rocketeer.connections', 'ConnectionsHandler', function ($mock) {
+ return $mock
+ ->shouldReceive('getRepositoryBranch')->andReturn('master')
+ ->shouldReceive('getStage')->andReturn('staging')
+ ->shouldReceive('getConnection')->andReturn('production')
+ ->shouldReceive('getServer')->andReturn('0')
+ ->shouldReceive('getServerCredentials')->andReturn(['host' => 'foo.bar.com']);
+ });
+
+ $this->task('deploy')->fireEvent('before');
+ }
+
+ public function testCanAppendStageToDetails()
+ {
+ $this->expectOutputString('Jean Eude finished deploying branch "master" on "staging@production" (foo.bar.com)');
+ $this->localStorage->set('notifier.name', 'Jean Eude');
+ $this->tasks->registerConfiguredEvents();
+ $this->connections->setStage('staging');
+
+ $this->task('Deploy')->fireEvent('before');
+ }
+
+ public function testCanSendDeploymentsNotifications()
+ {
+ $this->expectOutputString('Jean Eude finished deploying branch "master" on "production" (foo.bar.com)');
+ $this->localStorage->set('notifier.name', 'Jean Eude');
+
+ $this->task('Deploy')->fireEvent('after');
+ }
+
+ public function testDoesntSendNotificationsInPretendMode()
+ {
+ $this->expectOutputString('');
+ $this->localStorage->set('notifier.name', 'Jean Eude');
+
+ $this->pretendTask('Deploy')->fireEvent('after');
+ }
+}
diff --git a/tests/Plugins/NotifierTest.php b/tests/Plugins/NotifierTest.php
deleted file mode 100644
index 2c898a6b6..000000000
--- a/tests/Plugins/NotifierTest.php
+++ /dev/null
@@ -1,54 +0,0 @@
-swapConfig(array(
- 'rocketeer::stages.stages' => array('staging', 'production'),
- 'rocketeer::hooks' => array(),
- 'rocketeer::connections' => array(
- 'production' => array(
- 'host' => 'foo.bar.com'
- ),
- ),
- ));
- $this->app['rocketeer.tasks']->registerConfiguredEvents();
-
- $this->notifier = new DummyNotifier($this->app);
- $this->app['rocketeer.tasks']->plugin($this->notifier);
- }
-
- public function testCanAppendStageToDetails()
- {
- $this->expectOutputString('Jean Eude finished deploying branch "master" on "staging@production" (foo.bar.com)');
- $this->app['rocketeer.server']->setValue('notifier.name', 'Jean Eude');
- $this->app['rocketeer.rocketeer']->setStage('staging');
- $this->notifier = new DummyNotifier($this->app);
- $this->app['rocketeer.tasks']->plugin($this->notifier);
-
- $this->task('Deploy')->fireEvent('after');
- }
-
- public function testCanSendDeploymentsNotifications()
- {
- $this->expectOutputString('Jean Eude finished deploying branch "master" on "production" (foo.bar.com)');
- $this->app['rocketeer.server']->setValue('notifier.name', 'Jean Eude');
-
- $this->task('Deploy')->fireEvent('after');
- }
-
- public function testDoesntSendNotificationsInPretendMode()
- {
- $this->expectOutputString('');
- $this->app['rocketeer.server']->setValue('notifier.name', 'Jean Eude');
-
- $this->pretendTask('Deploy')->fireEvent('after');
- }
-}
diff --git a/tests/ReleasesManagerTest.php b/tests/ReleasesManagerTest.php
deleted file mode 100644
index 4f1dc89f6..000000000
--- a/tests/ReleasesManagerTest.php
+++ /dev/null
@@ -1,134 +0,0 @@
-app['rocketeer.releases']->getCurrentRelease();
-
- $this->assertEquals(20000000000000, $currentRelease);
- }
-
- public function testCanGetStateOfReleases()
- {
- $validation = $this->app['rocketeer.releases']->getValidationFile();
-
- $this->assertEquals(array(
- 10000000000000 => true,
- 15000000000000 => false,
- 20000000000000 => true,
- ), $validation);
- }
-
- public function testCanGetInvalidReleases()
- {
- $validation = $this->app['rocketeer.releases']->getInvalidReleases();
-
- $this->assertEquals(array(1 => 15000000000000), $validation);
- }
-
- public function testCanUpdateStateOfReleases()
- {
- $this->app['rocketeer.releases']->markReleaseAsValid(15000000000000);
- $validation = $this->app['rocketeer.releases']->getValidationFile();
-
- $this->assertEquals(array(
- 10000000000000 => true,
- 15000000000000 => true,
- 20000000000000 => true,
- ), $validation);
- }
-
- public function testCanMarkReleaseAsValid()
- {
- $this->app['rocketeer.releases']->markReleaseAsValid(123456789);
- $validation = $this->app['rocketeer.releases']->getValidationFile();
-
- $this->assertEquals(array(
- 10000000000000 => true,
- 15000000000000 => false,
- 20000000000000 => true,
- 123456789 => true,
- ), $validation);
- }
-
- public function testCanGetCurrentReleaseFromServerIfUncached()
- {
- $this->mock('rocketeer.server', 'Server', function ($mock) {
- return $mock
- ->shouldReceive('getValue')->with('current_release.production')->once()->andReturn(null)
- ->shouldReceive('setValue')->with('current_release.production', '20000000000000')->once()
- ->shouldReceive('getSeparator')->andReturn('/')
- ->shouldReceive('getLineEndings')->andReturn(PHP_EOL);
- });
-
- $currentRelease = $this->app['rocketeer.releases']->getCurrentRelease();
-
- $this->assertEquals(20000000000000, $currentRelease);
- }
-
- public function testCanGetReleasesPath()
- {
- $releasePath = $this->app['rocketeer.releases']->getReleasesPath();
-
- $this->assertEquals($this->server.'/releases', $releasePath);
- }
-
- public function testCanGetCurrentReleaseFolder()
- {
- $currentReleasePath = $this->app['rocketeer.releases']->getCurrentReleasePath();
-
- $this->assertEquals($this->server.'/releases/20000000000000', $currentReleasePath);
- }
-
- public function testCanGetReleases()
- {
- $releases = $this->app['rocketeer.releases']->getReleases();
-
- $this->assertEquals(array(1 => 15000000000000, 0 => 20000000000000, 2 => 10000000000000), $releases);
- }
-
- public function testCanGetDeprecatedReleases()
- {
- $releases = $this->app['rocketeer.releases']->getDeprecatedReleases();
-
- $this->assertEquals(array(15000000000000, 10000000000000), $releases);
- }
-
- public function testCanGetPreviousValidRelease()
- {
- $currentRelease = $this->app['rocketeer.releases']->getPreviousRelease();
-
- $this->assertEquals(10000000000000, $currentRelease);
- }
-
- public function testReturnsCurrentReleaseIfNoPreviousValidRelease()
- {
- file_put_contents($this->server.'/state.json', json_encode(array(
- '10000000000000' => false,
- '15000000000000' => false,
- '20000000000000' => true,
- )));
-
- $currentRelease = $this->app['rocketeer.releases']->getPreviousRelease();
-
- $this->assertEquals(20000000000000, $currentRelease);
- }
-
- public function testCanUpdateCurrentRelease()
- {
- $this->app['rocketeer.releases']->updateCurrentRelease(30000000000000);
-
- $this->assertEquals(30000000000000, $this->app['rocketeer.server']->getValue('current_release.production'));
- }
-
- public function testCanGetFolderInRelease()
- {
- $folder = $this->app['rocketeer.releases']->getCurrentReleasePath('{path.storage}');
-
- $this->assertEquals($this->server.'/releases/20000000000000/app/storage', $folder);
- }
-}
diff --git a/tests/RocketeerTest.php b/tests/RocketeerTest.php
index 9ea59f0f9..3922d47b8 100644
--- a/tests/RocketeerTest.php
+++ b/tests/RocketeerTest.php
@@ -5,131 +5,9 @@
class RocketeerTest extends RocketeerTestCase
{
- ////////////////////////////////////////////////////////////////////
- //////////////////////////////// TESTS /////////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- public function testCanGetAvailableConnections()
- {
- $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']->getAvailableConnections();
- $this->assertEquals(array('custom'), array_keys($connections));
- }
-
- public function testCanGetCurrentConnection()
- {
- $this->swapConfig(array('rocketeer::default' => 'foobar'));
- $this->assertEquals('production', $this->app['rocketeer.rocketeer']->getConnection());
-
- $this->swapConfig(array('rocketeer::default' => 'production'));
- $this->assertEquals('production', $this->app['rocketeer.rocketeer']->getConnection());
-
- $this->swapConfig(array('rocketeer::default' => '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:'.$this->repository;
- $this->expectRepositoryConfig($repository, '', '');
-
- $this->assertEquals($repository, $this->app['rocketeer.rocketeer']->getRepository());
- }
-
- public function testCanUseHttpsRepository()
- {
- $this->expectRepositoryConfig('https://github.com/'.$this->repository, 'foobar', 'bar');
-
- $this->assertEquals('https://foobar:bar@github.com/'.$this->repository, $this->app['rocketeer.rocketeer']->getRepository());
- }
-
- public function testCanUseHttpsRepositoryWithUsernameProvided()
- {
- $this->expectRepositoryConfig('https://foobar@github.com/'.$this->repository, 'foobar', 'bar');
-
- $this->assertEquals('https://foobar:bar@github.com/'.$this->repository, $this->app['rocketeer.rocketeer']->getRepository());
- }
-
- public function testCanUseHttpsRepositoryWithOnlyUsernameProvided()
- {
- $this->expectRepositoryConfig('https://foobar@github.com/'.$this->repository, 'foobar', '');
-
- $this->assertEquals('https://foobar@github.com/'.$this->repository, $this->app['rocketeer.rocketeer']->getRepository());
- }
-
- public function testCanCleanupProvidedRepositoryFromCredentials()
- {
- $this->expectRepositoryConfig('https://foobar@github.com/'.$this->repository, 'Anahkiasen', '');
-
- $this->assertEquals('https://Anahkiasen@github.com/'.$this->repository, $this->app['rocketeer.rocketeer']->getRepository());
- }
-
- public function testCanUseHttpsRepositoryWithoutCredentials()
- {
- $this->expectRepositoryConfig('https://github.com/'.$this->repository, '', '');
-
- $this->assertEquals('https://github.com/'.$this->repository, $this->app['rocketeer.rocketeer']->getRepository());
- }
-
- public function testCanCheckIfRepositoryNeedsCredentials()
- {
- $this->expectRepositoryConfig('https://github.com/'.$this->repository, '', '');
- $this->assertTrue($this->app['rocketeer.rocketeer']->needsCredentials());
- }
-
- public function testCangetRepositoryBranch()
- {
- $this->assertEquals('master', $this->app['rocketeer.rocketeer']->getRepositoryBranch());
- }
-
public function testCanGetApplicationName()
{
- $this->assertEquals('foobar', $this->app['rocketeer.rocketeer']->getApplicationName());
- }
-
- public function testCanGetHomeFolder()
- {
- $this->assertEquals($this->server.'', $this->app['rocketeer.rocketeer']->getHomeFolder());
- }
-
- public function testCanGetFolderWithStage()
- {
- $this->app['rocketeer.rocketeer']->setStage('test');
-
- $this->assertEquals($this->server.'/test/current', $this->app['rocketeer.rocketeer']->getFolder('current'));
- }
-
- public function testCanGetAnyFolder()
- {
- $this->assertEquals($this->server.'/current', $this->app['rocketeer.rocketeer']->getFolder('current'));
- }
-
- public function testCanReplacePatternsInFolders()
- {
- $folder = $this->app['rocketeer.rocketeer']->getFolder('{path.storage}');
-
- $this->assertEquals($this->server.'/app/storage', $folder);
- }
-
- public function testCannotReplaceUnexistingPatternsInFolders()
- {
- $folder = $this->app['rocketeer.rocketeer']->getFolder('{path.foobar}');
-
- $this->assertEquals($this->server.'/', $folder);
+ $this->assertEquals('foobar', $this->rocketeer->getApplicationName());
}
public function testCanUseRecursiveStageConfiguration()
@@ -139,49 +17,40 @@ public function testCanUseRecursiveStageConfiguration()
'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'));
+ $this->assertOptionValueEquals('master', 'scm.branch');
+ $this->connections->setStage('staging');
+ $this->assertOptionValueEquals('staging', 'scm.branch');
}
public function testCanUseRecursiveConnectionConfiguration()
{
$this->swapConfig(array(
- 'rocketeer::default' => 'production',
+ 'rocketeer::default' => 'production',
'rocketeer::scm.branch' => 'master',
'rocketeer::on.connections.staging.scm.branch' => 'staging',
));
- $this->assertEquals('master', $this->app['rocketeer.rocketeer']->getOption('scm.branch'));
+ $this->assertOptionValueEquals('master', 'scm.branch');
$this->swapConfig(array(
- 'rocketeer::default' => 'staging',
+ 'rocketeer::default' => 'staging',
'rocketeer::scm.branch' => 'master',
'rocketeer::on.connections.staging.scm.branch' => 'staging',
));
- $this->assertEquals('staging', $this->app['rocketeer.rocketeer']->getOption('scm.branch'));
+ $this->assertOptionValueEquals('staging', 'scm.branch');
}
- ////////////////////////////////////////////////////////////////////
- //////////////////////////////// HELPERS ///////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Make the config return specific SCM config
- *
- * @param string $repository
- * @param string $username
- * @param string $password
- *
- * @return void
- */
- protected function expectRepositoryConfig($repository, $username, $password)
+ public function testRocketeerCanGuessWhichStageHesIn()
{
- $this->swapConfig(array(
- 'rocketeer::scm' => array(
- 'repository' => $repository,
- 'username' => $username,
- 'password' => $password,
- ),
- ));
+ $path = '/home/www/foobar/production/releases/12345678901234/app';
+ $stage = Rocketeer::getDetectedStage('foobar', $path);
+ $this->assertEquals('production', $stage);
+
+ $path = '/home/www/foobar/staging/releases/12345678901234/app';
+ $stage = Rocketeer::getDetectedStage('foobar', $path);
+ $this->assertEquals('staging', $stage);
+
+ $path = '/home/www/foobar/releases/12345678901234/app';
+ $stage = Rocketeer::getDetectedStage('foobar', $path);
+ $this->assertEquals(false, $stage);
}
}
diff --git a/tests/Scm/GitTest.php b/tests/Scm/GitTest.php
index c4039ba60..18aefe09c 100644
--- a/tests/Scm/GitTest.php
+++ b/tests/Scm/GitTest.php
@@ -8,7 +8,7 @@ class GitTest extends RocketeerTestCase
/**
* The current SCM instance
*
- * @var Rocketeer\Scm\Git
+ * @var Git
*/
protected $scm;
@@ -46,30 +46,34 @@ public function testCanGetCurrentBranch()
public function testCanGetCheckout()
{
- $this->mock('rocketeer.rocketeer', 'Rocketeer', function ($mock) {
+ $this->mock('rocketeer.rocketeer', 'Rocketeer\Rocketeer', function ($mock) {
+ return $mock->shouldReceive('getOption')->once()->with('scm.shallow')->andReturn(true);
+ });
+ $this->mock('rocketeer.connections', 'ConnectionsHandler', function ($mock) {
return $mock
- ->shouldReceive('getOption')->once()->with('scm.shallow')->andReturn(true)
- ->shouldReceive('getRepository')->once()->andReturn('http://github.com/my/repository')
+ ->shouldReceive('getRepositoryEndpoint')->once()->andReturn('http://github.com/my/repository')
->shouldReceive('getRepositoryBranch')->once()->andReturn('develop');
});
$command = $this->scm->checkout($this->server);
- $this->assertEquals('git clone --depth 1 -b develop "http://github.com/my/repository" ' .$this->server, $command);
+ $this->assertEquals('git clone "http://github.com/my/repository" "'.$this->server.'" --branch="develop" --depth="1"', $command);
}
public function testCanGetDeepClone()
{
- $this->mock('rocketeer.rocketeer', 'Rocketeer', function ($mock) {
+ $this->mock('rocketeer.rocketeer', 'Rocketeer\Rocketeer', function ($mock) {
+ return $mock->shouldReceive('getOption')->once()->with('scm.shallow')->andReturn(false);
+ });
+ $this->mock('rocketeer.connections', 'ConnectionsHandler', function ($mock) {
return $mock
- ->shouldReceive('getOption')->once()->with('scm.shallow')->andReturn(false)
- ->shouldReceive('getRepository')->once()->andReturn('http://github.com/my/repository')
+ ->shouldReceive('getRepositoryEndpoint')->once()->andReturn('http://github.com/my/repository')
->shouldReceive('getRepositoryBranch')->once()->andReturn('develop');
});
$command = $this->scm->checkout($this->server);
- $this->assertEquals('git clone -b develop "http://github.com/my/repository" ' .$this->server, $command);
+ $this->assertEquals('git clone "http://github.com/my/repository" "'.$this->server.'" --branch="develop"', $command);
}
public function testCanGetReset()
diff --git a/tests/Scm/SvnTest.php b/tests/Scm/SvnTest.php
new file mode 100644
index 000000000..0490ee661
--- /dev/null
+++ b/tests/Scm/SvnTest.php
@@ -0,0 +1,95 @@
+scm = new Svn($this->app);
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ //////////////////////////////// TESTS /////////////////////////////
+ ////////////////////////////////////////////////////////////////////
+
+ public function testCanGetCheck()
+ {
+ $command = $this->scm->check();
+
+ $this->assertEquals('svn --version', $command);
+ }
+
+ public function testCanGetCurrentState()
+ {
+ $command = $this->scm->currentState();
+
+ $this->assertEquals('svn info -r "HEAD" | grep "Revision"', $command);
+ }
+
+ public function testCanGetCurrentBranch()
+ {
+ $command = $this->scm->currentBranch();
+
+ $this->assertEquals('echo trunk', $command);
+ }
+
+ public function testCanGetCheckout()
+ {
+ $this->mock('rocketeer.connections', 'ConnectionsHandler', function ($mock) {
+ return $mock
+ ->shouldReceive('getRepositoryCredentials')->once()->andReturn(['username' => 'foo', 'password' => 'bar'])
+ ->shouldReceive('getRepositoryEndpoint')->once()->andReturn('http://github.com/my/repository')
+ ->shouldReceive('getRepositoryBranch')->once()->andReturn('develop');
+ });
+
+ $command = $this->scm->checkout($this->server);
+
+ $this->assertEquals('svn co http://github.com/my/repository/develop '.$this->server.' --non-interactive --username="foo" --password="bar"', $command);
+ }
+
+ public function testCanGetDeepClone()
+ {
+ $this->mock('rocketeer.connections', 'ConnectionsHandler', function ($mock) {
+ return $mock
+ ->shouldReceive('getRepositoryCredentials')->once()->andReturn(['username' => 'foo', 'password' => 'bar'])
+ ->shouldReceive('getRepositoryEndpoint')->once()->andReturn('http://github.com/my/repository')
+ ->shouldReceive('getRepositoryBranch')->once()->andReturn('develop');
+ });
+
+ $command = $this->scm->checkout($this->server);
+
+ $this->assertEquals('svn co http://github.com/my/repository/develop '.$this->server.' --non-interactive --username="foo" --password="bar"', $command);
+ }
+
+ public function testCanGetReset()
+ {
+ $command = $this->scm->reset();
+
+ $this->assertEquals("svn status -q | grep -v '^[~XI ]' | awk '{print $2;}' | xargs svn revert", $command);
+ }
+
+ public function testCanGetUpdate()
+ {
+ $command = $this->scm->update();
+
+ $this->assertEquals('svn up --non-interactive', $command);
+ }
+
+ public function testCanGetSubmodules()
+ {
+ $command = $this->scm->submodules();
+
+ $this->assertEmpty($command);
+ }
+}
diff --git a/tests/ServerTest.php b/tests/ServerTest.php
deleted file mode 100644
index 24d269dc3..000000000
--- a/tests/ServerTest.php
+++ /dev/null
@@ -1,89 +0,0 @@
-app['path.storage'] = null;
- $this->app->offsetUnset('path.storage');
-
- new Server($this->app);
-
- $storage = $this->app['rocketeer.rocketeer']->getRocketeerConfigFolder();
- $exists = file_exists($storage);
- $this->app['files']->deleteDirectory($storage);
- $this->assertTrue($exists);
- }
-
- public function testCanGetValueFromDeploymentsFile()
- {
- $this->assertEquals('bar', $this->app['rocketeer.server']->getValue('foo'));
- }
-
- public function testCanSetValueInDeploymentsFile()
- {
- $this->app['rocketeer.server']->setValue('foo', 'baz');
-
- $this->assertEquals('baz', $this->app['rocketeer.server']->getValue('foo'));
- }
-
- public function testCandeleteRepository()
- {
- $this->app['rocketeer.server']->deleteRepository();
-
- $this->assertFalse($this->app['files']->exists(__DIR__.'/_meta/deployments.json'));
- }
-
- public function testCanFallbackIfFileDoesntExist()
- {
- $this->app['rocketeer.server']->deleteRepository();
-
- $this->assertEquals(null, $this->app['rocketeer.server']->getValue('foo'));
- }
-
- public function testCanGetLineEndings()
- {
- $this->app['rocketeer.server']->deleteRepository();
-
- $this->assertEquals(PHP_EOL, $this->app['rocketeer.server']->getLineEndings());
- }
-
- public function testCanGetSeparators()
- {
- $this->app['rocketeer.server']->deleteRepository();
-
- $this->assertEquals(DIRECTORY_SEPARATOR, $this->app['rocketeer.server']->getSeparator());
- }
-
- public function testCanComputeHashAccordingToContentsOfFiles()
- {
- $this->mock('files', 'Filesystem', function ($mock) {
- return $mock
- ->shouldReceive('put')->once()
- ->shouldReceive('exists')->twice()->andReturn(false)
- ->shouldReceive('glob')->once()->andReturn(array('foo', 'bar'))
- ->shouldReceive('getRequire')->once()->with('foo')->andReturn(array('foo'))
- ->shouldReceive('getRequire')->once()->with('bar')->andReturn(array('bar'));
- });
-
- $hash = $this->app['rocketeer.server']->getHash();
-
- $this->assertEquals(md5('["foo"]["bar"]'), $hash);
- }
-
- public function testCanCheckIfComposerIsNeeded()
- {
- $this->usesComposer(true);
- $this->assertTrue($this->app['rocketeer.server']->usesComposer());
-
- $this->usesComposer(false);
- $this->assertFalse($this->app['rocketeer.server']->usesComposer());
- }
-}
diff --git a/tests/Services/Connections/ConnectionsHandlerTest.php b/tests/Services/Connections/ConnectionsHandlerTest.php
new file mode 100644
index 000000000..264ace472
--- /dev/null
+++ b/tests/Services/Connections/ConnectionsHandlerTest.php
@@ -0,0 +1,179 @@
+connections->getAvailableConnections();
+ $this->assertEquals(array('production', 'staging'), array_keys($connections));
+
+ $this->app['rocketeer.storage.local']->set('connections.custom.username', 'foobar');
+ $connections = $this->connections->getAvailableConnections();
+ $this->assertEquals(array('production', 'staging', 'custom'), array_keys($connections));
+ }
+
+ public function testCanGetCurrentConnection()
+ {
+ $this->swapConfig(array('rocketeer::default' => 'foobar'));
+ $this->assertConnectionEquals('production');
+
+ $this->swapConfig(array('rocketeer::default' => 'production'));
+ $this->assertConnectionEquals('production');
+
+ $this->swapConfig(array('rocketeer::default' => 'staging'));
+ $this->assertConnectionEquals('staging');
+ }
+
+ public function testCanChangeConnection()
+ {
+ $this->assertConnectionEquals('production');
+
+ $this->connections->setConnection('staging');
+ $this->assertConnectionEquals('staging');
+
+ $this->connections->setConnections('staging,production');
+ $this->assertEquals(array('staging', 'production'), $this->connections->getConnections());
+ }
+
+ public function testCanUseSshRepository()
+ {
+ $repository = 'git@github.com:'.$this->repository;
+ $this->expectRepositoryConfig($repository, '', '');
+
+ $this->assertRepositoryEquals($repository);
+ }
+
+ public function testCanUseHttpsRepository()
+ {
+ $this->expectRepositoryConfig('https://github.com/'.$this->repository, 'foobar', 'bar');
+
+ $this->assertRepositoryEquals('https://foobar:bar@github.com/'.$this->repository);
+ }
+
+ public function testCanUseHttpsRepositoryWithUsernameProvided()
+ {
+ $this->expectRepositoryConfig('https://foobar@github.com/'.$this->repository, 'foobar', 'bar');
+
+ $this->assertRepositoryEquals('https://foobar:bar@github.com/'.$this->repository);
+ }
+
+ public function testCanUseHttpsRepositoryWithOnlyUsernameProvided()
+ {
+ $this->expectRepositoryConfig('https://foobar@github.com/'.$this->repository, 'foobar', '');
+
+ $this->assertRepositoryEquals('https://foobar@github.com/'.$this->repository);
+ }
+
+ public function testCanCleanupProvidedRepositoryFromCredentials()
+ {
+ $this->expectRepositoryConfig('https://foobar@github.com/'.$this->repository, 'Anahkiasen', '');
+
+ $this->assertRepositoryEquals('https://Anahkiasen@github.com/'.$this->repository);
+ }
+
+ public function testCanUseHttpsRepositoryWithoutCredentials()
+ {
+ $this->expectRepositoryConfig('https://github.com/'.$this->repository, '', '');
+
+ $this->assertRepositoryEquals('https://github.com/'.$this->repository);
+ }
+
+ public function testCanCheckIfRepositoryNeedsCredentials()
+ {
+ $this->expectRepositoryConfig('https://github.com/'.$this->repository, '', '');
+ $this->assertTrue($this->connections->needsCredentials());
+ }
+
+ public function testCangetRepositoryBranch()
+ {
+ $this->assertEquals('master', $this->connections->getRepositoryBranch());
+ }
+
+ public function testFillsConnectionCredentialsHoles()
+ {
+ $connections = $this->connections->getAvailableConnections();
+ $this->assertArrayHasKey('production', $connections);
+
+ $this->app['rocketeer.storage.local']->set('connections', array(
+ 'staging' => array(
+ 'host' => 'foobar',
+ 'username' => 'user',
+ 'password' => '',
+ 'keyphrase' => '',
+ 'key' => '/Users/user/.ssh/id_rsa',
+ 'agent' => '',
+ ),
+ ));
+ $connections = $this->connections->getAvailableConnections();
+ $this->assertArrayHasKey('production', $connections);
+ }
+
+ public function testCanCreateHandleForCurrent()
+ {
+ $handle = $this->connections->getHandle('foo', 2, 'staging');
+
+ $this->assertEquals('foo/2/staging', $handle);
+ }
+
+ public function testDoesntDisplayServerNumberIfNotMultiserver()
+ {
+ $handle = $this->connections->getHandle('foo', 0, 'staging');
+
+ $this->assertEquals('foo/staging', $handle);
+ }
+
+ public function testDoesntResetConnectionIfSameAsCurrent()
+ {
+ $this->mock('rocketeer.tasks', 'TasksHandler', function ($mock) {
+ return $mock
+ ->shouldReceive('registerConfiguredEvents')->once();
+ }, false);
+
+ $this->connections->setConnection('production');
+ $this->connections->setConnection('production');
+ $this->connections->setConnection('production');
+ }
+
+ public function testDoesntResetStageIfSameAsCurrent()
+ {
+ $this->mock('rocketeer.tasks', 'TasksHandler', function ($mock) {
+ return $mock
+ ->shouldReceive('registerConfiguredEvents')->once();
+ }, false);
+
+ $this->connections->setStage('foobar');
+ $this->connections->setStage('foobar');
+ $this->connections->setStage('foobar');
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ //////////////////////////////// HELPERS ///////////////////////////
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Make the config return specific SCM config
+ *
+ * @param string $repository
+ * @param string $username
+ * @param string $password
+ *
+ * @return void
+ */
+ protected function expectRepositoryConfig($repository, $username, $password)
+ {
+ $this->swapConfig(array(
+ 'rocketeer::scm' => array(
+ 'repository' => $repository,
+ 'username' => $username,
+ 'password' => $password,
+ ),
+ ));
+ }
+}
diff --git a/tests/Services/Connections/LocalConnectionTest.php b/tests/Services/Connections/LocalConnectionTest.php
new file mode 100644
index 000000000..177f26fc9
--- /dev/null
+++ b/tests/Services/Connections/LocalConnectionTest.php
@@ -0,0 +1,16 @@
+task;
+ $task->setLocal(true);
+ $task->run('ls');
+
+ $this->assertTrue($task->status());
+ }
+}
diff --git a/tests/Services/Connections/RemoteHandlerTest.php b/tests/Services/Connections/RemoteHandlerTest.php
new file mode 100644
index 000000000..46281dd42
--- /dev/null
+++ b/tests/Services/Connections/RemoteHandlerTest.php
@@ -0,0 +1,115 @@
+handler = new RemoteHandler($this->app);
+ unset($this->app['rocketeer.command']);
+ }
+
+ public function testCanCreateConnection()
+ {
+ $this->swapConfig(array(
+ 'rocketeer::connections' => array(
+ 'production' => array(
+ 'host' => 'foobar.com',
+ 'username' => 'foobar',
+ 'password' => 'foobar',
+ ),
+ ),
+ ));
+
+ $connection = $this->handler->connection();
+
+ $this->assertInstanceOf('Rocketeer\Services\Connections\Connection', $connection);
+ $this->assertEquals('production', $connection->getName());
+ $this->assertEquals('foobar', $connection->getUsername());
+ }
+
+ public function testThrowsExceptionIfMissingCredentials()
+ {
+ $this->setExpectedException('Rocketeer\Exceptions\MissingCredentialsException');
+
+ $this->swapConfig(array(
+ 'rocketeer::connections' => array(
+ 'production' => array(
+ 'host' => 'foobar.com',
+ 'username' => 'foobar',
+ ),
+ ),
+ ));
+
+ $this->handler->connection();
+ }
+
+ public function testThrowsExceptionIfMissingInformations()
+ {
+ $this->setExpectedException('Rocketeer\Exceptions\MissingCredentialsException');
+
+ $this->swapConfig(array(
+ 'rocketeer::connections' => array(
+ 'production' => array(
+ 'username' => 'foobar',
+ 'password' => 'foobar',
+ ),
+ ),
+ ));
+
+ $this->handler->connection();
+ }
+
+ public function testCachesConnections()
+ {
+ $this->swapConfig(array(
+ 'rocketeer::connections' => array(
+ 'production' => array(
+ 'host' => 'foobar.com',
+ 'username' => 'foobar',
+ 'password' => 'foobar',
+ ),
+ ),
+ ));
+
+ $connection = $this->handler->connection();
+ $this->assertInstanceOf('Rocketeer\Services\Connections\Connection', $connection);
+ $this->assertEquals('production', $connection->getName());
+
+ $this->swapConfig(array(
+ 'rocketeer::connections' => array(
+ 'production' => array(),
+ ),
+ ));
+
+ $connection = $this->handler->connection();
+ $this->assertInstanceOf('Rocketeer\Services\Connections\Connection', $connection);
+ $this->assertEquals('production', $connection->getName());
+ }
+
+ public function testThrowsExceptionIfUnableToConnect()
+ {
+ $this->setExpectedException('Rocketeer\Exceptions\ConnectionException');
+
+ $this->swapConfig(array(
+ 'rocketeer::connections' => array(
+ 'production' => array(
+ 'host' => 'foobar.com',
+ 'username' => 'foobar',
+ 'password' => 'foobar',
+ ),
+ ),
+ ));
+
+ $this->handler->run('ls');
+ }
+}
diff --git a/tests/Services/CredentialsGathererTest.php b/tests/Services/CredentialsGathererTest.php
new file mode 100644
index 000000000..43afc54df
--- /dev/null
+++ b/tests/Services/CredentialsGathererTest.php
@@ -0,0 +1,244 @@
+repository = 'git@github.com:Anahkiasen/rocketeer.git';
+ $this->username = 'Anahkiasen';
+ $this->password = 'foobar';
+ $this->host = 'some.host';
+ }
+
+ public function testIgnoresPlaceholdersWhenFillingCredentials()
+ {
+ $this->mockAnswers(array(
+ 'No repository is set for [repository]' => $this->repository,
+ 'No username is set for [repository]' => $this->username,
+ 'No password is set for [repository]' => $this->password,
+ ));
+ $this->command->shouldReceive('option')->andReturn(null);
+
+ $this->givenConfiguredRepositoryCredentials(['repository' => '{foobar}']);
+
+ $this->assertStoredCredentialsEquals(array(
+ 'repository' => $this->repository,
+ 'username' => $this->username,
+ 'password' => $this->password,
+ ));
+
+ $this->credentials->getRepositoryCredentials();
+ }
+
+ public function testCanGetRepositoryCredentials()
+ {
+ $this->mockAnswers(array(
+ 'No repository is set for [repository]' => $this->repository,
+ 'No username is set for [repository]' => $this->username,
+ 'No password is set for [repository]' => $this->password,
+ ));
+ $this->command->shouldReceive('option')->andReturn(null);
+
+ $this->givenConfiguredRepositoryCredentials([]);
+
+ $this->assertStoredCredentialsEquals(array(
+ 'repository' => $this->repository,
+ 'username' => $this->username,
+ 'password' => $this->password,
+ ));
+
+ $this->credentials->getRepositoryCredentials();
+ }
+
+ public function testDoesntAskForRepositoryCredentialsIfUneeded()
+ {
+ $this->mockAnswers();
+ $this->command->shouldReceive('option')->andReturn(null);
+
+ $this->givenConfiguredRepositoryCredentials([
+ 'repository' => $this->repository,
+ 'username' => null,
+ 'password' => null,
+ ], false);
+ $this->assertStoredCredentialsEquals(array(
+ 'repository' => $this->repository,
+ 'username' => null,
+ 'password' => null,
+ ));
+
+ $this->credentials->getRepositoryCredentials();
+ }
+
+ public function testCanFillRepositoryCredentialsIfNeeded()
+ {
+ $this->mockAnswers(array(
+ 'No username is set for [repository]' => $this->username,
+ 'No password is set for [repository]' => null,
+ ));
+ $this->command->shouldReceive('option')->andReturn(null);
+
+ $this->givenConfiguredRepositoryCredentials(['repository' => $this->repository], true);
+
+ $this->assertStoredCredentialsEquals(array(
+ 'repository' => $this->repository,
+ 'username' => 'Anahkiasen',
+ 'password' => null,
+ ));
+
+ $this->credentials->getRepositoryCredentials();
+ }
+
+ public function testCanGetServerCredentialsIfNoneDefined()
+ {
+ $this->swapConfig(array(
+ 'remote.connections' => [],
+ ));
+
+ $this->mockAnswers(array(
+ 'No host is set for [production]' => $this->host,
+ 'No username is set for [production]' => $this->username,
+ 'No password is set for [production]' => $this->password,
+ ));
+
+ $this->command->shouldReceive('askWith')->with('No connections have been set, please create one:', 'production')->andReturn('production');
+ $this->command->shouldReceive('askWith')->with(
+ 'No password or SSH key is set for [production], which would you use?',
+ 'key', ['key', 'password']
+ )->andReturn('password');
+ $this->command->shouldReceive('option')->andReturn(null);
+
+ $this->credentials->getServerCredentials();
+
+ $credentials = $this->connections->getServerCredentials('production', 0);
+ $this->assertEquals(array(
+ 'host' => $this->host,
+ 'username' => $this->username,
+ 'password' => $this->password,
+ 'keyphrase' => null,
+ 'key' => null,
+ 'agent' => null,
+ ), $credentials);
+ }
+
+ public function testCanPassCredentialsAsFlags()
+ {
+ $this->swapConfig(array(
+ 'remote.connections' => [],
+ ));
+
+ $this->mockAnswers(array(
+ 'No username is set for [production]' => $this->username,
+ ));
+
+ $this->command->shouldReceive('askWith')->with('No connections have been set, please create one:', 'production')->andReturn('production');
+ $this->command->shouldReceive('askWith')->with(
+ 'No password or SSH key is set for [production], which would you use?',
+ 'key', ['key', 'password']
+ )->andReturn('password');
+ $this->command->shouldReceive('option')->with('host')->andReturn($this->host);
+ $this->command->shouldReceive('option')->with('password')->andReturn($this->password);
+ $this->command->shouldReceive('option')->andReturn(null);
+
+ $this->credentials->getServerCredentials();
+
+ $credentials = $this->connections->getServerCredentials('production', 0);
+ $this->assertEquals(array(
+ 'host' => $this->host,
+ 'username' => $this->username,
+ 'password' => $this->password,
+ 'keyphrase' => null,
+ 'key' => null,
+ 'agent' => null,
+ ), $credentials);
+ }
+
+ public function testCanGetCredentialsForSpecifiedConnection()
+ {
+ $key = $this->paths->getDefaultKeyPath();
+ $this->mockAnswers(array(
+ 'No host is set for [staging/0]' => $this->host,
+ 'No username is set for [staging/0]' => $this->username,
+ 'If a keyphrase is required, provide it' => 'KEYPHRASE',
+ ));
+
+ $this->command->shouldReceive('option')->with('on')->andReturn('staging');
+ $this->command->shouldReceive('option')->andReturn(null);
+ $this->command->shouldReceive('askWith')->with(
+ 'Please enter the full path to your key', $key
+ )->andReturn($key);
+ $this->command->shouldReceive('askWith')->with(
+ 'No password or SSH key is set for [staging/0], which would you use?',
+ 'key', ['key', 'password']
+ )->andReturn('key');
+
+ $this->credentials->getServerCredentials();
+
+ $credentials = $this->connections->getServerCredentials('staging', 0);
+ $this->assertEquals(array(
+ 'host' => $this->host,
+ 'username' => $this->username,
+ 'password' => null,
+ 'keyphrase' => 'KEYPHRASE',
+ 'key' => $key,
+ 'agent' => null,
+ ), $credentials);
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// HELPERS ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Mock a set of question/answers
+ *
+ * @param array $answers
+ */
+ protected function mockAnswers($answers = array())
+ {
+ $this->mock('rocketeer.command', 'Command', function ($mock) use ($answers) {
+ if (!$answers) {
+ return $mock->shouldReceive('ask')->never();
+ }
+
+ foreach ($answers as $question => $answer) {
+ $question = strpos($question, 'is set for') !== false ? $question.', please provide one:' : $question;
+ $method = strpos($question, 'password') !== false ? 'askSecretly' : 'askWith';
+ $mock = $mock->shouldReceive($method)->with($question)->andReturn($answer);
+ }
+
+ return $mock;
+ });
+ }
+
+ /**
+ * Assert a certain set of credentials are saved to storage
+ *
+ * @param array $credentials
+ */
+ protected function assertStoredCredentialsEquals(array $credentials)
+ {
+ $this->mock('rocketeer.storage.local', 'LocalStorage', function ($mock) use ($credentials) {
+ return $mock->shouldReceive('set')->with('credentials', $credentials);
+ });
+ }
+
+ /**
+ * @param array $credentials
+ * @param boolean $need
+ */
+ protected function givenConfiguredRepositoryCredentials(array $credentials, $need = false)
+ {
+ $this->mock('rocketeer.connections', 'ConnectionsHandler', function ($mock) use ($need, $credentials) {
+ return $mock
+ ->shouldReceive('needsCredentials')->andReturn($need)
+ ->shouldReceive('getRepositoryCredentials')->andReturn($credentials);
+ });
+ }
+}
diff --git a/tests/Services/History/HistoryTest.php b/tests/Services/History/HistoryTest.php
new file mode 100644
index 000000000..cffc8181f
--- /dev/null
+++ b/tests/Services/History/HistoryTest.php
@@ -0,0 +1,32 @@
+bash->toHistory('foo');
+ usleep($this->sleep);
+ $this->bash->toHistory(['bar', 'baz']);
+
+ $history = $this->history->getFlattenedHistory();
+ $this->assertEquals(['foo', ['bar', 'baz']], $history);
+ }
+
+ public function testCanGetFlattenedOutput()
+ {
+ $this->bash->toOutput('foo');
+ usleep($this->sleep);
+ $this->bash->toOutput(['bar', 'baz']);
+
+ $history = $this->history->getFlattenedOutput();
+ $this->assertEquals(['foo', ['bar', 'baz']], $history);
+ }
+}
diff --git a/tests/LogsHandlerTest.php b/tests/Services/History/LogsHandlerTest.php
similarity index 52%
rename from tests/LogsHandlerTest.php
rename to tests/Services/History/LogsHandlerTest.php
index 884ac1d10..d3ebd6212 100644
--- a/tests/LogsHandlerTest.php
+++ b/tests/Services/History/LogsHandlerTest.php
@@ -1,5 +1,5 @@
app['rocketeer.logs']->getCurrentLogsFile();
+ $logs = $this->logs->getCurrentLogsFile();
$this->assertEquals($this->server.'/logs/production-.log', $logs);
- $this->app['rocketeer.rocketeer']->setConnection('staging');
- $this->app['rocketeer.rocketeer']->setStage('foobar');
- $logs = $this->app['rocketeer.logs']->getCurrentLogsFile();
+ $this->connections->setConnection('staging');
+ $this->connections->setStage('foobar');
+ $logs = $this->logs->getCurrentLogsFile();
$this->assertEquals($this->server.'/logs/staging-foobar.log', $logs);
}
public function testCanLogInformations()
{
- $this->app['rocketeer.logs']->log('foobar', 'error');
- $logs = $this->app['rocketeer.logs']->getCurrentLogsFile();
+ $this->logs->log('foobar');
+ $this->logs->write();
+ $logs = $this->logs->getCurrentLogsFile();
$logs = file_get_contents($logs);
- $this->assertContains('rocketeer.ERROR: foobar [] []', $logs);
- }
-
- public function testCanLogViaMagicMethods()
- {
- $this->app['rocketeer.logs']->error('foobar');
- $logs = $this->app['rocketeer.logs']->getCurrentLogsFile();
- $logs = file_get_contents($logs);
-
- $this->assertContains('rocketeer.ERROR: foobar [] []', $logs);
+ $this->assertContains('foobar', $logs);
}
public function testCanCreateLogsFolderIfItDoesntExistAlready()
{
$this->app['path.rocketeer.logs'] = $this->server.'/newlogs';
- $this->app['rocketeer.logs']->error('foobar');
- $logs = $this->app['rocketeer.logs']->getCurrentLogsFile();
+ $this->logs->log('foobar');
+ $this->logs->write();
+ $logs = $this->logs->getCurrentLogsFile();
$this->assertFileExists($logs);
$this->app['files']->deleteDirectory(dirname($logs));
diff --git a/tests/Services/Ignition/ConfigurationTest.php b/tests/Services/Ignition/ConfigurationTest.php
new file mode 100644
index 000000000..d8de045af
--- /dev/null
+++ b/tests/Services/Ignition/ConfigurationTest.php
@@ -0,0 +1,147 @@
+igniter = new Configuration($this->app);
+ unset($this->app['path.base']);
+ unset($this->app['path']);
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ //////////////////////////////// TESTS /////////////////////////////
+ ////////////////////////////////////////////////////////////////////
+
+ public function testDoesntRebindBasePath()
+ {
+ $base = 'src';
+ $this->app->instance('path.base', $base);
+ $this->igniter->bindPaths();
+
+ $this->assertEquals($base, $this->app['path.base']);
+ }
+
+ public function testCanBindBasePath()
+ {
+ $this->igniter->bindPaths();
+
+ $this->assertEquals(realpath(__DIR__.'/../../..'), $this->app['path.base']);
+ }
+
+ public function testCanBindConfigurationPaths()
+ {
+ $this->igniter->bindPaths();
+
+ $root = realpath(__DIR__.'/../../..');
+ $this->assertEquals($root.'/.rocketeer', $this->app['path.rocketeer.config']);
+ }
+
+ public function testCanBindTasksAndEventsPaths()
+ {
+ $this->igniter->bindPaths();
+ $this->igniter->exportConfiguration();
+
+ // Create some fake files
+ $root = realpath(__DIR__.'/../../../.rocketeer');
+ $this->files->put($root.'/events.php', '');
+ $this->files->makeDirectory($root.'/tasks');
+
+ $this->igniter->bindPaths();
+
+ $this->assertEquals($root.'/tasks', $this->app['path.rocketeer.tasks']);
+ $this->assertEquals($root.'/events.php', $this->app['path.rocketeer.events']);
+ }
+
+ public function testCanExportConfiguration()
+ {
+ $this->igniter->bindPaths();
+ $this->igniter->exportConfiguration();
+
+ $this->assertFileExists(__DIR__.'/../../../.rocketeer');
+ }
+
+ public function testCanReplaceStubsInConfigurationFile()
+ {
+ $this->igniter->bindPaths();
+ $path = $this->igniter->exportConfiguration();
+ $this->igniter->updateConfiguration($path, array('scm_username' => 'foobar'));
+
+ $this->assertFileExists(__DIR__.'/../../../.rocketeer');
+ $this->assertContains('foobar', file_get_contents(__DIR__.'/../../../.rocketeer/scm.php'));
+ }
+
+ public function testCanSetCurrentApplication()
+ {
+ $this->mock('rocketeer.storage.local', 'LocalStorage', function ($mock) {
+ return $mock->shouldReceive('setFile')->once()->with('foobar');
+ });
+
+ $this->igniter->bindPaths();
+ $path = $this->igniter->exportConfiguration();
+ $this->igniter->updateConfiguration($path, array('application_name' => 'foobar', 'scm_username' => 'foobar'));
+
+ $this->assertFileExists(__DIR__.'/../../../.rocketeer');
+ $this->assertContains('foobar', file_get_contents(__DIR__.'/../../../.rocketeer/config.php'));
+ }
+
+ public function testCanLoadFilesOrFolder()
+ {
+ $config = $this->customConfig;
+ $this->app['path.base'] = dirname($config);
+
+ $this->files->makeDirectory($config.'/events', 0755, true);
+ $this->files->put($config.'/tasks.php', 'files->put($config.'/events/some-event.php', 'igniter->bindPaths();
+ $this->igniter->loadUserConfiguration();
+ $this->tasks->registerConfiguredEvents();
+
+ $task = $this->builder->buildTask('DisplayFiles');
+ $this->assertInstanceOf('Rocketeer\Tasks\Closure', $task);
+ $this->assertEquals('DisplayFiles', $task->getName());
+
+ $events = $this->tasks->getTasksListeners($task, 'before');
+ $this->assertCount(1, $events);
+ $this->assertEquals('whoami', $events[0][0]->getStringTask());
+ }
+
+ public function testCanUseFilesAndFoldersForContextualConfig()
+ {
+ $this->mock('config', 'Config', function ($mock) {
+ return $mock->shouldReceive('set')->once()->with('rocketeer::on.connections.production.scm', ['scm' => 'svn']);
+ });
+
+ $file = $this->customConfig.'/connections/production/scm.php';
+ $this->files->makeDirectory(dirname($file), 0755, true);
+ $this->app['path.rocketeer.config'] = realpath($this->customConfig);
+
+ file_put_contents($file, ' "svn");');
+
+ $this->igniter->mergeContextualConfigurations();
+ }
+
+ public function testDoesntCrashIfNoSubfolder()
+ {
+ $this->files->makeDirectory($this->customConfig, 0755, true);
+ $this->app['path.rocketeer.config'] = realpath($this->customConfig);
+
+ $this->igniter->mergeContextualConfigurations();
+ }
+}
diff --git a/tests/Services/Ignition/PluginsTest.php b/tests/Services/Ignition/PluginsTest.php
new file mode 100644
index 000000000..8a0138334
--- /dev/null
+++ b/tests/Services/Ignition/PluginsTest.php
@@ -0,0 +1,72 @@
+plugins = new Plugins($this->app);
+ $this->from = $this->app['path.base'].'/vendor/anahkiasen/rocketeer-slack/config';
+ }
+
+ public function testCanPublishClassicPluginConfiguration()
+ {
+ unset($this->app['path']);
+
+ $this->mockFiles(function ($mock) {
+ $destination = $this->app['path.rocketeer.config'].'/plugins/rocketeers/rocketeer-slack';
+
+ return $mock
+ ->shouldReceive('isDirectory')->with($this->from)->andReturn(true)
+ ->shouldReceive('isDirectory')->with($destination)->andReturn(false)
+ ->shouldReceive('makeDirectory')->with($destination)->andReturn(true)
+ ->shouldReceive('copyDirectory')->with($this->from, $destination);
+ });
+
+ $this->plugins->publish('anahkiasen/rocketeer-slack');
+ }
+
+ public function testCancelsIfNoValidConfigurationPath()
+ {
+ unset($this->app['path']);
+
+ $this->mockFiles(function ($mock) {
+ return $mock
+ ->shouldReceive('isDirectory')->with($this->from)->andReturn(false)
+ ->shouldReceive('copyDirectory')->never();
+ });
+
+ $this->plugins->publish('anahkiasen/rocketeer-slack');
+ }
+
+ public function testCanPublishLaravelConfiguration()
+ {
+ $this->mock('artisan');
+
+ $this->mockFiles(function ($mock) {
+ $destination = $this->app['path'].'/config/packages/rocketeers/rocketeer-slack';
+
+ return $mock
+ ->shouldReceive('isDirectory')->with($this->from)->andReturn(true)
+ ->shouldReceive('isDirectory')->with($destination)->andReturn(false)
+ ->shouldReceive('makeDirectory')->with($destination)->andReturn(true)
+ ->shouldReceive('copyDirectory')->with($this->from, $destination);
+ });
+
+ $this->plugins->publish('anahkiasen/rocketeer-slack');
+ }
+}
diff --git a/tests/Services/PathfinderTest.php b/tests/Services/PathfinderTest.php
new file mode 100644
index 000000000..6d2a8bc5c
--- /dev/null
+++ b/tests/Services/PathfinderTest.php
@@ -0,0 +1,139 @@
+assertEquals($this->server, $this->paths->getHomeFolder());
+ }
+
+ public function testCanGetFolderWithStage()
+ {
+ $this->connections->setStage('test');
+
+ $this->assertEquals($this->server.'/test/current', $this->paths->getFolder('current'));
+ }
+
+ public function testCanGetAnyFolder()
+ {
+ $this->assertEquals($this->server.'/current', $this->paths->getFolder('current'));
+ }
+
+ public function testCanReplacePatternsInFolders()
+ {
+ $folder = $this->paths->getFolder('{path.storage}');
+
+ $this->assertEquals($this->server.'/app/storage', $folder);
+ }
+
+ public function testCannotReplaceUnexistingPatternsInFolders()
+ {
+ $folder = $this->paths->getFolder('{path.foobar}');
+
+ $this->assertEquals($this->server.'/', $folder);
+ }
+
+ public function testCanReplacePlaceholdersOnWindows()
+ {
+ $this->app['path.base'] = 'c:\xampp\htdocs\project';
+ $this->app['path.foobar'] = 'c:\xampp\htdocs\project\lol';
+
+ $this->assertEquals($this->server.'/lol', $this->paths->getFolder('{path.foobar}'));
+ }
+
+ public function testCanGetUserHomeFolder()
+ {
+ $_SERVER['HOME'] = '/some/folder';
+ $home = $this->paths->getUserHomeFolder();
+
+ $this->assertEquals('/some/folder', $home);
+ }
+
+ public function testCanGetWindowsHomeFolder()
+ {
+ unset($_SERVER['HOME']);
+
+ $_SERVER['HOMEDRIVE'] = 'C:';
+ $_SERVER['HOMEPATH'] = '\Users\someuser';
+ $home = $this->paths->getUserHomeFolder();
+
+ $this->assertEquals('C:\Users\someuser', $home);
+ }
+
+ public function testCanGetWindowsHomeFolderStatically()
+ {
+ unset($_SERVER['HOME']);
+
+ $_SERVER['HOMEDRIVE'] = 'C:';
+ $_SERVER['HOMEPATH'] = '\Users\someuser';
+ $home = Pathfinder::getUserHomeFolder();
+
+ $this->assertEquals('C:\Users\someuser', $home);
+ }
+
+ public function testCancelsIfNoHomeFolder()
+ {
+ $this->setExpectedException('Exception');
+
+ $_SERVER['HOME'] = null;
+ $_SERVER['HOMEDRIVE'] = 'C:';
+ $_SERVER['HOMEPATH'] = null;
+ $this->paths->getUserHomeFolder();
+ }
+
+ public function testCanGetRocketeerFolder()
+ {
+ $_SERVER['HOME'] = '/some/folder';
+ $rocketeer = $this->paths->getRocketeerConfigFolder();
+
+ $this->assertEquals('/some/folder/.rocketeer', $rocketeer);
+ }
+
+ public function testCanGetBoundPath()
+ {
+ $this->swapConfig(array(
+ 'rocketeer::paths.php' => '/bin/php',
+ ));
+ $path = $this->paths->getPath('php');
+
+ $this->assertEquals('/bin/php', $path);
+ }
+
+ public function testCanGetStoragePathWhenNoneBound()
+ {
+ unset($this->app['path.storage']);
+
+ $storage = $this->paths->getStoragePath();
+ $this->assertEquals('.rocketeer', $storage);
+ }
+
+ public function testCanGetStoragePathIfUnix()
+ {
+ $this->app['path.base'] = '/app';
+ $this->app['path.storage'] = '/app/local/folder';
+
+ $storage = $this->paths->getStoragePath();
+ $this->assertEquals('local/folder', $storage);
+ }
+
+ public function testCanGetStorageIfWindows()
+ {
+ $this->app['path.base'] = 'C:\Sites\app';
+ $this->app['path.storage'] = 'C:\Sites\app\local\folder';
+
+ $storage = $this->paths->getStoragePath();
+ $this->assertEquals('local/folder', $storage);
+ }
+
+ public function testCanGetStorageWhenBothForSomeReason()
+ {
+ $this->app['path.base'] = 'C:\Sites\app';
+ $this->app['path.storage'] = 'C:/Sites/app/local/folder';
+
+ $storage = $this->paths->getStoragePath();
+ $this->assertEquals('local/folder', $storage);
+ }
+}
diff --git a/tests/Services/ReleasesManagerTest.php b/tests/Services/ReleasesManagerTest.php
new file mode 100644
index 000000000..37f2444fc
--- /dev/null
+++ b/tests/Services/ReleasesManagerTest.php
@@ -0,0 +1,202 @@
+releasesManager->getCurrentRelease();
+
+ $this->assertEquals(20000000000000, $currentRelease);
+ }
+
+ public function testCanGetStateOfReleases()
+ {
+ $validation = $this->releasesManager->getValidationFile();
+
+ $this->assertEquals(array(
+ 10000000000000 => true,
+ 15000000000000 => false,
+ 20000000000000 => true,
+ ), $validation);
+ }
+
+ public function testCanGetInvalidReleases()
+ {
+ $validation = $this->releasesManager->getInvalidReleases();
+
+ $this->assertEquals([1 => 15000000000000], $validation);
+ }
+
+ public function testCanUpdateStateOfReleases()
+ {
+ $this->releasesManager->markReleaseAsValid(15000000000000);
+ $validation = $this->releasesManager->getValidationFile();
+
+ $this->assertEquals(array(
+ 10000000000000 => true,
+ 15000000000000 => true,
+ 20000000000000 => true,
+ ), $validation);
+ }
+
+ public function testCanMarkReleaseAsValid()
+ {
+ $this->releasesManager->markReleaseAsValid(123456789);
+ $validation = $this->releasesManager->getValidationFile();
+
+ $this->assertEquals(array(
+ 10000000000000 => true,
+ 15000000000000 => false,
+ 20000000000000 => true,
+ 123456789 => true,
+ ), $validation);
+ }
+
+ public function testCanGetCurrentReleaseFromServerIfUncached()
+ {
+ $this->mock('rocketeer.storage.local', 'LocalStorage', function ($mock) {
+ return $mock
+ ->shouldReceive('getSeparator')->andReturn('/')
+ ->shouldReceive('getLineEndings')->andReturn(PHP_EOL);
+ });
+
+ $currentRelease = $this->releasesManager->getCurrentRelease();
+
+ $this->assertEquals(20000000000000, $currentRelease);
+ }
+
+ public function testCanGetReleasesPath()
+ {
+ $releasePath = $this->releasesManager->getReleasesPath();
+
+ $this->assertEquals($this->server.'/releases', $releasePath);
+ }
+
+ public function testCanGetCurrentReleaseFolder()
+ {
+ $currentReleasePath = $this->releasesManager->getCurrentReleasePath();
+
+ $this->assertEquals($this->server.'/releases/20000000000000', $currentReleasePath);
+ }
+
+ public function testCanGetReleases()
+ {
+ $releases = $this->releasesManager->getReleases();
+
+ $this->assertEquals([1 => 15000000000000, 0 => 20000000000000, 2 => 10000000000000], $releases);
+ }
+
+ public function testCanGetDeprecatedReleases()
+ {
+ $releases = $this->releasesManager->getDeprecatedReleases();
+
+ $this->assertEquals([15000000000000, 10000000000000], $releases);
+ }
+
+ public function testCanGetPreviousValidRelease()
+ {
+ $currentRelease = $this->releasesManager->getPreviousRelease();
+
+ $this->assertEquals(10000000000000, $currentRelease);
+ }
+
+ public function testReturnsCurrentReleaseIfNoPreviousValidRelease()
+ {
+ $this->mockState(array(
+ '10000000000000' => false,
+ '15000000000000' => false,
+ '20000000000000' => true,
+ ));
+
+ $currentRelease = $this->releasesManager->getPreviousRelease();
+
+ $this->assertEquals(20000000000000, $currentRelease);
+ }
+
+ public function testReturnsCurrentReleaseIfOnlyRelease()
+ {
+ $this->mockState(array(
+ '20000000000000' => true,
+ ));
+
+ $currentRelease = $this->releasesManager->getPreviousRelease();
+
+ $this->assertEquals(20000000000000, $currentRelease);
+ }
+
+ public function testReturnsCorrectPreviousReleaseIfUpdatedBeforehand()
+ {
+ $this->mockState(array(
+ '20000000000000' => true,
+ ));
+
+ $previous = $this->releasesManager->getPreviousRelease();
+
+ $this->assertEquals(20000000000000, $previous);
+ }
+
+ public function testCanReturnPreviousReleaseIfNoReleases()
+ {
+ $this->mock('rocketeer.bash', 'Rocketeer\Bash', function ($mock) {
+ return $mock
+ ->shouldReceive('getFile')->times(1)
+ ->shouldReceive('listContents')->once()->with($this->server.'/releases')->andReturn([]);
+ });
+
+ $this->mockState(array());
+
+ $previous = $this->releasesManager->getPreviousRelease();
+ $this->assertNull($previous);
+ }
+
+ public function testCanGetFolderInRelease()
+ {
+ $folder = $this->releasesManager->getCurrentReleasePath('{path.storage}');
+
+ $this->assertEquals($this->server.'/releases/20000000000000/app/storage', $folder);
+ }
+
+ public function testDoesntPingForReleasesAllTheFuckingTime()
+ {
+ $this->mock('rocketeer.bash', 'Rocketeer\Bash', function ($mock) {
+ return $mock
+ ->shouldReceive('getFile')->times(1)
+ ->shouldReceive('listContents')->once()->with($this->server.'/releases')->andReturn([20000000000000]);
+ });
+
+ $this->releasesManager->getNonCurrentReleases();
+ $this->releasesManager->getNonCurrentReleases();
+ $this->releasesManager->getNonCurrentReleases();
+ $this->releasesManager->getNonCurrentReleases();
+ }
+
+ public function testDoesntPingForReleasesIfNoReleases()
+ {
+ $this->mock('rocketeer.bash', 'Rocketeer\Bash', function ($mock) {
+ return $mock
+ ->shouldReceive('getFile')->times(1)
+ ->shouldReceive('listContents')->once()->with($this->server.'/releases')->andReturn([]);
+ });
+
+ $this->releasesManager->getNonCurrentReleases();
+ $this->releasesManager->getNonCurrentReleases();
+ $this->releasesManager->getNonCurrentReleases();
+ $this->releasesManager->getNonCurrentReleases();
+ }
+
+ public function testIgnoresErrorsAndStuffWhenFetchingReleases()
+ {
+ $this->mock('rocketeer.bash', 'Rocketeer\Bash', function ($mock) {
+ return $mock
+ ->shouldReceive('getFile')->times(1)
+ ->shouldReceive('listContents')->times(1)->with($this->server.'/releases')->andReturn(['IMPOSSIBLE BECAUSE NOPE FUCK YOU']);
+ });
+
+ $releases = $this->releasesManager->getReleases();
+
+ $this->assertEmpty($releases);
+ }
+}
diff --git a/tests/Services/Storages/LocalStorageTest.php b/tests/Services/Storages/LocalStorageTest.php
new file mode 100644
index 000000000..d41bd1f65
--- /dev/null
+++ b/tests/Services/Storages/LocalStorageTest.php
@@ -0,0 +1,73 @@
+localStorage->getFilepath();
+ $this->localStorage->destroy();
+
+ $this->assertFileNotExists($file);
+ }
+
+ public function testCanCreateDeploymentsFileAnywhere()
+ {
+ $this->app['path.storage'] = null;
+ $this->app->offsetUnset('path.storage');
+
+ new LocalStorage($this->app);
+
+ $storage = $this->paths->getRocketeerConfigFolder();
+ $exists = file_exists($storage);
+ $this->files->deleteDirectory($storage);
+ $this->assertTrue($exists);
+ }
+
+ public function testCanGetLineEndings()
+ {
+ $this->localStorage->destroy();
+
+ $this->assertEquals(PHP_EOL, $this->localStorage->getLineEndings());
+ }
+
+ public function testCanGetSeparators()
+ {
+ $this->localStorage->destroy();
+
+ $this->assertEquals(DIRECTORY_SEPARATOR, $this->localStorage->getSeparator());
+ }
+
+ public function testCanComputeHashAccordingToContentsOfFiles()
+ {
+ $this->mockFiles(function ($mock) {
+ return $mock
+ ->shouldReceive('put')->once()
+ ->shouldReceive('exists')->twice()->andReturn(false)
+ ->shouldReceive('glob')->once()->andReturn(['foo', 'bar'])
+ ->shouldReceive('getRequire')->once()->with('foo')->andReturn(['foo'])
+ ->shouldReceive('getRequire')->once()->with('bar')->andReturn(['bar']);
+ });
+
+ $storage = new LocalStorage($this->app, 'deployments', $this->server);
+ $hash = $storage->getHash();
+
+ $this->assertEquals(md5('["foo"]["bar"]'), $hash);
+ }
+
+ public function testCanSwitchFolder()
+ {
+ $storage = new LocalStorage($this->app, 'foo', '/foo');
+ $storage->setFolder($this->server);
+ $file = $storage->getFilepath();
+
+ $this->assertEquals($this->server, $storage->getFolder());
+ $this->assertEquals($this->server.'/foo.json', $file);
+ }
+}
diff --git a/tests/Services/Storages/ServerStorageTest.php b/tests/Services/Storages/ServerStorageTest.php
new file mode 100644
index 000000000..87f6beda8
--- /dev/null
+++ b/tests/Services/Storages/ServerStorageTest.php
@@ -0,0 +1,16 @@
+app, 'test');
+ $file = $server->getFilepath();
+ $server->destroy();
+
+ $this->assertFileNotExists($file);
+ }
+}
diff --git a/tests/Services/Tasks/JobTest.php b/tests/Services/Tasks/JobTest.php
new file mode 100644
index 000000000..adb886efb
--- /dev/null
+++ b/tests/Services/Tasks/JobTest.php
@@ -0,0 +1,25 @@
+swapConfig(['rocketeer::default' => ['production', 'staging']]);
+
+ $pipeline = $this->queue->buildPipeline(['ls']);
+
+ $this->assertInstanceOf('Illuminate\Support\Collection', $pipeline);
+ $this->assertCount(2, $pipeline);
+ $this->assertInstanceOf('Rocketeer\Services\Tasks\Job', $pipeline[0]);
+ $this->assertInstanceOf('Rocketeer\Services\Tasks\Job', $pipeline[1]);
+
+ $this->assertEquals(['ls'], $pipeline[0]->queue);
+ $this->assertEquals(['ls'], $pipeline[1]->queue);
+
+ $this->assertEquals('production', $pipeline[0]->connection);
+ $this->assertEquals('staging', $pipeline[1]->connection);
+ }
+}
diff --git a/tests/Services/Tasks/TasksBuilderTest.php b/tests/Services/Tasks/TasksBuilderTest.php
new file mode 100644
index 000000000..3d45f97d3
--- /dev/null
+++ b/tests/Services/Tasks/TasksBuilderTest.php
@@ -0,0 +1,88 @@
+builder->buildTaskFromClass('Rocketeer\Tasks\Deploy');
+
+ $this->assertInstanceOf('Rocketeer\Abstracts\AbstractTask', $task);
+ }
+
+ public function testCanBuildCustomTaskByName()
+ {
+ $tasks = $this->builder->buildTasks(['Rocketeer\Tasks\Check']);
+
+ $this->assertInstanceOf('Rocketeer\Tasks\Check', $tasks[0]);
+ }
+
+ public function testCanBuildTaskFromString()
+ {
+ $string = 'echo "I love ducks"';
+
+ $string = $this->builder->buildTaskFromString($string);
+ $this->assertInstanceOf('Rocketeer\Tasks\Closure', $string);
+
+ $closure = $string->getClosure();
+ $this->assertInstanceOf('Closure', $closure);
+
+ $closureReflection = new ReflectionFunction($closure);
+ $this->assertEquals(array('stringTask' => 'echo "I love ducks"'), $closureReflection->getStaticVariables());
+
+ $this->assertEquals('I love ducks', $string->execute());
+ }
+
+ public function testCanBuildTaskFromClosure()
+ {
+ $originalClosure = function ($task) {
+ return $task->getCommand()->info('echo "I love ducks"');
+ };
+
+ $closure = $this->builder->buildTaskFromClosure($originalClosure);
+ $this->assertInstanceOf('Rocketeer\Tasks\Closure', $closure);
+ $this->assertEquals($originalClosure, $closure->getClosure());
+ }
+
+ public function testCanBuildTasks()
+ {
+ $queue = array(
+ 'foobar',
+ function () {
+ return 'lol';
+ },
+ 'Rocketeer\Tasks\Deploy',
+ );
+
+ $queue = $this->builder->buildTasks($queue);
+
+ $this->assertInstanceOf('Rocketeer\Tasks\Closure', $queue[0]);
+ $this->assertInstanceOf('Rocketeer\Tasks\Closure', $queue[1]);
+ $this->assertInstanceOf('Rocketeer\Tasks\Deploy', $queue[2]);
+ }
+
+ public function testThrowsExceptionOnUnbuildableTask()
+ {
+ $this->setExpectedException('Rocketeer\Exceptions\TaskCompositionException');
+
+ $this->builder->buildTaskFromClass('Nope');
+ }
+
+ public function testCanCreateCommandOfTask()
+ {
+ $command = $this->builder->buildCommand('Rocketeer', '');
+ $this->assertInstanceOf('Rocketeer\Console\Commands\RocketeerCommand', $command);
+ $this->assertEquals('deploy', $command->getName());
+
+ $command = $this->builder->buildCommand('Deploy', 'lol');
+ $this->assertInstanceOf('Rocketeer\Console\Commands\DeployCommand', $command);
+ $this->assertEquals('deploy:deploy', $command->getName());
+
+ $command = $this->builder->buildCommand('ls', 'ls');
+ $this->assertInstanceOf('Rocketeer\Console\Commands\BaseTaskCommand', $command);
+ $this->assertEquals('deploy:ls', $command->getName());
+ }
+}
diff --git a/tests/Services/Tasks/TasksQueueTest.php b/tests/Services/Tasks/TasksQueueTest.php
new file mode 100644
index 000000000..e0d5fd659
--- /dev/null
+++ b/tests/Services/Tasks/TasksQueueTest.php
@@ -0,0 +1,141 @@
+swapConfig(array(
+ 'rocketeer::default' => 'production',
+ ));
+
+ $this->expectOutputString('JOEY DOESNT SHARE FOOD');
+ $this->queue->run(array(
+ function () {
+ print 'JOEY DOESNT SHARE FOOD';
+ },
+ ), $this->getCommand());
+ }
+
+ public function testCanRunQueueOnDifferentConnectionsAndStages()
+ {
+ $this->swapConfig(array(
+ 'rocketeer::default' => ['staging', 'production'],
+ 'rocketeer::stages.stages' => ['first', 'second'],
+ ));
+
+ $output = array();
+ $queue = array(
+ function ($task) use (&$output) {
+ $output[] = $task->connections->getConnection().' - '.$task->connections->getStage();
+ },
+ );
+
+ $pipeline = $this->queue->run($queue);
+
+ $this->assertTrue($pipeline->succeeded());
+ $this->assertEquals(array(
+ 'staging - first',
+ 'staging - second',
+ 'production - first',
+ 'production - second',
+ ), $output);
+ }
+
+ public function testCanRunQueueViaExecute()
+ {
+ $this->swapConfig(array(
+ 'rocketeer::default' => 'production',
+ ));
+
+ $pipeline = $this->queue->run(array(
+ 'ls -a',
+ function () {
+ return 'JOEY DOESNT SHARE FOOD';
+ },
+ ));
+
+ $output = array_slice($this->history->getFlattenedOutput(), 2, 3);
+ $this->assertTrue($pipeline->succeeded());
+ $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'),
+ ));
+
+ $this->queue->on(array('staging', 'production'), function ($task) {
+ return $task->connections->getConnection().' - '.$task->connections->getStage();
+ });
+
+ $this->assertEquals(array(
+ 'staging - first',
+ 'staging - second',
+ 'production - first',
+ 'production - second',
+ ), $this->history->getFlattenedOutput());
+ }
+
+ public function testCanRunTasksInParallel()
+ {
+ $parallel = Mockery::mock('Parallel')
+ ->shouldReceive('isSupported')->andReturn(true)
+ ->shouldReceive('values')->once()->with(Mockery::type('array'))
+ ->mock();
+
+ $this->mockCommand(['parallel' => true]);
+ $this->queue->setParallel($parallel);
+
+ $task = function () {
+ sleep(1);
+
+ return time();
+ };
+
+ $this->queue->execute(array(
+ $task,
+ $task,
+ ));
+ }
+
+ public function testCanCancelQueueIfTaskFails()
+ {
+ $this->expectOutputString('The tasks queue was canceled by task "MyCustomHaltingTask"');
+
+ $this->mockCommand([], array(
+ 'error' => function ($error) {
+ echo $error;
+ },
+ ));
+
+ $pipeline = $this->queue->run(array(
+ 'Rocketeer\Dummies\MyCustomHaltingTask',
+ 'Rocketeer\Dummies\MyCustomTask',
+ ));
+
+ $this->assertTrue($pipeline->failed());
+ $this->assertEquals([false], $this->history->getFlattenedOutput());
+ }
+
+ public function testFallbacksToSynchonousIfErrorWhenRunningParallels()
+ {
+ $parallel = Mockery::mock('Parallel')
+ ->shouldReceive('isSupported')->andReturn(true)
+ ->shouldReceive('values')->once()->andThrow('LogicException')
+ ->mock();
+
+ $this->mockCommand(['parallel' => true]);
+ $this->queue->setParallel($parallel);
+
+ $this->queue->run(['ls']);
+ }
+}
diff --git a/tests/Services/TasksHandlerTest.php b/tests/Services/TasksHandlerTest.php
new file mode 100644
index 000000000..5e06bd145
--- /dev/null
+++ b/tests/Services/TasksHandlerTest.php
@@ -0,0 +1,196 @@
+tasks->add('Rocketeer\Tasks\Deploy');
+ $this->assertInstanceOf('Rocketeer\Console\Commands\BaseTaskCommand', $command);
+ $this->assertInstanceOf('Rocketeer\Tasks\Deploy', $command->getTask());
+ }
+
+ public function testCanGetTasksBeforeOrAfterAnotherTask()
+ {
+ $task = $this->task('Deploy');
+ $before = $this->tasks->getTasksListeners($task, 'before', true);
+
+ $this->assertEquals(['before', 'foobar'], $before);
+ }
+
+ public function testCanAddTasksViaFacade()
+ {
+ $task = $this->task('Deploy');
+ $before = $this->tasks->getTasksListeners($task, 'before', true);
+
+ $this->tasks->before('deploy', 'composer install');
+
+ $newBefore = array_merge($before, array('composer install'));
+ $this->assertEquals($newBefore, $this->tasks->getTasksListeners($task, 'before', true));
+ }
+
+ public function testCanAddMultipleTasksViaFacade()
+ {
+ $task = $this->task('Deploy');
+ $after = $this->tasks->getTasksListeners($task, 'after', true);
+ $this->tasks->after('deploy', array(
+ 'composer install',
+ 'bower install',
+ ));
+
+ $newAfter = array_merge($after, array('composer install', 'bower install'));
+ $this->assertEquals($newAfter, $this->tasks->getTasksListeners($task, 'after', true));
+ }
+
+ public function testCanRegisterCustomTask()
+ {
+ $this->swapConfig(array(
+ 'rocketeer::default' => 'production',
+ ));
+
+ $this->tasks->task('foobar', function ($task) {
+ $task->runForCurrentRelease('ls');
+ });
+
+ $this->assertInstanceOf('Rocketeer\Tasks\Closure', $this->builder->buildTask('foobar'));
+
+ $this->queue->run('foobar');
+ $this->assertHistory([['cd {server}/releases/{release}', 'ls']]);
+ }
+
+ public function testCanRegisterCustomTaskViaArray()
+ {
+ $this->swapConfig(array(
+ 'rocketeer::default' => 'production',
+ ));
+
+ $this->tasks->task('foobar', ['ls', 'ls']);
+ $this->assertInstanceOf('Rocketeer\Tasks\Closure', $this->builder->buildTask('foobar'));
+
+ $this->queue->run('foobar');
+ $this->assertHistory([['cd {server}/releases/{release}', 'ls', 'ls']]);
+ }
+
+ public function testCanAddSurroundTasksToNonExistingTasks()
+ {
+ $task = $this->task('Setup');
+ $this->tasks->after('setup', 'composer install');
+
+ $after = array('composer install');
+ $this->assertEquals($after, $this->tasks->getTasksListeners($task, 'after', true));
+ }
+
+ public function testCanAddSurroundTasksToMultipleTasks()
+ {
+ $this->tasks->after(array('cleanup', 'setup'), 'composer install');
+
+ $after = array('composer install');
+ $this->assertEquals($after, $this->tasks->getTasksListeners('setup', 'after', true));
+ $this->assertEquals($after, $this->tasks->getTasksListeners('cleanup', 'after', true));
+ }
+
+ public function testCangetTasksListenersOrAfterAnotherTaskBySlug()
+ {
+ $after = $this->tasks->getTasksListeners('deploy', 'after', true);
+
+ $this->assertEquals(array('after', 'foobar'), $after);
+ }
+
+ public function testCanAddEventsWithPriority()
+ {
+ $this->tasks->before('deploy', 'second', -5);
+ $this->tasks->before('deploy', 'first');
+
+ $listeners = $this->tasks->getTasksListeners('deploy', 'before', true);
+ $this->assertEquals(['before', 'foobar', 'first', 'second'], $listeners);
+ }
+
+ public function testCanExecuteContextualEvents()
+ {
+ $this->swapConfig(array(
+ 'rocketeer::stages.stages' => array('hasEvent', 'noEvent'),
+ 'rocketeer::on.stages.hasEvent.hooks' => array('before' => array('check' => 'ls')),
+ ));
+
+ $this->connections->setStage('hasEvent');
+ $this->assertEquals(['ls'], $this->tasks->getTasksListeners('check', 'before', true));
+
+ $this->connections->setStage('noEvent');
+ $this->assertEquals([], $this->tasks->getTasksListeners('check', 'before', true));
+ }
+
+ public function testCanbuildTasksFromConfigHook()
+ {
+ $tasks = array(
+ 'npm install',
+ 'bower install',
+ );
+
+ $this->swapConfig(array(
+ 'rocketeer::hooks' => ['after' => ['deploy' => $tasks]],
+ ));
+
+ $this->tasks->registerConfiguredEvents();
+ $listeners = $this->tasks->getTasksListeners('deploy', 'after', true);
+
+ $this->assertEquals($tasks, $listeners);
+ }
+
+ public function testCanHaveCustomConnectionHooks()
+ {
+ $tasks = array(
+ 'npm install',
+ 'bower install',
+ );
+
+ $this->swapConfig(array(
+ 'rocketeer::default' => 'production',
+ 'rocketeer::hooks' => [],
+ 'rocketeer::on.connections.staging.hooks' => ['after' => ['deploy' => $tasks]],
+ ));
+ $this->tasks->registerConfiguredEvents();
+
+ $this->connections->setConnection('production');
+ $events = $this->tasks->getTasksListeners('deploy', 'after', true);
+ $this->assertEmpty($events);
+
+ $this->connections->setConnection('staging');
+ $events = $this->tasks->getTasksListeners('deploy', 'after', true);
+
+ $this->assertEquals($tasks, $events);
+ }
+
+ public function testPluginsArentDeregisteredWhenSwitchingConnection()
+ {
+ $this->swapConfig(array(
+ 'rocketeer::hooks' => ['before' => ['deploy' => 'ls']],
+ ));
+
+ $this->tasks->plugin(new DummyNotifier($this->app));
+
+ $listeners = $this->tasks->getTasksListeners('deploy', 'before', true);
+ $this->assertEquals(['ls', 'notify'], $listeners);
+
+ $this->connections->setConnection('production');
+
+ $listeners = $this->tasks->getTasksListeners('deploy', 'before', true);
+ $this->assertEquals(['ls', 'notify'], $listeners);
+ }
+
+ public function testDoesntRegisterPluginsTwice()
+ {
+ $this->swapConfig(array(
+ 'rocketeer::hooks' => [],
+ ));
+
+ $this->tasks->plugin(new DummyNotifier($this->app));
+ $this->tasks->plugin(new DummyNotifier($this->app));
+ $this->tasks->plugin(new DummyNotifier($this->app));
+
+ $listeners = $this->tasks->getTasksListeners('deploy', 'before', true);
+ $this->assertEquals(['notify'], $listeners);
+ }
+}
diff --git a/tests/Strategies/Check/NodeStrategyTest.php b/tests/Strategies/Check/NodeStrategyTest.php
new file mode 100644
index 000000000..cfe47578d
--- /dev/null
+++ b/tests/Strategies/Check/NodeStrategyTest.php
@@ -0,0 +1,36 @@
+strategy = $this->builder->buildStrategy('Check', 'Node');
+ }
+
+ public function testCanParseLanguageConstraint()
+ {
+ $manager = Mockery::mock('Npm', array(
+ 'getBinary' => 'npm',
+ 'getManifestContents' => json_encode(['engines' => ['node' => '0.10.30']]),
+ ));
+ $this->strategy->setManager($manager);
+
+ $this->mockRemote('0.8.0');
+
+ $this->assertFalse($this->strategy->language());
+
+ $this->mockRemote('0.11.0');
+ $this->assertTrue($this->strategy->language());
+ }
+}
diff --git a/tests/Strategies/Check/PhpStrategyTest.php b/tests/Strategies/Check/PhpStrategyTest.php
new file mode 100644
index 000000000..b5a22e951
--- /dev/null
+++ b/tests/Strategies/Check/PhpStrategyTest.php
@@ -0,0 +1,62 @@
+strategy = $this->builder->buildStrategy('Check', 'Php');
+ }
+
+ public function testCanCheckPhpVersion()
+ {
+ $this->mockFiles(function ($mock) {
+ return $mock
+ ->shouldReceive('put')
+ ->shouldReceive('glob')->andReturn(array())
+ ->shouldReceive('exists')->andReturn(true)
+ ->shouldReceive('get')->andReturn('{"require":{"php":">=5.3.0"}}');
+ });
+ $this->assertTrue($this->strategy->language());
+
+ // This is is going to come bite me in the ass in 10 years
+ $this->mockFiles(function ($mock) {
+ return $mock
+ ->shouldReceive('put')
+ ->shouldReceive('glob')->andReturn(array())
+ ->shouldReceive('exists')->andReturn(true)
+ ->shouldReceive('get')->andReturn('{"require":{"php":">=5.9.0"}}');
+ });
+ $this->assertFalse($this->strategy->language());
+ }
+
+ public function testCanCheckPhpExtensions()
+ {
+ $this->swapConfig(array(
+ 'database.default' => 'sqlite',
+ 'cache.driver' => 'redis',
+ 'session.driver' => 'apc',
+ ));
+
+ $this->strategy->extensions();
+
+ $this->assertHistory(['{php} -m']);
+ }
+
+ public function testCanCheckForHhvmExtensions()
+ {
+ $this->mockRemote('HipHop VM 3.0.1 (rel)'.PHP_EOL.'Some more stuff');
+ $exists = $this->strategy->checkPhpExtension('_hhvm');
+
+ $this->assertTrue($exists);
+ }
+}
diff --git a/tests/Strategies/Check/RubyStrategyTest.php b/tests/Strategies/Check/RubyStrategyTest.php
new file mode 100644
index 000000000..7562bbca8
--- /dev/null
+++ b/tests/Strategies/Check/RubyStrategyTest.php
@@ -0,0 +1,35 @@
+strategy = $this->builder->buildStrategy('Check', 'Ruby');
+ }
+
+ public function testCanParseLanguageConstraint()
+ {
+ $manager = Mockery::mock('Bundler', array(
+ 'getBinary' => 'bundle',
+ 'getManifestContents' => '# Some comments'.PHP_EOL."ruby '2.0.0'",
+ ));
+ $this->strategy->setManager($manager);
+
+ $this->mockRemote('1.9.3');
+ $this->assertFalse($this->strategy->language());
+
+ $this->mockRemote('2.1.0');
+ $this->assertTrue($this->strategy->language());
+ }
+}
diff --git a/tests/Strategies/Dependencies/BowerStrategyTest.php b/tests/Strategies/Dependencies/BowerStrategyTest.php
new file mode 100644
index 000000000..63c02e3b5
--- /dev/null
+++ b/tests/Strategies/Dependencies/BowerStrategyTest.php
@@ -0,0 +1,67 @@
+app);
+ $bower->setBinary('bower');
+
+ $this->bower = $this->builder->buildStrategy('Dependencies', 'Bower');
+ $this->bower->setManager($bower);
+ }
+
+ public function testCanInstallDependencies()
+ {
+ $this->pretend();
+ $this->bower->install();
+
+ $this->assertHistory(array(
+ array(
+ 'cd {server}/releases/{release}',
+ 'bower install',
+ ),
+ ));
+ }
+
+ public function testCanUpdateDependencies()
+ {
+ $this->pretend();
+ $this->bower->update();
+
+ $this->assertHistory(array(
+ array(
+ 'cd {server}/releases/{release}',
+ 'bower update',
+ ),
+ ));
+ }
+
+ public function testUsesAllowRootIfRoot()
+ {
+ $this->mock('rocketeer.connections', 'Connections', function ($mock) {
+ return $mock->shouldReceive('getServerCredentials')->andReturn(['username' => 'root']);
+ });
+
+ $this->pretend();
+ $this->bower->install();
+
+ $this->assertHistory(array(
+ array(
+ 'cd {server}/releases/{release}',
+ 'bower install --allow-root',
+ ),
+ ));
+ }
+}
diff --git a/tests/Strategies/Dependencies/BundlerStrategyTest.php b/tests/Strategies/Dependencies/BundlerStrategyTest.php
new file mode 100644
index 000000000..5ee7e6303
--- /dev/null
+++ b/tests/Strategies/Dependencies/BundlerStrategyTest.php
@@ -0,0 +1,47 @@
+app);
+ $bundler->setBinary('bundle');
+
+ $this->bundler = $this->builder->buildStrategy('Dependencies', 'Bundler');
+ $this->bundler->setManager($bundler);
+ }
+
+ public function testCanInstallDependencies()
+ {
+ $this->pretend();
+ $this->bundler->install();
+
+ $this->assertHistory(array(
+ array(
+ 'cd {server}/releases/{release}',
+ 'bundle install',
+ ),
+ ));
+ }
+
+ public function testCanUpdateDependencies()
+ {
+ $this->pretend();
+ $this->bundler->update();
+
+ $this->assertHistory(array(
+ array(
+ 'cd {server}/releases/{release}',
+ 'bundle update',
+ ),
+ ));
+ }
+}
diff --git a/tests/Strategies/Dependencies/ComposerStrategyTest.php b/tests/Strategies/Dependencies/ComposerStrategyTest.php
new file mode 100644
index 000000000..0c8bb9949
--- /dev/null
+++ b/tests/Strategies/Dependencies/ComposerStrategyTest.php
@@ -0,0 +1,57 @@
+swapConfig(array(
+ 'rocketeer::scm' => array(
+ 'repository' => 'https://github.com/'.$this->repository,
+ 'username' => '',
+ 'password' => '',
+ ),
+ 'rocketeer::strategies.composer.install' => function ($composer, $task) {
+ return array(
+ $composer->selfUpdate(),
+ $composer->install([], '--prefer-source'),
+ );
+ },
+ ));
+
+ $this->pretendTask();
+ $composer = $this->builder->buildStrategy('Dependencies', 'Composer');
+ $composer->install();
+
+ $this->assertHistory(array(
+ array(
+ "cd {server}/releases/{release}",
+ "{composer} self-update",
+ "{composer} install --prefer-source",
+ ),
+ ));
+ }
+
+ public function testCancelsIfInvalidComposerRoutine()
+ {
+ $composer = $this->builder->buildStrategy('Dependencies', 'Composer');
+
+ $this->swapConfig(array(
+ 'rocketeer::strategies.composer.install' => 'lol',
+ ));
+
+ $composer->install();
+ $this->assertHistory([]);
+
+ $this->swapConfig(array(
+ 'rocketeer::strategies.composer.install' => function () {
+ return [];
+ },
+ ));
+
+ $composer->install();
+ $this->assertHistory([]);
+ }
+}
diff --git a/tests/Strategies/Dependencies/PolyglotStrategyTest.php b/tests/Strategies/Dependencies/PolyglotStrategyTest.php
new file mode 100644
index 000000000..755e418f1
--- /dev/null
+++ b/tests/Strategies/Dependencies/PolyglotStrategyTest.php
@@ -0,0 +1,27 @@
+usesComposer(true);
+ $this->files->put($this->server.'/current/Gemfile', '');
+
+ $polyglot = $this->builder->buildStrategy('Dependencies', 'Polyglot');
+ $polyglot->install();
+
+ $this->assertHistory(array(
+ array(
+ 'cd {server}/releases/{release}',
+ '{bundle} install',
+ ),
+ array(
+ 'cd {server}/releases/{release}',
+ '{composer} install --no-interaction --no-dev --prefer-dist',
+ ),
+ ));
+ }
+}
diff --git a/tests/Strategies/Deploy/CloneStrategyTest.php b/tests/Strategies/Deploy/CloneStrategyTest.php
new file mode 100644
index 000000000..3c3a0c377
--- /dev/null
+++ b/tests/Strategies/Deploy/CloneStrategyTest.php
@@ -0,0 +1,39 @@
+pretendTask('Deploy');
+ $task->getStrategy('Deploy')->deploy();
+
+ $matcher = array(
+ 'git clone "{repository}" "{server}/releases/{release}" --branch="master" --depth="1"',
+ array(
+ "cd {server}/releases/{release}",
+ "git submodule update --init --recursive",
+ ),
+ );
+
+ $this->assertHistory($matcher);
+ }
+
+ public function testCanUpdateRepository()
+ {
+ $task = $this->pretendTask('Deploy');
+ $task->getStrategy('Deploy')->update();
+
+ $matcher = array(
+ array(
+ "cd $this->server/releases/20000000000000",
+ "git reset --hard",
+ "git pull",
+ ),
+ );
+
+ $this->assertHistory($matcher);
+ }
+}
diff --git a/tests/Strategies/Deploy/CopyStrategyTest.php b/tests/Strategies/Deploy/CopyStrategyTest.php
new file mode 100644
index 000000000..396382035
--- /dev/null
+++ b/tests/Strategies/Deploy/CopyStrategyTest.php
@@ -0,0 +1,73 @@
+pretend();
+ }
+
+ public function testCanCopyPreviousRelease()
+ {
+ $this->builder->buildStrategy('Deploy', 'Copy')->deploy();
+
+ $matcher = array(
+ 'cp -r {server}/releases/10000000000000 {server}/releases/20000000000000',
+ array(
+ "cd {server}/releases/{release}",
+ "git reset --hard",
+ "git pull",
+ ),
+ );
+
+ $this->assertHistory($matcher);
+ }
+
+ public function testClonesIfNoPreviousRelease()
+ {
+ $this->mock('rocketeer.releases', 'ReleasesManager', function (MockInterface $mock) {
+ return $mock->shouldReceive('getReleases')->andReturn([])
+ ->shouldReceive('getCurrentReleasePath')->andReturn($this->server.'/releases/10000000000000');
+ });
+
+ $this->builder->buildStrategy('Deploy', 'Copy')->deploy();
+
+ $matcher = array(
+ 'git clone "{repository}" "{server}/releases/{release}" --branch="master" --depth="1"',
+ array(
+ "cd {server}/releases/{release}",
+ "git submodule update --init --recursive",
+ ),
+ );
+
+ $this->assertHistory($matcher);
+ }
+
+ public function testCanCloneIfPreviousReleaseIsInvalid()
+ {
+ $this->mock('rocketeer.releases', 'ReleasesManager', function (MockInterface $mock) {
+ return $mock->shouldReceive('getReleases')->andReturn([10000000000000])
+ ->shouldReceive('getPreviousRelease')->andReturn(null)
+ ->shouldReceive('getPathToRelease')->andReturn(null)
+ ->shouldReceive('getCurrentReleasePath')->andReturn($this->server.'/releases/10000000000000');
+ });
+
+ $this->builder->buildStrategy('Deploy', 'Copy')->deploy();
+
+ $matcher = array(
+ 'git clone "{repository}" "{server}/releases/{release}" --branch="master" --depth="1"',
+ array(
+ "cd {server}/releases/{release}",
+ "git submodule update --init --recursive",
+ ),
+ );
+
+ $this->assertHistory($matcher);
+ }
+}
diff --git a/tests/Strategies/Deploy/SyncStrategyTest.php b/tests/Strategies/Deploy/SyncStrategyTest.php
new file mode 100644
index 000000000..29140371a
--- /dev/null
+++ b/tests/Strategies/Deploy/SyncStrategyTest.php
@@ -0,0 +1,46 @@
+swapConfig(array(
+ 'rocketeer::connections' => array(
+ 'production' => array(
+ 'host' => 'bar.com',
+ 'username' => 'foo',
+ ),
+ ),
+ ));
+ }
+
+ public function testCanDeployRepository()
+ {
+ $task = $this->pretendTask('Deploy');
+ $task->getStrategy('Deploy', 'Sync')->deploy();
+
+ $matcher = array(
+ 'mkdir {server}/releases/{release}',
+ 'rsync ./ foo@bar.com:{server}/releases/{release} --verbose --recursive --rsh="ssh" --exclude=".git" --exclude="vendor"',
+ );
+
+ $this->assertHistory($matcher);
+ }
+
+ public function testCanUpdateRepository()
+ {
+ $task = $this->pretendTask('Deploy');
+ $task->getStrategy('Deploy', 'Sync')->update();
+
+ $matcher = array(
+ 'rsync ./ foo@bar.com:{server}/releases/{release} --verbose --recursive --rsh="ssh" --exclude=".git" --exclude="vendor"',
+ );
+
+ $this->assertHistory($matcher);
+ }
+}
diff --git a/tests/Strategies/Test/PhpunitStrategyTest.php b/tests/Strategies/Test/PhpunitStrategyTest.php
new file mode 100644
index 000000000..0c3b7f351
--- /dev/null
+++ b/tests/Strategies/Test/PhpunitStrategyTest.php
@@ -0,0 +1,20 @@
+pretendTask();
+ $this->builder->buildStrategy('Test', 'Phpunit')->test();
+
+ $this->assertHistory(array(
+ array(
+ 'cd {server}/releases/20000000000000',
+ '{phpunit} --stop-on-failure',
+ ),
+ ));
+ }
+}
diff --git a/tests/Tasks/CheckTest.php b/tests/Tasks/CheckTest.php
index b0fdbbce9..e79292366 100644
--- a/tests/Tasks/CheckTest.php
+++ b/tests/Tasks/CheckTest.php
@@ -5,7 +5,7 @@
class CheckTest extends RocketeerTestCase
{
- public function testCanDoBasicCheck()
+ public function testCanCheckScmVersionIfRequired()
{
$this->assertTaskHistory('Check', array(
'git --version',
@@ -13,40 +13,13 @@ public function testCanDoBasicCheck()
));
}
- public function testCanCheckPhpVersion()
- {
- $check = new Check($this->app);
-
- $this->mock('files', 'Filesystem', function ($mock) {
- return $mock
- ->shouldReceive('put')
- ->shouldReceive('glob')->andReturn(array())
- ->shouldReceive('exists')->andReturn(true)
- ->shouldReceive('get')->andReturn('{"require":{"php":">=5.3.0"}}');
- });
- $this->assertTrue($check->checkPhpVersion());
-
- // This is is going to come bite me in the ass in 10 years
- $this->mock('files', 'Filesystem', function ($mock) {
- return $mock
- ->shouldReceive('put')
- ->shouldReceive('glob')->andReturn(array())
- ->shouldReceive('exists')->andReturn(true)
- ->shouldReceive('get')->andReturn('{"require":{"php":">=5.9.0"}}');
- });
- $this->assertFalse($check->checkPhpVersion());
- }
-
- public function testCanCheckPhpExtensions()
+ public function testSkipsScmCheckIfNotRequired()
{
$this->swapConfig(array(
- 'database.default' => 'sqlite',
- 'cache.driver' => 'redis',
- 'session.driver' => 'apc',
+ 'rocketeer::strategies.deploy' => 'sync',
));
$this->assertTaskHistory('Check', array(
- 'git --version',
'{php} -m',
));
}
diff --git a/tests/Tasks/CleanupTest.php b/tests/Tasks/CleanupTest.php
index db541efb1..e0c8f201f 100644
--- a/tests/Tasks/CleanupTest.php
+++ b/tests/Tasks/CleanupTest.php
@@ -24,16 +24,39 @@ public function testCanPruneAllReleasesIfCleanAll()
return $mock
->shouldReceive('getDeprecatedReleases')->never()
->shouldReceive('getNonCurrentReleases')->once()->andReturn(array(1, 2))
+ ->shouldReceive('markReleaseAsValid')->once()
->shouldReceive('getPathToRelease')->times(2)->andReturnUsing(function ($release) {
return $release;
});
});
+ ob_start();
+
$this->assertTaskOutput('Cleanup', 'Removing 2 releases from the server', $this->getCommand(array(), array(
'clean-all' => true,
'verbose' => true,
'pretend' => false,
)));
+
+ ob_end_clean();
+ }
+
+ public function testCanRemoveAllReleasesAtOnce()
+ {
+ $this->mockReleases(function ($mock) {
+ return $mock
+ ->shouldReceive('getDeprecatedReleases')->never()
+ ->shouldReceive('getDeprecatedReleases')->once()->andReturn(array(1, 2))
+ ->shouldReceive('getPathToRelease')->times(2)->andReturnUsing(function ($release) {
+ return $release;
+ });
+ });
+
+ $this->pretendTask('Cleanup')->execute();
+
+ $this->assertHistory(array(
+ 'rm -rf {server}/1 {server}/2',
+ ));
}
public function testPrintsMessageIfNoCleanup()
diff --git a/tests/Tasks/ClosureTest.php b/tests/Tasks/ClosureTest.php
new file mode 100644
index 000000000..a912fa451
--- /dev/null
+++ b/tests/Tasks/ClosureTest.php
@@ -0,0 +1,16 @@
+builder->buildTask(['ls', 'ls'], 'FilesLister');
+
+ $this->assertEquals('FilesLister', $closure->getName());
+ $this->assertEquals('files-lister', $closure->getSlug());
+ $this->assertEquals('ls/ls', $closure->getDescription());
+ }
+}
diff --git a/tests/Tasks/DeployTest.php b/tests/Tasks/DeployTest.php
index 062703b1f..b56ec0bb4 100644
--- a/tests/Tasks/DeployTest.php
+++ b/tests/Tasks/DeployTest.php
@@ -1,6 +1,7 @@
assertTaskHistory('Deploy', $matcher, array(
'tests' => true,
'seed' => true,
- 'migrate' => true
+ 'migrate' => true,
));
}
- public function testCanDisableGitOptions()
+ public function testStepsRunnerDoesntCancelWithPermissionsAndShared()
{
$this->swapConfig(array(
- 'rocketeer::scm.shallow' => false,
- 'rocketeer::scm.submodules' => false,
- 'rocketeer::scm' => array(
- 'repository' => 'https://github.com/'.$this->repository,
- 'username' => '',
- 'password' => '',
- )
+ 'rocketeer::remote.shared' => [],
+ 'rocketeer::remote.permissions.files' => [],
));
$matcher = array(
- 'git clone -b master "https://github.com/Anahkiasen/html-object.git" {server}/releases/{release}',
+ 'git clone "{repository}" "{server}/releases/{release}" --branch="master" --depth="1"',
array(
"cd {server}/releases/{release}",
- exec('which phpunit')." --stop-on-failure "
+ "git submodule update --init --recursive",
),
array(
"cd {server}/releases/{release}",
- "chmod -R 755 {server}/releases/{release}/tests",
- "chmod -R g+s {server}/releases/{release}/tests",
- "chown -R www-data:www-data {server}/releases/{release}/tests"
+ "{phpunit} --stop-on-failure",
+ ),
+ array(
+ "cd {server}/releases/{release}",
+ "{php} artisan migrate",
),
array(
"cd {server}/releases/{release}",
- "{php} artisan migrate --seed"
+ "{php} artisan db:seed",
),
- "mkdir -p {server}/shared/tests",
- "mv {server}/releases/{release}/tests/Elements {server}/shared/tests/Elements",
"mv {server}/current {server}/releases/{release}",
"rm -rf {server}/current",
"ln -s {server}/releases/{release} {server}/current",
@@ -85,57 +83,72 @@ public function testCanDisableGitOptions()
$this->assertTaskHistory('Deploy', $matcher, array(
'tests' => true,
'seed' => true,
- 'migrate' => true
+ 'migrate' => true,
));
}
- public function testCanConfigureComposerCommands()
+ public function testCanDisableGitOptions()
{
$this->swapConfig(array(
- 'rocketeer::scm' => array(
+ 'rocketeer::scm.shallow' => false,
+ 'rocketeer::scm.submodules' => false,
+ 'rocketeer::scm' => array(
'repository' => 'https://github.com/'.$this->repository,
'username' => '',
'password' => '',
),
- 'rocketeer::remote.composer' => function ($task) {
- return array(
- $task->composer('self-update'),
- $task->composer('install --prefer-source'),
- );
- },
));
$matcher = array(
+ 'git clone "{repository}" "{server}/releases/{release}" --branch="master"',
+ array(
+ "cd {server}/releases/{release}",
+ '{phpunit} --stop-on-failure',
+ ),
array(
"cd {server}/releases/{release}",
- "{composer} self-update",
- "{composer} install --prefer-source",
+ "chmod -R 755 {server}/releases/{release}/tests",
+ "chmod -R g+s {server}/releases/{release}/tests",
+ "chown -R www-data:www-data {server}/releases/{release}/tests",
+ ),
+ array(
+ "cd {server}/releases/{release}",
+ "{php} artisan migrate",
),
+ array(
+ "cd {server}/releases/{release}",
+ "{php} artisan db:seed",
+ ),
+ "mv {server}/current {server}/releases/{release}",
+ "rm -rf {server}/current",
+ "ln -s {server}/releases/{release} {server}/current",
);
- $deploy = $this->pretendTask('Deploy');
- $deploy->runComposer(true);
-
- $this->assertTaskHistory($deploy->getHistory(), $matcher, array(
- 'tests' => false,
- 'seed' => false,
- 'migrate' => false
+ $this->assertTaskHistory('Deploy', $matcher, array(
+ 'tests' => true,
+ 'seed' => true,
+ 'migrate' => true,
));
}
public function testCanUseCopyStrategy()
{
$this->swapConfig(array(
- 'rocketeer::remote.strategy' => 'copy',
'rocketeer::scm' => array(
'repository' => 'https://github.com/'.$this->repository,
'username' => '',
'password' => '',
- )
+ ),
+ ));
+
+ $this->app['rocketeer.strategies.deploy'] = new CopyStrategy($this->app);
+
+ $this->mockState(array(
+ '10000000000000' => true,
));
$matcher = array(
- 'cp {server}/releases/10000000000000 {server}/releases/{release}',
+ 'cp -r {server}/releases/10000000000000 {server}/releases/{release}',
array(
'cd {server}/releases/{release}',
'git reset --hard',
@@ -145,10 +158,8 @@ public function testCanUseCopyStrategy()
"cd {server}/releases/{release}",
"chmod -R 755 {server}/releases/{release}/tests",
"chmod -R g+s {server}/releases/{release}/tests",
- "chown -R www-data:www-data {server}/releases/{release}/tests"
+ "chown -R www-data:www-data {server}/releases/{release}/tests",
),
- "mkdir -p {server}/shared/tests",
- "mv {server}/releases/{release}/tests/Elements {server}/shared/tests/Elements",
"mv {server}/current {server}/releases/{release}",
"rm -rf {server}/current",
"ln -s {server}/releases/{release} {server}/current",
@@ -157,30 +168,28 @@ public function testCanUseCopyStrategy()
$this->assertTaskHistory('Deploy', $matcher, array(
'tests' => false,
'seed' => false,
- 'migrate' => false
+ 'migrate' => false,
));
}
public function testCanRunDeployWithSeed()
{
$matcher = array(
- 'git clone --depth 1 -b master "" {server}/releases/{release}',
+ 'git clone "{repository}" "{server}/releases/{release}" --branch="master" --depth="1"',
array(
"cd {server}/releases/{release}",
- "git submodule update --init --recursive"
+ "git submodule update --init --recursive",
),
array(
"cd {server}/releases/{release}",
"chmod -R 755 {server}/releases/{release}/tests",
"chmod -R g+s {server}/releases/{release}/tests",
- "chown -R www-data:www-data {server}/releases/{release}/tests"
+ "chown -R www-data:www-data {server}/releases/{release}/tests",
),
array(
"cd {server}/releases/{release}",
- "{php} artisan db:seed"
+ "{php} artisan db:seed",
),
- "mkdir -p {server}/shared/tests",
- "mv {server}/releases/{release}/tests/Elements {server}/shared/tests/Elements",
"mv {server}/current {server}/releases/{release}",
"rm -rf {server}/current",
"ln -s {server}/releases/{release} {server}/current",
diff --git a/tests/Tasks/IgniteTest.php b/tests/Tasks/IgniteTest.php
index f53b00879..6f9850f2a 100644
--- a/tests/Tasks/IgniteTest.php
+++ b/tests/Tasks/IgniteTest.php
@@ -1,21 +1,59 @@
app['path']);
+ $this->app['path.base'] = 'E:\workspace\test';
+
+ $provider = new RocketeerServiceProvider($this->app);
+ $provider->bindPaths();
+
+ $this->mockFiles(function ($mock) {
+ return $mock
+ ->shouldReceive('files')->andReturn([])
+ ->shouldReceive('glob')->andReturn([])
+ ->shouldReceive('copyDirectory')->once()->with(realpath(__DIR__.'/../../src/config'), 'E:/workspace/test/.rocketeer');
+ });
+
+ $this->pretendTask('Ignite')->execute();
+ }
+
+ public function testCanIgniteConfigurationOnWindowsInLaravel()
+ {
+ $this->app['path.base'] = 'E:\workspace\test';
+ $this->app['path'] = 'E:\workspace\test\app';
+
+ $provider = new RocketeerServiceProvider($this->app);
+ $provider->bindPaths();
+
+ $this->mockFiles(function ($mock) {
+ return $mock
+ ->shouldReceive('exists')->andReturn(true)
+ ->shouldReceive('files')->andReturn([])
+ ->shouldReceive('glob')->andReturn([])
+ ->shouldReceive('copyDirectory')->once()->with(realpath(__DIR__.'/../../src/config'), 'E:/workspace/test/app/config/packages/anahkiasen/rocketeer');
+ });
+
+ $this->pretendTask('Ignite')->execute();
+ }
+
public function testCanIgniteConfigurationOutsideLaravel()
{
$command = $this->getCommand(array('ask' => 'foobar'));
$server = $this->server;
- $this->mock('rocketeer.igniter', 'Igniter', function ($mock) use ($server) {
+ $this->mock('rocketeer.igniter', 'Configuration', function ($mock) use ($server) {
return $mock
- ->shouldReceive('getConfigurationPath')->twice()
->shouldReceive('exportConfiguration')->once()->andReturn($server)
->shouldReceive('updateConfiguration')->once()->with($server, array(
- 'scm_repository' => '',
+ 'connection' => 'production',
+ 'scm_repository' => 'https://github.com/'.$this->repository,
'scm_username' => '',
'scm_password' => '',
'application_name' => 'foobar',
@@ -31,12 +69,12 @@ public function testCanIgniteConfigurationInLaravel()
$command->shouldReceive('call')->with('config:publish', array('package' => 'anahkiasen/rocketeer'))->andReturn('foobar');
$path = $this->app['path'].'/config/packages/anahkiasen/rocketeer';
- $this->mock('rocketeer.igniter', 'Igniter', function ($mock) use ($path) {
+ $this->mock('rocketeer.igniter', 'Configuration', function ($mock) use ($path) {
return $mock
- ->shouldReceive('getConfigurationPath')->twice()
->shouldReceive('exportConfiguration')->never()
->shouldReceive('updateConfiguration')->once()->with($path, array(
- 'scm_repository' => '',
+ 'connection' => 'production',
+ 'scm_repository' => 'https://github.com/'.$this->repository,
'scm_username' => '',
'scm_password' => '',
'application_name' => '',
diff --git a/tests/Tasks/RollbackTest.php b/tests/Tasks/RollbackTest.php
index 857b268d4..5846cd5e7 100644
--- a/tests/Tasks/RollbackTest.php
+++ b/tests/Tasks/RollbackTest.php
@@ -9,6 +9,36 @@ public function testCanRollbackRelease()
{
$this->task('Rollback')->execute();
- $this->assertEquals(10000000000000, $this->app['rocketeer.releases']->getCurrentRelease());
+ $this->assertEquals(10000000000000, $this->releasesManager->getCurrentRelease());
+ }
+
+ public function testCanRollbackToSpecificRelease()
+ {
+ $this->mockCommand([], ['argument' => 15000000000000]);
+ $this->command->shouldReceive('option')->andReturn([]);
+
+ $this->task('Rollback')->execute();
+
+ $this->assertEquals(15000000000000, $this->releasesManager->getCurrentRelease());
+ }
+
+ public function testCanGetShownAvailableReleases()
+ {
+ $this->command = $this->mockCommand(['list' => true]);
+ $this->command->shouldReceive('askWith')->andReturn(1);
+
+ $this->task('Rollback')->execute();
+
+ $this->assertEquals(15000000000000, $this->releasesManager->getCurrentRelease());
+ }
+
+ public function testCantRollbackIfNoPreviousRelease()
+ {
+ $this->mockReleases(function ($mock) {
+ return $mock->shouldReceive('getPreviousRelease')->andReturn(null);
+ });
+
+ $status = $this->task('Rollback')->execute();
+ $this->assertContains('Rocketeer could not rollback as no releases have yet been deployed', $status);
}
}
diff --git a/tests/Tasks/SetupTest.php b/tests/Tasks/SetupTest.php
index 88a0fed76..bcb31deef 100644
--- a/tests/Tasks/SetupTest.php
+++ b/tests/Tasks/SetupTest.php
@@ -14,6 +14,8 @@ public function testCanSetupServer()
});
$this->assertTaskHistory('Setup', array(
+ 'git --version',
+ '{php} -m',
"mkdir {server}/",
"mkdir -p {server}/releases",
"mkdir -p {server}/current",
@@ -33,6 +35,8 @@ public function testCanSetupStages()
));
$this->assertTaskHistory('Setup', array(
+ 'git --version',
+ '{php} -m',
"mkdir {server}/",
"mkdir -p {server}/staging/releases",
"mkdir -p {server}/staging/current",
@@ -43,7 +47,7 @@ public function testCanSetupStages()
));
}
- public function testRunningSetupKeepsCurrentCongiguredStage()
+ public function testRunningSetupKeepsCurrentConfiguredStage()
{
$this->mockReleases(function ($mock) {
return $mock
@@ -51,12 +55,14 @@ public function testRunningSetupKeepsCurrentCongiguredStage()
->shouldReceive('getCurrentReleasePath')->andReturn('1');
});
$this->swapConfig(array(
- 'rocketeer::stages.stages' => array('staging', 'production'),
+ 'rocketeer::stages.stages' => ['staging', 'production'],
));
- $this->app['rocketeer.rocketeer']->setStage('staging');
- $this->assertEquals('staging', $this->app['rocketeer.rocketeer']->getStage());
+ $this->connections->setStage('staging');
+ $this->assertEquals('staging', $this->connections->getStage());
$this->assertTaskHistory('Setup', array(
+ 'git --version',
+ '{php} -m',
"mkdir {server}/",
"mkdir -p {server}/staging/releases",
"mkdir -p {server}/staging/current",
@@ -67,6 +73,6 @@ public function testRunningSetupKeepsCurrentCongiguredStage()
), array(
'stage' => 'staging',
));
- $this->assertEquals('staging', $this->app['rocketeer.rocketeer']->getStage());
+ $this->assertEquals('staging', $this->connections->getStage());
}
}
diff --git a/tests/Tasks/Subtasks/NotifyTest.php b/tests/Tasks/Subtasks/NotifyTest.php
new file mode 100644
index 000000000..94dece431
--- /dev/null
+++ b/tests/Tasks/Subtasks/NotifyTest.php
@@ -0,0 +1,23 @@
+swapConfig(array(
+ 'rocketeer::hooks' => array(),
+ ));
+
+ $this->tasks->plugin(new DummyBeforeAfterNotifier($this->app));
+
+ $this->expectOutputString('before_deployafter_deploy');
+ $this->localStorage->set('notifier.name', 'Jean Eude');
+
+ $this->task('Deploy')->fireEvent('before');
+ $this->task('Deploy')->fireEvent('after');
+ }
+}
diff --git a/tests/Tasks/Subtasks/PrimerTest.php b/tests/Tasks/Subtasks/PrimerTest.php
new file mode 100644
index 000000000..1cea8276f
--- /dev/null
+++ b/tests/Tasks/Subtasks/PrimerTest.php
@@ -0,0 +1,19 @@
+swapConfig(array(
+ 'rocketeer::default' => 'production',
+ 'rocketeer::strategies.primer' => function () {
+ return 'ls';
+ },
+ ));
+
+ $this->assertTaskHistory('Primer', ['ls']);
+ }
+}
diff --git a/tests/Tasks/TeardownTest.php b/tests/Tasks/TeardownTest.php
index 63f8146ca..81ca215df 100644
--- a/tests/Tasks/TeardownTest.php
+++ b/tests/Tasks/TeardownTest.php
@@ -7,10 +7,10 @@ class TeardownTest extends RocketeerTestCase
{
public function testCanTeardownServer()
{
- $this->mock('rocketeer.server', 'Server', function ($mock) {
+ $this->mock('rocketeer.storage.local', 'LocalStorage', function ($mock) {
return $mock
->shouldReceive('getSeparator')->andReturn(DIRECTORY_SEPARATOR)
- ->shouldReceive('deleteRepository')->once();
+ ->shouldReceive('destroy')->once();
});
$this->assertTaskHistory('Teardown', array(
@@ -20,10 +20,10 @@ public function testCanTeardownServer()
public function testCanAbortTeardown()
{
- $this->mock('rocketeer.server', 'Server', function ($mock) {
+ $this->mock('rocketeer.storage.local', 'LocalStorage', function ($mock) {
return $mock
->shouldReceive('getSeparator')->andReturn(DIRECTORY_SEPARATOR)
- ->shouldReceive('deleteRepository')->never();
+ ->shouldReceive('destroy')->never();
});
$task = $this->pretendTask('Teardown', array(), array('confirm' => false));
diff --git a/tests/Tasks/TestTest.php b/tests/Tasks/TestTest.php
deleted file mode 100644
index 0a0e833da..000000000
--- a/tests/Tasks/TestTest.php
+++ /dev/null
@@ -1,17 +0,0 @@
-assertTaskHistory('Test', array(
- array(
- 'cd {server}/releases/20000000000000',
- '{phpunit} --stop-on-failure ',
- ),
- ));
- }
-}
diff --git a/tests/Tasks/UpdateTest.php b/tests/Tasks/UpdateTest.php
index 08d4fc25c..372952590 100644
--- a/tests/Tasks/UpdateTest.php
+++ b/tests/Tasks/UpdateTest.php
@@ -9,31 +9,33 @@ public function testCanUpdateRepository()
{
$task = $this->pretendTask('Update', array(
'migrate' => true,
- 'seed' => true
+ 'seed' => true,
));
$matcher = array(
array(
"cd {server}/releases/20000000000000",
"git reset --hard",
- "git pull"
+ "git pull",
),
- "mkdir -p {server}/shared/tests",
- "mv {server}/releases/20000000000000/tests/Elements {server}/shared/tests/Elements",
array(
"cd {server}/releases/20000000000000",
"chmod -R 755 {server}/releases/20000000000000/tests",
"chmod -R g+s {server}/releases/20000000000000/tests",
- "chown -R www-data:www-data {server}/releases/20000000000000/tests"
+ "chown -R www-data:www-data {server}/releases/20000000000000/tests",
),
array(
- "cd {server}/releases/20000000000000",
- "{php} artisan migrate --seed"
+ "cd {server}/releases/{release}",
+ "{php} artisan migrate",
+ ),
+ array(
+ "cd {server}/releases/{release}",
+ "{php} artisan db:seed",
),
array(
"cd {server}/releases/20000000000000",
- "{php} artisan cache:clear"
- )
+ "{php} artisan cache:clear",
+ ),
);
$this->assertTaskHistory($task, $matcher);
diff --git a/tests/TasksHandlerTest.php b/tests/TasksHandlerTest.php
deleted file mode 100644
index 15c82fe1b..000000000
--- a/tests/TasksHandlerTest.php
+++ /dev/null
@@ -1,104 +0,0 @@
-tasksQueue()->add('Rocketeer\Tasks\Deploy');
- $this->assertInstanceOf('Rocketeer\Commands\BaseTaskCommand', $command);
- $this->assertInstanceOf('Rocketeer\Tasks\Deploy', $command->getTask());
- }
-
- public function testCanUseFacadeOutsideOfLaravel()
- {
- Rocketeer::before('deploy', 'ls');
- $before = Rocketeer::getTasksListeners('deploy', 'before', true);
-
- $this->assertEquals(array('ls'), $before);
- }
-
- public function testCanGetTasksBeforeOrAfterAnotherTask()
- {
- $task = $this->task('Deploy');
- $before = $this->tasksQueue()->getTasksListeners($task, 'before', true);
-
- $this->assertEquals(array('before', 'foobar'), $before);
- }
-
- public function testCanAddTasksViaFacade()
- {
- $task = $this->task('Deploy');
- $before = $this->tasksQueue()->getTasksListeners($task, 'before', true);
-
- $this->tasksQueue()->before('deploy', 'composer install');
-
- $newBefore = array_merge($before, array('composer install'));
- $this->assertEquals($newBefore, $this->tasksQueue()->getTasksListeners($task, 'before', true));
- }
-
- public function testCanAddMultipleTasksViaFacade()
- {
- $task = $this->task('Deploy');
- $after = $this->tasksQueue()->getTasksListeners($task, 'after', true);
-
- $this->tasksQueue()->after('deploy', array(
- 'composer install',
- 'bower install'
- ));
-
- $newAfter = array_merge($after, array('composer install', 'bower install'));
- $this->assertEquals($newAfter, $this->tasksQueue()->getTasksListeners($task, 'after', true));
- }
-
- public function testCanAddSurroundTasksToNonExistingTasks()
- {
- $task = $this->task('Setup');
- $this->tasksQueue()->after('setup', 'composer install');
-
- $after = array('composer install');
- $this->assertEquals($after, $this->tasksQueue()->getTasksListeners($task, 'after', true));
- }
-
- public function testCanAddSurroundTasksToMultipleTasks()
- {
- $this->tasksQueue()->after(array('cleanup', 'setup'), 'composer install');
-
- $after = array('composer install');
- $this->assertEquals($after, $this->tasksQueue()->getTasksListeners('setup', 'after', true));
- $this->assertEquals($after, $this->tasksQueue()->getTasksListeners('cleanup', 'after', true));
- }
-
- public function testCangetTasksListenersOrAfterAnotherTaskBySlug()
- {
- $after = $this->tasksQueue()->getTasksListeners('deploy', 'after', true);
-
- $this->assertEquals(array('after', 'foobar'), $after);
- }
-
- public function testCanAddEventsWithPriority()
- {
- $this->tasksQueue()->before('deploy', 'second', -5);
- $this->tasksQueue()->before('deploy', 'first');
-
- $listeners = $this->tasksQueue()->getTasksListeners('deploy', 'before', true);
- $this->assertEquals(array('before', 'foobar', 'first', 'second'), $listeners);
- }
-
- public function testCanExecuteContextualEvents()
- {
- $this->swapConfig(array(
- 'rocketeer::stages.stages' => array('hasEvent', 'noEvent'),
- 'rocketeer::on.stages.hasEvent.hooks' => array('before' => array('check' => 'ls')),
- ));
-
- $this->app['rocketeer.rocketeer']->setStage('hasEvent');
- $this->assertEquals(array('ls'), $this->tasksQueue()->getTasksListeners('check', 'before', true));
-
- $this->app['rocketeer.rocketeer']->setStage('noEvent');
- $this->assertEquals(array(), $this->tasksQueue()->getTasksListeners('check', 'before', true));
- }
-}
diff --git a/tests/TasksQueueTest.php b/tests/TasksQueueTest.php
deleted file mode 100644
index 9cbceacdd..000000000
--- a/tests/TasksQueueTest.php
+++ /dev/null
@@ -1,144 +0,0 @@
-tasksQueue()->buildTaskFromClass('Rocketeer\Tasks\Deploy');
-
- $this->assertInstanceOf('Rocketeer\Traits\Task', $task);
- }
-
- public function testCanBuildCustomTaskByName()
- {
- $tasks = $this->tasksQueue()->buildQueue(array('Rocketeer\Tasks\Check'));
-
- $this->assertInstanceOf('Rocketeer\Tasks\Check', $tasks[0]);
- }
-
- public function testCanBuildTaskFromString()
- {
- $string = 'echo "I love ducks"';
-
- $string = $this->tasksQueue()->buildTaskFromClosure($string);
- $this->assertInstanceOf('Rocketeer\Tasks\Closure', $string);
-
- $closure = $string->getClosure();
- $this->assertInstanceOf('Closure', $closure);
-
- $closureReflection = new ReflectionFunction($closure);
- $this->assertEquals(array('stringTask' => 'echo "I love ducks"'), $closureReflection->getStaticVariables());
-
- $this->assertEquals('I love ducks', $string->execute());
- }
-
- public function testCanBuildTaskFromClosure()
- {
- $originalClosure = function ($task) {
- return $task->getCommand()->info('echo "I love ducks"');
- };
-
- $closure = $this->tasksQueue()->buildTaskFromClosure($originalClosure);
- $this->assertInstanceOf('Rocketeer\Tasks\Closure', $closure);
- $this->assertEquals($originalClosure, $closure->getClosure());
- }
-
- public function testCanBuildQueue()
- {
- $queue = array(
- 'foobar',
- function ($task) {
- return 'lol';
- },
- 'Rocketeer\Tasks\Deploy'
- );
-
- $queue = $this->tasksQueue()->buildQueue($queue);
-
- $this->assertInstanceOf('Rocketeer\Tasks\Closure', $queue[0]);
- $this->assertInstanceOf('Rocketeer\Tasks\Closure', $queue[1]);
- $this->assertInstanceOf('Rocketeer\Tasks\Deploy', $queue[2]);
- }
-
- public function testCanRunQueue()
- {
- $this->swapConfig(array(
- 'rocketeer::default' => '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::default' => 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::default' => '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/TestCases/ContainerTestCase.php b/tests/TestCases/ContainerTestCase.php
index 7b1ad0eda..f46882796 100644
--- a/tests/TestCases/ContainerTestCase.php
+++ b/tests/TestCases/ContainerTestCase.php
@@ -1,20 +1,37 @@
app->instance('path.base', '/src');
- $this->app->instance('path', '/src/app');
- $this->app->instance('path.public', '/src/public');
+ $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();
- $this->app['remote'] = $this->getRemote();
$this->app['artisan'] = $this->getArtisan();
+ $this->app['rocketeer.remote'] = $this->getRemote();
$this->app['rocketeer.command'] = $this->getCommand();
// Rocketeer classes ------------------------------------------- /
$serviceProvider = new RocketeerServiceProvider($this->app);
- $this->app = $serviceProvider->bindPaths($this->app);
- $this->app = $serviceProvider->bindCoreClasses($this->app);
- $this->app = $serviceProvider->bindClasses($this->app);
- $this->app = $serviceProvider->bindScm($this->app);
+ $serviceProvider->boot();
+
+ // Swap some instances with Mockeries -------------------------- /
+
+ $this->app['config'] = $this->getConfig();
}
/**
@@ -67,13 +84,21 @@ public function tearDown()
* @param string $handle
* @param string $class
* @param Closure $expectations
+ * @param boolean $partial
*
* @return Mockery
*/
- protected function mock($handle, $class, $expectations)
+ protected function mock($handle, $class = null, Closure $expectations = null, $partial = true)
{
+ $class = $class ?: $handle;
$mockery = Mockery::mock($class);
- $mockery = $expectations($mockery)->mock();
+ if ($partial) {
+ $mockery = $mockery->shouldIgnoreMissing();
+ }
+
+ if ($expectations) {
+ $mockery = $expectations($mockery)->mock();
+ }
$this->app[$handle] = $mockery;
@@ -94,11 +119,16 @@ protected function getCommand(array $expectations = array(), array $options = ar
return $message;
};
- $command = Mockery::mock('Command');
- $command->shouldReceive('comment')->andReturnUsing($message);
- $command->shouldReceive('error')->andReturnUsing($message);
- $command->shouldReceive('line')->andReturnUsing($message);
- $command->shouldReceive('info')->andReturnUsing($message);
+ $command = Mockery::mock('Command')->shouldIgnoreMissing();
+ $command->shouldReceive('getOutput')->andReturn(null);
+
+ // Bind the output expectations
+ $types = ['comment', 'error', 'line', 'info'];
+ foreach ($types as $type) {
+ if (!array_key_exists($type, $expectations)) {
+ $command->shouldReceive($type)->andReturnUsing($message);
+ }
+ }
// Merge defaults
$expectations = array_merge(array(
@@ -115,7 +145,8 @@ protected function getCommand(array $expectations = array(), array $options = ar
if ($key === 'option') {
$command->shouldReceive($key)->andReturn($value)->byDefault();
} else {
- $command->shouldReceive($key)->andReturn($value);
+ $returnMethod = $value instanceof Closure ? 'andReturnUsing' : 'andReturn';
+ $command->shouldReceive($key)->$returnMethod($value);
}
}
@@ -141,94 +172,33 @@ protected function getConfig($expectations = array())
$config = Mockery::mock('Illuminate\Config\Repository');
$config->shouldIgnoreMissing();
+ $defaults = $this->getFactoryConfiguration();
+ $expectations = array_merge($defaults, $expectations);
foreach ($expectations 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.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::application_name')->andReturn('foobar');
- $config->shouldReceive('get')->with('rocketeer::default')->andReturn(array('production', 'staging'));
- $config->shouldReceive('get')->with('rocketeer::logs')->andReturn(false);
- $config->shouldReceive('get')->with('rocketeer::connections')->andReturn(array());
- $config->shouldReceive('get')->with('rocketeer::remote.strategy')->andReturn('clone');
- $config->shouldReceive('get')->with('rocketeer::remote.keep_releases')->andReturn(1);
- $config->shouldReceive('get')->with('rocketeer::remote.permissions.callback')->andReturn(function ($task, $file) {
- return array(
- sprintf('chmod -R 755 %s', $file),
- sprintf('chmod -R g+s %s', $file),
- sprintf('chown -R www-data:www-data %s', $file),
- );
- });
- $config->shouldReceive('get')->with('rocketeer::remote.permissions.files')->andReturn(array('tests'));
- $config->shouldReceive('get')->with('rocketeer::remote.root_directory')->andReturn(__DIR__.'/../_server/');
- $config->shouldReceive('get')->with('rocketeer::remote.app_directory')->andReturn(null);
- $config->shouldReceive('get')->with('rocketeer::remote.shared')->andReturn(array('tests/Elements'));
- $config->shouldReceive('get')->with('rocketeer::remote.composer')->andReturn(function ($task) {
- return array(
- $task->composer('self-update'),
- $task->composer('install --no-interaction --no-dev --prefer-dist'),
- );
- });
- $config->shouldReceive('get')->with('rocketeer::stages.default')->andReturn(null);
- $config->shouldReceive('get')->with('rocketeer::stages.stages')->andReturn(array());
-
- // Paths
- $config->shouldReceive('get')->with('rocketeer::paths.php')->andReturn('');
- $config->shouldReceive('get')->with('rocketeer::paths.composer')->andReturn('');
- $config->shouldReceive('get')->with('rocketeer::paths.artisan')->andReturn('');
-
- // SCM
- $config->shouldReceive('get')->with('rocketeer::scm.branch')->andReturn('master');
- $config->shouldReceive('get')->with('rocketeer::scm.repository')->andReturn('https://github.com/'.$this->repository);
- $config->shouldReceive('get')->with('rocketeer::scm.scm')->andReturn('git');
- $config->shouldReceive('get')->with('rocketeer::scm.shallow')->andReturn(true);
- $config->shouldReceive('get')->with('rocketeer::scm.submodules')->andReturn(true);
-
- // Tasks
- $config->shouldReceive('get')->with('rocketeer::hooks')->andReturn(array(
- 'before' => array(
- 'deploy' => array(
- 'before',
- 'foobar'
- ),
- ),
- 'after' => array(
- 'check' => array(
- 'Rocketeer\Dummies\MyCustomTask',
- ),
- 'deploy' => array(
- 'after',
- 'foobar'
- ),
- ),
- ));
-
return $config;
}
/**
* Swap the current config
*
- * @param array $config
+ * @param array $config
*
* @return void
*/
protected function swapConfig($config)
{
- $this->app['rocketeer.rocketeer']->disconnect();
+ $this->connections->disconnect();
$this->app['config'] = $this->getConfig($config);
}
/**
* Mock the Remote component
*
+ * @param string|array|null $mockedOutput
+ *
* @return Mockery
*/
protected function getRemote($mockedOutput = null)
@@ -243,9 +213,9 @@ protected function getRemote($mockedOutput = null)
};
$remote = Mockery::mock('Illuminate\Remote\Connection');
+ $remote->shouldReceive('connected')->andReturn(true);
$remote->shouldReceive('into')->andReturn(Mockery::self());
$remote->shouldReceive('status')->andReturn(0)->byDefault();
- $remote->shouldReceive('run')->andReturnUsing($run)->byDefault();
$remote->shouldReceive('runRaw')->andReturnUsing($run)->byDefault();
$remote->shouldReceive('getString')->andReturnUsing(function ($file) {
return file_get_contents($file);
@@ -257,6 +227,14 @@ protected function getRemote($mockedOutput = null)
print $line.PHP_EOL;
});
+ if (is_array($mockedOutput)) {
+ foreach ($mockedOutput as $command => $output) {
+ $remote->shouldReceive('run')->with($command)->andReturn($output);
+ }
+ } else {
+ $remote->shouldReceive('run')->andReturnUsing($run)->byDefault();
+ }
+
return $remote;
}
@@ -274,4 +252,76 @@ protected function getArtisan()
return $artisan;
}
+
+ /**
+ * @return array
+ */
+ protected function getFactoryConfiguration()
+ {
+ if ($this->defaults) {
+ return $this->defaults;
+ }
+
+ // Base the mocked configuration off the factory values
+ $defaults = [];
+ $files = ['config', 'hooks', 'paths', 'remote', 'scm', 'stages', 'strategies'];
+ foreach ($files as $file) {
+ $defaults[$file] = $this->config->get('rocketeer::'.$file);
+ }
+
+ // Build correct keys
+ $defaults = array_dot($defaults);
+ $keys = array_keys($defaults);
+ $keys = array_map(function ($key) {
+ return 'rocketeer::'.str_replace('config.', null, $key);
+ }, $keys);
+ $defaults = array_combine($keys, array_values($defaults));
+
+ $overrides = array(
+ 'cache.driver' => 'file',
+ 'database.default' => 'mysql',
+ 'remote.default' => 'production',
+ 'session.driver' => 'file',
+ 'remote.connections' => array(
+ 'production' => [],
+ 'staging' => [],
+ ),
+ 'rocketeer::application_name' => 'foobar',
+ 'rocketeer::logs' => null,
+ 'rocketeer::remote.permissions.files' => ['tests'],
+ 'rocketeer::remote.shared' => ['tests/Elements'],
+ 'rocketeer::remote.keep_releases' => 1,
+ 'rocketeer::remote.root_directory' => __DIR__.'/../_server/',
+ 'rocketeer::scm' => array(
+ 'branch' => 'master',
+ 'repository' => 'https://github.com/'.$this->repository,
+ 'scm' => 'git',
+ 'shallow' => true,
+ 'submodules' => true,
+ ),
+ 'rocketeer::strategies.dependencies' => 'Composer',
+ 'rocketeer::hooks' => array(
+ 'before' => array(
+ 'deploy' => array(
+ 'before',
+ 'foobar',
+ ),
+ ),
+ 'after' => array(
+ 'check' => array(
+ 'Rocketeer\Dummies\MyCustomTask',
+ ),
+ 'deploy' => array(
+ 'after',
+ 'foobar',
+ ),
+ ),
+ ),
+ );
+
+ // Assign options to expectations
+ $this->defaults = array_merge($defaults, $overrides);
+
+ return $this->defaults;
+ }
}
diff --git a/tests/TestCases/Modules/RocketeerAssertions.php b/tests/TestCases/Modules/RocketeerAssertions.php
new file mode 100644
index 000000000..822d1e54f
--- /dev/null
+++ b/tests/TestCases/Modules/RocketeerAssertions.php
@@ -0,0 +1,157 @@
+assertEquals($connection, $this->connections->getConnection());
+ }
+
+ /**
+ * Assert that the current repository equals
+ *
+ * @param string $repository
+ */
+ protected function assertRepositoryEquals($repository)
+ {
+ $this->assertEquals($repository, $this->connections->getRepositoryEndpoint());
+ }
+
+ /**
+ * Assert an option has a certain value
+ *
+ * @param string $value
+ * @param string $option
+ */
+ protected function assertOptionValueEquals($value, $option)
+ {
+ $this->assertEquals($value, $this->rocketeer->getOption($option));
+ }
+
+ /**
+ * Assert a task has a particular output
+ *
+ * @param string $task
+ * @param string $output
+ * @param \Mockery $command
+ *
+ * @return Assertion
+ */
+ protected function assertTaskOutput($task, $output, $command = null)
+ {
+ if ($command) {
+ $this->app['rocketeer.command'] = $command;
+ }
+
+ return $this->assertContains($output, $this->task($task)->execute());
+ }
+
+ /**
+ * Assert a task's history matches an array
+ *
+ * @param string|AbstractTask $task
+ * @param array $expectedHistory
+ * @param array $options
+ *
+ * @return string
+ */
+ protected function assertTaskHistory($task, array $expectedHistory, array $options = array())
+ {
+ // Create task if needed
+ if (is_string($task)) {
+ $task = $this->pretendTask($task, $options);
+ }
+
+ // Execute task and get history
+ if (is_array($task)) {
+ $results = '';
+ $taskHistory = $task;
+ } else {
+ $results = $task->execute();
+ $taskHistory = $task->history->getFlattenedHistory();
+ }
+
+ $this->assertHistory($expectedHistory, $taskHistory);
+
+ return $results;
+ }
+
+ /**
+ * Assert an history matches another
+ *
+ * @param array $expected
+ * @param array $obtained
+ */
+ public function assertHistory(array $expected, array $obtained = array())
+ {
+ if (!$obtained) {
+ $obtained = $this->history->getFlattenedHistory();
+ }
+
+ // Look for release in history
+ $release = implode(array_flatten($obtained));
+ preg_match_all('/[0-9]{14}/', $release, $releases);
+ $release = Arr::get($releases, '0.0', date('YmdHis'));
+ if ($release === '10000000000000') {
+ $release = Arr::get($releases, '0.1', date('YmdHis'));
+ }
+
+ // Replace placeholders
+ $expected = $this->replaceHistoryPlaceholders($expected, $release);
+
+ // Check equality
+ $this->assertEquals($expected, $obtained);
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// HELPERS ///////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Replace placeholders in an history
+ *
+ * @param array $history
+ * @param integer|null $release
+ *
+ * @return array
+ */
+ protected function replaceHistoryPlaceholders($history, $release = null)
+ {
+ $release = $release ?: date('YmdHis');
+ $hhvm = defined('HHVM_VERSION');
+
+ $replaced = [];
+ foreach ($history as $key => $entries) {
+ if ($hhvm && $entries == '{php} -m') {
+ continue;
+ }
+
+ if (is_array($entries)) {
+ $replaced[$key] = $this->replaceHistoryPlaceholders($entries, $release);
+ continue;
+ }
+
+ $replaced[$key] = strtr($entries, array(
+ '{php}' => $this->binaries['php'],
+ '{bundle}' => $this->binaries['bundle'],
+ '{phpunit}' => $this->binaries['phpunit'],
+ '{repository}' => 'https://github.com/'.$this->repository,
+ '{server}' => $this->server,
+ '{release}' => $release,
+ '{composer}' => $this->binaries['composer'],
+ ));
+ }
+
+ return $replaced;
+ }
+}
diff --git a/tests/TestCases/Modules/RocketeerMockeries.php b/tests/TestCases/Modules/RocketeerMockeries.php
new file mode 100644
index 000000000..723445643
--- /dev/null
+++ b/tests/TestCases/Modules/RocketeerMockeries.php
@@ -0,0 +1,114 @@
+server.'/current/composer.json';
+ if ($uses) {
+ $this->files->put($composer, '{}');
+ } else {
+ $this->files->delete($composer);
+ }
+ }
+
+ /**
+ * @param array $state
+ */
+ protected function mockState(array $state)
+ {
+ file_put_contents($this->server.'/state.json', json_encode($state));
+ }
+
+ /**
+ * Set Rocketeer in pretend mode
+ *
+ * @param array $options
+ * @param array $expectations
+ */
+ protected function pretend($options = array(), $expectations = array())
+ {
+ $options['pretend'] = true;
+
+ $this->mockCommand($options, $expectations);
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ ////////////////////////////// SERVICES //////////////////////////////
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Mock the ReleasesManager
+ *
+ * @param Closure $expectations
+ *
+ * @return Mockery
+ */
+ protected function mockReleases(Closure $expectations)
+ {
+ return $this->mock('rocketeer.releases', 'ReleasesManager', $expectations);
+ }
+
+ /**
+ * Mock a Command
+ *
+ * @param array $options
+ * @param array $expectations
+ */
+ protected function mockCommand($options = array(), $expectations = array())
+ {
+ // Default options
+ $options = array_merge(array(
+ 'pretend' => false,
+ 'verbose' => false,
+ 'tests' => false,
+ 'migrate' => false,
+ 'seed' => false,
+ 'stage' => false,
+ 'parallel' => false,
+ 'update' => false,
+ ), $options);
+
+ $this->app['rocketeer.command'] = $this->getCommand($expectations, $options);
+ }
+
+ /**
+ * Mock the RemoteHandler
+ *
+ * @param string|array|null $expectations
+ */
+ protected function mockRemote($expectations = null)
+ {
+ $this->app['rocketeer.remote'] = $this->getRemote($expectations);
+ }
+
+ /**
+ * @param Closure|null $expectations
+ */
+ protected function mockFiles(Closure $expectations = null)
+ {
+ $this->mock('files', 'Illuminate\Filesystem\Filesystem', $expectations);
+ }
+
+ /**
+ * @param array $configuration
+ */
+ public function mockConfig(array $configuration)
+ {
+ $this->app['config'] = $this->getConfig($configuration);
+ }
+}
diff --git a/tests/TestCases/RocketeerTestCase.php b/tests/TestCases/RocketeerTestCase.php
index a5a8fcf89..6b6420e31 100644
--- a/tests/TestCases/RocketeerTestCase.php
+++ b/tests/TestCases/RocketeerTestCase.php
@@ -1,10 +1,15 @@
server = __DIR__.'/../_server/foobar';
- $this->deploymentsFile = __DIR__.'/../_meta/deployments.json';
+ $this->customConfig = $this->server.'/../.rocketeer';
+ $this->deploymentsFile = $this->server.'/deployments.json';
- // Bind new Server instance
- $meta = dirname($this->deploymentsFile);
- $this->app->bind('rocketeer.server', function ($app) use ($meta) {
- return new Server($app, 'deployments', $meta);
- });
-
- // Bind dummy Task
+ // Bind dummy AbstractTask
$this->task = $this->task('Cleanup');
$this->recreateVirtualServer();
- }
-
- /**
- * Recreates the local file server
- *
- * @return void
- */
- protected function recreateVirtualServer()
- {
- // Recreate deployments file
- $this->app['files']->put($this->deploymentsFile, json_encode(array(
- 'foo' => 'bar',
- 'current_release' => array('production' => 20000000000000),
- 'directory_separator' => '/',
- 'is_setup' => true,
- 'webuser' => array('username' => 'www-data','group' => 'www-data'),
- 'line_endings' => "\n",
- )));
- $rootPath = $this->server.'/../../..';
-
- // Recreate altered local server
- $this->app['files']->deleteDirectory($rootPath.'/storage');
- $this->app['files']->deleteDirectory($this->server.'/logs');
- $folders = array('current', 'shared', 'releases','releases/10000000000000', 'releases/15000000000000', 'releases/20000000000000');
- foreach ($folders as $folder) {
- $folder = $this->server.'/'.$folder;
+ // Bind new LocalStorage instance
+ $this->app->bind('rocketeer.storage.local', function ($app) {
+ $folder = dirname($this->deploymentsFile);
- $this->app['files']->deleteDirectory($folder);
- $this->app['files']->delete($folder);
- $this->app['files']->makeDirectory($folder, 0777, true);
- file_put_contents($folder.'/.gitkeep', '');
- }
- file_put_contents($this->server.'/state.json', json_encode(array(
- '10000000000000' => true,
- '15000000000000' => false,
- '20000000000000' => true,
- )));
+ return new LocalStorage($app, 'deployments', $folder);
+ });
- // Delete rocketeer config
- $binary = $rootPath.'/.rocketeer';
- $this->app['files']->deleteDirectory($binary);
+ // Cache paths
+ $this->binaries = array(
+ 'php' => exec('which php') ?: 'php',
+ 'bundle' => exec('which bundle') ?: 'bundle',
+ 'phpunit' => exec('which phpunit') ?: 'phpunit',
+ 'composer' => exec('which composer') ?: 'composer',
+ );
}
- ////////////////////////////////////////////////////////////////////
- ///////////////////////////// ASSERTIONS ///////////////////////////
- ////////////////////////////////////////////////////////////////////
-
/**
- * Assert a task has a particular output
- *
- * @param string $task
- * @param string $output
- * @param Mockery $command
- *
- * @return Assertion
+ * Cleanup tests
*/
- protected function assertTaskOutput($task, $output, $command = null)
+ public function tearDown()
{
- return $this->assertContains($output, $this->task($task, $command)->execute());
- }
-
- /**
- * Assert a task's history matches an array
- *
- * @param string|Task $task
- * @param array $history
- * @param array $options
- *
- * @return string
- */
- protected function assertTaskHistory($task, array $expectedHistory, array $options = array())
- {
- // Create task if needed
- if (is_string($task)) {
- $task = $this->pretendTask($task, $options);
- }
-
- // Execute task and get history
- if (is_array($task)) {
- $results = '';
- $taskHistory = $task;
- } else {
- $results = $task->execute();
- $taskHistory = $task->getHistory();
- }
+ parent::tearDown();
- // Look for release in history
- $release = join(array_flatten($taskHistory));
- preg_match_all('/[0-9]{14}/', $release, $releases);
- $release = array_get($releases, '0.0', date('YmdHis'));
- if ($release === '10000000000000') {
- $release = array_get($releases, '0.1', date('YmdHis'));
- }
-
- // Replace placeholders
- $expectedHistory = $this->replaceHistoryPlaceholders($expectedHistory, $release);
-
- // Check equality
- $this->assertEquals($expectedHistory, $taskHistory);
-
- return $results;
+ // Restore superglobals
+ $_SERVER['HOME'] = $this->home;
}
/**
- * Replace placeholders in an history
- *
- * @param array $history
+ * Recreates the local file server
*
- * @return array
+ * @return void
*/
- protected function replaceHistoryPlaceholders($history, $release = null)
+ protected function recreateVirtualServer()
{
- $release = $release ?: date('YmdHis');
-
- foreach ($history as $key => $entries) {
- if (is_array($entries)) {
- $history[$key] = $this->replaceHistoryPlaceholders($entries, $release);
- continue;
- }
-
- $history[$key] = strtr($entries, array(
- '{php}' => exec('which php'),
- '{phpunit}' => exec('which phpunit'),
- '{server}' => $this->server,
- '{release}' => $release,
- '{composer}' => exec('which composer'),
- ));
+ // Save superglobals
+ $this->home = $_SERVER['HOME'];
+
+ // Cleanup files created by tests
+ $cleanup = array(
+ realpath(__DIR__.'/../../.rocketeer'),
+ realpath(__DIR__.'/../.rocketeer'),
+ realpath($this->server),
+ realpath($this->customConfig),
+ );
+ array_map([$this->files, 'deleteDirectory'], $cleanup);
+ if (is_link($this->server.'/current')) {
+ unlink($this->server.'/current');
}
- return $history;
- }
-
- ////////////////////////////////////////////////////////////////////
- ////////////////////////////// MOCKERIES ///////////////////////////
- ////////////////////////////////////////////////////////////////////
-
- /**
- * Mock the ReleasesManager
- *
- * @param Closure $expectations
- *
- * @return Mockery
- */
- protected function mockReleases($expectations)
- {
- return $this->mock('rocketeer.releases', 'ReleasesManager', $expectations);
+ // Recreate altered local server
+ $this->files->copyDirectory($this->server.'-stub', $this->server);
}
////////////////////////////////////////////////////////////////////
@@ -207,66 +117,39 @@ protected function mockReleases($expectations)
////////////////////////////////////////////////////////////////////
/**
- * Mock the Composer check
+ * Get a pretend AbstractTask to run bogus commands
*
- * @param boolean $uses
+ * @param string $task
+ * @param array $options
+ * @param array $expectations
*
- * @return void
- */
- protected function usesComposer($uses = true)
- {
- $composer = $this->app['path.base'].DIRECTORY_SEPARATOR.'composer.json';
- $this->mock('files', 'Illuminate\Filesystem\Filesystem', function ($mock) use ($composer, $uses) {
- return $mock->makePartial()->shouldReceive('exists')->with($composer)->andReturn($uses);
- });
- }
-
- /**
- * Get a pretend Task to run bogus commands
- *
- * @return Task
+ * @return \Rocketeer\Abstracts\AbstractTask
*/
protected function pretendTask($task = 'Deploy', $options = array(), array $expectations = array())
{
- // Default options
- $options = array_merge(array(
- 'pretend' => true,
- 'verbose' => false,
- 'tests' => false,
- 'migrate' => false,
- 'seed' => false,
- ), $options);
+ $this->pretend($options, $expectations);
- return $this->task($task, $this->getCommand($expectations, $options));
+ return $this->task($task);
}
/**
- * Get Task instance
+ * Get AbstractTask instance
*
- * @param string $task
+ * @param string $task
+ * @param array $options
*
- * @return Task
+ * @return \Rocketeer\Abstracts\AbstractTask
*/
- protected function task($task = null, $command = null)
+ protected function task($task = null, $options = array())
{
- if ($command) {
- $this->app['rocketeer.command'] = $command;
+ if ($options) {
+ $this->mockCommand($options);
}
if (!$task) {
return $this->task;
}
- return $this->tasksQueue()->buildTaskFromClass('Rocketeer\Tasks\\'.$task);
- }
-
- /**
- * Get TasksQueue instance
- *
- * @return TasksQueue
- */
- protected function tasksQueue()
- {
- return $this->app['rocketeer.tasks'];
+ return $this->builder->buildTaskFromClass($task);
}
}
diff --git a/tests/Traits/BashModules/BinariesTest.php b/tests/Traits/BashModules/BinariesTest.php
index a64361831..0b9674951 100644
--- a/tests/Traits/BashModules/BinariesTest.php
+++ b/tests/Traits/BashModules/BinariesTest.php
@@ -1,71 +1,87 @@
app['config'] = $this->getConfig(array('rocketeer::paths.composer' => 'foobar'));
+ $this->mockConfig(['rocketeer::paths.composer' => __FILE__]);
- $this->assertEquals('foobar', $this->task->which('composer'));
+ $this->assertEquals(__FILE__, $this->task->which('composer'));
}
- public function testCanSetPathToPhpAndArtisan()
+ public function testStoredPathsAreInvalidatedIfIncorrect()
{
- $this->app['config'] = $this->getConfig(array(
- 'rocketeer::paths.php' => '/usr/local/bin/php',
- 'rocketeer::paths.artisan' => './laravel/artisan',
- ));
+ $this->mock('rocketeer.remote', 'Remote', function ($mock) {
+ return $mock
+ ->shouldReceive('run')->with(['which composer'], Mockery::any())->andReturn(null)
+ ->shouldReceive('run')->with(['which '.$this->binaries['composer']], Mockery::any())->andReturn($this->binaries['composer'])
+ ->shouldReceive('run')->with('[ -e ] && echo "true"', Mockery::any())->andReturn('false')
+ ->shouldReceive('run')->with('[ -e foobar ] && echo "true"', Mockery::any())->andReturn('false')
+ ->shouldReceive('runRaw')->andReturn('false');
+ }, false);
+
+ $this->localStorage->set('paths.composer', 'foobar');
- $this->assertEquals('/usr/local/bin/php ./laravel/artisan migrate', $this->task->artisan('migrate'));
+ $this->assertEquals('composer', $this->task->which('composer'));
+ $this->assertNull($this->localStorage->get('paths.composer'));
}
- public function testFetchesBinaryIfNotSpecifiedOrNull()
+ public function testCanSetPathToPhpAndArtisan()
{
- $this->app['config'] = $this->getConfig(array(
- 'rocketeer::paths.php' => '/usr/local/bin/php',
+ $this->mockConfig(array(
+ 'rocketeer::paths.php' => $this->binaries['php'],
+ 'rocketeer::paths.artisan' => $this->binaries['php'],
));
- $this->assertEquals('/usr/local/bin/php artisan migrate', $this->task->artisan('migrate'));
+ $this->assertEquals($this->binaries['php'].' '.$this->binaries['php'].' migrate', $this->task->artisan()->migrate());
}
- public function testCanGetBinary()
+ public function testFetchesBinaryIfNotSpecifiedOrNull()
{
- $whichGrep = exec('which grep');
- $grep = $this->task->which('grep');
+ $this->mockConfig(array(
+ 'rocketeer::paths.php' => $this->binaries['php'],
+ ));
- $this->assertEquals($whichGrep, $grep);
+ $this->assertEquals($this->binaries['php'].' artisan migrate', $this->task->artisan()->migrate());
}
- public function testCanGetFallbackForBinary()
+ public function testCanGetBinary()
{
$whichGrep = exec('which grep');
- $grep = $this->task->which('foobar', $whichGrep);
+ $grep = $this->task->which('grep');
$this->assertEquals($whichGrep, $grep);
- $this->assertFalse($this->task->which('fdsf'));
}
- public function testDoesntRunComposerIfNotNeeded()
+ public function testCanRunComposer()
{
$this->usesComposer(true);
$this->mock('rocketeer.command', 'Illuminate\Console\Command', function ($mock) {
return $mock
- ->shouldReceive('option')->andReturn(array())
- ->shouldReceive('comment')->once();
+ ->shouldIgnoreMissing()
+ ->shouldReceive('line')
+ ->shouldReceive('option')->andReturn([]);
});
- $this->task->runComposer();
+ $this->task('Dependencies')->execute();
+ $this->assertCount(2, $this->history->getFlattenedHistory()[0]);
+ }
+ public function testDoesntRunComposerIfNotNeeded()
+ {
$this->usesComposer(false);
$this->mock('rocketeer.command', 'Illuminate\Console\Command', function ($mock) {
return $mock
- ->shouldReceive('option')->andReturn(array())
- ->shouldReceive('comment')->never();
+ ->shouldIgnoreMissing()
+ ->shouldReceive('line')
+ ->shouldReceive('option')->andReturn([]);
});
- $this->task->runComposer();
+ $this->task('Dependencies')->execute();
+ $this->assertEmpty($this->history->getFlattenedHistory());
}
}
diff --git a/tests/Traits/BashModules/CoreTest.php b/tests/Traits/BashModules/CoreTest.php
index 40be091bd..ced529020 100644
--- a/tests/Traits/BashModules/CoreTest.php
+++ b/tests/Traits/BashModules/CoreTest.php
@@ -9,30 +9,71 @@ public function testCanGetArraysFromRawCommands()
{
$contents = $this->task->runRaw('ls', true, true);
- $this->assertCount(12, $contents);
+ $this->assertCount(11, $contents);
}
public function testCanCheckStatusOfACommand()
{
- $this->task->remote = clone $this->getRemote()->shouldReceive('status')->andReturn(1)->mock();
- ob_start();
- $status = $this->task->checkStatus(null, 'error');
- $output = ob_get_clean();
- $this->assertEquals('error'.PHP_EOL, $output);
+ $this->expectOutputRegex('/.+An error occured: "Oh noes", while running:\ngit clone.+/');
+
+ $this->app['rocketeer.remote'] = clone $this->getRemote()->shouldReceive('status')->andReturn(1)->mock();
+ $this->mockCommand([], array(
+ 'line' => function ($error) {
+ echo $error;
+ },
+ ));
+
+ $status = $this->task('Deploy')->checkStatus('Oh noes', 'git clone');
+
$this->assertFalse($status);
}
public function testCanGetTimestampOffServer()
{
$timestamp = $this->task->getTimestamp();
+
$this->assertEquals(date('YmdHis'), $timestamp);
}
public function testCanGetLocalTimestampIfError()
{
- $this->app['remote'] = $this->getRemote('NOPE');
+ $this->mockRemote('NOPE');
$timestamp = $this->task->getTimestamp();
$this->assertEquals(date('YmdHis'), $timestamp);
}
+
+ public function testDoesntAppendEnvironmentToStandardTasks()
+ {
+ $this->connections->setStage('staging');
+ $commands = $this->pretendTask()->processCommands(array(
+ 'artisan something',
+ 'rm readme*',
+ ));
+
+ $this->assertEquals(array(
+ 'artisan something --env="staging"',
+ 'rm readme*',
+ ), $commands);
+ }
+
+ public function testCanRemoveCommonPollutingOutput()
+ {
+ $this->mockRemote('stdin: is not a tty'.PHP_EOL.'something');
+ $result = $this->bash->run('ls');
+
+ $this->assertEquals('something', $result);
+ }
+
+ public function testCanRunCommandsLocally()
+ {
+ $this->mock('rocketeer.remote', 'Remote', function ($mock) {
+ return $mock->shouldReceive('run')->never();
+ });
+
+ $this->task->setLocal(true);
+ $contents = $this->task->runRaw('ls', true, true);
+
+ $this->assertCount(11, $contents);
+ }
}
diff --git a/tests/Traits/BashModules/FilesystemTest.php b/tests/Traits/BashModules/FilesystemTest.php
index ec06b5684..b974c3923 100644
--- a/tests/Traits/BashModules/FilesystemTest.php
+++ b/tests/Traits/BashModules/FilesystemTest.php
@@ -7,9 +7,9 @@ class FilesystemTest extends RocketeerTestCase
{
public function testCancelsSymlinkForUnexistingFolders()
{
- $task = $this->pretendTask();
- $folder = '{path.storage}/logs';
- $share = $task->share($folder);
+ $task = $this->pretendTask();
+ $folder = '{path.storage}/logs';
+ $share = $task->share($folder);
$this->assertFalse($share);
}
@@ -33,7 +33,10 @@ public function testCanListContentsOfAFolder()
{
$contents = $this->task->listContents($this->server);
- $this->assertEquals(array('current', 'releases', 'shared', 'state.json'), $contents);
+ $this->assertContains('current', $contents);
+ $this->assertContains('releases', $contents);
+ $this->assertContains('shared', $contents);
+ $this->assertContains('state.json', $contents);
}
public function testCanCheckIfFileExists()
@@ -41,4 +44,11 @@ public function testCanCheckIfFileExists()
$this->assertTrue($this->task->fileExists($this->server));
$this->assertFalse($this->task->fileExists($this->server.'/nope'));
}
+
+ public function testDoesntTryToMoveUnexistingFolders()
+ {
+ $this->pretendTask()->move('foobar', 'bazqux');
+
+ $this->assertEmpty($this->history->getFlattenedOutput());
+ }
}
diff --git a/tests/Traits/BashModules/ScmTest.php b/tests/Traits/BashModules/ScmTest.php
index a71adcaa9..b7b7b8798 100644
--- a/tests/Traits/BashModules/ScmTest.php
+++ b/tests/Traits/BashModules/ScmTest.php
@@ -7,19 +7,21 @@ class ScmTest extends RocketeerTestCase
{
public function testCanForgetCredentialsIfInvalid()
{
- $this->app['rocketeer.server']->setValue('credentials', array(
- 'repository' => 'https://Anahkiasen@bitbucket.org/Anahkiasen/registry.git',
+ $this->app['rocketeer.storage.local']->set('credentials', array(
+ 'repository' => 'https://bitbucket.org/Anahkiasen/registry.git',
'username' => 'Anahkiasen',
'password' => 'baz',
));
- // Create fake remote
- $remote = clone $this->getRemote();
- $remote->shouldReceive('status')->andReturn(1);
+ $this->mock('rocketeer.bash', 'Bash', function ($mock) {
+ return $mock
+ ->shouldIgnoreMissing()
+ ->shouldReceive('checkStatus')->andReturn(false);
+ });
+
$task = $this->pretendTask();
- $task->remote = $remote;
- $task->cloneRepository($this->server.'/test');
- $this->assertNull($this->app['rocketeer.server']->getValue('credentials'));
+ $task->getStrategy('Deploy')->deploy($this->server.'/test');
+ $this->assertNull($this->app['rocketeer.storage.local']->get('credentials'));
}
}
diff --git a/tests/Traits/ScmTest.php b/tests/Traits/ScmTest.php
deleted file mode 100644
index 822b4e9e8..000000000
--- a/tests/Traits/ScmTest.php
+++ /dev/null
@@ -1,30 +0,0 @@
-app);
- $command = $scm->getCommand('foo %s', 'bar');
-
- $this->assertEquals('git foo bar', $command);
- }
-
- public function testCanExecuteMethod()
- {
- $this->mock('rocketeer.bash', 'Bash', function ($mock) {
- return $mock->shouldReceive('run')->once()->withAnyArgs()->andReturnUsing(function ($arguments) {
- return $arguments;
- });
- });
-
- $scm = new Git($this->app);
- $command = $scm->execute('checkout', $this->server);
-
- $this->assertEquals('git clone --depth 1 -b master "" '.$this->server, $command);
- }
-}
diff --git a/tests/Traits/StepsRunnerTest.php b/tests/Traits/StepsRunnerTest.php
new file mode 100644
index 000000000..20ee453b7
--- /dev/null
+++ b/tests/Traits/StepsRunnerTest.php
@@ -0,0 +1,34 @@
+task;
+ $copy = $this->server.'/state2.json';
+ $task->steps()->copy($this->server.'/state.json', $copy);
+
+ $results = $task->runSteps();
+
+ $this->files->delete($copy);
+ $this->assertTrue($results);
+ }
+
+ public function testStepsAreClearedOnceRun()
+ {
+ $task = $this->task;
+ $task->steps()->run('ls');
+
+ $this->assertEquals(array(
+ ['run', ['ls']],
+ ), $task->steps()->getSteps());
+ $task->runSteps();
+ $task->steps()->run('php --version');
+ $this->assertEquals(array(
+ ['run', ['php --version']],
+ ), $task->steps()->getSteps());
+ }
+}
diff --git a/tests/Traits/TaskTest.php b/tests/Traits/TaskTest.php
deleted file mode 100644
index ba9c98df4..000000000
--- a/tests/Traits/TaskTest.php
+++ /dev/null
@@ -1,77 +0,0 @@
-pretendTask('Deploy');
- $task->updateRepository();
-
- $matcher = array(
- array(
- "cd $this->server/releases/20000000000000",
- "git reset --hard",
- "git pull",
- ),
- );
-
- $this->assertEquals($matcher, $task->getHistory());
- }
-
- public function testCanDisplayOutputOfCommandsIfVerbose()
- {
- $task = $this->pretendTask('Check', array(
- 'verbose' => true,
- 'pretend' => false
- ));
-
- ob_start();
- $task->run('ls');
- $output = ob_get_clean();
-
- $this->assertContains('tests', $output);
- }
-
- public function testCanPretendToRunTasks()
- {
- $task = $this->pretendTask();
-
- $output = $task->run('ls');
- $this->assertEquals('ls', $output);
- }
-
- public function testCanGetDescription()
- {
- $task = $this->task('Setup');
-
- $this->assertNotNull($task->getDescription());
- }
-
- public function testCanRunMigrations()
- {
- $task = $this->pretendTask();
- $php = exec('which php');
-
- $commands = $task->runMigrations();
- $this->assertEquals($php.' artisan migrate', $commands[1]);
-
- $commands = $task->runMigrations(true);
- $this->assertEquals($php.' artisan migrate --seed', $commands[1]);
- }
-
- public function testCanFireEventsDuringTasks()
- {
- $this->expectOutputString('foobar');
-
- $this->tasksQueue()->listenTo('closure.test.foobar', function ($task) {
- echo 'foobar';
- });
-
- $task = $this->tasksQueue()->execute(function ($task) {
- $task->fireEvent('test.foobar');
- }, 'staging');
- }
-}
diff --git a/tests/_meta/coverage.txt b/tests/_meta/coverage.txt
deleted file mode 100644
index 880dfdbf0..000000000
--- a/tests/_meta/coverage.txt
+++ /dev/null
@@ -1,68 +0,0 @@
-
-
-Code Coverage Report:
- 2014-03-08 19:50:07
-
- Summary:
- Classes: 45.16% (14/31)
- Methods: 80.30% (163/203)
- Lines: 90.04% (967/1074)
-
-\Rocketeer::Igniter
- Methods: 90.00% ( 9/10) Lines: 98.28% ( 57/ 58)
-\Rocketeer::LogsHandler
- Methods: 100.00% ( 6/ 6) Lines: 100.00% ( 27/ 27)
-\Rocketeer::ReleasesManager
- Methods: 94.12% (16/17) Lines: 97.14% ( 68/ 70)
-\Rocketeer::Rocketeer
- Methods: 96.00% (24/25) Lines: 98.31% (116/118)
-\Rocketeer::Server
- Methods: 69.23% ( 9/13) Lines: 86.49% ( 64/ 74)
-\Rocketeer::TasksHandler
- Methods: 81.82% ( 9/11) Lines: 91.94% ( 57/ 62)
-\Rocketeer::TasksQueue
- Methods: 63.64% ( 7/11) Lines: 89.33% ( 67/ 75)
-\Rocketeer\Plugins::Notifier
- Methods: 75.00% ( 3/ 4) Lines: 87.50% ( 21/ 24)
-\Rocketeer\Scm::Git
- Methods: 100.00% ( 7/ 7) Lines: 100.00% ( 10/ 10)
-\Rocketeer\Tasks::Check
- Methods: 75.00% ( 6/ 8) Lines: 92.65% ( 63/ 68)
-\Rocketeer\Tasks::Cleanup
- Methods: 100.00% ( 2/ 2) Lines: 100.00% ( 11/ 11)
-\Rocketeer\Tasks::Closure
- Methods: 100.00% ( 5/ 5) Lines: 100.00% ( 9/ 9)
-\Rocketeer\Tasks::CurrentRelease
- Methods: 100.00% ( 1/ 1) Lines: 100.00% ( 11/ 11)
-\Rocketeer\Tasks::Deploy
- Methods: 40.00% ( 2/ 5) Lines: 81.40% ( 35/ 43)
-\Rocketeer\Tasks::Ignite
- Methods: 100.00% ( 4/ 4) Lines: 100.00% ( 20/ 20)
-\Rocketeer\Tasks::Rollback
- Methods: 50.00% ( 1/ 2) Lines: 61.11% ( 11/ 18)
-\Rocketeer\Tasks::Setup
- Methods: 50.00% ( 1/ 2) Lines: 96.30% ( 26/ 27)
-\Rocketeer\Tasks::Teardown
- Methods: 100.00% ( 1/ 1) Lines: 100.00% ( 7/ 7)
-\Rocketeer\Tasks::Test
- Methods: 100.00% ( 1/ 1) Lines: 100.00% ( 2/ 2)
-\Rocketeer\Tasks::Update
- Methods: 100.00% ( 1/ 1) Lines: 100.00% ( 10/ 10)
-\Rocketeer\Traits::AbstractLocatorClass
- Methods: 100.00% ( 4/ 4) Lines: 100.00% ( 19/ 19)
-\Rocketeer\Traits::Plugin
- Methods: 100.00% ( 2/ 2) Lines: 100.00% ( 5/ 5)
-\Rocketeer\Traits::Scm
- Methods: 100.00% ( 3/ 3) Lines: 100.00% ( 9/ 9)
-\Rocketeer\Traits::Task
- Methods: 72.73% ( 8/11) Lines: 88.89% ( 32/ 36)
-\Rocketeer\Traits\BashModules::Binaries
- Methods: 50.00% ( 4/ 8) Lines: 89.09% ( 49/ 55)
-\Rocketeer\Traits\BashModules::Core
- Methods: 91.67% (11/12) Lines: 94.12% ( 64/ 68)
-\Rocketeer\Traits\BashModules::Filesystem
- Methods: 80.00% ( 8/10) Lines: 94.12% ( 32/ 34)
-\Rocketeer\Traits\BashModules::Flow
- Methods: 100.00% ( 6/ 6) Lines: 100.00% ( 22/ 22)
-\Rocketeer\Traits\BashModules::Scm
- Methods: 66.67% ( 2/ 3) Lines: 96.88% ( 31/ 32)
diff --git a/tests/_meta/deployments.json b/tests/_meta/deployments.json
deleted file mode 100644
index 4b0a6a22b..000000000
--- a/tests/_meta/deployments.json
+++ /dev/null
@@ -1 +0,0 @@
-{"foo":"bar","current_release":{"production":20000000000000},"directory_separator":"\/","is_setup":true,"webuser":{"username":"www-data","group":"www-data"},"line_endings":"\n","hash":"d41d8cd98f00b204e9800998ecf8427e"}
\ No newline at end of file
diff --git a/tests/_server/foobar/current/.gitkeep b/tests/_server/foobar-stub/current/.gitkeep
similarity index 100%
rename from tests/_server/foobar/current/.gitkeep
rename to tests/_server/foobar-stub/current/.gitkeep
diff --git a/tests/_server/foobar-stub/deployments.json b/tests/_server/foobar-stub/deployments.json
new file mode 100644
index 000000000..565fc8656
--- /dev/null
+++ b/tests/_server/foobar-stub/deployments.json
@@ -0,0 +1 @@
+{"foo":"bar","directory_separator":"\/","is_setup":true,"webuser":{"username":"www-data","group":"www-data"},"line_endings":"\n"}
diff --git a/tests/_server/foobar/releases/.gitkeep b/tests/_server/foobar-stub/releases/.gitkeep
similarity index 100%
rename from tests/_server/foobar/releases/.gitkeep
rename to tests/_server/foobar-stub/releases/.gitkeep
diff --git a/tests/_server/foobar/releases/10000000000000/.gitkeep b/tests/_server/foobar-stub/releases/10000000000000/.gitkeep
similarity index 100%
rename from tests/_server/foobar/releases/10000000000000/.gitkeep
rename to tests/_server/foobar-stub/releases/10000000000000/.gitkeep
diff --git a/tests/_server/foobar/releases/15000000000000/.gitkeep b/tests/_server/foobar-stub/releases/15000000000000/.gitkeep
similarity index 100%
rename from tests/_server/foobar/releases/15000000000000/.gitkeep
rename to tests/_server/foobar-stub/releases/15000000000000/.gitkeep
diff --git a/tests/_server/foobar/releases/20000000000000/.gitkeep b/tests/_server/foobar-stub/releases/20000000000000/.gitkeep
similarity index 100%
rename from tests/_server/foobar/releases/20000000000000/.gitkeep
rename to tests/_server/foobar-stub/releases/20000000000000/.gitkeep
diff --git a/tests/_server/foobar/shared/.gitkeep b/tests/_server/foobar-stub/shared/.gitkeep
similarity index 100%
rename from tests/_server/foobar/shared/.gitkeep
rename to tests/_server/foobar-stub/shared/.gitkeep
diff --git a/tests/_server/foobar/state.json b/tests/_server/foobar-stub/state.json
similarity index 100%
rename from tests/_server/foobar/state.json
rename to tests/_server/foobar-stub/state.json