firebase-tools
Version:
Command-Line Interface for Firebase
379 lines (378 loc) • 16 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AllIngressSettings = exports.AllVpcEgressSettings = exports.AllFunctionsPlatforms = void 0;
exports.empty = empty;
exports.of = of;
exports.isHttpsTriggered = isHttpsTriggered;
exports.isDataConnectGraphqlTriggered = isDataConnectGraphqlTriggered;
exports.isCallableTriggered = isCallableTriggered;
exports.isEventTriggered = isEventTriggered;
exports.isScheduleTriggered = isScheduleTriggered;
exports.isTaskQueueTriggered = isTaskQueueTriggered;
exports.isBlockingTriggered = isBlockingTriggered;
exports.resolveBackend = resolveBackend;
exports.envWithTypes = envWithTypes;
exports.toBackend = toBackend;
exports.applyPrefix = applyPrefix;
const backend = require("./backend");
const proto = require("../../gcp/proto");
const api = require("../../api");
const params = require("./params");
const error_1 = require("../../error");
const functional_1 = require("../../functional");
const cel_1 = require("./cel");
function empty() {
return {
requiredAPIs: [],
endpoints: {},
params: [],
};
}
function of(endpoints) {
const build = empty();
build.endpoints = endpoints;
return build;
}
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");
}
const allMemoryOptions = [128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768];
exports.AllFunctionsPlatforms = ["gcfv1", "gcfv2", "run"];
exports.AllVpcEgressSettings = ["PRIVATE_RANGES_ONLY", "ALL_TRAFFIC"];
exports.AllIngressSettings = [
"ALLOW_ALL",
"ALLOW_INTERNAL_ONLY",
"ALLOW_INTERNAL_AND_GCLB",
];
async function resolveBackend(opts) {
const paramValues = await params.resolveParams(opts.build.params, opts.firebaseConfig, envWithTypes(opts.build.params, opts.userEnvs), opts.nonInteractive, opts.isEmulator);
return { backend: toBackend(opts.build, paramValues), envs: paramValues };
}
function envWithTypes(definedParams, rawEnvs) {
const out = {};
for (const envName of Object.keys(rawEnvs)) {
const value = rawEnvs[envName];
let providedType = {
string: true,
boolean: true,
number: true,
list: true,
};
for (const param of definedParams) {
if (param.name === envName) {
if (param.type === "string") {
providedType = {
string: true,
boolean: false,
number: false,
list: false,
};
}
else if (param.type === "int") {
providedType = {
string: false,
boolean: false,
number: true,
list: false,
};
}
else if (param.type === "boolean") {
providedType = {
string: false,
boolean: true,
number: false,
list: false,
};
}
else if (param.type === "list") {
providedType = {
string: false,
boolean: false,
number: false,
list: true,
};
}
else if (param.type === "secret") {
providedType = {
string: true,
boolean: false,
number: false,
list: false,
};
}
}
}
out[envName] = new params.ParamValue(value, false, providedType);
}
return out;
}
class Resolver {
constructor(paramValues) {
this.paramValues = paramValues;
this.resolveInt = (i) => {
if (i === null) {
return i;
}
return params.resolveInt(i, this.paramValues);
};
this.resolveBoolean = (i) => {
if (i === null) {
return i;
}
return params.resolveBoolean(i, this.paramValues);
};
this.resolveString = (i) => {
if (i === null) {
return i;
}
return params.resolveString(i, this.paramValues);
};
}
resolveStrings(dest, src, ...keys) {
for (const key of keys) {
const orig = src[key];
if (typeof orig === "undefined") {
continue;
}
dest[key] = orig === null ? null : params.resolveString(orig, this.paramValues);
}
}
resolveInts(dest, src, ...keys) {
for (const key of keys) {
const orig = src[key];
if (typeof orig === "undefined") {
continue;
}
dest[key] = orig === null ? null : params.resolveInt(orig, this.paramValues);
}
}
}
function toBackend(build, paramValues) {
const r = new Resolver(paramValues);
const bkEndpoints = [];
for (const endpointId of Object.keys(build.endpoints)) {
const bdEndpoint = build.endpoints[endpointId];
if (r.resolveBoolean(bdEndpoint.omit || false)) {
continue;
}
let regions = [];
if (!bdEndpoint.region) {
regions = [api.functionsDefaultRegion()];
}
else if (Array.isArray(bdEndpoint.region)) {
regions = params.resolveList(bdEndpoint.region, paramValues);
}
else {
try {
regions = params.resolveList(bdEndpoint.region, paramValues);
}
catch (err) {
if (err instanceof cel_1.ExprParseError) {
regions = [params.resolveString(bdEndpoint.region, paramValues)];
}
else {
throw err;
}
}
}
for (const region of regions) {
const trigger = discoverTrigger(bdEndpoint, region, r);
if (typeof bdEndpoint.platform === "undefined") {
throw new error_1.FirebaseError("platform can't be undefined");
}
const bkEndpoint = {
id: endpointId,
project: bdEndpoint.project,
region: region,
entryPoint: bdEndpoint.entryPoint,
platform: bdEndpoint.platform,
runtime: bdEndpoint.runtime,
...trigger,
};
proto.copyIfPresent(bkEndpoint, bdEndpoint, "environmentVariables", "labels", "secretEnvironmentVariables", "baseImageUri", "command", "args");
r.resolveStrings(bkEndpoint, bdEndpoint, "serviceAccount");
proto.convertIfPresent(bkEndpoint, bdEndpoint, "ingressSettings", (from) => {
if (from !== null && !backend.AllIngressSettings.includes(from)) {
throw new error_1.FirebaseError(`Cannot set ingress settings to invalid value ${from}`);
}
return from;
});
proto.convertIfPresent(bkEndpoint, bdEndpoint, "availableMemoryMb", (from) => {
const mem = r.resolveInt(from);
if (mem !== null && !backend.isValidMemoryOption(mem)) {
throw new error_1.FirebaseError(`Function memory (${mem}) must resolve to a supported value, if present: ${JSON.stringify(allMemoryOptions)}`);
}
return mem || null;
});
r.resolveStrings(bkEndpoint, bdEndpoint, "serviceAccount");
r.resolveInts(bkEndpoint, bdEndpoint, "timeoutSeconds", "maxInstances", "minInstances", "concurrency");
proto.convertIfPresent(bkEndpoint, bdEndpoint, "cpu", (0, functional_1.nullsafeVisitor)((cpu) => (cpu === "gcf_gen1" ? cpu : r.resolveInt(cpu))));
if (bdEndpoint.vpc) {
bkEndpoint.vpc = {};
if (typeof bdEndpoint.vpc.connector !== "undefined" && bdEndpoint.vpc.connector !== null) {
const connector = params.resolveString(bdEndpoint.vpc.connector, paramValues);
bkEndpoint.vpc.connector =
connector.includes("/") || connector === ""
? connector
: `projects/${bdEndpoint.project}/locations/${region}/connectors/${connector}`;
}
if (bdEndpoint.vpc.egressSettings) {
const egress = params.resolveString(bdEndpoint.vpc.egressSettings, paramValues);
if (!backend.AllVpcEgressSettings.includes(egress)) {
throw new error_1.FirebaseError(`Value "${egress}" is an invalid egress setting.`);
}
bkEndpoint.vpc.egressSettings = egress;
}
if (bdEndpoint.vpc.networkInterfaces) {
bkEndpoint.vpc.networkInterfaces = bdEndpoint.vpc.networkInterfaces.map((ni) => {
const resolved = {};
if (ni.network)
resolved.network = params.resolveString(ni.network, paramValues);
if (ni.subnetwork)
resolved.subnetwork = params.resolveString(ni.subnetwork, paramValues);
if (ni.tags) {
resolved.tags = ni.tags.map((tag) => params.resolveString(tag, paramValues));
}
return resolved;
});
}
}
else if (bdEndpoint.vpc === null) {
bkEndpoint.vpc = null;
}
bkEndpoints.push(bkEndpoint);
}
}
const bkend = backend.of(...bkEndpoints);
bkend.requiredAPIs = build.requiredAPIs;
return bkend;
}
function discoverTrigger(endpoint, region, r) {
if (isHttpsTriggered(endpoint)) {
const httpsTrigger = {};
if (endpoint.httpsTrigger.invoker === null) {
httpsTrigger.invoker = null;
}
else if (typeof endpoint.httpsTrigger.invoker !== "undefined") {
httpsTrigger.invoker = endpoint.httpsTrigger.invoker.map(r.resolveString);
}
return { httpsTrigger };
}
else if (isDataConnectGraphqlTriggered(endpoint)) {
const dataConnectGraphqlTrigger = {};
if (endpoint.dataConnectGraphqlTrigger.invoker === null) {
dataConnectGraphqlTrigger.invoker = null;
}
else if (typeof endpoint.dataConnectGraphqlTrigger.invoker !== "undefined") {
dataConnectGraphqlTrigger.invoker = endpoint.dataConnectGraphqlTrigger.invoker.map(r.resolveString);
}
proto.copyIfPresent(dataConnectGraphqlTrigger, endpoint.dataConnectGraphqlTrigger, "schemaFilePath");
return { dataConnectGraphqlTrigger };
}
else if (isCallableTriggered(endpoint)) {
const trigger = { callableTrigger: {} };
proto.copyIfPresent(trigger.callableTrigger, endpoint.callableTrigger, "genkitAction");
return trigger;
}
else if (isBlockingTriggered(endpoint)) {
return { blockingTrigger: endpoint.blockingTrigger };
}
else if (isEventTriggered(endpoint)) {
const eventTrigger = {
eventType: endpoint.eventTrigger.eventType,
retry: r.resolveBoolean(endpoint.eventTrigger.retry) || false,
};
if (endpoint.eventTrigger.eventFilters) {
eventTrigger.eventFilters = (0, functional_1.mapObject)(endpoint.eventTrigger.eventFilters, r.resolveString);
}
if (endpoint.eventTrigger.eventFilterPathPatterns) {
eventTrigger.eventFilterPathPatterns = (0, functional_1.mapObject)(endpoint.eventTrigger.eventFilterPathPatterns, r.resolveString);
}
r.resolveStrings(eventTrigger, endpoint.eventTrigger, "serviceAccount", "region", "channel");
return { eventTrigger };
}
else if (isScheduleTriggered(endpoint)) {
const bkSchedule = {
schedule: r.resolveString(endpoint.scheduleTrigger.schedule),
};
if (endpoint.scheduleTrigger.timeZone !== undefined) {
bkSchedule.timeZone = r.resolveString(endpoint.scheduleTrigger.timeZone);
}
if (endpoint.scheduleTrigger.retryConfig) {
const bkRetry = {};
r.resolveInts(bkRetry, endpoint.scheduleTrigger.retryConfig, "maxBackoffSeconds", "minBackoffSeconds", "maxRetrySeconds", "retryCount", "maxDoublings");
bkSchedule.retryConfig = bkRetry;
}
else if (endpoint.scheduleTrigger.retryConfig === null) {
bkSchedule.retryConfig = null;
}
return { scheduleTrigger: bkSchedule };
}
else if ("taskQueueTrigger" in endpoint) {
const taskQueueTrigger = {};
if (endpoint.taskQueueTrigger.rateLimits) {
taskQueueTrigger.rateLimits = {};
r.resolveInts(taskQueueTrigger.rateLimits, endpoint.taskQueueTrigger.rateLimits, "maxConcurrentDispatches", "maxDispatchesPerSecond");
}
else if (endpoint.taskQueueTrigger.rateLimits === null) {
taskQueueTrigger.rateLimits = null;
}
if (endpoint.taskQueueTrigger.retryConfig) {
taskQueueTrigger.retryConfig = {};
r.resolveInts(taskQueueTrigger.retryConfig, endpoint.taskQueueTrigger.retryConfig, "maxAttempts", "maxBackoffSeconds", "minBackoffSeconds", "maxRetrySeconds", "maxDoublings");
}
else if (endpoint.taskQueueTrigger.retryConfig === null) {
taskQueueTrigger.retryConfig = null;
}
if (endpoint.taskQueueTrigger.invoker) {
taskQueueTrigger.invoker = endpoint.taskQueueTrigger.invoker.map(r.resolveString);
}
else if (endpoint.taskQueueTrigger.invoker === null) {
taskQueueTrigger.invoker = null;
}
return { taskQueueTrigger };
}
(0, functional_1.assertExhaustive)(endpoint);
}
function applyPrefix(build, prefix) {
if (!prefix) {
return;
}
const newEndpoints = {};
for (const [id, endpoint] of Object.entries(build.endpoints)) {
const newId = `${prefix}-${id}`;
if (newId.length > 63) {
throw new error_1.FirebaseError(`Function id '${newId}' exceeds 63 characters after applying prefix '${prefix}'. Please shorten the prefix or function name.`);
}
const fnIdRegex = /^[a-zA-Z][a-zA-Z0-9_-]{0,62}$/;
if (!fnIdRegex.test(newId)) {
throw new error_1.FirebaseError(`Function id '${newId}' is invalid after applying prefix '${prefix}'. Function names must start with a letter and can contain letters, numbers, underscores, and hyphens, with a maximum length of 63 characters.`);
}
newEndpoints[newId] = endpoint;
if (endpoint.secretEnvironmentVariables) {
endpoint.secretEnvironmentVariables = endpoint.secretEnvironmentVariables.map((secret) => ({
...secret,
secret: `${prefix}-${secret.secret}`,
}));
}
}
build.endpoints = newEndpoints;
}