diff --git a/tools/commands.js b/tools/commands.js index 4a1243e884..ae0400638e 100644 --- a/tools/commands.js +++ b/tools/commands.js @@ -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; } @@ -1088,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({ @@ -1129,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); }); /////////////////////////////////////////////////////////////////////////////// @@ -1190,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 " + @@ -1238,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); }); diff --git a/tools/deploy-galaxy.js b/tools/deploy-galaxy.js deleted file mode 100644 index 9db8496e50..0000000000 --- a/tools/deploy-galaxy.js +++ /dev/null @@ -1,418 +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 - }); - - 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; -}; diff --git a/tools/help.txt b/tools/help.txt index 6995fe34bb..f60e5d5c34 100644 --- a/tools/help.txt +++ b/tools/help.txt @@ -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