From ead7027e1d38e1d564e1d7e38d0c468df90d5ada Mon Sep 17 00:00:00 2001 From: Remie Bolte Date: Fri, 29 Jun 2018 05:34:30 +0200 Subject: [PATCH 01/11] Add 9-minute timeout to emulated functions. Fixes #669 --- lib/functionsEmulator.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/functionsEmulator.js b/lib/functionsEmulator.js index 1a451f9c..d117f994 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); From b342ba7bf18ee3a11a0e59fe593cdbe0aefcb425 Mon Sep 17 00:00:00 2001 From: Remie Bolte Date: Fri, 29 Jun 2018 22:06:58 +0200 Subject: [PATCH 02/11] Handle empty configstore with FIREBASE_TOKEN. Fixes #364 --- lib/ensureDefaultCredentials.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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); From 6e14dffe37bd19da693740ffce4f9166cc44a2d8 Mon Sep 17 00:00:00 2001 From: Lauren Long Date: Mon, 2 Jul 2018 13:17:34 -0700 Subject: [PATCH 03/11] Retry on 503 error when generating functions upload URL (#811) --- lib/api.js | 23 ++++- lib/gcp/cloudfunctions.js | 2 + lib/gcp/runtimeconfig.js | 150 +++++++++++++++---------------- scripts/test-functions-config.js | 2 + 4 files changed, 93 insertions(+), 84 deletions(-) diff --git a/lib/api.js b/lib/api.js index 94cfd64a..bb911de4 100644 --- a/lib/api.js +++ b/lib/api.js @@ -194,13 +194,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); - }); + requestFunction = function() { + return api.addRequestHeaders(reqOptions).then(function(reqOptionsWithToken) { + return _request(reqOptionsWithToken); + }); + }; } - return _request(reqOptions); + 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/gcp/cloudfunctions.js b/lib/gcp/cloudfunctions.js index 6df1a877..82f766c6 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/scripts/test-functions-config.js b/scripts/test-functions-config.js index 232ad5c1..dec198c4 100644 --- a/scripts/test-functions-config.js +++ b/scripts/test-functions-config.js @@ -3,6 +3,7 @@ var chalk = require("chalk"); 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."); }; From 600b0fa344fe43836d702ded7af46f68379892f8 Mon Sep 17 00:00:00 2001 From: Lauren Long Date: Mon, 2 Jul 2018 13:29:40 -0700 Subject: [PATCH 04/11] Changelog for v3.19.2 (#813) --- changelog.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.txt b/changelog.txt index e69de29b..a0fdbe92 100644 --- a/changelog.txt +++ b/changelog.txt @@ -0,0 +1,2 @@ +fixed - Fixed bug where function emulator did not properly set timeout to 9 minutes. +fixed - Improved function deploy resilience to temporary errors during upload From c40600422fc81ad427ef8914ab61b899bca01fb6 Mon Sep 17 00:00:00 2001 From: Firebase Operations Date: Mon, 2 Jul 2018 20:32:07 +0000 Subject: [PATCH 05/11] [firebase-release] Updated CLI to 3.19.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9068c2b1..0718aec7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "3.19.1", + "version": "3.19.2", "description": "Command-Line Interface for Firebase", "main": "index.js", "bin": { From a9ff9f5f5a65dd88d23edef9cbcdaf025c37eaf2 Mon Sep 17 00:00:00 2001 From: Firebase Operations Date: Mon, 2 Jul 2018 20:32:16 +0000 Subject: [PATCH 06/11] [firebase-release] Removed change log and reset repo after 3.19.2 release --- changelog.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index a0fdbe92..e69de29b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,2 +0,0 @@ -fixed - Fixed bug where function emulator did not properly set timeout to 9 minutes. -fixed - Improved function deploy resilience to temporary errors during upload From b2392aeb86b4a383747f4cdf9db8c54c3c7a9d86 Mon Sep 17 00:00:00 2001 From: Lauren Long Date: Mon, 2 Jul 2018 15:05:43 -0700 Subject: [PATCH 07/11] Bold product name when deploying rules (#816) --- lib/RulesDeploy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/RulesDeploy.js b/lib/RulesDeploy.js index dd045331..223c736a 100644 --- a/lib/RulesDeploy.js +++ b/lib/RulesDeploy.js @@ -57,7 +57,7 @@ RulesDeploy.prototype = { var promises = []; _.forEach(this.rulesFiles, function(files, filename) { utils.logBullet( - chalk.cyan(self.type + ":") + " uploading rules " + chalk.bold(filename) + "..." + chalk.bold.cyan(self.type + ":") + " uploading rules " + chalk.bold(filename) + "..." ); promises.push( gcp.rules.createRuleset(self.options.project, files).then(function(rulesetName) { From 5ad18b34230ad4fc323de9579e1b8abec3498763 Mon Sep 17 00:00:00 2001 From: Lauren Long Date: Tue, 3 Jul 2018 11:00:33 -0700 Subject: [PATCH 08/11] Fix typeError in lib/responseToError.js (#817) --- lib/responseToError.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/responseToError.js b/lib/responseToError.js index 779d6e89..ea7bd0b4 100644 --- a/lib/responseToError.js +++ b/lib/responseToError.js @@ -7,9 +7,19 @@ module.exports = function(response, body) { if (response.statusCode < 400) { return null; } + + 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, }; } From d29567bf5d44b9cd510e6699535e32d8573783ee Mon Sep 17 00:00:00 2001 From: Lauren Long Date: Tue, 3 Jul 2018 11:09:16 -0700 Subject: [PATCH 09/11] Changelog for v3.19.3 --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index e69de29b..d5001c51 100644 --- a/changelog.txt +++ b/changelog.txt @@ -0,0 +1 @@ +fixed - Fix bug where API error messages were swallowed up by "TypeError: Cannot create property 'error' on string". From 8c65d3333dad9d2c279a0575173e2c2ed99e381f Mon Sep 17 00:00:00 2001 From: Firebase Operations Date: Tue, 3 Jul 2018 18:16:15 +0000 Subject: [PATCH 10/11] [firebase-release] Updated CLI to 3.19.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0718aec7..e8922354 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "3.19.2", + "version": "3.19.3", "description": "Command-Line Interface for Firebase", "main": "index.js", "bin": { From 0ff6a55e6ea7d70db7d2b83df22c36e2eccb41f1 Mon Sep 17 00:00:00 2001 From: Firebase Operations Date: Tue, 3 Jul 2018 18:16:23 +0000 Subject: [PATCH 11/11] [firebase-release] Removed change log and reset repo after 3.19.3 release --- changelog.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index d5001c51..e69de29b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1 +0,0 @@ -fixed - Fix bug where API error messages were swallowed up by "TypeError: Cannot create property 'error' on string".