123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.updateEndpointSecret = exports.pruneAndDestroySecrets = exports.pruneSecrets = exports.inUse = exports.getSecretVersions = exports.of = exports.ensureSecret = exports.ensureValidKey = exports.labels = exports.isFirebaseManaged = void 0;
- const utils = require("../utils");
- const poller = require("../operation-poller");
- const gcf = require("../gcp/cloudfunctions");
- const secretManager_1 = require("../gcp/secretManager");
- const error_1 = require("../error");
- const utils_1 = require("../utils");
- const prompt_1 = require("../prompt");
- const env_1 = require("./env");
- const logger_1 = require("../logger");
- const api_1 = require("../api");
- const functional_1 = require("../functional");
- const FIREBASE_MANGED = "firebase-managed";
- function isFirebaseManaged(secret) {
- return Object.keys(secret.labels || []).includes(FIREBASE_MANGED);
- }
- exports.isFirebaseManaged = isFirebaseManaged;
- function labels() {
- return { [FIREBASE_MANGED]: "true" };
- }
- exports.labels = labels;
- function toUpperSnakeCase(key) {
- return key
- .replace(/[.-]/g, "_")
- .replace(/([a-z])([A-Z])/g, "$1_$2")
- .toUpperCase();
- }
- async function ensureValidKey(key, options) {
- const transformedKey = toUpperSnakeCase(key);
- if (transformedKey !== key) {
- if (options.force) {
- throw new error_1.FirebaseError("Secret key must be in UPPER_SNAKE_CASE.");
- }
- (0, utils_1.logWarning)(`By convention, secret key must be in UPPER_SNAKE_CASE.`);
- const confirm = await (0, prompt_1.promptOnce)({
- name: "updateKey",
- type: "confirm",
- default: true,
- message: `Would you like to use ${transformedKey} as key instead?`,
- }, options);
- if (!confirm) {
- throw new error_1.FirebaseError("Secret key must be in UPPER_SNAKE_CASE.");
- }
- }
- try {
- (0, env_1.validateKey)(transformedKey);
- }
- catch (err) {
- throw new error_1.FirebaseError(`Invalid secret key ${transformedKey}`, { children: [err] });
- }
- return transformedKey;
- }
- exports.ensureValidKey = ensureValidKey;
- async function ensureSecret(projectId, name, options) {
- try {
- const secret = await (0, secretManager_1.getSecret)(projectId, name);
- if (!isFirebaseManaged(secret)) {
- if (!options.force) {
- (0, utils_1.logWarning)("Your secret is not managed by Firebase. " +
- "Firebase managed secrets are automatically pruned to reduce your monthly cost for using Secret Manager. ");
- const confirm = await (0, prompt_1.promptOnce)({
- name: "updateLabels",
- type: "confirm",
- default: true,
- message: `Would you like to have your secret ${secret.name} managed by Firebase?`,
- }, options);
- if (confirm) {
- return (0, secretManager_1.patchSecret)(projectId, secret.name, Object.assign(Object.assign({}, secret.labels), labels()));
- }
- }
- }
- return secret;
- }
- catch (err) {
- if (err.status !== 404) {
- throw err;
- }
- }
- return await (0, secretManager_1.createSecret)(projectId, name, labels());
- }
- exports.ensureSecret = ensureSecret;
- function of(endpoints) {
- return endpoints.reduce((envs, endpoint) => [...envs, ...(endpoint.secretEnvironmentVariables || [])], []);
- }
- exports.of = of;
- function getSecretVersions(endpoint) {
- return (endpoint.secretEnvironmentVariables || []).reduce((memo, { secret, version }) => {
- memo[secret] = version || "";
- return memo;
- }, {});
- }
- exports.getSecretVersions = getSecretVersions;
- function inUse(projectInfo, secret, endpoint) {
- const { projectId, projectNumber } = projectInfo;
- for (const sev of of([endpoint])) {
- if ((sev.projectId === projectId || sev.projectId === projectNumber) &&
- sev.secret === secret.name) {
- return true;
- }
- }
- return false;
- }
- exports.inUse = inUse;
- async function pruneSecrets(projectInfo, endpoints) {
- const { projectId, projectNumber } = projectInfo;
- const pruneKey = (name, version) => `${name}@${version}`;
- const prunedSecrets = new Set();
- const haveSecrets = await (0, secretManager_1.listSecrets)(projectId, `labels.${FIREBASE_MANGED}=true`);
- for (const secret of haveSecrets) {
- const versions = await (0, secretManager_1.listSecretVersions)(projectId, secret.name, `NOT state: DESTROYED`);
- for (const version of versions) {
- prunedSecrets.add(pruneKey(secret.name, version.versionId));
- }
- }
- const secrets = [];
- for (const secret of of(endpoints)) {
- if (!secret.version) {
- throw new error_1.FirebaseError(`Secret ${secret.secret} version is unexpectedly empty.`);
- }
- if (secret.projectId === projectId || secret.projectId === projectNumber) {
- if (secret.version) {
- secrets.push(Object.assign(Object.assign({}, secret), { version: secret.version }));
- }
- }
- }
- for (const sev of secrets) {
- let name = sev.secret;
- if (name.includes("/")) {
- const secret = (0, secretManager_1.parseSecretResourceName)(name);
- name = secret.name;
- }
- let version = sev.version;
- if (version === "latest") {
- const resolved = await (0, secretManager_1.getSecretVersion)(projectId, name, version);
- version = resolved.versionId;
- }
- prunedSecrets.delete(pruneKey(name, version));
- }
- return Array.from(prunedSecrets)
- .map((key) => key.split("@"))
- .map(([secret, version]) => ({ projectId, version, secret, key: secret }));
- }
- exports.pruneSecrets = pruneSecrets;
- async function pruneAndDestroySecrets(projectInfo, endpoints) {
- const { projectId, projectNumber } = projectInfo;
- logger_1.logger.debug("Pruning secrets to find unused secret versions...");
- const unusedSecrets = await module.exports.pruneSecrets({ projectId, projectNumber }, endpoints);
- if (unusedSecrets.length === 0) {
- return { destroyed: [], erred: [] };
- }
- const destroyed = [];
- const erred = [];
- const msg = unusedSecrets.map((s) => `${s.secret}@${s.version}`);
- logger_1.logger.debug(`Found unused secret versions: ${msg}. Destroying them...`);
- const destroyResults = await utils.allSettled(unusedSecrets.map(async (sev) => {
- await (0, secretManager_1.destroySecretVersion)(sev.projectId, sev.secret, sev.version);
- return sev;
- }));
- for (const result of destroyResults) {
- if (result.status === "fulfilled") {
- destroyed.push(result.value);
- }
- else {
- erred.push(result.reason);
- }
- }
- return { destroyed, erred };
- }
- exports.pruneAndDestroySecrets = pruneAndDestroySecrets;
- async function updateEndpointSecret(projectInfo, secretVersion, endpoint) {
- const { projectId, projectNumber } = projectInfo;
- if (!inUse(projectInfo, secretVersion.secret, endpoint)) {
- return endpoint;
- }
- const updatedSevs = [];
- for (const sev of of([endpoint])) {
- const updatedSev = Object.assign({}, sev);
- if ((updatedSev.projectId === projectId || updatedSev.projectId === projectNumber) &&
- updatedSev.secret === secretVersion.secret.name) {
- updatedSev.version = secretVersion.versionId;
- }
- updatedSevs.push(updatedSev);
- }
- if (endpoint.platform === "gcfv1") {
- const fn = gcf.functionFromEndpoint(endpoint, "");
- const op = await gcf.updateFunction({
- name: fn.name,
- runtime: fn.runtime,
- entryPoint: fn.entryPoint,
- secretEnvironmentVariables: updatedSevs,
- });
- const gcfV1PollerOptions = {
- apiOrigin: api_1.functionsOrigin,
- apiVersion: gcf.API_VERSION,
- masterTimeout: 25 * 60 * 1000,
- maxBackoff: 10000,
- pollerName: `update-${endpoint.region}-${endpoint.id}`,
- operationResourceName: op.name,
- };
- const cfn = await poller.pollOperation(gcfV1PollerOptions);
- return gcf.endpointFromFunction(cfn);
- }
- else if (endpoint.platform === "gcfv2") {
- throw new error_1.FirebaseError(`Unsupported platform ${endpoint.platform}`);
- }
- else {
- (0, functional_1.assertExhaustive)(endpoint.platform);
- }
- }
- exports.updateEndpointSecret = updateEndpointSecret;
|