Skip to content

Commit

Permalink
Implement SFTP connections
Browse files Browse the repository at this point in the history
Signed-off-by: Fadion Dashi <[email protected]>
  • Loading branch information
fadion committed Nov 1, 2014
1 parent 617c1bb commit 9ae891a
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 112 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/vendor
composer.phar
composer.lock
.DS_Store
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
> Maneuver was recently updated to use SFTP, in addition to FTP. This change doesn't break any API, except a few simple configuration changes. You'll need to add a `scheme` option to your servers with either `ftp` or `ssh2` as a value. In addition, the `username` field will have to be renamed to `user` and `password` to `pass`.
> This release is tagged with a version. Please update your composer as instructed in the [Installation](#installation) section.
# Maneuver

A Laravel package that makes deployment as easy as it has never been. It uses Git to read file changes and deploys to your server(s) via FTP. **Why Git?** Because anyone should already version their files and if they do, it's almost certain they're using Git. **Why FTP?** Because it is the easiest transport protocol to implement, but others (SFTP, ssh) will be rolling soon.
A Laravel package that makes deployment as easy as it has never been. It uses Git to read file changes and deploys to your server(s) via FTP or SFTP. **Why Git?** Because anyone should already version their files and if they do, it's almost certain they're using Git. **Why FTP?** Because it is the easiest transport protocol to implement and use.

It is dead-simple to use! Add your servers in the config and just run one command. That's it! Anything else will be handled automatically. Oh, and it even supports Git SubModules and Sub-SubModules. Isn't that neat?

Expand All @@ -12,7 +16,7 @@ Maneuver is very tightly coupled to [PHPloy](https://Github.com/banago/PHPloy),

There are plenty of fantastic tools for deployment: Capistrano, Rocketeer or Envoy (Laravel's own ssh task runner), to name a few. They get the job done, probably in a more elegant way. So why use this approach?

While any sane developer uses Git for version control, not anyone of them knows or bothers to understand how you can exploit Git to deploy. The point is that Git's domain isn't deployment, so setting it up to push to a remote repo and trigger hooks to transfer the work tree can be a tedious task. Worth mentioning is that there are projects hosted on shared hosts, where setting up a remote Git repo may not be possible.
While any sane developer uses Git for version control, not anyone of them knows or bothers to understand how you can exploit Git to deploy. The point is that Git's domain isn't deployment, so setting it up to push to a remote repo and trigger hooks to transfer the work tree can be a tedious task. Worth mentioning is that there are still projects hosted on shared hosts, where setting up a remote Git repo may not be possible.

Maneuver solves these problems with a very simple approach. It takes the best of version control and combines it with file transfer, without needing anything special on the server. Developers have used FTP for decades to selectively upload files, in a very time consuming and error-prone process. Now they can use an automatic tool that needs only a few minutes to get started.

Expand All @@ -23,7 +27,7 @@ Maneuver solves these problems with a very simple approach. It takes the best of
```json
{
"require": {
"fadion/maneuver": "dev-master"
"fadion/maneuver": "~1.0"
}
}
```
Expand All @@ -38,6 +42,8 @@ The first step is to add servers in the configuration file. If you followed step

Add one or more servers in the `connections` array, providing a unique, recognizable name for each. Credentials should obviously be entered too. Optionally, specify a default server for deployment, by entering the server's name in the `default` option. Changes will be deployed to that server if not overriden. In case you leave the `default` option empty, deployment will be run to all the servers.

Don't forget to set the `scheme` for your servers to either `ftp` or `ssh2` for SFTP.

## Usage

You'll use Maneuver from the command line, in the same way you run other `artisan` commands. Even if the terminal feels like an obscure environment that you can't wrap your head around, I assure it'll be a piece of cake.
Expand Down
8 changes: 5 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "fadion/maneuver",
"description": "Easily deploy Laravel projects via FTP, using git for versioning.",
"description": "Easily deploy Laravel projects via FTP or SFTP, using Git for versioning.",
"license": "MIT",
"keywords": ["laravel", "deployment", "git", "ftp"],
"keywords": ["laravel", "deployment", "git", "ftp", "ssh"],
"authors": [
{
"name": "Fadion Dashi",
Expand All @@ -19,7 +19,9 @@
"php": ">=5.3.0",
"illuminate/support": "4.2.*",
"illuminate/console": "4.2.*",
"illuminate/config": "4.2.*"
"illuminate/config": "4.2.*",
"banago/bridge": "dev-master",
"jakeasmith/http_build_url": "dev-master"
},
"autoload": {
"psr-0": {
Expand Down
133 changes: 38 additions & 95 deletions src/Fadion/Maneuver/Deploy.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<?php namespace Fadion\Maneuver;

use Banago\Bridge\Bridge;
use Exception;

/**
* Class Deploy
*
* Handles FTP connection and transfers.
* Handles remote server operations.
*/
class Deploy {

Expand All @@ -15,14 +16,14 @@ class Deploy {
protected $git;

/**
* @var string Server credentials
* @var \Banago\Bridge\Bridge
*/
protected $server;
protected $bridge;

/**
* @var resource FTP resource
* @var string Server credentials
*/
protected $connection;
protected $server;

/**
* @var string Revision filename
Expand Down Expand Up @@ -58,53 +59,17 @@ class Deploy {
* Constructor
*
* @param \Fadion\Maneuver\Git $git
* @param \Banago\Bridge\Bridge $bridge
* @param array $server
*/
public function __construct(Git $git, $server)
public function __construct(Git $git, Bridge $bridge, $server)
{
$this->git = $git;
$this->bridge = $bridge;
$this->ignoredFiles = $git->getIgnored();
$this->server = $server;
}

/**
* Connects to FTP server
*
* @throws Exception if it can't connect to FTP server
* @throws Exception if it can't login to FTP server
* @throws Exception if it can't change FTP directory
*/
public function connect()
{
$server = $this->server;

// Make sure the path has a trailing slash.
$server['path'] = rtrim($server['path'], '/') . '/';

// Make the connection.
$connection = @ftp_connect($server['host'], $server['port']);

if (!$connection) {
throw new Exception("Couldn't connect to '{$server['host']}'.");
}

// Try logging in.
if (!@ftp_login($connection, $server['username'], $server['password'])) {
throw new Exception("Couldn't login to '{$server['host']}' with user '{$server['username']}'");
}

// Set passive mode from connection config.
ftp_pasv($connection, (bool)$server['passive']);

// Try changing the directory to the one set
// in the connection config.
if (!ftp_chdir($connection, $server['path'])) {
throw new Exception("Couldn't change the FTP directory to '{$server['path']}'.");
}

$this->connection = $connection;
}

/**
* Compares local revision to the remote one and
* builds files to upload and delete
Expand All @@ -115,7 +80,6 @@ public function connect()
public function compare()
{
$remoteRevision = null;
$tempFile = tmpfile();
$filesToUpload = array();
$filesToDelete = array();

Expand All @@ -124,17 +88,11 @@ public function compare()
$this->revisionFile = $this->isSubmodule . '/' . $this->revisionFile;
}

// When a revision file exists, get the version,
// so a diff can be made.
if (@ftp_fget($this->connection, $tempFile, $this->revisionFile, FTP_ASCII)) {
fseek($tempFile, 0);
$remoteRevision = trim(fread($tempFile, 1024));
fclose($tempFile);
if ($this->bridge->exists($this->revisionFile)) {
$remoteRevision = $this->bridge->get($this->revisionFile);

$message = "\r\n» Taking it from '" . substr($remoteRevision, 0, 7) . "'";
}
// Otherwise it will start a fresh upload.
else {
} else {
$message = "\r\n» Fresh deployment - grab a coffee";
}

Expand Down Expand Up @@ -240,32 +198,29 @@ public function upload($file)
$pathThatExists = null;
$output = array();

// Iterate through directory pieces.
for ($i = 0, $count = count($dir); $i < $count; $i++) {
$path .= $dir[$i].'/';
// Skip basedir or parent.
if ($dir[0] != '.' and $dir[0] != '..') {
// Iterate through directory pieces.
for ($i = 0, $count = count($dir); $i < $count; $i++) {
$path .= $dir[$i].'/';

if (!isset($pathThatExists[$path])) {
$origin = ftp_pwd($this->connection);
if (!isset($pathThatExists[$path])) {
$origin = $this->bridge->pwd();

// When it fails changing the directory, it means
// that it doesn't exist.
if (!@ftp_chdir($this->connection, $path)) {
// Attempt to create the directory.
if (!@ftp_mkdir($this->connection, $path)) {
$output[] = "Failed to create '$path'.'";
return $output;
// The directory doesn't exist.
if (! $this->bridge->exists($path)) {
// Attempt to create the directory.
$this->bridge->mkdir($path);
$output[] = "Created directoy '$path'.'";
}
// The directory exists.
else {
$output[] = "Created directory '$path'.";
$pathThatExists[$path] = true;
$this->bridge->cd($path);
}
}
// The directory exists.
else {

$pathThatExists[$path] = true;
$this->bridge->cd($origin);
}

ftp_chdir($this->connection, $origin);
}
}

Expand All @@ -282,7 +237,8 @@ public function upload($file)
return $output;
}

$uploaded = @ftp_put($this->connection, $file, $file, FTP_BINARY);
$data = file_get_contents($file);
$uploaded = $this->bridge->put($data, $file);

if (!$uploaded) {
$attempts++;
Expand All @@ -302,7 +258,7 @@ public function upload($file)
*/
public function delete($file)
{
@ftp_delete($this->connection, $file);
$this->bridge->rm($file);

return "× \033[0;37m{$file}\033[0m \033[0;31mremoved\033[0m";
}
Expand All @@ -316,30 +272,17 @@ public function delete($file)
public function writeRevision()
{
if ($this->syncCommit) {
$locRev = $this->syncCommit;
$localRevision = $this->syncCommit;
} else {
$locRev = $this->git->localRevision()[0];
$localRevision = $this->git->localRevision()[0];
}

$temp = tempnam(sys_get_temp_dir(), 'gitRevision');
file_put_contents($temp, $locRev);

if (ftp_put($this->connection, $this->revisionFile, $temp, FTP_BINARY)) {
unlink($temp);
} else {
unlink($temp);
throw new Exception('Could not update the revision file on server.');
try {
$this->bridge->put($localRevision, $this->revisionFile);
}
catch (Exception $e) {
throw new Exception("Could not update the revision file on server: {$e->getMessage()}");
}
}

/**
* Closes FTP connection
*
* @return void
*/
public function close()
{
ftp_close($this->connection);
}

}
20 changes: 13 additions & 7 deletions src/Fadion/Maneuver/Maneuver.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<?php namespace Fadion\Maneuver;

use Banago\Bridge\Bridge;
use Exception;

/**
* Class Maneuver
*/
Expand Down Expand Up @@ -101,10 +104,17 @@ public function start()
// There may be one or more servers, but in each
// case it's build as an array.
foreach ($servers as $name => $credentials) {
$deploy = new Deploy($git, $credentials);
try {
// Connect to the server using the selected
// scheme and options.
$bridge = new Bridge(http_build_url('', $credentials));
}
catch (Exception $e) {
print "Oh snap: {$e->getMessage()}";
continue;
}

// Try to connect to the server.
$deploy->connect();
$deploy = new Deploy($git, $bridge, $credentials);

print "\r\n+ --------------- § --------------- +";
print "\n» Server: $name";
Expand All @@ -114,7 +124,6 @@ public function start()
if ($this->mode == self::MODE_SYNC) {
$deploy->setSyncCommit($this->optSyncCommit);
$deploy->writeRevision();
$deploy->close();

print "\n √ Synced local revision file to remote";
print "\n+ --------------- √ --------------- +\r\n";
Expand Down Expand Up @@ -164,9 +173,6 @@ public function start()
if ($this->mode == self::MODE_ROLLBACK) {
$git->revertToMaster();
}

// Close connection.
$deploy->close();
}
}

Expand Down
10 changes: 6 additions & 4 deletions src/config/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,20 @@
'connections' => array(

'development' => array(
'scheme' => 'ftp',
'host' => 'yourdevserver.com',
'username' => 'user',
'password' => 'myawesomepass',
'user' => 'user',
'pass' => 'myawesomepass',
'path' => '/path/to/server/',
'port' => 21,
'passive' => true
),

'production' => array(
'scheme' => 'ftp',
'host' => 'yourserver.com',
'username' => 'user',
'password' => 'myawesomepass',
'user' => 'user',
'pass' => 'myawesomepass',
'path' => '/path/to/server/',
'port' => 21,
'passive' => true
Expand Down

0 comments on commit 9ae891a

Please sign in to comment.