diff --git a/lib/RulesDeploy.js b/lib/RulesDeploy.js index b6e09094..abc5bb15 100644 --- a/lib/RulesDeploy.js +++ b/lib/RulesDeploy.js @@ -56,7 +56,9 @@ RulesDeploy.prototype = { var self = this; var promises = []; _.forEach(this.rulesFiles, function(files, filename) { - utils.logBullet(clc.cyan(self.type + ":") + " uploading rules " + clc.bold(filename) + "..."); + utils.logBullet( + clc.bold.cyan(self.type + ":") + " uploading rules " + clc.bold(filename) + "..." + ); promises.push( gcp.rules.createRuleset(self.options.project, files).then(function(rulesetName) { self.rulesetNames[filename] = rulesetName; diff --git a/lib/api.js b/lib/api.js index 89d77574..13e6e05d 100644 --- a/lib/api.js +++ b/lib/api.js @@ -197,13 +197,28 @@ var api = { reqOptions.qs = options.qs; reqOptions.headers = options.headers; + var requestFunction = function() { + return _request(reqOptions); + }; if (options.auth === true) { - return api.addRequestHeaders(reqOptions).then(function(reqOptionsWithToken) { - return _request(reqOptionsWithToken, options.logOptions); - }); + requestFunction = function() { + return api.addRequestHeaders(reqOptions).then(function(reqOptionsWithToken) { + return _request(reqOptionsWithToken, options.logOptions); + }); + }; } - return _request(reqOptions, options.logOptions); + return requestFunction().catch(function(err) { + if ( + options.retryCodes && + _.includes(options.retryCodes, _.get(err, "context.response.statusCode")) + ) { + return new Promise(function(resolve) { + setTimeout(resolve, 1000); + }).then(requestFunction); + } + return Promise.reject(err); + }); }, getProject: function(projectId) { return api diff --git a/lib/ensureDefaultCredentials.js b/lib/ensureDefaultCredentials.js index 2a4a42bf..f0b2bb2b 100644 --- a/lib/ensureDefaultCredentials.js +++ b/lib/ensureDefaultCredentials.js @@ -31,11 +31,13 @@ module.exports = function() { "application_default_credentials.json" ); + var tokens = configstore.get("tokens") || {}; + var credentials = { client_id: api.clientId, client_secret: api.clientSecret, type: "authorized_user", - refresh_token: configstore.get("tokens").refresh_token, + refresh_token: tokens.refresh_token || process.env.FIREBASE_TOKEN, }; // Mimic the effects of running "gcloud auth application-default login" fs.ensureDirSync(GCLOUD_CREDENTIAL_DIR); diff --git a/lib/functionsEmulator.js b/lib/functionsEmulator.js index fa354fac..bd386ee4 100644 --- a/lib/functionsEmulator.js +++ b/lib/functionsEmulator.js @@ -150,6 +150,9 @@ FunctionsEmulator.prototype.start = function(shellMode) { firebase: true, source: functionsDir, triggerHttp: true, + timeout: { + seconds: 540, + }, }) .catch(function(e) { logger.debug("Error while deploying to emulator: " + e + "\n" + e.stack); diff --git a/lib/gcp/cloudfunctions.js b/lib/gcp/cloudfunctions.js index c71886b6..24390a3b 100644 --- a/lib/gcp/cloudfunctions.js +++ b/lib/gcp/cloudfunctions.js @@ -26,11 +26,13 @@ function _functionsOpLogReject(func, type, err) { function _generateUploadUrl(projectId, location) { var parent = "projects/" + projectId + "/locations/" + location; var endpoint = "/" + API_VERSION + "/" + parent + "/functions:generateUploadUrl"; + return api .request("POST", endpoint, { auth: true, json: false, origin: api.functionsOrigin, + retryCodes: [503], }) .then( function(result) { diff --git a/lib/gcp/runtimeconfig.js b/lib/gcp/runtimeconfig.js index d2ec871d..87587046 100644 --- a/lib/gcp/runtimeconfig.js +++ b/lib/gcp/runtimeconfig.js @@ -8,127 +8,116 @@ var _ = require("lodash"); var API_VERSION = "v1beta1"; -function _retryOnServerError(requestFunction) { - return requestFunction().catch(function(err) { - if (_.includes([500, 503], _.get(err, "context.response.statusCode"))) { - return new Promise(function(resolve) { - setTimeout(resolve, 1000); - }).then(requestFunction); - } - return Promise.reject(err); - }); -} - function _listConfigs(projectId) { - return _retryOnServerError(function() { - return api.request("GET", utils.endpoint([API_VERSION, "projects", projectId, "configs"]), { + return api + .request("GET", utils.endpoint([API_VERSION, "projects", projectId, "configs"]), { auth: true, origin: api.runtimeconfigOrigin, + retryCodes: [500, 503], + }) + .then(function(resp) { + return Promise.resolve(resp.body.configs); }); - }).then(function(resp) { - return Promise.resolve(resp.body.configs); - }); } function _createConfig(projectId, configId) { var path = _.join(["projects", projectId, "configs"], "/"); var endpoint = utils.endpoint([API_VERSION, path]); - return _retryOnServerError(function() { - return api.request("POST", endpoint, { + return api + .request("POST", endpoint, { auth: true, origin: api.runtimeconfigOrigin, data: { name: path + "/" + configId, }, + retryCodes: [500, 503], + }) + .catch(function(err) { + if (_.get(err, "context.response.statusCode") === 409) { + // Config has already been created as part of a parallel operation during firebase functions:config:set + return Promise.resolve(); + } + return Promise.reject(err); }); - }).catch(function(err) { - if (_.get(err, "context.response.statusCode") === 409) { - // Config has already been created as part of a parallel operation during firebase functions:config:set - return Promise.resolve(); - } - return Promise.reject(err); - }); } function _deleteConfig(projectId, configId) { - return _retryOnServerError(function() { - return api.request( - "DELETE", - utils.endpoint([API_VERSION, "projects", projectId, "configs", configId]), - { - auth: true, - origin: api.runtimeconfigOrigin, + return api + .request("DELETE", utils.endpoint([API_VERSION, "projects", projectId, "configs", configId]), { + auth: true, + origin: api.runtimeconfigOrigin, + retryCodes: [500, 503], + }) + .catch(function(err) { + if (_.get(err, "context.response.statusCode") === 404) { + logger.debug("Config already deleted."); + return Promise.resolve(); } - ); - }).catch(function(err) { - if (_.get(err, "context.response.statusCode") === 404) { - logger.debug("Config already deleted."); - return Promise.resolve(); - } - return Promise.reject(err); - }); + return Promise.reject(err); + }); } function _listVariables(configPath) { - return _retryOnServerError(function() { - return api.request("GET", utils.endpoint([API_VERSION, configPath, "variables"]), { + return api + .request("GET", utils.endpoint([API_VERSION, configPath, "variables"]), { auth: true, origin: api.runtimeconfigOrigin, + retryCodes: [500, 503], + }) + .then(function(resp) { + return Promise.resolve(resp.body.variables); }); - }).then(function(resp) { - return Promise.resolve(resp.body.variables); - }); } function _getVariable(varPath) { - return _retryOnServerError(function() { - return api.request("GET", utils.endpoint([API_VERSION, varPath]), { + return api + .request("GET", utils.endpoint([API_VERSION, varPath]), { auth: true, origin: api.runtimeconfigOrigin, + retryCodes: [500, 503], + }) + .then(function(resp) { + return Promise.resolve(resp.body); }); - }).then(function(resp) { - return Promise.resolve(resp.body); - }); } function _createVariable(projectId, configId, varId, value) { var path = _.join(["projects", projectId, "configs", configId, "variables"], "/"); var endpoint = utils.endpoint([API_VERSION, path]); - return _retryOnServerError(function() { - return api.request("POST", endpoint, { + return api + .request("POST", endpoint, { auth: true, origin: api.runtimeconfigOrigin, data: { name: path + "/" + varId, text: value, }, + retryCodes: [500, 503], + }) + .catch(function(err) { + if (_.get(err, "context.response.statusCode") === 404) { + // parent config doesn't exist yet + return _createConfig(projectId, configId).then(function() { + return _createVariable(projectId, configId, varId, value); + }); + } + return Promise.reject(err); }); - }).catch(function(err) { - if (_.get(err, "context.response.statusCode") === 404) { - // parent config doesn't exist yet - return _createConfig(projectId, configId).then(function() { - return _createVariable(projectId, configId, varId, value); - }); - } - return Promise.reject(err); - }); } function _updateVariable(projectId, configId, varId, value) { var path = _.join(["projects", projectId, "configs", configId, "variables", varId], "/"); var endpoint = utils.endpoint([API_VERSION, path]); - return _retryOnServerError(function() { - return api.request("PUT", endpoint, { - auth: true, - origin: api.runtimeconfigOrigin, - data: { - name: path, - text: value, - }, - }); + return api.request("PUT", endpoint, { + auth: true, + origin: api.runtimeconfigOrigin, + data: { + name: path, + text: value, + }, + retryCodes: [500, 503], }); } - function _setVariable(projectId, configId, varId, value) { var path = _.join(["projects", projectId, "configs", configId, "variables", varId], "/"); return _getVariable(path) @@ -147,18 +136,19 @@ function _deleteVariable(projectId, configId, varId) { var endpoint = utils.endpoint([API_VERSION, "projects", projectId, "configs", configId, "variables", varId]) + "?recursive=true"; - return _retryOnServerError(function() { - return api.request("DELETE", endpoint, { + return api + .request("DELETE", endpoint, { auth: true, origin: api.runtimeconfigOrigin, + retryCodes: [500, 503], + }) + .catch(function(err) { + if (_.get(err, "context.response.statusCode") === 404) { + logger.debug("Variable already deleted."); + return Promise.resolve(); + } + return Promise.reject(err); }); - }).catch(function(err) { - if (_.get(err, "context.response.statusCode") === 404) { - logger.debug("Variable already deleted."); - return Promise.resolve(); - } - return Promise.reject(err); - }); } module.exports = { diff --git a/lib/responseToError.js b/lib/responseToError.js index e368fdbd..4547edab 100644 --- a/lib/responseToError.js +++ b/lib/responseToError.js @@ -15,9 +15,19 @@ module.exports = function(response, body) { if (response.statusCode < 400) { return null; } - if (!body.error && typeof body === "object") { + + if (typeof body !== "object") { + try { + body = JSON.parse(body); + } catch (e) { + body = {}; + } + } + + if (!body.error) { + var message = response.statusCode === 404 ? "Not Found" : "Unknown Error"; body.error = { - message: "Unknown Error", + message: message, }; } diff --git a/package.json b/package.json index ffc5acfb..a785258f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "3.19.1", + "version": "3.19.3", "description": "Command-Line Interface for Firebase", "main": "index.js", "bin": { diff --git a/scripts/test-functions-config.js b/scripts/test-functions-config.js index 009cb98e..3ae9ccf2 100644 --- a/scripts/test-functions-config.js +++ b/scripts/test-functions-config.js @@ -3,6 +3,7 @@ var clc = require("cli-color"); var exec = require("child_process").exec; +var execSync = require("child_process").execSync; var expect = require("chai").expect; var fs = require("fs-extra"); var tmp = require("tmp"); @@ -21,6 +22,7 @@ var preTest = function() { fs.copySync(projectDir, tmpDir); api.setRefreshToken(configstore.get("tokens").refresh_token); api.setScopes(scopes.CLOUD_PLATFORM); + execSync(localFirebase + " functions:config:unset foo", { cwd: tmpDir }); console.log("Done pretest prep."); };