Merge branch 'legacy-code-cleanup' into devel

This commit is contained in:
David Glasser
2015-01-29 14:03:51 -08:00
52 changed files with 64 additions and 2459 deletions

View File

@@ -31,8 +31,8 @@ rest of this section will explain the specific API commands in greater detail.
// Use Underscore package, but only on the server.
// Version not specified, so it will be as of Meteor 0.9.0.
api.use('underscore', 'server');
// Use application-configuration package, version 1.0.0 or newer.
api.use('application-configuration@1.0.0');
// Use iron:router package, version 1.0.0 or newer.
api.use('iron:router@1.0.0');
// Give users of this package access to the Templating package.
api.imply('templating')
// Export the object 'Email' to packages or apps that use this package.

1
meteor
View File

@@ -60,7 +60,6 @@ function install_dev_bundle {
rm -rf "$BUNDLE_TMPDIR"
mkdir "$BUNDLE_TMPDIR"
# fyi: URL duplicated in packages/dev-bundle-fetcher/dev-bundle
DEV_BUNDLE_URL_ROOT="https://d3sqy0vbqsdhku.cloudfront.net/"
# If you set $USE_TEST_DEV_BUNDLE_SERVER then we will download
# dev bundles copied by copy-dev-bundle-from-jenkins.sh without --prod.

View File

@@ -1,2 +0,0 @@
.build
.build*

View File

@@ -1,4 +0,0 @@
# Application-Configuration
This was an internal Meteor package used for interacting with internal prototype
systems that no longer exist.

View File

@@ -1,200 +0,0 @@
var Future = Npm.require("fibers/future");
AppConfig = {};
AppConfig.findGalaxy = _.once(function () {
if (!('GALAXY' in process.env || 'ULTRAWORLD_DDP_ENDPOINT' in process.env)) {
return null;
}
return Follower.connect(process.env.ULTRAWORLD_DDP_ENDPOINT || process.env.GALAXY);
});
var ultra = AppConfig.findGalaxy();
var subFuture = new Future();
var subFutureJobs = new Future();
if (ultra) {
ultra.subscribe("oneApp", process.env.GALAXY_APP, subFuture.resolver());
ultra.subscribe("oneJob", process.env.GALAXY_JOB, subFutureJobs.resolver());
}
var Apps;
var Jobs;
var Services;
var collectionFuture = new Future();
Meteor.startup(function () {
var Mongo = Package.mongo.Mongo;
if (ultra) {
Apps = new Mongo.Collection("apps", {
connection: ultra
});
Jobs = new Mongo.Collection("jobs", {
connection: ultra
});
Services = new Mongo.Collection('services', {
connection: ultra
});
// allow us to block on the collections being ready
collectionFuture.return();
}
});
// XXX: Remove this once we allow the same collection to be new'd from multiple
// places.
AppConfig._getAppCollection = function () {
collectionFuture.wait();
return Apps;
};
AppConfig._getJobsCollection = function () {
collectionFuture.wait();
return Jobs;
};
var staticAppConfig;
try {
if (process.env.APP_CONFIG) {
staticAppConfig = JSON.parse(process.env.APP_CONFIG);
} else {
var settings;
try {
if (process.env.METEOR_SETTINGS) {
settings = JSON.parse(process.env.METEOR_SETTINGS);
}
} catch (e) {
Log.warn("Could not parse METEOR_SETTINGS as JSON");
}
staticAppConfig = {
settings: settings,
packages: {
'mongo-livedata': {
url: process.env.MONGO_URL,
oplog: process.env.MONGO_OPLOG_URL
}
}
};
}
} catch (e) {
Log.warn("Could not parse initial APP_CONFIG environment variable");
};
AppConfig.getAppConfig = function () {
if (!subFuture.isResolved() && staticAppConfig) {
return staticAppConfig;
}
subFuture.wait();
var myApp = Apps.findOne(process.env.GALAXY_APP);
if (!myApp) {
throw new Error("there is no app config for this app");
}
var config = myApp.config;
return config;
};
AppConfig.getStarForThisJob = function () {
if (ultra) {
subFutureJobs.wait();
var job = Jobs.findOne(process.env.GALAXY_JOB);
if (job) {
return job.star;
}
}
return null;
};
AppConfig.configurePackage = function (packageName, configure) {
var appConfig = AppConfig.getAppConfig(); // Will either be based in the env var,
// or wait for galaxy to connect.
var lastConfig =
(appConfig && appConfig.packages &&
appConfig.packages[packageName]) || {};
// Always call the configure callback "soon" even if the initial configuration
// is empty (synchronously, though deferred would be OK).
// XXX make sure that all callers of configurePackage deal well with multiple
// callback invocations! eg, email does not
configure(lastConfig);
var configureIfDifferent = function (app) {
if (!EJSON.equals(
app.config && app.config.packages && app.config.packages[packageName],
lastConfig)) {
lastConfig = app.config.packages[packageName];
configure(lastConfig);
}
};
var subHandle;
var observed = new Future();
// This is not required to finish, so defer it so it doesn't block anything
// else.
Meteor.defer( function () {
// there's a Meteor.startup() that produces the various collections, make
// sure it runs first before we continue.
collectionFuture.wait();
subHandle = Apps.find(process.env.GALAXY_APP).observe({
added: configureIfDifferent,
changed: configureIfDifferent
});
observed.return();
});
return {
stop: function () {
observed.wait();
subHandle.stop();
}
};
};
AppConfig.configureService = function (serviceName, version, configure) {
// Collect all the endpoints for this service, from both old- and new-format
// documents, and call the `configure` callback with all the service endpoints
// that we know about.
var callConfigure = function (doc) {
var serviceDocs = Services.find({
name: serviceName,
version: version
});
var endpoints = [];
serviceDocs.forEach(function (serviceDoc) {
if (serviceDoc.providers) {
_.each(serviceDoc.providers, function (endpoint, app) {
endpoints.push(endpoint);
});
} else {
endpoints.push(serviceDoc.endpoint);
}
});
configure(endpoints);
};
if (ultra) {
// there's a Meteor.startup() that produces the various collections, make
// sure it runs first before we continue.
collectionFuture.wait();
// First try to subscribe to the new format service registrations; if that
// sub doesn't exist, then ultraworld hasn't updated to the new format yet,
// so try the old format `servicesByName` sub instead.
ultra.subscribe('services', serviceName, version, {
onError: function (err) {
if (err.error === 404) {
ultra.subscribe('servicesByName', serviceName);
}
}
});
return Services.find({
name: serviceName,
version: version
}).observe({
added: callConfigure,
changed: callConfigure,
removed: callConfigure
});
}
};

View File

@@ -1,13 +0,0 @@
Package.describe({
summary: "Interaction with the configuration sources for your apps",
version: '1.0.4'
});
Package.onUse(function (api) {
api.use(['logging', 'underscore', 'ddp', 'ejson', 'follower-livedata']);
api.use(['mongo'], {
unordered: true
});
api.addFiles(['config.js'], 'server');
api.export('AppConfig', 'server');
});

View File

@@ -1 +0,0 @@
.build*

View File

@@ -1 +0,0 @@
node_modules

View File

@@ -1,7 +0,0 @@
This directory and the files immediately inside it are automatically generated
when you change this package's NPM dependencies. Commit the files in this
directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
so that others run the same versions of sub-dependencies.
You should NOT check in the node_modules directory that Meteor automatically
creates; if you are using git, the .gitignore file tells git to ignore it.

View File

@@ -1,15 +0,0 @@
{
"dependencies": {
"optimist": {
"version": "0.6.0",
"dependencies": {
"wordwrap": {
"version": "0.0.2"
},
"minimist": {
"version": "0.0.5"
}
}
}
}
}

View File

@@ -1,2 +0,0 @@
This was an internal Meteor package used for interacting with internal prototype
systems that no longer exist.

View File

@@ -1,275 +0,0 @@
var optimist = Npm.require('optimist');
var Future = Npm.require('fibers/future');
Ctl = {};
var connection;
var checkConnection;
_.extend(Ctl, {
Commands: [],
main: function (argv) {
var opt = optimist(argv)
.alias('h', 'help')
.boolean('help');
argv = opt.argv;
if (argv.help) {
argv._.splice(0, 0, "help");
delete argv.help;
}
var cmdName = 'help';
if (argv._.length)
cmdName = argv._.splice(0,1)[0];
Ctl.findCommand(cmdName).func(argv);
Ctl.disconnect();
return 0;
},
startServerlikeProgramIfNotPresent: function (program, tags, admin) {
var numServers = Ctl.getJobsByApp(
Ctl.myAppName(), {program: program, done: false}).count();
if (numServers === 0) {
return Ctl.startServerlikeProgram(program, tags, admin);
} else {
console.log(program, "already running.");
}
return null;
},
startServerlikeProgram: function (program, tags, admin) {
var appConfig = Ctl.prettyCall(
Ctl.findGalaxy(), 'getAppConfiguration', [Ctl.myAppName()]);
if (typeof admin == 'undefined')
admin = appConfig.admin;
admin = !!admin;
var jobId = null;
var rootUrl = Ctl.rootUrl;
if (! rootUrl) {
var bindPathPrefix = "";
if (admin) {
bindPathPrefix = "/" + encodeURIComponent(Ctl.myAppName()).replace(/\./g, '_');
}
rootUrl = "https://" + appConfig.sitename + bindPathPrefix;
}
// Allow appConfig settings to be objects or strings. We need to stringify
// them to pass them to the app in the env var.
// Backwards compat with old app config format.
_.each(["settings", "METEOR_SETTINGS"], function (settingsKey) {
if (appConfig[settingsKey] && typeof appConfig[settingsKey] === "object")
appConfig[settingsKey] = JSON.stringify(appConfig[settingsKey]);
});
// XXX args? env?
var env = {
ROOT_URL: rootUrl,
METEOR_SETTINGS: appConfig.settings || appConfig.METEOR_SETTINGS
};
if (admin)
env.ADMIN_APP = 'true';
jobId = Ctl.prettyCall(Ctl.findGalaxy(), 'run', [Ctl.myAppName(), program, {
exitPolicy: 'restart',
env: env,
ports: {
"main": {
bindEnv: "PORT",
routeEnv: "ROUTE"//,
//bindIpEnv: "BIND_IP" // Later, we can teach Satellite to do
//something like recommend the process bind to a particular IP here.
//For now, we don't have a way of setting this, so Satellite binds
//to 0.0.0.0
}
},
tags: tags
}]);
console.log("Started", program);
return jobId;
},
findCommand: function (name) {
var cmd = _.where(Ctl.Commands, { name: name })[0];
if (! cmd) {
console.log("'" + name + "' is not a ctl command. See 'ctl --help'.");
process.exit(1);
}
return cmd;
},
hasProgram: function (name) {
Ctl.subscribeToAppJobs(Ctl.myAppName());
var myJob = Ctl.jobsCollection().findOne(Ctl.myJobId());
var manifest = Ctl.prettyCall(Ctl.findGalaxy(), 'getStarManifest', [myJob.star]);
if (!manifest)
return false;
var found = false;
return _.find(manifest.programs, function (prog) { return prog.name === name; });
},
findGalaxy: _.once(function () {
if (!('GALAXY' in process.env)) {
console.log(
"GALAXY environment variable must be set. See 'galaxy --help'.");
process.exit(1);
}
connection = Follower.connect(process.env['ULTRAWORLD_DDP_ENDPOINT']);
checkConnection = Meteor.setInterval(function () {
if (Ctl.findGalaxy().status().status !== "connected" &&
Ctl.findGalaxy().status().retryCount > 2) {
console.log("Cannot connect to galaxy; exiting");
process.exit(3);
}
}, 2*1000);
return connection;
}),
disconnect: function () {
if (connection) {
connection.disconnect();
}
if (checkConnection) {
Meteor.clearInterval(checkConnection);
checkConnection = null;
}
},
updateProxyActiveTags: function (tags, options) {
var proxy;
var proxyTagSwitchFuture = new Future;
options = options || {};
AppConfig.configureService('proxy', 'pre0', function (proxyService) {
if (proxyService && ! _.isEmpty(proxyService)) {
try {
proxy = Follower.connect(proxyService, {
group: "proxy"
});
var tries = 0;
while (tries < 100) {
try {
proxy.call('updateTags', Ctl.myAppName(), tags, options);
break;
} catch (e) {
if (e.error === 'not-enough-bindings') {
tries++;
// try again in a sec.
Meteor._sleepForMs(1000);
} else {
throw e;
}
}
}
proxy.disconnect();
if (!proxyTagSwitchFuture.isResolved())
proxyTagSwitchFuture['return']();
} catch (e) {
if (!proxyTagSwitchFuture.isResolved())
proxyTagSwitchFuture['throw'](e);
}
}
});
var proxyTimeout = Meteor.setTimeout(function () {
if (!proxyTagSwitchFuture.isResolved())
proxyTagSwitchFuture['throw'](
new Error("Timed out looking for a proxy " +
"or trying to change tags on it. Status: " +
(proxy ? proxy.status().status : "no connection"))
);
}, 50*1000);
proxyTagSwitchFuture.wait();
Meteor.clearTimeout(proxyTimeout);
},
jobsCollection: _.once(function () {
return new Mongo.Collection("jobs", {manager: Ctl.findGalaxy()});
}),
// use _.memoize so that this is called only once per app.
subscribeToAppJobs: _.memoize(function (appName) {
Ctl.findGalaxy()._subscribeAndWait("jobsByApp", [appName]);
}),
// XXX this never unsubs...
getJobsByApp: function (appName, restOfSelector) {
var galaxy = Ctl.findGalaxy();
Ctl.subscribeToAppJobs(appName);
var selector = {app: appName};
if (restOfSelector)
_.extend(selector, restOfSelector);
return Ctl.jobsCollection().find(selector);
},
myAppName: _.once(function () {
if (!('GALAXY_APP' in process.env)) {
console.log("GALAXY_APP environment variable must be set.");
process.exit(1);
}
return process.env.GALAXY_APP;
}),
myJobId: _.once(function () {
if (!('GALAXY_JOB' in process.env)) {
console.log("GALAXY_JOB environment variable must be set.");
process.exit(1);
}
return process.env.GALAXY_JOB;
}),
usage: function() {
process.stdout.write(
"Usage: ctl [--help] <command> [<args>]\n" +
"\n" +
"For now, the GALAXY environment variable must be set to the location of\n" +
"your Galaxy management server (Ultraworld.) This string is in the same\n" +
"format as the argument to DDP.connect().\n" +
"\n" +
"Commands:\n");
_.each(Ctl.Commands, function (cmd) {
if (cmd.help && ! cmd.hidden) {
var name = cmd.name + " ".substr(cmd.name.length);
process.stdout.write(" " + name + cmd.help + "\n");
}
});
process.stdout.write("\n");
process.stdout.write(
"See 'ctl help <command>' for details on a command.\n");
process.exit(1);
},
// XXX copied to meteor/tools/deploy-galaxy.js
exitWithError: function (error, messages) {
messages = messages || {};
if (! (error instanceof Meteor.Error))
throw error; // get a stack
var msg = messages[error.error];
if (msg)
process.stderr.write(msg + "\n");
else if (error instanceof Meteor.Error)
process.stderr.write("Denied: " + error.message + "\n");
process.exit(1);
},
// XXX copied to meteor/tools/deploy-galaxy.js
prettyCall: function (galaxy, name, args, messages) {
try {
var ret = galaxy.apply(name, args);
} catch (e) {
Ctl.exitWithError(e, messages);
}
return ret;
},
kill: function (programName, jobId) {
console.log("Killing %s (%s)", programName, jobId);
Ctl.prettyCall(Ctl.findGalaxy(), 'kill', [jobId]);
}
});

View File

@@ -1,12 +0,0 @@
Package.describe({
summary: "Helpers for control programs",
version: "1.0.5"
});
Npm.depends({optimist: '0.6.0'});
Package.onUse(function (api) {
api.use(['logging', 'underscore', 'ddp', 'mongo', 'follower-livedata', 'application-configuration'], 'server');
api.export('Ctl', 'server');
api.addFiles('ctl-helper.js', 'server');
});

View File

@@ -1 +0,0 @@
.build*

View File

@@ -1,2 +0,0 @@
This was an internal Meteor package used for interacting with internal prototype
systems that no longer exist.

View File

@@ -1,194 +0,0 @@
var Future = Npm.require("fibers/future");
Ctl.Commands.push({
name: "help",
func: function (argv) {
if (!argv._.length || argv.help)
Ctl.usage();
var cmd = argv._.splice(0,1)[0];
argv.help = true;
Ctl.findCommand(cmd).func(argv);
}
});
var startFun = function (argv) {
if (argv.help || argv._.length !== 0) {
process.stderr.write(
"Usage: ctl start\n" +
"\n" +
"Starts the app. For now, this just means that it runs the 'server'\n" +
"program.\n"
);
process.exit(1);
}
Ctl.subscribeToAppJobs(Ctl.myAppName());
var jobs = Ctl.jobsCollection();
var thisJob = jobs.findOne(Ctl.myJobId());
Ctl.updateProxyActiveTags(['', thisJob.star]);
if (Ctl.hasProgram("console")) {
console.log("starting console for app", Ctl.myAppName());
Ctl.startServerlikeProgramIfNotPresent("console", ["admin"], true);
}
console.log("starting server for app", Ctl.myAppName());
Ctl.startServerlikeProgramIfNotPresent("server", ["runner"]);
};
Ctl.Commands.push({
name: "start",
help: "Start this app",
func: startFun
});
Ctl.Commands.push({
name: "endUpdate",
help: "Start this app to end an update",
func: startFun
});
var stopFun = function (argv) {
if (argv.help || argv._.length !== 0) {
process.stderr.write(
"Usage: ctl stop\n" +
"\n" +
"Stops the app. For now, this just means that it kills all jobs\n" +
"other than itself.\n"
);
process.exit(1);
}
// Get all jobs (other than this job: don't commit suicide!) that are not
// already killed.
var jobs = Ctl.getJobsByApp(
Ctl.myAppName(), {_id: {$ne: Ctl.myJobId()}, done: false});
jobs.forEach(function (job) {
// Don't commit suicide.
if (job._id === Ctl.myJobId())
return;
// It's dead, Jim.
if (job.done)
return;
Ctl.kill(job.program, job._id);
});
console.log("Server stopped.");
};
Ctl.Commands.push({
name: "stop",
help: "Stop this app",
func: stopFun
});
var waitForDone = function (jobCollection, jobId) {
var fut = new Future();
var found = false;
try {
var observation = jobCollection.find(jobId).observe({
added: function (doc) {
found = true;
if (doc.done)
fut['return']();
},
changed: function (doc) {
if (doc.done)
fut['return']();
},
removed: function (doc) {
fut['return']();
}
});
// if the document doesn't exist at all, it's certainly done.
if (!found)
fut['return']();
fut.wait();
} finally {
observation.stop();
}
};
Ctl.Commands.push({
name: "beginUpdate",
help: "Stop this app to begin an update",
func: function (argv) {
Ctl.subscribeToAppJobs(Ctl.myAppName());
var jobs = Ctl.jobsCollection();
var thisJob = jobs.findOne(Ctl.myJobId());
// Look at all the server jobs that are on the old star.
var oldJobSelector = {
app: Ctl.myAppName(),
star: {$ne: thisJob.star},
program: "server",
done: false
};
var oldServers = jobs.find(oldJobSelector).fetch();
// Start a new job for each of them.
var newServersAlreadyPresent = jobs.find({
app: Ctl.myAppName(),
star: thisJob.star,
program: "server",
done: false
}).count();
// discount any new servers we've already started.
oldServers.splice(0, newServersAlreadyPresent);
console.log("starting " + oldServers.length + " new servers to match old");
_.each(oldServers, function (oldServer) {
Ctl.startServerlikeProgram("server",
oldServer.tags,
oldServer.env.ADMIN_APP);
});
// Wait for them all to come up and bind to the proxy.
var updateProxyActiveTagsOptions = {
requireRegisteredBindingCount: {}
};
// How many new servers should be up when we update the tags, given how many
// servers we're aiming at:
var target;
switch (oldServers.length) {
case 0:
target = 0;
break;
case 1:
target = 1;
break;
case 2:
target = 1;
break;
default:
var c = oldServers.length;
target = Math.min(c - 1, Math.ceil(c*.8));
break;
}
updateProxyActiveTagsOptions.requireRegisteredBindingCount[thisJob.star] =
target;
Ctl.updateProxyActiveTags(['', thisJob.star], updateProxyActiveTagsOptions);
// (eventually) tell the proxy to switch over to using the new star
// One by one, kill all the old star's server jobs.
var jobToKill = jobs.findOne(oldJobSelector);
while (jobToKill) {
Ctl.kill("server", jobToKill._id);
// Wait for it to go down
waitForDone(jobs, jobToKill._id);
// Spend some time in between to allow any reconnect storm to die down.
Meteor._sleepForMs(5000);
jobToKill = jobs.findOne(oldJobSelector);
}
// Now kill all old non-server jobs. They're less important.
jobs.find({
app: Ctl.myAppName(),
star: {$ne: thisJob.star},
program: {$ne: "server"},
done: false
}).forEach(function (job) {
Ctl.kill(job.program, job._id);
});
// fin
process.exit(0);
}
});
main = function (argv) {
return Ctl.main(argv);
};

View File

@@ -1,10 +0,0 @@
Package.describe({
summary: "Default control program for an application",
version: "1.0.3"
});
Package.onUse(function (api) {
api.use(['underscore', 'ddp', 'mongo', 'ctl-helper', 'application-configuration', 'follower-livedata'], 'server');
api.export('main', 'server');
api.addFiles('ctl.js', 'server');
});

View File

@@ -56,28 +56,10 @@ StreamServer = function () {
self.server.installHandlers(Package.webapp.WebApp.httpServer);
Package.webapp.WebApp.httpServer.addListener('request', Package.webapp.WebApp._timeoutAdjustmentRequestCallback);
Package.webapp.WebApp.httpServer.on('meteor-closing', function () {
_.each(self.open_sockets, function (socket) {
socket.end();
});
});
// Support the /websocket endpoint
self._redirectWebsocketEndpoint();
self.server.on('connection', function (socket) {
if (Package.webapp.WebAppInternals.usingDdpProxy) {
// If we are behind a DDP proxy, immediately close any sockjs connections
// that are not using websockets; the proxy will terminate sockjs for us,
// so we don't expect to be handling any other transports.
if (socket.protocol !== "websocket" &&
socket.protocol !== "websocket-raw") {
socket.close();
return;
}
}
socket.send = function (data) {
socket.write(data);
};

View File

@@ -1 +0,0 @@
.build*

View File

@@ -1,2 +0,0 @@
This was an internal Meteor package used for interacting with internal prototype
systems that no longer exist.

View File

@@ -1,78 +0,0 @@
set -e
trap "echo Failed to fetch binary dependencies." EXIT
if [ -z "$DATA_DIR" ]; then
echo "Please set DATA_DIR to a writeable directory."
exit 1
fi
cd `dirname $0`
# Duplicated from scripts/admin/launch-meteor, except that we hardcode the path
# to sysctl. (When satellite spawns ultraworld, ultraworld doesn't have a PATH
# and so can't find sysctl itself.)
UNAME=$(uname)
if [ "$UNAME" != "Linux" -a "$UNAME" != "Darwin" ] ; then
echo "Sorry, this OS is not supported yet." 1>&2
exit 1
fi
# If you change this, also change host() in tools/archinfo.js
if [ "$UNAME" = "Darwin" ] ; then
if [ "i386" != "$(uname -p)" -o "1" != "$(/usr/sbin/sysctl -n hw.cpu64bit_capable 2>/dev/null || echo 0)" ] ; then
# Can't just test uname -m = x86_64, because Snow Leopard can
# return other values.
echo "Only 64-bit Intel processors are supported at this time." 1>&2
exit 1
fi
ARCH="x86_64"
elif [ "$UNAME" = "Linux" ] ; then
ARCH="$(uname -m)"
if [ "$ARCH" != "i686" -a "$ARCH" != "x86_64" ] ; then
echo "Unsupported architecture: $ARCH" 1>&2
echo "Meteor only supports i686 and x86_64 for now." 1>&2
exit 1
fi
fi
PLATFORM="${UNAME}_${ARCH}"
# XXX don't hardcode linux :)
TARBALL="dev_bundle_${PLATFORM}_##BUNDLE_VERSION##.tar.gz"
BUNDLE_TMPDIR="$DATA_DIR/dependencies.fetch"
rm -rf "$BUNDLE_TMPDIR"
mkdir "$BUNDLE_TMPDIR"
# Cache dev bundles in /tmp.
# XXX something more secure is needed in production
CACHED_TARBALL="/tmp/$TARBALL"
if [ ! -r "$CACHED_TARBALL" ]; then
# Duplicated from 'meteor' script in root of repository
TEMP_TARBALL="${CACHED_TARBALL}.tmp.${RANDOM}"
curl -s "https://d3sqy0vbqsdhku.cloudfront.net/$TARBALL" >"$TEMP_TARBALL"
mv "$TEMP_TARBALL" "$CACHED_TARBALL"
fi
tar -xzf "$CACHED_TARBALL" -C "$BUNDLE_TMPDIR"
# Delete old dev bundle and rename the new one on top of it.
# XXX probably we can just trust that dependencies from last time are good
DEPS_DIR="$DATA_DIR/dependencies"
rm -rf "$DEPS_DIR"
mv "$BUNDLE_TMPDIR" "$DEPS_DIR"
trap - EXIT
set +e
# If there are global node_modules in the bundle, remove them, since
# they override NODE_PATH. Why are these present? Well, 'meteor
# bundle' historically embeds them to give you a self-contained
# bundle. But that never worked very well, because you'd get the
# version for the arch you built on, and you'd have to manually
# rebuild the binary modules (node-fibers) on the target system. It is
# not ideal to have the bundle modify itself (much better for it to be
# immutable) but it'll do for now.
rm -rf node_modules 2>/dev/null || true
export NODE_PATH="$DEPS_DIR/lib/node_modules"
exec "$DEPS_DIR/bin/node" ##RUN_FILE## ##IMAGE## "$@"

View File

@@ -1,5 +0,0 @@
DevBundleFetcher = {
script: function () {
return Assets.getText("dev-bundle");
}
};

View File

@@ -1,9 +0,0 @@
Package.describe({
summary: "A shell script for downloading the Meteor dev bundle",
version: "1.0.2"
});
Package.onUse(function (api) {
api.export('DevBundleFetcher', 'server');
api.addFiles(['dev-bundle', 'dev-bundle.js'], ['server']);
});

View File

@@ -31,29 +31,14 @@ var makePool = function (mailUrlString) {
return pool;
};
// We construct smtpPool at the first call to Email.send, so that
// Meteor.startup code can set $MAIL_URL.
var smtpPoolFuture = new Future;;
var configured = false;
var getPool = function () {
// We check MAIL_URL in case someone else set it in Meteor.startup code.
if (!configured) {
configured = true;
AppConfig.configurePackage('email', function (config) {
// XXX allow reconfiguration when the app config changes
if (smtpPoolFuture.isResolved())
return;
var url = config.url || process.env.MAIL_URL;
var pool = null;
if (url)
pool = makePool(url);
smtpPoolFuture.return(pool);
});
}
return smtpPoolFuture.wait();
};
var getPool = _.once(function () {
// We delay this check until the first call to Email.send, in case someone
// set process.env.MAIL_URL in startup code.
var url = process.env.MAIL_URL;
if (! url)
return null;
return makePool(url);
});
var next_devmode_mail_id = 0;
var output_stream = process.stdout;

View File

@@ -12,7 +12,6 @@ Npm.depends({
Package.onUse(function (api) {
api.use('underscore', 'server');
api.use('application-configuration');
api.export('Email', 'server');
api.export('EmailTest', 'server', {testOnly: true});
api.addFiles('email.js', 'server');

View File

@@ -1 +0,0 @@
.build*

View File

@@ -1,2 +0,0 @@
This was an internal Meteor package used for interacting with internal prototype
systems that no longer exist.

View File

@@ -1,244 +0,0 @@
var fs = Npm.require('fs');
var Future = Npm.require('fibers/future');
var MONITOR_INTERVAL = 5*1000; // every 5 seconds
/**
* Follower.connect() replaces DDP.connect() for connecting to DDP services that
* implement a leadership set. The follower connection tries to keep connected
* to the leader, and fails over as the leader changes.
*
* Options: {
* group: The name of the leadership group to connect to. Default "package.leadershipLivedata"
* }
*
* A Follower connection implements the following interfaces over and above a
* normal DDP connection:
*
* onLost(callback): calls callback when the library considers itself to have
* tried all its known options for the leadership group.
*
* onFound(callback): Called when the follower was previously lost, but has now
* successfully connected to something in the right leadership group.
*/
Follower = {
connect: function (urlSet, options) {
var electorTries;
options = _.extend({
group: "package.leadershipLivedata"
}, options);
// start each elector as untried/assumed connectable.
var makeElectorTries = function (urlSet) {
electorTries = {};
if (typeof urlSet === 'string') {
urlSet = _.map(urlSet.split(','), function (url) {return url.trim();});
}
_.each(urlSet, function (url) {
electorTries[url] = 0;
});
};
makeElectorTries(urlSet);
var tryingUrl = null;
var outstandingGetElectorate = false;
var conn = null;
var prevReconnect = null;
var prevDisconnect = null;
var prevApply = null;
var leader = null;
var connectedTo = null;
var intervalHandle = null;
// Used to defer all method calls until we're sure that we connected to the
// right leadership group.
var connectedToLeadershipGroup = new Future();
var lost = false;
var lostCallbacks = [];
var foundCallbacks = [];
var findFewestTries = function () {
var min = 10000;
var minElector = null;
_.each(electorTries, function (tries, elector) {
if (tries < min) {
min = tries;
minElector = elector;
}
});
if (min > 1 && !lost) {
// we've tried everything once; we just became lost.
lost = true;
_.each(lostCallbacks, function (f) { f(); });
}
return minElector;
};
var updateElectorate = function (res) {
leader = res.leader;
electorTries = {};
_.each(res.electorate, function (elector) {
electorTries[elector] = 0; // verified that this is in the current elector set.
});
};
var tryElector = function (url) {
if (tryingUrl) {
electorTries[tryingUrl]++;
}
url = url || findFewestTries();
//console.log("trying", url, electorTries, tryingUrl, process.env.GALAXY_JOB);
// Don't keep trying the same url as fast as we can if it's not working.
if (electorTries[url] > 2) {
Meteor._sleepForMs(3 * 1000);
}
if (conn) {
prevReconnect.apply(conn, [{
url: url
}]);
} else {
conn = DDP.connect(url, options);
prevReconnect = conn.reconnect;
prevDisconnect = conn.disconnect;
prevApply = conn.apply;
}
tryingUrl = url;
if (!outstandingGetElectorate) {
outstandingGetElectorate = true;
conn.call('getElectorate', options.group, function (err, res) {
outstandingGetElectorate = false;
connectedTo = tryingUrl;
if (err) {
tryElector();
return;
}
if (!_.contains(res.electorate, connectedTo)) {
Log.warn("electorate " + res.electorate + " does not contain " + connectedTo);
}
tryingUrl = null;
if (! connectedToLeadershipGroup.isResolved()) {
connectedToLeadershipGroup["return"]();
}
// we got an answer! Connected!
electorTries[url] = 0;
if (res.leader === connectedTo) {
// we're good.
if (lost) {
// we're found.
lost = false;
_.each(foundCallbacks, function (f) { f(); });
}
} else {
// let's connect to the leader anyway, if we think it
// is connectable.
if (electorTries[res.leader] == 0) {
tryElector(res.leader);
} else {
// XXX: leader is probably down, we're probably going to elect
// soon. Wait for the next round.
}
}
updateElectorate(res);
});
}
};
tryElector();
var checkConnection = function () {
if (conn.status().status !== 'connected' || connectedTo !== leader) {
tryElector();
} else {
conn.call('getElectorate', options.group, function (err, res) {
if (err) {
electorTries[connectedTo]++;
tryElector();
} else if (res.leader !== leader) {
// update the electorate, and then definitely try to connect to the leader.
updateElectorate(res);
tryElector(res.leader);
} else {
if (! connectedToLeadershipGroup.isResolved()) {
connectedToLeadershipGroup["return"]();
}
//console.log("updating electorate with", res);
updateElectorate(res);
}
});
}
};
var monitorConnection = function () {
return Meteor.setInterval(checkConnection, MONITOR_INTERVAL);
};
intervalHandle = monitorConnection();
conn.disconnect = function () {
if (intervalHandle)
Meteor.clearInterval(intervalHandle);
intervalHandle = null;
prevDisconnect.apply(conn);
};
conn.reconnect = function () {
if (!intervalHandle)
intervalHandle = monitorConnection();
if (arguments[0] && arguments[0].url) {
makeElectorTries(arguments[0].url);
tryElector();
} else {
prevReconnect.apply(conn, arguments);
}
};
conn.getUrl = function () {
return _.keys(electorTries).join(',');
};
conn.tries = function () {
return electorTries;
};
// Assumes that `call` is implemented in terms of `apply`. All method calls
// should be deferred until we are sure we've connected to the right
// leadership group.
conn.apply = function (/* arguments */) {
var args = _.toArray(arguments);
if (typeof args[args.length-1] === 'function') {
// this needs to be independent of this fiber if there is a callback.
Meteor.defer(function () {
connectedToLeadershipGroup.wait();
return prevApply.apply(conn, args);
});
return null; // if there is a callback, the return value is not used
} else {
connectedToLeadershipGroup.wait();
return prevApply.apply(conn, args);
}
};
conn.onLost = function (callback) {
lostCallbacks.push(callback);
};
conn.onFound = function (callback) {
foundCallbacks.push(callback);
};
return conn;
}
};

View File

@@ -1,10 +0,0 @@
Package.describe({
summary: "Maintain a connection to the leader of an election set",
version: '1.0.3'
});
Package.onUse(function (api) {
api.use(['logging', 'underscore', 'ddp', 'ejson']);
api.addFiles(['follower.js'], 'server');
api.export('Follower');
});

View File

@@ -5,26 +5,7 @@ Meteor = {
Meteor.settings = {};
if (process.env.APP_CONFIG) {
// put settings from the app configuration in the settings. Don't depend on
// the Galaxy package for now, to avoid silly loops.
try {
var appConfig = JSON.parse(process.env.APP_CONFIG);
if (!appConfig.settings) {
Meteor.settings = {};
} else if (typeof appConfig.settings === "string") {
Meteor.settings = JSON.parse(appConfig.settings);
} else {
// Old versions of Galaxy may store settings in MongoDB as objects. Newer
// versions store it as strings (so that we aren't restricted to
// MongoDB-compatible objects). This line makes it work on older Galaxies.
// XXX delete this eventually
Meteor.settings = appConfig.settings;
}
} catch (e) {
throw new Error("Settings from APP_CONFIG are not valid JSON: " + process.env.APP_CONFIG);
}
} else if (process.env.METEOR_SETTINGS) {
if (process.env.METEOR_SETTINGS) {
try {
Meteor.settings = JSON.parse(process.env.METEOR_SETTINGS);
} catch (e) {

View File

@@ -1229,7 +1229,7 @@ MongoConnection.prototype._observeChangesTailable = function (
// XXX We probably need to find a better way to expose this. Right now
// it's only used by tests, but in fact you need it in normal
// operation to interact with capped collections (eg, Galaxy uses it).
// operation to interact with capped collections.
MongoInternals.MongoTimestamp = MongoDB.Timestamp;
MongoInternals.Connection = MongoConnection;

View File

@@ -22,7 +22,7 @@ Npm.strip({
Package.onUse(function (api) {
api.use(['random', 'ejson', 'json', 'underscore', 'minimongo', 'logging',
'ddp', 'tracker', 'application-configuration'],
'ddp', 'tracker'],
['client', 'server']);
api.use('check', ['client', 'server']);

View File

@@ -24,22 +24,16 @@ _.extend(MongoInternals.RemoteCollectionDriver.prototype, {
// only require Mongo configuration if it's actually used (eg, not if
// you're only trying to receive data from a remote DDP server.)
MongoInternals.defaultRemoteCollectionDriver = _.once(function () {
var mongoUrl;
var connectionOptions = {};
AppConfig.configurePackage("mongo-livedata", function (config) {
// This will keep running if mongo gets reconfigured. That's not ideal, but
// should be ok for now.
mongoUrl = config.url;
var mongoUrl = process.env.MONGO_URL;
if (config.oplog)
connectionOptions.oplogUrl = config.oplog;
});
if (process.env.MONGO_OPLOG_URL) {
connectionOptions.oplogUrl = process.env.MONGO_OPLOG_URL;
}
// XXX bad error since it could also be set directly in METEOR_DEPLOY_CONFIG
if (! mongoUrl)
throw new Error("MONGO_URL must be set in environment");
return new MongoInternals.RemoteCollectionDriver(mongoUrl, connectionOptions);
});

View File

@@ -1 +0,0 @@
.build*

View File

@@ -1 +0,0 @@
node_modules

View File

@@ -1,7 +0,0 @@
This directory and the files immediately inside it are automatically generated
when you change this package's NPM dependencies. Commit the files in this
directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
so that others run the same versions of sub-dependencies.
You should NOT check in the node_modules directory that Meteor automatically
creates; if you are using git, the .gitignore file tells git to ignore it.

View File

@@ -1,7 +0,0 @@
{
"dependencies": {
"ncp": {
"version": "0.4.2"
}
}
}

View File

@@ -1,2 +0,0 @@
This was an internal Meteor package used for interacting with internal prototype
systems that no longer exist.

View File

@@ -1,12 +0,0 @@
Package.describe({
summary: "A package for translating old bundles into stars",
version: "1.0.5"
});
Package.onUse(function (api) {
api.use(['dev-bundle-fetcher']);
api.export('StarTranslator');
api.addFiles(['translator.js'], 'server');
});
Npm.depends({ncp: "0.4.2"});

View File

@@ -1,112 +0,0 @@
var fs = Npm.require('fs');
var path = Npm.require('path');
var ncp = Npm.require('ncp').ncp;
StarTranslator = {};
// Produces a star version of bundlePath in translatedPath, where bundlePath can
// point to either an old Meteor bundle or a star. Returns the star's manifest.
// bundlePath can equal translatedPath, in which case bundlePath is converted
// directly into a star.
StarTranslator.maybeTranslate = function (bundlePath, translatedPath) {
var self = this;
if (path.resolve(bundlePath) !== path.resolve(translatedPath)) {
var _ncp = Meteor.wrapAsync(ncp);
_ncp(bundlePath, translatedPath);
}
try {
// If the directory contains a star.json file with JSON inside it, then we
// consider it a star. Otherwise we translate it into a star.
var manifest = JSON.parse(fs.readFileSync(path.join(translatedPath,
"star.json"),
'utf8'));
return manifest;
} catch (e) {
return self._translate(translatedPath);
}
};
StarTranslator._translate = function (bundlePath) {
var self = this;
var clientProgPath = path.join(bundlePath, 'client.json');
var serverProgPath = path.join(bundlePath, 'server.sh');
var starPath = path.join(bundlePath, 'star.json');
// Format defined in meteor/tools/bundler.js
var manifest = {
"format": "site-archive-pre1",
"builtBy": "Star translator",
"programs": [
{
"name": "web.browser",
"arch": "web.browser",
"path": "client.json"
},
{
"name": "server",
"arch": self._getArch(),
"path": "server.sh"
}
]
};
self._writeServerProg(bundlePath, serverProgPath);
self._writeClientProg(bundlePath, clientProgPath);
fs.writeFileSync(starPath, JSON.stringify(manifest, null, 2));
return manifest;
};
StarTranslator._writeServerProg = function (bundlePath, serverProgPath) {
var platform = this._getPlatform();
var bundleVersion = this._getBundleVersion(bundlePath);
var runFile = 'main.js';
var serverScript = DevBundleFetcher.script();
// Duplicated from meteor/tools/bundler.js
serverScript = serverScript.replace(/##PLATFORM##/g, platform);
serverScript = serverScript.replace(/##BUNDLE_VERSION##/g, bundleVersion);
serverScript = serverScript.replace(/##RUN_FILE##/g, runFile);
serverScript = serverScript.replace(/##IMAGE##/g, '');
fs.writeFileSync(serverProgPath, serverScript);
fs.chmodSync(serverProgPath, '744');
};
StarTranslator._getArch = function () {
return Meteor.settings.arch;
};
StarTranslator._getPlatform = function () {
var self = this;
// Duplicated from meteor/tools/bundler.js
var archToPlatform = {
'os.linux.x86_32': 'Linux_i686',
'os.linux.x86_64': 'Linux_x86_64',
'os.osx.x86_64': 'Darwin_x86_64'
};
return archToPlatform[self._getArch()];
};
StarTranslator._getBundleVersion = function (bundlePath) {
var version = fs.readFileSync(path.join(bundlePath,
"server", ".bundle_version.txt"),
'utf8');
return version.trim();
};
StarTranslator._writeClientProg = function (bundlePath, clientProgPath) {
var origClientManifest = JSON.parse(fs.readFileSync(path.join(bundlePath,
"app.json"),
'utf8'));
var clientManifest = {
"format": "web-program-pre1",
"manifest": origClientManifest.manifest,
// XXX Haven't updated this for the app.html -> head/body change, but
// surely we don't need to because code in pre-star apps doesn't
// even read this file?
"page": "app.html",
"static": "static",
"staticCacheable": "static_cacheable"
};
fs.writeFileSync(clientProgPath, JSON.stringify(clientManifest, null, 2));
};

View File

@@ -16,9 +16,7 @@ Package.onUse(function (api) {
api.use(['logging', 'underscore', 'routepolicy', 'boilerplate-generator',
'spacebars', 'htmljs', 'blaze', 'webapp-hashing'], 'server');
api.use(['underscore'], 'client');
api.use(['application-configuration', 'follower-livedata'], {
unordered: true
});
// At response serving time, webapp uses browser-policy if it is loaded. If
// browser-policy is loaded, then it must be loaded after webapp
// (browser-policy depends on webapp). So we don't explicitly depend in any

View File

@@ -701,39 +701,6 @@ var runWebAppServer = function () {
httpServer.on('request', WebApp._timeoutAdjustmentRequestCallback);
// For now, handle SIGHUP here. Later, this should be in some centralized
// Meteor shutdown code.
process.on('SIGHUP', Meteor.bindEnvironment(function () {
shuttingDown = true;
// tell others with websockets open that we plan to close this.
// XXX: Eventually, this should be done with a standard meteor shut-down
// logic path.
httpServer.emit('meteor-closing');
httpServer.close(Meteor.bindEnvironment(function () {
if (proxy) {
try {
proxy.call('removeBindingsForJob', process.env.GALAXY_JOB);
} catch (e) {
Log.error("Error removing bindings: " + e.message);
process.exit(1);
}
}
process.exit(0);
}, "On http server close failed"));
// Ideally we will close before this hits.
Meteor.setTimeout(function () {
Log.warn("Closed by SIGHUP but one or more HTTP requests may not have finished.");
process.exit(1);
}, 5000);
}, function (err) {
console.log(err);
process.exit(1);
}));
// start up app
_.extend(WebApp, {
connectHandlers: packageAndAppHandlers,
@@ -759,10 +726,6 @@ var runWebAppServer = function () {
// middlewares and update __meteor_runtime_config__, then keep going to set up
// actually serving HTML.
main = function (argv) {
// main happens post startup hooks, so we don't need a Meteor.startup() to
// ensure this happens after the galaxy package is loaded.
var AppConfig = Package["application-configuration"].AppConfig;
WebAppInternals.generateBoilerplate();
// only start listening after all the startup code has run.
@@ -772,49 +735,6 @@ var runWebAppServer = function () {
httpServer.listen(localPort, localIp, Meteor.bindEnvironment(function() {
if (process.env.METEOR_PRINT_ON_LISTEN)
console.log("LISTENING"); // must match run-app.js
var proxyBinding;
AppConfig.configurePackage('webapp', function (configuration) {
if (proxyBinding)
proxyBinding.stop();
if (configuration && configuration.proxy) {
// TODO: We got rid of the place where this checks the app's
// configuration, because this wants to be configured for some things
// on a per-job basis. Discuss w/ teammates.
proxyBinding = AppConfig.configureService(
"proxy",
"pre0",
function (proxyService) {
if (proxyService && ! _.isEmpty(proxyService)) {
var proxyConf;
// XXX Figure out a per-job way to specify bind location
// (besides hardcoding the location for ADMIN_APP jobs).
if (process.env.ADMIN_APP) {
var bindPathPrefix = "";
if (process.env.GALAXY_APP !== "panel") {
bindPathPrefix = "/" + bindPathPrefix +
encodeURIComponent(
process.env.GALAXY_APP
).replace(/\./g, '_');
}
proxyConf = {
bindHost: process.env.GALAXY_NAME,
bindPathPrefix: bindPathPrefix,
requiresAuth: true
};
} else {
proxyConf = configuration.proxy;
}
Log("Attempting to bind to proxy at " +
proxyService);
WebAppInternals.bindToProxy(_.extend({
proxyEndpoint: proxyService
}, proxyConf));
}
}
);
}
});
var callbacks = onListeningCallbacks;
onListeningCallbacks = null;
@@ -830,289 +750,6 @@ var runWebAppServer = function () {
};
var proxy;
WebAppInternals.bindToProxy = function (proxyConfig) {
var securePort = proxyConfig.securePort || 4433;
var insecurePort = proxyConfig.insecurePort || 8080;
var bindPathPrefix = proxyConfig.bindPathPrefix || "";
// XXX also support galaxy-based lookup
if (!proxyConfig.proxyEndpoint)
throw new Error("missing proxyEndpoint");
if (!proxyConfig.bindHost)
throw new Error("missing bindHost");
if (!process.env.GALAXY_JOB)
throw new Error("missing $GALAXY_JOB");
if (!process.env.GALAXY_APP)
throw new Error("missing $GALAXY_APP");
if (!process.env.LAST_START)
throw new Error("missing $LAST_START");
// XXX rename pid argument to bindTo.
// XXX factor out into a 'getPid' function in a 'galaxy' package?
var pid = {
job: process.env.GALAXY_JOB,
lastStarted: +(process.env.LAST_START),
app: process.env.GALAXY_APP
};
var myHost = os.hostname();
WebAppInternals.usingDdpProxy = true;
// This is run after packages are loaded (in main) so we can use
// Follower.connect.
if (proxy) {
// XXX the concept here is that our configuration has changed and
// we have connected to an entirely new follower set, which does
// not have the state that we set up on the follower set that we
// were previously connected to, and so we need to recreate all of
// our bindings -- analogous to getting a SIGHUP and rereading
// your configuration file. so probably this should actually tear
// down the connection and make a whole new one, rather than
// hot-reconnecting to a different URL.
proxy.reconnect({
url: proxyConfig.proxyEndpoint
});
} else {
proxy = Package["follower-livedata"].Follower.connect(
proxyConfig.proxyEndpoint, {
group: "proxy"
}
);
}
var route = process.env.ROUTE;
var ourHost = route.split(":")[0];
var ourPort = +route.split(":")[1];
var outstanding = 0;
var startedAll = false;
var checkComplete = function () {
if (startedAll && ! outstanding)
Log("Bound to proxy.");
};
var makeCallback = function () {
outstanding++;
return function (err) {
if (err)
throw err;
outstanding--;
checkComplete();
};
};
// for now, have our (temporary) requiresAuth flag apply to all
// routes created by this process.
var requiresDdpAuth = !! proxyConfig.requiresAuth;
var requiresHttpAuth = (!! proxyConfig.requiresAuth) &&
(pid.app !== "panel" && pid.app !== "auth");
// XXX a current limitation is that we treat securePort and
// insecurePort as a global configuration parameter -- we assume
// that if the proxy wants us to ask for 8080 to get port 80 traffic
// on our default hostname, that's the same port that we would use
// to get traffic on some other hostname that our proxy listens
// for. Likewise, we assume that if the proxy can receive secure
// traffic for our domain, it can assume secure traffic for any
// domain! Hopefully this will get cleaned up before too long by
// pushing that logic into the proxy service, so we can just ask for
// port 80.
// XXX BUG: if our configuration changes, and bindPathPrefix
// changes, it appears that we will not remove the routes derived
// from the old bindPathPrefix from the proxy (until the process
// exits). It is not actually normal for bindPathPrefix to change,
// certainly not without a process restart for other reasons, but
// it'd be nice to fix.
_.each(routes, function (route) {
var parsedUrl = url.parse(route.url, /* parseQueryString */ false,
/* slashesDenoteHost aka workRight */ true);
if (parsedUrl.protocol || parsedUrl.port || parsedUrl.search)
throw new Error("Bad url");
parsedUrl.host = null;
parsedUrl.path = null;
if (! parsedUrl.hostname) {
parsedUrl.hostname = proxyConfig.bindHost;
if (! parsedUrl.pathname)
parsedUrl.pathname = "";
if (! parsedUrl.pathname.indexOf("/") !== 0) {
// Relative path
parsedUrl.pathname = bindPathPrefix + parsedUrl.pathname;
}
}
var version = "";
var AppConfig = Package["application-configuration"].AppConfig;
version = AppConfig.getStarForThisJob() || "";
var parsedDdpUrl = _.clone(parsedUrl);
parsedDdpUrl.protocol = "ddp";
// Node has a hardcoded list of protocols that get '://' instead
// of ':'. ddp needs to be added to that whitelist. Until then, we
// can set the undocumented attribute 'slashes' to get the right
// behavior. It's not clear whether than is by design or accident.
parsedDdpUrl.slashes = true;
parsedDdpUrl.port = '' + securePort;
var ddpUrl = url.format(parsedDdpUrl);
var proxyToHost, proxyToPort, proxyToPathPrefix;
if (! _.has(route, 'forwardTo')) {
proxyToHost = ourHost;
proxyToPort = ourPort;
proxyToPathPrefix = parsedUrl.pathname;
} else {
var parsedFwdUrl = url.parse(route.forwardTo, false, true);
if (! parsedFwdUrl.hostname || parsedFwdUrl.protocol)
throw new Error("Bad forward url");
proxyToHost = parsedFwdUrl.hostname;
proxyToPort = parseInt(parsedFwdUrl.port || "80");
proxyToPathPrefix = parsedFwdUrl.pathname || "";
}
if (route.ddp) {
proxy.call('bindDdp', {
pid: pid,
bindTo: {
ddpUrl: ddpUrl,
insecurePort: insecurePort
},
proxyTo: {
tags: [version],
host: proxyToHost,
port: proxyToPort,
pathPrefix: proxyToPathPrefix + '/websocket'
},
requiresAuth: requiresDdpAuth
}, makeCallback());
}
if (route.http) {
proxy.call('bindHttp', {
pid: pid,
bindTo: {
host: parsedUrl.hostname,
port: insecurePort,
pathPrefix: parsedUrl.pathname
},
proxyTo: {
tags: [version],
host: proxyToHost,
port: proxyToPort,
pathPrefix: proxyToPathPrefix
},
requiresAuth: requiresHttpAuth
}, makeCallback());
// Only make the secure binding if we've been told that the
// proxy knows how terminate secure connections for us (has an
// appropriate cert, can bind the necessary port..)
if (proxyConfig.securePort !== null) {
proxy.call('bindHttp', {
pid: pid,
bindTo: {
host: parsedUrl.hostname,
port: securePort,
pathPrefix: parsedUrl.pathname,
ssl: true
},
proxyTo: {
tags: [version],
host: proxyToHost,
port: proxyToPort,
pathPrefix: proxyToPathPrefix
},
requiresAuth: requiresHttpAuth
}, makeCallback());
}
}
});
startedAll = true;
checkComplete();
};
// (Internal, unsupported interface -- subject to change)
//
// Listen for HTTP and/or DDP traffic and route it somewhere. Only
// takes effect when using a proxy service.
//
// 'url' is the traffic that we want to route, interpreted relative to
// the default URL where this app has been told to serve itself. It
// may not have a scheme or port, but it may have a host and a path,
// and if no host is provided the path need not be absolute. The
// following cases are possible:
//
// //somehost.com
// All incoming traffic for 'somehost.com'
// //somehost.com/foo/bar
// All incoming traffic for 'somehost.com', but only when
// the first two path components are 'foo' and 'bar'.
// /foo/bar
// Incoming traffic on our default host, but only when the
// first two path components are 'foo' and 'bar'.
// foo/bar
// Incoming traffic on our default host, but only when the path
// starts with our default path prefix, followed by 'foo' and
// 'bar'.
//
// (Yes, these scheme-less URLs that start with '//' are legal URLs.)
//
// You can select either DDP traffic, HTTP traffic, or both. Both
// secure and insecure traffic will be gathered (assuming the proxy
// service is capable, eg, has appropriate certs and port mappings).
//
// With no 'forwardTo' option, the traffic is received by this process
// for service by the hooks in this 'webapp' package. The original URL
// is preserved (that is, if you bind "/a", and a user visits "/a/b",
// the app receives a request with a path of "/a/b", not a path of
// "/b").
//
// With 'forwardTo', the process is instead sent to some other remote
// host. The URL is adjusted by stripping the path components in 'url'
// and putting the path components in the 'forwardTo' URL in their
// place. For example, if you forward "//somehost/a" to
// "//otherhost/x", and the user types "//somehost/a/b" into their
// browser, then otherhost will receive a request with a Host header
// of "somehost" and a path of "/x/b".
//
// The routing continues until this process exits. For now, all of the
// routes must be set up ahead of time, before the initial
// registration with the proxy. Calling addRoute from the top level of
// your JS should do the trick.
//
// When multiple routes are present that match a given request, the
// most specific route wins. When routes with equal specificity are
// present, the proxy service will distribute the traffic between
// them.
//
// options may be:
// - ddp: if true, the default, include DDP traffic. This includes
// both secure and insecure traffic, and both websocket and sockjs
// transports.
// - http: if true, the default, include HTTP/HTTPS traffic.
// - forwardTo: if provided, should be a URL with a host, optional
// path and port, and no scheme (the scheme will be derived from the
// traffic type; for now it will always be a http or ws connection,
// never https or wss, but we could add a forwardSecure flag to
// re-encrypt).
var routes = [];
WebAppInternals.addRoute = function (url, options) {
options = _.extend({
ddp: true,
http: true
}, options || {});
if (proxy)
// In the future, lift this restriction
throw new Error("Too late to add routes");
routes.push(_.extend({ url: url }, options));
};
// Receive traffic on our default URL.
WebAppInternals.addRoute("");
runWebAppServer();

View File

@@ -181,7 +181,8 @@ var getSession = function (sessionData, domain) {
// types:
// - "meteor-account": a login to your Meteor Account
// - "galaxy": a login to a Galaxy
// We previously used:
// - "galaxy": a login to a legacy Galaxy prototype server
var ensureSessionType = function (session, type) {
if (! _.has(session, 'type'))
session.type = type;
@@ -259,53 +260,6 @@ var removePendingRevoke = function (domain, tokenIds) {
writeSessionData(data);
};
var tryRevokeGalaxyTokens = function (domain, tokenIds, options) {
var oauthInfo = fetchGalaxyOAuthInfo(domain, options.timeout);
if (oauthInfo) {
url = oauthInfo.revokeUri;
} else {
return false;
}
try {
var result = httpHelpers.request({
url: url,
method: "POST",
form: {
tokenId: tokenIds.join(',')
},
useSessionHeader: true,
timeout: options.timeout
});
} catch (e) {
// most likely we don't have a net connection
return false;
}
var response = result.response;
if (response.statusCode === 200 &&
response.body) {
try {
var body = JSON.parse(response.body);
if (body.tokenRevoked) {
// Server confirms that the tokens have been revoked. Checking for a
// `tokenRevoked` key in the response confirms that we hit an actual
// galaxy auth server that understands that we were trying to revoke some
// tokens, not just a random URL that happened to return a 200
// response.
// (Be careful to reread session data in case httpHelpers changed it)
removePendingRevoke(domain, tokenIds);
}
} catch (e) {
return false;
}
return true;
} else {
return false;
}
};
// If there are any logged out (pendingRevoke) tokens that haven't
// been sent to the server for revocation yet, try to send
// them. Reads the session file and then writes it back out to
@@ -364,9 +318,10 @@ var tryRevokeOldTokens = function (options) {
}
return;
} else if (session.type === "galaxy") {
if (! tryRevokeGalaxyTokens(domain, tokenIds, options)) {
logoutFailWarning(domain);
}
// These are tokens from a legacy Galaxy prototype, which cannot be
// revoked (because the prototype no longer exists), but we can at least
// remove them from the file.
removePendingRevoke(domain, tokenIds);
} else {
// don't know how to revoke tokens of this type
logoutFailWarning(domain);
@@ -375,40 +330,6 @@ var tryRevokeOldTokens = function (options) {
});
};
// Sends a request to https://<galaxyName>:<DISCOVERY_PORT> to find out the
// galaxy's OAuth client id and redirect_uri that should be used for
// authorization codes for this galaxy. Returns an object with keys
// 'oauthClientId', 'redirectUri', and 'revokeUri', or null if the
// request failed.
//
// 'timeout' is an optional request timeout in milliseconds.
var fetchGalaxyOAuthInfo = function (galaxyName, timeout) {
var galaxyAuthUrl = 'https://' + galaxyName + ':' +
config.getDiscoveryPort() + '/_GALAXYAUTH_';
try {
var result = httpHelpers.request({
url: galaxyAuthUrl,
json: true,
// on by default in our version of request, but just in case
strictSSL: true,
followRedirect: false,
timeout: timeout || 5000
});
} catch (e) {
return null;
}
if (result.response.statusCode === 200 &&
result.body &&
result.body.oauthClientId &&
result.body.redirectUri &&
result.body.revokeUri) {
return result.body;
} else {
return null;
}
};
var sendAuthorizeRequest = function (clientId, redirectUri, state) {
var authCodeUrl = config.getOauthUrl() + "/authorize?" +
querystring.stringify({
@@ -506,77 +427,6 @@ var oauthFlow = function (conn, options) {
}
};
// Uses meteor accounts to log in to the specified galaxy. Returns an
// object with keys `token` and `tokenId` if the login was
// successful. If an error occurred, returns one of:
// { error: 'access-denied' }
// { error: 'no-galaxy' }
// { error: 'no-account-server' }
var logInToGalaxy = function (galaxyName) {
var oauthInfo = fetchGalaxyOAuthInfo(galaxyName);
if (! oauthInfo) {
return { error: 'no-galaxy' };
}
var galaxyClientId = oauthInfo.oauthClientId;
var galaxyRedirect = oauthInfo.redirectUri;
// If the redirect URI is not in the DNS namespace that belongs to the
// Galaxy, then something is wrong.
if (url.parse(galaxyRedirect).hostname !== galaxyName) {
// XXX It's more like 'bad-galaxy' than 'no-galaxy'.
return { error: 'no-galaxy' };
}
// Ask the accounts server for an authorization code.
var crypto = require('crypto');
var session = crypto.randomBytes(16).toString('hex');
var stateInfo = { session: session };
var authorizeResult;
try {
authorizeResult = sendAuthorizeRequest(
galaxyClientId,
galaxyRedirect,
encodeURIComponent(JSON.stringify(stateInfo))
);
} catch (err) {
return { error: err.message };
}
// Ask the galaxy to log us in with our auth code.
try {
var galaxyResult = httpHelpers.request({
url: authorizeResult.location,
method: 'GET',
strictSSL: true,
headers: {
cookie: 'GALAXY_OAUTH_SESSION=' + session +
'; GALAXY_USER_AGENT_TOOL=' +
encodeURIComponent(JSON.stringify(utils.getAgentInfo()))
}
});
var body = JSON.parse(galaxyResult.body);
} catch (e) {
return { error: (body && body.error) || 'no-galaxy' };
}
var response = galaxyResult.response;
// 'access-denied' isn't exactly right because it's possible that the galaxy
// went down since our last request, but close enough.
if (response.statusCode !== 200 ||
! body ||
! _.has(galaxyResult.setCookie, 'GALAXY_AUTH'))
return { error: (body && body.error) || 'access-denied' };
return {
token: galaxyResult.setCookie.GALAXY_AUTH,
tokenId: body.tokenId
};
};
// Prompt the user for a password, and then log in. Returns true if a
// successful login was accomplished, else false.
//
@@ -681,11 +531,9 @@ exports.loginCommand = withAccountsConnection(function (options,
config.printUniverseBanner();
var data = readSessionData();
var galaxy = options.galaxy;
if (! galaxy &&
(! getSession(data, config.getAccountsDomain()).token ||
options.overwriteExistingToken)) {
if (! getSession(data, config.getAccountsDomain()).token ||
options.overwriteExistingToken) {
var loginOptions = {};
if (options.email) {
@@ -707,43 +555,13 @@ exports.loginCommand = withAccountsConnection(function (options,
}
}
// XXX Make the galaxy login not do a login if there is an existing token, just like MA
if (galaxy) {
var galaxyLoginResult = logInToGalaxy(galaxy);
if (galaxyLoginResult.error) {
// XXX add human readable error messages
var failedLoginMsg = "\nLogin to ' + galaxy + ' failed. ";
if (galaxyLoginResult.error === 'unauthorized') {
Console.error(
failedLoginMsg + 'You are not authorized for this galaxy.');
} else if (galaxyLoginResult.error === 'no_oauth_server') {
Console.error(
failedLoginMsg + 'The galaxy could not contact Meteor Accounts.');
} else if (galaxyLoginResult.error === 'no_identity') {
Console.error(
failedLoginMsg + 'Your login information could not be found.');
} else {
Console.error(failedLoginMsg + 'Error: ' + galaxyLoginResult.error );
}
return 1;
}
data = readSessionData(); // be careful to reread data file after RPC
var session = getSession(data, galaxy);
ensureSessionType(session, "galaxy");
session.token = galaxyLoginResult.token;
session.tokenId = galaxyLoginResult.tokenId;
writeSessionData(data);
}
tryRevokeOldTokens({ firstTry: true, connection: connection });
data = readSessionData();
Console.error();
Console.error("Logged in" + (galaxy ? " to " + galaxy : "") +
(currentUsername(data) ?
" as " + currentUsername(data) : "") + ".",
"Thanks for being a Meteor developer!");
Console.error("Logged in" +
(currentUsername(data) ? " as " + currentUsername(data) : "") +
". Thanks for being a Meteor developer!");
return 0;
});

View File

@@ -36,10 +36,9 @@
// running in standalone mode (after setting appropriate environment
// variables as documented in README)
//
// /server/.bundle_version.txt: contains the dev_bundle version that
// legacy (read: current) Galaxy version read in order to set
// NODE_PATH to point to arch-specific builds of binary node modules
// (primarily this is for node-fibers)
// /server/.bundle_version.txt: contains the dev_bundle version that the meteor
// deploy server reads in order to set NODE_PATH to point to arch-specific
// builds of binary node modules
//
// XXX in the future one program (which must be a server-type
// architecture) will be designated as the 'init' program. The
@@ -1648,11 +1647,6 @@ _.extend(ServerTarget.prototype, {
write: function (builder, options) {
var self = this;
// Pick a start script name
// XXX base it on the name of the target
var scriptName = 'start.sh';
builder.reserve(scriptName);
// This is where the dev_bundle will be downloaded and unpacked
builder.reserve('dependencies');
@@ -1718,21 +1712,10 @@ _.extend(ServerTarget.prototype, {
return;
}
var devBundleVersion =
files.readFile(
files.pathJoin(files.getDevBundle(), '.bundle_version.txt'), 'utf8');
devBundleVersion = devBundleVersion.split('\n')[0];
var Packages = isopackets.load('dev-bundle-fetcher');
var script = Packages["dev-bundle-fetcher"].DevBundleFetcher.script();
script = script.replace(/##PLATFORM##/g, platform);
script = script.replace(/##BUNDLE_VERSION##/g, devBundleVersion);
script = script.replace(/##IMAGE##/g, imageControlFile);
script = script.replace(/##RUN_FILE##/g, 'boot.js');
builder.write(scriptName, { data: new Buffer(script, 'utf8'),
executable: true });
return scriptName;
// Nothing actually pays attention to the `path` field for a server program
// in star.json any more, so it might as well be boot.js. (It used to be
// start.sh, a script included for the legacy Galaxy prototype.)
return 'boot.js';
}
});
@@ -1793,7 +1776,6 @@ var writeTargetToPath = function (name, target, outputPath, options) {
// options:
// - includeNodeModulesSymlink: bool
// - builtBy: vanity identification string to write into metadata
// - controlProgram: name of the control program (should be a target name)
// - releaseName: The Meteor release version
// - getRelativeTargetPath: see doc at ServerTarget.write
var writeSiteArchive = function (targets, outputPath, options) {
@@ -1804,25 +1786,19 @@ var writeSiteArchive = function (targets, outputPath, options) {
symlink: options.includeNodeModulesSymlink
});
if (options.controlProgram && ! (options.controlProgram in targets))
throw new Error("controlProgram '" + options.controlProgram +
"' is not the name of a target?");
try {
var json = {
format: "site-archive-pre1",
builtBy: options.builtBy,
programs: [],
control: options.controlProgram || undefined,
meteorRelease: options.releaseName
};
// Tell Galaxy what version of the dependency kit we're using, so
// it can load the right modules. (Include this even if we copied
// or symlinked a node_modules, since that's probably enough for
// it to work in spite of the presence of node_modules for the
// wrong arch). The place we stash this is grody for temporary
// reasons of backwards compatibility.
// Tell the deploy server what version of the dependency kit we're using, so
// it can load the right modules. (Include this even if we copied or
// symlinked a node_modules, since that's probably enough for it to work in
// spite of the presence of node_modules for the wrong arch). The place we
// stash this is grody for temporary reasons of backwards compatibility.
builder.write(files.pathJoin('server', '.bundle_version.txt'), {
file: files.pathJoin(files.getDevBundle(), '.bundle_version.txt')
});
@@ -1868,7 +1844,6 @@ var writeSiteArchive = function (targets, outputPath, options) {
json.programs.push(writeTargetToPath(name, target, builder.buildPath, {
includeNodeModulesSymlink: options.includeNodeModulesSymlink,
builtBy: options.builtBy,
controlProgram: options.controlProgram,
releaseName: options.releaseName,
getRelativeTargetPath: options.getRelativeTargetPath
}));
@@ -1925,9 +1900,6 @@ var writeSiteArchive = function (targets, outputPath, options) {
* - hasCachedBundle: true if we already have a cached bundle stored in
* /build. When true, we only build the new client targets in the bundle.
*
* - requireControlProgram: true if we need to include a "ctl" program in
* the bundle. This is required for an old prototype of Galaxy.
*
* Returns an object with keys:
* - errors: A buildmessage.MessageSet, or falsy if bundling succeeded.
* - serverWatchSet: Information about server files and paths that were
@@ -2044,15 +2016,6 @@ exports.bundle = function (options) {
targets.server = server;
}
// Create a "control program". This is required for an old version of
// Galaxy.
var controlProgram = null;
if (options.requireControlProgram) {
var target = makeServerTarget("ctl");
targets["ctl"] = target;
controlProgram = "ctl";
}
// Hack to let servers find relative paths to clients. Should find
// another solution eventually (probably some kind of mount
// directive that mounts the client bundle in the server at runtime)
@@ -2076,7 +2039,6 @@ exports.bundle = function (options) {
var writeOptions = {
includeNodeModulesSymlink: includeNodeModulesSymlink,
builtBy: builtBy,
controlProgram: controlProgram,
releaseName: releaseName,
getRelativeTargetPath: getRelativeTargetPath
};

View File

@@ -23,8 +23,8 @@ var execFileSync = require('./utils.js').execFileSync;
var Console = require('./console.js').Console;
var projectContextModule = require('./project-context.js');
// The architecture used by Galaxy servers; it's the architecture used
// by 'meteor deploy'.
// The architecture used by MDG's hosted servers; it's the architecture used by
// 'meteor deploy'.
var DEPLOY_ARCH = 'os.linux.x86_64';
// The default port that the development server listens on.
@@ -55,14 +55,6 @@ var qualifySitename = function (site) {
return site;
};
// Given a (non necessarily fully qualified) site name from the
// command line, return true if the site is hosted by a Galaxy, else
// false.
var hostedWithGalaxy = function (site) {
var site = qualifySitename(site);
return !! require('./deploy-galaxy.js').discoverGalaxy(site);
};
// Display a message showing valid Meteor architectures.
var showInvalidArchMsg = function (arch) {
Console.info("Invalid architecture: " + arch);
@@ -917,13 +909,8 @@ main.registerCommand({
var site = qualifySitename(options.args[0]);
config.printUniverseBanner();
if (hostedWithGalaxy(site)) {
var deployGalaxy = require('./deploy-galaxy.js');
mongoUrl = deployGalaxy.temporaryMongoUrl(site);
} else {
mongoUrl = deploy.temporaryMongoUrl(site);
usedMeteorAccount = true;
}
mongoUrl = deploy.temporaryMongoUrl(site);
usedMeteorAccount = true;
if (! mongoUrl)
// temporaryMongoUrl() will have printed an error message
@@ -997,14 +984,9 @@ main.registerCommand({
'delete': { type: Boolean, short: 'D' },
debug: { type: Boolean },
settings: { type: String },
star: { type: String },
// No longer supported, but we still parse it out so that we can
// print a custom error message.
password: { type: String },
// Shouldn't be documented until the Galaxy release. Marks the
// application as an admin app, so that it will be available in
// Galaxy admin interface.
admin: { type: Boolean },
// Override architecture to deploy whatever stuff we have locally, even if
// it contains binary packages that should be incompatible. A hack to allow
// people to deploy from checkout or do other weird shit. We are not
@@ -1012,42 +994,24 @@ main.registerCommand({
'override-architecture-with-local' : { type: Boolean }
},
requiresApp: function (options) {
return options.delete || options.star ? false : true;
return ! options.delete;
},
catalogRefresh: new catalog.Refresh.Never()
}, function (options) {
var site = qualifySitename(options.args[0]);
config.printUniverseBanner();
var useGalaxy = hostedWithGalaxy(site);
var deployGalaxy;
if (options.delete) {
if (useGalaxy) {
deployGalaxy = require('./deploy-galaxy.js');
return deployGalaxy.deleteApp(site);
} else {
return deploy.deleteApp(site);
}
return deploy.deleteApp(site);
}
if (options.password) {
if (useGalaxy) {
Console.error("Galaxy does not support --password.");
} else {
Console.error(
"Setting passwords on apps is no longer supported. Now there are " +
Console.error(
"Setting passwords on apps is no longer supported. Now there are " +
"user accounts and your apps are associated with your account so " +
"that only you (and people you designate) can access them. See the " +
Console.command("'meteor claim'") + " and " +
Console.command("'meteor authorized'") + " commands.");
}
return 1;
}
var starball = options.star;
if (starball && ! useGalaxy) {
// XXX it would be nice to support this for non-Galaxy deploys too
Console.error("--star: only supported when deploying to Galaxy.");
return 1;
}
@@ -1074,8 +1038,7 @@ main.registerCommand({
var projectContext = new projectContextModule.ProjectContext({
projectDir: options.appDir,
serverArchitectures: _.uniq([buildArch, archinfo.host()]),
requireControlProgram: useGalaxy
serverArchitectures: _.uniq([buildArch, archinfo.host()])
});
main.captureAndExit("=> Errors while initializing project:", function () {
@@ -1089,25 +1052,12 @@ main.registerCommand({
serverArch: buildArch
};
var deployResult;
if (useGalaxy) {
deployGalaxy = require('./deploy-galaxy.js');
deployResult = deployGalaxy.deploy({
projectContext: projectContext,
app: site,
settingsFile: options.settings,
starball: starball,
buildOptions: buildOptions,
admin: options.admin
});
} else {
deployResult = deploy.bundleAndDeploy({
projectContext: projectContext,
site: site,
settingsFile: options.settings,
buildOptions: buildOptions
});
}
var deployResult = deploy.bundleAndDeploy({
projectContext: projectContext,
site: site,
settingsFile: options.settings,
buildOptions: buildOptions
});
if (deployResult === 0) {
auth.maybePrintRegistrationLink({
@@ -1130,27 +1080,11 @@ main.registerCommand({
name: 'logs',
minArgs: 1,
maxArgs: 1,
options: {
// XXX once Galaxy is released, document this
stream: { type: Boolean, short: 'f' }
},
catalogRefresh: new catalog.Refresh.Never()
}, function (options) {
var site = qualifySitename(options.args[0]);
if (hostedWithGalaxy(site)) {
var deployGalaxy = require('./deploy-galaxy.js');
var ret = deployGalaxy.logs({
app: site,
streaming: options.stream
});
if (options.stream && ret === null) {
throw new main.WaitForExit;
}
return ret;
} else {
return deploy.logs(site);
}
return deploy.logs(site);
});
///////////////////////////////////////////////////////////////////////////////
@@ -1191,14 +1125,6 @@ main.registerCommand({
auth.pollForRegistrationCompletion();
var site = qualifySitename(options.args[0]);
if (hostedWithGalaxy(site)) {
Console.error(
"Sites hosted on Galaxy do not have an authorized user list. " +
"Instead, go to your Galaxy dashboard to change the authorized users " +
"of your Galaxy.\n");
return 1;
}
if (! auth.isLoggedIn()) {
Console.error(
"You must be logged in for that. Try " +
@@ -1239,12 +1165,6 @@ main.registerCommand({
return 1;
}
if (hostedWithGalaxy(site)) {
Console.error(
"Sorry, you can't claim sites that are hosted on Galaxy.");
return 1;
}
return deploy.claim(site);
});
@@ -1587,10 +1507,7 @@ main.registerCommand({
main.registerCommand({
name: 'login',
options: {
email: { type: Boolean },
// Undocumented: get credentials on a specific Galaxy. Do we still
// need this?
galaxy: { type: String }
email: { type: Boolean }
},
catalogRefresh: new catalog.Refresh.Never()
}, function (options) {

View File

@@ -24,8 +24,8 @@ var compiler = exports;
// You should also update this whenever you update any of the packages used
// directly by the isopack creation process (eg js-analyze) since they do not
// end up as watched dependencies. (At least for now, packages only used in
// target creation (eg minifiers and dev-bundle-fetcher) don't require you to
// update BUILT_BY, though you will need to quit and rerun "meteor run".)
// target creation (eg minifiers) don't require you to update BUILT_BY, though
// you will need to quit and rerun "meteor run".)
compiler.BUILT_BY = 'meteor/15';
// This is a list of all possible architectures that a build can target. (Client

View File

@@ -19,8 +19,7 @@ var tropohouse = require('./tropohouse.js');
// We're not quite there yet though:
// - When developing locally, you may need to set DISCOVERY_PORT (see
// getDiscoveryPort below)
// - GALAXY can still be used to override Galaxy discovery, and
// DELPOY_HOSTNAME can still be set to override classic-style
// - DEPLOY_HOSTNAME can still be set to override classic-style
// deploys
// - The update/warehouse system hasn't been touched and still has its
// hardcoded URLs for now (update.meteor.com and

View File

@@ -1,419 +0,0 @@
var Future = require('fibers/future');
var files = require('./files.js');
var config = require('./config.js');
var path = require('path');
var isopackets = require("./isopackets.js");
var httpHelpers = require('./http-helpers.js');
var auth = require('./auth.js');
var url = require('url');
var _ = require('underscore');
var buildmessage = require('./buildmessage.js');
var ServiceConnection = require('./service-connection.js');
var stats = require('./stats.js');
var Console = require('./console.js').Console;
// If 'error' is an exception that we know how to report in a
// user-friendly way, print an approprite message to stderr and return
// an appropriate exit status for a command. Else rethrow error.
//
// galaxyName should be the name of the galaxy that we're talking to.
// If messages is provided, it is a map from DDP error names to
// human-readable explanation to use.
var handleError = function (error, galaxyName, messages) {
messages = messages || {};
if (error.errorType === "Meteor.Error") {
var msg = messages[error.error];
if (msg)
Console.error(msg);
else if (error.message)
Console.error("Denied: " + error.message);
return 1;
} else if (error.errorType === "DDP.ConnectionError") {
// If we have an http/https URL for a galaxyName instead of a
// proper galaxyName (which is what the code in this file
// currently passes), strip off the scheme and trailing slash.
var m = galaxyName.match(/^https?:\/\/(.*[^\/])\/?$/);
if (m)
galaxyName = m[1];
Console.error(galaxyName + ": connection failed");
return 1;
} else {
throw error;
}
};
// Returns a ServiceConnection to a galaxy service that is authenticated
// from the credential cache.
//
// - galaxy: the name of the galaxy to connect to, as returned by
// discoverGalaxy (as described there, should probably be a galaxy
// name, but currently is a https or http URL)
// - service: the service to connect to within the Galaxy, such as
// 'ultraworld' or 'log-reader'.
var galaxyServiceConnection = function (galaxy, service) {
var endpointUrl = galaxy + "/" + service;
var parsedEndpoint = url.parse(endpointUrl);
var authToken = auth.getSessionToken(parsedEndpoint.hostname);
// XXX XXX higher up on the stack, we need to get the galaxy name
// from the hostname of endpointUrl, and run the login command for
// that galaxy.
if (! authToken)
throw new Error("not logged in to galaxy?");
return new ServiceConnection(endpointUrl, {
headers: {
cookie: "GALAXY_AUTH=" + authToken
}
});
};
// Determine if a particular site is hosted by Galaxy, and if so, by
// which Galaxy. 'app' should be a hostname, like 'myapp.meteor.com'
// or 'mysite.com'. Returns the base URL for the Galaxy
// (https://[galaxyname], or possibly http://[galaxyname] if running
// locally). The URL will not have a trailing slash. Returns null if
// the site is not hosted by Galaxy.
//
// The result is cached, so there is no penality for calling this
// function multiple times (during the same run of the
// tool). (Assuming you wait for the first call to complete before
// making the subsequent calls. The caching doesn't kick in until the
// first call returns.)
//
// XXX in the future, should probably return the name of the Galaxy,
// rather than a URL.
//
// XXX at many places in this file we call discoverGalaxy and don't
// check its return value. This is safe because we expect that
// command.js will have already called discoverGalaxy on the same app
// before we get here, and gotten a satisfactory value, which is now
// cached. But it's not great -- add better error handling.
var discoveryCache = {};
exports.discoverGalaxy = function (app) {
var cacheKey = app;
if (_.has(discoveryCache, cacheKey))
return discoveryCache[cacheKey];
app = app + ":" + config.getDiscoveryPort();
var discoveryUrl = "https://" + app + "/_GALAXY_";
var fut = new Future();
if (process.env.GALAXY)
return process.env.GALAXY;
// At some point we may want to send a version in the request so that galaxy
// can respond differently to different versions of meteor.
httpHelpers.request({
url: discoveryUrl,
json: true,
strictSSL: true,
// We don't want to be confused by, eg, a non-Galaxy-hosted site which
// redirects to a Galaxy-hosted site.
followRedirect: false
}, function (err, resp, body) {
if (! err &&
resp.statusCode === 200 &&
body &&
_.has(body, "galaxyDiscoveryVersion") &&
_.has(body, "galaxyUrl") &&
(body.galaxyDiscoveryVersion === "galaxy-discovery-pre0")) {
var result = body.galaxyUrl;
if (result.indexOf("https://") === -1)
result = "https://" + result;
if (result[result.length - 1] === "/")
result = result.substring(0, result.length - 1);
fut.return(result);
} else {
fut.return(null);
}
});
var result = fut.wait();
discoveryCache[cacheKey] = result;
return result;
};
exports.deleteApp = function (app) {
var galaxy = exports.discoverGalaxy(app);
var conn = galaxyServiceConnection(galaxy, "ultraworld");
try {
conn.call("destroyApp", app);
Console.info("Deleted.");
} catch (e) {
return handleError(e, galaxy);
} finally {
conn.close();
}
};
// Returns exit code for deploy command.
//
// options:
// - app
// - appDir
// - settingsFile
// - buildOptions
// - starball
// XXX refactor this to separate the "maybe bundle" part from "actually deploy"
// so we can be careful to not rely on any of the app dir context when
// in --star mode.
exports.deploy = function (options) {
var conn = null;
try {
var tmpdir = files.mkdtemp('deploy');
var buildDir = path.join(tmpdir, 'build');
var topLevelDirName = path.basename(options.appDir);
var bundlePath = path.join(buildDir, topLevelDirName);
var bundler = require('./bundler.js');
var starball;
var settings = null;
var messages = buildmessage.capture({
title: "preparing to deploy",
rootPath: process.cwd()
}, function () {
if (options.settingsFile)
settings = files.getSettings(options.settingsFile);
});
// Don't try to connect to galaxy before the bundle is
// done. Because bundling doesn't yield, this will cause the
// connection to time out. Eventually we'd like to have bundle
// yield, so that we can connect (and make sure auth works)
// concurrent with bundling.
if (! options.starball && ! messages.hasMessages()) {
Console.info('Deploying ' + options.app + '. Bundling...');
var bundleResult = bundler.bundle({
projectContext: options.projectContext,
outputPath: bundlePath,
buildOptions: options.buildOptions,
requireControlProgram: true
});
if (bundleResult.errors) {
messages = bundleResult.errors;
} else {
stats.recordPackages({
what: "sdk.deploy",
projectContext: options.projectContext,
site: options.app
});
// S3 (which is what's likely on the other end our upload)
// requires a content-length header for HTTP PUT uploads. That
// means that we have to actually tgz up the bundle before we
// can start the upload rather than streaming it. S3 has an
// alternate API for doing chunked uploads, but (a) it has a
// minimum chunk size of 5 MB, so it doesn't help us much
// (many/most stars will be smaller than that), and (b) it's
// nonstandard, so we'd have to bake in S3's specific
// scheme. Doesn't seem worthwhile for now, so just tar to a
// temporary directory. If stars get radically bigger then it
// might be worthwhile to tar to memory and spill to S3 every
// 5MB.
starball = path.join(tmpdir, topLevelDirName + ".tar.gz");
files.createTarball(bundlePath, starball);
}
} else {
starball = options.starball;
}
if (messages.hasMessages()) {
Console.info("\nErrors prevented deploying:");
Console.info(messages.formatMessages());
return 1;
}
Console.info('Uploading...');
var galaxy = exports.discoverGalaxy(options.app);
conn = galaxyServiceConnection(galaxy, "ultraworld");
var created = true;
var appConfig = {};
if (settings !== null)
appConfig.settings = settings;
if (options.admin)
appConfig.admin = true;
try {
conn.call('createApp', options.app, appConfig);
} catch (e) {
if (e.errorType === 'Meteor.Error' && e.error === 'already-exists') {
// Cool, it already exists. No problem. Just set the settings
// if they were passed. We explicitly check for undefined
// because we want to allow you to unset settings by passing
// an empty file.
if (appConfig.settings !== undefined) {
conn.call('updateAppConfiguration', options.app, appConfig);
}
created = false;
} else {
return handleError(e, galaxy);
}
}
// Get the upload information from Galaxy. It's a surprise if this
// fails (we already know the app exists).
try {
var info = conn.call('beginUploadStar', options.app,
bundleResult.starManifest);
} catch (e) {
return handleError(e, galaxy);
}
// Upload
// XXX copied from galaxy/tool/galaxy.js
var fileSize = files.stat(starball).size;
var fileStream = files.createReadStream(starball);
var future = new Future;
var req = httpHelpers.request({
method: "PUT",
url: info.put,
headers: { 'content-length': fileSize,
'content-type': 'application/octet-stream' },
strictSSL: true
}, function (error, response, body) {
if (error || ((response.statusCode !== 200)
&& (response.statusCode !== 201))) {
if (error && error.message)
Console.error("Upload failed: " + error.message);
else
Console.error("Upload failed" +
(response.statusCode ?
" (" + response.statusCode + ")" : ""));
future['return'](false);
} else
future['return'](true);
});
fileStream.pipe(req);
var uploadSucceeded = future.wait();
if (! uploadSucceeded)
return 1;
try {
var result = conn.call('completeUploadStar', info.id);
} catch (e) {
return handleError(e, galaxy, {
'no-such-upload': 'Upload request expired. Try again.'
});
}
if (created)
Console.error(options.app + ": created app\n");
Console.error(options.app + ": " +
"pushed revision " + result.serial);
return 0;
} finally {
// Close the connection to Galaxy (otherwise Node will continue running).
conn && conn.close();
}
};
// options:
// - app
// - streaming (BOOL)
//
// The log messages are printed. Returns a command exit code, or if
// streaming is true and streaming was successfully started, returns
// null.
exports.logs = function (options) {
var galaxy = exports.discoverGalaxy(options.app);
var logReader = galaxyServiceConnection(galaxy, "log-reader");
try {
var lastLogId = null;
var Log = isopackets.load('logging').logging.Log;
// XXX we're cheating a bit here, relying on the server sending
// the log messages in order
var ok = logReader.connection.registerStore('logs', {
update: function (msg) {
// Ignore all messages but 'changed'
if (msg.msg !== 'changed')
return;
var obj = msg.fields.obj;
lastLogId = msg.fields.id;
obj = Log.parse(obj);
obj && console.log(Log.format(obj, {color: true}));
}
});
if (! ok)
throw new Error("Can't listen to messages on the logs collection");
var logsSubscription = null;
try {
logsSubscription =
logReader.subscribeAndWait("logsForApp", options.app,
{ streaming: options.streaming });
} catch (e) {
return handleError(e, galaxy, {
"no-such-app": "No such app: " + options.app
});
}
// In case of reconnect recover the state so user sees only new logs.
// Only set up the onReconnect handler after the subscribe and wait
// has returned; if we set it up before, then we'll end up with two
// subscriptions, because the onReconnect handler will run for the
// first time before the subscribeAndWait returns.
logReader.connection.onReconnect = function () {
logsSubscription && logsSubscription.stop();
var opts = { streaming: options.streaming };
if (lastLogId)
opts.resumeAfterId = lastLogId;
// Don't use subscribeAndWait here; it'll deadlock. We can't
// process the sub messages until `onReconnect` returns, and
// `onReconnect` won't return unless the sub messages have been
// processed. There's no reason we need to wait for the sub to be
// ready here anyway.
// XXX correctly handle errors on resubscribe
logsSubscription = logReader.connection.subscribe(
"logsForApp",
options.app,
opts
);
};
return options.streaming ? null : 0;
} finally {
// If not streaming, close the connection to log-reader so that
// Node can exit cleanly. If streaming, leave the connection open
// so that we continue to get logs.
if (! options.streaming) {
logReader.close();
}
}
};
// On failure, prints a message to stderr and returns null. Otherwise,
// returns a temporary authenticated Mongo URL allowing access to this
// site's database.
exports.temporaryMongoUrl = function (app) {
var galaxy = exports.discoverGalaxy(app);
var conn = galaxyServiceConnection(galaxy, "ultraworld");
try {
var mongoUrl = conn.call('getTemporaryMongoUrl', app);
} catch (e) {
handleError(e, galaxy);
return null;
} finally {
conn.close();
}
return mongoUrl;
};

View File

@@ -359,7 +359,6 @@ Options:
--delete, -D permanently delete this deployment
--debug deploy in debug mode (don't minify, etc)
--settings set optional data for Meteor.settings
--star a star (tarball) to deploy instead of the current Meteor app
>>> logs

View File

@@ -50,7 +50,6 @@ var ISOPACKETS = {
'mongo': ['mongo'],
'ejson': ['ejson'],
'minifiers': ['minifiers'],
'dev-bundle-fetcher': ['dev-bundle-fetcher'],
'constraint-solver': ['constraint-solver'],
'cordova-support': ['boilerplate-generator', 'logging', 'webapp-hashing',
'xmlbuilder'],

View File

@@ -113,9 +113,6 @@ _.extend(ProjectContext.prototype, {
// package names.
self._upgradePackageNames = options.upgradePackageNames;
// Set when deploying to a previous Galaxy prototype.
self._requireControlProgram = options.requireControlProgram;
// Set by publishing commands to ensure that published packages always have
// a web.cordova slice (because we aren't yet smart enough to just default
// to using the web.browser slice instead or make a common 'web' slice).
@@ -501,7 +498,6 @@ _.extend(ProjectContext.prototype, {
self._addAppConstraints(depsAndConstraints);
self._addLocalPackageConstraints(depsAndConstraints);
self._addReleaseConstraints(depsAndConstraints);
self._addGalaxyPrototypeConstraints(depsAndConstraints);
return depsAndConstraints;
},
@@ -540,17 +536,6 @@ _.extend(ProjectContext.prototype, {
});
},
// We only need to build ctl if deploying to the legacy Galaxy
// prototype. (Note that this means that we will need a new constraint
// solution when deploying vs when running locally. This code will be deleted
// soon anyway.)
_addGalaxyPrototypeConstraints: function (depsAndConstraints) {
var self = this;
if (self._requireControlProgram) {
depsAndConstraints.deps.push('ctl');
}
},
_getAnticipatedPrereleases: function (rootConstraints, cachedVersions) {
var self = this;

View File

@@ -40,6 +40,9 @@ var logsOrMongoForApp = function (sandbox, command, appName, options) {
// I suppose it's possible that we don't have any INFO messages in
// the logs, but it seems unlikely. Every time we run a command we
// hit /_GALAXY_ on the site.
// XXX This is no longer true now that we've removed legacy Galaxy
// prototype support, so if this causes test flakiness, it may
// need to be tweaked.
matchString = 'INFO';
} else {
throw new Error('Command must be "logs" or "mongo"');