Merge remote-tracking branch 'settings' into devel

This commit is contained in:
Naomi Seyfer
2012-12-06 16:43:53 -08:00
7 changed files with 226 additions and 55 deletions

View File

@@ -120,7 +120,25 @@ kill $METEOR_PID
ps ax | grep -e "$MONGOMARK" | grep -v grep | awk '{print $1}' | xargs kill
echo "... settings"
cat > settings.json <<EOF
{ "foo" : "bar",
"baz" : "quux"
}
EOF
cat > settings.js <<EOF
if (Meteor.isServer) {
Meteor.startup(function () {
if (!Meteor.settings) process.exit(1);
if (Meteor.settings.foo !== "bar") process.exit(1);
process.exit(0);
});
}
EOF
$METEOR -p $PORT --settings='settings.json' --once > /dev/null
# XXX more tests here!

View File

@@ -131,17 +131,24 @@ var find_mongo_and_kill_it_dead = function (port, callback) {
};
exports.launch_mongo = function (app_dir, port, launch_callback, on_exit_callback) {
var handle = {stop: function (callback) { callback(); } };
launch_callback = launch_callback || function () {};
on_exit_callback = on_exit_callback || function () {};
// If we are passed an external mongo, assume it is launched and never
// exits. Matches code in run.js:exports.run.
// Since it is externally managed, asking it to actually stop would be
// impolite, so our stoppable handle is a noop
if (process.env.MONGO_URL) {
launch_callback();
return;
return handle;
}
var mongod_path = path.join(files.get_dev_bundle(), 'mongodb', 'bin', 'mongod');
var mongod_path = path.join(files.get_dev_bundle(),
'mongodb',
'bin',
'mongod');
// store data in app_dir
var data_path = path.join(app_dir, '.meteor', 'local', 'db');
@@ -161,15 +168,15 @@ exports.launch_mongo = function (app_dir, port, launch_callback, on_exit_callbac
'--port', port,
'--dbpath', data_path
]);
handle.stop = function (callback) {
var tries = 0;
var exited = false;
proc.removeListener('exit', on_exit_callback);
proc.kill('SIGINT');
callback && callback(err);
};
proc.on('exit', function (code, signal) {
on_exit_callback(code, signal);
});
// proc.stderr.setEncoding('utf8');
// proc.stderr.on('data', function (data) {
// process.stdout.write(data);
// });
proc.on('exit', on_exit_callback);
proc.stdout.setEncoding('utf8');
proc.stdout.on('data', function (data) {
@@ -178,6 +185,5 @@ exports.launch_mongo = function (app_dir, port, launch_callback, on_exit_callbac
launch_callback();
});
});
return handle;
};

View File

@@ -33,10 +33,15 @@ if (process.env.EMACS == "t") {
// interactively prompt for here.
var meteor_rpc = function (rpc_name, method, site, query_params, callback) {
var url = "https://" + DEPLOY_HOSTNAME + '/' + rpc_name + '/' + site;
var url;
if (DEPLOY_HOSTNAME.indexOf("http://") === 0)
url = DEPLOY_HOSTNAME + '/' + rpc_name + '/' + site;
else
url = "https://" + DEPLOY_HOSTNAME + '/' + rpc_name + '/' + site;
if (!_.isEmpty(query_params))
if (!_.isEmpty(query_params)) {
url += '?' + qs.stringify(query_params);
}
var r = request({method: method, url: url}, function (error, response, body) {
if (error || ((response.statusCode !== 200)
@@ -51,26 +56,39 @@ var meteor_rpc = function (rpc_name, method, site, query_params, callback) {
};
var deploy_app = function (url, app_dir, opt_debug, opt_tests,
opt_set_password) {
opt_set_password, settings) {
var parsed_url = parse_url(url);
// a bit contorted here to make sure we ask for the password before
// launching the slow bundle process.
with_password(parsed_url.hostname, function (password) {
var deployOptions = {
site: parsed_url.hostname,
appDir: app_dir,
debug: opt_debug,
tests: opt_tests,
password: password,
settings: settings
};
if (opt_set_password)
get_new_password(function (set_password) {
bundle_and_deploy(parsed_url.hostname, app_dir, opt_debug, opt_tests,
password, set_password);
deployOptions.setPassword = set_password;
bundle_and_deploy(deployOptions);
});
else
bundle_and_deploy(parsed_url.hostname, app_dir, opt_debug, opt_tests,
password);
bundle_and_deploy(deployOptions);
});
};
var bundle_and_deploy = function (site, app_dir, opt_debug, opt_tests,
password, set_password) {
var bundle_and_deploy = function (options) {
var site = options.site;
var app_dir = options.appDir;
var opt_debug = options.debug;
var opt_tests = options.tests;
var password = options.password;
var set_password = options.setPassword;
var settings = options.settings;
var build_dir = path.join(app_dir, '.meteor', 'local', 'build_tar');
var bundle_path = path.join(build_dir, 'bundle');
var bundle_opts = { skip_dev_bundle: true, no_minify: !!opt_debug,
@@ -90,14 +108,17 @@ var bundle_and_deploy = function (site, app_dir, opt_debug, opt_tests,
process.stdout.write('uploading ... ');
var opts = {};
if (password) opts.password = password;
if (set_password) opts.set_password = set_password;
var rpcOptions = {};
if (password) rpcOptions.password = password;
if (set_password) rpcOptions.set_password = set_password;
// When it hits the wire, all these opts will be URL-encoded.
if (settings !== undefined) rpcOptions.settings = settings;
var tar = child_process.spawn(
'tar', ['czf', '-', 'bundle'], {cwd: build_dir});
var rpc = meteor_rpc('deploy', 'POST', site, opts, function (err, body) {
var rpc = meteor_rpc('deploy', 'POST', site, rpcOptions, function (err, body) {
if (err) {
var errorMessage = (body || ("Connection error (" + err.message + ")"));
process.stderr.write("\nError deploying application: " + errorMessage + "\n");

View File

@@ -83,6 +83,26 @@ var findCommand = function (name) {
process.exit(1);
};
var getSettings = function (filename) {
var str;
try {
str = fs.readFileSync(filename, "utf8");
} catch (e) {
throw new Error("Could not find settings file " + filename);
}
if (str.length > 0x10000) {
throw new Error("Settings file must be less than 64 KB long");
}
// Ensure that the string is parseable in JSON, but there's
// no reason to use the object value of it yet.
if (str.match(/\S/)) {
JSON.parse(str);
return str;
} else {
return "";
}
};
// XXX when the pass unexpected argument or unrecognized flags, print
// an error and fail out
@@ -101,6 +121,8 @@ Commands.push({
.describe('debug', 'Run in debug mode for node-inspector')
.boolean('debug-brk')
.describe('debug-brk', 'Run in debug mode and break on first line')
.describe('settings', 'Set optional data for Meteor.settings on the server')
.boolean('once')
.usage(
"Usage: meteor run [options]\n" +
"\n" +
@@ -113,22 +135,26 @@ Commands.push({
"are automatically detected and applied to the running application.\n" +
"\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;
var settings = "";
if (argv.help) {
process.stdout.write(opt.help());
process.exit(1);
}
if (new_argv.settings) {
settings = getSettings(new_argv.settings);
}
var app_dir = path.resolve(require_project("run", true)); // app or package
var bundle_opts = { no_minify: !new_argv.production, symlink_dev_bundle: true};
var bundle_opts = { no_minify: !new_argv.production, symlink_dev_bundle: true };
var debugStatus = runner.DebugStatus.OFF;
if (new_argv['debug']) debugStatus = runner.DebugStatus.DEBUG;
if (new_argv['debug-brk']) debugStatus = runner.DebugStatus.BREAK;
runner.run(app_dir, bundle_opts, new_argv.port, debugStatus);
runner.run(app_dir, bundle_opts, new_argv.port, new_argv.once, settings, debugStatus);
}
});
@@ -468,7 +494,7 @@ Commands.push({
process.exit(1);
}
new_argv = opt.argv;
var new_argv = opt.argv;
if (new_argv._.length === 1) {
// localhost mode
@@ -485,7 +511,7 @@ Commands.push({
var mongo_url = "mongodb://127.0.0.1:" + mongod_port + "/meteor";
if (new_argv.url)
console.log(mongo_url)
console.log(mongo_url);
else
deploy.run_mongo_shell(mongo_url);
});
@@ -518,11 +544,12 @@ Commands.push({
.boolean('debug')
.describe('debug', 'deploy in debug mode (don\'t minify, etc)')
.boolean('tests')
.describe('settings', 'set optional data for Meteor.settings on the server')
// .describe('tests', 'deploy the tests instead of the actual application')
.usage(
// XXX document --tests in the future, once we publicly
// support tests
"Usage: meteor deploy <site> [--password] [--delete] [--debug]\n" +
"Usage: meteor deploy <site> [--password] [--settings settings.json] [--debug] [--delete]\n" +
"\n" +
"Deploys the project in your current directory to Meteor's servers.\n" +
"\n" +
@@ -532,6 +559,12 @@ Commands.push({
"'myapp.mydomain.com', then you'll also need to configure your domain's\n" +
"DNS records. See the Meteor docs for details.\n" +
"\n" +
"The --settings flag can be used to pass deploy-specific information to\n" +
"the application. It will be available at runtime in Meteor.settings, but only\n" +
"on the server. The argument is the name of a file containing the JSON data\n" +
"to use. The settings will persist across deployments until you again specify\n" +
"a settings file. To unset Meteor.settings, pass an empty settings file.\n" +
"\n" +
"The --delete flag permanently removes a deployed application, including\n" +
"all of its stored data.\n" +
"\n" +
@@ -550,10 +583,13 @@ Commands.push({
if (new_argv.delete) {
deploy.delete_app(new_argv._[1]);
} else {
var settings = undefined;
if (new_argv.settings)
settings = getSettings(new_argv.settings);
// accept packages iff we're deploying tests
var project_dir = path.resolve(require_project("bundle", new_argv.tests));
deploy.deploy_app(new_argv._[1], project_dir, new_argv.debug,
new_argv.tests, new_argv.password);
new_argv.tests, new_argv.password, settings);
}
}
});

View File

@@ -14,6 +14,7 @@ var mongo_runner = require(path.join(__dirname, '..', 'lib', 'mongo_runner.js'))
var _ = require(path.join(__dirname, '..', 'lib', 'third', 'underscore.js'));
////////// Globals //////////
//XXX: Refactor to not have globals anymore?
// list of log objects from the child process.
var server_log = [];
@@ -23,18 +24,42 @@ var Status = {
crashing: false, // does server crash whenever we start it?
listening: false, // do we expect the server to be listening now.
counter: 0, // how many crashes in rapid succession
code: 0, // exit code last returned
shouldRestart: true, // true if we should be restarting the server
shuttingDown: false, // true if we're on the way to shutting down the server
exitNow: function () {
var self = this;
log_to_clients({'exit': "Your application is exiting."});
self.shuttingDown = true;
self.mongoHandle && self.mongoHandle.stop(function (err) {
if (err)
process.stdout.write(err.reason + "\n");
process.exit(self.code);
});
},
reset: function () {
this.crashing = false;
this.counter = 0;
},
hard_crashed: function () {
var self = this;
if (!self.shouldRestart) {
self.exitNow();
return;
}
log_to_clients({'exit': "Your application is crashing. Waiting for file change."});
this.crashing = true;
},
soft_crashed: function () {
var self = this;
if (!self.shouldRestart) {
self.exitNow();
return;
}
if (this.counter === 0)
setTimeout(function () {
this.counter = 0;
@@ -48,6 +73,9 @@ var Status = {
}
};
// List of queued requests. Each item in the list is a function to run
// when the inner app is ready to receive connections.
var request_queue = [];
@@ -167,16 +195,38 @@ var log_to_clients = function (msg) {
};
////////// Launch server process //////////
// Takes options:
// bundlePath
// outerPort
// innerPort
// mongoURL
// onExit
// [onListen]
// [debugStatus]
//
// [runOnce]: boolean; default false; if true doesn't ever try to restart, and
// forwards server exit code.
// [settings]
var start_server = function (bundle_path, outer_port, inner_port, mongo_url,
on_exit_callback, on_listen_callback, dbg) {
var start_server = function (options) {
// environment
options = _.extend({runOnce: false,
debugStatus: exports.DebugStatus.OFF
},
options);
if (options.runOnce) {
Status.shouldRestart = false;
}
var env = {};
for (var k in process.env)
env[k] = process.env[k];
env.PORT = inner_port;
env.MONGO_URL = mongo_url;
env.ROOT_URL = env.ROOT_URL || ('http://localhost:' + outer_port);
env.PORT = options.innerPort;
env.MONGO_URL = options.mongoURL;
env.ROOT_URL = env.ROOT_URL || ('http://localhost:' + options.outerPort);
var dbg = options.debugStatus;
var nodeOptions = [];
if (dbg === exports.DebugStatus.DEBUG)
nodeOptions.push('--debug');
@@ -184,9 +234,13 @@ var start_server = function (bundle_path, outer_port, inner_port, mongo_url,
console.log('Debug will break on the first line');
nodeOptions.push('--debug-brk');
}
//spawn inner server, with debug enabled if requested
if (options.settings)
env.METEOR_SETTINGS = options.settings;
var proc = spawn(process.execPath,
nodeOptions.concat([path.join(bundle_path, 'main.js'), '--keepalive']),
nodeOptions.concat([path.join(options.bundlePath, 'main.js'), '--keepalive']),
{env: env});
// XXX deal with test server logging differently?!
@@ -199,7 +253,7 @@ var start_server = function (bundle_path, outer_port, inner_port, mongo_url,
// string must match server.js
data = data.replace(/^LISTENING\s*(?:\n|$)/m, '');
if (data.length != originalLength)
on_listen_callback && on_listen_callback();
options.onListen && options.onListen();
if (data)
log_to_clients({stdout: data});
});
@@ -216,7 +270,7 @@ var start_server = function (bundle_path, outer_port, inner_port, mongo_url,
log_to_clients({'exit': 'Exited with code: ' + code});
}
on_exit_callback();
options.onExit(code);
});
// this happens sometimes when we write a keepalive after the app is
@@ -317,7 +371,7 @@ _.extend(DependencyWatcher.prototype, {
return false;
try {
var stats = fs.lstatSync(filepath)
var stats = fs.lstatSync(filepath);
} catch (e) {
// doesn't exist -- leave stats undefined
}
@@ -462,9 +516,7 @@ exports.DebugStatus = {
// This function never returns and will call process.exit() if it
// can't continue. If you change this, remember to call
// watcher.destroy() as appropriate.
exports.run = function (app_dir, bundle_opts, port, dbg) {
debug = bundle_opts.debug;
debug_brk = bundle_opts.debug_brk;
exports.run = function (app_dir, bundle_opts, port, once, settings, dbg) {
var outer_port = port || 3000;
var inner_port = outer_port + 1;
var mongo_port = outer_port + 2;
@@ -477,6 +529,7 @@ exports.run = function (app_dir, bundle_opts, port, dbg) {
var test_mongo_url = "mongodb://127.0.0.1:" + mongo_port + "/meteor_test";
var test_bundle_opts;
if (files.is_app_dir(app_dir)) {
// If we're an app, make separate test_bundle_opts to trigger a
// separate runner.
@@ -498,6 +551,8 @@ exports.run = function (app_dir, bundle_opts, port, dbg) {
var watcher;
var start_watching = function () {
if (!Status.shouldRestart)
return;
if (deps_info) {
if (watcher)
watcher.destroy();
@@ -561,22 +616,30 @@ exports.run = function (app_dir, bundle_opts, port, dbg) {
start_watching();
Status.running = true;
server_handle = start_server(
bundle_path, outer_port, inner_port, mongo_url,
function () {
server_handle = start_server({
bundlePath: bundle_path,
outerPort: outer_port,
innerPort: inner_port,
mongoURL: mongo_url,
onExit: function (code) {
// on server exit
Status.running = false;
Status.listening = false;
Status.code = code;
Status.soft_crashed();
if (!Status.crashing)
restart_server();
}, function () {
},
onListen: function () {
// on listen
Status.listening = true;
_.each(request_queue, function (f) { f(); });
request_queue = [];
},
dbg);
debugStatus: dbg,
runOnce: once,
settings: settings
});
// launch test bundle and server if needed.
@@ -590,12 +653,16 @@ exports.run = function (app_dir, bundle_opts, port, dbg) {
});
files.rm_recursive(test_bundle_path);
} else {
test_server_handle = start_server(
test_bundle_path, test_port, test_mongo_url, function () {
test_server_handle = start_server({
bundlePath: test_bundle_path,
outerPort: test_port,
innerPort: test_port,
mongoURL: test_mongo_url,
onExit: function (code) {
// No restarting or crash loop prevention on the test server
// for now. We'll see how annoying it is.
log_to_clients({'system': "Test server crashed."});
});
}});
}
};
};
@@ -606,7 +673,7 @@ exports.run = function (app_dir, bundle_opts, port, dbg) {
var mongo_startup_print_timer;
var process_startup_printer;
var launch = function () {
mongo_runner.launch_mongo(
Status.mongoHandle = mongo_runner.launch_mongo(
app_dir,
mongo_port,
function () { // On Mongo startup complete
@@ -623,6 +690,9 @@ exports.run = function (app_dir, bundle_opts, port, dbg) {
restart_server();
},
function (code, signal) { // On Mongo dead
if (Status.shuttingDown) {
return;
}
console.log("Unexpected mongo exit code " + code + ". Restarting.");
// if mongo dies 3 times with less than 5 seconds between each,

View File

@@ -90,6 +90,18 @@ domain like myapp.com, you'll need a DNS A record, matching the IP
address of origin.meteor.com.
{{/warning}}
To add deploy-specific information to your application, use the `--settings`
option. This will set the variable `Meteor.settings` in your application, but
only on the server. The `--settings` option takes an argument: the name of a
file containing JSON data to put into `Meteor.settings`.
The settings you pass will persist on your deployed application across
invocations of `meteor deploy` until you again pass the `--settings` option with
different contents in your settings file. To unset `Meteor.settings`, pass an
empty settings file.
<h3 id="meteorlogs">meteor logs <i>site</i></h3>
Retrieves the server logs for the named Meteor application.

View File

@@ -2,3 +2,11 @@ Meteor = {
isClient: false,
isServer: true
};
try {
if (process.env.METEOR_SETTINGS)
Meteor.settings = JSON.parse(process.env.METEOR_SETTINGS);
} catch (e) {
throw new Error("Settings are not valid JSON");
}