diff --git a/tools/cli/commands.js b/tools/cli/commands.js index c5f8bbfdd0..38a41e83b1 100644 --- a/tools/cli/commands.js +++ b/tools/cli/commands.js @@ -1145,7 +1145,6 @@ to this command.`); } else { // remote mode var site = qualifySitename(options.args[0]); - config.printUniverseBanner(); mongoUrl = deploy.temporaryMongoUrl(site); usedMeteorAccount = true; @@ -1240,7 +1239,6 @@ main.registerCommand({ catalogRefresh: new catalog.Refresh.Never() }, function (options, {rawOptions}) { var site = options.args[0]; - config.printUniverseBanner(); if (options.delete) { return deploy.deleteApp(site); @@ -1358,7 +1356,6 @@ main.registerCommand({ return 1; } - config.printUniverseBanner(); auth.pollForRegistrationCompletion(); var site = qualifySitename(options.args[0]); @@ -1390,7 +1387,6 @@ main.registerCommand({ maxArgs: 1, catalogRefresh: new catalog.Refresh.Never() }, function (options) { - config.printUniverseBanner(); auth.pollForRegistrationCompletion(); var site = qualifySitename(options.args[0]); @@ -1956,8 +1952,6 @@ main.registerCommand({ throw new main.ShowUsage; } - config.printUniverseBanner(); - var username = options.add || options.remove; var conn = loggedInAccountsConnectionOrPrompt( diff --git a/tools/meteor-services/auth.js b/tools/meteor-services/auth.js index 8fd4af9953..2d2c043a6e 100644 --- a/tools/meteor-services/auth.js +++ b/tools/meteor-services/auth.js @@ -555,8 +555,6 @@ exports.doInteractivePasswordLogin = doInteractivePasswordLogin; exports.loginCommand = withAccountsConnection(function (options, connection) { - config.printUniverseBanner(); - var data = readSessionData(); if (! getSession(data, config.getAccountsDomain()).token || @@ -593,8 +591,6 @@ exports.loginCommand = withAccountsConnection(function (options, }); exports.logoutCommand = function (options) { - config.printUniverseBanner(); - var data = readSessionData(); var wasLoggedIn = !! loggedIn(data); logOutAllSessions(data); @@ -686,7 +682,6 @@ exports.registrationUrl = function () { }; exports.whoAmICommand = function (options) { - config.printUniverseBanner(); auth.pollForRegistrationCompletion(); var data = readSessionData(); diff --git a/tools/meteor-services/config.js b/tools/meteor-services/config.js index fc2ee2f051..ffda20a6a3 100644 --- a/tools/meteor-services/config.js +++ b/tools/meteor-services/config.js @@ -8,179 +8,54 @@ var tropohouse = require('../packaging/tropohouse.js'); // deploying apps to the MDG free hosting sandbox, publishing packages, // getting an ssh access to a build farm. These functions need // configuration. -// -// The idea is that eventually, the `meteor` will take only one -// configuration parameter, the "universe" it is talking to, which -// defaults to "www.meteor.com". In a git checkout it can be set by -// creating a file at the root of the checkout called "universe" that -// contains the name of the universe you wish to use. Then, all other -// needed configuration is derived from the universe name. -// -// We're not quite there yet though: -// - When developing locally, you may need to set DISCOVERY_PORT (see -// getDiscoveryPort below) -// - 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 -// warehouse.meteor.com). Really, it's debatable whether these -// should (necessarily) change when you change your universe name. - -var universe; -var getUniverse = function () { - if (! universe) { - universe = "www.meteor.com"; - - if (files.inCheckout()) { - var p = files.pathJoin(files.getCurrentToolsDir(), 'universe'); - if (files.exists(p)) { - universe = files.readFile(p, 'utf8').trim(); - } - } - } - - return universe; -}; - -var isLocalUniverse = function () { - return !! getUniverse().match(/^localhost(:([\d]+))?$/); -}; - -var localhostOffset = function (portOffset) { - var match = getUniverse().match(/^localhost(:([\d]+))?$/); - if (! match) { - throw new Error("not a local universe?"); - } - return "localhost:" + (parseInt(match[2] || "80") + portOffset); -}; - -var getAuthServiceHost = function () { - if (! isLocalUniverse()) { - return universe; - } else { - // Special case for local development. Point - // $METEOR_CHECKOUT/universe at the place where you are running - // frontpage (eg, localhost:3000), and run the accounts server ten - // port numbers higher. Like so: - // cd meteor-accounts - // ROOT_URL=http://localhost:3010/auth curmeteor -p 3010 - return localhostOffset(10); - } -}; - -// Given a hostname, add "http://" or "https://" as -// appropriate. (localhost gets http; anything else is always https.) -var addScheme = function (host) { - if (host.match(/^localhost(:\d+)?$/)) { - return "http://" + host; - } else { - return "https://" + host; - } -}; var config = exports; _.extend(exports, { - // True if this the production universe (www.meteor.com) - isProduction: function () { - return getUniverse() === "www.meteor.com"; - }, - - // The current universe name. Should be used for cosmetic purposes - // only (displaying to the user). If you want to programmatically - // derive configuration from it, add a new method to this file. - getUniverse: function () { - return getUniverse(); - }, - - // Base URL for Meteor Accounts OAuth services, typically - // "https://www.meteor.com/oauth2". Endpoints include /authorize and - // /token. + // Base URL for Meteor Accounts OAuth services. Endpoints include /authorize + // and /token. getOauthUrl: function () { - return addScheme(getAuthServiceHost()) + "/oauth2"; + return "https://www.meteor.com/oauth2"; }, - // Base URL for Meteor Accounts API, typically - // "https://www.meteor.com/api/v1". Endpoints include '/login' and + // Base URL for Meteor Accounts API. Endpoints include '/login' and // '/logoutById'. getAccountsApiUrl: function () { - return addScheme(getAuthServiceHost()) + "/api/v1"; + return "https://www.meteor.com/api/v1"; }, - // URL for the DDP interface to Meteor Accounts, typically - // "https://www.meteor.com/auth". (Really should be a ddp:// URL -- - // we'll get there soon enough.) + // URL for the DDP interface to Meteor Accounts. getAuthDDPUrl: function () { - return addScheme(getAuthServiceHost()) + "/auth"; + return "https://www.meteor.com/auth"; }, // URL for the DDP interface to the meteor build farm, typically // "https://build.meteor.com". getBuildFarmUrl: function () { - if (process.env.METEOR_BUILD_FARM_URL) { - return process.env.METEOR_BUILD_FARM_URL; - } - var host = config.getBuildFarmDomain(); - - return addScheme(host); + return process.env.METEOR_BUILD_FARM_URL || "https://build.meteor.com"; }, getBuildFarmDomain: function () { - if (process.env.METEOR_BUILD_FARM_URL) { - var parsed = url.parse(process.env.METEOR_BUILD_FARM_URL); - return parsed.host; - } else { - return getUniverse().replace(/^www\./, 'build.'); - } + return url.parse(config.getBuildFarmUrl()).host; }, // URL for the DDP interface to the package server, typically - // "https://packages.meteor.com". (Really should be a ddp:// URL -- - // we'll get there soon enough.) - // - // When running everything locally, run the package server at the - // base universe port number (that is, the Meteor Accounts port - // number) plus 20. + // "https://packages.meteor.com". getPackageServerUrl: function () { - if (process.env.METEOR_PACKAGE_SERVER_URL) { - return process.env.METEOR_PACKAGE_SERVER_URL; - } - var host = config.getPackageServerDomain(); - - return addScheme(host); + return process.env.METEOR_PACKAGE_SERVER_URL || + "https://packages.meteor.com"; }, getPackageServerDomain: function () { - if (isLocalUniverse()) { - return localhostOffset(20); - } else { - if (process.env.METEOR_PACKAGE_SERVER_URL) { - var parsed = url.parse(process.env.METEOR_PACKAGE_SERVER_URL); - return parsed.host; - } else { - return getUniverse().replace(/^www\./, 'packages.'); - } - } + return url.parse(config.getPackageServerUrl()).host; }, getPackageStatsServerUrl: function () { - if (process.env.METEOR_PACKAGE_STATS_SERVER_URL) { - return process.env.METEOR_PACKAGE_STATS_SERVER_URL; - } - - var host = config.getPackageStatsServerDomain(); - return addScheme(host); + return process.env.METEOR_PACKAGE_STATS_SERVER_URL || + "https://activity.meteor.com"; }, getPackageStatsServerDomain: function () { - if (process.env.METEOR_PACKAGE_STATS_SERVER_URL) { - return url.parse(process.env.METEOR_PACKAGE_STATS_SERVER_URL).hostname; - } - - if (isLocalUniverse()) { - return localhostOffset(30); - } else { - return getUniverse().replace(/^www\./, 'activity.'); - } + return url.parse(config.getPackageStatsServerUrl()).host; }, // Note: this is NOT guaranteed to return a distinct prefix for every @@ -246,56 +121,7 @@ _.extend(exports, { // use. This is used as a key for storing your Meteor Accounts // login token. getAccountsDomain: function () { - return getUniverse(); - }, - - getDeployHostname: function () { - return process.env.DEPLOY_HOSTNAME || "meteor.com"; - }, - - getFullAppName: function(appName) { - var domain = process.env.DEPLOY_DOMAIN || this.getDeployHostname(); - if (appName.indexOf(".") === -1) { - return appName + "." + domain; - } - return appName; - }, - - // Deploy URL for MDG free hosting, eg 'https://deploy.meteor.com'. - getDeployUrl: function () { - var host; - - // Support the old DEPLOY_HOSTNAME environment variable for a - // while longer. Soon, let's remove this in favor of the universe - // scheme. - if (process.env.DEPLOY_HOSTNAME) { - host = process.env.DEPLOY_HOSTNAME; - if (host.match(/^http/)) { - // allow it to contain a URL scheme - return host; - } - } else { - // Otherwise, base it on the universe. - if (isLocalUniverse()) { - throw new Error("local development of deploy server not supported"); - } else { - host = getUniverse().replace(/^www\./, 'deploy.'); - } - } - - return addScheme(host); - }, - - // URL from which the update manifest may be fetched, eg - // 'https://update.meteor.com/manifest.json' - getUpdateManifestUrl: function () { - if (isLocalUniverse()) { - // localhost can't run the manifest server - u = "www.meteor.com"; - } - var host = getUniverse().replace(/^www\./, 'update.'); - - return addScheme(host) + "/manifest.json"; + return "www.meteor.com"; }, // Path to file that contains our credentials for any services that @@ -305,32 +131,5 @@ _.extend(exports, { // METEOR_SESSION_FILE is for automated testing purposes only. return process.env.METEOR_SESSION_FILE || files.pathJoin(files.getHomeDir(), '.meteorsession'); - }, - - // Port to use when querying URLs for the deploy server that backs - // them, and for querying oauth clients for their oauth information - // (so we can log into them). - // - // In production this should always be 443 (we *must* - // cryptographically authenticate the server answering the query), - // but this can be inconvenient for local development since 443 is a - // privileged port, so you can set DISCOVERY_PORT to override. (A - // better solution would probably be to spin up a local VM.) - getDiscoveryPort: function () { - if (process.env.DISCOVERY_PORT) { - return parseInt(process.env.DISCOVERY_PORT); - } else { - return 443; - } - }, - - // It's easy to forget that you're in an alternate universe (and - // that that is the reason you're not seeing your deploys). If not - // in production mode, print a quick hint about the universe you're - // in. - printUniverseBanner: function () { - if (! config.isProduction()) { - process.stderr.write('[Universe: ' + config.getUniverse() + ']\n'); - } } }); diff --git a/tools/meteor-services/deploy.js b/tools/meteor-services/deploy.js index 6f41940418..f582036c54 100644 --- a/tools/meteor-services/deploy.js +++ b/tools/meteor-services/deploy.js @@ -42,6 +42,8 @@ const CAPABILITIES = ['showDeployMessages', 'canTransferAuthorization']; // - bodyStream: if provided, a stream to use as the request body // - any other parameters accepted by the node 'request' module, for example // 'qs' to set query string parameters +// - printDeployURL: provided if we should show the deploy URL; set this +// for the first RPC of any user command // // Waits until server responds, then returns an object with the // following keys: @@ -66,10 +68,16 @@ var deployRpc = function (options) { } options.qs = _.extend({}, options.qs, {capabilities: CAPABILITIES}); + const deployURLBase = getDeployURL(options.site).await(); + + if (options.printDeployURL) { + Console.info("Talking to Galaxy servers at " + deployURLBase); + } + // XXX: Reintroduce progress for upload try { var result = httpHelpers.request(_.extend(options, { - url: config.getDeployUrl() + '/' + options.operation + + url: deployURLBase + '/' + options.operation + (options.site ? ('/' + options.site) : ''), method: options.method || 'GET', bodyStream: options.bodyStream, @@ -148,7 +156,8 @@ var authedRpc = function (options) { operation: 'info', site: rpcOptions.site, expectPayload: [], - qs: options.qs + qs: options.qs, + printDeployURL: options.printDeployURL }); if (infoResult.statusCode === 401 && rpcOptions.promptIfAuthFails) { @@ -374,7 +383,8 @@ var bundleAndDeploy = function (options) { site: site, preflight: true, promptIfAuthFails: promptIfAuthFails, - qs: options.rawOptions + qs: options.rawOptions, + printDeployURL: true }); if (preflight.errorMessage) { @@ -466,7 +476,6 @@ var bundleAndDeploy = function (options) { dns.resolve(hostname, 'CNAME', function (err, cnames) { if (err || cnames[0] !== 'origin.meteor.com') { dns.resolve(hostname, 'A', function (err, addresses) { - console.log('and here') if (err || addresses[0] !== '107.22.210.133') { Console.info('-------------'); Console.info( @@ -494,7 +503,8 @@ var deleteApp = function (site) { method: 'DELETE', operation: 'deploy', site: site, - promptIfAuthFails: true + promptIfAuthFails: true, + printDeployURL: true }); if (result.errorMessage) { @@ -519,7 +529,8 @@ var checkAuthThenSendRpc = function (site, operation, what) { operation: operation, site: site, preflight: true, - promptIfAuthFails: true + promptIfAuthFails: true, + printDeployURL: true }); if (preflight.errorMessage) { @@ -625,7 +636,8 @@ var listAuthorized = function (site) { var result = deployRpc({ operation: 'info', site: site, - expectPayload: [] + expectPayload: [], + printDeployURL: true }); if (result.errorMessage) { Console.error("Couldn't get authorized users list: " + result.errorMessage); @@ -676,7 +688,8 @@ var changeAuthorized = function (site, action, username) { operation: 'authorized', site: site, qs: {[action]: username}, - promptIfAuthFails: true + promptIfAuthFails: true, + printDeployURL: true }); if (result.errorMessage) { @@ -705,7 +718,8 @@ var claim = function (site) { // straight away (at a cost of an extra REST call) var infoResult = deployRpc({ operation: 'info', - site: site + site: site, + printDeployURL: true }); if (infoResult.statusCode === 404) { Console.error( @@ -800,6 +814,82 @@ var listSites = function () { return 0; }; +// Given a hostname, add "http://" or "https://" as +// appropriate. (localhost gets http; anything else is always https.) +function addScheme(hostOrURL) { + if (hostOrURL.match(/^http/)) { + return hostOrURL; + } else if (hostOrURL.match(/^localhost(:\d+)?$/)) { + return "http://" + hostOrURL; + } else { + return "https://" + hostOrURL; + } +}; + +// Maps from "site" to Promise, so we don't have to re-ping on each +// RPC (even if the calls to getDeployURL overlap). +const galaxyDiscoveryCache = new Map; + +// getDeployURL returns the a Promise for the base deploy URL for the given app. +// "app" may be falsey for certain RPCs (eg meteor list-sites). +function getDeployURL(site) { + // Always trust explicitly configuration via env. + if (process.env.DEPLOY_HOSTNAME) { + return Promise.resolve(addScheme(process.env.DEPLOY_HOSTNAME)); + } + + const defaultURL = "https://galaxy.meteor.com"; + + // No site? Just use the default. + if (!site) { + return Promise.resolve(defaultURL); + } + + // If we have a site, we can try to do Galaxy discovery. + + // Do we already have an answer? + if (galaxyDiscoveryCache.has(site)) { + return galaxyDiscoveryCache.get(site); + } + + // Otherwise, try https first, then http, then just use the default. + const p = discoverGalaxy(site, "https") + .catch(() => discoverGalaxy(site, "http")) + .catch(() => defaultURL); + galaxyDiscoveryCache.set(site, p); + return p; +} + +// discoverGalaxy returns the URL to use for Galaxy discovery, or an error if it +// couldn't be fetched. +async function discoverGalaxy(site, scheme) { + const discoveryURL = + scheme + "://" + site + "/.well-known/meteor/deploy-url"; + // If httpHelpers.request throws, the returned Promise will reject, which is + // fine. + const { response, body } = 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 + }); + if (response.statusCode !== 200) { + throw new Error("bad status code: " + response.statusCode); + } + if (!body) { + throw new Error("response had no body"); + } + if (body.galaxyDiscoveryVersion !== "galaxy-1") { + throw new Error( + "unexpected galaxyDiscoveryVersion: " + body.galaxyDiscoveryVersion); + } + if (!_.has(body, "deployURL")) { + throw new Error("no deployURL"); + } + return body.deployURL; +} exports.bundleAndDeploy = bundleAndDeploy; exports.deleteApp = deleteApp; diff --git a/tools/tests/organizations.js b/tools/tests/organizations.js index c6ab6212a8..820a9491d3 100644 --- a/tools/tests/organizations.js +++ b/tools/tests/organizations.js @@ -36,54 +36,3 @@ selftest.define("organizations - logged out", function () { run.stop(); }); - -// For now, this test only runs from checkout with a universe file -// pointing to a testing meteor-accounts server (e.g. one deployed with -// Meteor.settings.testing = true). -selftest.define("organizations", ["net", "slow", "checkout"], function () { - var s = new Sandbox; - - testUtils.login(s, "test", "testtest"); - - // Create an organization for the test. - var orgName = testUtils.createOrganization("test", "testtest"); - - // Add a nonexistent user. - var run = s.run("admin", "members", - orgName, "--add", testUtils.randomString(15)); - run.waitSecs(commandTimeoutSecs); - run.matchErr("user does not exist"); - run.expectExit(1); - - // Add a user to a nonexistent org. - run = s.run("admin", "members", - testUtils.randomString(15), "--add", "testtest"); - run.waitSecs(commandTimeoutSecs); - run.matchErr("Organization does not exist"); - run.expectExit(1); - - // Add a real user to a real org. - run = s.run("admin", "members", orgName, "--add", "testtest"); - run.waitSecs(commandTimeoutSecs); - run.match("testtest added to organization " + orgName); - run.expectExit(0); - - // Try to show a nonexistent organization. - run = s.run("admin", "members", testUtils.randomString(15)); - run.waitSecs(commandTimeoutSecs); - run.matchErr("Organization does not exist"); - run.expectExit(1); - - // 'show-organization' should show the right members, and - // 'list-organization' should show that 'test' is a member. - run = s.run("admin", "members", orgName); - run.waitSecs(commandTimeoutSecs); - run.read("test\ntesttest\n"); - run.expectExit(0); - run = s.run("admin", "list-organizations"); - run.waitSecs(commandTimeoutSecs); - run.match(orgName + "\n"); - run.expectExit(0); - - testUtils.logout(s); -}); diff --git a/tools/tool-testing/test-utils.js b/tools/tool-testing/test-utils.js index 46d78265f7..7f5f18744e 100644 --- a/tools/tool-testing/test-utils.js +++ b/tools/tool-testing/test-utils.js @@ -48,11 +48,6 @@ exports.logout = function (s) { run.expectExit(0); }; -exports.getUserId = function (s) { - var data = JSON.parse(s.readSessionFile()); - return data.sessions[config.getUniverse()].userId; -}; - var registrationUrlRegexp = /https:\/\/www\.meteor\.com\/setPassword\?([a-zA-Z0-9\+\/]+)/; exports.registrationUrlRegexp = registrationUrlRegexp;