Clean up galaxy command setup and ssh tunnel cleanup.

Uses cleanup.js to register an exit handler that cleans up the ssh tunnel,
instead of the try/finally. The galaxyCommand wrapper takes care of finding a
galaxy, opening a tunnel, and cleaning up the tunnel when the command
finishes. Commands that want to keep the tunnel open can just use
prepareForGalaxy and not galaxyCommand.

galaxyCommand required refactoring argument parsing a bit; now each command has
an optional argumentParser step that runs before the actual command, which
allows us to wrap the command function knowing that the arguments have already
been parsed (e.g. site is always in argv._[1]).

Also reverts c185b2be because that fix is no longer necessary.
This commit is contained in:
Emily Stark
2013-07-15 21:58:56 -07:00
parent eb1e15e457
commit 5c3b769e44
3 changed files with 132 additions and 94 deletions

View File

@@ -274,8 +274,6 @@ exports.logs = function (options) {
// Close connections to Galaxy and log-reader // Close connections to Galaxy and log-reader
// (otherwise Node will continue running). // (otherwise Node will continue running).
logReader.close(); logReader.close();
} else {
fiberHelpers.yieldForever();
} }
}; };

View File

@@ -37,11 +37,3 @@ exports.parallelEach = function (collection, callback, context) {
// Throw if any threw. // Throw if any threw.
_.each(futures, function (f) { f.get(); }); _.each(futures, function (f) { f.get(); });
}; };
// https://github.com/laverdet/node-fibers/issues/131
var fibersYieldForever = [];
exports.yieldForever = function () {
fibersYieldForever.push(Fiber.current);
Fiber.yield();
};

View File

@@ -20,6 +20,7 @@ Fiber(function () {
var warehouse = require('./warehouse.js'); var warehouse = require('./warehouse.js');
var logging = require('./logging.js'); var logging = require('./logging.js');
var deployGalaxy; var deployGalaxy;
var cleanup = require('./cleanup.js');
var Future = require('fibers/future'); var Future = require('fibers/future');
// This code is duplicated in app/server/server.js. // This code is duplicated in app/server/server.js.
@@ -30,7 +31,11 @@ Fiber(function () {
process.exit(1); process.exit(1);
} }
var tunnel; var killTunnel = function (tunnel) {
// XXX check if tunnel is already dead?
tunnel.kill("SIGHUP");
tunnel.waitExit();
};
var sshTunnel = function (to, localPort, remoteEnd, keyfile) { var sshTunnel = function (to, localPort, remoteEnd, keyfile) {
var args = []; var args = [];
@@ -72,6 +77,12 @@ Fiber(function () {
tunnel.waitExit = _.bind(exitFuture.wait, exitFuture); tunnel.waitExit = _.bind(exitFuture.wait, exitFuture);
tunnel.waitConnected = _.bind(connectedFuture.wait, connectedFuture); tunnel.waitConnected = _.bind(connectedFuture.wait, connectedFuture);
cleanup.onExit(function () {
Fiber(function () {
killTunnel(tunnel);
}).run();
});
return tunnel; return tunnel;
}; };
@@ -125,6 +136,7 @@ Fiber(function () {
var calculateGalaxyContextAndTunnel = function (deployEndpoint, var calculateGalaxyContextAndTunnel = function (deployEndpoint,
context, sshIdentity) { context, sshIdentity) {
var galaxyContext = {}; var galaxyContext = {};
var tunnel;
// 9414 because 9414xy (gAlAxy) in 1337 // 9414 because 9414xy (gAlAxy) in 1337
galaxyContext.port = process.env.PORT || 9414; galaxyContext.port = process.env.PORT || 9414;
if (deployEndpoint && deployEndpoint.indexOf("ssh://") === 0) { if (deployEndpoint && deployEndpoint.indexOf("ssh://") === 0) {
@@ -139,19 +151,40 @@ Fiber(function () {
tunnel.waitConnected(); tunnel.waitConnected();
context.galaxy = galaxyContext; context.galaxy = galaxyContext;
} else if (deployEndpoint) { } else if (deployEndpoint) {
tunnel = null;
galaxyContext.url = deployEndpoint + "/ultraworld"; galaxyContext.url = deployEndpoint + "/ultraworld";
galaxyContext.adminBaseUrl = deployEndpoint + "/"; galaxyContext.adminBaseUrl = deployEndpoint + "/";
context.galaxy = galaxyContext; context.galaxy = galaxyContext;
} }
return tunnel;
}; };
var prepareForGalaxy = function (site, context, argv) { var prepareForGalaxy = function (site, context, sshIdentity) {
if (! deployGalaxy) if (! deployGalaxy)
deployGalaxy = require('./deploy-galaxy.js'); deployGalaxy = require('./deploy-galaxy.js');
var deployEndpoint = deployGalaxy.discoverGalaxy(site); var deployEndpoint = deployGalaxy.discoverGalaxy(site);
calculateGalaxyContextAndTunnel(deployEndpoint, context, return calculateGalaxyContextAndTunnel(deployEndpoint, context,
argv["ssh-identity"]); sshIdentity);
};
// A command wrapped with galaxyCommand does the following:
// 1. Looks for the first non-hyphenated argument, and assumes that that is the
// site.
// 2. Tries to discover and set up a connection to a galaxy. If the galaxy
// discovery process indicates that a ssh tunnel needs to be set up, the optional
// ssh-identity argument is used to set it up.
// 3. Runs the command, and kills the tunnel, if any, when it finishes.
var galaxyCommand = function (cmd) {
return function (argv) {
if (argv._[1]) {
var tunnel = prepareForGalaxy(argv._[1], context, argv["ssh-identity"]);
var result = cmd(argv);
if (tunnel)
killTunnel(tunnel);
return result;
} else {
return cmd(argv);
}
};
}; };
var setReleaseVersion = function (version) { var setReleaseVersion = function (version) {
@@ -280,13 +313,21 @@ Fiber(function () {
process.exit(1); process.exit(1);
}; };
var runCommand = function (cmd, argv) {
var cmdRunner = findCommand(cmd);
if (cmdRunner.argumentParser)
cmdRunner.func(cmdRunner.argumentParser(argv));
else
cmdRunner.func(argv);
};
// XXX when the pass unexpected argument or unrecognized flags, print // XXX when the pass unexpected argument or unrecognized flags, print
// an error and fail out // an error and fail out
Commands.push({ Commands.push({
name: "run", name: "run",
help: "[default] Run this project in local development mode", help: "[default] Run this project in local development mode",
func: function (argv) { argumentParser: function (argv) {
// reparse args // reparse args
// This help logic should probably move to run.js eventually // This help logic should probably move to run.js eventually
var opt = require('optimist') var opt = require('optimist')
@@ -317,21 +358,21 @@ Fiber(function () {
"The application's database persists between runs. It's stored under\n" + "The application's database persists between runs. It's stored under\n" +
"the .meteor directory in the root of the project.\n"); "the .meteor directory in the root of the project.\n");
var new_argv = opt.argv;
if (argv.help) { if (argv.help) {
process.stdout.write(opt.help()); process.stdout.write(opt.help());
process.exit(1); process.exit(1);
} }
return opt.argv;
},
func: function (argv) {
requireDirInApp("run"); requireDirInApp("run");
maybePrintUserOverrideMessage(); maybePrintUserOverrideMessage();
runner.run(context, { runner.run(context, {
port: new_argv.port, port: argv.port,
minify: new_argv.production, minify: argv.production,
once: new_argv.once, once: argv.once,
settingsFile: new_argv.settings, settingsFile: argv.settings,
program: new_argv.program || undefined program: argv.program || undefined
}); });
} }
}); });
@@ -343,9 +384,8 @@ Fiber(function () {
var cmd = argv._.splice(0, 1)[0]; var cmd = argv._.splice(0, 1)[0];
switch (cmd) { switch (cmd) {
case "configure": case "configure":
prepareForGalaxy(null, context, argv); prepareForGalaxy(null, context, argv["ssh-identity"]);
console.log("Visit http://localhost:" + context.galaxy.port + "/panel to configure your galaxy"); console.log("Visit http://localhost:" + context.galaxy.port + "/panel to configure your galaxy");
Fiber.yield();
break; break;
default: default:
break; break;
@@ -360,14 +400,14 @@ Fiber(function () {
usage(); usage();
var cmd = argv._.splice(0,1)[0]; var cmd = argv._.splice(0,1)[0];
argv.help = true; argv.help = true;
findCommand(cmd).func(argv); runCommand(cmd, argv);
} }
}); });
Commands.push({ Commands.push({
name: "create", name: "create",
help: "Create a new project", help: "Create a new project",
func: function (argv) { argumentParser: function (argv) {
// reparse args // reparse args
var opt = require('optimist') var opt = require('optimist')
.describe('example', 'Example template to use.') .describe('example', 'Example template to use.')
@@ -388,20 +428,30 @@ Fiber(function () {
"sample applications. Use --list to see the available examples."); "sample applications. Use --list to see the available examples.");
var new_argv = opt.argv; var new_argv = opt.argv;
var appPath; var appPath;
if (argv._.length === 1)
appPath = argv._[0];
else if (argv._.length === 0 && argv.example)
appPath = argv.example;
if (appPath) {
new_argv.appPath = appPath;
} else if (argv.help) {
process.stdout.write(opt.help());
process.exit(1);
}
return new_argv;
},
func: function (argv) {
var appPath = argv.appPath;
var example_dir = path.join(__dirname, '..', 'examples'); var example_dir = path.join(__dirname, '..', 'examples');
var examples = _.reject(fs.readdirSync(example_dir), function (e) { var examples = _.reject(fs.readdirSync(example_dir), function (e) {
return (e === 'unfinished' || e === 'other' || e[0] === '.'); return (e === 'unfinished' || e === 'other' || e[0] === '.');
}); });
if (argv._.length === 1) { if (argv['list']) {
appPath = argv._[0];
} else if (argv._.length === 0 && new_argv.example) {
appPath = new_argv.example;
}
if (new_argv['list']) {
process.stdout.write("Available examples:\n"); process.stdout.write("Available examples:\n");
_.each(examples, function (e) { _.each(examples, function (e) {
process.stdout.write(" " + e + "\n"); process.stdout.write(" " + e + "\n");
@@ -411,11 +461,6 @@ Fiber(function () {
process.exit(1); process.exit(1);
}; };
if (argv.help || !appPath) {
process.stdout.write(opt.help());
process.exit(1);
}
if (fs.existsSync(appPath)) { if (fs.existsSync(appPath)) {
process.stderr.write(appPath + ": Already exists\n"); process.stderr.write(appPath + ": Already exists\n");
process.exit(1); process.exit(1);
@@ -431,13 +476,13 @@ Fiber(function () {
return x.replace(/~name~/g, path.basename(appPath)); return x.replace(/~name~/g, path.basename(appPath));
}; };
if (new_argv.example) { if (argv.example) {
if (examples.indexOf(new_argv.example) === -1) { if (examples.indexOf(argv.example) === -1) {
process.stderr.write(new_argv.example + ": no such example\n\n"); process.stderr.write(argv.example + ": no such example\n\n");
process.stderr.write("List available applications with 'meteor create --list'.\n"); process.stderr.write("List available applications with 'meteor create --list'.\n");
process.exit(1); process.exit(1);
} else { } else {
files.cp_r(path.join(example_dir, new_argv.example), appPath, { files.cp_r(path.join(example_dir, argv.example), appPath, {
ignore: [/^local$/] ignore: [/^local$/]
}); });
} }
@@ -461,9 +506,9 @@ Fiber(function () {
project.writeMeteorReleaseVersion(appPath, context.globalReleaseVersion); project.writeMeteorReleaseVersion(appPath, context.globalReleaseVersion);
process.stderr.write(appPath + ": created"); process.stderr.write(appPath + ": created");
if (new_argv.example && if (argv.example &&
new_argv.example !== appPath) argv.example !== appPath)
process.stderr.write(" (from '" + new_argv.example + "' template)"); process.stderr.write(" (from '" + argv.example + "' template)");
process.stderr.write(".\n\n"); process.stderr.write(".\n\n");
process.stderr.write( process.stderr.write(
@@ -476,7 +521,7 @@ Fiber(function () {
Commands.push({ Commands.push({
name: "update", name: "update",
help: "Upgrade this project to the latest version of Meteor", help: "Upgrade this project to the latest version of Meteor",
func: function (argv) { argumentParser: function (argv) {
// reparse args // reparse args
var opt = require('optimist').usage( var opt = require('optimist').usage(
"Usage: meteor update [--release <release>]\n" + "Usage: meteor update [--release <release>]\n" +
@@ -489,7 +534,9 @@ Fiber(function () {
process.stdout.write(opt.help()); process.stdout.write(opt.help());
process.exit(1); process.exit(1);
} }
return opt.argv;
},
func: function (argv) {
// refuse to update if we're in a git checkout. // refuse to update if we're in a git checkout.
if (!files.usesWarehouse()) { if (!files.usesWarehouse()) {
logging.die( logging.die(
@@ -501,9 +548,9 @@ Fiber(function () {
// Unless the user specified a specific release (or we're doing a // Unless the user specified a specific release (or we're doing a
// mid-update springboard), go get the latest release. // mid-update springboard), go get the latest release.
if (!opt.argv.release) { if (!argv.release) {
// Undocumented flag (used, eg, by upgrade-to-engine.js). // Undocumented flag (used, eg, by upgrade-to-engine.js).
if (!opt.argv["dont-fetch-latest"]) { if (!argv["dont-fetch-latest"]) {
try { try {
didGlobalUpdateWithoutSpringboarding = didGlobalUpdateWithoutSpringboarding =
warehouse.fetchLatestRelease(); warehouse.fetchLatestRelease();
@@ -531,9 +578,9 @@ Fiber(function () {
// If we're not in an app, then we're done (other than maybe printing some // If we're not in an app, then we're done (other than maybe printing some
// stuff). // stuff).
if (!context.appDir) { if (!context.appDir) {
if (opt.argv["dont-fetch-latest"]) if (argv["dont-fetch-latest"])
return; return;
if (opt.argv.release || didGlobalUpdateWithoutSpringboarding) { if (argv.release || didGlobalUpdateWithoutSpringboarding) {
// If the user specified a specific release, or we just did a global // If the user specified a specific release, or we just did a global
// update (with springboarding, in which case --release is set, or // update (with springboarding, in which case --release is set, or
// without springboarding, in which case didGlobalUpdate is set), // without springboarding, in which case didGlobalUpdate is set),
@@ -767,7 +814,7 @@ Fiber(function () {
Commands.push({ Commands.push({
name: "mongo", name: "mongo",
help: "Connect to the Mongo database for the specified site", help: "Connect to the Mongo database for the specified site",
func: function (argv) { argumentParser: function (argv) {
var opt = require('optimist') var opt = require('optimist')
.boolean('url') .boolean('url')
.boolean('U') .boolean('U')
@@ -796,10 +843,18 @@ Fiber(function () {
process.exit(1); process.exit(1);
} }
var new_argv = opt.argv; if (opt.argv._.length !== 1 && opt.argv._.length !== 2) {
// usage
process.stdout.write(opt.help());
process.exit(1);
}
return opt.argv;
},
func: galaxyCommand(function (argv) {
var mongoUrl; var mongoUrl;
if (new_argv._.length === 1) { if (argv._.length === 1) {
// localhost mode // localhost mode
var fut = new Future(); var fut = new Future();
find_mongo_port("mongo", function (mongod_port) { find_mongo_port("mongo", function (mongod_port) {
@@ -816,9 +871,8 @@ Fiber(function () {
}); });
mongoUrl = fut.wait(); mongoUrl = fut.wait();
} else if (new_argv._.length === 2) { } else if (argv._.length === 2) {
var site = new_argv._[1]; var site = argv._[1];
prepareForGalaxy(site, context, new_argv);
// remote mode // remote mode
if (context.galaxy) { if (context.galaxy) {
mongoUrl = deployGalaxy.temporaryMongoUrl({ mongoUrl = deployGalaxy.temporaryMongoUrl({
@@ -828,25 +882,20 @@ Fiber(function () {
} else { } else {
mongoUrl = deploy.temporaryMongoUrl(site); mongoUrl = deploy.temporaryMongoUrl(site);
} }
} else {
// usage
process.stdout.write(opt.help());
process.exit(1);
} }
if (argv.url) {
if (new_argv.url) {
console.log(mongoUrl); console.log(mongoUrl);
} else { } else {
process.stdin.pause(); process.stdin.pause();
deploy.run_mongo_shell(mongoUrl); deploy.run_mongo_shell(mongoUrl);
} }
} })
}); });
Commands.push({ Commands.push({
name: "deploy", name: "deploy",
help: "Deploy this project to Meteor", help: "Deploy this project to Meteor",
func: function (argv) { argumentParser: function (argv) {
var opt = require('optimist') var opt = require('optimist')
.alias('password', 'P') .alias('password', 'P')
.boolean('password') .boolean('password')
@@ -895,25 +944,27 @@ Fiber(function () {
process.stdout.write(opt.help()); process.stdout.write(opt.help());
process.exit(1); process.exit(1);
} }
var site = new_argv._[1]; return new_argv;
prepareForGalaxy(site, context, new_argv); },
func: galaxyCommand(function (argv) {
var site = argv._[1];
if (new_argv.delete) { if (argv.delete) {
if (context.galaxy) if (context.galaxy)
deployGalaxy.deleteApp(context); deployGalaxy.deleteApp(context);
else else
deploy.delete_app(site); deploy.delete_app(site);
} else { } else {
var starball = new_argv.star; var starball = argv.star;
// We don't need to be in an app if we're not going to run the bundler. // We don't need to be in an app if we're not going to run the bundler.
if (!starball) if (!starball)
requireDirInApp("deploy"); requireDirInApp("deploy");
var settings = undefined; var settings = undefined;
if (new_argv.settings) if (argv.settings)
settings = runner.getSettings(new_argv.settings); settings = runner.getSettings(argv.settings);
if (context.galaxy) { if (context.galaxy) {
if (new_argv.password) { if (argv.password) {
process.stderr.write("Galaxy does not support --password.\n"); process.stderr.write("Galaxy does not support --password.\n");
process.exit(1); process.exit(1);
} }
@@ -926,7 +977,7 @@ Fiber(function () {
starball: starball, starball: starball,
bundleOptions: { bundleOptions: {
nodeModulesMode: 'skip', nodeModulesMode: 'skip',
minify: !new_argv.debug, minify: !argv.debug,
releaseStamp: context.releaseVersion, releaseStamp: context.releaseVersion,
library: context.library library: context.library
} }
@@ -936,27 +987,28 @@ Fiber(function () {
url: site, url: site,
appDir: context.appDir, appDir: context.appDir,
settings: settings, settings: settings,
setPassword: !!new_argv.password, setPassword: !!argv.password,
bundleOptions: { bundleOptions: {
nodeModulesMode: 'skip', nodeModulesMode: 'skip',
minify: !new_argv.debug, minify: !argv.debug,
releaseStamp: context.releaseVersion, releaseStamp: context.releaseVersion,
library: context.library library: context.library
} }
}); });
} }
} }
} })
}); });
Commands.push({ Commands.push({
name: "logs", name: "logs",
help: "Show logs for specified site", help: "Show logs for specified site",
argumentParser: function (argv) {
return require('optimist').boolean('f').argv;
},
func: function (argv) { func: function (argv) {
argv = require('optimist').boolean('f').argv;
var site = argv._[1]; var site = argv._[1];
prepareForGalaxy(site, context, argv); var tunnel = prepareForGalaxy(site, context, argv["ssh-identity"]);
var useGalaxy = !!context.galaxy; var useGalaxy = !!context.galaxy;
if (argv.help || argv._.length !== 2) { if (argv.help || argv._.length !== 2) {
@@ -978,11 +1030,14 @@ Fiber(function () {
} }
if (useGalaxy) { if (useGalaxy) {
var streaming = !!argv.f;
deployGalaxy.logs({ deployGalaxy.logs({
context: context, context: context,
app: site, app: site,
streaming: !!argv.f streaming: streaming
}); });
if (! streaming && tunnel)
killTunnel(tunnel);
} else { } else {
deploy.logs(site); deploy.logs(site);
} }
@@ -1028,7 +1083,7 @@ Fiber(function () {
Commands.push({ Commands.push({
name: "test-packages", name: "test-packages",
help: "Test one or more packages", help: "Test one or more packages",
func: function (argv) { argumentParser: function (argv) {
// reparse args // reparse args
// This help logic should probably move to run.js eventually // This help logic should probably move to run.js eventually
var opt = require('optimist') var opt = require('optimist')
@@ -1061,13 +1116,13 @@ Fiber(function () {
"can use in conjunction with a service like Browserling or BrowserStack\n" + "can use in conjunction with a service like Browserling or BrowserStack\n" +
"to try the tests against many different browser versions."); "to try the tests against many different browser versions.");
if (argv.help) { if (argv.help) {
process.stdout.write(opt.help()); process.stdout.write(opt.help());
process.exit(1); process.exit(1);
} }
return opt.argv;
argv = opt.argv; },
func: function (argv) {
// remove 'test-packages'. // remove 'test-packages'.
// XXX we need to fix up this argv stuff once and for all to provide a // XXX we need to fix up this argv stuff once and for all to provide a
// real interface to commands that isn't terrible. // real interface to commands that isn't terrible.
@@ -1398,14 +1453,7 @@ Fiber(function () {
if (PROFILE_REQUIRE) if (PROFILE_REQUIRE)
require('./profile-require.js').printReport(); require('./profile-require.js').printReport();
try { runCommand(cmd, argv);
findCommand(cmd).func(argv);
} finally {
if (tunnel) {
tunnel.kill('SIGHUP');
tunnel.waitExit();
}
}
}; };
main(); main();