firebase-tools 9.19.0 → 9.23.0
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/CHANGELOG.md +1 -3
- package/lib/api.js +3 -0
- package/lib/apiv2.js +7 -4
- package/lib/commands/crashlytics-symbols-upload.js +146 -0
- package/lib/commands/deploy.js +9 -1
- package/lib/commands/ext-configure.js +3 -1
- package/lib/commands/ext-dev-deprecate.js +63 -0
- package/lib/commands/ext-dev-undeprecate.js +56 -0
- package/lib/commands/ext-dev-unpublish.js +10 -3
- package/lib/commands/ext-export.js +44 -0
- package/lib/commands/ext-install.js +24 -3
- package/lib/commands/ext-uninstall.js +6 -0
- package/lib/commands/ext-update.js +10 -3
- package/lib/commands/functions-config-export.js +115 -0
- package/lib/commands/functions-delete.js +47 -25
- package/lib/commands/functions-list.js +12 -12
- package/lib/commands/index.js +9 -0
- package/lib/commands/init.js +3 -0
- package/lib/config.js +3 -2
- package/lib/deploy/extensions/args.js +2 -0
- package/lib/deploy/extensions/deploy.js +49 -0
- package/lib/deploy/extensions/deploymentSummary.js +52 -0
- package/lib/deploy/extensions/errors.js +31 -0
- package/lib/deploy/extensions/index.js +8 -0
- package/lib/deploy/extensions/params.js +39 -0
- package/lib/deploy/extensions/planner.js +94 -0
- package/lib/deploy/extensions/prepare.js +111 -0
- package/lib/deploy/extensions/release.js +43 -0
- package/lib/deploy/extensions/secrets.js +150 -0
- package/lib/deploy/extensions/tasks.js +98 -0
- package/lib/deploy/extensions/validate.js +17 -0
- package/lib/deploy/functions/backend.js +93 -115
- package/lib/deploy/functions/checkIam.js +8 -8
- package/lib/deploy/functions/containerCleaner.js +71 -14
- package/lib/deploy/functions/deploy.js +4 -10
- package/lib/deploy/functions/functionsDeployHelper.js +3 -68
- package/lib/deploy/functions/prepare.js +63 -27
- package/lib/deploy/functions/pricing.js +17 -17
- package/lib/deploy/functions/prompts.js +22 -21
- package/lib/deploy/functions/release/executor.js +39 -0
- package/lib/deploy/functions/release/fabricator.js +422 -0
- package/lib/deploy/functions/release/index.js +73 -0
- package/lib/deploy/functions/release/planner.js +162 -0
- package/lib/deploy/functions/release/reporter.js +165 -0
- package/lib/deploy/functions/release/sourceTokenScraper.js +28 -0
- package/lib/deploy/functions/release/timer.js +14 -0
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +129 -126
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +41 -45
- package/lib/deploy/functions/triggerRegionHelper.js +40 -0
- package/lib/deploy/functions/validate.js +1 -24
- package/lib/deploy/index.js +10 -1
- package/lib/downloadUtils.js +37 -0
- package/lib/emulator/auth/apiSpec.js +549 -6
- package/lib/emulator/auth/handlers.js +4 -3
- package/lib/emulator/auth/operations.js +154 -14
- package/lib/emulator/auth/server.js +26 -15
- package/lib/emulator/auth/state.js +151 -13
- package/lib/emulator/download.js +2 -31
- package/lib/emulator/downloadableEmulators.js +7 -7
- package/lib/emulator/functionsEmulator.js +18 -4
- package/lib/emulator/functionsEmulatorRuntime.js +29 -7
- package/lib/emulator/storage/cloudFunctions.js +37 -7
- package/lib/extensions/askUserForConsent.js +14 -1
- package/lib/extensions/askUserForParam.js +81 -4
- package/lib/extensions/checkProjectBilling.js +7 -7
- package/lib/extensions/export.js +107 -0
- package/lib/extensions/extensionsApi.js +104 -21
- package/lib/extensions/extensionsHelper.js +6 -2
- package/lib/extensions/listExtensions.js +16 -11
- package/lib/extensions/paramHelper.js +9 -6
- package/lib/extensions/provisioningHelper.js +16 -3
- package/lib/extensions/refs.js +9 -1
- package/lib/extensions/secretsUtils.js +59 -0
- package/lib/extensions/updateHelper.js +12 -2
- package/lib/extensions/versionHelper.js +14 -0
- package/lib/extensions/warnings.js +33 -1
- package/lib/functional.js +8 -1
- package/lib/functions/env.js +10 -4
- package/lib/functions/runtimeConfigExport.js +137 -0
- package/lib/gcp/artifactregistry.js +16 -0
- package/lib/gcp/cloudfunctions.js +20 -74
- package/lib/gcp/cloudfunctionsv2.js +12 -90
- package/lib/gcp/cloudscheduler.js +22 -16
- package/lib/gcp/cloudtasks.js +143 -0
- package/lib/gcp/docker.js +7 -1
- package/lib/gcp/proto.js +2 -2
- package/lib/gcp/pubsub.js +1 -9
- package/lib/gcp/secretManager.js +132 -0
- package/lib/gcp/storage.js +16 -0
- package/lib/projectUtils.js +10 -1
- package/lib/requireInteractive.js +12 -0
- package/lib/utils.js +30 -1
- package/package.json +5 -4
- package/schema/firebase-config.json +9 -0
- package/lib/deploy/functions/deploymentPlanner.js +0 -113
- package/lib/deploy/functions/deploymentTimer.js +0 -23
- package/lib/deploy/functions/errorHandler.js +0 -75
- package/lib/deploy/functions/release.js +0 -116
- package/lib/deploy/functions/tasks.js +0 -324
- package/lib/functions/listFunctions.js +0 -10
- package/lib/functionsDelete.js +0 -60
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.queueFromEndpoint = exports.queueNameForEndpoint = exports.setEnqueuer = exports.getIamPolicy = exports.setIamPolicy = exports.deleteQueue = exports.purgeQueue = exports.upsertQueue = exports.updateQueue = exports.getQueue = exports.createQueue = exports.DEFAULT_SETTINGS = void 0;
|
|
4
|
+
const proto = require("./proto");
|
|
5
|
+
const apiv2_1 = require("../apiv2");
|
|
6
|
+
const api_1 = require("../api");
|
|
7
|
+
const API_VERSION = "v2";
|
|
8
|
+
const client = new apiv2_1.Client({
|
|
9
|
+
urlPrefix: api_1.cloudTasksOrigin,
|
|
10
|
+
auth: true,
|
|
11
|
+
apiVersion: API_VERSION,
|
|
12
|
+
});
|
|
13
|
+
exports.DEFAULT_SETTINGS = {
|
|
14
|
+
rateLimits: {
|
|
15
|
+
maxConcurrentDispatches: 1000,
|
|
16
|
+
maxBurstSize: 100,
|
|
17
|
+
maxDispatchesPerSecond: 500,
|
|
18
|
+
},
|
|
19
|
+
state: "RUNNING",
|
|
20
|
+
retryConfig: {
|
|
21
|
+
maxDoublings: 16,
|
|
22
|
+
maxAttempts: 3,
|
|
23
|
+
maxBackoff: "3600s",
|
|
24
|
+
minBackoff: "0.100s",
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
async function createQueue(queue) {
|
|
28
|
+
const path = queue.name.substring(0, queue.name.lastIndexOf("/"));
|
|
29
|
+
const res = await client.post(path, queue);
|
|
30
|
+
return res.body;
|
|
31
|
+
}
|
|
32
|
+
exports.createQueue = createQueue;
|
|
33
|
+
async function getQueue(name) {
|
|
34
|
+
const res = await client.get(name);
|
|
35
|
+
return res.body;
|
|
36
|
+
}
|
|
37
|
+
exports.getQueue = getQueue;
|
|
38
|
+
async function updateQueue(queue) {
|
|
39
|
+
const res = await client.patch(queue.name, queue, {
|
|
40
|
+
queryParams: { updateMask: proto.fieldMasks(queue).join(",") },
|
|
41
|
+
});
|
|
42
|
+
return res.body;
|
|
43
|
+
}
|
|
44
|
+
exports.updateQueue = updateQueue;
|
|
45
|
+
async function upsertQueue(queue) {
|
|
46
|
+
var _a, _b;
|
|
47
|
+
try {
|
|
48
|
+
const existing = await module.exports.getQueue(queue.name);
|
|
49
|
+
if (JSON.stringify(queue) === JSON.stringify(existing)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if (existing.state === "DISABLED") {
|
|
53
|
+
await module.exports.purgeQueue(queue.name);
|
|
54
|
+
}
|
|
55
|
+
await module.exports.updateQueue(queue);
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
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) === 404) {
|
|
60
|
+
await module.exports.createQueue(queue);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
exports.upsertQueue = upsertQueue;
|
|
67
|
+
async function purgeQueue(name) {
|
|
68
|
+
await client.post(`${name}:purge`);
|
|
69
|
+
}
|
|
70
|
+
exports.purgeQueue = purgeQueue;
|
|
71
|
+
async function deleteQueue(name) {
|
|
72
|
+
await client.delete(name);
|
|
73
|
+
}
|
|
74
|
+
exports.deleteQueue = deleteQueue;
|
|
75
|
+
async function setIamPolicy(name, policy) {
|
|
76
|
+
const res = await client.post(`${name}:setIamPolicy`, {
|
|
77
|
+
policy,
|
|
78
|
+
});
|
|
79
|
+
return res.body;
|
|
80
|
+
}
|
|
81
|
+
exports.setIamPolicy = setIamPolicy;
|
|
82
|
+
async function getIamPolicy(name) {
|
|
83
|
+
const res = await client.post(`${name}:getIamPolicy`);
|
|
84
|
+
return res.body;
|
|
85
|
+
}
|
|
86
|
+
exports.getIamPolicy = getIamPolicy;
|
|
87
|
+
const ENQUEUER_ROLE = "roles/cloudtasks.enqueuer";
|
|
88
|
+
async function setEnqueuer(name, invoker, assumeEmpty = false) {
|
|
89
|
+
var _a, _b;
|
|
90
|
+
let existing;
|
|
91
|
+
if (assumeEmpty) {
|
|
92
|
+
existing = {
|
|
93
|
+
bindings: [],
|
|
94
|
+
etag: "",
|
|
95
|
+
version: 3,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
existing = await module.exports.getIamPolicy(name);
|
|
100
|
+
}
|
|
101
|
+
const [, project] = name.split("/");
|
|
102
|
+
const invokerMembers = proto.getInvokerMembers(invoker, project);
|
|
103
|
+
while (true) {
|
|
104
|
+
const policy = {
|
|
105
|
+
bindings: existing.bindings.filter((binding) => binding.role != ENQUEUER_ROLE),
|
|
106
|
+
etag: existing.etag,
|
|
107
|
+
version: existing.version,
|
|
108
|
+
};
|
|
109
|
+
if (invokerMembers.length) {
|
|
110
|
+
policy.bindings.push({ role: ENQUEUER_ROLE, members: invokerMembers });
|
|
111
|
+
}
|
|
112
|
+
if (JSON.stringify(policy) === JSON.stringify(existing)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
await module.exports.setIamPolicy(name, policy);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
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) {
|
|
121
|
+
existing = await module.exports.getIamPolicy(name);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
exports.setEnqueuer = setEnqueuer;
|
|
129
|
+
function queueNameForEndpoint(endpoint) {
|
|
130
|
+
return `projects/${endpoint.project}/locations/${endpoint.region}/queues/${endpoint.id}`;
|
|
131
|
+
}
|
|
132
|
+
exports.queueNameForEndpoint = queueNameForEndpoint;
|
|
133
|
+
function queueFromEndpoint(endpoint) {
|
|
134
|
+
const queue = Object.assign(Object.assign({}, JSON.parse(JSON.stringify(exports.DEFAULT_SETTINGS))), { name: queueNameForEndpoint(endpoint) });
|
|
135
|
+
if (endpoint.taskQueueTrigger.rateLimits) {
|
|
136
|
+
proto.copyIfPresent(queue.rateLimits, endpoint.taskQueueTrigger.rateLimits, "maxBurstSize", "maxConcurrentDispatches", "maxDispatchesPerSecond");
|
|
137
|
+
}
|
|
138
|
+
if (endpoint.taskQueueTrigger.retryConfig) {
|
|
139
|
+
proto.copyIfPresent(queue.retryConfig, endpoint.taskQueueTrigger.retryConfig, "maxAttempts", "maxBackoff", "maxDoublings", "maxRetryDuration", "minBackoff");
|
|
140
|
+
}
|
|
141
|
+
return queue;
|
|
142
|
+
}
|
|
143
|
+
exports.queueFromEndpoint = queueFromEndpoint;
|
package/lib/gcp/docker.js
CHANGED
|
@@ -4,7 +4,7 @@ exports.Client = void 0;
|
|
|
4
4
|
const error_1 = require("../error");
|
|
5
5
|
const api = require("../apiv2");
|
|
6
6
|
function isErrors(response) {
|
|
7
|
-
return Object.prototype.hasOwnProperty.call(response, "errors");
|
|
7
|
+
return !!response && Object.prototype.hasOwnProperty.call(response, "errors");
|
|
8
8
|
}
|
|
9
9
|
const API_VERSION = "v2";
|
|
10
10
|
class Client {
|
|
@@ -27,6 +27,9 @@ class Client {
|
|
|
27
27
|
async deleteTag(path, tag) {
|
|
28
28
|
var _a;
|
|
29
29
|
const response = await this.client.delete(`${path}/manifests/${tag}`);
|
|
30
|
+
if (!response.body) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
30
33
|
if (((_a = response.body.errors) === null || _a === void 0 ? void 0 : _a.length) != 0) {
|
|
31
34
|
throw new error_1.FirebaseError(`Failed to delete tag ${tag} at path ${path}`, {
|
|
32
35
|
children: response.body.errors,
|
|
@@ -36,6 +39,9 @@ class Client {
|
|
|
36
39
|
async deleteImage(path, digest) {
|
|
37
40
|
var _a;
|
|
38
41
|
const response = await this.client.delete(`${path}/manifests/${digest}`);
|
|
42
|
+
if (!response.body) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
39
45
|
if (((_a = response.body.errors) === null || _a === void 0 ? void 0 : _a.length) != 0) {
|
|
40
46
|
throw new error_1.FirebaseError(`Failed to delete image ${digest} at path ${path}`, {
|
|
41
47
|
children: response.body.errors,
|
package/lib/gcp/proto.js
CHANGED
|
@@ -67,10 +67,10 @@ function fieldMasksHelper(prefixes, cursor, doNotRecurseIn, masks) {
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
function getInvokerMembers(invoker, projectId) {
|
|
70
|
-
if (invoker
|
|
70
|
+
if (invoker.includes("private")) {
|
|
71
71
|
return [];
|
|
72
72
|
}
|
|
73
|
-
if (invoker
|
|
73
|
+
if (invoker.includes("public")) {
|
|
74
74
|
return ["allUsers"];
|
|
75
75
|
}
|
|
76
76
|
return invoker.map((inv) => formatServiceAccount(inv, projectId));
|
package/lib/gcp/pubsub.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.deleteTopic = exports.updateTopic = exports.getTopic = exports.createTopic = void 0;
|
|
4
4
|
const apiv2_1 = require("../apiv2");
|
|
5
5
|
const api_1 = require("../api");
|
|
6
|
-
const backend = require("../deploy/functions/backend");
|
|
7
6
|
const proto = require("./proto");
|
|
8
7
|
const API_VERSION = "v1";
|
|
9
8
|
const client = new apiv2_1.Client({
|
|
@@ -33,10 +32,3 @@ async function deleteTopic(name) {
|
|
|
33
32
|
await client.delete(name);
|
|
34
33
|
}
|
|
35
34
|
exports.deleteTopic = deleteTopic;
|
|
36
|
-
function topicFromSpec(spec) {
|
|
37
|
-
return {
|
|
38
|
-
name: backend.topicName(spec),
|
|
39
|
-
labels: Object.assign({}, spec.labels),
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
exports.topicFromSpec = topicFromSpec;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.grantServiceAgentRole = exports.addVersion = exports.createSecret = exports.toSecretVersionResourceName = exports.parseSecretVersionResourceName = exports.parseSecretResourceName = exports.secretExists = exports.getSecretVersion = exports.getSecret = exports.listSecrets = exports.secretManagerConsoleUri = void 0;
|
|
4
|
+
const utils_1 = require("../utils");
|
|
5
|
+
const api = require("../api");
|
|
6
|
+
exports.secretManagerConsoleUri = (projectId) => `https://console.cloud.google.com/security/secret-manager?project=${projectId}`;
|
|
7
|
+
async function listSecrets(projectId) {
|
|
8
|
+
const listRes = await api.request("GET", `/v1beta1/projects/${projectId}/secrets`, {
|
|
9
|
+
auth: true,
|
|
10
|
+
origin: api.secretManagerOrigin,
|
|
11
|
+
});
|
|
12
|
+
return listRes.body.secrets.map((s) => parseSecretResourceName(s.name));
|
|
13
|
+
}
|
|
14
|
+
exports.listSecrets = listSecrets;
|
|
15
|
+
async function getSecret(projectId, name) {
|
|
16
|
+
var _a;
|
|
17
|
+
const getRes = await api.request("GET", `/v1beta1/projects/${projectId}/secrets/${name}`, {
|
|
18
|
+
auth: true,
|
|
19
|
+
origin: api.secretManagerOrigin,
|
|
20
|
+
});
|
|
21
|
+
const secret = parseSecretResourceName(getRes.body.name);
|
|
22
|
+
secret.labels = (_a = getRes.body.labels) !== null && _a !== void 0 ? _a : {};
|
|
23
|
+
return secret;
|
|
24
|
+
}
|
|
25
|
+
exports.getSecret = getSecret;
|
|
26
|
+
async function getSecretVersion(projectId, name, version) {
|
|
27
|
+
const getRes = await api.request("GET", `/v1beta1/projects/${projectId}/secrets/${name}/versions/${version}`, {
|
|
28
|
+
auth: true,
|
|
29
|
+
origin: api.secretManagerOrigin,
|
|
30
|
+
});
|
|
31
|
+
return parseSecretVersionResourceName(getRes.body.name);
|
|
32
|
+
}
|
|
33
|
+
exports.getSecretVersion = getSecretVersion;
|
|
34
|
+
async function secretExists(projectId, name) {
|
|
35
|
+
try {
|
|
36
|
+
await getSecret(projectId, name);
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
if (err.status === 404) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.secretExists = secretExists;
|
|
47
|
+
function parseSecretResourceName(resourceName) {
|
|
48
|
+
const nameTokens = resourceName.split("/");
|
|
49
|
+
return {
|
|
50
|
+
projectId: nameTokens[1],
|
|
51
|
+
name: nameTokens[3],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
exports.parseSecretResourceName = parseSecretResourceName;
|
|
55
|
+
function parseSecretVersionResourceName(resourceName) {
|
|
56
|
+
const nameTokens = resourceName.split("/");
|
|
57
|
+
return {
|
|
58
|
+
secret: {
|
|
59
|
+
projectId: nameTokens[1],
|
|
60
|
+
name: nameTokens[3],
|
|
61
|
+
},
|
|
62
|
+
versionId: nameTokens[5],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
exports.parseSecretVersionResourceName = parseSecretVersionResourceName;
|
|
66
|
+
function toSecretVersionResourceName(secretVersion) {
|
|
67
|
+
return `projects/${secretVersion.secret.projectId}/secrets/${secretVersion.secret.name}/versions/${secretVersion.versionId}`;
|
|
68
|
+
}
|
|
69
|
+
exports.toSecretVersionResourceName = toSecretVersionResourceName;
|
|
70
|
+
async function createSecret(projectId, name, labels) {
|
|
71
|
+
const createRes = await api.request("POST", `/v1beta1/projects/${projectId}/secrets?secretId=${name}`, {
|
|
72
|
+
auth: true,
|
|
73
|
+
origin: api.secretManagerOrigin,
|
|
74
|
+
data: {
|
|
75
|
+
replication: {
|
|
76
|
+
automatic: {},
|
|
77
|
+
},
|
|
78
|
+
labels,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
return parseSecretResourceName(createRes.body.name);
|
|
82
|
+
}
|
|
83
|
+
exports.createSecret = createSecret;
|
|
84
|
+
async function addVersion(secret, payloadData) {
|
|
85
|
+
const res = await api.request("POST", `/v1beta1/projects/${secret.projectId}/secrets/${secret.name}:addVersion`, {
|
|
86
|
+
auth: true,
|
|
87
|
+
origin: api.secretManagerOrigin,
|
|
88
|
+
data: {
|
|
89
|
+
payload: {
|
|
90
|
+
data: Buffer.from(payloadData).toString("base64"),
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
const nameTokens = res.body.name.split("/");
|
|
95
|
+
return {
|
|
96
|
+
secret: {
|
|
97
|
+
projectId: nameTokens[1],
|
|
98
|
+
name: nameTokens[3],
|
|
99
|
+
},
|
|
100
|
+
versionId: nameTokens[5],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
exports.addVersion = addVersion;
|
|
104
|
+
async function grantServiceAgentRole(secret, serviceAccountEmail, role) {
|
|
105
|
+
const getPolicyRes = await api.request("GET", `/v1beta1/projects/${secret.projectId}/secrets/${secret.name}:getIamPolicy`, {
|
|
106
|
+
auth: true,
|
|
107
|
+
origin: api.secretManagerOrigin,
|
|
108
|
+
});
|
|
109
|
+
const bindings = getPolicyRes.body.bindings || [];
|
|
110
|
+
if (bindings.find((b) => b.role == role &&
|
|
111
|
+
b.members.find((m) => m == `serviceAccount:${serviceAccountEmail}`))) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
bindings.push({
|
|
115
|
+
role: role,
|
|
116
|
+
members: [`serviceAccount:${serviceAccountEmail}`],
|
|
117
|
+
});
|
|
118
|
+
await api.request("POST", `/v1beta1/projects/${secret.projectId}/secrets/${secret.name}:setIamPolicy`, {
|
|
119
|
+
auth: true,
|
|
120
|
+
origin: api.secretManagerOrigin,
|
|
121
|
+
data: {
|
|
122
|
+
policy: {
|
|
123
|
+
bindings,
|
|
124
|
+
},
|
|
125
|
+
updateMask: {
|
|
126
|
+
paths: "bindings",
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
utils_1.logLabeledSuccess("SecretManager", `Granted ${role} on projects/${secret.projectId}/secrets/${secret.name} to ${serviceAccountEmail}`);
|
|
131
|
+
}
|
|
132
|
+
exports.grantServiceAgentRole = grantServiceAgentRole;
|
package/lib/gcp/storage.js
CHANGED
|
@@ -61,9 +61,25 @@ function _deleteObject(location) {
|
|
|
61
61
|
origin: api.storageOrigin,
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
|
+
async function _getBucket(bucketName) {
|
|
65
|
+
try {
|
|
66
|
+
const result = await api.request("GET", `/storage/v1/b/${bucketName}`, {
|
|
67
|
+
auth: true,
|
|
68
|
+
origin: api.storageOrigin,
|
|
69
|
+
});
|
|
70
|
+
return result.body;
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
logger.debug(err);
|
|
74
|
+
throw new FirebaseError("Failed to obtain the storage bucket", {
|
|
75
|
+
original: err,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
64
79
|
module.exports = {
|
|
65
80
|
getDefaultBucket: _getDefaultBucket,
|
|
66
81
|
deleteObject: _deleteObject,
|
|
67
82
|
upload: _uploadSource,
|
|
68
83
|
uploadObject: _uploadObject,
|
|
84
|
+
getBucket: _getBucket,
|
|
69
85
|
};
|
package/lib/projectUtils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.needProjectNumber = exports.needProjectId = exports.getProjectId = void 0;
|
|
3
|
+
exports.getAliases = exports.needProjectNumber = exports.needProjectId = exports.getProjectId = void 0;
|
|
4
4
|
const projects_1 = require("./management/projects");
|
|
5
5
|
const clc = require("cli-color");
|
|
6
6
|
const marked = require("marked");
|
|
@@ -48,3 +48,12 @@ async function needProjectNumber(options) {
|
|
|
48
48
|
return options.projectNumber;
|
|
49
49
|
}
|
|
50
50
|
exports.needProjectNumber = needProjectNumber;
|
|
51
|
+
function getAliases(options, projectId) {
|
|
52
|
+
if (options.rc.hasProjects) {
|
|
53
|
+
return Object.entries(options.rc.projects)
|
|
54
|
+
.filter((entry) => entry[1] === projectId)
|
|
55
|
+
.map((entry) => entry[0]);
|
|
56
|
+
}
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
exports.getAliases = getAliases;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const error_1 = require("./error");
|
|
4
|
+
function requireInteractive(options) {
|
|
5
|
+
if (options.nonInteractive) {
|
|
6
|
+
return Promise.reject(new error_1.FirebaseError("This command cannot run in non-interactive mode", {
|
|
7
|
+
exit: 1,
|
|
8
|
+
}));
|
|
9
|
+
}
|
|
10
|
+
return Promise.resolve();
|
|
11
|
+
}
|
|
12
|
+
exports.default = requireInteractive;
|
package/lib/utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.assertIsStringOrUndefined = exports.assertIsNumber = exports.assertIsString = exports.assertDefined = exports.thirtyDaysFromNow = exports.isRunningInWSL = exports.isCloudEnvironment = exports.datetimeString = exports.createDestroyer = exports.promiseWithSpinner = exports.setupLoggers = exports.tryParse = exports.tryStringify = exports.promiseProps = exports.promiseWhile = exports.promiseAllSettled = exports.getFunctionsEventProvider = exports.endpoint = exports.makeActiveProject = exports.streamToString = exports.stringToStream = exports.explainStdin = exports.reject = exports.logLabeledWarning = exports.logWarning = exports.logLabeledBullet = exports.logBullet = exports.logLabeledSuccess = exports.logSuccess = exports.addSubdomain = exports.addDatabaseNamespace = exports.getDatabaseViewDataUrl = exports.getDatabaseUrl = exports.envOverride = exports.getInheritedOption = exports.consoleUrl = exports.envOverrides = void 0;
|
|
3
|
+
exports.assertIsStringOrUndefined = exports.assertIsNumber = exports.assertIsString = exports.assertDefined = exports.thirtyDaysFromNow = exports.isRunningInWSL = exports.isCloudEnvironment = exports.datetimeString = exports.createDestroyer = exports.promiseWithSpinner = exports.setupLoggers = exports.tryParse = exports.tryStringify = exports.promiseProps = exports.promiseWhile = exports.promiseAllSettled = exports.getFunctionsEventProvider = exports.endpoint = exports.makeActiveProject = exports.streamToString = exports.stringToStream = exports.explainStdin = exports.allSettled = exports.reject = exports.logLabeledWarning = exports.logWarning = exports.logLabeledBullet = exports.logBullet = exports.logLabeledSuccess = exports.logSuccess = exports.addSubdomain = exports.addDatabaseNamespace = exports.getDatabaseViewDataUrl = exports.getDatabaseUrl = exports.envOverride = exports.getInheritedOption = exports.consoleUrl = exports.envOverrides = void 0;
|
|
4
4
|
const _ = require("lodash");
|
|
5
5
|
const url = require("url");
|
|
6
6
|
const clc = require("cli-color");
|
|
@@ -108,6 +108,35 @@ function reject(message, options) {
|
|
|
108
108
|
return Promise.reject(new error_1.FirebaseError(message, options));
|
|
109
109
|
}
|
|
110
110
|
exports.reject = reject;
|
|
111
|
+
function allSettled(promises) {
|
|
112
|
+
if (!promises.length) {
|
|
113
|
+
return Promise.resolve([]);
|
|
114
|
+
}
|
|
115
|
+
return new Promise((resolve) => {
|
|
116
|
+
let remaining = promises.length;
|
|
117
|
+
const results = [];
|
|
118
|
+
for (let i = 0; i < promises.length; i++) {
|
|
119
|
+
void Promise.resolve(promises[i])
|
|
120
|
+
.then((result) => {
|
|
121
|
+
results[i] = {
|
|
122
|
+
status: "fulfilled",
|
|
123
|
+
value: result,
|
|
124
|
+
};
|
|
125
|
+
}, (err) => {
|
|
126
|
+
results[i] = {
|
|
127
|
+
status: "rejected",
|
|
128
|
+
reason: err,
|
|
129
|
+
};
|
|
130
|
+
})
|
|
131
|
+
.then(() => {
|
|
132
|
+
if (!--remaining) {
|
|
133
|
+
resolve(results);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
exports.allSettled = allSettled;
|
|
111
140
|
function explainStdin() {
|
|
112
141
|
if (IS_WINDOWS) {
|
|
113
142
|
throw new error_1.FirebaseError("STDIN input is not available on Windows.", {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firebase-tools",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.23.0",
|
|
4
4
|
"description": "Command-Line Interface for Firebase",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"test:client-integration": "./scripts/client-integration-tests/run.sh",
|
|
27
27
|
"test:compile": "tsc --project tsconfig.compile.json",
|
|
28
28
|
"test:emulator": "./scripts/emulator-tests/run.sh",
|
|
29
|
+
"test:extensions-deploy": "./scripts/extensions-deploy-tests/run.sh",
|
|
29
30
|
"test:extensions-emulator": "./scripts/extensions-emulator-tests/run.sh",
|
|
30
31
|
"test:hosting": "./scripts/hosting-tests/run.sh",
|
|
31
32
|
"test:triggers-end-to-end": "./scripts/triggers-end-to-end-tests/run.sh",
|
|
@@ -93,7 +94,7 @@
|
|
|
93
94
|
"chokidar": "^3.0.2",
|
|
94
95
|
"cjson": "^0.3.1",
|
|
95
96
|
"cli-color": "^1.2.0",
|
|
96
|
-
"cli-table": "^0.3.
|
|
97
|
+
"cli-table": "^0.3.8",
|
|
97
98
|
"commander": "^4.0.1",
|
|
98
99
|
"configstore": "^5.0.1",
|
|
99
100
|
"cors": "^2.8.5",
|
|
@@ -137,7 +138,7 @@
|
|
|
137
138
|
"universal-analytics": "^0.4.16",
|
|
138
139
|
"unzipper": "^0.10.10",
|
|
139
140
|
"update-notifier": "^5.1.0",
|
|
140
|
-
"uuid": "^3.
|
|
141
|
+
"uuid": "^8.3.2",
|
|
141
142
|
"winston": "^3.0.0",
|
|
142
143
|
"winston-transport": "^4.4.0",
|
|
143
144
|
"ws": "^7.2.3"
|
|
@@ -181,7 +182,7 @@
|
|
|
181
182
|
"@types/tmp": "^0.1.0",
|
|
182
183
|
"@types/triple-beam": "^1.3.0",
|
|
183
184
|
"@types/unzipper": "^0.10.0",
|
|
184
|
-
"@types/uuid": "^3.
|
|
185
|
+
"@types/uuid": "^8.3.1",
|
|
185
186
|
"@types/winston": "^2.4.4",
|
|
186
187
|
"@types/ws": "^7.2.3",
|
|
187
188
|
"@typescript-eslint/eslint-plugin": "^4.12.0",
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"additionalProperties": false,
|
|
4
|
+
"definitions": {
|
|
5
|
+
"ExtensionsConfig": {
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"type": "object"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
4
10
|
"properties": {
|
|
5
11
|
"database": {
|
|
6
12
|
"anyOf": [
|
|
@@ -273,6 +279,9 @@
|
|
|
273
279
|
},
|
|
274
280
|
"type": "object"
|
|
275
281
|
},
|
|
282
|
+
"extensions": {
|
|
283
|
+
"$ref": "#/definitions/ExtensionsConfig"
|
|
284
|
+
},
|
|
276
285
|
"firestore": {
|
|
277
286
|
"additionalProperties": false,
|
|
278
287
|
"properties": {
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createDeploymentPlan = exports.calculateRegionalFunctionChanges = exports.allRegions = exports.functionsByRegion = void 0;
|
|
4
|
-
const functionsDeployHelper_1 = require("./functionsDeployHelper");
|
|
5
|
-
const validate_1 = require("./validate");
|
|
6
|
-
const deploymentTool_1 = require("../../deploymentTool");
|
|
7
|
-
const utils_1 = require("../../utils");
|
|
8
|
-
const backend = require("./backend");
|
|
9
|
-
const gcfv2 = require("../../gcp/cloudfunctionsv2");
|
|
10
|
-
function functionsByRegion(allFunctions) {
|
|
11
|
-
const partitioned = {};
|
|
12
|
-
for (const fn of allFunctions) {
|
|
13
|
-
partitioned[fn.region] = partitioned[fn.region] || [];
|
|
14
|
-
partitioned[fn.region].push(fn);
|
|
15
|
-
}
|
|
16
|
-
return partitioned;
|
|
17
|
-
}
|
|
18
|
-
exports.functionsByRegion = functionsByRegion;
|
|
19
|
-
function allRegions(spec, existing) {
|
|
20
|
-
return Object.keys(Object.assign(Object.assign({}, spec), existing));
|
|
21
|
-
}
|
|
22
|
-
exports.allRegions = allRegions;
|
|
23
|
-
const matchesId = (hasId) => (test) => {
|
|
24
|
-
return hasId.id === test.id;
|
|
25
|
-
};
|
|
26
|
-
function calculateRegionalFunctionChanges(want, have, options) {
|
|
27
|
-
want = want.filter((fn) => functionsDeployHelper_1.functionMatchesAnyGroup(fn, options.filters));
|
|
28
|
-
have = have.filter((fn) => functionsDeployHelper_1.functionMatchesAnyGroup(fn, options.filters));
|
|
29
|
-
let upgradedToGCFv2WithoutSettingConcurrency = false;
|
|
30
|
-
const functionsToCreate = want.filter((fn) => !have.some(matchesId(fn)));
|
|
31
|
-
const functionsToUpdate = want
|
|
32
|
-
.filter((fn) => {
|
|
33
|
-
const haveFn = have.find(matchesId(fn));
|
|
34
|
-
if (!haveFn) {
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
validate_1.checkForInvalidChangeOfTrigger(fn, haveFn);
|
|
38
|
-
if (!options.overwriteEnvs) {
|
|
39
|
-
fn.environmentVariables = Object.assign(Object.assign({}, haveFn.environmentVariables), fn.environmentVariables);
|
|
40
|
-
}
|
|
41
|
-
if (haveFn.platform === "gcfv1" && fn.platform === "gcfv2" && !fn.concurrency) {
|
|
42
|
-
upgradedToGCFv2WithoutSettingConcurrency = true;
|
|
43
|
-
}
|
|
44
|
-
return true;
|
|
45
|
-
})
|
|
46
|
-
.map((fn) => {
|
|
47
|
-
const haveFn = have.find(matchesId(fn));
|
|
48
|
-
const deleteAndRecreate = needsDeleteAndRecreate(haveFn, fn);
|
|
49
|
-
return {
|
|
50
|
-
func: fn,
|
|
51
|
-
deleteAndRecreate,
|
|
52
|
-
};
|
|
53
|
-
});
|
|
54
|
-
const functionsToDelete = have
|
|
55
|
-
.filter((fn) => !want.some(matchesId(fn)))
|
|
56
|
-
.filter((fn) => deploymentTool_1.isFirebaseManaged(fn.labels || {}));
|
|
57
|
-
if (upgradedToGCFv2WithoutSettingConcurrency) {
|
|
58
|
-
utils_1.logLabeledBullet("functions", "You are updating one or more functions to Google Cloud Functions v2, " +
|
|
59
|
-
"which introduces support for concurrent execution. New functions " +
|
|
60
|
-
"default to 80 concurrent executions, but existing functions keep the " +
|
|
61
|
-
"old default of 1. You can change this with the 'concurrency' option.");
|
|
62
|
-
}
|
|
63
|
-
return { functionsToCreate, functionsToUpdate, functionsToDelete };
|
|
64
|
-
}
|
|
65
|
-
exports.calculateRegionalFunctionChanges = calculateRegionalFunctionChanges;
|
|
66
|
-
function createDeploymentPlan(want, have, options) {
|
|
67
|
-
const deployment = {
|
|
68
|
-
regionalDeployments: {},
|
|
69
|
-
schedulesToUpsert: [],
|
|
70
|
-
schedulesToDelete: [],
|
|
71
|
-
topicsToDelete: [],
|
|
72
|
-
};
|
|
73
|
-
const wantRegionalFunctions = functionsByRegion(want.cloudFunctions);
|
|
74
|
-
const haveRegionalFunctions = functionsByRegion(have.cloudFunctions);
|
|
75
|
-
for (const region of allRegions(wantRegionalFunctions, haveRegionalFunctions)) {
|
|
76
|
-
const want = wantRegionalFunctions[region] || [];
|
|
77
|
-
const have = haveRegionalFunctions[region] || [];
|
|
78
|
-
deployment.regionalDeployments[region] = calculateRegionalFunctionChanges(want, have, options);
|
|
79
|
-
}
|
|
80
|
-
deployment.schedulesToUpsert = want.schedules.filter((schedule) => functionsDeployHelper_1.functionMatchesAnyGroup(schedule.targetService, options.filters));
|
|
81
|
-
deployment.schedulesToDelete = have.schedules
|
|
82
|
-
.filter((schedule) => !want.schedules.some(matchesId(schedule)))
|
|
83
|
-
.filter((schedule) => functionsDeployHelper_1.functionMatchesAnyGroup(schedule.targetService, options.filters));
|
|
84
|
-
deployment.topicsToDelete = have.topics
|
|
85
|
-
.filter((topic) => !want.topics.some(matchesId(topic)))
|
|
86
|
-
.filter((topic) => functionsDeployHelper_1.functionMatchesAnyGroup(topic.targetService, options.filters));
|
|
87
|
-
return deployment;
|
|
88
|
-
}
|
|
89
|
-
exports.createDeploymentPlan = createDeploymentPlan;
|
|
90
|
-
function needsDeleteAndRecreate(exFn, fn) {
|
|
91
|
-
return changedV2PubSubTopic(exFn, fn);
|
|
92
|
-
}
|
|
93
|
-
function changedV2PubSubTopic(exFn, fn) {
|
|
94
|
-
if (exFn.platform !== "gcfv2") {
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
if (fn.platform !== "gcfv2") {
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
if (!backend.isEventTrigger(exFn.trigger)) {
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
if (!backend.isEventTrigger(fn.trigger)) {
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
if (exFn.trigger.eventType !== gcfv2.PUBSUB_PUBLISH_EVENT) {
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
109
|
-
if (fn.trigger.eventType != gcfv2.PUBSUB_PUBLISH_EVENT) {
|
|
110
|
-
return false;
|
|
111
|
-
}
|
|
112
|
-
return exFn.trigger.eventFilters["resource"] != fn.trigger.eventFilters["resource"];
|
|
113
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DeploymentTimer = void 0;
|
|
4
|
-
const logger_1 = require("../../logger");
|
|
5
|
-
const track = require("../../track");
|
|
6
|
-
class DeploymentTimer {
|
|
7
|
-
constructor() {
|
|
8
|
-
this.timings = {};
|
|
9
|
-
}
|
|
10
|
-
startTimer(name, type) {
|
|
11
|
-
this.timings[name] = { type: type, t0: process.hrtime() };
|
|
12
|
-
}
|
|
13
|
-
endTimer(name) {
|
|
14
|
-
if (!this.timings[name]) {
|
|
15
|
-
logger_1.logger.debug("[functions] no timer initialized for", name);
|
|
16
|
-
return 0;
|
|
17
|
-
}
|
|
18
|
-
const duration = process.hrtime(this.timings[name].t0);
|
|
19
|
-
track("Functions Deploy (Duration)", this.timings[name].type, duration[0] * 1000 + Math.round(duration[1] * 1e-6));
|
|
20
|
-
return duration[0] * 1000 * Math.round(duration[1] * 1e-6);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
exports.DeploymentTimer = DeploymentTimer;
|