├── tests
├── server
│ └── foobar
│ │ ├── current
│ │ └── .gitkeep
│ │ ├── releases
│ │ ├── .gitkeep
│ │ ├── 10000000000000
│ │ │ └── .gitkeep
│ │ └── 20000000000000
│ │ │ └── .gitkeep
│ │ └── shared
│ │ └── .gitkeep
├── meta
│ ├── deployments.json
│ ├── MyCustomTask.php
│ └── coverage.txt
├── Tasks
│ ├── CheckTest.php
│ ├── RollbackTest.php
│ ├── TestTest.php
│ ├── SetupTest.php
│ ├── CurrentReleaseTest.php
│ ├── CleanupTest.php
│ ├── TeardownTest.php
│ ├── DeployTest.php
│ ├── IgniteTest.php
│ └── UpdateTest.php
├── ConsoleTest.php
├── TasksTest.php
├── ReleasesManagerTest.php
├── ServerTest.php
├── BashTest.php
├── TasksQueueTest.php
├── RocketeerTest.php
└── _start.php
├── .gitignore
├── .gitmodules
├── rocketeer
├── CONTRIBUTING.md
├── .travis.yml
├── src
├── Rocketeer
│ ├── Console.php
│ ├── Facades
│ │ ├── Console.php
│ │ └── Rocketeer.php
│ ├── Tasks
│ │ ├── Test.php
│ │ ├── Closure.php
│ │ ├── Update.php
│ │ ├── Ignite.php
│ │ ├── Cleanup.php
│ │ ├── CurrentRelease.php
│ │ ├── Teardown.php
│ │ ├── Rollback.php
│ │ ├── Setup.php
│ │ ├── Deploy.php
│ │ └── Check.php
│ ├── Commands
│ │ ├── DeployCommand.php
│ │ ├── DeployTestCommand.php
│ │ ├── DeployFlushCommand.php
│ │ ├── DeployUpdateCommand.php
│ │ ├── DeployDeployCommand.php
│ │ ├── BaseTaskCommand.php
│ │ ├── DeployRollbackCommand.php
│ │ └── BaseDeployCommand.php
│ ├── Scm
│ │ ├── ScmInterface.php
│ │ ├── Git.php
│ │ └── Svn.php
│ ├── Traits
│ │ ├── Scm.php
│ │ └── Task.php
│ ├── ReleasesManager.php
│ ├── Server.php
│ ├── RocketeerServiceProvider.php
│ ├── Rocketeer.php
│ ├── Bash.php
│ └── TasksQueue.php
└── config
│ └── config.php
├── provides.json
├── composer.json
├── phpunit.xml
├── README.md
├── CHANGELOG.md
└── composer.lock
/tests/server/foobar/current/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/server/foobar/releases/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/server/foobar/shared/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/server/foobar/releases/10000000000000/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/server/foobar/releases/20000000000000/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | storage
3 | tests/coverage
4 | vendor
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "docs"]
2 | path = docs
3 | url = https://github.com/Anahkiasen/rocketeer.wiki.git
4 |
--------------------------------------------------------------------------------
/rocketeer:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | pretendTask('Check');
8 | $task->execute();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/tests/ConsoleTest.php:
--------------------------------------------------------------------------------
1 | assertContains('Rocketeer version 0', $console);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/tests/Tasks/RollbackTest.php:
--------------------------------------------------------------------------------
1 | task('Rollback')->execute();
8 |
9 | $this->assertEquals(10000000000000, $this->app['rocketeer.releases']->getCurrentRelease());
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tests/Tasks/TestTest.php:
--------------------------------------------------------------------------------
1 | pretendTask('Test')->execute();
7 |
8 | $this->assertEquals('cd '.$this->server.'/releases/20000000000000', $tests[0]);
9 | $this->assertContains('phpunit --stop-on-failure', $tests[1]);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tests/Tasks/SetupTest.php:
--------------------------------------------------------------------------------
1 | app['files']->deleteDirectory($this->server);
8 | $this->task('Setup')->execute();
9 |
10 | $this->assertFileExists($this->server);
11 | $this->assertFileExists($this->server.'/current');
12 | $this->assertFileExists($this->server.'/releases');
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/tests/Tasks/CurrentReleaseTest.php:
--------------------------------------------------------------------------------
1 | task('CurrentRelease')->execute();
8 | $this->assertContains('20000000000000', $current);
9 |
10 | $this->app['rocketeer.server']->setValue('current_release', 0);
11 | $current = $this->task('CurrentRelease')->execute();
12 | $this->assertEquals('No release has yet been deployed', $current);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/tests/Tasks/CleanupTest.php:
--------------------------------------------------------------------------------
1 | task('Cleanup');
8 | $output = $cleanup->execute();
9 |
10 | $this->assertFileNotExists($this->server.'/releases/10000000000000');
11 | $this->assertEquals('Removing 1 release from the server', $output);
12 |
13 | $output = $cleanup->execute();
14 | $this->assertEquals('No releases to prune from the server', $output);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Rocketeer/Facades/Console.php:
--------------------------------------------------------------------------------
1 | command->info('Testing the application');
27 |
28 | return $this->runTests();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/Tasks/TeardownTest.php:
--------------------------------------------------------------------------------
1 | task('Teardown')->execute();
8 |
9 | $this->assertFileNotExists($this->deploymentsFile);
10 | $this->assertFileNotExists($this->server);
11 | }
12 |
13 | public function testCanAbortTeardown()
14 | {
15 | $command = Mockery::mock('Command');;
16 | $command->shouldReceive('confirm')->andReturn(false);
17 | $command->shouldReceive('info')->andReturnUsing(function ($message) { return $message; });
18 |
19 | $message = $this->task('Teardown', $command)->execute();
20 |
21 | $this->assertEquals('Teardown aborted', $message);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Rocketeer/Commands/DeployCommand.php:
--------------------------------------------------------------------------------
1 | option('version')) {
27 | return $this->line('Rocketeer version '.Rocketeer::VERSION.'');
28 | }
29 |
30 | // Deploy
31 | return parent::fire();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Rocketeer/Commands/DeployTestCommand.php:
--------------------------------------------------------------------------------
1 | input->setOption('verbose', true);
31 |
32 | return $this->fireTasksQueue('Rocketeer\Tasks\Test');
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Rocketeer/Commands/DeployFlushCommand.php:
--------------------------------------------------------------------------------
1 | laravel['rocketeer.server']->deleteRepository();
31 | $this->info("Rocketeer's cache has been properly flushed");
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/Tasks/DeployTest.php:
--------------------------------------------------------------------------------
1 | app['config']->shouldReceive('get')->with('rocketeer::scm')->andReturn(array(
8 | 'repository' => 'https://github.com/Anahkiasen/rocketeer.git',
9 | 'username' => '',
10 | 'password' => '',
11 | ));
12 |
13 | $this->task('Deploy')->execute();
14 | $release = $this->app['rocketeer.releases']->getCurrentRelease();
15 |
16 | $releasePath = $this->server.'/releases/'.$release;
17 | $this->assertFileExists($this->server.'/shared/tests/meta/deployments.json');
18 | $this->assertFileExists($releasePath);
19 | $this->assertFileExists($releasePath.'/.git');
20 | $this->assertFileExists($releasePath.'/vendor');
21 |
22 | $this->recreateVirtualServer();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "anahkiasen/rocketeer",
3 | "description": "Rocketeer provides a fast and easy way to deploy your Laravel projects",
4 | "license": "MIT",
5 | "keywords": [
6 | "laravel",
7 | "deployment",
8 | "ssh"
9 | ],
10 | "authors": [
11 | {
12 | "name": "Maxime Fabre",
13 | "email": "ehtnam6@gmail.com"
14 | }
15 | ],
16 | "require": {
17 | "php": ">=5.3.0",
18 | "illuminate/support": "~4",
19 | "illuminate/config": "~4",
20 | "illuminate/console": "~4",
21 | "illuminate/container": "~4",
22 | "illuminate/filesystem": "~4"
23 | },
24 | "require-dev": {
25 | "mockery/mockery": "dev-master",
26 | "nesbot/carbon": "dev-master",
27 | "patchwork/utf8": "dev-master"
28 | },
29 | "minimum-stability": "dev",
30 | "bin": [
31 | "rocketeer"
32 | ],
33 | "autoload": {
34 | "psr-0": {
35 | "Rocketeer": "src/"
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Rocketeer/Tasks/Closure.php:
--------------------------------------------------------------------------------
1 | closure = $closure;
27 | }
28 |
29 | /**
30 | * Get the Task's Closure
31 | *
32 | * @return Closure
33 | */
34 | public function getClosure()
35 | {
36 | return $this->closure;
37 | }
38 |
39 | /**
40 | * Run the Task
41 | *
42 | * @return void
43 | */
44 | public function execute()
45 | {
46 | $closure = $this->closure;
47 |
48 | return $closure($this);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Rocketeer/Scm/ScmInterface.php:
--------------------------------------------------------------------------------
1 | updateRepository();
27 |
28 | // Recompile dependencies and stuff
29 | $this->runComposer();
30 |
31 | // Set permissions
32 | $this->setApplicationPermissions();
33 |
34 | // Run migrations
35 | if ($this->getOption('migrate')) {
36 | $this->runMigrations($this->getOption('seed'));
37 | }
38 |
39 | // Clear cache
40 | $this->runForCurrentRelease('php artisan cache:clear');
41 |
42 | $this->command->info('Successfully updated application');
43 |
44 | return $this->history;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Rocketeer/Tasks/Ignite.php:
--------------------------------------------------------------------------------
1 | app->bound('artisan')) {
27 | return $this->command->call('config:publish', array('package' => 'anahkiasen/rocketeer'));
28 | }
29 |
30 | // Else copy it at the root
31 | $config = __DIR__.'/../../config/config.php';
32 | $root = $this->app['path.base'].'/rocketeer.php';
33 | $this->app['files']->copy($config, $root);
34 |
35 | // Display info
36 | $folder = basename(dirname($root)).'/'.basename($root);
37 | $this->command->line('The Rocketeer configuration was created at '.$folder.'');
38 |
39 | return $this->history;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Rocketeer/Tasks/Cleanup.php:
--------------------------------------------------------------------------------
1 | releasesManager->getDeprecatedReleases();
28 | foreach ($trash as $release) {
29 | $this->removeFolder($this->releasesManager->getPathToRelease($release));
30 | }
31 |
32 | // If no releases to prune
33 | if (empty($trash)) {
34 | return $this->command->comment('No releases to prune from the server');
35 | }
36 |
37 | // Create final message
38 | $trash = sizeof($trash);
39 | $message = sprintf('Removing %d %s from the server', $trash, Str::plural('release', $trash));
40 |
41 | return $this->command->line($message);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Rocketeer/Tasks/CurrentRelease.php:
--------------------------------------------------------------------------------
1 | releasesManager->getCurrentRelease();
28 | if (!$currentRelease) {
29 | return $this->command->error('No release has yet been deployed');
30 | }
31 |
32 | // Create message
33 | $date = Carbon::createFromFormat('YmdHis', $currentRelease)->toDateTimeString();
34 | $state = $this->runForCurrentRelease($this->scm->currentState());
35 | $message = sprintf('The current release is %s (%s deployed at %s)', $currentRelease, $state, $date);
36 |
37 | return $this->command->line($message);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Rocketeer/Commands/DeployUpdateCommand.php:
--------------------------------------------------------------------------------
1 | fireTasksQueue('Rocketeer\Tasks\Update');
33 | }
34 |
35 | /**
36 | * Get the console command options.
37 | *
38 | * @return array
39 | */
40 | protected function getOptions()
41 | {
42 | return array_merge(parent::getOptions(), array(
43 | array('migrate', 'm', InputOption::VALUE_NONE, 'Run the migrations'),
44 | array('seed', 's', InputOption::VALUE_NONE, 'Seed the database after migrating the database'),
45 | ));
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
17 |
18 |
19 |
20 |
21 |
22 | src/Rocketeer
23 |
24 | src/Rocketeer/RocketeerServiceProvider.php
25 | src/Rocketeer/Commands
26 | src/Rocketeer/Facades
27 |
28 |
29 |
30 |
31 |
32 |
33 | tests
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/Rocketeer/Tasks/Teardown.php:
--------------------------------------------------------------------------------
1 | command->confirm('This will remove all folders on the server, not just releases. Do you want to proceed ?');
34 | if (!$confirm) {
35 | return $this->command->info('Teardown aborted');
36 | }
37 |
38 | // Remove remote folders
39 | $this->removeFolder();
40 |
41 | // Remove deployments file
42 | $this->server->deleteRepository();
43 |
44 | $this->command->info('The application was successfully removed from the remote servers');
45 |
46 | return $this->history;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Rocketeer/Commands/DeployDeployCommand.php:
--------------------------------------------------------------------------------
1 | fireTasksQueue(array(
33 | 'Rocketeer\Tasks\Deploy',
34 | 'Rocketeer\Tasks\Cleanup',
35 | ));
36 | }
37 |
38 | /**
39 | * Get the console command options.
40 | *
41 | * @return array
42 | */
43 | protected function getOptions()
44 | {
45 | return array_merge(parent::getOptions(), array(
46 | array('tests', 't', InputOption::VALUE_NONE, 'Runs the tests on deploy'),
47 | array('migrate', 'm', InputOption::VALUE_NONE, 'Run the migrations'),
48 | array('seed', 's', InputOption::VALUE_NONE, 'Seed the database after migrating the database'),
49 | ));
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tests/Tasks/IgniteTest.php:
--------------------------------------------------------------------------------
1 | app['artisan'] = null;
8 | $this->app->offsetUnset('artisan');
9 |
10 | $this->app['path.base'] = __DIR__.'/../..';
11 |
12 | // Execute Task
13 | $task = $this->task('Ignite');
14 | $task->execute();
15 |
16 | $root = $this->app['path.base'].'/rocketeer.php';
17 | $this->assertFileExists($root);
18 |
19 | $config = include $this->app['path.base'].'/src/config/config.php';
20 | $contents = include $root;
21 | $this->assertEquals($config, $contents);
22 | }
23 |
24 | public function testCanIgniteConfigurationInLaravel()
25 | {
26 | $this->app['path.base'] = __DIR__.'/../..';
27 | $root = $this->app['path.base'].'/rocketeer.php';
28 |
29 | $command = $this->getCommand();
30 | $command->shouldReceive('call')->with('config:publish', array('package' => 'anahkiasen/rocketeer'))->andReturnUsing(function () use ($root) {
31 | file_put_contents($root, 'foobar');
32 | });
33 |
34 | $task = $this->task('Ignite', $command);
35 | $task->execute();
36 |
37 | $contents = file_get_contents($root);
38 | $this->assertEquals('foobar', $contents);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Tasks/UpdateTest.php:
--------------------------------------------------------------------------------
1 | pretendTask('Update', array(
8 | 'migrate' => true,
9 | 'seed' => true
10 | ));
11 |
12 | $update = $task->execute();
13 | $composer = exec('which composer');
14 |
15 | $matcher = array(
16 | array(
17 | "cd " .$this->server. "/releases/20000000000000",
18 | "git reset --hard",
19 | "git pull",
20 | ),
21 | $composer,
22 | array(
23 | "cd " .$this->server. "/releases/20000000000000",
24 | $composer. " install",
25 | ),
26 | array(
27 | "cd " .$this->server. "/releases/20000000000000",
28 | "chmod -R 755 " .$this->server. "/releases/20000000000000/tests",
29 | "chmod -R g+s " .$this->server. "/releases/20000000000000/tests",
30 | "chown -R www-data:www-data " .$this->server. "/releases/20000000000000/tests",
31 | ),
32 | array(
33 | "cd " .$this->server. "/releases/20000000000000",
34 | "php artisan migrate --seed",
35 | ),
36 | array(
37 | "cd " .$this->server. "/releases/20000000000000",
38 | "php artisan cache:clear",
39 | ),
40 | );
41 |
42 | $this->assertEquals($matcher, $update);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Rocketeer/Traits/Scm.php:
--------------------------------------------------------------------------------
1 | app = $app;
24 | }
25 |
26 | ////////////////////////////////////////////////////////////////////
27 | //////////////////////////////// HELPERS ///////////////////////////
28 | ////////////////////////////////////////////////////////////////////
29 |
30 | /**
31 | * Returns a command with the SCM's binary
32 | *
33 | * @param string $command
34 | *
35 | * @return string
36 | */
37 | public function getCommand($command)
38 | {
39 | return $this->binary. ' ' .$command;
40 | }
41 |
42 | /**
43 | * Execute one of the commands
44 | *
45 | * @param string $command
46 | * @param string $arguments,...
47 | *
48 | * @return mixed
49 | */
50 | public function execute()
51 | {
52 | $arguments = func_get_args();
53 | $command = array_shift($arguments);
54 | $command = call_user_func_array(array($this, $command), $arguments);
55 |
56 | return $this->app['rocketeer.bash']->run($command);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/TasksTest.php:
--------------------------------------------------------------------------------
1 | task->runForCurrentRelease('git init');
8 | $this->task->updateRepository();
9 | $output = $this->task->run('git status');
10 |
11 | $this->assertContains('working directory clean', $output);
12 | }
13 |
14 | public function testCanDisplayOutputOfCommandsIfVerbose()
15 | {
16 | $task = $this->pretendTask('Check', array(
17 | 'verbose' => true,
18 | 'pretend' => false
19 | ));
20 |
21 | ob_start();
22 | $task->run('ls');
23 | $output = ob_get_clean();
24 |
25 | $this->assertContains('tests', $output);
26 | }
27 |
28 | public function testCanPretendToRunTasks()
29 | {
30 | $task = $this->pretendTask();
31 |
32 | $output = $task->run('ls');
33 | $this->assertEquals('ls', $output);
34 | }
35 |
36 | public function testCanGetDescription()
37 | {
38 | $task = $this->task('Setup');
39 |
40 | $this->assertNotNull($task->getDescription());
41 | }
42 |
43 | public function testCanRunMigrations()
44 | {
45 | $task = $this->pretendTask();
46 |
47 | $commands = $task->runMigrations();
48 | $this->assertEquals('php artisan migrate', $commands[1]);
49 |
50 | $commands = $task->runMigrations(true);
51 | $this->assertEquals('php artisan migrate --seed', $commands[1]);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Rocketeer/Commands/BaseTaskCommand.php:
--------------------------------------------------------------------------------
1 | task = $task;
38 | $this->task->command = $this;
39 |
40 | // Set name
41 | $this->name = $name ?: $task->getSlug();
42 | $this->name = 'deploy:'.$this->name;
43 |
44 | // Set description
45 | $this->setDescription($task->getDescription());
46 | }
47 |
48 | /**
49 | * Fire the custom Task
50 | *
51 | * @return string
52 | */
53 | public function fire()
54 | {
55 | return $this->fireTasksQueue($this->task);
56 | }
57 |
58 | /**
59 | * Get the Task this command executes
60 | *
61 | * @return Task
62 | */
63 | public function getTask()
64 | {
65 | return $this->task;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Rocketeer/Commands/DeployRollbackCommand.php:
--------------------------------------------------------------------------------
1 | fireTasksQueue('Rocketeer\Tasks\Rollback');
34 | }
35 |
36 | /**
37 | * Get the console command arguments.
38 | *
39 | * @return array
40 | */
41 | protected function getArguments()
42 | {
43 | return array(
44 | array('release', InputArgument::OPTIONAL, 'The release to rollback to'),
45 | );
46 | }
47 |
48 | /**
49 | * Get the console command options.
50 | *
51 | * @return array
52 | */
53 | protected function getOptions()
54 | {
55 | return array_merge(parent::getOptions(), array(
56 | array('list', 'L', InputOption::VALUE_NONE, 'Shows the available release to rollbacl to'),
57 | ));
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tests/meta/coverage.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 | Code Coverage Report
4 | 2013-10-19 14:44:37
5 |
6 | Summary:
7 | Classes: 60.00% (12/20)
8 | Methods: 86.99% (107/123)
9 | Lines: 90.18% (588/652)
10 |
11 | \Rocketeer::Bash
12 | Methods: 100.00% (17/17) Lines: 93.52% (101/108)
13 | \Rocketeer::ReleasesManager
14 | Methods: 100.00% ( 9/ 9) Lines: 100.00% ( 22/ 22)
15 | \Rocketeer::Rocketeer
16 | Methods: 100.00% (22/22) Lines: 100.00% ( 97/ 97)
17 | \Rocketeer::Server
18 | Methods: 100.00% (10/10) Lines: 91.11% ( 41/ 45)
19 | \Rocketeer::TasksQueue
20 | Methods: 100.00% (17/17) Lines: 95.73% (112/117)
21 | \Rocketeer\Scm::Git
22 | Methods: 100.00% ( 6/ 6) Lines: 100.00% ( 8/ 8)
23 | \Rocketeer\Tasks::Check
24 | Methods: 100.00% ( 7/ 7) Lines: 66.67% ( 36/ 54)
25 | \Rocketeer\Tasks::Cleanup
26 | Methods: 100.00% ( 1/ 1) Lines: 100.00% ( 9/ 9)
27 | \Rocketeer\Tasks::Closure
28 | Methods: 100.00% ( 3/ 3) Lines: 100.00% ( 5/ 5)
29 | \Rocketeer\Tasks::CurrentRelease
30 | Methods: 100.00% ( 1/ 1) Lines: 100.00% ( 7/ 7)
31 | \Rocketeer\Tasks::Deploy
32 | Methods: 100.00% ( 4/ 4) Lines: 63.89% ( 23/ 36)
33 | \Rocketeer\Tasks::Ignite
34 | Methods: 100.00% ( 1/ 1) Lines: 100.00% ( 8/ 8)
35 | \Rocketeer\Tasks::Rollback
36 | Methods: 100.00% ( 2/ 2) Lines: 50.00% ( 10/ 20)
37 | \Rocketeer\Tasks::Setup
38 | Methods: 100.00% ( 2/ 2) Lines: 88.89% ( 24/ 27)
39 | \Rocketeer\Tasks::Teardown
40 | Methods: 100.00% ( 1/ 1) Lines: 100.00% ( 7/ 7)
41 | \Rocketeer\Tasks::Test
42 | Methods: 100.00% ( 1/ 1) Lines: 100.00% ( 2/ 2)
43 | \Rocketeer\Tasks::Update
44 | Methods: 100.00% ( 1/ 1) Lines: 100.00% ( 9/ 9)
45 | \Rocketeer\Traits::Scm
46 | Methods: 100.00% ( 3/ 3) Lines: 100.00% ( 7/ 7)
47 | \Rocketeer\Traits::Task
48 | Methods: 93.75% (15/16) Lines: 94.81% ( 73/ 77)
49 |
--------------------------------------------------------------------------------
/src/Rocketeer/Tasks/Rollback.php:
--------------------------------------------------------------------------------
1 | getRollbackRelease();
20 |
21 | // If no release specified, display the available ones
22 | if ($this->command->option('list')) {
23 | $releases = $this->releasesManager->getReleases();
24 | $this->command->info('Here are the available releases :');
25 |
26 | foreach ($releases as $key => $name) {
27 | $name = DateTime::createFromFormat('YmdHis', $name);
28 | $name = $name->format('Y-m-d H:i:s');
29 |
30 | $this->command->comment(sprintf('[%d] %s', $key, $name));
31 | }
32 |
33 | // Get actual release name from date
34 | $rollbackRelease = $this->command->ask('Which one do you want to go back to ? (0)', 0);
35 | $rollbackRelease = $releases[$rollbackRelease];
36 | }
37 |
38 | // Rollback release
39 | $this->command->info('Rolling back to release '.$rollbackRelease);
40 | $this->updateSymlink($rollbackRelease);
41 |
42 | return $this->history;
43 | }
44 |
45 | ////////////////////////////////////////////////////////////////////
46 | /////////////////////////////// HELPERS ////////////////////////////
47 | ////////////////////////////////////////////////////////////////////
48 |
49 | /**
50 | * Get the release to rollback to
51 | *
52 | * @return integer
53 | */
54 | protected function getRollbackRelease()
55 | {
56 | $release = $this->command->argument('release');
57 | if (!$release) {
58 | $release = $this->releasesManager->getPreviousRelease();
59 | }
60 |
61 | return $release;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/tests/ReleasesManagerTest.php:
--------------------------------------------------------------------------------
1 | app['rocketeer.releases']->getCurrentRelease();
9 |
10 | $this->assertEquals(20000000000000, $currentRelease);
11 | }
12 |
13 | public function testCanGetReleasesPath()
14 | {
15 | $releasePath = $this->app['rocketeer.releases']->getReleasesPath();
16 |
17 | $this->assertEquals($this->server.'/releases', $releasePath);
18 | }
19 |
20 | public function testCanGetCurrentReleaseFolder()
21 | {
22 | $currentReleasePath = $this->app['rocketeer.releases']->getCurrentReleasePath();
23 |
24 | $this->assertEquals($this->server.'/releases/20000000000000', $currentReleasePath);
25 | }
26 |
27 | public function testCanGetReleases()
28 | {
29 | $releases = $this->app['rocketeer.releases']->getReleases();
30 |
31 | $this->assertEquals(array(1 => 10000000000000, 0 => 20000000000000), $releases);
32 | }
33 |
34 | public function testCanGetDeprecatedReleases()
35 | {
36 | $releases = $this->app['rocketeer.releases']->getDeprecatedReleases();
37 |
38 | $this->assertEquals(array(10000000000000), $releases);
39 | }
40 |
41 | public function testCanGetPreviousRelease()
42 | {
43 | $currentRelease = $this->app['rocketeer.releases']->getPreviousRelease();
44 |
45 | $this->assertEquals(10000000000000, $currentRelease);
46 | }
47 |
48 | public function testCanUpdateCurrentRelease()
49 | {
50 | $this->app['rocketeer.releases']->updateCurrentRelease(30000000000000);
51 |
52 | $this->assertEquals(30000000000000, $this->app['rocketeer.server']->getValue('current_release'));
53 | }
54 |
55 | public function testCanGetFolderInRelease()
56 | {
57 | $folder = $this->app['rocketeer.releases']->getCurrentReleasePath('{path.storage}');
58 |
59 | $this->assertEquals($this->server.'/releases/20000000000000/app/storage', $folder);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/tests/ServerTest.php:
--------------------------------------------------------------------------------
1 | app['path.storage'] = null;
13 | $this->app->offsetUnset('path.storage');
14 |
15 | new Rocketeer\Server($this->app);
16 |
17 | $storage = __DIR__.'/../storage';
18 | $exists = file_exists($storage);
19 | $this->app['files']->deleteDirectory($storage);
20 | $this->assertTrue($exists);
21 | }
22 |
23 | public function testCanGetValueFromDeploymentsFile()
24 | {
25 | $this->assertEquals('bar', $this->app['rocketeer.server']->getValue('foo'));
26 | }
27 |
28 | public function testCanSetValueInDeploymentsFile()
29 | {
30 | $this->app['rocketeer.server']->setValue('foo', 'baz');
31 |
32 | $this->assertEquals('baz', $this->app['rocketeer.server']->getValue('foo'));
33 | }
34 |
35 | public function testCandeleteRepository()
36 | {
37 | $this->app['rocketeer.server']->deleteRepository();
38 |
39 | $this->assertFalse($this->app['files']->exists(__DIR__.'/meta/deployments.json'));
40 | }
41 |
42 | public function testCanFallbackIfFileDoesntExist()
43 | {
44 | $this->app['rocketeer.server']->deleteRepository();
45 |
46 | $this->assertEquals(null, $this->app['rocketeer.server']->getValue('foo'));
47 | }
48 |
49 | public function testCanGetLineEndings()
50 | {
51 | $this->app['rocketeer.server']->deleteRepository();
52 |
53 | $this->assertEquals(PHP_EOL, $this->app['rocketeer.server']->getLineEndings());
54 | }
55 |
56 | public function testCanGetSeparators()
57 | {
58 | $this->app['rocketeer.server']->deleteRepository();
59 |
60 | $this->assertEquals(DIRECTORY_SEPARATOR, $this->app['rocketeer.server']->getSeparator());
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Rocketeer/Scm/Git.php:
--------------------------------------------------------------------------------
1 | getCommand('--version');
30 | }
31 |
32 | /**
33 | * Get the current state
34 | *
35 | * @return string
36 | */
37 | public function currentState()
38 | {
39 | return $this->getCommand('rev-parse HEAD');
40 | }
41 |
42 | /**
43 | * Get the current branch
44 | *
45 | * @return string
46 | */
47 | public function currentBranch()
48 | {
49 | return $this->getCommand('rev-parse --abbrev-ref HEAD');
50 | }
51 |
52 | ////////////////////////////////////////////////////////////////////
53 | /////////////////////////////// ACTIONS ////////////////////////////
54 | ////////////////////////////////////////////////////////////////////
55 |
56 | /**
57 | * Clone a repository
58 | *
59 | * @param string $destination
60 | *
61 | * @return string
62 | */
63 | public function checkout($destination)
64 | {
65 | $branch = $this->app['rocketeer.rocketeer']->getRepositoryBranch();
66 | $repository = $this->app['rocketeer.rocketeer']->getRepository();
67 |
68 | return sprintf($this->getCommand('clone --depth 1 -b %s %s %s'), $branch, $repository, $destination);
69 | }
70 |
71 | /**
72 | * Resets the repository
73 | *
74 | * @return string
75 | */
76 | public function reset()
77 | {
78 | return $this->getCommand('reset --hard');
79 | }
80 |
81 | /**
82 | * Updates the repository
83 | *
84 | * @return string
85 | */
86 | public function update()
87 | {
88 | return $this->getCommand('pull');
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/Rocketeer/Tasks/Setup.php:
--------------------------------------------------------------------------------
1 | executeTask('Check')) {
34 | return false;
35 | }
36 |
37 | // Remove existing installation
38 | if ($this->isSetup()) {
39 | $this->executeTask('Teardown');
40 | }
41 |
42 | // Create base folder
43 | $this->createFolder();
44 | $this->createStages();
45 |
46 | // Set setup to true
47 | $this->server->setValue('is_setup', true);
48 |
49 | // Get server informations
50 | $this->command->comment('Getting some informations about the server');
51 | $this->server->getSeparator();
52 | $this->server->getLineEndings();
53 |
54 | // Create confirmation message
55 | $application = $this->rocketeer->getApplicationName();
56 | $homeFolder = $this->rocketeer->getHomeFolder();
57 | $this->command->info(sprintf('Successfully setup "%s" at "%s"', $application, $homeFolder));
58 |
59 | return $this->history;
60 | }
61 |
62 | ////////////////////////////////////////////////////////////////////
63 | /////////////////////////////// HELPERS ////////////////////////////
64 | ////////////////////////////////////////////////////////////////////
65 |
66 | /**
67 | * Create the Application's folders
68 | *
69 | * @return void
70 | */
71 | protected function createStages()
72 | {
73 | // Get stages
74 | $stages = $this->rocketeer->getStages();
75 | if (empty($stages)) {
76 | $stages = array(null);
77 | }
78 |
79 | // Create folders
80 | foreach ($stages as $stage) {
81 | $this->rocketeer->setStage($stage);
82 | $this->createFolder('releases', true);
83 | $this->createFolder('current', true);
84 | $this->createFolder('shared', true);
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/Rocketeer/Tasks/Deploy.php:
--------------------------------------------------------------------------------
1 | isSetup()) {
20 | $this->command->error('Server is not ready, running Setup task');
21 | $this->executeTask('Setup');
22 | }
23 |
24 | // Update current release
25 | $release = date('YmdHis');
26 | $this->releasesManager->updateCurrentRelease($release);
27 |
28 | // Clone Git repository
29 | if (!$this->cloneRepository()) {
30 | return $this->cancel();
31 | }
32 |
33 | // Run Composer
34 | if (!$this->runComposer()) {
35 | return $this->cancel();
36 | }
37 |
38 | // Run tests
39 | if ($this->getOption('tests')) {
40 | if (!$this->runTests()) {
41 | $this->command->error('Tests failed');
42 | return $this->cancel();
43 | }
44 | }
45 |
46 | // Set permissions
47 | $this->setApplicationPermissions();
48 |
49 | // Run migrations
50 | if ($this->getOption('migrate')) {
51 | $this->runMigrations($this->getOption('seed'));
52 | }
53 |
54 | // Synchronize shared folders and files
55 | $this->syncSharedFolders();
56 |
57 | // Update symlink
58 | $this->updateSymlink();
59 |
60 | $this->command->info('Successfully deployed release '.$release);
61 |
62 | return $this->history;
63 | }
64 |
65 | ////////////////////////////////////////////////////////////////////
66 | /////////////////////////////// HELPERS ////////////////////////////
67 | ////////////////////////////////////////////////////////////////////
68 |
69 | /**
70 | * Cancel deploy
71 | *
72 | * @return false
73 | */
74 | protected function cancel()
75 | {
76 | $this->executeTask('Rollback');
77 |
78 | return false;
79 | }
80 |
81 | /**
82 | * Sync the requested folders and files
83 | *
84 | * @return void
85 | */
86 | protected function syncSharedFolders()
87 | {
88 | $shared = (array) $this->rocketeer->getOption('remote.shared');
89 | foreach ($shared as $file) {
90 | $this->share($file);
91 | }
92 | }
93 |
94 | /**
95 | * Set permissions for the folders used by the application
96 | *
97 | * @return void
98 | */
99 | protected function setApplicationPermissions()
100 | {
101 | $files = (array) $this->rocketeer->getOption('remote.permissions.files');
102 | foreach ($files as $file) {
103 | $this->setPermissions($file);
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/Rocketeer/Scm/Svn.php:
--------------------------------------------------------------------------------
1 | getCommand('--version');
30 | }
31 |
32 | /**
33 | * Get the current state
34 | *
35 | * @return string
36 | */
37 | public function currentState()
38 | {
39 | return $this->getCommand('info -r "HEAD" | grep "Revision"');
40 | }
41 |
42 | /**
43 | * Get the current branch
44 | *
45 | * @return string
46 | */
47 | public function currentBranch()
48 | {
49 | return 'echo trunk';
50 | }
51 |
52 | ////////////////////////////////////////////////////////////////////
53 | /////////////////////////////// ACTIONS ////////////////////////////
54 | ////////////////////////////////////////////////////////////////////
55 |
56 | /**
57 | * Clone a repository
58 | *
59 | * @param string $destination
60 | *
61 | * @return string
62 | */
63 | public function checkout($destination)
64 | {
65 | $branch = $this->app['rocketeer.rocketeer']->getRepositoryBranch();
66 | $repository = $this->app['rocketeer.rocketeer']->getRepository();
67 |
68 | return sprintf(
69 | $this->getCommand('co %s %s %s'),
70 | $this->getCredentials(),
71 | rtrim($repository, '/') . '/' . ltrim($branch, '/'),
72 | $destination
73 | );
74 | }
75 |
76 | /**
77 | * Resets the repository
78 | *
79 | * @return string
80 | */
81 | public function reset()
82 | {
83 | $cmd = 'status -q | grep -v \'^[~XI ]\' | awk \'{print $2;}\' | xargs %s revert';
84 |
85 | return $this->getCommand(sprintf($cmd, $this->binary));
86 | }
87 |
88 | /**
89 | * Updates the repository
90 | *
91 | * @return string
92 | */
93 | public function update()
94 | {
95 | return sprintf($this->getCommand('up %s'), $this->getCredentials());
96 | }
97 |
98 | /**
99 | * Return credential options
100 | *
101 | * @return string
102 | */
103 | protected function getCredentials()
104 | {
105 | $options = array('--non-interactive');
106 | $credentials = $this->app['rocketeer.rocketeer']->getCredentials();
107 |
108 | // Build command
109 | if ($user = array_get($credentials, 'username')) {
110 | $options[] = '--username=' . $user;
111 | }
112 | if ($pass = array_get($credentials, 'password')) {
113 | $options[] = '--password=' . $pass;
114 | }
115 |
116 | return implode(' ', $options);
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/tests/BashTest.php:
--------------------------------------------------------------------------------
1 | app['rocketeer.server']->setValue('paths.composer', 'foobar');
7 |
8 | $this->assertEquals('foobar', $this->task->which('composer'));
9 | }
10 |
11 | public function testCanGetBinary()
12 | {
13 | $whichGrep = exec('which grep');
14 | $grep = $this->task->which('grep');
15 |
16 | $this->assertEquals($whichGrep, $grep);
17 | }
18 |
19 | public function testCanGetFallbackForBinary()
20 | {
21 | $whichGrep = exec('which grep');
22 | $grep = $this->task->which('foobar', $whichGrep);
23 |
24 | $this->assertEquals($whichGrep, $grep);
25 | $this->assertFalse($this->task->which('fdsf'));
26 | }
27 |
28 | public function testCanGetArraysFromRawCommands()
29 | {
30 | $contents = $this->task->runRaw('ls', true);
31 |
32 | $this->assertCount(12, $contents);
33 | }
34 |
35 | public function testCanListContentsOfAFolder()
36 | {
37 | $contents = $this->task->listContents($this->server);
38 |
39 | $this->assertEquals(array('current', 'releases', 'shared'), $contents);
40 | }
41 |
42 | public function testCanCheckIfFileExists()
43 | {
44 | $this->assertTrue($this->task->fileExists($this->server));
45 | $this->assertFalse($this->task->fileExists($this->server.'/nope'));
46 | }
47 |
48 | public function testCanCheckStatusOfACommand()
49 | {
50 | $this->task->remote = clone $this->getRemote()->shouldReceive('status')->andReturn(1)->mock();
51 | ob_start();
52 | $status = $this->task->checkStatus(null, 'error');
53 | $output = ob_get_clean();
54 | $this->assertEquals('error'.PHP_EOL, $output);
55 | $this->assertFalse($status);
56 |
57 | $this->task->remote = clone $this->getRemote()->shouldReceive('status')->andReturn(0)->mock();
58 | $status = $this->task->checkStatus(null);
59 | $this->assertNull($status);
60 | }
61 |
62 | public function testCanForgetCredentialsIfInvalid()
63 | {
64 | $this->app['rocketeer.server']->setValue('credentials', array(
65 | 'repository' => 'https://Anahkiasen@bitbucket.org/Anahkiasen/registry.git',
66 | 'username' => 'Anahkiasen',
67 | 'password' => 'baz',
68 | ));
69 |
70 | // Create fake remote
71 | $remote = clone $this->getRemote();
72 | $remote->shouldReceive('status')->andReturn(1);
73 | $task = $this->task();
74 | $task->remote = $remote;
75 |
76 | $task->cloneRepository($this->server.'/test');
77 | $this->assertNull($this->app['rocketeer.server']->getValue('credentials'));
78 | }
79 |
80 | public function testCancelsSymlinkForUnexistingFolders()
81 | {
82 | $task = $this->pretendTask();
83 | $folder = '{path.storage}/logs';
84 | $share = $task->share($folder);
85 |
86 | $this->assertFalse($share);
87 | }
88 |
89 | public function testCanSymlinkFolders()
90 | {
91 | // Create dummy file
92 | $folder = $this->server.'/releases/20000000000000/src';
93 | mkdir($folder);
94 | file_put_contents($folder.'/foobar.txt', 'test');
95 |
96 | $task = $this->pretendTask();
97 | $folder = '{path.base}/foobar.txt';
98 | $share = $task->share($folder);
99 | $matcher = sprintf('ln -s %s %s', $this->server.'/shared//src/foobar.txt', $this->server.'/releases/20000000000000//src/foobar.txt');
100 |
101 | $this->assertEquals($matcher, $share);
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/Rocketeer/ReleasesManager.php:
--------------------------------------------------------------------------------
1 | app = $app;
26 | }
27 |
28 | ////////////////////////////////////////////////////////////////////
29 | /////////////////////////////// RELEASES ///////////////////////////
30 | ////////////////////////////////////////////////////////////////////
31 |
32 | /**
33 | * Get all the releases on the server
34 | *
35 | * @return array
36 | */
37 | public function getReleases()
38 | {
39 | // Get releases on server
40 | $releases = $this->app['rocketeer.bash']->listContents($this->getReleasesPath());
41 | rsort($releases);
42 |
43 | return $releases;
44 | }
45 |
46 | /**
47 | * Get an array of deprecated releases
48 | *
49 | * @return array
50 | */
51 | public function getDeprecatedReleases()
52 | {
53 | $releases = $this->getReleases();
54 | $maxReleases = $this->app['config']->get('rocketeer::remote.keep_releases');
55 |
56 | return array_slice($releases, $maxReleases);
57 | }
58 |
59 | ////////////////////////////////////////////////////////////////////
60 | ////////////////////////////// PATHS ///////////////////////////////
61 | ////////////////////////////////////////////////////////////////////
62 |
63 | /**
64 | * Get the path to the releases folder
65 | *
66 | * @return string
67 | */
68 | public function getReleasesPath()
69 | {
70 | return $this->app['rocketeer.rocketeer']->getFolder('releases');
71 | }
72 |
73 | /**
74 | * Get the path to a release
75 | *
76 | * @param integer $release
77 | *
78 | * @return string
79 | */
80 | public function getPathToRelease($release)
81 | {
82 | return $this->app['rocketeer.rocketeer']->getFolder('releases/'.$release);
83 | }
84 |
85 | /**
86 | * Get the path to the current release
87 | *
88 | * @param string $folder A folder in the release
89 | *
90 | * @return string
91 | */
92 | public function getCurrentReleasePath($folder = null)
93 | {
94 | if ($folder) {
95 | $folder = '/'.$folder;
96 | }
97 |
98 | return $this->getPathToRelease($this->getCurrentRelease().$folder);
99 | }
100 |
101 | ////////////////////////////////////////////////////////////////////
102 | /////////////////////////// CURRENT RELEASE ////////////////////////
103 | ////////////////////////////////////////////////////////////////////
104 |
105 | /**
106 | * Get the current release
107 | *
108 | * @return string
109 | */
110 | public function getCurrentRelease()
111 | {
112 | return $this->app['rocketeer.server']->getValue('current_release');
113 | }
114 |
115 | /**
116 | * Get the release before the current one
117 | *
118 | * @param string $release A release name
119 | *
120 | * @return string
121 | */
122 | public function getPreviousRelease($release = null)
123 | {
124 | // Get all releases and the current one
125 | $releases = $this->getReleases();
126 | $current = $release ?: $this->getCurrentRelease();
127 |
128 | // Get the one before that, or default to current
129 | $key = array_search($current, $releases);
130 | $release = array_get($releases, $key + 1, $current);
131 |
132 | return $release;
133 | }
134 |
135 | /**
136 | * Update the current release
137 | *
138 | * @param string $release A release name
139 | *
140 | * @return void
141 | */
142 | public function updateCurrentRelease($release)
143 | {
144 | $this->app['rocketeer.server']->setValue('current_release', $release);
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Rocketeer
2 |
3 | [](https://travis-ci.org/Anahkiasen/rocketeer)
4 | [](https://packagist.org/packages/anahkiasen/rocketeer)
5 | [](https://packagist.org/packages/anahkiasen/rocketeer)
6 | [](https://scrutinizer-ci.com/g/Anahkiasen/rocketeer/)
7 | [](https://scrutinizer-ci.com/g/Anahkiasen/rocketeer/)
8 |
9 | Rocketeer provides a fast and easy way to set-up and deploy your Laravel projects. **Rocketeer requires Laravel 4.1 as it uses the new _illuminate/remote_ component**.
10 | It can be used on Laravel 4.0 but requires a tiny-bit more setup, see the getting started guide for more informations.
11 |
12 | ## Using Rocketeer
13 |
14 | I recommend you checkout this [Getting Started](https://github.com/Anahkiasen/rocketeer/wiki/Getting-started) guide before anything. It will get you quickly set up to use Rocketeer.
15 |
16 | The available commands in Rocketeer are :
17 |
18 | ```
19 | deploy
20 | deploy:check Check if the server is ready to receive the application
21 | deploy:cleanup Clean up old releases from the server
22 | deploy:current Display what the current release is
23 | deploy:deploy Deploy the website.
24 | deploy:rollback Rollback to the previous release, or to a specific one
25 | deploy:rollback {release} Rollback to a specific release
26 | deploy:setup Set up the remote server for deployment
27 | deploy:teardown Remove the remote applications and existing caches
28 | deploy:test Run the tests on the server and displays the output
29 | deploy:update Update the remote server without doing a new release
30 | ```
31 |
32 | ## Tasks
33 |
34 | 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.
35 | Now, the core of Rocketeer is you can hook into any of those Tasks to perform additional actions, for this you'll use the `before` and `after` arrays of Rocketeer's config file.
36 |
37 | You can read more about Tasks and what you can do with them [in the wiki](https://github.com/Anahkiasen/rocketeer/wiki/Tasks).
38 |
39 | ## Why not Capistrano ?
40 |
41 | 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.
42 |
43 | 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 Laravel, even outside of the deploy routine.
44 | 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`.
45 |
46 | 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 Laravel developer are integrated in Rocketeer's core deploy process.
47 |
48 | ## Table of contents
49 |
50 | - **[Getting Started](https://github.com/Anahkiasen/rocketeer/wiki/Getting-started)**
51 | - **[Tasks](https://github.com/Anahkiasen/rocketeer/wiki/Tasks)**
52 | - **[Architecture](https://github.com/Anahkiasen/rocketeer/wiki/Architecture)**
--------------------------------------------------------------------------------
/src/Rocketeer/Tasks/Check.php:
--------------------------------------------------------------------------------
1 | checkScm()) {
37 | $errors[] = $this->command->error($this->scm->binary . ' could not be found on the server');
38 | }
39 |
40 | // Check PHP
41 | if (!$this->checkPhpVersion()) {
42 | $errors[] = $this->command->error('The version of PHP on the server does not match Laravel\'s requirements');
43 | }
44 |
45 | // Check MCrypt
46 | if (!$this->checkPhpExtension('mcrypt')) {
47 | $errors[] = $this->command->error(sprintf($extension, 'mcrypt'));
48 | }
49 |
50 | // Check Composer
51 | if (!$this->checkComposer()) {
52 | $errors[] = $this->command->error('Composer does not seem to be present on the server');
53 | }
54 |
55 | // Check database
56 | $database = $this->app['config']->get('database.default');
57 | if (!$this->checkDatabaseExtension($database)) {
58 | $errors[] = $this->command->error(sprintf($extension, $database));
59 | }
60 |
61 | // Check Cache/Session driver
62 | $cache = $this->app['config']->get('cache.driver');
63 | $session = $this->app['config']->get('session.driver');
64 | if (!$this->checkCacheDriver($cache) or !$this->checkCacheDriver($session)) {
65 | $errors[] = $this->command->error(sprintf($extension, $cache));
66 | }
67 |
68 | // Return false if any error
69 | if (!empty($errors)) {
70 | return false;
71 | }
72 |
73 | // Display confirmation message
74 | $this->command->info('Your server is ready to deploy');
75 |
76 | return true;
77 | }
78 |
79 | ////////////////////////////////////////////////////////////////////
80 | /////////////////////////////// HELPERS ////////////////////////////
81 | ////////////////////////////////////////////////////////////////////
82 |
83 | /**
84 | * Check the presence of an SCM on the server
85 | *
86 | * @return boolean
87 | */
88 | public function checkScm()
89 | {
90 | $this->command->comment('Checking presence of '.$this->scm->binary);
91 | $this->scm->execute('check');
92 |
93 | return $this->remote->status() == 0;
94 | }
95 |
96 | /**
97 | * Check if Composer is on the server
98 | *
99 | * @return boolean
100 | */
101 | public function checkComposer()
102 | {
103 | $this->command->comment('Checking presence of Composer');
104 |
105 | return $this->getComposer();
106 | }
107 |
108 | /**
109 | * Check if the server is ready to support PHP
110 | *
111 | * @return boolean
112 | */
113 | public function checkPhpVersion()
114 | {
115 | $this->command->comment('Checking PHP version');
116 | $version = $this->run('php -r "print PHP_VERSION;"');
117 |
118 | return version_compare($version, '5.3.7', '>=');
119 | }
120 |
121 | /**
122 | * Check the presence of the correct database PHP extension
123 | *
124 | * @param string $database
125 | *
126 | * @return boolean
127 | */
128 | public function checkDatabaseExtension($database)
129 | {
130 | switch ($database) {
131 | case 'sqlite':
132 | return $this->checkPhpExtension('pdo_sqlite');
133 |
134 | case 'mysql':
135 | return $this->checkPhpExtension('mysql') and $this->checkPhpExtension('pdo_mysql');
136 |
137 | default:
138 | return true;
139 | }
140 | }
141 |
142 | /**
143 | * Check the presence of the correct cache PHP extension
144 | *
145 | * @param string $cache
146 | *
147 | * @return boolean
148 | */
149 | public function checkCacheDriver($cache)
150 | {
151 | switch ($cache) {
152 | case 'memcached':
153 | case 'apc':
154 | case 'redis':
155 | return $this->checkPhpExtension($cache);
156 |
157 | default:
158 | return true;
159 | }
160 | }
161 |
162 | /**
163 | * Check the presence of a PHP extension
164 | *
165 | * @param string $extension The extension
166 | *
167 | * @return boolean
168 | */
169 | public function checkPhpExtension($extension)
170 | {
171 | $this->command->comment('Checking presence of '.$extension. ' extension');
172 |
173 | if (!$this->extensions) {
174 | $this->extensions = $this->run('php -m', true, true);
175 | }
176 |
177 | return in_array($extension, $this->extensions);
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/src/config/config.php:
--------------------------------------------------------------------------------
1 | array('production'),
11 |
12 | // SCM repository
13 | //////////////////////////////////////////////////////////////////////
14 |
15 | 'scm' => array(
16 |
17 | // The SCM used (supported: "git", "svn")
18 | 'scm' => 'git',
19 |
20 | // The SSH/HTTPS adress to your repository
21 | // Example: https://github.com/vendor/website.git
22 | 'repository' => '',
23 |
24 | // The repository credentials : you can leave those empty
25 | // if you're using SSH or if your repository is public
26 | // In other cases you can leave this empty too, and you will
27 | // be prompted for the credentials on deploy
28 | 'username' => '',
29 | 'password' => '',
30 |
31 | // The branch to deploy
32 | 'branch' => 'master',
33 | ),
34 |
35 | // Stages
36 | //
37 | // The multiples stages of your application
38 | // if you don't know what this does, then you don't need it
39 | //////////////////////////////////////////////////////////////////////
40 |
41 | 'stages' => array(
42 |
43 | // Adding entries to this array will split the remote folder in stages
44 | // Like /var/www/yourapp/staging and /var/www/yourapp/production
45 | 'stages' => array(),
46 |
47 | // The default stage to execute tasks on when --stage is not provided
48 | 'default' => '',
49 | ),
50 |
51 | // Remote server
52 | //////////////////////////////////////////////////////////////////////
53 |
54 | 'remote' => array(
55 |
56 | // Variables about the servers. Those can be guessed but in
57 | // case of problem it's best to input those manually
58 | 'variables' => array(
59 | 'directory_separator' => '/',
60 | 'line_endings' => "\n",
61 | ),
62 |
63 | // The root directory where your applications will be deployed
64 | 'root_directory' => '/home/www/',
65 |
66 | // The name of the application to deploy
67 | // This will create a folder of the same name in the root directory
68 | // configured above, so be careful about the characters used
69 | 'application_name' => 'application',
70 |
71 | // The number of releases to keep at all times
72 | 'keep_releases' => 4,
73 |
74 | // A list of folders/file to be shared between releases
75 | // Use this to list folders that need to keep their state, like
76 | // user uploaded data, file-based databases, etc.
77 | 'shared' => array(
78 | '{path.storage}/logs',
79 | '{path.storage}/sessions',
80 | ),
81 |
82 | 'permissions' => array(
83 |
84 | // The permissions to CHMOD folders to
85 | // Change to null to leave the folders untouched
86 | 'permissions' => 755,
87 |
88 | // The folders and files to set as web writable
89 | // You can pass paths in brackets, so {path.public} will return
90 | // the correct path to the public folder
91 | 'files' => array(
92 | 'app/database/production.sqlite',
93 | '{path.storage}',
94 | '{path.public}',
95 | ),
96 |
97 | // The web server user and group to CHOWN folders to
98 | // Leave empty to leave the above folders untouched
99 | 'webuser' => array(
100 | 'user' => 'www-data',
101 | 'group' => 'www-data',
102 | ),
103 |
104 | ),
105 | ),
106 |
107 | // Tasks
108 | //
109 | // Here you can define in the `before` and `after` array, Tasks to execute
110 | // before or after the core Rocketeer Tasks. You can either put a simple command,
111 | // a closure which receives a $task object, or the name of a class extending
112 | // the Rocketeer\Traits\Task class
113 | //
114 | // In the `custom` array you can list custom Tasks classes to be added
115 | // to Rocketeer. Those will then be available in Artisan
116 | // as `php artisan deploy:yourtask`
117 | //////////////////////////////////////////////////////////////////////
118 |
119 | 'tasks' => array(
120 |
121 | // Tasks to execute before the core Rocketeer Tasks
122 | 'before' => array(
123 | 'setup' => array(),
124 | 'deploy' => array(),
125 | 'cleanup' => array(),
126 | ),
127 |
128 | // Tasks to execute after the core Rocketeer Tasks
129 | 'after' => array(
130 | 'setup' => array(),
131 | 'deploy' => array(),
132 | 'cleanup' => array(),
133 | ),
134 |
135 | // Custom Tasks to register with Rocketeer
136 | 'custom' => array(),
137 | ),
138 |
139 | // Contextual options
140 | //
141 | // In this section you can fine-tune the above configuration according
142 | // to the stage or connection currently in use.
143 | // Per example :
144 | // 'stages' => array(
145 | // 'staging' => array(
146 | // 'scm' => array('branch' => 'staging'),
147 | // ),
148 | // 'production' => array(
149 | // 'scm' => array('branch' => 'master'),
150 | // ),
151 | // ),
152 |
153 | 'on' => array(
154 |
155 | // Stages configurations
156 | 'stages' => array(
157 | ),
158 |
159 | // Connections configuration
160 | 'connections' => array(
161 | ),
162 |
163 | ),
164 |
165 | );
166 |
--------------------------------------------------------------------------------
/src/Rocketeer/Commands/BaseDeployCommand.php:
--------------------------------------------------------------------------------
1 | laravel['events'])) {
42 | return str_replace('deploy:', null, $this->name);
43 | }
44 |
45 | return $this->name;
46 | }
47 |
48 | ////////////////////////////////////////////////////////////////////
49 | ///////////////////////////// CORE METHODS /////////////////////////
50 | ////////////////////////////////////////////////////////////////////
51 |
52 | /**
53 | * Fire a Tasks Queue
54 | *
55 | * @param string|array $tasks
56 | *
57 | * @return mixed
58 | */
59 | protected function fireTasksQueue($tasks)
60 | {
61 | // Check for credentials
62 | $this->getServerCredentials();
63 | $this->getRepositoryCredentials();
64 |
65 | // Start timer
66 | $timerStart = microtime(true);
67 |
68 | // Convert tasks to array if necessary
69 | if (!is_array($tasks)) {
70 | $tasks = array($tasks);
71 | }
72 |
73 | // Run tasks and display timer
74 | $this->laravel['rocketeer.tasks']->run($tasks, $this);
75 | $this->line('Execution time: '.round(microtime(true) - $timerStart, 4). 's');
76 | }
77 |
78 | /**
79 | * Get the Repository's credentials
80 | *
81 | * @return void
82 | */
83 | protected function getRepositoryCredentials()
84 | {
85 | // Check for repository credentials
86 | $repositoryInfos = $this->laravel['rocketeer.rocketeer']->getCredentials();
87 | $credentials = array('repository');
88 | if (!array_get($repositoryInfos, 'repository') or $this->laravel['rocketeer.rocketeer']->needsCredentials()) {
89 | $credentials = array('repository', 'username', 'password');
90 | }
91 |
92 | // Gather credentials
93 | foreach ($credentials as $credential) {
94 | ${$credential} = array_get($repositoryInfos, $credential);
95 | if (!${$credential}) {
96 | ${$credential} = $this->ask('No '.$credential. ' is set for the repository, please provide one :');
97 | }
98 | }
99 |
100 | // Save them
101 | $credentials = compact($credentials);
102 | $this->laravel['rocketeer.server']->setValue('credentials', $credentials);
103 | foreach ($credentials as $key => $credential) {
104 | $this->laravel['config']->set('rocketeer::scm.'.$key, $credential);
105 | }
106 | }
107 |
108 | /**
109 | * Get the Server's credentials
110 | *
111 | * @return void
112 | */
113 | protected function getServerCredentials()
114 | {
115 | if ($connections = $this->option('on')) {
116 | $this->laravel['rocketeer.rocketeer']->setConnections($connections);
117 | }
118 |
119 | // Check for configured connections
120 | $connections = $this->laravel['rocketeer.rocketeer']->getAvailableConnections();
121 | $connectionName = $this->laravel['rocketeer.rocketeer']->getConnection();
122 |
123 | if (is_null($connectionName)) {
124 | $connectionName = $this->ask('No connections have been set, please create one : (production)', 'production');
125 | }
126 |
127 | // Check for server credentials
128 | $connection = array_get($connections, $connectionName, array());
129 | $credentials = array('host' => true, 'username' => true, 'password' => false, 'keyphrase' => null, 'key' => false);
130 |
131 | // Gather credentials
132 | foreach ($credentials as $credential => $required) {
133 | ${$credential} = array_get($connection, $credential);
134 | if (!${$credential} and $required) {
135 | ${$credential} = $this->ask('No '.$credential. ' is set for [' .$connectionName. '], please provide one :');
136 | }
137 | }
138 |
139 | // Get password or key
140 | if (!$password and !$key) {
141 | $type = $this->ask('No password or SSH key is set for [' .$connectionName. '], which would you use ? [key/password]');
142 | if ($type == 'key') {
143 | $key = $this->ask('Please enter the full path to your key');
144 | $keyphrase = $this->ask('If a keyphrase is required, provide it');
145 | } else {
146 | $password = $this->ask('Please enter your password');
147 | }
148 | }
149 |
150 | // Save them
151 | $credentials = compact(array_keys($credentials));
152 | $this->laravel['rocketeer.server']->setValue('connections.'.$connectionName, $credentials);
153 | $this->laravel['config']->set('remote.connections.'.$connectionName, $credentials);
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/Rocketeer/Server.php:
--------------------------------------------------------------------------------
1 | app = $app;
35 |
36 | // Create personnal storage if necessary
37 | if (!$app->bound('path.storage') and !$storage) {
38 | $storage = __DIR__.DS.'..'.DS.'..'.DS.'storage';
39 | @mkdir($storage);
40 | }
41 |
42 | // Get correct storage path
43 | $storage = $storage ?: $app['path.storage'].DS.'meta';
44 | $this->repository = $storage.DS.'deployments.json';
45 | }
46 |
47 | ////////////////////////////////////////////////////////////////////
48 | /////////////////////////// REMOTE VARIABLES ///////////////////////
49 | ////////////////////////////////////////////////////////////////////
50 |
51 | /**
52 | * Get the directory separators on the remove server
53 | *
54 | * @return string
55 | */
56 | public function getSeparator()
57 | {
58 | // If manually set by the user, return it
59 | $user = $this->app['rocketeer.rocketeer']->getOption('remote.variables.directory_separator');
60 | if ($user) {
61 | return $user;
62 | }
63 |
64 | $bash = $this->app['rocketeer.bash'];
65 | return $this->getValue('directory_separator', function ($server) use ($bash) {
66 | $separator = $bash->runRaw('php -r "echo DIRECTORY_SEPARATOR;"');
67 |
68 | // Throw an Exception if we receive invalid output
69 | if (strlen($separator) > 1) {
70 | throw new Exception(
71 | 'An error occured while fetching the directory separators used on the server.'.PHP_EOL.
72 | 'Output received was : '.$separator
73 | );
74 | }
75 |
76 | // Cache separator
77 | $server->setValue('directory_separator', $separator);
78 |
79 | return $separator;
80 | });
81 | }
82 |
83 | /**
84 | * Get the remote line endings on the remove server
85 | *
86 | * @return string
87 | */
88 | public function getLineEndings()
89 | {
90 | // If manually set by the user, return it
91 | $user = $this->app['rocketeer.rocketeer']->getOption('remote.variables.line_endings');
92 | if ($user) {
93 | return $user;
94 | }
95 |
96 | $bash = $this->app['rocketeer.bash'];
97 | return $this->getValue('line_endings', function ($server) use ($bash) {
98 | $endings = $bash->runRaw('php -r "echo PHP_EOL;"');
99 | $server->setValue('line_endings', $endings);
100 |
101 | return $endings;
102 | });
103 | }
104 |
105 | ////////////////////////////////////////////////////////////////////
106 | /////////////////////////////// KEYSTORE ///////////////////////////
107 | ////////////////////////////////////////////////////////////////////
108 |
109 | /**
110 | * Get a value from the repository file
111 | *
112 | * @param string $key
113 | * @param \Closure|string $fallback
114 | *
115 | * @return mixed
116 | */
117 | public function getValue($key, $fallback = null)
118 | {
119 | $value = array_get($this->getRepository(), $key, null);
120 |
121 | // Get fallback value
122 | if (is_null($value)) {
123 | return is_callable($fallback) ? $fallback($this) : $fallback;
124 | }
125 |
126 | return $value;
127 | }
128 |
129 | /**
130 | * Set a value from the repository file
131 | *
132 | * @param string $key
133 | * @param mixed $value
134 | *
135 | * @return array
136 | */
137 | public function setValue($key, $value)
138 | {
139 | $repository = $this->getRepository();
140 | array_set($repository, $key, $value);
141 |
142 | return $this->updateRepository($repository);
143 | }
144 |
145 | /**
146 | * Forget a value from the repository file
147 | *
148 | * @param string $key
149 | *
150 | * @return array
151 | */
152 | public function forgetValue($key)
153 | {
154 | $repository = $this->getRepository();
155 | array_forget($repository, $key);
156 |
157 | return $this->updateRepository($repository);
158 | }
159 |
160 | ////////////////////////////////////////////////////////////////////
161 | ////////////////////////// REPOSITORY FILE /////////////////////////
162 | ////////////////////////////////////////////////////////////////////
163 |
164 | /**
165 | * Replace the contents of the deployments file
166 | *
167 | * @param array $data
168 | *
169 | * @return array
170 | */
171 | public function updateRepository($data)
172 | {
173 | $this->app['files']->put($this->repository, json_encode($data));
174 |
175 | return $data;
176 | }
177 |
178 | /**
179 | * Get the contents of the deployments file
180 | *
181 | * @return array
182 | */
183 | public function getRepository()
184 | {
185 | // Cancel if the file doesn't exist
186 | if (!$this->app['files']->exists($this->repository)) {
187 | return array();
188 | }
189 |
190 | // Get and parse file
191 | $repository = $this->app['files']->get($this->repository);
192 | $repository = json_decode($repository, true);
193 |
194 | return $repository;
195 | }
196 |
197 | /**
198 | * Deletes the deployments file
199 | *
200 | * @return boolean
201 | */
202 | public function deleteRepository()
203 | {
204 | return $this->app['files']->delete($this->repository);
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### Changelog
2 |
3 | ### 0.9.0
4 |
5 | - **Rocketeer now supports SVN**
6 | - **Rocketeer now has a [Campfire plugin](https://github.com/Anahkiasen/rocketeer-campfire)**
7 | - Add option to manually set remote variables when encountering problems
8 | - Add keyphrase support
9 |
10 | ### 0.8.0
11 |
12 | - **Rocketeer can now have specific configurations for stages and connections**
13 | - **Better handling of multiple connections**
14 | - **Added facade shortcuts `Rocketeer::execute(Task)` and `Rocketeer::on(connection[s], Task)` to execute commands on the remote servers**
15 | - Added the `--list` flag on the `rollback` command to show a list of available releases and pick one to rollback to
16 | - Added the `--on` flag to all commands to specify which connections the task should be executed on (ex. `production`, `staging,production`)
17 | - Added `deploy:flush` to clear Rocketeer's cache of credentials
18 |
19 | ### 0.7.0
20 |
21 | - **Rocketeer can now work outside of Laravel**
22 | - **Better handling of SSH keys**
23 | - Permissions are now entirely configurable
24 | - Rocketeer now prompts for confirmation before executing the Teardown task
25 | - Allow the use of patterns in shared folders
26 | - Share `sessions` folder by default
27 | - Rocketeer now prompts for binaries it can't find (composer, phpunit, etc)
28 |
29 | ### 0.6.5
30 |
31 | - **Make Rocketeer prompt for both server and SCM credentials if they're not stored**
32 | - **`artisan deploy` now deploys the project if the `--version` flat is not passed**
33 | - Make Rocketeer forget invalid credentials provided by prompt
34 | - Fix a bug where incorrect SCM urls would be generated
35 |
36 | ### 0.6.4
37 |
38 | - Make the output of commands in realtime when `--verbose` instead of when the command is done
39 | - Fix a bug where custom Task classes would be analyzed as string commands
40 | - Fix Rocketeeer not taking into account custom paths to **app/**, **storage/**, **public/** etc.
41 | - Reverse sluggification of application name
42 |
43 | ### 0.6.3
44 |
45 | - Application name is now always sluggified as a security
46 | - Fix a bug where the Check task would fail on pretend mode
47 | - Fix a bug where invalid directory separators would get cached and used
48 |
49 | ### 0.6.2
50 |
51 | - Make the Check task check for the remote presence of the configured SCM
52 | - Fix Rocketeer not being able to use a `composer.phar` on the server
53 |
54 | ### 0.6.1
55 |
56 | - Fix a bug where the configured user would not have the rights to set permissions
57 |
58 | ### 0.6.0
59 |
60 | - **Add multistage strategy**
61 | - **Add compatibility to Laravel 4.0**
62 | - Migrations are now under a `--migrate` flag
63 | - Split Git from the SCM implementation (**requires a config update**)
64 | - Releases are now named as `YmdHis` instead of `time()`
65 | - If the `scm.branch` option is empty, Rocketeer will now use the current Git branch
66 | - Fix a delay where the `current` symlink would get updated before the complete end of the deploy
67 | - Fix errors with Git and Composer not canceling deploy
68 | - Fix some compatibility problems with Windows
69 | - Fix a bug where string tasks would not be run in latest release folder
70 | - Fix Apache username and group using `www-data` by default
71 |
72 | ### 0.5.0
73 |
74 | - **Add a `deploy:update` task that updates the remote server without doing a new release**
75 | - **Add a `deploy:test` to run the tests on the server**
76 | - **Rocketeer can now prompt for Git credentials if you don't want to store them in the config**
77 | - The `deploy:check` command now checks PHP extensions for the cache/database/session drivers you set
78 | - Rocketeer now share logs by default between releases
79 | - Add ability to specify an array of Tasks in Rocketeer::before|after
80 | - Added a `$silent` flag to make a `Task::run` call silent no matter what
81 | - Rocketeer now displays how long the task took
82 |
83 | ### 0.4.0
84 |
85 | - **Add ability to share files and folders between releases**
86 | - **Add ability to create custom tasks integrated in the CLI**
87 | - **Add a `deploy:check` Task that checks if the server is ready to receive a Laravel app**
88 | - Add `Task::listContents` and `Task::fileExists` helpers
89 | - Add Task helper to run outstanding migrations
90 | - Add `Rocketeer::add` method on the facade to register custom Tasks
91 | - Fix `Task::runComposer` not taking into account a local `composer.phar`
92 |
93 | ### 0.3.2
94 |
95 | - Fixed wrong tag used in `deploy:cleanup`
96 |
97 | ### 0.3.1
98 |
99 | - Added `--pretend` flag on all commands to print out a list of the commands that would have been executed instead of running them
100 |
101 | ### 0.3.0
102 |
103 | - Added `Task::runInFolder` to run tasks in a specific folder
104 | - Added `Task::runForCurrentRelease` Task helper
105 | - Fixed a bug where `Task::run` would only return the last line of the command's output
106 | - Added `Task::runTests` methods to run the PHPUnit tests of the application
107 | - Integrated `Task::runTests` in the `Deploy` task under the `--tests` flag ; failing tests will cancel deploy and rollback
108 |
109 | ### 0.2.0
110 |
111 | - The core of Rocketeer's actions is now split into a system of Tasks for flexibility
112 | - Added a `Rocketeer` facade to easily add tasks via `before` and `after` (see Tasks docs)
113 |
114 | ### 0.1.1
115 |
116 | - Fixed a bug where the commands would try to connect to the remote hosts on construct
117 | - Fixed `ReleasesManager::getPreviousRelease` returning the wrong release
118 |
119 | ### 0.1.0
120 |
121 | - Add `deploy:teardown` to remove the application from remote servers
122 | - Add support for the connections defined in the remote config file
123 | - Add `deploy:rollback` and `deploy:current` commands
124 | - Add `deploy:cleanup` command
125 | - Add config file
126 | - Add `deploy:setup` and `deploy:deploy` commands
127 |
--------------------------------------------------------------------------------
/tests/TasksQueueTest.php:
--------------------------------------------------------------------------------
1 | task('Deploy'));
10 |
11 | $this->assertEquals(array('ls'), $before);
12 | }
13 |
14 | public function testCanBuildTaskByName()
15 | {
16 | $task = $this->tasksQueue()->buildTask('Rocketeer\Tasks\Deploy');
17 |
18 | $this->assertInstanceOf('Rocketeer\Traits\Task', $task);
19 | }
20 |
21 | public function testCanBuildCustomTaskByName()
22 | {
23 | $tasks = $this->tasksQueue()->buildQueue(array('Rocketeer\Tasks\Check'));
24 |
25 | $this->assertInstanceOf('Rocketeer\Tasks\Check', $tasks[0]);
26 | $this->assertInstanceOf('Tasks\MyCustomTask', $tasks[1]);
27 | }
28 |
29 | public function testCanAddCommandsToArtisan()
30 | {
31 | $command = $this->tasksQueue()->add('Rocketeer\Tasks\Deploy');
32 | $this->assertInstanceOf('Rocketeer\Commands\BaseTaskCommand', $command);
33 | $this->assertInstanceOf('Rocketeer\Tasks\Deploy', $command->getTask());
34 | }
35 |
36 | public function testCanGetTasksBeforeOrAfterAnotherTask()
37 | {
38 | $task = $this->task('Deploy');
39 | $before = $this->tasksQueue()->getBefore($task);
40 |
41 | $this->assertEquals(array('before', 'foobar'), $before);
42 | }
43 |
44 | public function testCanAddTasksViaFacade()
45 | {
46 | $task = $this->task('Deploy');
47 | $before = $this->tasksQueue()->getBefore($task);
48 |
49 | $this->tasksQueue()->before('deploy', 'composer install');
50 |
51 | $newBefore = array_merge($before, array('composer install'));
52 | $this->assertEquals($newBefore, $this->tasksQueue()->getBefore($task));
53 | }
54 |
55 | public function testCanAddMultipleTasksViaFacade()
56 | {
57 | $task = $this->task('Deploy');
58 | $after = $this->tasksQueue()->getAfter($task);
59 |
60 | $this->tasksQueue()->after('Rocketeer\Tasks\Deploy', array(
61 | 'composer install',
62 | 'bower install'
63 | ));
64 |
65 | $newAfter = array_merge($after, array('composer install', 'bower install'));
66 | $this->assertEquals($newAfter, $this->tasksQueue()->getAfter($task));
67 | }
68 |
69 | public function testCanAddSurroundTasksToNonExistingTasks()
70 | {
71 | $task = $this->task('Setup');
72 | $this->tasksQueue()->after('setup', 'composer install');
73 |
74 | $after = array('composer install');
75 | $this->assertEquals($after, $this->tasksQueue()->getAfter($task));
76 | }
77 |
78 | public function testCanAddSurroundTasksToMultipleTasks()
79 | {
80 | $this->tasksQueue()->after(array('cleanup', 'setup'), 'composer install');
81 |
82 | $after = array('composer install');
83 | $this->assertEquals($after, $this->tasksQueue()->getAfter($this->task('Setup')));
84 | $this->assertEquals($after, $this->tasksQueue()->getAfter($this->task('Cleanup')));
85 | }
86 |
87 | public function testCanGetBeforeOrAfterAnotherTaskBySlug()
88 | {
89 | $task = $this->task('Deploy');
90 | $after = $this->tasksQueue()->getAfter($task);
91 |
92 | $this->assertEquals(array('after', 'foobar'), $after);
93 | }
94 |
95 | public function testCanBuildTaskFromString()
96 | {
97 | $string = 'echo "I love ducks"';
98 |
99 | $string = $this->tasksQueue()->buildTaskFromClosure($string);
100 | $this->assertInstanceOf('Rocketeer\Tasks\Closure', $string);
101 |
102 | $closure = $string->getClosure();
103 | $this->assertInstanceOf('Closure', $closure);
104 |
105 | $closureReflection = new ReflectionFunction ($closure);
106 | $this->assertEquals(array('stringTask' => 'echo "I love ducks"'), $closureReflection->getStaticVariables());
107 |
108 | $this->assertEquals('I love ducks', $string->execute());
109 | }
110 |
111 | public function testCanBuildTaskFromClosure()
112 | {
113 | $originalClosure = function ($task) {
114 | return $task->getCommand()->info('echo "I love ducks"');
115 | };
116 |
117 | $closure = $this->tasksQueue()->buildTaskFromClosure($originalClosure);
118 | $this->assertInstanceOf('Rocketeer\Tasks\Closure', $closure);
119 | $this->assertEquals($originalClosure, $closure->getClosure());
120 | }
121 |
122 | public function testCanBuildQueue()
123 | {
124 | $queue = array(
125 | 'foobar',
126 | function ($task) {
127 | return 'lol';
128 | },
129 | 'Rocketeer\Tasks\Deploy'
130 | );
131 |
132 | $queue = $this->tasksQueue()->buildQueue($queue);
133 |
134 | $this->assertInstanceOf('Rocketeer\Tasks\Closure', $queue[0]);
135 | $this->assertInstanceOf('Rocketeer\Tasks\Closure', $queue[1]);
136 | $this->assertInstanceOf('Rocketeer\Tasks\Closure', $queue[2]);
137 | $this->assertInstanceOf('Rocketeer\Tasks\Closure', $queue[3]);
138 | $this->assertInstanceOf('Rocketeer\Tasks\Deploy', $queue[4]);
139 | $this->assertInstanceOf('Rocketeer\Tasks\Closure', $queue[5]);
140 | $this->assertInstanceOf('Rocketeer\Tasks\Closure', $queue[6]);
141 | }
142 |
143 | public function testCanRunQueue()
144 | {
145 | $this->swapConfig(array(
146 | 'rocketeer::connections' => 'production',
147 | ));
148 |
149 | $this->expectOutputString('JOEY DOESNT SHARE FOOD');
150 | $this->tasksQueue()->run(array(
151 | function ($task) {
152 | print 'JOEY DOESNT SHARE FOOD';
153 | }
154 | ), $this->getCommand());
155 | }
156 |
157 | public function testCanRunQueueOnDifferentConnectionsAndStages()
158 | {
159 | $this->swapConfig(array(
160 | 'rocketeer::connections' => array('staging', 'production'),
161 | 'rocketeer::stages.stages' => array('first', 'second'),
162 | ));
163 |
164 | $output = array();
165 | $queue = array(
166 | function ($task) use (&$output) {
167 | $output[] = $task->rocketeer->getConnection(). ' - ' .$task->rocketeer->getStage();
168 | }
169 | );
170 |
171 | $queue = $this->tasksQueue()->buildQueue($queue);
172 | $this->tasksQueue()->run($queue, $this->getCommand());
173 |
174 | $this->assertEquals(array(
175 | 'staging - first',
176 | 'staging - second',
177 | 'production - first',
178 | 'production - second',
179 | ), $output);
180 | }
181 |
182 | public function testCanRunQueueViaExecute()
183 | {
184 | $this->swapConfig(array(
185 | 'rocketeer::connections' => 'production',
186 | ));
187 |
188 | $output = $this->tasksQueue()->execute(array(
189 | 'ls -a',
190 | function ($task) {
191 | return 'JOEY DOESNT SHARE FOOD';
192 | }
193 | ));
194 |
195 | $this->assertEquals(array(
196 | '.'.PHP_EOL.'..'.PHP_EOL.'.gitkeep',
197 | 'JOEY DOESNT SHARE FOOD',
198 | ), $output);
199 | }
200 |
201 | public function testCanRunOnMultipleConnectionsViaOn()
202 | {
203 | $this->swapConfig(array(
204 | 'rocketeer::stages.stages' => array('first', 'second'),
205 | ));
206 |
207 | $output = $this->tasksQueue()->on(array('staging', 'production'), function ($task) {
208 | return $task->rocketeer->getConnection(). ' - ' .$task->rocketeer->getStage();
209 | });
210 |
211 | $this->assertEquals(array(
212 | 'staging - first',
213 | 'staging - second',
214 | 'production - first',
215 | 'production - second',
216 | ), $output);
217 | }
218 | }
219 |
--------------------------------------------------------------------------------
/tests/RocketeerTest.php:
--------------------------------------------------------------------------------
1 | app['rocketeer.rocketeer']->getAvailableConnections();
12 | $this->assertEquals(array('production', 'staging'), array_keys($connections));
13 |
14 | $this->app['rocketeer.server']->setValue('connections.custom.username', 'foobar');
15 | $connections = $this->app['rocketeer.rocketeer']->getAvailableConnections();
16 | $this->assertEquals(array('custom'), array_keys($connections));
17 | }
18 |
19 | public function testCanGetCurrentConnection()
20 | {
21 | $this->swapConfig(array('rocketeer::connections' => 'foobar'));
22 | $this->assertEquals('production', $this->app['rocketeer.rocketeer']->getConnection());
23 |
24 | $this->swapConfig(array('rocketeer::connections' => 'production'));
25 | $this->assertEquals('production', $this->app['rocketeer.rocketeer']->getConnection());
26 |
27 | $this->swapConfig(array('rocketeer::connections' => 'staging'));
28 | $this->assertEquals('staging', $this->app['rocketeer.rocketeer']->getConnection());
29 | }
30 |
31 | public function testCanChangeConnection()
32 | {
33 | $this->assertEquals('production', $this->app['rocketeer.rocketeer']->getConnection());
34 |
35 | $this->app['rocketeer.rocketeer']->setConnection('staging');
36 | $this->assertEquals('staging', $this->app['rocketeer.rocketeer']->getConnection());
37 |
38 | $this->app['rocketeer.rocketeer']->setConnections('staging,production');
39 | $this->assertEquals(array('staging', 'production'), $this->app['rocketeer.rocketeer']->getConnections());
40 | }
41 |
42 | public function testCanUseSshRepository()
43 | {
44 | $repository = 'git@github.com:Anahkiasen/rocketeer.git';
45 | $this->expectRepositoryConfig($repository, '', '');
46 |
47 | $this->assertEquals($repository, $this->app['rocketeer.rocketeer']->getRepository());
48 | }
49 |
50 | public function testCanUseHttpsRepository()
51 | {
52 | $this->expectRepositoryConfig('https://github.com/Anahkiasen/rocketeer.git', 'foobar', 'bar');
53 |
54 | $this->assertEquals('https://foobar:bar@github.com/Anahkiasen/rocketeer.git', $this->app['rocketeer.rocketeer']->getRepository());
55 | }
56 |
57 | public function testCanUseHttpsRepositoryWithUsernameProvided()
58 | {
59 | $this->expectRepositoryConfig('https://foobar@github.com/Anahkiasen/rocketeer.git', 'foobar', 'bar');
60 |
61 | $this->assertEquals('https://foobar:bar@github.com/Anahkiasen/rocketeer.git', $this->app['rocketeer.rocketeer']->getRepository());
62 | }
63 |
64 | public function testCanUseHttpsRepositoryWithOnlyUsernameProvided()
65 | {
66 | $this->expectRepositoryConfig('https://foobar@github.com/Anahkiasen/rocketeer.git', 'foobar', '');
67 |
68 | $this->assertEquals('https://foobar@github.com/Anahkiasen/rocketeer.git', $this->app['rocketeer.rocketeer']->getRepository());
69 | }
70 |
71 | public function testCanCleanupProvidedRepositoryFromCredentials()
72 | {
73 | $this->expectRepositoryConfig('https://foobar@github.com/Anahkiasen/rocketeer.git', 'Anahkiasen', '');
74 |
75 | $this->assertEquals('https://Anahkiasen@github.com/Anahkiasen/rocketeer.git', $this->app['rocketeer.rocketeer']->getRepository());
76 | }
77 |
78 | public function testCanUseHttpsRepositoryWithoutCredentials()
79 | {
80 | $this->expectRepositoryConfig('https://github.com/Anahkiasen/rocketeer.git', '', '');
81 |
82 | $this->assertEquals('https://github.com/Anahkiasen/rocketeer.git', $this->app['rocketeer.rocketeer']->getRepository());
83 | }
84 |
85 | public function testCanCheckIfRepositoryNeedsCredentials()
86 | {
87 | $this->expectRepositoryConfig('https://github.com/Anahkiasen/rocketeer.git', '', '');
88 | $this->assertTrue($this->app['rocketeer.rocketeer']->needsCredentials());
89 | }
90 |
91 | public function testCangetRepositoryBranch()
92 | {
93 | $this->assertEquals('master', $this->app['rocketeer.rocketeer']->getRepositoryBranch());
94 | }
95 |
96 | public function testCanGetApplicationName()
97 | {
98 | $this->assertEquals('foobar', $this->app['rocketeer.rocketeer']->getApplicationName());
99 | }
100 |
101 | public function testCanGetHomeFolder()
102 | {
103 | $this->assertEquals($this->server.'', $this->app['rocketeer.rocketeer']->getHomeFolder());
104 | }
105 |
106 | public function testCanGetFolderWithStage()
107 | {
108 | $this->app['rocketeer.rocketeer']->setStage('test');
109 |
110 | $this->assertEquals($this->server.'/test/current', $this->app['rocketeer.rocketeer']->getFolder('current'));
111 | }
112 |
113 | public function testCanGetAnyFolder()
114 | {
115 | $this->assertEquals($this->server.'/current', $this->app['rocketeer.rocketeer']->getFolder('current'));
116 | }
117 |
118 | public function testCanReplacePatternsInFolders()
119 | {
120 | $folder = $this->app['rocketeer.rocketeer']->getFolder('{path.storage}');
121 |
122 | $this->assertEquals($this->server.'/app/storage', $folder);
123 | }
124 |
125 | public function testCannotReplaceUnexistingPatternsInFolders()
126 | {
127 | $folder = $this->app['rocketeer.rocketeer']->getFolder('{path.foobar}');
128 |
129 | $this->assertEquals($this->server.'/', $folder);
130 | }
131 |
132 | public function testCanUseRecursiveStageConfiguration()
133 | {
134 | $this->swapConfig(array(
135 | 'rocketeer::scm.branch' => 'master',
136 | 'rocketeer::on.stages.staging.scm.branch' => 'staging',
137 | ));
138 |
139 | $this->assertEquals('master', $this->app['rocketeer.rocketeer']->getOption('scm.branch'));
140 | $this->app['rocketeer.rocketeer']->setStage('staging');
141 | $this->assertEquals('staging', $this->app['rocketeer.rocketeer']->getOption('scm.branch'));
142 | }
143 |
144 | public function testCanUseRecursiveConnectionConfiguration()
145 | {
146 | $this->swapConfig(array(
147 | 'rocketeer::connections' => 'production',
148 | 'rocketeer::scm.branch' => 'master',
149 | 'rocketeer::on.connections.staging.scm.branch' => 'staging',
150 | ));
151 | $this->assertEquals('master', $this->app['rocketeer.rocketeer']->getOption('scm.branch'));
152 |
153 | $this->swapConfig(array(
154 | 'rocketeer::connections' => 'staging',
155 | 'rocketeer::scm.branch' => 'master',
156 | 'rocketeer::on.connections.staging.scm.branch' => 'staging',
157 | ));
158 | $this->assertEquals('staging', $this->app['rocketeer.rocketeer']->getOption('scm.branch'));
159 | }
160 |
161 | ////////////////////////////////////////////////////////////////////
162 | //////////////////////////////// HELPERS ///////////////////////////
163 | ////////////////////////////////////////////////////////////////////
164 |
165 | /**
166 | * Make the config return specific SCM config
167 | *
168 | * @param string $repository
169 | * @param string $username
170 | * @param string $password
171 | *
172 | * @return void
173 | */
174 | protected function expectRepositoryConfig($repository, $username, $password)
175 | {
176 | $this->app['config']->shouldReceive('get')->with('rocketeer::scm')->andReturn(array(
177 | 'repository' => $repository,
178 | 'username' => $username,
179 | 'password' => $password,
180 | ));
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/src/Rocketeer/RocketeerServiceProvider.php:
--------------------------------------------------------------------------------
1 | app = static::make($this->app);
47 | }
48 |
49 | /**
50 | * Get the services provided by the provider.
51 | *
52 | * @return array
53 | */
54 | public function provides()
55 | {
56 | return array('rocketeer');
57 | }
58 |
59 | ////////////////////////////////////////////////////////////////////
60 | /////////////////////////// CLASS BINDINGS /////////////////////////
61 | ////////////////////////////////////////////////////////////////////
62 |
63 | /**
64 | * Make a Rocketeer container
65 | *
66 | * @return Container
67 | */
68 | public static function make($app = null)
69 | {
70 | if (!$app) {
71 | $app = new Container;
72 | }
73 |
74 | $serviceProvider = new static($app);
75 |
76 | // Bind classes
77 | $app = $serviceProvider->bindCoreClasses($app);
78 | $app = $serviceProvider->bindClasses($app);
79 | $app = $serviceProvider->bindScm($app);
80 | $app = $serviceProvider->bindCommands($app);
81 |
82 | return $app;
83 | }
84 |
85 | /**
86 | * Bind the core classes
87 | *
88 | * @param Container $app
89 | *
90 | * @return Container
91 | */
92 | public function bindCoreClasses(Container $app)
93 | {
94 | $app->bindIf('files', 'Illuminate\Filesystem\Filesystem');
95 |
96 | $app->bindIf('request', function ($app) {
97 | return Request::createFromGlobals();
98 | }, true);
99 |
100 | $app->bindIf('config', function ($app) {
101 | $fileloader = new FileLoader($app['files'], __DIR__.'/../config');
102 |
103 | return new Repository($fileloader, 'config');
104 | }, true);
105 |
106 | $app->bindIf('remote', function ($app) {
107 | return new RemoteManager($app);
108 | }, true);
109 |
110 | // Register factory and custom configurations
111 | $app = $this->registerConfig($app);
112 |
113 | return $app;
114 | }
115 |
116 | /**
117 | * Bind the Rocketeer classes to the Container
118 | *
119 | * @param Container $app
120 | *
121 | * @return Container
122 | */
123 | public function bindClasses(Container $app)
124 | {
125 | $app->singleton('rocketeer.rocketeer', function ($app) {
126 | return new Rocketeer($app);
127 | });
128 |
129 | $app->bind('rocketeer.releases', function ($app) {
130 | return new ReleasesManager($app);
131 | });
132 |
133 | $app->bind('rocketeer.server', function ($app) {
134 | return new Server($app);
135 | });
136 |
137 | $app->bind('rocketeer.bash', function ($app) {
138 | return new Bash($app);
139 | });
140 |
141 | $app->singleton('rocketeer.tasks', function ($app) {
142 | return new TasksQueue($app);
143 | });
144 |
145 | $app->singleton('rocketeer.console', function ($app) {
146 | return new Console('Rocketeer', Rocketeer::VERSION);
147 | });
148 |
149 | $app['rocketeer.console']->setLaravel($app);
150 |
151 | return $app;
152 | }
153 |
154 | /**
155 | * Bind the SCM instance
156 | *
157 | * @param Container $app
158 | *
159 | * @return Container
160 | */
161 | public function bindScm(Container $app)
162 | {
163 | // Currently only one
164 | $scm = $this->app['rocketeer.rocketeer']->getOption('scm.scm');
165 | $scm = 'Rocketeer\Scm\\'.ucfirst($scm);
166 |
167 | $app->bind('rocketeer.scm', function ($app) use ($scm) {
168 | return new $scm($app);
169 | });
170 |
171 | return $app;
172 | }
173 |
174 | /**
175 | * Bind the commands to the Container
176 | *
177 | * @param Container $app
178 | *
179 | * @return Container
180 | */
181 | public function bindCommands(Container $app)
182 | {
183 | // Base commands
184 | $tasks = array(
185 | '' => '',
186 | 'check' => 'Check',
187 | 'cleanup' => 'Cleanup',
188 | 'current' => 'CurrentRelease',
189 | 'deploy' => 'Deploy',
190 | 'flush' => 'Flush',
191 | 'ignite' => 'Ignite',
192 | 'rollback' => 'Rollback',
193 | 'setup' => 'Setup',
194 | 'teardown' => 'Teardown',
195 | 'test' => 'Test',
196 | 'update' => 'Update',
197 | );
198 |
199 | // Add User commands
200 | $userTasks = (array) $this->app['config']->get('rocketeer::tasks.custom');
201 | $tasks = array_merge($tasks, $userTasks);
202 |
203 | // Bind the commands
204 | foreach ($tasks as $slug => $task) {
205 |
206 | // Check if we have an actual command to use
207 | $commandClass = 'Rocketeer\Commands\Deploy'.$task.'Command';
208 | $fakeCommand = !class_exists($commandClass);
209 |
210 | // Build command slug
211 | if ($fakeCommand) {
212 | $taskInstance = $this->app['rocketeer.tasks']->buildTask($task);
213 | if (is_numeric($slug)) {
214 | $slug = $taskInstance->getSlug();
215 | }
216 | }
217 |
218 | // Add command to array
219 | $command = trim('deploy.'.$slug, '.');
220 | $this->commands[] = $command;
221 |
222 | // Look for an existing command
223 | if (!$fakeCommand) {
224 | $this->app->bind($command, function ($app) use ($commandClass) {
225 | return new $commandClass;
226 | });
227 |
228 | // Else create a fake one
229 | } else {
230 | $this->app->bind($command, function ($app) use ($taskInstance, $slug) {
231 | return new Commands\BaseTaskCommand($taskInstance, $slug);
232 | });
233 | }
234 |
235 | }
236 |
237 | // Add commands to Artisan
238 | foreach ($this->commands as $command) {
239 | $app['rocketeer.console']->add($app[$command]);
240 | if (isset($app['events'])) {
241 | $this->commands($command);
242 | }
243 | }
244 |
245 | return $app;
246 | }
247 |
248 | ////////////////////////////////////////////////////////////////////
249 | /////////////////////////////// HELPERS ////////////////////////////
250 | ////////////////////////////////////////////////////////////////////
251 |
252 | /**
253 | * Register factory and custom configurations
254 | *
255 | * @param Container $app
256 | *
257 | * @return Container
258 | */
259 | protected function registerConfig(Container $app)
260 | {
261 | // Register paths
262 | if (!$app->bound('path.base')) {
263 | $app['path.base'] = realpath(__DIR__.'/../../../');
264 | }
265 |
266 | // Register config file
267 | $app['config']->package('anahkiasen/rocketeer', __DIR__.'/../config');
268 |
269 | // Register custom config
270 | $custom = $app['path.base'].'/rocketeer.php';
271 | if (file_exists($custom)) {
272 | $app['config']->afterLoading('rocketeer', function ($me, $group, $items) use ($custom) {
273 | $custom = include $custom;
274 | return array_replace_recursive($items, $custom);
275 | });
276 | }
277 |
278 | return $app;
279 | }
280 | }
281 |
--------------------------------------------------------------------------------
/src/Rocketeer/Traits/Task.php:
--------------------------------------------------------------------------------
1 | description;
51 | }
52 |
53 | /**
54 | * Run the Task
55 | *
56 | * @return void
57 | */
58 | abstract public function execute();
59 |
60 | ////////////////////////////////////////////////////////////////////
61 | /////////////////////////////// HELPERS ////////////////////////////
62 | ////////////////////////////////////////////////////////////////////
63 |
64 | /**
65 | * Check if the remote server is setup
66 | *
67 | * @return boolean
68 | */
69 | public function isSetup()
70 | {
71 | return $this->fileExists($this->rocketeer->getFolder('current'));
72 | }
73 |
74 | /**
75 | * Check if the Task uses stages
76 | *
77 | * @return boolean
78 | */
79 | public function usesStages()
80 | {
81 | $stages = $this->rocketeer->getStages();
82 |
83 | return $this->usesStages and !empty($stages);
84 | }
85 |
86 | /**
87 | * Run actions in the current release's folder
88 | *
89 | * @param string|array $tasks One or more tasks
90 | *
91 | * @return string
92 | */
93 | public function runForCurrentRelease($tasks)
94 | {
95 | return $this->runInFolder($this->releasesManager->getCurrentReleasePath(), $tasks);
96 | }
97 |
98 | /**
99 | * Execute another Task by name
100 | *
101 | * @param string $task
102 | *
103 | * @return string The Task's output
104 | */
105 | public function executeTask($task)
106 | {
107 | return $this->app['rocketeer.tasks']->buildTask($task)->execute();
108 | }
109 |
110 | ////////////////////////////////////////////////////////////////////
111 | //////////////////////////////// TASKS /////////////////////////////
112 | ////////////////////////////////////////////////////////////////////
113 |
114 | /**
115 | * Clone the repo into a release folder
116 | *
117 | * @param string $destination Where to clone to
118 | *
119 | * @return string
120 | */
121 | public function cloneRepository($destination = null)
122 | {
123 | if (!$destination) {
124 | $destination = $this->releasesManager->getCurrentReleasePath();
125 | }
126 |
127 | $this->command->info('Cloning repository in "' .$destination. '"');
128 | $output = $this->scm->execute('checkout', $destination);
129 |
130 | $status = $this->checkStatus('Unable to clone the repository', $output);
131 | if ($status === false) {
132 | // Forget the SCM credentials if they're invalid
133 | $this->server->forgetValue('credentials');
134 | }
135 |
136 | return $status;
137 | }
138 |
139 | /**
140 | * Update the current release
141 | *
142 | * @param boolean $reset Whether the repository should be reset first
143 | *
144 | * @return string
145 | */
146 | public function updateRepository($reset = true)
147 | {
148 | $this->command->info('Pulling changes');
149 | $tasks = array($this->scm->update());
150 |
151 | // Reset if requested
152 | if ($reset) {
153 | array_unshift($tasks, $this->scm->reset());
154 | }
155 |
156 | return $this->runForCurrentRelease($tasks);
157 | }
158 |
159 | /**
160 | * Update the current symlink
161 | *
162 | * @param integer $release A release to mark as current
163 | *
164 | * @return string
165 | */
166 | public function updateSymlink($release = null)
167 | {
168 | // If the release is specified, update to make it the current one
169 | if ($release) {
170 | $this->releasesManager->updateCurrentRelease($release);
171 | }
172 |
173 | // Get path to current/ folder and latest release
174 | $currentReleasePath = $this->releasesManager->getCurrentReleasePath();
175 | $currentFolder = $this->rocketeer->getFolder('current');
176 |
177 | return $this->symlink($currentReleasePath, $currentFolder);
178 | }
179 |
180 | /**
181 | * Share a file or folder between releases
182 | *
183 | * @param string $file Path to the file in a release folder
184 | *
185 | * @return string
186 | */
187 | public function share($file)
188 | {
189 | // Get path to current file and shared file
190 | $currentFile = $this->releasesManager->getCurrentReleasePath($file);
191 | $sharedFile = preg_replace('#releases/[0-9]+/#', 'shared/', $currentFile);
192 |
193 | // If no instance of the shared file exists, use current one
194 | if (!$this->fileExists($sharedFile)) {
195 | $this->move($currentFile, $sharedFile);
196 | }
197 |
198 | $this->command->comment('Sharing file '.$currentFile);
199 |
200 | return $this->symlink($sharedFile, $currentFile);
201 | }
202 |
203 | /**
204 | * Set a folder as web-writable
205 | *
206 | * @param string $folder
207 | *
208 | * @return string
209 | */
210 | public function setPermissions($folder)
211 | {
212 | $commands = array();
213 |
214 | // Get path to folder
215 | $folder = $this->releasesManager->getCurrentReleasePath($folder);
216 | $this->command->comment('Setting permissions for '.$folder);
217 |
218 | // Get permissions options
219 | $options = $this->rocketeer->getOption('remote.permissions');
220 | $chmod = array_get($options, 'permissions');
221 | $user = array_get($options, 'webuser.user');
222 | $group = array_get($options, 'webuser.group');
223 |
224 | // Add chmod
225 | if ($chmod) {
226 | $commands[] = sprintf('chmod -R %s %s', $chmod, $folder);
227 | $commands[] = sprintf('chmod -R g+s %s', $folder);
228 | }
229 |
230 | // And chown
231 | if ($user and $group) {
232 | $commands[] = sprintf('chown -R %s:%s %s', $user, $group, $folder);
233 | }
234 |
235 | // Cancel if setting of permissions is not configured
236 | if (empty($commands)) {
237 | return true;
238 | }
239 |
240 | return $this->runForCurrentRelease($commands);
241 | }
242 |
243 | ////////////////////////////////////////////////////////////////////
244 | //////////////////////// LARAVEL-SPECIFIC TASKS ////////////////////
245 | ////////////////////////////////////////////////////////////////////
246 |
247 | /**
248 | * Run Composer on the folder
249 | *
250 | * @return string
251 | */
252 | public function runComposer()
253 | {
254 | $this->command->comment('Installing Composer dependencies');
255 | $output = $this->runForCurrentRelease($this->getComposer(). ' install');
256 |
257 | return $this->checkStatus('Composer could not install dependencies', $output);
258 | }
259 |
260 | /**
261 | * Get the path to Composer binary
262 | *
263 | * @return string
264 | */
265 | public function getComposer()
266 | {
267 | $composer = $this->which('composer', $this->releasesManager->getCurrentReleasePath().'/composer.phar');
268 | if (strpos($composer, 'composer.phar') !== false) {
269 | $composer = 'php '.$composer;
270 | }
271 |
272 | return $composer;
273 | }
274 |
275 | /**
276 | * Run any outstanding migrations
277 | *
278 | * @param boolean $seed Whether the database should also be seeded
279 | *
280 | * @return string
281 | */
282 | public function runMigrations($seed = false)
283 | {
284 | $seed = $seed ? ' --seed' : null;
285 | $this->command->comment('Running outstanding migrations');
286 |
287 | return $this->runForCurrentRelease('php artisan migrate'.$seed);
288 | }
289 |
290 | /**
291 | * Run the application's tests
292 | *
293 | * @param string $arguments Additional arguments to pass to PHPUnit
294 | *
295 | * @return boolean
296 | */
297 | public function runTests($arguments = null)
298 | {
299 | // Look for PHPUnit
300 | $phpunit = $this->which('phpunit', $this->releasesManager->getCurrentReleasePath().'/vendor/bin/phpunit');
301 | if (!$phpunit) {
302 | return true;
303 | }
304 |
305 | // Run PHPUnit
306 | $this->command->info('Running tests...');
307 | $output = $this->runForCurrentRelease(array(
308 | $phpunit. ' --stop-on-failure '.$arguments,
309 | ));
310 |
311 | return $this->checkStatus('Tests failed', $output, 'Tests passed successfully');
312 | }
313 | }
314 |
--------------------------------------------------------------------------------
/src/Rocketeer/Rocketeer.php:
--------------------------------------------------------------------------------
1 | app = $app;
56 | }
57 |
58 | /**
59 | * Get an option from Rocketeer's config file
60 | *
61 | * @param string $option
62 | *
63 | * @return mixed
64 | */
65 | public function getOption($option)
66 | {
67 | if ($contextual = $this->getContextualOption($option, 'stages')) {
68 | return $contextual;
69 | }
70 |
71 | if ($contextual = $this->getContextualOption($option, 'connections')) {
72 | return $contextual;
73 | }
74 |
75 | return $this->app['config']->get('rocketeer::'.$option);
76 | }
77 |
78 | /**
79 | * Get a contextual option
80 | *
81 | * @param string $option
82 | * @param string $type [stage,connection]
83 | *
84 | * @return mixed
85 | */
86 | protected function getContextualOption($option, $type)
87 | {
88 | switch ($type) {
89 | case 'stages':
90 | $contextual = sprintf('rocketeer::on.stages.%s.%s', $this->stage, $option);
91 | break;
92 |
93 | case 'connections':
94 | $contextual = sprintf('rocketeer::on.connections.%s.%s', $this->getConnection(), $option);
95 | break;
96 | }
97 |
98 | return $this->app['config']->get($contextual);
99 | }
100 |
101 | ////////////////////////////////////////////////////////////////////
102 | //////////////////////////////// STAGES ////////////////////////////
103 | ////////////////////////////////////////////////////////////////////
104 |
105 | /**
106 | * Set the stage Tasks will execute on
107 | *
108 | * @param string $stage
109 | *
110 | * @return void
111 | */
112 | public function setStage($stage)
113 | {
114 | $this->stage = $stage;
115 | }
116 |
117 | /**
118 | * Get the current stage
119 | *
120 | * @return string
121 | */
122 | public function getStage()
123 | {
124 | return $this->stage;
125 | }
126 |
127 | /**
128 | * Get the various stages provided by the User
129 | *
130 | * @return array
131 | */
132 | public function getStages()
133 | {
134 | return $this->getOption('stages.stages');
135 | }
136 |
137 | ////////////////////////////////////////////////////////////////////
138 | ///////////////////////////// APPLICATION //////////////////////////
139 | ////////////////////////////////////////////////////////////////////
140 |
141 | /**
142 | * Whether the repository used is using SSH or HTTPS
143 | *
144 | * @return boolean
145 | */
146 | public function needsCredentials()
147 | {
148 | return Str::contains($this->getRepository(), 'https://');
149 | }
150 |
151 | /**
152 | * Get the available connections
153 | *
154 | * @return array
155 | */
156 | public function getAvailableConnections()
157 | {
158 | $connections = $this->app['rocketeer.server']->getValue('connections');
159 | if (!$connections) {
160 | $connections = $this->app['config']->get('remote.connections');
161 | }
162 |
163 | return $connections;
164 | }
165 |
166 | /**
167 | * Check if a connection has credentials related to it
168 | *
169 | * @param string $connection
170 | *
171 | * @return boolean
172 | */
173 | public function isValidConnection($connection)
174 | {
175 | $available = (array) $this->getAvailableConnections();
176 |
177 | return array_key_exists($connection, $available);
178 | }
179 |
180 | /**
181 | * Get the connection in use
182 | *
183 | * @return string
184 | */
185 | public function getConnections()
186 | {
187 | // Get cached resolved connections
188 | if ($this->connections) {
189 | return $this->connections;
190 | }
191 |
192 | // Get all and defaults
193 | $connections = (array) $this->app['config']->get('rocketeer::connections');
194 | $default = $this->app['config']->get('remote.default');
195 |
196 | // Remove invalid connections
197 | $instance = $this;
198 | $connections = array_filter($connections, function ($value) use ($instance) {
199 | return $instance->isValidConnection($value);
200 | });
201 |
202 | // Return default if no active connection(s) set
203 | if (empty($connections)) {
204 | return array($default);
205 | }
206 |
207 | // Set current connection as default
208 | $this->connections = $connections;
209 |
210 | return $connections;
211 | }
212 |
213 | /**
214 | * Get the active connection
215 | *
216 | * @return string
217 | */
218 | public function getConnection()
219 | {
220 | // Get cached resolved connection
221 | if ($this->connection) {
222 | return $this->connection;
223 | }
224 |
225 | $connection = array_get($this->getConnections(), 0);
226 | $this->connection = $connection;
227 |
228 | return $this->connection;
229 | }
230 |
231 | /**
232 | * Set the active connections
233 | *
234 | * @param string|array $connections
235 | */
236 | public function setConnections($connections)
237 | {
238 | if (!is_array($connections)) {
239 | $connections = explode(',', $connections);
240 | }
241 |
242 | $this->connections = $connections;
243 | }
244 |
245 | /**
246 | * Set the curent connection
247 | *
248 | * @param string $connection
249 | */
250 | public function setConnection($connection)
251 | {
252 | if ($this->isValidConnection($connection)) {
253 | $this->connection = $connection;
254 | $this->app['config']->set('remote.default', $connection);
255 | }
256 | }
257 |
258 | /**
259 | * Flush active connection(s)
260 | *
261 | * @return void
262 | */
263 | public function disconnect()
264 | {
265 | $this->connection = null;
266 | $this->connections = null;
267 | }
268 |
269 | /**
270 | * Get the name of the application to deploy
271 | *
272 | * @return string
273 | */
274 | public function getApplicationName()
275 | {
276 | return $this->getOption('remote.application_name');
277 | }
278 |
279 | ////////////////////////////////////////////////////////////////////
280 | /////////////////////////// GIT REPOSITORY /////////////////////////
281 | ////////////////////////////////////////////////////////////////////
282 |
283 | /**
284 | * Get the credentials for the repository
285 | *
286 | * @return array
287 | */
288 | public function getCredentials()
289 | {
290 | $credentials = $this->app['rocketeer.server']->getValue('credentials');
291 | if (!$credentials) {
292 | $credentials = $this->getOption('scm');
293 | }
294 |
295 | return $credentials;
296 | }
297 |
298 | /**
299 | * Get the URL to the Git repository
300 | *
301 | * @param string $username
302 | * @param string $password
303 | *
304 | * @return string
305 | */
306 | public function getRepository()
307 | {
308 | // Get credentials
309 | $repository = $this->getCredentials();
310 | $username = array_get($repository, 'username');
311 | $password = array_get($repository, 'password');
312 | $repository = array_get($repository, 'repository');
313 |
314 | // Add credentials if possible
315 | if ($username or $password) {
316 |
317 | // Build credentials chain
318 | $credentials = $password ? $username.':'.$password : $username;
319 | $credentials .= '@';
320 |
321 | // Add them in chain
322 | $repository = preg_replace('#https://(.+)@#', 'https://', $repository);
323 | $repository = str_replace('https://', 'https://'.$credentials, $repository);
324 | }
325 |
326 | return $repository;
327 | }
328 |
329 | /**
330 | * Get the Git branch
331 | *
332 | * @return string
333 | */
334 | public function getRepositoryBranch()
335 | {
336 | exec($this->app['rocketeer.scm']->currentBranch(), $fallback);
337 | $fallback = trim($fallback[0]) ?: 'master';
338 | $branch = $this->getOption('scm.branch') ?: $fallback;
339 |
340 | return $branch;
341 | }
342 |
343 | ////////////////////////////////////////////////////////////////////
344 | //////////////////////////////// PATHS /////////////////////////////
345 | ////////////////////////////////////////////////////////////////////
346 |
347 | /**
348 | * Replace patterns in a folder path
349 | *
350 | * @param string $path
351 | *
352 | * @return string
353 | */
354 | public function replacePatterns($path)
355 | {
356 | $app = $this->app;
357 |
358 | // Replace folder patterns
359 | return preg_replace_callback('/\{[a-z\.]+\}/', function ($match) use ($app) {
360 | $folder = substr($match[0], 1, -1);
361 |
362 | if ($app->bound($folder)) {
363 | return str_replace($app['path.base'].'/', null, $app->make($folder));
364 | }
365 |
366 | return false;
367 | }, $path);
368 | }
369 |
370 | /**
371 | * Get the path to a folder, taking into account application name and stage
372 | *
373 | * @param string $folder
374 | *
375 | * @return string
376 | */
377 | public function getFolder($folder = null)
378 | {
379 | $folder = $this->replacePatterns($folder);
380 |
381 | $base = $this->getHomeFolder().'/';
382 | if ($folder and $this->stage) {
383 | $base .= $this->stage.'/';
384 | }
385 | $folder = str_replace($base, null, $folder);
386 |
387 | return $base.$folder;
388 | }
389 |
390 | /**
391 | * Get the path to the root folder of the application
392 | *
393 | * @return string
394 | */
395 | public function getHomeFolder()
396 | {
397 | $rootDirectory = $this->getOption('remote.root_directory');
398 | $rootDirectory = Str::finish($rootDirectory, '/');
399 |
400 | return $rootDirectory.$this->getApplicationName();
401 | }
402 | }
403 |
--------------------------------------------------------------------------------
/tests/_start.php:
--------------------------------------------------------------------------------
1 | server = __DIR__.'/server/foobar';
49 | $this->deploymentsFile = __DIR__.'/meta/deployments.json';
50 |
51 | $this->app = new Container;
52 |
53 | // Laravel classes --------------------------------------------- /
54 |
55 | $this->app->instance('path.base', '/src');
56 | $this->app->instance('path', '/src/app');
57 | $this->app->instance('path.public', '/src/public');
58 | $this->app->instance('path.storage', '/src/app/storage');
59 |
60 | $this->app['files'] = new Filesystem;
61 | $this->app['config'] = $this->getConfig();
62 | $this->app['remote'] = $this->getRemote();
63 | $this->app['artisan'] = $this->getArtisan();
64 |
65 | // Rocketeer classes ------------------------------------------- /
66 |
67 | $serviceProvider = new RocketeerServiceProvider($this->app);
68 | $this->app = $serviceProvider->bindClasses($this->app);
69 | $this->app = $serviceProvider->bindScm($this->app);
70 |
71 | $this->app->bind('rocketeer.server', function ($app) {
72 | return new Rocketeer\Server($app, __DIR__.'/meta');
73 | });
74 |
75 | $command = $this->getCommand();
76 | $this->app->singleton('rocketeer.tasks', function ($app) use ($command) {
77 | return new Rocketeer\TasksQueue($app, $command);
78 | });
79 |
80 | // Bind dummy Task
81 | $this->task = $this->task('Cleanup');
82 | $this->recreateVirtualServer();
83 | }
84 |
85 | /**
86 | * Tears down the tests
87 | *
88 | * @return void
89 | */
90 | public function tearDown()
91 | {
92 | Mockery::close();
93 | }
94 |
95 | /**
96 | * Recreates the local file server
97 | *
98 | * @return void
99 | */
100 | protected function recreateVirtualServer()
101 | {
102 | // Recreate deployments file
103 | $this->app['files']->put($this->deploymentsFile, json_encode(array(
104 | "foo" => "bar",
105 | "current_release" => 20000000000000,
106 | "directory_separator" => "/",
107 | "is_setup" => true,
108 | "webuser" => array("username" => "www-datda","group" => "www-datda"),
109 | "line_endings" => "\n",
110 | )));
111 |
112 | // Recreate altered local server
113 | $this->app['files']->deleteDirectory(__DIR__.'/../storage');
114 | $folders = array('current', 'shared', 'releases', 'releases/10000000000000', 'releases/20000000000000');
115 | foreach ($folders as $folder) {
116 | $folder = $this->server.'/'.$folder;
117 |
118 | $this->app['files']->deleteDirectory($folder);
119 | $this->app['files']->delete($folder);
120 | $this->app['files']->makeDirectory($folder, 0777, true);
121 | file_put_contents($folder.'/.gitkeep', '');
122 | }
123 |
124 | // Delete rocketeer binary
125 | $binary = __DIR__.'/../rocketeer.php';
126 | $this->app['files']->delete($binary);
127 | }
128 |
129 | ////////////////////////////////////////////////////////////////////
130 | /////////////////////////////// HELPERS ////////////////////////////
131 | ////////////////////////////////////////////////////////////////////
132 |
133 | /**
134 | * Get a pretend Task to run bogus commands
135 | *
136 | * @return Task
137 | */
138 | protected function pretendTask($task = 'Deploy', $options = array())
139 | {
140 | // Default options
141 | $default = array('pretend' => true, 'verbose' => false);
142 | $options = array_merge($default, $options);
143 |
144 | // Create command
145 | $command = clone $this->getCommand();
146 | foreach ($options as $name => $value) {
147 | $command->shouldReceive('option')->with($name)->andReturn($value);
148 | }
149 |
150 | // Bind it to Task
151 | $task = $this->task($task);
152 | $task->command = $command;
153 |
154 | return $task;
155 | }
156 |
157 | /**
158 | * Get Task instance
159 | *
160 | * @param string $task
161 | *
162 | * @return Task
163 | */
164 | protected function task($task = null, $command = null)
165 | {
166 | if ($command) {
167 | $this->app->singleton('rocketeer.tasks', function ($app) use ($command) {
168 | return new Rocketeer\TasksQueue($app, $command);
169 | });
170 | }
171 |
172 | if (!$task) {
173 | return $this->task;
174 | }
175 |
176 | return $this->tasksQueue()->buildTask('Rocketeer\Tasks\\'.$task);
177 | }
178 |
179 | /**
180 | * Get TasksQueue instance
181 | *
182 | * @return TasksQueue
183 | */
184 | protected function tasksQueue()
185 | {
186 | return $this->app['rocketeer.tasks'];
187 | }
188 |
189 | ////////////////////////////////////////////////////////////////////
190 | ///////////////////////////// DEPENDENCIES /////////////////////////
191 | ////////////////////////////////////////////////////////////////////
192 |
193 | /**
194 | * Mock the Command class
195 | *
196 | * @return Mockery
197 | */
198 | protected function getCommand()
199 | {
200 | $message = function ($message) {
201 | return $message;
202 | };
203 |
204 | $command = Mockery::mock('Command');
205 | $command->shouldReceive('comment')->andReturnUsing($message);
206 | $command->shouldReceive('error')->andReturnUsing($message);
207 | $command->shouldReceive('line')->andReturnUsing($message);
208 | $command->shouldReceive('info')->andReturnUsing($message);
209 | $command->shouldReceive('argument');
210 | $command->shouldReceive('ask');
211 | $command->shouldReceive('confirm')->andReturn(true);
212 | $command->shouldReceive('secret');
213 | $command->shouldReceive('option')->andReturn(null)->byDefault();
214 |
215 | return $command;
216 | }
217 |
218 | /**
219 | * Mock the Config component
220 | *
221 | * @return Mockery
222 | */
223 | protected function getConfig($options = array())
224 | {
225 | $config = Mockery::mock('Illuminate\Config\Repository');
226 | $config->shouldIgnoreMissing();
227 |
228 | foreach ($options as $key => $value) {
229 | $config->shouldReceive('get')->with($key)->andReturn($value);
230 | }
231 |
232 | // Drivers
233 | $config->shouldReceive('get')->with('cache.driver')->andReturn('file');
234 | $config->shouldReceive('get')->with('database.default')->andReturn('mysql');
235 | $config->shouldReceive('get')->with('remote.default')->andReturn('production');
236 | $config->shouldReceive('get')->with('remote.connections')->andReturn(array('production' => array(), 'staging' => array()));
237 | $config->shouldReceive('get')->with('session.driver')->andReturn('file');
238 |
239 | // Rocketeer
240 | $config->shouldReceive('get')->with('rocketeer::connections')->andReturn(array('production', 'staging'));
241 | $config->shouldReceive('get')->with('rocketeer::remote.application_name')->andReturn('foobar');
242 | $config->shouldReceive('get')->with('rocketeer::remote.keep_releases')->andReturn(1);
243 | $config->shouldReceive('get')->with('rocketeer::remote.permissions')->andReturn(array(
244 | 'permissions' => 755,
245 | 'webuser' => array('user' => 'www-data', 'group' => 'www-data')
246 | ));
247 | $config->shouldReceive('get')->with('rocketeer::remote.permissions.files')->andReturn(array('tests'));
248 | $config->shouldReceive('get')->with('rocketeer::remote.root_directory')->andReturn(__DIR__.'/server/');
249 | $config->shouldReceive('get')->with('rocketeer::remote.shared')->andReturn(array('tests/meta'));
250 | $config->shouldReceive('get')->with('rocketeer::stages.default')->andReturn(null);
251 | $config->shouldReceive('get')->with('rocketeer::stages.stages')->andReturn(array());
252 |
253 | // SCM
254 | $config->shouldReceive('get')->with('rocketeer::scm.branch')->andReturn('master');
255 | $config->shouldReceive('get')->with('rocketeer::scm.repository')->andReturn('https://github.com/Anahkiasen/rocketeer.git');
256 | $config->shouldReceive('get')->with('rocketeer::scm.scm')->andReturn('git');
257 |
258 | // Tasks
259 | $config->shouldReceive('get')->with('rocketeer::tasks')->andReturn(array(
260 | 'before' => array(
261 | 'deploy' => array(
262 | 'before',
263 | 'foobar'
264 | ),
265 | ),
266 | 'after' => array(
267 | 'check' => array(
268 | 'Tasks\MyCustomTask',
269 | ),
270 | 'Rocketeer\Tasks\Deploy' => array(
271 | 'after',
272 | 'foobar'
273 | ),
274 | ),
275 | ));
276 |
277 | return $config;
278 | }
279 |
280 | /**
281 | * Swap the current config
282 | *
283 | * @param array $config
284 | *
285 | * @return void
286 | */
287 | protected function swapConfig($config)
288 | {
289 | $this->app['rocketeer.rocketeer']->disconnect();
290 | $this->app['config'] = $this->getConfig($config);
291 | }
292 |
293 | /**
294 | * Mock the Remote component
295 | *
296 | * @return Mockery
297 | */
298 | protected function getRemote()
299 | {
300 | $run = function ($task, $callback) {
301 | if (is_array($task)) {
302 | $task = implode(' && ', $task);
303 | }
304 | $output = shell_exec($task);
305 |
306 | $callback($output);
307 | };
308 |
309 | $remote = Mockery::mock('Illuminate\Remote\Connection');
310 | $remote->shouldReceive('into')->andReturn(Mockery::self());
311 | $remote->shouldReceive('status')->andReturn(0)->byDefault();
312 | $remote->shouldReceive('run')->andReturnUsing($run)->byDefault();
313 | $remote->shouldReceive('runRaw')->andReturnUsing($run)->byDefault();
314 | $remote->shouldReceive('display')->andReturnUsing(function ($line) {
315 | print $line.PHP_EOL;
316 | });
317 |
318 | return $remote;
319 | }
320 |
321 | /**
322 | * Mock Artisan
323 | *
324 | * @return Mockery
325 | */
326 | protected function getArtisan()
327 | {
328 | $artisan = Mockery::mock('Artisan');
329 | $artisan->shouldReceive('add')->andReturnUsing(function ($command) {
330 | return $command;
331 | });
332 |
333 | return $artisan;
334 | }
335 | }
336 |
--------------------------------------------------------------------------------
/src/Rocketeer/Bash.php:
--------------------------------------------------------------------------------
1 | app = $app;
49 | $this->command = $command;
50 | }
51 |
52 | /**
53 | * Get an instance from the Container
54 | *
55 | * @param string $key
56 | *
57 | * @return object
58 | */
59 | public function __get($key)
60 | {
61 | $shortcuts = array(
62 | 'releasesManager' => 'rocketeer.releases',
63 | 'server' => 'rocketeer.server',
64 | 'rocketeer' => 'rocketeer.rocketeer',
65 | 'scm' => 'rocketeer.scm',
66 | );
67 |
68 | // Replace shortcuts
69 | if (array_key_exists($key, $shortcuts)) {
70 | $key = $shortcuts[$key];
71 | }
72 |
73 | return $this->app[$key];
74 | }
75 |
76 | /**
77 | * Set an instance on the Container
78 | *
79 | * @param string $key
80 | * @param object $value
81 | */
82 | public function __set($key, $value)
83 | {
84 | $this->app[$key] = $value;
85 | }
86 |
87 | ////////////////////////////////////////////////////////////////////
88 | ///////////////////////////// CORE METHODS /////////////////////////
89 | ////////////////////////////////////////////////////////////////////
90 |
91 | /**
92 | * Run actions on the remote server and gather the ouput
93 | *
94 | * @param string|array $commands One or more commands
95 | * @param boolean $silent Whether the command should stay silent no matter what
96 | * @param boolean $array Whether the output should be returned as an array
97 | *
98 | * @return string|array
99 | */
100 | public function run($commands, $silent = false, $array = false)
101 | {
102 | $output = null;
103 | $commands = $this->processCommands($commands);
104 | $verbose = $this->getOption('verbose') and !$silent;
105 |
106 | // Log the commands for pretend
107 | if ($this->getOption('pretend') and !$silent) {
108 | $this->command->line(implode(PHP_EOL, $commands));
109 | $commands = (sizeof($commands) == 1) ? $commands[0] : $commands;
110 | $this->history[] = $commands;
111 |
112 | return $commands;
113 | }
114 |
115 | // Run commands
116 | $bash = $this;
117 | $this->remote->run($commands, function ($results) use (&$output, $verbose, $bash) {
118 | $output .= $results;
119 |
120 | if ($verbose) {
121 | $bash->remote->display(trim($results));
122 | }
123 | });
124 |
125 | // Explode output if necessary
126 | if ($array) {
127 | $output = explode($this->server->getLineEndings(), $output);
128 | }
129 |
130 | // Trim output
131 | $output = is_array($output)
132 | ? array_filter($output)
133 | : trim($output);
134 |
135 | // Append output
136 | $this->history[] = $output;
137 |
138 | return $output;
139 | }
140 |
141 | /**
142 | * Run a raw command, without any processing, and
143 | * get its output as a string or array
144 | *
145 | * @param string|array $commands
146 | * @param boolean $array Whether the output should be returned as an array
147 | *
148 | * @return string
149 | */
150 | public function runRaw($commands, $array = false)
151 | {
152 | $output = null;
153 |
154 | // Run commands
155 | $this->remote->run($commands, function ($results) use (&$output) {
156 | $output .= $results;
157 | });
158 |
159 | // Explode output if necessary
160 | if ($array) {
161 | $output = explode($this->server->getLineEndings(), $output);
162 | $output = array_filter($output);
163 | }
164 |
165 | return $output;
166 | }
167 |
168 | /**
169 | * Run commands in a folder
170 | *
171 | * @param string $folder
172 | * @param string|array $tasks
173 | *
174 | * @return string
175 | */
176 | public function runInFolder($folder = null, $tasks = array())
177 | {
178 | // Convert to array
179 | if (!is_array($tasks)) {
180 | $tasks = array($tasks);
181 | }
182 |
183 | // Prepend folder
184 | array_unshift($tasks, 'cd '.$this->rocketeer->getFolder($folder));
185 |
186 | return $this->run($tasks);
187 | }
188 |
189 | /**
190 | * Get a binary
191 | *
192 | * @param string $binary The name of the binary
193 | * @param string $fallback A fallback location
194 | *
195 | * @return string
196 | */
197 | public function which($binary, $fallback = null)
198 | {
199 | // Get custom path if any was set
200 | $custom = 'paths.'.$binary;
201 | if ($location = $this->server->getValue($custom)) {
202 | return $location;
203 | }
204 |
205 | // Else ask the server where the binary is
206 | $location = $this->run('which '.$binary, true);
207 | if ($location and $this->fileExists($location)) {
208 | return $location;
209 | }
210 |
211 | // Else use the fallback path
212 | if ($fallback) {
213 | $location = $this->run('which '.$fallback, true);
214 | if ($location and $this->fileExists($location)) {
215 | return $location;
216 | }
217 | }
218 |
219 | // Else prompt the User for the actual path
220 | $location = $this->command->ask($binary. ' could not be found, please enter the path to it');
221 | if ($location) {
222 | $this->server->setValue($custom, $location);
223 | return $location;
224 | }
225 |
226 | return false;
227 | }
228 |
229 | /**
230 | * Check the status of the last run command, return an error if any
231 | *
232 | * @param string $error The message to display on error
233 | * @param string $output The command's output
234 | * @param string $success The message to display on success
235 | *
236 | * @return boolean|string
237 | */
238 | public function checkStatus($error, $output = null, $success = null)
239 | {
240 | // If all went well
241 | if ($this->remote->status() == 0) {
242 | if ($success) {
243 | $this->command->comment($success);
244 | }
245 |
246 | return $output;
247 | }
248 |
249 | // Else
250 | $this->command->error($error);
251 | print $output.PHP_EOL;
252 |
253 | return false;
254 | }
255 |
256 | ////////////////////////////////////////////////////////////////////
257 | /////////////////////////////// FOLDERS ////////////////////////////
258 | ////////////////////////////////////////////////////////////////////
259 |
260 | /**
261 | * Symlinks two folders
262 | *
263 | * @param string $folder The folder in shared/
264 | * @param string $symlink The folder that will symlink to it
265 | *
266 | * @return string
267 | */
268 | public function symlink($folder, $symlink)
269 | {
270 | if (!$this->fileExists($folder)) {
271 | if (!$this->fileExists($symlink)) {
272 | return false;
273 | }
274 |
275 | $this->move($symlink, $folder);
276 | }
277 |
278 | // Remove existing symlink
279 | $this->removeFolder($symlink);
280 |
281 | return $this->run(sprintf('ln -s %s %s', $folder, $symlink));
282 | }
283 |
284 | /**
285 | * Move a file
286 | *
287 | * @param string $origin
288 | * @param string $destination
289 | *
290 | * @return string
291 | */
292 | public function move($origin, $destination)
293 | {
294 | $folder = dirname($destination);
295 | if (!$this->fileExists($folder)) {
296 | $this->createFolder($folder, true);
297 | }
298 |
299 | return $this->run(sprintf('mv %s %s', $origin, $destination));
300 | }
301 |
302 | /**
303 | * Get the contents of a directory
304 | *
305 | * @param string $directory
306 | *
307 | * @return array
308 | */
309 | public function listContents($directory)
310 | {
311 | return $this->run('ls '.$directory, false, true);
312 | }
313 |
314 | /**
315 | * Check if a file exists
316 | *
317 | * @param string $file Path to the file
318 | *
319 | * @return boolean
320 | */
321 | public function fileExists($file)
322 | {
323 | $exists = $this->runRaw('if [ -e ' .$file. ' ]; then echo "true"; fi');
324 |
325 | return trim($exists) == 'true';
326 | }
327 |
328 | /**
329 | * Create a folder in the application's folder
330 | *
331 | * @param string $folder The folder to create
332 | * @param boolean $recursive
333 | *
334 | * @return string The task
335 | */
336 | public function createFolder($folder = null, $recursive = false)
337 | {
338 | $recursive = $recursive ? '-p ' : null;
339 |
340 | return $this->run('mkdir '.$recursive.$this->rocketeer->getFolder($folder));
341 | }
342 |
343 | /**
344 | * Remove a folder in the application's folder
345 | *
346 | * @param string $folder The folder to remove
347 | *
348 | * @return string The task
349 | */
350 | public function removeFolder($folder = null)
351 | {
352 | return $this->run('rm -rf '.$this->rocketeer->getFolder($folder));
353 | }
354 |
355 | ////////////////////////////////////////////////////////////////////
356 | /////////////////////////////// HELPERS ////////////////////////////
357 | ////////////////////////////////////////////////////////////////////
358 |
359 | /**
360 | * Get an option from the Command
361 | *
362 | * @param string $option
363 | *
364 | * @return string
365 | */
366 | protected function getOption($option)
367 | {
368 | return $this->command ? $this->command->option($option) : null;
369 | }
370 |
371 | /**
372 | * Process an array of commands
373 | *
374 | * @param string|array $commands
375 | *
376 | * @return array
377 | */
378 | protected function processCommands($commands)
379 | {
380 | $stage = $this->rocketeer->getStage();
381 | $separator = $this->server->getSeparator();
382 |
383 | // Cast commands to array
384 | if (!is_array($commands)) {
385 | $commands = array($commands);
386 | }
387 |
388 | // Process commands
389 | foreach ($commands as &$command) {
390 |
391 | // Replace directory separators
392 | if (DS !== $separator) {
393 | $command = str_replace(DS, $separator, $command);
394 | }
395 |
396 | // Add stage flag
397 | if (Str::startsWith($command, 'php artisan') and $stage) {
398 | $command .= ' --env='.$stage;
399 | }
400 |
401 | }
402 |
403 | return $commands;
404 | }
405 | }
406 |
--------------------------------------------------------------------------------
/src/Rocketeer/TasksQueue.php:
--------------------------------------------------------------------------------
1 | app = $app;
58 | $this->tasks = $app['config']->get('rocketeer::tasks');
59 | $this->command = $command;
60 | }
61 |
62 | ////////////////////////////////////////////////////////////////////
63 | ////////////////////////// PUBLIC INTERFACE ////////////////////////
64 | ////////////////////////////////////////////////////////////////////
65 |
66 | /**
67 | * Register a custom Task with Rocketeer
68 | *
69 | * @param Task|string $task
70 | *
71 | * @return Container
72 | */
73 | public function add($task)
74 | {
75 | // Build Task if necessary
76 | if (is_string($task)) {
77 | $task = $this->buildTask($task);
78 | }
79 |
80 | return $this->app['artisan']->add(new BaseTaskCommand($task));
81 | }
82 |
83 | /**
84 | * Execute a Task before another one
85 | *
86 | * @param string $task
87 | * @param string|Closure|Task $surroundingTask
88 | *
89 | * @return void
90 | */
91 | public function before($task, $surroundingTask)
92 | {
93 | $this->addSurroundingTask($task, $surroundingTask, 'before');
94 | }
95 |
96 | /**
97 | * Execute a Task after another one
98 | *
99 | * @param string $task
100 | * @param string|Closure|Task $surroundingTask
101 | *
102 | * @return void
103 | */
104 | public function after($task, $surroundingTask)
105 | {
106 | $this->addSurroundingTask($task, $surroundingTask, 'after');
107 | }
108 |
109 | /**
110 | * Get the tasks to execute before a Task
111 | *
112 | * @param Task $task
113 | *
114 | * @return array
115 | */
116 | public function getBefore(Task $task)
117 | {
118 | return $this->getSurroundingTasks($task, 'before');
119 | }
120 |
121 | /**
122 | * Get the tasks to execute after a Task
123 | *
124 | * @param Task $task
125 | *
126 | * @return array
127 | */
128 | public function getAfter(Task $task)
129 | {
130 | return $this->getSurroundingTasks($task, 'after');
131 | }
132 |
133 | /**
134 | * Execute Tasks on the default connection
135 | *
136 | * @param string|array|Closure $task
137 | *
138 | * @return array
139 | */
140 | public function execute($queue)
141 | {
142 | $queue = (array) $queue;
143 | $queue = $this->buildQueue($queue);
144 |
145 | return $this->run($queue);
146 | }
147 |
148 | /**
149 | * Execute Tasks on various connections
150 | *
151 | * @param string|array $connections
152 | * @param string|array|Closure $queue
153 | *
154 | * @return array
155 | */
156 | public function on($connections, $queue)
157 | {
158 | $this->app['rocketeer.rocketeer']->setConnections($connections);
159 |
160 | $queue = (array) $queue;
161 | $queue = $this->buildQueue($queue);
162 |
163 | return $this->run($queue);
164 | }
165 |
166 | ////////////////////////////////////////////////////////////////////
167 | //////////////////////////////// QUEUE /////////////////////////////
168 | ////////////////////////////////////////////////////////////////////
169 |
170 | /**
171 | * Run the queue
172 | *
173 | * Here we will actually process the queue to take into account the
174 | * various ways to hook into the queue : Tasks, Closures and Commands
175 | *
176 | * @param array $tasks An array of tasks
177 | * @param Command $command The command executing the tasks
178 | *
179 | * @return array An array of output
180 | */
181 | public function run(array $tasks, $command = null)
182 | {
183 | $this->command = $command;
184 | $queue = $this->buildQueue($tasks);
185 |
186 | // Get the connections to execute the tasks on
187 | $connections = (array) $this->app['rocketeer.rocketeer']->getConnections();
188 | foreach ($connections as $connection) {
189 | $this->app['rocketeer.rocketeer']->setConnection($connection);
190 |
191 | // Check if we provided a stage
192 | $stage = $this->getStage();
193 | $stages = $this->app['rocketeer.rocketeer']->getStages();
194 | if ($stage and in_array($stage, $stages)) {
195 | $stages = array($stage);
196 | }
197 |
198 | // Run the Tasks on each stage
199 | if (!empty($stages)) {
200 | foreach ($stages as $stage) {
201 | $state = $this->runQueue($queue, $stage);
202 | }
203 | } else {
204 | $state = $this->runQueue($queue);
205 | }
206 | }
207 |
208 | return $this->output;
209 | }
210 |
211 | /**
212 | * Run the queue, taking into account the stage
213 | *
214 | * @param array $tasks
215 | * @param string $stage
216 | *
217 | * @return boolean
218 | */
219 | protected function runQueue($tasks, $stage = null)
220 | {
221 | foreach ($tasks as $task) {
222 | $currentStage = $task->usesStages() ? $stage : null;
223 | $this->app['rocketeer.rocketeer']->setStage($currentStage);
224 |
225 | $state = $task->execute();
226 | $this->output[] = $state;
227 | if ($state === false) {
228 | return false;
229 | }
230 | }
231 |
232 | return true;
233 | }
234 |
235 | /**
236 | * Build a queue from a list of tasks
237 | *
238 | * Here we will take the various Task names or actual Task instances
239 | * provided by the user, get the Tasks to execute before and after
240 | * each one, and flatten the whole thing into an actual queue
241 | *
242 | * @param array $tasks
243 | *
244 | * @return array
245 | */
246 | public function buildQueue(array $tasks)
247 | {
248 | $queue = array();
249 | foreach ($tasks as $task) {
250 |
251 | // If we provided a Closure or a string command, add straight to queue
252 | if ($task instanceof Closure or (is_string($task) and !class_exists($task))) {
253 | $queue[] = $task;
254 | continue;
255 | }
256 |
257 | // Else build class and add to queue
258 | if (!($task instanceof Task)) {
259 | $task = $this->buildTask($task);
260 | }
261 |
262 | $queue = array_merge($queue, $this->getBefore($task), array($task), $this->getAfter($task));
263 | }
264 |
265 | // Build the tasks provided as Closures/strings
266 | foreach ($queue as &$task) {
267 | if (!($task instanceof Task)) {
268 | $task = $this->buildTaskFromClosure($task);
269 | }
270 | }
271 |
272 | return $queue;
273 | }
274 |
275 | /**
276 | * Build a Task from a Closure or a string command
277 | *
278 | * @param Closure|string $task
279 | *
280 | * @return Task
281 | */
282 | public function buildTaskFromClosure($task)
283 | {
284 | // If the User provided a string to execute
285 | if (is_string($task) and !class_exists($task)) {
286 | $stringTask = $task;
287 | $closure = function ($task) use ($stringTask) {
288 | return $task->runForCurrentRelease($stringTask);
289 | };
290 |
291 | // If the User provided a Closure
292 | } elseif ($task instanceof Closure) {
293 | $closure = $task;
294 | }
295 |
296 | // Build the ClosureTask
297 | if (isset($closure)) {
298 | $task = $this->buildTask('Rocketeer\Tasks\Closure');
299 | $task->setClosure($closure);
300 | }
301 |
302 | if (!($task instanceof Task)) {
303 | $task = $this->buildTask($task);
304 | }
305 |
306 | return $task;
307 | }
308 |
309 | /**
310 | * Build a Task from its name
311 | *
312 | * @param string $task
313 | *
314 | * @return Task
315 | */
316 | public function buildTask($task)
317 | {
318 | // Shortcut for calling Rocketeer Tasks
319 | if (class_exists('Rocketeer\Tasks\\'.$task)) {
320 | $task = 'Rocketeer\Tasks\\'.$task;
321 | }
322 |
323 | return new $task(
324 | $this->app,
325 | $this->command
326 | );
327 | }
328 |
329 | ////////////////////////////////////////////////////////////////////
330 | ///////////////////////////// SURROUNDINGS /////////////////////////
331 | ////////////////////////////////////////////////////////////////////
332 |
333 | /**
334 | * Add a Task to surround another Task
335 | *
336 | * @param string $task
337 | * @param mixed $surroundingTask
338 | * @param string $position before|after
339 | */
340 | protected function addSurroundingTask($task, $surroundingTask, $position)
341 | {
342 | // Recursive call
343 | if (is_array($task)) {
344 | foreach ($task as $t) {
345 | $this->addSurroundingTask($t, $surroundingTask, $position);
346 | }
347 |
348 | return;
349 | }
350 |
351 | // Create array if it doesn't exist
352 | if (!array_key_exists($task, $this->tasks[$position])) {
353 | $this->tasks[$position][$task] = array();
354 | }
355 |
356 | // Add Task to Tasks
357 | if (is_array($surroundingTask)) {
358 | $this->tasks[$position][$task] = array_merge($this->tasks[$position][$task], $surroundingTask);
359 | } else {
360 | $this->tasks[$position][$task][] = $surroundingTask;
361 | }
362 | }
363 |
364 | /**
365 | * Get the tasks surrounding another Task
366 | *
367 | * @param Task $task
368 | * @param string $position before|after
369 | *
370 | * @return array
371 | */
372 | protected function getSurroundingTasks(Task $task, $position)
373 | {
374 | // First we look for the fully qualified class name
375 | $key = get_class($task);
376 | if (array_key_exists($key, $this->tasks[$position])) {
377 | $tasks = array_get($this->tasks, $position.'.'.$key);
378 |
379 | // Then for the class slug
380 | } else {
381 | $tasks = array_get($this->tasks, $position.'.'.$task->getSlug());
382 | }
383 |
384 | return (array) $tasks;
385 | }
386 |
387 | ////////////////////////////////////////////////////////////////////
388 | //////////////////////////////// STAGES ////////////////////////////
389 | ////////////////////////////////////////////////////////////////////
390 |
391 | /**
392 | * Get the stage to execute Tasks in
393 | * If null, execute on all stages
394 | *
395 | * @return string
396 | */
397 | protected function getStage()
398 | {
399 | $stage = $this->app['rocketeer.rocketeer']->getOption('stages.default');
400 | if ($this->command) {
401 | $stage = $this->command->option('stage') ?: $stage;
402 | }
403 |
404 | // Return all stages if "all"
405 | if ($stage == 'all') {
406 | $stage = null;
407 | }
408 |
409 | return $stage;
410 | }
411 | }
412 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
5 | ],
6 | "hash": "71efae810506470c9d4c7718dcf34cb4",
7 | "packages": [
8 | {
9 | "name": "illuminate/config",
10 | "version": "dev-master",
11 | "target-dir": "Illuminate/Config",
12 | "source": {
13 | "type": "git",
14 | "url": "https://github.com/illuminate/config.git",
15 | "reference": "a48873b5e777ea09777291b02cb498032924844e"
16 | },
17 | "dist": {
18 | "type": "zip",
19 | "url": "https://api.github.com/repos/illuminate/config/zipball/a48873b5e777ea09777291b02cb498032924844e",
20 | "reference": "a48873b5e777ea09777291b02cb498032924844e",
21 | "shasum": ""
22 | },
23 | "require": {
24 | "illuminate/filesystem": "4.1.x",
25 | "illuminate/support": "4.1.x",
26 | "php": ">=5.3.0"
27 | },
28 | "require-dev": {
29 | "phpunit/phpunit": "3.7.*"
30 | },
31 | "type": "library",
32 | "extra": {
33 | "branch-alias": {
34 | "dev-master": "4.1-dev"
35 | }
36 | },
37 | "autoload": {
38 | "psr-0": {
39 | "Illuminate\\Config": ""
40 | }
41 | },
42 | "notification-url": "https://packagist.org/downloads/",
43 | "license": [
44 | "MIT"
45 | ],
46 | "authors": [
47 | {
48 | "name": "Taylor Otwell",
49 | "email": "taylorotwell@gmail.com",
50 | "homepage": "https://github.com/taylorotwell",
51 | "role": "Developer"
52 | }
53 | ],
54 | "time": "2013-10-01 15:20:00"
55 | },
56 | {
57 | "name": "illuminate/console",
58 | "version": "dev-master",
59 | "target-dir": "Illuminate/Console",
60 | "source": {
61 | "type": "git",
62 | "url": "https://github.com/illuminate/console.git",
63 | "reference": "0b4badcd1aecdb623d3e435d1c6d8797e772e233"
64 | },
65 | "dist": {
66 | "type": "zip",
67 | "url": "https://api.github.com/repos/illuminate/console/zipball/0b4badcd1aecdb623d3e435d1c6d8797e772e233",
68 | "reference": "0b4badcd1aecdb623d3e435d1c6d8797e772e233",
69 | "shasum": ""
70 | },
71 | "require": {
72 | "symfony/console": "2.4.*"
73 | },
74 | "require-dev": {
75 | "phpunit/phpunit": "3.7.*"
76 | },
77 | "type": "library",
78 | "extra": {
79 | "branch-alias": {
80 | "dev-master": "4.1-dev"
81 | }
82 | },
83 | "autoload": {
84 | "psr-0": {
85 | "Illuminate\\Console": ""
86 | }
87 | },
88 | "notification-url": "https://packagist.org/downloads/",
89 | "license": [
90 | "MIT"
91 | ],
92 | "authors": [
93 | {
94 | "name": "Taylor Otwell",
95 | "email": "taylorotwell@gmail.com",
96 | "homepage": "https://github.com/taylorotwell",
97 | "role": "Developer"
98 | }
99 | ],
100 | "time": "2013-10-08 21:46:57"
101 | },
102 | {
103 | "name": "illuminate/container",
104 | "version": "dev-master",
105 | "target-dir": "Illuminate/Container",
106 | "source": {
107 | "type": "git",
108 | "url": "https://github.com/illuminate/container.git",
109 | "reference": "1444907a30937bf4b7a910489160ba4576f3afa9"
110 | },
111 | "dist": {
112 | "type": "zip",
113 | "url": "https://api.github.com/repos/illuminate/container/zipball/1444907a30937bf4b7a910489160ba4576f3afa9",
114 | "reference": "1444907a30937bf4b7a910489160ba4576f3afa9",
115 | "shasum": ""
116 | },
117 | "require": {
118 | "php": ">=5.3.0"
119 | },
120 | "require-dev": {
121 | "phpunit/phpunit": "3.7.*"
122 | },
123 | "type": "library",
124 | "extra": {
125 | "branch-alias": {
126 | "dev-master": "4.1-dev"
127 | }
128 | },
129 | "autoload": {
130 | "psr-0": {
131 | "Illuminate\\Container": ""
132 | }
133 | },
134 | "notification-url": "https://packagist.org/downloads/",
135 | "license": [
136 | "MIT"
137 | ],
138 | "authors": [
139 | {
140 | "name": "Taylor Otwell",
141 | "email": "taylorotwell@gmail.com",
142 | "homepage": "https://github.com/taylorotwell",
143 | "role": "Developer"
144 | }
145 | ],
146 | "time": "2013-10-02 21:50:28"
147 | },
148 | {
149 | "name": "illuminate/filesystem",
150 | "version": "dev-master",
151 | "target-dir": "Illuminate/Filesystem",
152 | "source": {
153 | "type": "git",
154 | "url": "https://github.com/illuminate/filesystem.git",
155 | "reference": "98c6e3710853f4e0aee7ea14b09194083815bf1b"
156 | },
157 | "dist": {
158 | "type": "zip",
159 | "url": "https://api.github.com/repos/illuminate/filesystem/zipball/98c6e3710853f4e0aee7ea14b09194083815bf1b",
160 | "reference": "98c6e3710853f4e0aee7ea14b09194083815bf1b",
161 | "shasum": ""
162 | },
163 | "require": {
164 | "illuminate/support": "4.1.*",
165 | "php": ">=5.3.0",
166 | "symfony/finder": "2.4.*"
167 | },
168 | "require-dev": {
169 | "phpunit/phpunit": "3.7.*"
170 | },
171 | "type": "library",
172 | "extra": {
173 | "branch-alias": {
174 | "dev-master": "4.1-dev"
175 | }
176 | },
177 | "autoload": {
178 | "psr-0": {
179 | "Illuminate\\Filesystem": ""
180 | }
181 | },
182 | "notification-url": "https://packagist.org/downloads/",
183 | "license": [
184 | "MIT"
185 | ],
186 | "authors": [
187 | {
188 | "name": "Taylor Otwell",
189 | "email": "taylorotwell@gmail.com",
190 | "homepage": "https://github.com/taylorotwell",
191 | "role": "Developer"
192 | }
193 | ],
194 | "time": "2013-10-08 21:46:57"
195 | },
196 | {
197 | "name": "illuminate/support",
198 | "version": "dev-master",
199 | "target-dir": "Illuminate/Support",
200 | "source": {
201 | "type": "git",
202 | "url": "https://github.com/illuminate/support.git",
203 | "reference": "33df9bdf3d9b38353097d2e6cfdfc4ebfe9900c1"
204 | },
205 | "dist": {
206 | "type": "zip",
207 | "url": "https://api.github.com/repos/illuminate/support/zipball/33df9bdf3d9b38353097d2e6cfdfc4ebfe9900c1",
208 | "reference": "33df9bdf3d9b38353097d2e6cfdfc4ebfe9900c1",
209 | "shasum": ""
210 | },
211 | "require": {
212 | "php": ">=5.3.0"
213 | },
214 | "require-dev": {
215 | "mockery/mockery": "0.7.2",
216 | "patchwork/utf8": "1.1.*",
217 | "phpunit/phpunit": "3.7.*"
218 | },
219 | "type": "library",
220 | "extra": {
221 | "branch-alias": {
222 | "dev-master": "4.1-dev"
223 | }
224 | },
225 | "autoload": {
226 | "psr-0": {
227 | "Illuminate\\Support": ""
228 | },
229 | "files": [
230 | "Illuminate/Support/helpers.php"
231 | ]
232 | },
233 | "notification-url": "https://packagist.org/downloads/",
234 | "license": [
235 | "MIT"
236 | ],
237 | "authors": [
238 | {
239 | "name": "Taylor Otwell",
240 | "email": "taylorotwell@gmail.com",
241 | "homepage": "https://github.com/taylorotwell",
242 | "role": "Developer"
243 | }
244 | ],
245 | "time": "2013-10-03 21:32:36"
246 | },
247 | {
248 | "name": "symfony/console",
249 | "version": "dev-master",
250 | "target-dir": "Symfony/Component/Console",
251 | "source": {
252 | "type": "git",
253 | "url": "https://github.com/symfony/Console.git",
254 | "reference": "608960cd7f7e906e64d6586b9152e308f7ea0ff2"
255 | },
256 | "dist": {
257 | "type": "zip",
258 | "url": "https://api.github.com/repos/symfony/Console/zipball/608960cd7f7e906e64d6586b9152e308f7ea0ff2",
259 | "reference": "608960cd7f7e906e64d6586b9152e308f7ea0ff2",
260 | "shasum": ""
261 | },
262 | "require": {
263 | "php": ">=5.3.3"
264 | },
265 | "require-dev": {
266 | "symfony/event-dispatcher": "~2.1"
267 | },
268 | "suggest": {
269 | "symfony/event-dispatcher": ""
270 | },
271 | "type": "library",
272 | "extra": {
273 | "branch-alias": {
274 | "dev-master": "2.4-dev"
275 | }
276 | },
277 | "autoload": {
278 | "psr-0": {
279 | "Symfony\\Component\\Console\\": ""
280 | }
281 | },
282 | "notification-url": "https://packagist.org/downloads/",
283 | "license": [
284 | "MIT"
285 | ],
286 | "authors": [
287 | {
288 | "name": "Fabien Potencier",
289 | "email": "fabien@symfony.com"
290 | },
291 | {
292 | "name": "Symfony Community",
293 | "homepage": "http://symfony.com/contributors"
294 | }
295 | ],
296 | "description": "Symfony Console Component",
297 | "homepage": "http://symfony.com",
298 | "time": "2013-10-16 16:16:10"
299 | },
300 | {
301 | "name": "symfony/finder",
302 | "version": "dev-master",
303 | "target-dir": "Symfony/Component/Finder",
304 | "source": {
305 | "type": "git",
306 | "url": "https://github.com/symfony/Finder.git",
307 | "reference": "e2ce3164ab58b4d54612e630571f158035ee8603"
308 | },
309 | "dist": {
310 | "type": "zip",
311 | "url": "https://api.github.com/repos/symfony/Finder/zipball/e2ce3164ab58b4d54612e630571f158035ee8603",
312 | "reference": "e2ce3164ab58b4d54612e630571f158035ee8603",
313 | "shasum": ""
314 | },
315 | "require": {
316 | "php": ">=5.3.3"
317 | },
318 | "type": "library",
319 | "extra": {
320 | "branch-alias": {
321 | "dev-master": "2.4-dev"
322 | }
323 | },
324 | "autoload": {
325 | "psr-0": {
326 | "Symfony\\Component\\Finder\\": ""
327 | }
328 | },
329 | "notification-url": "https://packagist.org/downloads/",
330 | "license": [
331 | "MIT"
332 | ],
333 | "authors": [
334 | {
335 | "name": "Fabien Potencier",
336 | "email": "fabien@symfony.com"
337 | },
338 | {
339 | "name": "Symfony Community",
340 | "homepage": "http://symfony.com/contributors"
341 | }
342 | ],
343 | "description": "Symfony Finder Component",
344 | "homepage": "http://symfony.com",
345 | "time": "2013-09-19 09:47:34"
346 | }
347 | ],
348 | "packages-dev": [
349 | {
350 | "name": "mockery/mockery",
351 | "version": "dev-master",
352 | "source": {
353 | "type": "git",
354 | "url": "https://github.com/padraic/mockery.git",
355 | "reference": "09ab879a09def2a658d6e8030f88432cc479f5a8"
356 | },
357 | "dist": {
358 | "type": "zip",
359 | "url": "https://api.github.com/repos/padraic/mockery/zipball/09ab879a09def2a658d6e8030f88432cc479f5a8",
360 | "reference": "09ab879a09def2a658d6e8030f88432cc479f5a8",
361 | "shasum": ""
362 | },
363 | "require": {
364 | "lib-pcre": ">=7.0",
365 | "php": ">=5.3.2"
366 | },
367 | "require-dev": {
368 | "hamcrest/hamcrest": "1.1.0"
369 | },
370 | "type": "library",
371 | "autoload": {
372 | "psr-0": {
373 | "Mockery": "library/"
374 | }
375 | },
376 | "notification-url": "https://packagist.org/downloads/",
377 | "license": [
378 | "BSD-3-Clause"
379 | ],
380 | "authors": [
381 | {
382 | "name": "Pádraic Brady",
383 | "email": "padraic.brady@gmail.com",
384 | "homepage": "http://blog.astrumfutura.com"
385 | }
386 | ],
387 | "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.",
388 | "homepage": "http://github.com/padraic/mockery",
389 | "keywords": [
390 | "BDD",
391 | "TDD",
392 | "library",
393 | "mock",
394 | "mock objects",
395 | "mockery",
396 | "stub",
397 | "test",
398 | "test double",
399 | "testing"
400 | ],
401 | "time": "2013-10-18 15:18:30"
402 | },
403 | {
404 | "name": "nesbot/carbon",
405 | "version": "dev-master",
406 | "source": {
407 | "type": "git",
408 | "url": "https://github.com/briannesbitt/Carbon.git",
409 | "reference": "06f0b8a99a90c5392ceccb09b75b74ff6c08ec07"
410 | },
411 | "dist": {
412 | "type": "zip",
413 | "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/06f0b8a99a90c5392ceccb09b75b74ff6c08ec07",
414 | "reference": "06f0b8a99a90c5392ceccb09b75b74ff6c08ec07",
415 | "shasum": ""
416 | },
417 | "require": {
418 | "php": ">=5.3.0"
419 | },
420 | "type": "library",
421 | "autoload": {
422 | "psr-0": {
423 | "Carbon": "src"
424 | }
425 | },
426 | "notification-url": "https://packagist.org/downloads/",
427 | "license": [
428 | "MIT"
429 | ],
430 | "authors": [
431 | {
432 | "name": "Brian Nesbitt",
433 | "email": "brian@nesbot.com",
434 | "homepage": "http://nesbot.com"
435 | }
436 | ],
437 | "description": "A simple API extension for DateTime.",
438 | "homepage": "https://github.com/briannesbitt/Carbon",
439 | "keywords": [
440 | "date",
441 | "datetime",
442 | "time"
443 | ],
444 | "time": "2013-09-09 02:39:19"
445 | },
446 | {
447 | "name": "patchwork/utf8",
448 | "version": "dev-master",
449 | "source": {
450 | "type": "git",
451 | "url": "https://github.com/nicolas-grekas/Patchwork-UTF8.git",
452 | "reference": "efd5478a233f6940d3296ab27c2a02ba47831968"
453 | },
454 | "dist": {
455 | "type": "zip",
456 | "url": "https://api.github.com/repos/nicolas-grekas/Patchwork-UTF8/zipball/efd5478a233f6940d3296ab27c2a02ba47831968",
457 | "reference": "efd5478a233f6940d3296ab27c2a02ba47831968",
458 | "shasum": ""
459 | },
460 | "require": {
461 | "lib-pcre": "*",
462 | "php": ">=5.3.0"
463 | },
464 | "suggest": {
465 | "ext-mbstring": "Use Mbstring for best performance",
466 | "lib-iconv": "Use iconv for best performance",
467 | "lib-icu": "Use Intl for best performance"
468 | },
469 | "type": "library",
470 | "autoload": {
471 | "psr-0": {
472 | "Patchwork": "class/",
473 | "Normalizer": "class/"
474 | }
475 | },
476 | "notification-url": "https://packagist.org/downloads/",
477 | "license": [
478 | "(Apache-2.0 or GPL-2.0)"
479 | ],
480 | "authors": [
481 | {
482 | "name": "Nicolas Grekas",
483 | "email": "p@tchwork.com",
484 | "role": "Developer"
485 | }
486 | ],
487 | "description": "Extensive, portable and performant handling of UTF-8 and grapheme clusters for PHP",
488 | "homepage": "https://github.com/nicolas-grekas/Patchwork-UTF8",
489 | "keywords": [
490 | "i18n",
491 | "unicode",
492 | "utf-8",
493 | "utf8"
494 | ],
495 | "time": "2013-10-15 08:18:30"
496 | }
497 | ],
498 | "aliases": [
499 |
500 | ],
501 | "minimum-stability": "dev",
502 | "stability-flags": {
503 | "mockery/mockery": 20,
504 | "nesbot/carbon": 20,
505 | "patchwork/utf8": 20
506 | },
507 | "platform": {
508 | "php": ">=5.3.0"
509 | },
510 | "platform-dev": [
511 |
512 | ]
513 | }
514 |
--------------------------------------------------------------------------------