UNPKG

firebase-tools

Version:
204 lines (203 loc) 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.serviceFromEndpoint = exports.endpointFromService = exports.FIREBASE_FUNCTION_METADTA_ANNOTATION = exports.FUNCTION_SIGNATURE_TYPE_ENV = exports.FUNCTION_TARGET_ENV = exports.FUNCTION_ID_ANNOTATION = exports.FUNCTION_TARGET_ANNOTATION = exports.TRIGGER_TYPE_ANNOTATION = exports.CLIENT_NAME_LABEL = exports.RUNTIME_LABEL = exports.listServices = exports.updateService = exports.submitBuild = exports.API_VERSION = void 0; const apiv2_1 = require("../apiv2"); const error_1 = require("../error"); const api_1 = require("../api"); const proto = require("./proto"); const metaprogramming_1 = require("../metaprogramming"); const operation_poller_1 = require("../operation-poller"); const backend = require("../deploy/functions/backend"); const constants_1 = require("../functions/constants"); const k8s_1 = require("./k8s"); const supported_1 = require("../deploy/functions/runtimes/supported"); const logger_1 = require("../logger"); const functional_1 = require("../functional"); exports.API_VERSION = "v2"; const client = new apiv2_1.Client({ urlPrefix: (0, api_1.runOrigin)(), auth: true, apiVersion: exports.API_VERSION, }); (0, metaprogramming_1.assertImplements)(); async function submitBuild(projectId, location, build) { const res = await client.post(`/projects/${projectId}/locations/${location}/builds`, build); if (res.status !== 200) { throw new error_1.FirebaseError(`Failed to submit build: ${res.status} ${res.body}`); } await (0, operation_poller_1.pollOperation)({ apiOrigin: (0, api_1.cloudbuildOrigin)(), apiVersion: "v1", operationResourceName: res.body.buildOperation, }); } exports.submitBuild = submitBuild; async function updateService(service) { const fieldMask = proto.fieldMasks(service, "labels", "annotations", "tags"); fieldMask.push("template.revision"); const res = await client.post(service.name, service, { queryParams: { updateMask: fieldMask.join(","), }, }); const svc = await (0, operation_poller_1.pollOperation)({ apiOrigin: (0, api_1.runOrigin)(), apiVersion: exports.API_VERSION, operationResourceName: res.body.name, }); return svc; } exports.updateService = updateService; async function listServices(projectId) { var _a, _b; const allServices = []; let pageToken = undefined; do { const queryParams = {}; if (pageToken) { queryParams["pageToken"] = pageToken; } const res = await client.get(`/projects/${projectId}/locations/-/services`, { queryParams }); if (res.status !== 200) { throw new error_1.FirebaseError(`Failed to list services. HTTP Error: ${res.status}`, { original: res.body, }); } if (res.body.services) { for (const service of res.body.services) { if (((_a = service.labels) === null || _a === void 0 ? void 0 : _a[exports.CLIENT_NAME_LABEL]) === "cloud-functions" || ((_b = service.labels) === null || _b === void 0 ? void 0 : _b[exports.CLIENT_NAME_LABEL]) === "firebase-functions") { allServices.push(service); } } } pageToken = res.body.nextPageToken; } while (pageToken); return allServices; } exports.listServices = listServices; function functionNameToServiceName(id) { return id.toLowerCase().replace(/_/g, "-"); } exports.RUNTIME_LABEL = "goog-cloudfunctions-runtime"; exports.CLIENT_NAME_LABEL = "goog-managed-by"; exports.TRIGGER_TYPE_ANNOTATION = "cloudfunctions.googleapis.com/trigger-type"; exports.FUNCTION_TARGET_ANNOTATION = "run.googleapis.com/build-function-target"; exports.FUNCTION_ID_ANNOTATION = "cloudfunctions.googleapis.com/function-id"; exports.FUNCTION_TARGET_ENV = "FUNCTION_TARGET"; exports.FUNCTION_SIGNATURE_TYPE_ENV = "FUNCTION_SIGNATURE_TYPE"; exports.FIREBASE_FUNCTION_METADTA_ANNOTATION = "firebase-functions-metadata"; function endpointFromService(service) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l; const [, project, , location, , svcId] = service.name.split("/"); const metadata = JSON.parse(((_a = service.annotations) === null || _a === void 0 ? void 0 : _a[exports.FIREBASE_FUNCTION_METADTA_ANNOTATION]) || "{}"); const [env, secretEnv] = (0, functional_1.partition)(service.template.containers[0].env || [], (e) => "value" in e); const id = metadata.functionId || ((_b = service.annotations) === null || _b === void 0 ? void 0 : _b[exports.FUNCTION_ID_ANNOTATION]) || ((_c = service.annotations) === null || _c === void 0 ? void 0 : _c[exports.FUNCTION_TARGET_ANNOTATION]) || ((_d = env.find((e) => e.name === exports.FUNCTION_TARGET_ENV)) === null || _d === void 0 ? void 0 : _d.value) || svcId; const memory = (0, k8s_1.mebibytes)(service.template.containers[0].resources.limits.memory); if (!backend.isValidMemoryOption(memory)) { logger_1.logger.debug("Converting a service to an endpoint with an invalid memory option", memory); } const cpu = Number(service.template.containers[0].resources.limits.cpu); const endpoint = Object.assign({ platform: ((_e = service.labels) === null || _e === void 0 ? void 0 : _e[exports.CLIENT_NAME_LABEL]) === "cloud-functions" ? "gcfv2" : "run", id, project, labels: service.labels || {}, region: location, runtime: ((_f = service.labels) === null || _f === void 0 ? void 0 : _f[exports.RUNTIME_LABEL]) || (0, supported_1.latest)("nodejs"), availableMemoryMb: memory, cpu: cpu, entryPoint: ((_g = env.find((e) => e.name === exports.FUNCTION_TARGET_ENV)) === null || _g === void 0 ? void 0 : _g.value) || ((_h = service.annotations) === null || _h === void 0 ? void 0 : _h[exports.FUNCTION_TARGET_ANNOTATION]) || ((_j = service.annotations) === null || _j === void 0 ? void 0 : _j[exports.FUNCTION_ID_ANNOTATION]) || id }, (((_k = service.annotations) === null || _k === void 0 ? void 0 : _k[exports.TRIGGER_TYPE_ANNOTATION]) === "HTTP_TRIGGER" ? { httpsTrigger: {} } : { eventTrigger: { eventType: ((_l = service.annotations) === null || _l === void 0 ? void 0 : _l[exports.TRIGGER_TYPE_ANNOTATION]) || "unknown", retry: false, }, })); proto.renameIfPresent(endpoint, service.template, "concurrency", "containerConcurrency"); proto.renameIfPresent(endpoint, service.labels || {}, "codebase", constants_1.CODEBASE_LABEL); proto.renameIfPresent(endpoint, service.scaling || {}, "minInstances", "minInstanceCount"); proto.renameIfPresent(endpoint, service.scaling || {}, "maxInstances", "maxInstanceCount"); endpoint.environmentVariables = env.reduce((acc, e) => { acc[e.name] = e.value; return acc; }, {}); endpoint.secretEnvironmentVariables = secretEnv.map((e) => { const [, projectId, , secret] = e.valueSource.secretKeyRef.secret.split("/"); return { key: e.name, projectId, secret, version: e.valueSource.secretKeyRef.version || "latest", }; }); return endpoint; } exports.endpointFromService = endpointFromService; function serviceFromEndpoint(endpoint, image) { const labels = Object.assign(Object.assign(Object.assign({}, endpoint.labels), (endpoint.runtime ? { [exports.RUNTIME_LABEL]: endpoint.runtime } : {})), { [exports.CLIENT_NAME_LABEL]: "firebase-functions" }); delete labels["deployment-tool"]; if (endpoint.codebase) { labels[constants_1.CODEBASE_LABEL] = endpoint.codebase; } const annotations = { [exports.FIREBASE_FUNCTION_METADTA_ANNOTATION]: JSON.stringify({ functionId: endpoint.id, }), }; const template = { containers: [ { name: "worker", image, env: [ ...Object.entries(endpoint.environmentVariables || {}).map(([name, value]) => ({ name, value, })), ...(endpoint.secretEnvironmentVariables || []).map((secret) => ({ name: secret.key, valueSource: { secretKeyRef: { secret: secret.secret, version: secret.version, }, }, })), { name: exports.FUNCTION_TARGET_ENV, value: endpoint.entryPoint, }, { name: exports.FUNCTION_SIGNATURE_TYPE_ENV, value: backend.isEventTriggered(endpoint) ? "cloudevent" : "http", }, ], resources: { limits: { cpu: String(endpoint.cpu || 1), memory: `${endpoint.availableMemoryMb || 256}Mi`, }, cpuIdle: true, startupCpuBoost: true, }, }, ], containerConcurrency: endpoint.concurrency || backend.DEFAULT_CONCURRENCY, }; proto.renameIfPresent(template, endpoint, "containerConcurrency", "concurrency"); const service = { name: `projects/${endpoint.project}/locations/${endpoint.region}/services/${functionNameToServiceName(endpoint.id)}`, labels, annotations, template, client: "cli-firebase", }; if (endpoint.minInstances || endpoint.maxInstances) { service.scaling = {}; proto.renameIfPresent(service.scaling, endpoint, "minInstanceCount", "minInstances"); proto.renameIfPresent(service.scaling, endpoint, "maxInstanceCount", "maxInstances"); } return service; } exports.serviceFromEndpoint = serviceFromEndpoint;