firebase-tools
Version:
Command-Line Interface for Firebase
204 lines (203 loc) • 10.1 kB
JavaScript
;
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;