Rocketeer A fast deployer for the PHP world

Rocketeer

Build Status Latest Stable Version Total Downloads Scrutinizer Quality Score Code Coverage Support via Gittip

Rocketeer is a task runner and deployment package for the PHP world. It is inspired by the Laravel Framework philosophy and thus aims to be fast, elegant, and more importantly easy to use.

Installation

The easiest way is to get the latest compiled version from the website, 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.

Rocketeer also integrates nicely with the Laravel framework, for that refer to the Getting Started pages of the documentation.

Usage

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.

Testing

$ phpunit

Contributing

Please see CONTRIBUTING for details.

Credits

License

The MIT License (MIT). Please see License File for more information.


Available plugins

Why not Capistrano ?

That's a question that's been asked to me, why not simply use Capistrano ? I've used Capistrano in the past, it does everything you want it to do, that's a given.

But, it remains a Ruby package and one that's tightly coupled to Rails in some ways; Rocketeer makes it so that you don't have Ruby files hanging around your app. That way you configure it once and can use it wherever you want in the realm of your application, even outside of the deploy routine. 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.

Table of contents

Getting started

Core concepts

Going further

What's Rocketeer ?

Rocketeer is a fast and easy deploying tool for the PHP world. If you've already used Capistrano in the past you're already familiar with the gist of what it does, and can probably skip this section. The rest of you, bear with me.

Whether you've always deployed manually via FTP, plugins like SFTP for Sublime or via custom-made deploy scripts, Rocketeer is here to help you and automate your burden. Please note that Rocketeer requires an SSH connection – meaning if you're on a shared hosting, I probably can't do anything for you.

Task runner

In its spirit, Rocketeer is a basic SSH task runner, it defines servers, commands to execute on said server, and run them according to various contexts. You can use Rocketeer as such, for this see the Tasks documentation.

Deployments

Rocketeer provides a handful of tasks built-in to deploy and manage your remote projects.

Core folders

At its core, this package's strategy is inspired by Capistrano's and is relatively simple. Before anything you'll provide Rocketeer with a root_folder on your server – this folder is Rocketeer's little self-contained world : whatever it does will be in that folder.

Then you give it your application_name. This is to let Rocketeer handle having multiple applications on the same server. The application name will be used to create a subfolder in the root_folder where everything related to this application will happen. If per example your root_folder is /var/www/ and your application_name is facebook, then Rocketeer will create /var/www/facebook/ and everything it does in the span of this project will happen in that folder. Everything is packaged and contained, that way you can have as many applications on your server and it will still be all smooth.

The strategy

As I said, Rocketeer's folder architecture is inspired by Capistrano's. Because as much as there are aspects of the latter I dislike, you cannot go against something that's been though out and refined for years either.

In the folder mentioned above (/var/www/facebook/), Rocketeer will create three folders.

current is where the latest version of your application will always be. No matter what happens in the other folders, that's the folder you want to serve online. You never ever serve any other folder than this one, if you need to serve a release that is not the latest one (because the latest one is bugged per example) then you need to use Rocketeer's tools to rollback. In our case, the Apache directive will look like this :

<Directory /var/www/facebook/current/public/>
  Options Indexes FollowSymLinks MultiViews
  Order allow,deny
  Allow from all
  AllowOverride All
</Directory>

The second folder, releases, is where the history of your application is stored. Every time you hit deploy, a timestamped folder will be created in releases (20130721010101 per example for 2013-07-21 01:01:01). You can configure in your config file how deep the history goes : by default it will keep the four latest releases. Once a new release is created and is ready to be served, Rocketeer will update the symlink of the current folder to make it point to it. This system is particularly flexible as it allows Rocketeer to simply update what folder current points to in case of rollback.

And finally the third folder, shared, is where files that are shared between each releases are stored. Take per example our Facebook application, it has users that can upload their avatars on it, and they are stored in public/users/avatars. This is all fine until you decide to deploy again and Rocketeer creates a new release pristine folder from scratch where your uploaded images won't be. To solve this problem, in the config file you have a shared array where you can put paths relative to the root folder of your application, like public/users/avatars. Once Rocketeer see that, it will automatically move the folder avatars to shared and from there, every time you deploy, the new release will inherit all the shared folders. By default Rocketeer always shares the logs of the application so that an history of the Exceptions that occurred is kept, but you can add as many folders as you like depending of your application. If you have an SQLite database that is stored in a file, you might want to share it too.

To sum it up, here is what your remote server will look like :

| var
|-- www
  |-- facebook
    |-- current => /var/www/facebook/releases/20130721000000
    |-- releases
    |  |-- 20130721000000
    |  |  |-- app
    |  |     |-- storage
    |  |       |-- logs => /var/www/facebook/shared/app/storage/logs
    |  | 20130602000000
    |-- shared
      |-- app
        |-- storage
          |-- logs

Setup

Installing Rocketeer on a project is quite easy, there are several ways to do so.

Via the compiled archive

The easiest way is to just get the archive by doing downloading it at the following addresssand putting it in your application's directory.

You can also install it globally by doing mv rocketeer.phar /usr/local/bin/rocketeer which will then give you a global rocketeer command to use in any folder.

Then you just need to type rocketeer ignite. You'll get asked a series of question to setup your project and you should be good to go.

With Composer

Locally

You can also install Rocketeer with Composer as any package :

composer require anahkiasen/rocketeer:dev-master

php vendor/bin/rocketeer ignite

And that's all, the configuration file referenced below will be created at yourapp/.rocketeer/.

Use php vendor/bin/rocketeer to access Rocketeer's commands or simply alias it : alias rocketeer=php vendor/bin/rocketeer. You can also add vendor/bin to your PATH to always have local binaries accessible.

Globally

As with all Composer packages, Rocketeer can also be installed as a global dependency by preprending global :

composer global require anahkiasen/rocketeer:dev-master

rocketeer ignite

Integration with Laravel

Rocketeer integrates nicely with Laravel, although you'll need to do some more steps, depending on whether you're using Laravel 4.0 or later. To check what version you're using, just type php artisan --version in your terminal.

Preliminary steps for Laravel 4.0

If you're on 4.0 I highly recommand to use the PHAR archive instead. If you really do want to do this, you'll have a few extra steps as you need to setup the remote component first. If you're on 4.1 you can skip to the next step.

First, add this to you composer.json file in the require section :

"illuminate/remote": "dev-master",

Then add this at the end of your composer.json :

"repositories": [
  {
    "type": "vcs",
    "url": "https://github.com/changwuf31/remote"
  }
],

Run update, then add this to your app/config/app.php file in the providers array :

'Illuminate\Remote\RemoteServiceProvider',

Installing Rocketeer

Now type the following composer require anahkiasen/rocketeer:dev-master.

You'll need to add these lines to the providers array in your app/config/app.php file :

'Rocketeer\RocketeerServiceProvider',

Then, this line to the aliases array in your app/config/app.php file :

'Rocketeer' => 'Rocketeer\Facades\Rocketeer',

Then publish the config :

artisan deploy:ignite

Configuration

You'll do most of your configuration in Rocketeer's configuration files, in the .rocketeer folder. If you're on Laravel and added the package as a dependency, you'll instead need to configure files in app/config/packages/anahkiasen/rocketeer/.

Rocketeer recognizes Laravel's remote.php file so you can configure your connections here if you want, Rocketeer will read it first before falling back to the configuration file.

There are a lot of options in the configuration file, each is explained thoroughly so take your time to read the comments carefully.

Using Rocketeer

If you're using the archive, you'll need to hit php rocketeer.phar to access the commands, if you're on Laravel and installed it as a package, you'll want to hit the php artisan deploy: namespace. Per example the update command would be accessed as php rocketeer.phar update with the archive, and php artisan deploy:update with the package.

Once you're done with the configuration, it is recommended to run the php rocketeer.phar check command, it will run various commands on the server to check whether the latter is ready to receive your application.

Then you can just hit php rocketeer.phar deploy. It will create an initial release on your remote server. Afterwards, to update it you can either run the same command again which will create an entirely new release, or simply do an php rocketeer.phar update which will update the repository and dependencies of your application.

Folder organization

Rocketeer recognizes its custom configuration in the folder you currently are in. Same thing for the custom tasks.php and events.php files, so you could have that organization :

| .rocketeer
| -- config.php
| -- hooks.php
| -- paths.php
| -- remote.php
| -- scm.php
| -- stages.php
| -- events.php
| -- tasks.php
| rocketeer.phar
| index.php
| public
| etc.

Tasks

An important concept in Rocketeer is Tasks : most of the commands you see right above are using predefined Tasks underneath : Rocketeer\Tasks\Setup, Rocketeer\Tasks\Deploy, etc. A lot of those are already defined by Rocketeer, but you can add your own tasks to Rocketeer to execute custom actions on your server too.

A task can be three things :

  • A simple one-line command which will be executed in the latest release's folder, like composer install, or an array of one-line commands
  • A closure, giving you access to Rocketeer's core helpers to perform more advanced actions
  • And finally a class, extending the Rocketeer\Traits\Task class, giving you full at-home control. All custom-made Tasks must have at least an execute method. And that's all.

Each level gives you a little more control and comfort – this is intentional, if you need more control than what Closures give you, then you probably need a class.


Hooking into Rocketeer's Tasks

What most user will do is hook into the existing Rocketeer's Tasks. Rocketeer provides a built-in events system that allows you – at minimum – to execute actions before or after a Task's execution.

Defining Tasks in the config file

You can hook into any task via the tasks array in Rocketeer's config file. The syntax is pretty basic, here you can see an example with the three types of Tasks mentionned above :

<?php
'after' => array(
  'setup' => array(

    // Commands
    'composer install',

    // Actual Tasks classes
    'Rocketeer\Tasks\Cleanup',
    'MyNamespace\MyTaskClass',

    // Closures
    function($task) {
      $tests = $task->runForCurrentRelease('phpunit --coverage-html=tests/coverage');

      if ($tests) {
        $task->command->info('Tests ran perfectly dude !');
      } else {
        $task->command->error('Aw man, tests failed and stuff')
      }
    },

  ),
?>

Defining Tasks using the facade

Rocketeer also provides you with a facade to use, if you don't want to put stuff in the config file, as it can get dirty with closures.

Rocketeer allows two things : creating a .rocketeer/tasks.php file where all your hooks are contained, or if you have more files like classes and want one file per task, you can simply create a .rocketeer/tasks/ folder and every task you put in it will automatically be loaded by Rocketeer.

<?php
use Rocketeer\Facades\Rocketeer;

Rocketeer::before('deploy', function($task) {
  $task->command->info('Sup guys');
});

Rocketeer::after(['update', 'deploy'], array(
  'composer install',
  'bower install'
));

Rocketeer::after('deploy', 'MyClass');
?>

You give as first argument the name of the name of the Task you'd like to act on (or an array of names), and then your task. Again, you can use the three types of tasks : strings, closures or classes.

Defining your own Tasks

Rocketeer also provides you with a task-running system to create, manage and run your own tasks.

Via the facade

<?php
Rocketeer::task('composer', 'composer install');

Rocketeer::task('composer', array(
  'composer self-update',
  'composer install',
));

Rocketeer::task('phpunit', function ($task) {
  return $task->runForCurrentRelease('phpunit');
});
?>

These tasks will be automatically registered with Rocketeer, you'll then be able to execute them either via the CLI by doing per example php rocketeer composer or via the facade :

Rocketeer::execute('composer');

Rocketeer::execute(['composer', 'phpunit']);

Via classes

<?php
namespace MyTasks;

class Migrate extends Rocketeer\Traits\Task
{
  /**
   * Description of the Task
   *
   * @var string
   */
  protected $description = 'Migrates the database';

  /**
   * Executes the Task
   *
   * @return void
   */
  public function execute()
  {
    $this->command->info('Running migrations');
    $this->runForCurrentRelease('php artisan migrate --seed');
  }
}
?>

Classes aren't automatically registered with Rocketeer so you'll need to do that manually. You can either do so via the config file, in the tasks.custom array :

'custom' => array(
  'MyTasks\Migrate',
),

Or via the facade :

Rocketeer::add('MyTasks\Migrate');

And there you go, tadah !

artisan

Executing tasks

Once a task is defined, it will be available in two places : the command line interface, and the facade.

If you registered a task named composer per example you'll be able to do this :

$ php rocketeer composer

Or in your PHP code (in tasks.php per example) :

Rocketeer::execute('composer');

You can execute multiple tasks by passing an array of tasks. One thing that is crucial to remember : Rocketeer will always process the queue you pass to it, that means you can pass anything that is considered a task :

  • A string command
  • A closure
  • The name of a task
  • The class of a task
<?php
Rocketeer::execute(array(
  'my-task',

  'composer install',

  'Rocketeer\Tasks\Deploy',

  function ($task) {
    return $task->run('ls');
  },
));
?>

Writing Tasks

Core methods

The core methods of any Task is the run method, this is the one that lies at the bottom of nearly every other helper. It just runs commands on the remote server, and returns the output.

<?php
$folders = $this->run('ls');
?>

You can also pass it an array of commands to execute. Now, note this because it's important : every call to run is self contained. Meaning this (pwd returns the current folder) :

<?php
// Returns /
$this->run('cd first-folder');
$folder = $this->run('pwd');

// Returns /first-folder/
$folder = $this->run(array(
  'cd first-folder',
  'pwd',
));
?>

To automate running tasks in folders, two helpers exist : runInFolder and runForCurrentRelease. The first one will run one or more tasks in a folder, while the other one will run one or more tasks in the current release's folder.

<?php
$this->run(array(
  'cd /home/www/website/releases/123456789',
  'ls',
));

// Is the same as

$this->runInFolder('releases/123456789', 'ls');

// Is the same as

$this->runForCurrentRelease('ls');
?>

Folder helpers

A few folder/file-manipulation methods are also present, they're very basic and just abstract low-level bash commands but, hey, they're good to have :

<?php
$this->move('folder/file.php', 'new-folder/file.php');

$array = $this->listContents('folder');

$boolean = $this->fileExists('file.php');
$boolean = $this->fileExists('folder');

$this->createFolder('folder');
$this->removeFolder('folder');

$this->symlink('folder-a', 'folder-b');

$phpunit = $this->which('phpunit', 'vendor/bin/phpunit'); // Second argument is fallback
?>

Some methods are used by other Rocketeer tasks and can be used by you to create your own. All of them are relative to the current release.

<?php
// Run tests
$boolean = $this->runTests();
$boolean = $this->runTests('--stop-on-failure');

// Run migrations
$this->runMigrations();
$this->runMigrations(true); // Seeds the database too

// Run Composer
$this->runComposer();

// Set folders as web-writtable
$this->setPermissions('public/images/users');
?>

External methods

Tasks also have access to the other classes of Rocketeer. You can call other tasks :

<?php
$this->executeTask('Rollback');

And call other classes's methods. There are five main classes surrounding Tasks which you'll like want to use :

  • The Command is the command executing the Task. You'll use it to display messages and fetch options and arguments
  • The Releases Manager handles releases and their paths
  • The Server class keeps up to date the deployments.json file which tracks the remote server's state
  • The Remote is your entry point to the server, it's the class the run method uses
  • The Rocketeer classes handles informations provided by the User (config and stuff)
<?php
$this->releasesManager->getCurrentRelease();
$this->releasesManager->getPathToRelease('123456789');
$this->releasesManager->getPreviousRelease();
$this->releasesManager->getReleases();

$this->server->setValue('key', 'value');
$this->server->getValue('key');
$this->server->getRepository();

$this->rocketeer->getHomeFolder();
$this->rocketeer->getApplicationName();
$this->rocketeer->getRepository();
$this->rocketeer->getOption('remote.shared');
$this->rocketeer->getStage();

$this->command->argument('argument');
$this->command->option('verbose');
$this->command->info('It works !');
$this->command->error('It does not work !');
$this->command->comment('Now running cleanup');

$this->remote->run(array(
  'cd folder', 'ls',
));
?>

This is not all of it of course, you can check out these classes's sources for a full documented list of the methods available.

Connections and stages

You'll meet to main concepts when it comes to communicating with your server : Connections and Stages.

Connections

Configuration

Connections represent the various servers you may want to connect to. You'll configure those in config.php. Here's what a connection may look like :

'connections' => array(
  'production' => array(
    'host'      => 'my-server.com',
    'username'  => 'johndoe',
    'password'  => '',
    'key'       => '/Users/johndoe/.ssh/id_rsa',
    'keyphrase' => '',
  ),
),

You can connect by two ways to a server : via a password, or via an SSH key. So if you're connection with a password you can leave key and keyphrase empty, etc.

Important note : you do not have to put your credentials here. Rocketeer uses a fallback system that allows you to not have to put your credentials in files that may be tracked. So if you leave all of those fields empty, Rocketeer will prompt you for them and store them in an hidden file. All that matters is that your connection is defined. Meaning this :

'connections' => array(
  'production' => array(),
),

Is a perfectly valid connection.

Multiple connections

Now this was a fairly basic connection. Now imagine a more complicated setup, you might have multiple connections :

'connections' => array(
  'production' => array(
    'host' => 'my-server.com',
  ),
  'staging' => array(
    'host' => 'staging.my-server.com',
  ),
),

From there to interact with those two connections, there are multiple ways. First you can change the value of the default array in the config.php file we've been editing :

'default' => array('production', 'staging'),

What this array means is : whenever you'll execute a Task, Rocketeer will execute it on all of those connections. But now, that may not be what you want, you may want to cherry-pick what you do on what. For this you'll use the --on flag.

Take a deploy command, you'd execute it like this normally : rocketeer deploy. To select which connections to deploy to, you'll pass one or more connections to it as a flag :

$ rocketeer deploy --on="staging"
$ rocketeer deploy --on="staging,production"

Stages

In the realm of Rocketeer, stages are separated environments on a given connection. Here is a classic folder organization scaffolded by Rocketeer :

| my-app
| -- current
| -- releases
| ----- 20140119010101
| -- shared

A server with multiple stages, say testing and production will on the other hand look like this :

| my-app
| -- testing
| ----- current
| ----- releases
| -------- 20140119010101
| ----- shared
| -- production
| ----- current
| ----- releases
| -------- 20140519413121
| ----- shared

Everything you do on a stage is self-contained.

Configuration

Configuring stages is fairly easy, you'll want to open the stages file. To define how many stages you have and their names, simply fill the array (per example for our setup above) :

'stages' => array('testing', 'production'),

Just like for connections, you can chose which stages to execute tasks on by default via the default array. It works exactly the same way :

'default' => array('testing'),

Multiple stages

Just like for connections, you can pass a flag to execute tasks on a particular stage :

$ rocketeer test --stage="testing"

Contextual configuration

Now that's all good and stuff but, you only have one set of configuration files, if you have three servers each with three stages, surely the configuration will vary from one to the other.

Rocketeer handles contextual configuration via nested arrays, which you can find in the main config.php file (where you define your connections). Let's take a fairly classic example, you have two servers, and the path to PHP is different in both. Now usually the path to PHP can be found in the paths.php file, under php. So here's how we'd define it for our two connections :

'on' => array(
  'connections' => array(

    'staging' => array(
      'paths' => ['php' => 'PATH-TO-PHP'],
    ),
    'production' => array(
      'paths' => ['php' => 'PATH-TO-PHP'],
    ),

  ),
),

Everything you'll put in either staging or production here will be a miniature version of the configuration files Rocketeer created. To override any key, simply create an array named like the file (here paths) and put the config in there.

File-based alternative

Now as this can get quite lengthy and you don't want to work in an array nested in an array nested in an array nested in an array, here's how you can proceed if you have a lot of things to override.

Create a folder for each connection and/or stage you have, and copy the base configuration in those. Then in your main config.php simply do this :

'on' => array(
  'connections' => array(

    'staging' => array(
      'paths' => include 'staging/paths.php',
      'scm'   => include 'staging/scm.php',
    ),
    'production' => array(
      'paths' => include 'production/paths.php',
    ),

  ),
),

And so on.

Events

Rocketeer is at its core driven by Tasks. Each of these tasks has a whole ecosystem of events fired in their lifetime, powered by the illuminate/events components. Therefor, when you're doing this :

Rocketeer::before('deploy', 'MyApp\MyCustomTask');

What you're actually doing is, more simply put :

$app['events']->listen('rocketeer.deploy.before', 'MyApp\MyCustomTask@execute');

This is not "just this" of course, as Rocketeer does some magic on the second argument so that Rocketeer::before('deploy', 'composer install') transforms composer install into an actual Task class the Events Dispatcher can call. But in its concept, it's just your basic Dispatcher (or Observer if you like) system.

Registering events

First of all, you can register events in any file that is autoloaded by Composer. If you're using the Rocketeer archive and have no particular autoloading, Rocketeer will by default try to load .rocketeer/events.php or if you have more events and want to split them in multiple files, it'll autoload every file in a .rocketeer/events/* folder.

Listening to events

All tasks in Rocketeer fire two basic events : before and after on which you can hook. But some tasks fire internal events, during their execution, allowing you to execute actions at various points in their lifetime. To listen to these events, there are two methods you can use. Say you want to execute something before the Deploy task symlinks the current folder to the latest release :

Rocketeer::addTaskListeners('deploy', 'before-symlink', function ($task) {
  echo $task->releasesManager->getCurrentRelease();
});

// Or

Rocketeer::listenTo('deploy.before-symlink', function ($task) {
  echo $task->releasesManager->getCurrentRelease();
});

These two methods look very similar but in appearance only, the first one actually calls the second but allows you to pass an array of tasks names or instances :

Rocketeer::addTaskListeners(['deploy', new Rocketeer\Tasks\Setup], 'some-event', function ($task) {
  echo $task->releasesManager->getCurrentRelease();
});

Firing events in your own tasks

You can fire events in your own tasks too by using the fireEvent method :

class MyTask extends Rocketeer\Traits\Task
{
    public function execute()
    {
        $this->fireEvent('making-coffee');
        $this->makeCoffee();

        $this->fireEvent('drinking-coffee');
        $this->drinkCoffee();
    }
}

You don't need to namespace your events, as Rocketeer will do it for you. It will first namespace all events in the rocketeer. space, then add a slug of the current task, so the two events above would be fired as rocketeer.my-task.making-coffee and rocketeer.my-task.drinking-coffee.

Now, you can also fire events in Closure Tasks, by you will need to manually namespace those : as all Closure Tasks are at their core anonymous functions, they're anonymous tasks as well which means all events will get fired in rocketeer.closure :

Rocketeer::after('deploy', function ($task) {
    $task->fireEvent('some-event'); // Will get fired as rocketeer.closure.some-event
});

This is not a problem per se but can get problematic if you have a lot of Closure Tasks.

Firing events in a particular order

As the events system use illuminate/events, it inherits its priority methods. That means that for every method that adds listeners to an event, you can specify a priority for these listeners. Simple example :

Rocketeer::after('deploy', function ($task) {
    $task->campfire->notify('New version deployed on the server');
});

Rocketeer::after('deploy', function ($task) {
    $task->runForCurrentRelease(['npm install', 'grunt']);
});

Now ideally you'd want your chat room on Campfire to be notified about the deployment only when the NPM packages are installed and Grunt has run its course, because an error might happen there. For this you add a priority at the end of the call : priority is a basic integer, listeners with lowest priority will be fired at the end, and vice versa. So to make sure our Campfire notification would get sent at really the very end of all our listeners, we can just do this :

Rocketeer::after('deploy', function ($task) {
    $task->campfire->notify('New version deployed on the server');
}, -10);

Rocketeer::after('deploy', function ($task) {
    $task->runForCurrentRelease(['npm install', 'grunt']);
});

Here are some methods that accept a priority argument :

Rocketeer::before($task, $listeners, $priority = 0)
Rocketeer::after($task, $listeners, $priority = 0)
Rocketeer::listenTo($event, $listeners, $priority = 0)
Rocketeer::addTaskListeners($tasks, $event, $listeners, $priority = 0)

Halting the queue in an event

Whenever an event returns a strict false, Rocketeer will recognize it and halt the whole queue. This is useful to do checks before certain major events and cancel per example deployment if some conditions are not met.

To halt the queue you can either simply return false :

Rocketeer::before('deploy', function ($task) {
    if (!$something) {
        return false;
    }
});

Or if you want to pass additional details, you can use the Task::halt method which will display as error whatever you pass to it, and then return false :

Rocketeer::before('deploy', function ($task) {
    if (!$something) {
        return $this->halt('Something was wrong here, cancelling');
    }
});

Whatever you use, Rocketeer will display an additional error message stating the queue was canceled and by what Task.

Plugins

You can add functionalities to Rocketeer or simply bundle common tasks into reusable modules by using the plugins system. A plugin at its core is a class implementing the Rocketeer\Traits\Plugin abstract.

Adding a plugin

To add a plugin to an application, you simply need to add it via Composer and call Rocketeer::plugin. You can pass in configuration as the second parameter :

tasks.php

Rocketeer::plugin('Rocketeer\Plugins\Campfire', array(
  'domain' => 'MyDomain',
  'room'   => 1234,
));

If you're on Laravel, you simply need to add the plugin's ServiceProvider to your application which will do this for you. You can then publish the package's configuration in app/config/packages by doing artisan config:publish rocketeer/my-plugin.

Creating a plugin

There's two methods a plugin will most likely have on its class : register(Container $app) and onQueue(TasksQueue $queue).

  • The first one will be used to bind eventual instances into Rocketeer's container, that is a facultative method that if overrided needs to return the Container at the end.
  • The second one is used to add actions or tasks to Rocketeer : the TasksQueue class is the one behing the Rocketeer facade so most of the methods you're familiar with are available on it : $queue->before('deploy', ...), $queue->add('MyCustomTask') etc.

Here is an example dumbed-down version of the current Campfire plugin, using rcrowe/Campfire as a dependency :

use rcrowe\Campfire;
use Rocketeer\TasksQueue;
use Rocketeer\Traits\Plugin;

class RocketeerCampfire extends Plugin
{
  /**
   * Bind additional classes to the Container
   *
   * @param Container $app
   *
   * @return void
   */
  public function register(Container $app)
  {
    $app->bind('campfire', function ($app) {
      return new Campfire(['domain' => '...', 'key' => '...', 'room' => '...']);
    });

    return $app;
  }

  /**
   * Register Tasks with Rocketeer
   *
   * @param TasksQueue $queue
   *
   * @return void
   */
  public function onQueue(TasksQueue $queue)
  {
    $queue->after('deploy', function ($task) {
      $application = $task->rocketeer->getApplicationName();

      $task->campfire->send($application. ' was deployed!');
    });
  }
}

As you can see a plugin can be something really simple you can save up somewhere and reuse from project to project.

Plugin configurations

Plugins can have their own configuration, by creating a config folder in your plugin's src folder. You'll need to set the path to it on your class, in the constructor per example :

function __construct()
{
  $this->configurationFolder = __DIR__.'/../../config';
}

In that folder you can then create a config.php file to put your options as a PHP array. The configuration for your plugin will then be available via the Config class in your tasks, under the my-plugin:: namespace, per example if your class is RocketeerHipchat, you'll get the configuration by doing $task->config->get('rocketeer-hipchat::myoption').

On Laravel

Note, if you're in a Laravel application, you can add a plugin's ServiceProvider to it and then do artisan config:publish rocketeer/the-plugin to get an overridable configuration file in app/config/packages.

Changelog

1.2.2

  • The Notifier plugin module not has a hook for before and after deployment
  • Add ability to disable composer completely
  • Add support for ssh-agent for secure connections
  • 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

  • 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

  • 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 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

  • Added a Rocketeer\Plugins\Notifier class to easily add third-party deployment notification plugins
  • Fixed a bug where the custom tasks/events file/folders might not exist

1.1.1

  • 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

  • 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
  • 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

  • Rocketeer is now available as a standalone 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
  • 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

  • Rocketeer now supports SVN
  • Rocketeer now has a Campfire plugin
  • Add option to manually set remote variables when encountering problems
  • Add keyphrase support

0.8.0

  • Rocketeer can now have specific configurations for stages and connections
  • Better handling of multiple connections
  • Added facade shortcuts Rocketeer::execute(Task) and Rocketeer::on(connection[s], Task) to execute commands on the remote servers
  • Added the --list flag on the rollback command to show a list of available releases and pick one to rollback to
  • Added the --on flag to all commands to specify which connections the task should be executed on (ex. production, staging,production)
  • Added deploy:flush to clear Rocketeer's cache of credentials

0.7.0

  • Rocketeer can now work outside of Laravel
  • Better handling of SSH keys
  • 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

  • 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
  • Fix a bug where incorrect SCM urls would be generated

0.6.4

  • Make the output of commands in realtime when --verbose instead of when the command is done
  • 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

  • Application name is now always sluggified as a security
  • 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

  • Make the Check task check for the remote presence of the configured SCM
  • Fix Rocketeer not being able to use a composer.phar on the server

0.6.1

  • Fix a bug where the configured user would not have the rights to set permissions

0.6.0

  • Add multistage strategy
  • Add compatibility to Laravel 4.0
  • Migrations are now under a --migrate flag
  • Split Git from the SCM implementation (requires a config update)
  • 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

  • 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
  • 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

  • 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

0.3.2

  • Fixed wrong tag used in deploy:cleanup

0.3.1

  • 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

  • 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

  • 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

  • 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

  • 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

Troubleshooting

When I deploy my old scripts are running instead of the new, but the deployment was successful

PHP 5.5 has Opcache built-in. Opcache is supposed to invalidate when the script has changed, but it has issue when the script is in a symlinked folder and called from the webserver.

Basically, Opcache "thinks" that the script is /var/www/hello/current/public/index while in reality, it's /var/www/hello/releases/20140101011/public/index.