Support configuration of function region, memory, and timeout (#285)

This commit is contained in:
Lauren Long
2018-07-13 10:23:39 -07:00
parent b35592d9e0
commit f9d3460506
4 changed files with 504 additions and 250 deletions

View File

@@ -9,13 +9,145 @@ var logger = require("../../logger");
var track = require("../../track");
var utils = require("../../utils");
var pollOperation = require("../../pollOperations");
var helper = require("../../functionsDeployHelper");
var CLI_DEPLOYMENT_TOOL = "cli-firebase";
var CLI_DEPLOYMENT_LABELS = {
"deployment-tool": CLI_DEPLOYMENT_TOOL,
};
var timings = {};
var deployments = [];
var failedDeployments = 0;
function _pollAndManageOperations(operations) {
var interval;
// Poll less frequently when there are many operations to avoid hitting read quota.
// See "Read requests" quota at https://cloud.google.com/console/apis/api/cloudfunctions/quotas
if (_.size(operations) > 90) {
utils.logWarning(
chalk.bold.yellow("functions:") +
" too many functions are being deployed, cannot poll status."
);
logger.info(
"In a few minutes, you can check status at " +
utils.consoleUrl(options.project, "/functions/logs")
);
logger.info(
"You can use the --only flag to deploy only a portion of your functions in the future."
);
deployments = []; // prevents analytics tracking of deployments
return Promise.resolve();
} else if (_.size(operations) > 40) {
interval = 10 * 1000;
} else if (_.size(operations) > 15) {
interval = 5 * 1000;
} else {
interval = 2 * 1000;
}
var pollFunction = gcp.cloudfunctions.check;
var printSuccess = function(op) {
_endTimer(op.func);
utils.logSuccess(
chalk.bold.green("functions[" + helper.getFunctionLabel(op.func) + "]: ") +
"Successful " +
op.type +
" operation. "
);
if (op.triggerUrl && op.type !== "delete") {
logger.info(
chalk.bold("Function URL"),
"(" + helper.getFunctionName(op.func) + "):",
op.triggerUrl
);
}
};
var printFail = function(op) {
_endTimer(op.func);
failedDeployments += 1;
utils.logWarning(
chalk.bold.yellow("functions[" + helper.getFunctionLabel(op.func) + "]: ") +
"Deployment error."
);
if (op.error.code === 8) {
logger.debug(op.error.message);
logger.info(
"You have exceeded your deployment quota, please deploy your functions in batches by using the --only flag, " +
"and wait a few minutes before deploying again. Go to https://firebase.google.com/docs/cli/#partial_deploys to learn more."
);
} else {
logger.info(op.error.message);
}
};
var retryCondition = function(result) {
// The error codes from a Google.LongRunning operation follow google.rpc.Code format.
var retryableCodes = [
1, // cancelled by client
4, // deadline exceeded
10, // aborted (typically due to concurrency issue)
14, // unavailable
];
if (_.includes(retryableCodes, result.error.code)) {
return true;
}
return false;
};
return pollOperation.pollAndRetry(
operations,
pollFunction,
interval,
printSuccess,
printFail,
retryCondition
);
}
function _startTimer(name, type) {
timings[name] = { type: type, t0: process.hrtime() };
}
function _endTimer(name) {
if (!timings[name]) {
logger.debug("[functions] no timer initialized for", name);
return;
}
// hrtime returns a duration as an array of [seconds, nanos]
var duration = process.hrtime(timings[name].t0);
track(
"Functions Deploy (Duration)",
timings[name].type,
duration[0] * 1000 + Math.round(duration[1] * 1e-6)
);
}
function _fetchTriggerUrls(projectId, ops, sourceUrl) {
if (!_.find(ops, ["trigger.httpsTrigger", {}])) {
// No HTTPS functions being deployed
return Promise.resolve();
}
// TODO make multi region
return gcp.cloudfunctions.listAll(projectId).then(function(functions) {
var httpFunctions = _.chain(functions)
.filter({ sourceUploadUrl: sourceUrl })
.filter("httpsTrigger")
.value();
_.forEach(httpFunctions, function(httpFunc) {
_.chain(ops)
.find({ func: httpFunc.name })
.assign({ triggerUrl: httpFunc.httpsTrigger.url })
.value();
});
return Promise.resolve();
});
}
module.exports = function(context, options, payload) {
if (!options.config.has("functions")) {
return Promise.resolve();
}
var GCP_REGION = gcp.cloudfunctions.DEFAULT_REGION;
var projectId = context.projectId;
var sourceUrl = context.uploadUrl;
// Used in CLI releases v3.4.0 to v3.17.6
@@ -23,270 +155,60 @@ module.exports = function(context, options, payload) {
"gs://" + "staging." + context.firebaseConfig.storageBucket + "/firebase-functions-source";
// Used in CLI releases v3.3.0 and prior
var legacySourceUrlOne = "gs://" + projectId + "-gcf/" + projectId;
var CLI_DEPLOYMENT_TOOL = "cli-firebase";
var CLI_DEPLOYMENT_LABELS = {
"deployment-tool": CLI_DEPLOYMENT_TOOL,
};
var functionsInfo = payload.functions.triggers;
var functionsInfo = helper.getFunctionsInfo(payload.functions.triggers, projectId);
var uploadedNames = _.map(functionsInfo, "name");
var timings = {};
var failedDeployments = 0;
var deployments = [];
function _startTimer(name, type) {
timings[name] = { type: type, t0: process.hrtime() };
}
function _endTimer(name) {
if (!timings[name]) {
logger.debug("[functions] no timer initialized for", name);
return;
}
// hrtime returns a duration as an array of [seconds, nanos]
var duration = process.hrtime(timings[name].t0);
track(
"Functions Deploy (Duration)",
timings[name].type,
duration[0] * 1000 + Math.round(duration[1] * 1e-6)
);
}
function _fetchTriggerUrls(ops) {
if (!_.find(ops, ["trigger.httpsTrigger", {}])) {
// No HTTPS functions being deployed
return Promise.resolve();
}
return gcp.cloudfunctions.list(projectId, GCP_REGION).then(function(functions) {
var httpFunctions = _.chain(functions)
.filter({ sourceUploadUrl: sourceUrl })
.filter("httpsTrigger")
.value();
_.forEach(httpFunctions, function(httpFunc) {
_.chain(ops)
.find({ func: httpFunc.name })
.assign({ triggerUrl: httpFunc.httpsTrigger.url })
.value();
});
return Promise.resolve();
});
}
function _functionMatchesGroup(functionName, groupChunks) {
return _.isEqual(groupChunks, functionName.split("-").slice(0, groupChunks.length));
}
function _getFilterGroups() {
if (!options.only) {
return [];
}
var opts;
return _.chain(options.only.split(","))
.filter(function(filter) {
opts = filter.split(":");
return opts[0] === "functions" && opts[1];
})
.map(function(filter) {
return filter.split(":")[1].split(".");
})
.value();
}
function _getReleaseNames(uploadNames, existingNames, functionFilterGroups) {
if (functionFilterGroups.length === 0) {
return uploadNames;
}
var allFunctions = _.union(uploadNames, existingNames);
return _.filter(allFunctions, function(functionName) {
return _.some(
_.map(functionFilterGroups, function(groupChunks) {
return _functionMatchesGroup(functionName, groupChunks);
})
);
});
}
function _logFilters(existingNames, releaseNames, functionFilterGroups) {
if (functionFilterGroups.length === 0) {
return;
}
logger.debug("> [functions] filtering triggers to: " + JSON.stringify(releaseNames, null, 2));
track("Functions Deploy with Filter", "", releaseNames.length);
if (existingNames.length > 0) {
utils.logBullet(
chalk.bold.cyan("functions: ") + "current functions in project: " + existingNames.join(", ")
);
}
if (releaseNames.length > 0) {
utils.logBullet(
chalk.bold.cyan("functions: ") +
"uploading functions in project: " +
releaseNames.join(", ")
);
}
var allFunctions = _.union(releaseNames, existingNames);
var unmatchedFilters = _.chain(functionFilterGroups)
.filter(function(filterGroup) {
return !_.some(
_.map(allFunctions, function(functionName) {
return _functionMatchesGroup(functionName, filterGroup);
})
);
})
.map(function(group) {
return group.join("-");
})
.value();
if (unmatchedFilters.length > 0) {
utils.logWarning(
chalk.bold.yellow("functions: ") +
"the following filters were specified but do not match any functions in the project: " +
unmatchedFilters.join(", ")
);
}
}
function _pollAndManageOperations(operations) {
var interval;
// Poll less frequently when there are many operations to avoid hitting read quota.
// See "Read requests" quota at https://cloud.google.com/console/apis/api/cloudfunctions/quotas
if (_.size(operations) > 90) {
utils.logWarning(
chalk.bold.yellow("functions:") +
" too many functions are being deployed, cannot poll status."
);
logger.info(
"In a few minutes, you can check status at " +
utils.consoleUrl(options.project, "/functions/logs")
);
logger.info(
"You can use the --only flag to deploy only a portion of your functions in the future."
);
deployments = []; // prevents analytics tracking of deployments
return Promise.resolve();
} else if (_.size(operations) > 40) {
interval = 10 * 1000;
} else if (_.size(operations) > 15) {
interval = 5 * 1000;
} else {
interval = 2 * 1000;
}
var pollFunction = gcp.cloudfunctions.check;
var printSuccess = function(op) {
_endTimer(op.functionName);
utils.logSuccess(
chalk.bold.green("functions[" + op.functionName + "]: ") +
"Successful " +
op.type +
" operation. "
);
if (op.triggerUrl && op.type !== "delete") {
logger.info(chalk.bold("Function URL"), "(" + op.functionName + "):", op.triggerUrl);
}
};
var printFail = function(op) {
_endTimer(op.functionName);
failedDeployments += 1;
utils.logWarning(
chalk.bold.yellow("functions[" + op.functionName + "]: ") + "Deployment error."
);
if (op.error.code === 8) {
logger.debug(op.error.message);
logger.info(
"You have exceeded your deployment quota, please deploy your functions in batches by using the --only flag, " +
"and wait a few minutes before deploying again. Go to https://firebase.google.com/docs/cli/#partial_deploys to learn more."
);
} else {
logger.info(op.error.message);
}
};
var retryCondition = function(result) {
// The error codes from a Google.LongRunning operation follow google.rpc.Code format.
var retryableCodes = [
1, // cancelled by client
4, // deadline exceeded
10, // aborted (typically due to concurrency issue)
14, // unavailable
];
if (_.includes(retryableCodes, result.error.code)) {
return true;
}
return false;
};
return pollOperation.pollAndRetry(
operations,
pollFunction,
interval,
printSuccess,
printFail,
retryCondition
);
}
function _getFunctionTrigger(functionInfo) {
if (functionInfo.httpsTrigger) {
return _.pick(functionInfo, "httpsTrigger");
} else if (functionInfo.eventTrigger) {
var trigger = functionInfo.eventTrigger;
return { eventTrigger: trigger };
}
logger.debug("Unknown trigger type found in:", functionInfo);
return new FirebaseError("Could not parse function trigger, unknown trigger type.");
}
delete payload.functions;
return gcp.cloudfunctions
.list(projectId, GCP_REGION)
.listAll(projectId)
.then(function(existingFunctions) {
var pluckName = function(functionObject) {
var fullName = _.get(functionObject, "name"); // e.g.'projects/proj1/locations/us-central1/functions/func'
return _.last(fullName.split("/"));
return _.get(functionObject, "name"); // e.g.'projects/proj1/locations/us-central1/functions/func'
};
var existingNames = _.map(existingFunctions, pluckName);
var functionFilterGroups = _getFilterGroups();
var releaseNames = _getReleaseNames(uploadedNames, existingNames, functionFilterGroups);
var functionFilterGroups = helper.getFilterGroups(options);
var releaseNames = helper.getReleaseNames(uploadedNames, existingNames, functionFilterGroups);
// If not using function filters, then `deleteReleaseNames` should be equivalent to existingNames so that intersection is a noop
var deleteReleaseNames = functionFilterGroups.length > 0 ? releaseNames : existingNames;
_logFilters(existingNames, releaseNames, functionFilterGroups);
helper.logFilters(existingNames, releaseNames, functionFilterGroups);
// Create functions
_.chain(uploadedNames)
.difference(existingNames)
.intersection(releaseNames)
.forEach(function(functionName) {
var functionInfo = _.find(functionsInfo, { name: functionName });
var functionTrigger = _getFunctionTrigger(functionInfo);
.forEach(function(name) {
var functionInfo = _.find(functionsInfo, { name: name });
var functionTrigger = helper.getFunctionTrigger(functionInfo);
var functionName = helper.getFunctionName(name);
var region = helper.getRegion(name);
utils.logBullet(
chalk.bold.cyan("functions: ") + "creating function " + chalk.bold(functionName) + "..."
chalk.bold.cyan("functions: ") +
"creating function " +
chalk.bold(helper.getFunctionLabel(name)) +
"..."
);
logger.debug("Trigger is: ", JSON.stringify(functionTrigger));
var eventType = functionTrigger.eventTrigger
? functionTrigger.eventTrigger.eventType
: "https";
_startTimer(functionName, "create");
_startTimer(name, "create");
deployments.push({
functionName: functionName,
name: name,
retryFunction: function() {
return gcp.cloudfunctions.create({
projectId: projectId,
region: GCP_REGION,
region: region,
eventType: eventType,
functionName: functionName,
entryPoint: functionInfo.entryPoint,
trigger: functionTrigger,
labels: CLI_DEPLOYMENT_LABELS,
labels: _.assign({}, CLI_DEPLOYMENT_LABELS, functionsInfo.labels),
sourceUploadUrl: sourceUrl,
availableMemoryMb: functionInfo.availableMemoryMb,
timeout: functionInfo.timeout,
});
},
trigger: functionTrigger,
@@ -298,11 +220,17 @@ module.exports = function(context, options, payload) {
_.chain(uploadedNames)
.intersection(existingNames)
.intersection(releaseNames)
.forEach(function(functionName) {
var functionInfo = _.find(functionsInfo, { name: functionName });
var functionTrigger = _getFunctionTrigger(functionInfo);
.forEach(function(name) {
var functionInfo = _.find(functionsInfo, { name: name });
var functionTrigger = helper.getFunctionTrigger(functionInfo);
var functionName = helper.getFunctionName(name);
var region = helper.getRegion(name);
utils.logBullet(
chalk.bold.cyan("functions: ") + "updating function " + chalk.bold(functionName) + "..."
chalk.bold.cyan("functions: ") +
"updating function " +
chalk.bold(helper.getFunctionLabel(name)) +
"..."
);
logger.debug("Trigger is: ", JSON.stringify(functionTrigger));
var eventType = functionTrigger.eventTrigger
@@ -336,17 +264,19 @@ module.exports = function(context, options, payload) {
"deleting this function.\n"
);
} else {
_startTimer(functionName, "update");
_startTimer(name, "update");
deployments.push({
functionName: functionName,
name: name,
retryFunction: function() {
return gcp.cloudfunctions.update({
projectId: projectId,
region: GCP_REGION,
region: region,
functionName: functionName,
trigger: functionTrigger,
sourceUploadUrl: sourceUrl,
labels: CLI_DEPLOYMENT_LABELS,
labels: _.assign({}, CLI_DEPLOYMENT_LABELS, functionsInfo.labels),
availableMemoryMb: functionInfo.availableMemoryMb,
timeout: functionInfo.timeout,
});
},
trigger: functionTrigger,
@@ -369,17 +299,23 @@ module.exports = function(context, options, payload) {
.map(pluckName)
.difference(uploadedNames)
.intersection(deleteReleaseNames)
.map(function(functionName) {
.map(function(name) {
var functionName = helper.getFunctionName(name);
var region = helper.getRegion(name);
utils.logBullet(
chalk.bold.cyan("functions: ") + "deleting function " + chalk.bold(functionName) + "..."
chalk.bold.cyan("functions: ") +
"deleting function " +
chalk.bold(helper.getFunctionLabel(name)) +
"..."
);
_startTimer(functionName, "delete");
_startTimer(name, "delete");
deployments.push({
functionName: functionName,
name: name,
retryFunction: function() {
return gcp.cloudfunctions.delete({
projectId: projectId,
region: GCP_REGION,
region: region,
functionName: functionName,
});
},
@@ -406,7 +342,7 @@ module.exports = function(context, options, payload) {
.value();
failedDeployments += failedCalls.length;
return _fetchTriggerUrls(successfulCalls)
return _fetchTriggerUrls(projectId, successfulCalls, sourceUrl)
.then(function() {
return _pollAndManageOperations(successfulCalls).catch(function() {
utils.logWarning(

View File

@@ -0,0 +1,149 @@
"use strict";
var _ = require("lodash");
var chalk = require("chalk");
var FirebaseError = require("./error");
var logger = require("./logger");
var track = require("./track");
var utils = require("./utils");
function _functionMatchesGroup(functionName, groupChunks) {
return _.isEqual(
groupChunks,
_.last(functionName.split("/"))
.split("-")
.slice(0, groupChunks.length)
);
}
function getFilterGroups(options) {
if (!options.only) {
return [];
}
var opts;
return _.chain(options.only.split(","))
.filter(function(filter) {
opts = filter.split(":");
return opts[0] === "functions" && opts[1];
})
.map(function(filter) {
return filter.split(":")[1].split(".");
})
.value();
}
function getReleaseNames(uploadNames, existingNames, functionFilterGroups) {
if (functionFilterGroups.length === 0) {
return uploadNames;
}
var allFunctions = _.union(uploadNames, existingNames);
return _.filter(allFunctions, function(functionName) {
return _.some(
_.map(functionFilterGroups, function(groupChunks) {
return _functionMatchesGroup(functionName, groupChunks);
})
);
});
}
function logFilters(existingNames, releaseNames, functionFilterGroups) {
if (functionFilterGroups.length === 0) {
return;
}
logger.debug("> [functions] filtering triggers to: " + JSON.stringify(releaseNames, null, 2));
track("Functions Deploy with Filter", "", releaseNames.length);
if (existingNames.length > 0) {
var list = _.map(existingNames, function(name) {
return getFunctionName(name) + "(" + getRegion(name) + ")";
}).join(", ");
utils.logBullet(chalk.bold.cyan("functions: ") + "current functions in project: " + list);
}
if (releaseNames.length > 0) {
var list = _.map(releaseNames, function(name) {
return getFunctionName(name) + "(" + getRegion(name) + ")";
}).join(", ");
utils.logBullet(chalk.bold.cyan("functions: ") + "uploading functions in project: " + list);
}
var allFunctions = _.union(releaseNames, existingNames);
var unmatchedFilters = _.chain(functionFilterGroups)
.filter(function(filterGroup) {
return !_.some(
_.map(allFunctions, function(functionName) {
return _functionMatchesGroup(functionName, filterGroup);
})
);
})
.map(function(group) {
return group.join("-");
})
.value();
if (unmatchedFilters.length > 0) {
utils.logWarning(
chalk.bold.yellow("functions: ") +
"the following filters were specified but do not match any functions in the project: " +
unmatchedFilters.join(", ")
);
}
}
function getFunctionsInfo(parsedTriggers, projectId) {
var functionsInfo = [];
_.forEach(parsedTriggers, function(trigger) {
if (!trigger.regions) {
trigger.regions = ["us-central1"];
}
// SDK exports list of regions for each function to be deployed to, need to add a new entry
// to functionsInfo for each region.
_.forEach(trigger.regions, function(region) {
functionsInfo.push(
_.chain(trigger)
.omit("regions")
.assign({
name: ["projects", projectId, "locations", region, "functions", trigger.name].join("/"),
})
.value()
);
});
});
return functionsInfo;
}
function getFunctionTrigger(functionInfo) {
if (functionInfo.httpsTrigger) {
return _.pick(functionInfo, "httpsTrigger");
} else if (functionInfo.eventTrigger) {
var trigger = functionInfo.eventTrigger;
return { eventTrigger: trigger };
}
logger.debug("Unknown trigger type found in:", functionInfo);
return new FirebaseError("Could not parse function trigger, unknown trigger type.");
}
function getFunctionName(fullName) {
return fullName.split("/")[5];
}
function getRegion(fullName) {
return fullName.split("/")[3];
}
function getFunctionLabel(fullName) {
return getFunctionName(fullName) + "(" + getRegion(fullName) + ")";
}
module.exports = {
getFilterGroups: getFilterGroups,
getReleaseNames: getReleaseNames,
logFilters: logFilters,
getFunctionsInfo: getFunctionsInfo,
getFunctionTrigger: getFunctionTrigger,
getFunctionName: getFunctionName,
getRegion: getRegion,
getFunctionLabel: getFunctionLabel,
};

View File

@@ -58,11 +58,11 @@ function _createFunction(options) {
entryPoint: options.entryPoint,
labels: options.labels,
};
if (options.availableMemory) {
data.availableMemoryMb = options.availableMemory;
if (options.availableMemoryMb) {
data.availableMemoryMb = options.availableMemoryMb;
}
if (options.functionTimeout) {
data.timeout = options.functionTimeout;
if (options.timeout) {
data.timeout = options.timeout;
}
return api
.request("POST", endpoint, {
@@ -100,6 +100,14 @@ function _updateFunction(options) {
);
var masks = ["sourceUploadUrl", "name", "labels"];
if (options.availableMemoryMb) {
data.availableMemoryMb = options.availableMemoryMb;
masks.push("availableMemoryMb");
}
if (options.timeout) {
data.timeout = options.timeout;
masks.push("timeout");
}
if (options.trigger.eventTrigger) {
masks = _.concat(
masks,
@@ -183,6 +191,11 @@ function _listFunctions(projectId, region) {
);
}
function _listAllFunctions(projectId) {
// "-" instead of a region string lists functions in all regions
return _listFunctions(projectId, "-");
}
function _checkOperation(operation) {
return api
.request("GET", "/" + API_VERSION + "/" + operation.name, {
@@ -215,5 +228,6 @@ module.exports = {
update: _updateFunction,
delete: _deleteFunction,
list: _listFunctions,
listAll: _listAllFunctions,
check: _checkOperation,
};

View File

@@ -0,0 +1,155 @@
"use strict";
var chai = require("chai");
var expect = chai.expect;
var helper = require("../../lib/functionsDeployHelper");
describe("functionsDeployHelper", function() {
describe("getFilterGroups", function() {
it("should parse multiple filters", function() {
var options = {
only: "functions:myFunc,functions:myOtherFunc",
};
expect(helper.getFilterGroups(options)).to.deep.equal([["myFunc"], ["myOtherFunc"]]);
});
it("should parse nested filters", function() {
var options = {
only: "functions:groupA.myFunc",
};
expect(helper.getFilterGroups(options)).to.deep.equal([["groupA", "myFunc"]]);
});
});
describe("getReleaseNames", function() {
it("should handle function update", function() {
var uploadNames = ["projects/myProject/locations/us-central1/functions/myFunc"];
var existingNames = ["projects/myProject/locations/us-central1/functions/myFunc"];
var filter = [["myFunc"]];
expect(helper.getReleaseNames(uploadNames, existingNames, filter)).to.deep.equal([
"projects/myProject/locations/us-central1/functions/myFunc",
]);
});
it("should handle function deletion", function() {
var uploadNames = [];
var existingNames = ["projects/myProject/locations/us-central1/functions/myFunc"];
var filter = [["myFunc"]];
expect(helper.getReleaseNames(uploadNames, existingNames, filter)).to.deep.equal([
"projects/myProject/locations/us-central1/functions/myFunc",
]);
});
it("should handle function creation", function() {
var uploadNames = ["projects/myProject/locations/us-central1/functions/myFunc"];
var existingNames = [];
var filter = [["myFunc"]];
expect(helper.getReleaseNames(uploadNames, existingNames, filter)).to.deep.equal([
"projects/myProject/locations/us-central1/functions/myFunc",
]);
});
it("should handle existing function not being in filter", function() {
var uploadNames = ["projects/myProject/locations/us-central1/functions/myFunc"];
var existingNames = ["projects/myProject/locations/us-central1/functions/myFunc2"];
var filter = [["myFunc"]];
expect(helper.getReleaseNames(uploadNames, existingNames, filter)).to.deep.equal([
"projects/myProject/locations/us-central1/functions/myFunc",
]);
});
it("should handle no functions satisfying filter", function() {
var uploadNames = ["projects/myProject/locations/us-central1/functions/myFunc2"];
var existingNames = ["projects/myProject/locations/us-central1/functions/myFunc3"];
var filter = [["myFunc"]];
expect(helper.getReleaseNames(uploadNames, existingNames, filter)).to.deep.equal([]);
});
it("should handle entire function groups", function() {
var uploadNames = ["projects/myProject/locations/us-central1/functions/myGroup-func1"];
var existingNames = ["projects/myProject/locations/us-central1/functions/myGroup-func2"];
var filter = [["myGroup"]];
expect(helper.getReleaseNames(uploadNames, existingNames, filter)).to.deep.equal([
"projects/myProject/locations/us-central1/functions/myGroup-func1",
"projects/myProject/locations/us-central1/functions/myGroup-func2",
]);
});
it("should handle functions within groups", function() {
var uploadNames = ["projects/myProject/locations/us-central1/functions/myGroup-func1"];
var existingNames = ["projects/myProject/locations/us-central1/functions/myGroup-func2"];
var filter = [["myGroup", "func1"]];
expect(helper.getReleaseNames(uploadNames, existingNames, filter)).to.deep.equal([
"projects/myProject/locations/us-central1/functions/myGroup-func1",
]);
});
});
describe("getFunctionsInfo", function() {
it("should handle default region", function() {
var triggers = [
{
name: "myFunc",
},
{
name: "myOtherFunc",
},
];
expect(helper.getFunctionsInfo(triggers, "myProject")).to.deep.equal([
{
name: "projects/myProject/locations/us-central1/functions/myFunc",
},
{
name: "projects/myProject/locations/us-central1/functions/myOtherFunc",
},
]);
});
it("should handle customized region", function() {
var triggers = [
{
name: "myFunc",
regions: ["us-east1"],
},
{
name: "myOtherFunc",
},
];
expect(helper.getFunctionsInfo(triggers, "myProject")).to.deep.equal([
{
name: "projects/myProject/locations/us-east1/functions/myFunc",
},
{
name: "projects/myProject/locations/us-central1/functions/myOtherFunc",
},
]);
});
it("should handle multiple customized region for a function", function() {
var triggers = [
{
name: "myFunc",
regions: ["us-east1", "eu-west1"],
},
];
expect(helper.getFunctionsInfo(triggers, "myProject")).to.deep.equal([
{
name: "projects/myProject/locations/us-east1/functions/myFunc",
},
{
name: "projects/myProject/locations/eu-west1/functions/myFunc",
},
]);
});
});
});