Skip to content

Commit

Permalink
implement transfer() for copying files to remote hosts. several minor…
Browse files Browse the repository at this point in the history
… improvements
  • Loading branch information
pstadler committed Feb 16, 2014
1 parent 779978a commit 3a2ae8f
Show file tree
Hide file tree
Showing 11 changed files with 207 additions and 64 deletions.
54 changes: 47 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ plan.local(function(local) {
local.log('Run build');
local.exec('gulp build');

local.log('Copy files to remote host');
var filesToCopy = '(git ls-files -z;find assets/public -type f -print0)';
local.exec(filesToCopy + '|rsync --files-from - -avz0 --rsh="ssh"'
+ ' ./ [email protected]:/tmp/' + tmpDir);
local.log('Copy files to remote hosts');
var filesToCopy = local.exec('git ls-files', {silent: true});
// rsync `filesToCopy` to all the destination's hosts
local.transfer(filesToCopy, '/tmp/' + tmpDir);
});

// run commands on remote hosts (destinations)
Expand Down Expand Up @@ -328,13 +328,54 @@ transport.sudo('echo Hello world', {user: 'www'});
transport.sudo('echo Hello world', {user: 'www', silent: true, failsafe: true});
```

### transport.transfer(files, remoteDir[, options]) → [results]

Copy a list of files to the current destination's remote host(s) using
`rsync` with the SSH protocol. File transfers are executed in parallel.
After finishing all transfers, an array containing results from
`transport.exec()` is returned. This method is only available on local
flights.

```javascript
var files = ['path/to/file1', 'path/to/file2'];
local.transfer(files, '/tmp/foo');
```

#### Files argument
To make things more comfortable, the `files` argument doesn't have to be
passed as an array. Results from previous commands and zero-terminated
strings are handled as well:

```javascript
// use result from a previous command
var files = local.git('ls-files', {silent: true}); // get list of files under version control
local.transfer(files, '/tmp/foo');

// use zero-terminated result from a previous command
var files = local.exec('(git ls-files -z;find node_modules -type f -print0)', {silent: true});
local.transfer(files, '/tmp/foo');

// use results from multiple commands
var result1 = local.git('ls-files', {silent: true}).stdout.split('\n');
var result2 = local.find('node_modules -type f', {silent: true}).stdout.split('\n');
var files = result1.concat(result2);
files.push('path/to/another/file');
local.transfer(files, '/tmp/foo');
```

`transfer()` will use the current host's username defined with
`briefing()` unless `fly` is called with the `-u|--username` option.
In this case the latter will be used. If debugging is enabled
(either with `briefing()` or with `fly --debug`), `rsync` is executed
in verbose mode (`-v`).

### transport.log(message)

Print a message to stdout. Flightplan takes care that the message
is formatted correctly within the current context.

```javascript
transport.log('Copying files to remote host');
transport.log('Copying files to remote hosts');
```

### transport.silent()
Expand Down Expand Up @@ -395,7 +436,7 @@ Print a debug message to stdout. Flightplan takes care that the message
is formatted correctly within the current context.

```javascript
remote.debug('Copying files to remote host');
remote.debug('Copying files to remote hosts');
```

### transport.abort([message])
Expand All @@ -415,7 +456,6 @@ remote.abort('Severe turbulences over the atlantic ocean!');
## What's planned?

- Add possibility to define a `sudoUser` per host with `briefing()`.
- Add a simple interface for file transport to remote hosts (e.g. `rsync`).
- Tests will be implemented with upcoming releases. A part of this will be driven by bug reports.

[npm-url]: https://npmjs.org/package/flightplan
Expand Down
2 changes: 1 addition & 1 deletion bin/fly.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ if(!fs.existsSync(flightFile)) {
var flightplan = require(flightFile)
, options = {
username: program.username || null,
debug: program.debug || false
debug: program.debug || null
};

var destination = program.args[0];
Expand Down
24 changes: 10 additions & 14 deletions lib/briefing.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@ var util = require('util');

function Briefing(flightplan, config) {
this.flightplan = flightplan;
this.logger = flightplan.logger;

this.config = util._extend({
debug: false,
destinations: {}
}, config);

this.logger.enableDebug(!!this.config.debug);
config = config || {};
this.debug = config.debug || false;
this.destinations = config.destinations || {};
this.flightplan.logger.enableDebug(this.debug);
}

Briefing.prototype = {
Expand All @@ -24,19 +20,19 @@ Briefing.prototype = {
}
}.bind(this));
}
if(options.debug) {
this.config.debug = options.debug;
this.flightplan.logger.enableDebug(this.config.debug);
if(options.debug !== undefined && options.debug !== null) {
this.debug = options.debug;
this.flightplan.logger.enableDebug(this.debug);
}
},

getDestinations: function() {
return Object.keys(this.config.destinations);
return Object.keys(this.destinations);
},

getHostsForDestination: function(destination) {
try {
var hosts = this.config.destinations[destination];
var hosts = this.destinations[destination];
return (hosts instanceof Array) ? hosts : [hosts];
} catch(e) {
return null;
Expand All @@ -45,7 +41,7 @@ Briefing.prototype = {

hasDestination: function(destination) {
try {
return !!this.config.destinations[destination];
return !!this.destinations[destination];
} catch(e) {
return false;
}
Expand Down
42 changes: 23 additions & 19 deletions lib/flight.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ function Flight(flightplan, transportClass, fn) {
this.fn = fn;
this.transportClass = transportClass;
this.logger = flightplan.logger;
this.hosts = null;
this.status = {
aborted: false,
crashRecordings: null,
Expand All @@ -15,14 +16,34 @@ function Flight(flightplan, transportClass, fn) {

Flight.prototype = {

liftoff: function(config) {
start: function(destination) {
this.hosts = this.flightplan.briefing().getHostsForDestination(destination);
this.__start();
return this.getStatus();
},

abort: function(msg) {
this.flightplan.abort();
this.status.aborted = true;
this.status.crashRecordings = msg || null;
},

isAborted: function() {
return this.status.aborted;
},

getStatus: function() {
return this.status;
},

__start: function() {
var future = new Future();

var task = function() {
Fiber(function() {
var t = process.hrtime();

var transport = new this.transportClass(this, config);
var transport = new this.transportClass(this);
this.fn(transport);
transport.close();

Expand All @@ -40,24 +61,7 @@ Flight.prototype = {
}.bind(this);

Future.wait(task());

return this.status;
},

abort: function(msg) {
this.flightplan.abort();
this.status.aborted = true;
this.status.crashRecordings = msg || null;
},

isAborted: function() {
return this.status.aborted;
},

getStatus: function() {
return this.status;
}

};

module.exports = Flight;
6 changes: 3 additions & 3 deletions lib/flightplan.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@ function Flightplan() {
}.bind(this));

process.on('uncaughtException', function(err) {
this.logger.error(err);
this.logger.error('Flightplan aborted'.error);
this.logger.error(err.stack);
this.disasterCallback();
this.debriefingCallback();
this.logger.error('Flightplan aborted'.error);
process.exit(1);
}.bind(this));

Expand Down Expand Up @@ -279,7 +279,7 @@ Flightplan.prototype = {
this.logger.info('Flight'.info, this.logger.format('%s/%s', i+1, len).magenta, 'launched...'.info);
this.logger.space();

flight.liftoff(this.briefing().getHostsForDestination(destination));
flight.start(destination);

var status = flight.getStatus();
if(flight.isAborted()) {
Expand Down
14 changes: 10 additions & 4 deletions lib/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ var messageTypes = {

colors.setTheme(messageTypes);

var __debug = false; // persistent

function Logger(debug) {
this._debug = debug || false;
__debug = (debug !== null && debug !== undefined) ? debug : __debug;
this.symbol = '✈';
this.prefix = '';

Expand Down Expand Up @@ -75,11 +77,15 @@ Object.keys(messageTypes).forEach(function(type) {
Logger.prototype = util._extend(Logger.prototype, {

enableDebug: function(flag) {
this._debug = !!flag;
__debug = !!flag;
},

debugEnabled: function() {
return __debug;
},

clone: function() {
return new Logger(this._debug);
return new Logger();
},

cloneWithPrefix: function(prefix) {
Expand All @@ -98,7 +104,7 @@ Logger.prototype = util._extend(Logger.prototype, {
},

debug: function() {
if(this._debug) {
if(__debug) {
var msg = this._parseArgs(arguments);
this._log(this.symbol.debug, this.prefix, msg);
}
Expand Down
10 changes: 5 additions & 5 deletions lib/remote.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ function RemoteFlight(flightplan, fn) {

util.inherits(RemoteFlight, Flight);

RemoteFlight.prototype.liftoff = function(hosts) {
var task = function(config) {
RemoteFlight.prototype.__start = function() {
var task = function(host) {
var future = new Future();
var flight = new RemoteFlight(this.flightplan, this.fn);
Fiber(function() {
var t = process.hrtime();

var transport = new flight.transportClass(flight, config);
var transport = new flight.transportClass(flight, host);
flight.fn(transport);
transport.close();

Expand All @@ -42,8 +42,8 @@ RemoteFlight.prototype.liftoff = function(hosts) {
}.bind(this);

var tasks = [];
for(var i=0, len=hosts.length; i < len; i++) {
tasks.push(task(hosts[i]));
for(var i=0, len=this.hosts.length; i < len; i++) {
tasks.push(task(this.hosts[i]));
}
Future.wait(tasks);

Expand Down
51 changes: 49 additions & 2 deletions lib/transport/shell.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
var util = require('util')
, exec = require("child_process").exec
, Fiber = require('fibers')
, Future = require('fibers/future')
, Transport = require('./transport');

function ShellTransport(flight) {
ShellTransport.super_.call(this, flight);
this.logger = this.logger.cloneWithPrefix('local');
this.host = 'local';
this.logger = this.logger.cloneWithPrefix(this.host);
}

util.inherits(ShellTransport, Transport);
Expand Down Expand Up @@ -45,7 +47,7 @@ ShellTransport.prototype.__exec = function(cmd, args, options) {
this.logger.warn(this.logger.format('failed safely').warn, 'with exit code:', ret.code);
} else {
this.logger.error(this.logger.format('failed').error, 'with exit code:', ret.code);
this.flight.abort(this.logger.format('`%s` failed on %s', cmd.white, 'local'));
this.flight.abort(this.logger.format('`%s` failed on localhost', cmd.white));
}
fiber.run(ret);
}.bind(this));
Expand All @@ -56,4 +58,49 @@ ShellTransport.prototype.__exec = function(cmd, args, options) {
return Fiber.yield();
};

ShellTransport.prototype.__transfer = function(files, remoteDir, options) {
if(!remoteDir) {
throw new Error('transfer: missing remote path');
}

if(files instanceof Array) {
files = files.join('\n');
} else if(files instanceof Object) {
if(!files.hasOwnProperty('stdout')) {
throw new Error('transfer: invalid object passed');
}
files = files.stdout;
}

files = (files || '').trim().replace(/[\r|\n|\0]/mg, '\\n');
if(!files) {
throw new Error('transfer: empty file list passed');
}

var rsyncFlags = 'az';
if(this.logger.debugEnabled()) {
rsyncFlags += 'v';
}
var _results = [];
var task = function(config) {
var future = new Future();

Fiber(function() {
var cmd = util.format('(echo "%s") | rsync --files-from - -%s --rsh="ssh" ./ %s@%s:%s'
, files, rsyncFlags, config.username, config.host, remoteDir);
_results.push(this.exec(cmd, options));
return future.return();
}.bind(this)).run();

return future;
}.bind(this);

var tasks = [];
for(var i=0, len=this.flight.hosts.length; i < len; i++) {
tasks.push(task(this.flight.hosts[i]));
}
Future.wait(tasks);
return _results;
};

module.exports = ShellTransport;
3 changes: 2 additions & 1 deletion lib/transport/ssh.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ var util = require('util')
function SSHTransport(flight, config) {
SSHTransport.super_.call(this, flight);
this.config = config;
this.host = this.config.host;
this.logger = this.logger.cloneWithPrefix(this.host);
this.connection = new Connection();
this.logger = this.logger.cloneWithPrefix(this.config.host);

var _fiber = Fiber.current;

Expand Down
Loading

0 comments on commit 3a2ae8f

Please sign in to comment.