"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.functionFromEndpoint = exports.endpointFromFunction = exports.listAllFunctions = exports.listFunctions = exports.deleteFunction = exports.updateFunction = exports.setInvokerUpdate = exports.setInvokerCreate = exports.getIamPolicy = exports.setIamPolicy = exports.createFunction = exports.generateUploadUrl = exports.API_VERSION = void 0; const clc = require("colorette"); const error_1 = require("../error"); const logger_1 = require("../logger"); const backend = require("../deploy/functions/backend"); const utils = require("../utils"); const proto = require("./proto"); const runtimes = require("../deploy/functions/runtimes"); const projectConfig = require("../functions/projectConfig"); const apiv2_1 = require("../apiv2"); const api_1 = require("../api"); const constants_1 = require("../functions/constants"); exports.API_VERSION = "v1"; const client = new apiv2_1.Client({ urlPrefix: api_1.functionsOrigin, apiVersion: exports.API_VERSION }); function functionsOpLogReject(funcName, type, err) { var _a, _b; if (((_b = (_a = err === null || err === void 0 ? void 0 : err.context) === null || _a === void 0 ? void 0 : _a.response) === null || _b === void 0 ? void 0 : _b.statusCode) === 429) { utils.logWarning(`${clc.bold(clc.yellow("functions:"))} got "Quota Exceeded" error while trying to ${type} ${funcName}. Waiting to retry...`); } else { utils.logWarning(clc.bold(clc.yellow("functions:")) + " failed to " + type + " function " + funcName); } throw new error_1.FirebaseError(`Failed to ${type} function ${funcName}`, { original: err, context: { function: funcName }, }); } async function generateUploadUrl(projectId, location) { const parent = "projects/" + projectId + "/locations/" + location; const endpoint = `/${parent}/functions:generateUploadUrl`; try { const res = await client.post(endpoint, {}, { retryCodes: [503] }); return res.body.uploadUrl; } catch (err) { logger_1.logger.info("\n\nThere was an issue deploying your functions. Verify that your project has a Google App Engine instance setup at https://console.cloud.google.com/appengine and try again. If this issue persists, please contact support."); throw err; } } exports.generateUploadUrl = generateUploadUrl; async function createFunction(cloudFunction) { const apiPath = cloudFunction.name.substring(0, cloudFunction.name.lastIndexOf("/")); const endpoint = `/${apiPath}`; try { const res = await client.post(endpoint, cloudFunction); return { name: res.body.name, type: "create", done: false, }; } catch (err) { throw functionsOpLogReject(cloudFunction.name, "create", err); } } exports.createFunction = createFunction; async function setIamPolicy(options) { const endpoint = `/${options.name}:setIamPolicy`; try { await client.post(endpoint, { policy: options.policy, updateMask: Object.keys(options.policy).join(","), }); } catch (err) { throw new error_1.FirebaseError(`Failed to set the IAM Policy on the function ${options.name}`, { original: err, }); } } exports.setIamPolicy = setIamPolicy; async function getIamPolicy(fnName) { const endpoint = `/${fnName}:getIamPolicy`; try { const res = await client.get(endpoint); return res.body; } catch (err) { throw new error_1.FirebaseError(`Failed to get the IAM Policy on the function ${fnName}`, { original: err, }); } } exports.getIamPolicy = getIamPolicy; async function setInvokerCreate(projectId, fnName, invoker) { if (invoker.length === 0) { throw new error_1.FirebaseError("Invoker cannot be an empty array"); } const invokerMembers = proto.getInvokerMembers(invoker, projectId); const invokerRole = "roles/cloudfunctions.invoker"; const bindings = [{ role: invokerRole, members: invokerMembers }]; const policy = { bindings: bindings, etag: "", version: 3, }; await setIamPolicy({ name: fnName, policy: policy }); } exports.setInvokerCreate = setInvokerCreate; async function setInvokerUpdate(projectId, fnName, invoker) { var _a; if (invoker.length === 0) { throw new error_1.FirebaseError("Invoker cannot be an empty array"); } const invokerMembers = proto.getInvokerMembers(invoker, projectId); const invokerRole = "roles/cloudfunctions.invoker"; const currentPolicy = await getIamPolicy(fnName); const currentInvokerBinding = (_a = currentPolicy.bindings) === null || _a === void 0 ? void 0 : _a.find((binding) => binding.role === invokerRole); if (currentInvokerBinding && JSON.stringify(currentInvokerBinding.members.sort()) === JSON.stringify(invokerMembers.sort())) { return; } const bindings = (currentPolicy.bindings || []).filter((binding) => binding.role !== invokerRole); bindings.push({ role: invokerRole, members: invokerMembers, }); const policy = { bindings: bindings, etag: currentPolicy.etag || "", version: 3, }; await setIamPolicy({ name: fnName, policy: policy }); } exports.setInvokerUpdate = setInvokerUpdate; async function updateFunction(cloudFunction) { const endpoint = `/${cloudFunction.name}`; const fieldMasks = proto.fieldMasks(cloudFunction, "labels", "environmentVariables", "secretEnvironmentVariables"); try { const res = await client.patch(endpoint, cloudFunction, { queryParams: { updateMask: fieldMasks.join(","), }, }); return { done: false, name: res.body.name, type: "update", }; } catch (err) { throw functionsOpLogReject(cloudFunction.name, "update", err); } } exports.updateFunction = updateFunction; async function deleteFunction(name) { const endpoint = `/${name}`; try { const res = await client.delete(endpoint); return { done: false, name: res.body.name, type: "delete", }; } catch (err) { throw functionsOpLogReject(name, "delete", err); } } exports.deleteFunction = deleteFunction; async function list(projectId, region) { const endpoint = "/projects/" + projectId + "/locations/" + region + "/functions"; try { const res = await client.get(endpoint); if (res.body.unreachable && res.body.unreachable.length > 0) { logger_1.logger.debug(`[functions] unable to reach the following regions: ${res.body.unreachable.join(", ")}`); } return { functions: res.body.functions || [], unreachable: res.body.unreachable || [], }; } catch (err) { logger_1.logger.debug(`[functions] failed to list functions for ${projectId}`); logger_1.logger.debug(`[functions] ${err === null || err === void 0 ? void 0 : err.message}`); throw new error_1.FirebaseError(`Failed to list functions for ${projectId}`, { original: err, status: err instanceof error_1.FirebaseError ? err.status : undefined, }); } } async function listFunctions(projectId, region) { const res = await list(projectId, region); return res.functions; } exports.listFunctions = listFunctions; async function listAllFunctions(projectId) { return list(projectId, "-"); } exports.listAllFunctions = listAllFunctions; function endpointFromFunction(gcfFunction) { var _a, _b, _c, _d, _e, _f, _g, _h; const [, project, , region, , id] = gcfFunction.name.split("/"); let trigger; let uri; let securityLevel; if ((_a = gcfFunction.labels) === null || _a === void 0 ? void 0 : _a["deployment-scheduled"]) { trigger = { scheduleTrigger: {}, }; } else if ((_b = gcfFunction.labels) === null || _b === void 0 ? void 0 : _b["deployment-taskqueue"]) { trigger = { taskQueueTrigger: {}, }; } else if (((_c = gcfFunction.labels) === null || _c === void 0 ? void 0 : _c["deployment-callable"]) || ((_d = gcfFunction.labels) === null || _d === void 0 ? void 0 : _d["deployment-callabled"])) { trigger = { callableTrigger: {}, }; } else if ((_e = gcfFunction.labels) === null || _e === void 0 ? void 0 : _e[constants_1.BLOCKING_LABEL]) { trigger = { blockingTrigger: { eventType: constants_1.BLOCKING_LABEL_KEY_TO_EVENT[gcfFunction.labels[constants_1.BLOCKING_LABEL]], }, }; } else if (gcfFunction.httpsTrigger) { trigger = { httpsTrigger: {} }; } else { trigger = { eventTrigger: { eventType: gcfFunction.eventTrigger.eventType, eventFilters: { resource: gcfFunction.eventTrigger.resource }, retry: !!((_f = gcfFunction.eventTrigger.failurePolicy) === null || _f === void 0 ? void 0 : _f.retry), }, }; } if (gcfFunction.httpsTrigger) { uri = gcfFunction.httpsTrigger.url; securityLevel = gcfFunction.httpsTrigger.securityLevel; } if (!runtimes.isValidRuntime(gcfFunction.runtime)) { logger_1.logger.debug("GCFv1 function has a deprecated runtime:", JSON.stringify(gcfFunction, null, 2)); } const endpoint = Object.assign(Object.assign({ platform: "gcfv1", id, project, region }, trigger), { entryPoint: gcfFunction.entryPoint, runtime: gcfFunction.runtime }); if (uri) { endpoint.uri = uri; } if (securityLevel) { endpoint.securityLevel = securityLevel; } proto.copyIfPresent(endpoint, gcfFunction, "minInstances", "maxInstances", "ingressSettings", "labels", "environmentVariables", "secretEnvironmentVariables", "sourceUploadUrl"); proto.renameIfPresent(endpoint, gcfFunction, "serviceAccount", "serviceAccountEmail"); proto.convertIfPresent(endpoint, gcfFunction, "availableMemoryMb", (raw) => raw); proto.convertIfPresent(endpoint, gcfFunction, "timeoutSeconds", "timeout", (dur) => dur === null ? null : proto.secondsFromDuration(dur)); if (gcfFunction.vpcConnector) { endpoint.vpc = { connector: gcfFunction.vpcConnector }; proto.convertIfPresent(endpoint.vpc, gcfFunction, "egressSettings", "vpcConnectorEgressSettings", (raw) => raw); } endpoint.codebase = ((_g = gcfFunction.labels) === null || _g === void 0 ? void 0 : _g[constants_1.CODEBASE_LABEL]) || projectConfig.DEFAULT_CODEBASE; if ((_h = gcfFunction.labels) === null || _h === void 0 ? void 0 : _h[constants_1.HASH_LABEL]) { endpoint.hash = gcfFunction.labels[constants_1.HASH_LABEL]; } return endpoint; } exports.endpointFromFunction = endpointFromFunction; function functionFromEndpoint(endpoint, sourceUploadUrl) { var _a, _b; if (endpoint.platform !== "gcfv1") { throw new error_1.FirebaseError("Trying to create a v1 CloudFunction with v2 API. This should never happen"); } if (!runtimes.isValidRuntime(endpoint.runtime)) { throw new error_1.FirebaseError("Failed internal assertion. Trying to deploy a new function with a deprecated runtime." + " This should never happen"); } const gcfFunction = { name: backend.functionName(endpoint), sourceUploadUrl: sourceUploadUrl, entryPoint: endpoint.entryPoint, runtime: endpoint.runtime, dockerRegistry: "ARTIFACT_REGISTRY", }; if (typeof endpoint.labels !== "undefined") { gcfFunction.labels = Object.assign({}, endpoint.labels); } if (backend.isEventTriggered(endpoint)) { if (!((_a = endpoint.eventTrigger.eventFilters) === null || _a === void 0 ? void 0 : _a.resource)) { throw new error_1.FirebaseError("Cannot create v1 function from an eventTrigger without a resource"); } gcfFunction.eventTrigger = { eventType: endpoint.eventTrigger.eventType, resource: endpoint.eventTrigger.eventFilters.resource, }; gcfFunction.eventTrigger.failurePolicy = endpoint.eventTrigger.retry ? { retry: {} } : undefined; } else if (backend.isScheduleTriggered(endpoint)) { const id = backend.scheduleIdForFunction(endpoint); gcfFunction.eventTrigger = { eventType: "google.pubsub.topic.publish", resource: `projects/${endpoint.project}/topics/${id}`, }; gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { "deployment-scheduled": "true" }); } else if (backend.isTaskQueueTriggered(endpoint)) { gcfFunction.httpsTrigger = {}; gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { "deployment-taskqueue": "true" }); } else if (backend.isBlockingTriggered(endpoint)) { gcfFunction.httpsTrigger = {}; gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [constants_1.BLOCKING_LABEL]: constants_1.BLOCKING_EVENT_TO_LABEL_KEY[endpoint.blockingTrigger.eventType] }); } else { gcfFunction.httpsTrigger = {}; if (backend.isCallableTriggered(endpoint)) { gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { "deployment-callable": "true" }); } if (endpoint.securityLevel) { gcfFunction.httpsTrigger.securityLevel = endpoint.securityLevel; } } proto.copyIfPresent(gcfFunction, endpoint, "minInstances", "maxInstances", "ingressSettings", "environmentVariables", "secretEnvironmentVariables"); proto.renameIfPresent(gcfFunction, endpoint, "serviceAccountEmail", "serviceAccount"); proto.convertIfPresent(gcfFunction, endpoint, "availableMemoryMb", (mem) => mem); proto.convertIfPresent(gcfFunction, endpoint, "timeout", "timeoutSeconds", (sec) => sec ? proto.durationFromSeconds(sec) : null); if (endpoint.vpc) { proto.renameIfPresent(gcfFunction, endpoint.vpc, "vpcConnector", "connector"); proto.renameIfPresent(gcfFunction, endpoint.vpc, "vpcConnectorEgressSettings", "egressSettings"); } else if (endpoint.vpc === null) { gcfFunction.vpcConnector = null; gcfFunction.vpcConnectorEgressSettings = null; } const codebase = endpoint.codebase || projectConfig.DEFAULT_CODEBASE; if (codebase !== projectConfig.DEFAULT_CODEBASE) { gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [constants_1.CODEBASE_LABEL]: codebase }); } else { (_b = gcfFunction.labels) === null || _b === void 0 ? true : delete _b[constants_1.CODEBASE_LABEL]; } if (endpoint.hash) { gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [constants_1.HASH_LABEL]: endpoint.hash }); } return gcfFunction; } exports.functionFromEndpoint = functionFromEndpoint;