firebase-tools 10.1.3 → 10.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/api.js +1 -0
- package/lib/apiv2.js +4 -0
- package/lib/auth.js +62 -25
- package/lib/commands/auth-import.js +1 -1
- package/lib/commands/ext-configure.js +1 -0
- package/lib/commands/ext-install.js +1 -0
- package/lib/commands/ext-uninstall.js +1 -0
- package/lib/commands/ext-update.js +1 -0
- package/lib/commands/functions-config-clone.js +1 -1
- package/lib/commands/functions-secrets-access.js +17 -0
- package/lib/commands/functions-secrets-destroy.js +40 -0
- package/lib/commands/functions-secrets-get.js +21 -0
- package/lib/commands/functions-secrets-prune.js +50 -0
- package/lib/commands/functions-secrets-set.js +46 -0
- package/lib/commands/index.js +7 -3
- package/lib/commands/login.js +1 -1
- package/lib/deploy/functions/backend.js +9 -1
- package/lib/deploy/functions/ensure.js +112 -0
- package/lib/deploy/functions/ensureCloudBuildEnabled.js +0 -49
- package/lib/deploy/functions/prepare.js +12 -18
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +1 -0
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +12 -0
- package/lib/deploy/functions/validate.js +56 -1
- package/lib/deploy/hosting/client.js +9 -0
- package/lib/deploy/hosting/convertConfig.js +6 -0
- package/lib/deploy/hosting/index.js +5 -5
- package/lib/deploy/hosting/prepare.js +25 -25
- package/lib/deploy/hosting/release.js +21 -24
- package/lib/emulator/commandUtils.js +5 -1
- package/lib/emulator/controller.js +3 -1
- package/lib/emulator/downloadableEmulators.js +29 -12
- package/lib/emulator/emulatorLogger.js +7 -0
- package/lib/emulator/functionsEmulator.js +122 -80
- package/lib/emulator/functionsEmulatorRuntime.js +100 -83
- package/lib/emulator/functionsEmulatorShared.js +51 -1
- package/lib/emulator/functionsEmulatorShell.js +1 -2
- package/lib/emulator/functionsRuntimeWorker.js +1 -1
- package/lib/emulator/storage/apis/gcloud.js +2 -2
- package/lib/emulator/storage/files.js +8 -3
- package/lib/extensions/askUserForParam.js +1 -1
- package/lib/extensions/diagnose.js +56 -0
- package/lib/extensions/extensionsHelper.js +10 -17
- package/lib/extensions/listExtensions.js +2 -0
- package/lib/extensions/resolveSource.js +1 -53
- package/lib/extensions/secretsUtils.js +1 -1
- package/lib/extensions/updateHelper.js +0 -14
- package/lib/extensions/utils.js +4 -2
- package/lib/functions/env.js +5 -7
- package/lib/functions/secrets.js +112 -0
- package/lib/gcp/cloudfunctions.js +2 -2
- package/lib/gcp/secretManager.js +128 -46
- package/lib/gcp/storage.js +5 -3
- package/lib/hosting/functionsProxy.js +15 -5
- package/lib/previews.js +1 -1
- package/lib/responseToError.js +16 -7
- package/lib/serve/functions.js +2 -2
- package/lib/serve/hosting.js +1 -1
- package/lib/utils.js +6 -1
- package/npm-shrinkwrap.json +124 -45
- package/package.json +4 -4
- package/schema/firebase-config.json +27 -0
|
@@ -8,6 +8,7 @@ const extensionsApi_1 = require("./extensionsApi");
|
|
|
8
8
|
const extensionsHelper_1 = require("./extensionsHelper");
|
|
9
9
|
const utils = require("../utils");
|
|
10
10
|
const extensionsUtils = require("./utils");
|
|
11
|
+
const logger_1 = require("../logger");
|
|
11
12
|
async function listExtensions(projectId) {
|
|
12
13
|
const instances = await (0, extensionsApi_1.listInstances)(projectId);
|
|
13
14
|
if (instances.length < 1) {
|
|
@@ -47,6 +48,7 @@ async function listExtensions(projectId) {
|
|
|
47
48
|
});
|
|
48
49
|
});
|
|
49
50
|
utils.logLabeledBullet(extensionsHelper_1.logPrefix, `list of extensions installed in ${clc.bold(projectId)}:`);
|
|
51
|
+
logger_1.logger.info(table.toString());
|
|
50
52
|
return formatted;
|
|
51
53
|
}
|
|
52
54
|
exports.listExtensions = listExtensions;
|
|
@@ -1,63 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getTrustedPublishers = exports.getExtensionRegistry =
|
|
3
|
+
exports.getTrustedPublishers = exports.getExtensionRegistry = void 0;
|
|
4
4
|
const _ = require("lodash");
|
|
5
|
-
const clc = require("cli-color");
|
|
6
|
-
const { marked } = require("marked");
|
|
7
|
-
const error_1 = require("../error");
|
|
8
5
|
const logger_1 = require("../logger");
|
|
9
|
-
const prompt_1 = require("../prompt");
|
|
10
6
|
const apiv2_1 = require("../apiv2");
|
|
11
7
|
const api_1 = require("../api");
|
|
12
8
|
const EXTENSIONS_REGISTRY_ENDPOINT = "/extensions.json";
|
|
13
|
-
async function confirmUpdateWarning(updateWarning) {
|
|
14
|
-
logger_1.logger.info(marked(updateWarning.description));
|
|
15
|
-
if (updateWarning.action) {
|
|
16
|
-
logger_1.logger.info(marked(updateWarning.action));
|
|
17
|
-
}
|
|
18
|
-
const continueUpdate = await (0, prompt_1.promptOnce)({
|
|
19
|
-
type: "confirm",
|
|
20
|
-
message: "Do you wish to continue with this update?",
|
|
21
|
-
default: false,
|
|
22
|
-
});
|
|
23
|
-
if (!continueUpdate) {
|
|
24
|
-
throw new error_1.FirebaseError(`Update cancelled.`, { exit: 2 });
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
exports.confirmUpdateWarning = confirmUpdateWarning;
|
|
28
|
-
function resolveSourceUrl(registryEntry, name, version) {
|
|
29
|
-
const targetVersion = getTargetVersion(registryEntry, version);
|
|
30
|
-
const sourceUrl = _.get(registryEntry, ["versions", targetVersion]);
|
|
31
|
-
if (!sourceUrl) {
|
|
32
|
-
throw new error_1.FirebaseError(`Could not find version ${clc.bold(version)} of extension ${clc.bold(name)}.`);
|
|
33
|
-
}
|
|
34
|
-
return sourceUrl;
|
|
35
|
-
}
|
|
36
|
-
exports.resolveSourceUrl = resolveSourceUrl;
|
|
37
|
-
function isOfficialSource(registryEntry, sourceUrl) {
|
|
38
|
-
const versions = _.get(registryEntry, "versions");
|
|
39
|
-
return _.includes(versions, sourceUrl);
|
|
40
|
-
}
|
|
41
|
-
exports.isOfficialSource = isOfficialSource;
|
|
42
|
-
async function resolveRegistryEntry(name) {
|
|
43
|
-
const extensionsRegistry = await getExtensionRegistry();
|
|
44
|
-
const registryEntry = _.get(extensionsRegistry, name);
|
|
45
|
-
if (!registryEntry) {
|
|
46
|
-
throw new error_1.FirebaseError(`Unable to find extension source named ${clc.bold(name)}.`);
|
|
47
|
-
}
|
|
48
|
-
return registryEntry;
|
|
49
|
-
}
|
|
50
|
-
exports.resolveRegistryEntry = resolveRegistryEntry;
|
|
51
|
-
function getTargetVersion(registryEntry, versionOrLabel) {
|
|
52
|
-
const seekVersion = versionOrLabel || "latest";
|
|
53
|
-
const versionFromLabel = _.get(registryEntry, ["labels", seekVersion]);
|
|
54
|
-
return versionFromLabel || seekVersion;
|
|
55
|
-
}
|
|
56
|
-
exports.getTargetVersion = getTargetVersion;
|
|
57
|
-
function getMinRequiredVersion(registryEntry) {
|
|
58
|
-
return _.get(registryEntry, ["labels", "minRequired"]);
|
|
59
|
-
}
|
|
60
|
-
exports.getMinRequiredVersion = getMinRequiredVersion;
|
|
61
9
|
async function getExtensionRegistry(onlyFeatured) {
|
|
62
10
|
const client = new apiv2_1.Client({ urlPrefix: api_1.firebaseExtensionsRegistryOrigin });
|
|
63
11
|
const res = await client.get(EXTENSIONS_REGISTRY_ENDPOINT);
|
|
@@ -22,7 +22,7 @@ async function grantFirexServiceAgentSecretAdminRole(secret) {
|
|
|
22
22
|
const projectNumber = await (0, getProjectNumber_1.getProjectNumber)({ projectId: secret.projectId });
|
|
23
23
|
const firexSaProjectId = utils.envOverride("FIREBASE_EXTENSIONS_SA_PROJECT_ID", "gcp-sa-firebasemods");
|
|
24
24
|
const saEmail = `service-${projectNumber}@${firexSaProjectId}.iam.gserviceaccount.com`;
|
|
25
|
-
return secretManagerApi.
|
|
25
|
+
return secretManagerApi.ensureServiceAgentRole(secret, [saEmail], "roles/secretmanager.admin");
|
|
26
26
|
}
|
|
27
27
|
exports.grantFirexServiceAgentSecretAdminRole = grantFirexServiceAgentSecretAdminRole;
|
|
28
28
|
async function getManagedSecrets(instance) {
|
|
@@ -6,7 +6,6 @@ const semver = require("semver");
|
|
|
6
6
|
const { marked } = require("marked");
|
|
7
7
|
const error_1 = require("../error");
|
|
8
8
|
const logger_1 = require("../logger");
|
|
9
|
-
const resolveSource = require("./resolveSource");
|
|
10
9
|
const extensionsApi = require("./extensionsApi");
|
|
11
10
|
const refs = require("./refs");
|
|
12
11
|
const extensionsHelper_1 = require("./extensionsHelper");
|
|
@@ -123,19 +122,6 @@ async function updateToVersionFromPublisherSource(projectId, instanceId, extVers
|
|
|
123
122
|
catch (err) {
|
|
124
123
|
throw new error_1.FirebaseError(`Could not find source '${clc.bold(extVersionRef)}' because (${clc.bold(version)}) is not a published version. To update, use the latest version of this extension (${clc.bold(extension.latestVersion)}).`);
|
|
125
124
|
}
|
|
126
|
-
let registryEntry;
|
|
127
|
-
try {
|
|
128
|
-
registryEntry = await resolveSource.resolveRegistryEntry(existingSpec.name);
|
|
129
|
-
}
|
|
130
|
-
catch (err) {
|
|
131
|
-
logger_1.logger.debug(`Unable to fetch registry.json entry for ${existingSpec.name}`);
|
|
132
|
-
}
|
|
133
|
-
if (registryEntry) {
|
|
134
|
-
const minVer = resolveSource.getMinRequiredVersion(registryEntry);
|
|
135
|
-
if (minVer && semver.gt(minVer, source.spec.version)) {
|
|
136
|
-
throw new error_1.FirebaseError(`The version you are trying to update to (${clc.bold(source.spec.version)}) is less than the minimum version required (${clc.bold(minVer)}) to use this extension.`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
125
|
showUpdateVersionInfo(instanceId, existingSpec.version, source.spec.version, extVersionRef);
|
|
140
126
|
warningUpdateToOtherSource(extensionsHelper_1.SourceOrigin.PUBLISHED_EXTENSION);
|
|
141
127
|
const releaseNotes = await changelog.getReleaseNotesForUpdate({
|
package/lib/extensions/utils.js
CHANGED
|
@@ -22,12 +22,14 @@ function convertExtensionOptionToLabeledList(options) {
|
|
|
22
22
|
}
|
|
23
23
|
exports.convertExtensionOptionToLabeledList = convertExtensionOptionToLabeledList;
|
|
24
24
|
function convertOfficialExtensionsToList(officialExts) {
|
|
25
|
-
|
|
25
|
+
const l = _.map(officialExts, (entry, key) => {
|
|
26
26
|
return {
|
|
27
27
|
checked: false,
|
|
28
|
-
value: key
|
|
28
|
+
value: `${entry.publisher}/${key}`,
|
|
29
29
|
};
|
|
30
30
|
});
|
|
31
|
+
l.sort((a, b) => a.value.localeCompare(b.value));
|
|
32
|
+
return l;
|
|
31
33
|
}
|
|
32
34
|
exports.convertOfficialExtensionsToList = convertOfficialExtensionsToList;
|
|
33
35
|
function getRandomString(length) {
|
package/lib/functions/env.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.loadFirebaseEnvs = exports.loadUserEnvs = exports.hasUserEnvs = exports.validateKey = exports.KeyValidationError = exports.parse = void 0;
|
|
3
|
+
exports.loadFirebaseEnvs = exports.loadUserEnvs = exports.hasUserEnvs = exports.parseStrict = exports.validateKey = exports.KeyValidationError = exports.parse = void 0;
|
|
4
4
|
const clc = require("cli-color");
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
const path = require("path");
|
|
7
7
|
const error_1 = require("../error");
|
|
8
8
|
const logger_1 = require("../logger");
|
|
9
|
-
const previews_1 = require("../previews");
|
|
10
9
|
const utils_1 = require("../utils");
|
|
11
10
|
const FUNCTIONS_EMULATOR_DOTENV = ".env.local";
|
|
11
|
+
const RESERVED_PREFIXES = ["X_GOOGLE_", "FIREBASE_", "EXT_"];
|
|
12
12
|
const RESERVED_KEYS = [
|
|
13
13
|
"FIREBASE_CONFIG",
|
|
14
14
|
"CLOUD_RUNTIME_CONFIG",
|
|
@@ -87,8 +87,8 @@ function validateKey(key) {
|
|
|
87
87
|
throw new KeyValidationError(key, `Key ${key} must start with an uppercase ASCII letter or underscore` +
|
|
88
88
|
", and then consist of uppercase ASCII letters, digits, and underscores.");
|
|
89
89
|
}
|
|
90
|
-
if (
|
|
91
|
-
throw new KeyValidationError(key, `Key ${key} starts with a reserved prefix (
|
|
90
|
+
if (RESERVED_PREFIXES.some((prefix) => key.startsWith(prefix))) {
|
|
91
|
+
throw new KeyValidationError(key, `Key ${key} starts with a reserved prefix (${RESERVED_PREFIXES.join(" ")})`);
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
exports.validateKey = validateKey;
|
|
@@ -117,6 +117,7 @@ function parseStrict(data) {
|
|
|
117
117
|
}
|
|
118
118
|
return envs;
|
|
119
119
|
}
|
|
120
|
+
exports.parseStrict = parseStrict;
|
|
120
121
|
function findEnvfiles(functionsSource, projectId, projectAlias, isEmulator) {
|
|
121
122
|
const files = [".env"];
|
|
122
123
|
if (isEmulator) {
|
|
@@ -139,9 +140,6 @@ function hasUserEnvs({ functionsSource, projectId, projectAlias, isEmulator, })
|
|
|
139
140
|
exports.hasUserEnvs = hasUserEnvs;
|
|
140
141
|
function loadUserEnvs({ functionsSource, projectId, projectAlias, isEmulator, }) {
|
|
141
142
|
var _a;
|
|
142
|
-
if (!previews_1.previews.dotenv) {
|
|
143
|
-
return {};
|
|
144
|
-
}
|
|
145
143
|
const envFiles = findEnvfiles(functionsSource, projectId, projectAlias, isEmulator);
|
|
146
144
|
if (envFiles.length == 0) {
|
|
147
145
|
return {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pruneSecrets = exports.of = exports.ensureSecret = exports.ensureValidKey = exports.labels = exports.isFirebaseManaged = void 0;
|
|
4
|
+
const secretManager_1 = require("../gcp/secretManager");
|
|
5
|
+
const error_1 = require("../error");
|
|
6
|
+
const utils_1 = require("../utils");
|
|
7
|
+
const prompt_1 = require("../prompt");
|
|
8
|
+
const env_1 = require("./env");
|
|
9
|
+
const FIREBASE_MANGED = "firebase-managed";
|
|
10
|
+
function isFirebaseManaged(secret) {
|
|
11
|
+
return Object.keys(secret.labels || []).includes(FIREBASE_MANGED);
|
|
12
|
+
}
|
|
13
|
+
exports.isFirebaseManaged = isFirebaseManaged;
|
|
14
|
+
function labels() {
|
|
15
|
+
return { [FIREBASE_MANGED]: "true" };
|
|
16
|
+
}
|
|
17
|
+
exports.labels = labels;
|
|
18
|
+
function toUpperSnakeCase(key) {
|
|
19
|
+
return key
|
|
20
|
+
.replace("-", "_")
|
|
21
|
+
.replace(".", "_")
|
|
22
|
+
.replace(/([a-z])([A-Z])/g, "$1_$2")
|
|
23
|
+
.toUpperCase();
|
|
24
|
+
}
|
|
25
|
+
async function ensureValidKey(key, options) {
|
|
26
|
+
const transformedKey = toUpperSnakeCase(key);
|
|
27
|
+
if (transformedKey !== key) {
|
|
28
|
+
if (options.force) {
|
|
29
|
+
throw new error_1.FirebaseError("Secret key must be in UPPER_SNAKE_CASE.");
|
|
30
|
+
}
|
|
31
|
+
(0, utils_1.logWarning)(`By convention, secret key must be in UPPER_SNAKE_CASE.`);
|
|
32
|
+
const confirm = await (0, prompt_1.promptOnce)({
|
|
33
|
+
name: "updateKey",
|
|
34
|
+
type: "confirm",
|
|
35
|
+
default: true,
|
|
36
|
+
message: `Would you like to use ${transformedKey} as key instead?`,
|
|
37
|
+
}, options);
|
|
38
|
+
if (!confirm) {
|
|
39
|
+
throw new error_1.FirebaseError("Secret key must be in UPPER_SNAKE_CASE.");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
(0, env_1.validateKey)(transformedKey);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
throw new error_1.FirebaseError(`Invalid secret key ${transformedKey}`, { children: [err] });
|
|
47
|
+
}
|
|
48
|
+
return transformedKey;
|
|
49
|
+
}
|
|
50
|
+
exports.ensureValidKey = ensureValidKey;
|
|
51
|
+
async function ensureSecret(projectId, name, options) {
|
|
52
|
+
try {
|
|
53
|
+
const secret = await (0, secretManager_1.getSecret)(projectId, name);
|
|
54
|
+
if (!isFirebaseManaged(secret)) {
|
|
55
|
+
if (!options.force) {
|
|
56
|
+
(0, utils_1.logWarning)("Your secret is not managed by Firebase. " +
|
|
57
|
+
"Firebase managed secrets are automatically pruned to reduce your monthly cost for using Secret Manager. ");
|
|
58
|
+
const confirm = await (0, prompt_1.promptOnce)({
|
|
59
|
+
name: "updateLabels",
|
|
60
|
+
type: "confirm",
|
|
61
|
+
default: true,
|
|
62
|
+
message: `Would you like to have your secret ${secret.name} managed by Firebase?`,
|
|
63
|
+
}, options);
|
|
64
|
+
if (confirm) {
|
|
65
|
+
return (0, secretManager_1.patchSecret)(projectId, secret.name, Object.assign(Object.assign({}, secret.labels), labels()));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return secret;
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
if (err.status !== 404) {
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return await (0, secretManager_1.createSecret)(projectId, name, labels());
|
|
77
|
+
}
|
|
78
|
+
exports.ensureSecret = ensureSecret;
|
|
79
|
+
function of(endpoints) {
|
|
80
|
+
return endpoints.reduce((envs, endpoint) => [...envs, ...(endpoint.secretEnvironmentVariables || [])], []);
|
|
81
|
+
}
|
|
82
|
+
exports.of = of;
|
|
83
|
+
async function pruneSecrets(projectInfo, endpoints) {
|
|
84
|
+
const { projectId, projectNumber } = projectInfo;
|
|
85
|
+
const pruneKey = (name, version) => `${name}@${version}`;
|
|
86
|
+
const prunedSecrets = new Set();
|
|
87
|
+
const haveSecrets = await (0, secretManager_1.listSecrets)(projectId, `labels.${FIREBASE_MANGED}=true`);
|
|
88
|
+
for (const secret of haveSecrets) {
|
|
89
|
+
const versions = await (0, secretManager_1.listSecretVersions)(projectId, secret.name, `state: ENABLED`);
|
|
90
|
+
for (const version of versions) {
|
|
91
|
+
prunedSecrets.add(pruneKey(secret.name, version.versionId));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const sevs = of(endpoints).filter((sev) => sev.projectId === projectId || sev.projectId === projectNumber);
|
|
95
|
+
for (const sev of sevs) {
|
|
96
|
+
let name = sev.secret;
|
|
97
|
+
if (name.includes("/")) {
|
|
98
|
+
const secret = (0, secretManager_1.parseSecretResourceName)(name);
|
|
99
|
+
name = secret.name;
|
|
100
|
+
}
|
|
101
|
+
let version = sev.version;
|
|
102
|
+
if (version === "latest") {
|
|
103
|
+
const resolved = await (0, secretManager_1.getSecretVersion)(projectId, name, version);
|
|
104
|
+
version = resolved.versionId;
|
|
105
|
+
}
|
|
106
|
+
prunedSecrets.delete(pruneKey(name, version));
|
|
107
|
+
}
|
|
108
|
+
return Array.from(prunedSecrets)
|
|
109
|
+
.map((key) => key.split("@"))
|
|
110
|
+
.map(([secret, version]) => ({ projectId, version, secret, key: secret }));
|
|
111
|
+
}
|
|
112
|
+
exports.pruneSecrets = pruneSecrets;
|
|
@@ -240,7 +240,7 @@ function endpointFromFunction(gcfFunction) {
|
|
|
240
240
|
if (uri) {
|
|
241
241
|
endpoint.uri = uri;
|
|
242
242
|
}
|
|
243
|
-
proto.copyIfPresent(endpoint, gcfFunction, "serviceAccountEmail", "availableMemoryMb", "timeout", "minInstances", "maxInstances", "vpcConnector", "vpcConnectorEgressSettings", "ingressSettings", "labels", "environmentVariables", "sourceUploadUrl");
|
|
243
|
+
proto.copyIfPresent(endpoint, gcfFunction, "serviceAccountEmail", "availableMemoryMb", "timeout", "minInstances", "maxInstances", "vpcConnector", "vpcConnectorEgressSettings", "ingressSettings", "labels", "environmentVariables", "secretEnvironmentVariables", "sourceUploadUrl");
|
|
244
244
|
return endpoint;
|
|
245
245
|
}
|
|
246
246
|
exports.endpointFromFunction = endpointFromFunction;
|
|
@@ -283,7 +283,7 @@ function functionFromEndpoint(endpoint, sourceUploadUrl) {
|
|
|
283
283
|
else {
|
|
284
284
|
gcfFunction.httpsTrigger = {};
|
|
285
285
|
}
|
|
286
|
-
proto.copyIfPresent(gcfFunction, endpoint, "serviceAccountEmail", "timeout", "availableMemoryMb", "minInstances", "maxInstances", "vpcConnector", "vpcConnectorEgressSettings", "ingressSettings", "environmentVariables");
|
|
286
|
+
proto.copyIfPresent(gcfFunction, endpoint, "serviceAccountEmail", "timeout", "availableMemoryMb", "minInstances", "maxInstances", "vpcConnector", "vpcConnectorEgressSettings", "ingressSettings", "environmentVariables", "secretEnvironmentVariables");
|
|
287
287
|
return gcfFunction;
|
|
288
288
|
}
|
|
289
289
|
exports.functionFromEndpoint = functionFromEndpoint;
|
package/lib/gcp/secretManager.js
CHANGED
|
@@ -1,30 +1,88 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.ensureServiceAgentRole = exports.setIamPolicy = exports.getIamPolicy = exports.addVersion = exports.deleteSecret = exports.patchSecret = exports.createSecret = exports.toSecretVersionResourceName = exports.parseSecretVersionResourceName = exports.parseSecretResourceName = exports.secretExists = exports.destroySecretVersion = exports.accessSecretVersion = exports.getSecretVersion = exports.listSecretVersions = exports.listSecrets = exports.getSecret = exports.secretManagerConsoleUri = void 0;
|
|
4
4
|
const utils_1 = require("../utils");
|
|
5
|
-
const
|
|
5
|
+
const error_1 = require("../error");
|
|
6
6
|
const apiv2_1 = require("../apiv2");
|
|
7
|
+
const api_1 = require("../api");
|
|
8
|
+
const SECRET_NAME_REGEX = new RegExp("projects\\/" +
|
|
9
|
+
"(?<project>(?:\\d+)|(?:[A-Za-z]+[A-Za-z\\d-]*[A-Za-z\\d]?))\\/" +
|
|
10
|
+
"secrets\\/" +
|
|
11
|
+
"(?<secret>[A-Za-z\\d\\-_]+)");
|
|
12
|
+
const SECRET_VERSION_NAME_REGEX = new RegExp(SECRET_NAME_REGEX.source + "\\/versions\\/" + "(?<version>latest|[0-9]+)");
|
|
7
13
|
const secretManagerConsoleUri = (projectId) => `https://console.cloud.google.com/security/secret-manager?project=${projectId}`;
|
|
8
14
|
exports.secretManagerConsoleUri = secretManagerConsoleUri;
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
const listRes = await apiClient.get(`/projects/${projectId}/secrets`);
|
|
12
|
-
return listRes.body.secrets.map((s) => parseSecretResourceName(s.name));
|
|
13
|
-
}
|
|
14
|
-
exports.listSecrets = listSecrets;
|
|
15
|
+
const API_VERSION = "v1";
|
|
16
|
+
const client = new apiv2_1.Client({ urlPrefix: api_1.secretManagerOrigin, apiVersion: API_VERSION });
|
|
15
17
|
async function getSecret(projectId, name) {
|
|
16
18
|
var _a;
|
|
17
|
-
const getRes = await
|
|
19
|
+
const getRes = await client.get(`projects/${projectId}/secrets/${name}`);
|
|
18
20
|
const secret = parseSecretResourceName(getRes.body.name);
|
|
19
21
|
secret.labels = (_a = getRes.body.labels) !== null && _a !== void 0 ? _a : {};
|
|
20
22
|
return secret;
|
|
21
23
|
}
|
|
22
24
|
exports.getSecret = getSecret;
|
|
25
|
+
async function listSecrets(projectId, filter) {
|
|
26
|
+
var _a;
|
|
27
|
+
const secrets = [];
|
|
28
|
+
const path = `projects/${projectId}/secrets`;
|
|
29
|
+
const baseOpts = filter ? { queryParams: { filter } } : {};
|
|
30
|
+
let pageToken = "";
|
|
31
|
+
while (true) {
|
|
32
|
+
const opts = pageToken === ""
|
|
33
|
+
? baseOpts
|
|
34
|
+
: Object.assign(Object.assign({}, baseOpts), { queryParams: Object.assign(Object.assign({}, baseOpts === null || baseOpts === void 0 ? void 0 : baseOpts.queryParams), { pageToken }) });
|
|
35
|
+
const res = await client.get(path, opts);
|
|
36
|
+
for (const s of res.body.secrets) {
|
|
37
|
+
secrets.push(Object.assign(Object.assign({}, parseSecretResourceName(s.name)), { labels: (_a = s.labels) !== null && _a !== void 0 ? _a : {} }));
|
|
38
|
+
}
|
|
39
|
+
if (!res.body.nextPageToken) {
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
pageToken = res.body.nextPageToken;
|
|
43
|
+
}
|
|
44
|
+
return secrets;
|
|
45
|
+
}
|
|
46
|
+
exports.listSecrets = listSecrets;
|
|
47
|
+
async function listSecretVersions(projectId, name, filter) {
|
|
48
|
+
const secrets = [];
|
|
49
|
+
const path = `projects/${projectId}/secrets/${name}/versions`;
|
|
50
|
+
const baseOpts = filter ? { queryParams: { filter } } : {};
|
|
51
|
+
let pageToken = "";
|
|
52
|
+
while (true) {
|
|
53
|
+
const opts = pageToken === ""
|
|
54
|
+
? baseOpts
|
|
55
|
+
: Object.assign(Object.assign({}, baseOpts), { queryParams: Object.assign(Object.assign({}, baseOpts === null || baseOpts === void 0 ? void 0 : baseOpts.queryParams), { pageToken }) });
|
|
56
|
+
const res = await client.get(path, opts);
|
|
57
|
+
for (const s of res.body.versions || []) {
|
|
58
|
+
secrets.push(Object.assign(Object.assign({}, parseSecretVersionResourceName(s.name)), { state: s.state }));
|
|
59
|
+
}
|
|
60
|
+
if (!res.body.nextPageToken) {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
pageToken = res.body.nextPageToken;
|
|
64
|
+
}
|
|
65
|
+
return secrets;
|
|
66
|
+
}
|
|
67
|
+
exports.listSecretVersions = listSecretVersions;
|
|
23
68
|
async function getSecretVersion(projectId, name, version) {
|
|
24
|
-
const getRes = await
|
|
25
|
-
return parseSecretVersionResourceName(getRes.body.name);
|
|
69
|
+
const getRes = await client.get(`projects/${projectId}/secrets/${name}/versions/${version}`);
|
|
70
|
+
return Object.assign(Object.assign({}, parseSecretVersionResourceName(getRes.body.name)), { state: getRes.body.state });
|
|
26
71
|
}
|
|
27
72
|
exports.getSecretVersion = getSecretVersion;
|
|
73
|
+
async function accessSecretVersion(projectId, name, version) {
|
|
74
|
+
const res = await client.get(`projects/${projectId}/secrets/${name}/versions/${version}:access`);
|
|
75
|
+
return Buffer.from(res.body.payload.data, "base64").toString();
|
|
76
|
+
}
|
|
77
|
+
exports.accessSecretVersion = accessSecretVersion;
|
|
78
|
+
async function destroySecretVersion(projectId, name, version) {
|
|
79
|
+
if (version === "latest") {
|
|
80
|
+
const sv = await getSecretVersion(projectId, name, "latest");
|
|
81
|
+
version = sv.versionId;
|
|
82
|
+
}
|
|
83
|
+
await client.post(`projects/${projectId}/secrets/${name}/versions/${version}:destroy`);
|
|
84
|
+
}
|
|
85
|
+
exports.destroySecretVersion = destroySecretVersion;
|
|
28
86
|
async function secretExists(projectId, name) {
|
|
29
87
|
try {
|
|
30
88
|
await getSecret(projectId, name);
|
|
@@ -39,21 +97,27 @@ async function secretExists(projectId, name) {
|
|
|
39
97
|
}
|
|
40
98
|
exports.secretExists = secretExists;
|
|
41
99
|
function parseSecretResourceName(resourceName) {
|
|
42
|
-
const
|
|
100
|
+
const match = SECRET_NAME_REGEX.exec(resourceName);
|
|
101
|
+
if (!(match === null || match === void 0 ? void 0 : match.groups)) {
|
|
102
|
+
throw new error_1.FirebaseError(`Invalid secret resource name [${resourceName}].`);
|
|
103
|
+
}
|
|
43
104
|
return {
|
|
44
|
-
projectId:
|
|
45
|
-
name:
|
|
105
|
+
projectId: match.groups.project,
|
|
106
|
+
name: match.groups.secret,
|
|
46
107
|
};
|
|
47
108
|
}
|
|
48
109
|
exports.parseSecretResourceName = parseSecretResourceName;
|
|
49
110
|
function parseSecretVersionResourceName(resourceName) {
|
|
50
|
-
const
|
|
111
|
+
const match = resourceName.match(SECRET_VERSION_NAME_REGEX);
|
|
112
|
+
if (!(match === null || match === void 0 ? void 0 : match.groups)) {
|
|
113
|
+
throw new error_1.FirebaseError(`Invalid secret version resource name [${resourceName}].`);
|
|
114
|
+
}
|
|
51
115
|
return {
|
|
52
116
|
secret: {
|
|
53
|
-
projectId:
|
|
54
|
-
name:
|
|
117
|
+
projectId: match.groups.project,
|
|
118
|
+
name: match.groups.secret,
|
|
55
119
|
},
|
|
56
|
-
versionId:
|
|
120
|
+
versionId: match.groups.version,
|
|
57
121
|
};
|
|
58
122
|
}
|
|
59
123
|
exports.parseSecretVersionResourceName = parseSecretVersionResourceName;
|
|
@@ -62,7 +126,8 @@ function toSecretVersionResourceName(secretVersion) {
|
|
|
62
126
|
}
|
|
63
127
|
exports.toSecretVersionResourceName = toSecretVersionResourceName;
|
|
64
128
|
async function createSecret(projectId, name, labels) {
|
|
65
|
-
const createRes = await
|
|
129
|
+
const createRes = await client.post(`projects/${projectId}/secrets`, {
|
|
130
|
+
name,
|
|
66
131
|
replication: {
|
|
67
132
|
automatic: {},
|
|
68
133
|
},
|
|
@@ -71,41 +136,58 @@ async function createSecret(projectId, name, labels) {
|
|
|
71
136
|
return parseSecretResourceName(createRes.body.name);
|
|
72
137
|
}
|
|
73
138
|
exports.createSecret = createSecret;
|
|
74
|
-
async function
|
|
75
|
-
const
|
|
139
|
+
async function patchSecret(projectId, name, labels) {
|
|
140
|
+
const fullName = `projects/${projectId}/secrets/${name}`;
|
|
141
|
+
const res = await client.patch(fullName, { name: fullName, labels }, { queryParams: { updateMask: "labels" } });
|
|
142
|
+
return parseSecretResourceName(res.body.name);
|
|
143
|
+
}
|
|
144
|
+
exports.patchSecret = patchSecret;
|
|
145
|
+
async function deleteSecret(projectId, name) {
|
|
146
|
+
const path = `projects/${projectId}/secrets/${name}`;
|
|
147
|
+
await client.delete(path);
|
|
148
|
+
}
|
|
149
|
+
exports.deleteSecret = deleteSecret;
|
|
150
|
+
async function addVersion(projectId, name, payloadData) {
|
|
151
|
+
const res = await client.post(`projects/${projectId}/secrets/${name}:addVersion`, {
|
|
76
152
|
payload: {
|
|
77
153
|
data: Buffer.from(payloadData).toString("base64"),
|
|
78
154
|
},
|
|
79
155
|
});
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
secret: {
|
|
83
|
-
projectId: nameTokens[1],
|
|
84
|
-
name: nameTokens[3],
|
|
85
|
-
},
|
|
86
|
-
versionId: nameTokens[5],
|
|
87
|
-
};
|
|
156
|
+
return Object.assign(Object.assign({}, parseSecretVersionResourceName(res.body.name)), { state: res.body.state });
|
|
88
157
|
}
|
|
89
158
|
exports.addVersion = addVersion;
|
|
90
|
-
async function
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
bindings.push({
|
|
98
|
-
role: role,
|
|
99
|
-
members: [`serviceAccount:${serviceAccountEmail}`],
|
|
100
|
-
});
|
|
101
|
-
await apiClient.post(`/projects/${secret.projectId}/secrets/${secret.name}:setIamPolicy`, {
|
|
159
|
+
async function getIamPolicy(secret) {
|
|
160
|
+
const res = await client.get(`projects/${secret.projectId}/secrets/${secret.name}:getIamPolicy`);
|
|
161
|
+
return res.body;
|
|
162
|
+
}
|
|
163
|
+
exports.getIamPolicy = getIamPolicy;
|
|
164
|
+
async function setIamPolicy(secret, bindings) {
|
|
165
|
+
await client.post(`projects/${secret.projectId}/secrets/${secret.name}:setIamPolicy`, {
|
|
102
166
|
policy: {
|
|
103
167
|
bindings,
|
|
104
168
|
},
|
|
105
|
-
updateMask:
|
|
106
|
-
paths: "bindings",
|
|
107
|
-
},
|
|
169
|
+
updateMask: "bindings",
|
|
108
170
|
});
|
|
109
|
-
(0, utils_1.logLabeledSuccess)("SecretManager", `Granted ${role} on projects/${secret.projectId}/secrets/${secret.name} to ${serviceAccountEmail}`);
|
|
110
171
|
}
|
|
111
|
-
exports.
|
|
172
|
+
exports.setIamPolicy = setIamPolicy;
|
|
173
|
+
async function ensureServiceAgentRole(secret, serviceAccountEmails, role) {
|
|
174
|
+
const policy = await module.exports.getIamPolicy(secret);
|
|
175
|
+
const bindings = policy.bindings || [];
|
|
176
|
+
let binding = bindings.find((b) => b.role === role);
|
|
177
|
+
if (!binding) {
|
|
178
|
+
binding = { role, members: [] };
|
|
179
|
+
bindings.push(binding);
|
|
180
|
+
}
|
|
181
|
+
let shouldShortCircuit = true;
|
|
182
|
+
for (const serviceAccount of serviceAccountEmails) {
|
|
183
|
+
if (!binding.members.find((m) => m === `serviceAccount:${serviceAccount}`)) {
|
|
184
|
+
binding.members.push(`serviceAccount:${serviceAccount}`);
|
|
185
|
+
shouldShortCircuit = false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (shouldShortCircuit)
|
|
189
|
+
return;
|
|
190
|
+
await module.exports.setIamPolicy(secret, bindings);
|
|
191
|
+
(0, utils_1.logLabeledSuccess)("secretmanager", `Granted ${role} on projects/${secret.projectId}/secrets/${secret.name} to ${serviceAccountEmails.join(", ")}`);
|
|
192
|
+
}
|
|
193
|
+
exports.ensureServiceAgentRole = ensureServiceAgentRole;
|
package/lib/gcp/storage.js
CHANGED
|
@@ -6,10 +6,10 @@ const api_1 = require("../api");
|
|
|
6
6
|
const apiv2_1 = require("../apiv2");
|
|
7
7
|
const logger_1 = require("../logger");
|
|
8
8
|
const error_1 = require("../error");
|
|
9
|
-
const storageAPIClient = new apiv2_1.Client({ urlPrefix: api_1.storageOrigin, apiVersion: "v1" });
|
|
10
9
|
async function getDefaultBucket(projectId) {
|
|
11
10
|
try {
|
|
12
|
-
const
|
|
11
|
+
const appengineClient = new apiv2_1.Client({ urlPrefix: api_1.appengineOrigin, apiVersion: "v1" });
|
|
12
|
+
const resp = await appengineClient.get(`/apps/${projectId}`);
|
|
13
13
|
if (resp.body.defaultBucket === "undefined") {
|
|
14
14
|
logger_1.logger.debug("Default storage bucket is undefined.");
|
|
15
15
|
throw new error_1.FirebaseError("Your project is being set up. Please wait a minute before deploying again.");
|
|
@@ -29,6 +29,7 @@ async function upload(source, uploadUrl, extraHeaders) {
|
|
|
29
29
|
method: "PUT",
|
|
30
30
|
path: url.pathname,
|
|
31
31
|
queryParams: url.searchParams,
|
|
32
|
+
responseType: "xml",
|
|
32
33
|
headers: Object.assign({ "content-type": "application/zip" }, extraHeaders),
|
|
33
34
|
body: source.stream,
|
|
34
35
|
skipLog: { resBody: true },
|
|
@@ -42,8 +43,9 @@ async function uploadObject(source, bucketName) {
|
|
|
42
43
|
if (path.extname(source.file) !== ".zip") {
|
|
43
44
|
throw new error_1.FirebaseError(`Expected a file name ending in .zip, got ${source.file}`);
|
|
44
45
|
}
|
|
46
|
+
const localAPIClient = new apiv2_1.Client({ urlPrefix: api_1.storageOrigin });
|
|
45
47
|
const location = `/${bucketName}/${path.basename(source.file)}`;
|
|
46
|
-
const res = await
|
|
48
|
+
const res = await localAPIClient.request({
|
|
47
49
|
method: "PUT",
|
|
48
50
|
path: location,
|
|
49
51
|
headers: {
|
|
@@ -1,26 +1,36 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.functionsProxy = void 0;
|
|
3
4
|
const lodash_1 = require("lodash");
|
|
4
5
|
const proxy_1 = require("./proxy");
|
|
5
6
|
const projectUtils_1 = require("../projectUtils");
|
|
6
7
|
const registry_1 = require("../emulator/registry");
|
|
7
8
|
const types_1 = require("../emulator/types");
|
|
8
9
|
const functionsEmulator_1 = require("../emulator/functionsEmulator");
|
|
9
|
-
|
|
10
|
+
const error_1 = require("../error");
|
|
11
|
+
function functionsProxy(options) {
|
|
10
12
|
return (rewrite) => {
|
|
11
13
|
return new Promise((resolve) => {
|
|
12
14
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
13
|
-
|
|
15
|
+
if (!("function" in rewrite)) {
|
|
16
|
+
throw new error_1.FirebaseError(`A non-function rewrite cannot be used in functionsProxy`, {
|
|
17
|
+
exit: 2,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
if (!rewrite.region) {
|
|
21
|
+
rewrite.region = "us-central1";
|
|
22
|
+
}
|
|
23
|
+
let url = `https://${rewrite.region}-${projectId}.cloudfunctions.net/${rewrite.function}`;
|
|
14
24
|
let destLabel = "live";
|
|
15
25
|
if ((0, lodash_1.includes)(options.targets, "functions")) {
|
|
16
26
|
destLabel = "local";
|
|
17
27
|
const functionsEmu = registry_1.EmulatorRegistry.get(types_1.Emulators.FUNCTIONS);
|
|
18
28
|
if (functionsEmu) {
|
|
19
|
-
url = functionsEmulator_1.FunctionsEmulator.getHttpFunctionUrl(functionsEmu.getInfo().host, functionsEmu.getInfo().port, projectId, rewrite.function,
|
|
29
|
+
url = functionsEmulator_1.FunctionsEmulator.getHttpFunctionUrl(functionsEmu.getInfo().host, functionsEmu.getInfo().port, projectId, rewrite.function, rewrite.region);
|
|
20
30
|
}
|
|
21
31
|
}
|
|
22
|
-
resolve((0, proxy_1.proxyRequestHandler)(url, `${destLabel} Function ${rewrite.function}`));
|
|
32
|
+
resolve((0, proxy_1.proxyRequestHandler)(url, `${destLabel} Function ${rewrite.region}/${rewrite.function}`));
|
|
23
33
|
});
|
|
24
34
|
};
|
|
25
35
|
}
|
|
26
|
-
exports.
|
|
36
|
+
exports.functionsProxy = functionsProxy;
|
package/lib/previews.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.previews = void 0;
|
|
4
4
|
const lodash_1 = require("lodash");
|
|
5
5
|
const configstore_1 = require("./configstore");
|
|
6
|
-
exports.previews = Object.assign({ rtdbrules: false, ext: false, extdev: false, rtdbmanagement: false, functionsv2: false, golang: false, deletegcfartifacts: false,
|
|
6
|
+
exports.previews = Object.assign({ rtdbrules: false, ext: false, extdev: false, rtdbmanagement: false, functionsv2: false, golang: false, deletegcfartifacts: false, artifactregistry: false, emulatoruisnapshot: false }, configstore_1.configstore.get("previews"));
|
|
7
7
|
if (process.env.FIREBASE_CLI_PREVIEWS) {
|
|
8
8
|
process.env.FIREBASE_CLI_PREVIEWS.split(",").forEach((feature) => {
|
|
9
9
|
if ((0, lodash_1.has)(exports.previews, feature)) {
|