firebase-tools
Version:
Command-Line Interface for Firebase
343 lines (342 loc) • 12.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.missingEndpoint = exports.hasEndpoint = exports.AllFunctionsPlatforms = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_CPU_FOR_CONCURRENCY = exports.DEFAULT_MEMORY = exports.DEFAULT_CONCURRENCY = exports.AllIngressSettings = exports.AllVpcEgressSettings = void 0;
exports.endpointTriggerType = endpointTriggerType;
exports.isValidMemoryOption = isValidMemoryOption;
exports.isValidEgressSetting = isValidEgressSetting;
exports.memoryOptionDisplayName = memoryOptionDisplayName;
exports.memoryToGen1Cpu = memoryToGen1Cpu;
exports.memoryToGen2Cpu = memoryToGen2Cpu;
exports.secretVersionName = secretVersionName;
exports.isHttpsTriggered = isHttpsTriggered;
exports.isDataConnectGraphqlTriggered = isDataConnectGraphqlTriggered;
exports.isCallableTriggered = isCallableTriggered;
exports.isEventTriggered = isEventTriggered;
exports.isScheduleTriggered = isScheduleTriggered;
exports.isTaskQueueTriggered = isTaskQueueTriggered;
exports.isBlockingTriggered = isBlockingTriggered;
exports.empty = empty;
exports.of = of;
exports.merge = merge;
exports.isEmptyBackend = isEmptyBackend;
exports.functionName = functionName;
exports.scheduleIdForFunction = scheduleIdForFunction;
exports.existingBackend = existingBackend;
exports.checkAvailability = checkAvailability;
exports.allEndpoints = allEndpoints;
exports.someEndpoint = someEndpoint;
exports.findEndpoint = findEndpoint;
exports.matchingBackend = matchingBackend;
exports.regionalEndpoints = regionalEndpoints;
exports.compareFunctions = compareFunctions;
const gcf = require("../../gcp/cloudfunctions");
const gcfV2 = require("../../gcp/cloudfunctionsv2");
const run = require("../../gcp/runv2");
const utils = require("../../utils");
const error_1 = require("../../error");
const functional_1 = require("../../functional");
const logger_1 = require("../../logger");
const experiments = require("../../experiments");
function endpointTriggerType(endpoint) {
if (isScheduleTriggered(endpoint)) {
return "scheduled";
}
else if (isHttpsTriggered(endpoint)) {
return "https";
}
else if (isDataConnectGraphqlTriggered(endpoint)) {
return "dataConnectGraphql";
}
else if (isCallableTriggered(endpoint)) {
return "callable";
}
else if (isEventTriggered(endpoint)) {
return endpoint.eventTrigger.eventType;
}
else if (isTaskQueueTriggered(endpoint)) {
return "taskQueue";
}
else if (isBlockingTriggered(endpoint)) {
return endpoint.blockingTrigger.eventType;
}
(0, functional_1.assertExhaustive)(endpoint);
}
exports.AllVpcEgressSettings = ["PRIVATE_RANGES_ONLY", "ALL_TRAFFIC"];
exports.AllIngressSettings = [
"ALLOW_ALL",
"ALLOW_INTERNAL_ONLY",
"ALLOW_INTERNAL_AND_GCLB",
];
const allMemoryOptions = [128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768];
function isValidMemoryOption(mem) {
return allMemoryOptions.includes(mem);
}
function isValidEgressSetting(egress) {
return egress === "PRIVATE_RANGES_ONLY" || egress === "ALL_TRAFFIC";
}
function memoryOptionDisplayName(option) {
return {
128: "128MB",
256: "256MB",
512: "512MB",
1024: "1GB",
2048: "2GB",
4096: "4GB",
8192: "8GB",
16384: "16GB",
32768: "32GB",
}[option];
}
function memoryToGen1Cpu(memory) {
return {
128: 0.0833,
256: 0.1666,
512: 0.3333,
1024: 0.5833,
2048: 1,
4096: 2,
8192: 2,
16384: 4,
32768: 8,
}[memory];
}
function memoryToGen2Cpu(memory) {
return {
128: 1,
256: 1,
512: 1,
1024: 1,
2048: 1,
4096: 2,
8192: 2,
16384: 4,
32768: 8,
}[memory];
}
exports.DEFAULT_CONCURRENCY = 80;
exports.DEFAULT_MEMORY = 256;
exports.MIN_CPU_FOR_CONCURRENCY = 1;
exports.SCHEDULED_FUNCTION_LABEL = Object.freeze({ deployment: "firebase-schedule" });
function secretVersionName(s) {
return `projects/${s.projectId}/secrets/${s.secret}/versions/${s.version ?? "latest"}`;
}
exports.AllFunctionsPlatforms = ["gcfv1", "gcfv2", "run"];
function isHttpsTriggered(triggered) {
return {}.hasOwnProperty.call(triggered, "httpsTrigger");
}
function isDataConnectGraphqlTriggered(triggered) {
return {}.hasOwnProperty.call(triggered, "dataConnectGraphqlTrigger");
}
function isCallableTriggered(triggered) {
return {}.hasOwnProperty.call(triggered, "callableTrigger");
}
function isEventTriggered(triggered) {
return {}.hasOwnProperty.call(triggered, "eventTrigger");
}
function isScheduleTriggered(triggered) {
return {}.hasOwnProperty.call(triggered, "scheduleTrigger");
}
function isTaskQueueTriggered(triggered) {
return {}.hasOwnProperty.call(triggered, "taskQueueTrigger");
}
function isBlockingTriggered(triggered) {
return {}.hasOwnProperty.call(triggered, "blockingTrigger");
}
function empty() {
return {
requiredAPIs: [],
endpoints: {},
environmentVariables: {},
};
}
function of(...endpoints) {
const bkend = { ...empty() };
for (const endpoint of endpoints) {
bkend.endpoints[endpoint.region] = bkend.endpoints[endpoint.region] || {};
if (bkend.endpoints[endpoint.region][endpoint.id]) {
throw new Error("Trying to create a backend with the same endpoint twice");
}
bkend.endpoints[endpoint.region][endpoint.id] = endpoint;
}
return bkend;
}
function merge(...backends) {
const merged = of(...(0, functional_1.flattenArray)(backends.map((b) => allEndpoints(b))));
const apiToReasons = {};
for (const b of backends) {
for (const { api, reason } of b.requiredAPIs) {
const reasons = apiToReasons[api] || new Set();
if (reason) {
reasons.add(reason);
}
apiToReasons[api] = reasons;
}
merged.environmentVariables = { ...merged.environmentVariables, ...b.environmentVariables };
}
for (const [api, reasons] of Object.entries(apiToReasons)) {
merged.requiredAPIs.push({ api, reason: Array.from(reasons).join(" ") });
}
return merged;
}
function isEmptyBackend(backend) {
return (Object.keys(backend.requiredAPIs).length === 0 && Object.keys(backend.endpoints).length === 0);
}
function functionName(cloudFunction) {
return `projects/${cloudFunction.project}/locations/${cloudFunction.region}/functions/${cloudFunction.id}`;
}
function scheduleIdForFunction(cloudFunction) {
return `firebase-schedule-${cloudFunction.id}-${cloudFunction.region}`;
}
function existingBackend(context, forceRefresh) {
if (!context.existingBackendPromise || forceRefresh) {
context.existingBackendPromise = loadExistingBackend(context);
}
return context.existingBackendPromise;
}
async function loadExistingBackend(ctx) {
const existingBackend = {
...empty(),
};
const unreachableRegions = {
gcfV1: [],
gcfV2: [],
run: [],
};
const gcfV1Results = await gcf.listAllFunctions(ctx.projectId);
for (const apiFunction of gcfV1Results.functions) {
const endpoint = gcf.endpointFromFunction(apiFunction);
existingBackend.endpoints[endpoint.region] = existingBackend.endpoints[endpoint.region] || {};
existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
}
unreachableRegions.gcfV1 = gcfV1Results.unreachable;
if (experiments.isEnabled("functionsrunapionly")) {
try {
const runServices = await run.listServices(ctx.projectId);
for (const service of runServices) {
const endpoint = run.endpointFromService(service);
existingBackend.endpoints[endpoint.region] =
existingBackend.endpoints[endpoint.region] || {};
existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
}
}
catch (err) {
logger_1.logger.debug(err.message);
unreachableRegions.run = ["unknown"];
}
}
else {
const gcfV2Results = await gcfV2.listAllFunctions(ctx.projectId);
for (const apiFunction of gcfV2Results.functions) {
const endpoint = gcfV2.endpointFromFunction(apiFunction);
existingBackend.endpoints[endpoint.region] = existingBackend.endpoints[endpoint.region] || {};
existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
}
unreachableRegions.gcfV2 = gcfV2Results.unreachable;
}
ctx.existingBackend = existingBackend;
ctx.unreachableRegions = unreachableRegions;
return ctx.existingBackend;
}
async function checkAvailability(context, want) {
await existingBackend(context);
const gcfV1Regions = new Set();
const gcfV2Regions = new Set();
for (const ep of allEndpoints(want)) {
if (ep.platform === "gcfv1") {
gcfV1Regions.add(ep.region);
}
else {
gcfV2Regions.add(ep.region);
}
}
const neededUnreachableV1 = context.unreachableRegions?.gcfV1.filter((region) => gcfV1Regions.has(region));
const neededUnreachableV2 = context.unreachableRegions?.gcfV2.filter((region) => gcfV2Regions.has(region));
if (neededUnreachableV1?.length) {
throw new error_1.FirebaseError("The following Cloud Functions regions are currently unreachable:\n\t" +
neededUnreachableV1.join("\n\t") +
"\nThis deployment contains functions in those regions. Please try again in a few minutes, or exclude these regions from your deployment.");
}
if (neededUnreachableV2?.length) {
throw new error_1.FirebaseError("The following Cloud Functions V2 regions are currently unreachable:\n\t" +
neededUnreachableV2.join("\n\t") +
"\nThis deployment contains functions in those regions. Please try again in a few minutes, or exclude these regions from your deployment.");
}
if (context.unreachableRegions?.gcfV1.length) {
utils.logLabeledWarning("functions", "The following Cloud Functions regions are currently unreachable:\n" +
context.unreachableRegions.gcfV1.join("\n") +
"\nCloud Functions in these regions won't be deleted.");
}
if (context.unreachableRegions?.gcfV2.length) {
utils.logLabeledWarning("functions", "The following Cloud Functions V2 regions are currently unreachable:\n" +
context.unreachableRegions.gcfV2.join("\n") +
"\nCloud Functions in these regions won't be deleted.");
}
if (context.unreachableRegions?.run.length) {
utils.logLabeledWarning("functions", "The following Cloud Run regions are currently unreachable:\n" +
context.unreachableRegions.run.join("\n") +
"\nCloud Run services in these regions won't be deleted.");
}
}
function allEndpoints(backend) {
return Object.values(backend.endpoints).reduce((accum, perRegion) => {
return [...accum, ...Object.values(perRegion)];
}, []);
}
function someEndpoint(backend, predicate) {
for (const endpoints of Object.values(backend.endpoints)) {
if (Object.values(endpoints).some(predicate)) {
return true;
}
}
return false;
}
function findEndpoint(backend, predicate) {
for (const endpoints of Object.values(backend.endpoints)) {
const endpoint = Object.values(endpoints).find(predicate);
if (endpoint)
return endpoint;
}
}
function matchingBackend(backend, predicate) {
const filtered = {
...backend,
endpoints: {},
};
for (const endpoint of allEndpoints(backend)) {
if (!predicate(endpoint)) {
continue;
}
filtered.endpoints[endpoint.region] = filtered.endpoints[endpoint.region] || {};
filtered.endpoints[endpoint.region][endpoint.id] = endpoint;
}
return filtered;
}
function regionalEndpoints(backend, region) {
return backend.endpoints[region] ? Object.values(backend.endpoints[region]) : [];
}
const hasEndpoint = (backend) => (endpoint) => {
return (!!backend.endpoints[endpoint.region] && !!backend.endpoints[endpoint.region][endpoint.id]);
};
exports.hasEndpoint = hasEndpoint;
const missingEndpoint = (backend) => (endpoint) => {
return !(0, exports.hasEndpoint)(backend)(endpoint);
};
exports.missingEndpoint = missingEndpoint;
function compareFunctions(left, right) {
if (left.platform !== right.platform) {
return right.platform < left.platform ? -1 : 1;
}
if (left.region < right.region) {
return -1;
}
if (left.region > right.region) {
return 1;
}
if (left.id < right.id) {
return -1;
}
if (left.id > right.id) {
return 1;
}
return 0;
}