firebase-tools 9.17.0 → 9.21.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 +3 -7
- package/lib/api.js +1 -0
- package/lib/apiv2.js +5 -3
- package/lib/appdistribution/client.js +84 -72
- package/lib/appdistribution/distribution.js +8 -26
- package/lib/appdistribution/options-parser-util.js +51 -0
- package/lib/command.js +1 -1
- package/lib/commands/appdistribution-distribute.js +74 -91
- package/lib/commands/appdistribution-testers-add.js +18 -0
- package/lib/commands/appdistribution-testers-remove.js +32 -0
- package/lib/commands/crashlytics-symbols-upload.js +146 -0
- package/lib/commands/ext-configure.js +9 -1
- package/lib/commands/ext-dev-extension-delete.js +2 -1
- package/lib/commands/ext-dev-publish.js +11 -4
- package/lib/commands/ext-dev-unpublish.js +12 -4
- package/lib/commands/ext-install.js +115 -48
- package/lib/commands/ext-uninstall.js +6 -0
- package/lib/commands/ext-update.js +61 -18
- package/lib/commands/functions-config-export.js +115 -0
- package/lib/commands/functions-delete.js +45 -25
- package/lib/commands/functions-list.js +54 -0
- package/lib/commands/functions-log.js +5 -22
- package/lib/commands/hosting-channel-deploy.js +6 -4
- package/lib/commands/index.js +12 -0
- package/lib/deploy/functions/backend.js +118 -121
- package/lib/deploy/functions/checkIam.js +8 -8
- package/lib/deploy/functions/containerCleaner.js +5 -1
- package/lib/deploy/functions/deploy.js +11 -15
- package/lib/deploy/functions/functionsDeployHelper.js +3 -68
- package/lib/deploy/functions/prepare.js +67 -33
- package/lib/deploy/functions/pricing.js +17 -17
- package/lib/deploy/functions/prompts.js +24 -41
- package/lib/deploy/functions/release/executor.js +39 -0
- package/lib/deploy/functions/release/fabricator.js +362 -0
- package/lib/deploy/functions/release/index.js +69 -0
- package/lib/deploy/functions/release/planner.js +159 -0
- package/lib/deploy/functions/release/reporter.js +162 -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 +102 -126
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +34 -50
- package/lib/deploy/functions/triggerRegionHelper.js +40 -0
- package/lib/deploy/functions/validate.js +1 -24
- package/lib/downloadUtils.js +37 -0
- package/lib/emulator/auth/apiSpec.js +1788 -403
- package/lib/emulator/auth/handlers.js +6 -5
- package/lib/emulator/auth/operations.js +439 -40
- package/lib/emulator/auth/server.js +32 -11
- package/lib/emulator/auth/state.js +205 -5
- package/lib/emulator/auth/widget_ui.js +2 -2
- package/lib/emulator/download.js +2 -31
- package/lib/emulator/downloadableEmulators.js +7 -7
- package/lib/emulator/emulatorLogger.js +0 -3
- package/lib/emulator/events/types.js +16 -0
- package/lib/emulator/functionsEmulator.js +117 -20
- package/lib/emulator/functionsEmulatorRuntime.js +46 -121
- package/lib/emulator/functionsEmulatorShared.js +51 -7
- package/lib/emulator/functionsEmulatorShell.js +1 -1
- package/lib/emulator/pubsubEmulator.js +61 -40
- package/lib/emulator/storage/cloudFunctions.js +37 -7
- package/lib/extensions/askUserForConsent.js +16 -13
- package/lib/extensions/askUserForParam.js +72 -3
- package/lib/extensions/billingMigrationHelper.js +1 -11
- package/lib/extensions/changelog.js +2 -1
- package/lib/extensions/displayExtensionInfo.js +35 -33
- package/lib/extensions/emulator/optionsHelper.js +3 -3
- package/lib/extensions/emulator/triggerHelper.js +2 -32
- package/lib/extensions/extensionsApi.js +67 -94
- package/lib/extensions/extensionsHelper.js +49 -35
- package/lib/extensions/paramHelper.js +79 -36
- package/lib/extensions/refs.js +59 -0
- package/lib/extensions/secretsUtils.js +58 -0
- package/lib/extensions/updateHelper.js +21 -45
- package/lib/extensions/warnings.js +1 -7
- package/lib/functional.js +64 -0
- package/lib/functions/env.js +26 -13
- package/lib/functions/functionslog.js +40 -0
- package/lib/functions/runtimeConfigExport.js +137 -0
- package/lib/gcp/cloudfunctions.js +46 -38
- package/lib/gcp/cloudfunctionsv2.js +47 -47
- package/lib/gcp/cloudlogging.js +27 -21
- package/lib/gcp/cloudscheduler.js +22 -16
- package/lib/gcp/pubsub.js +1 -9
- package/lib/gcp/secretManager.js +111 -0
- package/lib/gcp/storage.js +16 -0
- package/lib/previews.js +1 -1
- package/lib/requireInteractive.js +12 -0
- package/lib/utils.js +30 -1
- package/package.json +5 -4
- 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/functionsDelete.js +0 -60
|
@@ -1,38 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.compareFunctions = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = exports.someEndpoint = exports.allEndpoints = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.functionName = exports.isEmptyBackend = exports.of = exports.empty = exports.isScheduleTriggered = exports.isEventTriggered = exports.isHttpsTriggered = exports.SCHEDULED_FUNCTION_LABEL = exports.memoryOptionDisplayName = exports.endpointTriggerType = void 0;
|
|
4
4
|
const gcf = require("../../gcp/cloudfunctions");
|
|
5
5
|
const gcfV2 = require("../../gcp/cloudfunctionsv2");
|
|
6
6
|
const utils = require("../../utils");
|
|
7
7
|
const error_1 = require("../../error");
|
|
8
8
|
const previews_1 = require("../../previews");
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
exports.isEventTrigger = isEventTrigger;
|
|
13
|
-
function triggerTag(fn) {
|
|
14
|
-
var _a, _b;
|
|
15
|
-
if ((_a = fn.labels) === null || _a === void 0 ? void 0 : _a["deployment-scheduled"]) {
|
|
16
|
-
if (fn.platform === "gcfv1") {
|
|
17
|
-
return "v1.scheduled";
|
|
18
|
-
}
|
|
19
|
-
return "v2.scheduled";
|
|
9
|
+
function endpointTriggerType(endpoint) {
|
|
10
|
+
if (isScheduleTriggered(endpoint)) {
|
|
11
|
+
return "scheduled";
|
|
20
12
|
}
|
|
21
|
-
if ((
|
|
22
|
-
|
|
23
|
-
return "v1.callable";
|
|
24
|
-
}
|
|
25
|
-
return "v2.callable";
|
|
13
|
+
else if (isHttpsTriggered(endpoint)) {
|
|
14
|
+
return "https";
|
|
26
15
|
}
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
16
|
+
else if (isEventTriggered(endpoint)) {
|
|
17
|
+
return endpoint.eventTrigger.eventType;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
throw new Error("Unexpected trigger type for endpoint " + JSON.stringify(endpoint));
|
|
32
21
|
}
|
|
33
|
-
return fn.trigger.eventType;
|
|
34
22
|
}
|
|
35
|
-
exports.
|
|
23
|
+
exports.endpointTriggerType = endpointTriggerType;
|
|
36
24
|
function memoryOptionDisplayName(option) {
|
|
37
25
|
return {
|
|
38
26
|
128: "128MB",
|
|
@@ -46,38 +34,46 @@ function memoryOptionDisplayName(option) {
|
|
|
46
34
|
}
|
|
47
35
|
exports.memoryOptionDisplayName = memoryOptionDisplayName;
|
|
48
36
|
exports.SCHEDULED_FUNCTION_LABEL = Object.freeze({ deployment: "firebase-schedule" });
|
|
37
|
+
function isHttpsTriggered(triggered) {
|
|
38
|
+
return {}.hasOwnProperty.call(triggered, "httpsTrigger");
|
|
39
|
+
}
|
|
40
|
+
exports.isHttpsTriggered = isHttpsTriggered;
|
|
41
|
+
function isEventTriggered(triggered) {
|
|
42
|
+
return {}.hasOwnProperty.call(triggered, "eventTrigger");
|
|
43
|
+
}
|
|
44
|
+
exports.isEventTriggered = isEventTriggered;
|
|
45
|
+
function isScheduleTriggered(triggered) {
|
|
46
|
+
return {}.hasOwnProperty.call(triggered, "scheduleTrigger");
|
|
47
|
+
}
|
|
48
|
+
exports.isScheduleTriggered = isScheduleTriggered;
|
|
49
49
|
function empty() {
|
|
50
50
|
return {
|
|
51
51
|
requiredAPIs: {},
|
|
52
|
-
|
|
53
|
-
schedules: [],
|
|
54
|
-
topics: [],
|
|
52
|
+
endpoints: {},
|
|
55
53
|
environmentVariables: {},
|
|
56
54
|
};
|
|
57
55
|
}
|
|
58
56
|
exports.empty = empty;
|
|
57
|
+
function of(...endpoints) {
|
|
58
|
+
const bkend = Object.assign({}, empty());
|
|
59
|
+
for (const endpoint of endpoints) {
|
|
60
|
+
bkend.endpoints[endpoint.region] = bkend.endpoints[endpoint.region] || {};
|
|
61
|
+
if (bkend.endpoints[endpoint.region][endpoint.id]) {
|
|
62
|
+
throw new Error("Trying to create a backend with the same endpiont twice");
|
|
63
|
+
}
|
|
64
|
+
bkend.endpoints[endpoint.region][endpoint.id] = endpoint;
|
|
65
|
+
}
|
|
66
|
+
return bkend;
|
|
67
|
+
}
|
|
68
|
+
exports.of = of;
|
|
59
69
|
function isEmptyBackend(backend) {
|
|
60
|
-
return (Object.keys(backend.requiredAPIs).length == 0 &&
|
|
61
|
-
backend.cloudFunctions.length === 0 &&
|
|
62
|
-
backend.schedules.length === 0 &&
|
|
63
|
-
backend.topics.length === 0);
|
|
70
|
+
return (Object.keys(backend.requiredAPIs).length == 0 && Object.keys(backend.endpoints).length === 0);
|
|
64
71
|
}
|
|
65
72
|
exports.isEmptyBackend = isEmptyBackend;
|
|
66
73
|
function functionName(cloudFunction) {
|
|
67
74
|
return `projects/${cloudFunction.project}/locations/${cloudFunction.region}/functions/${cloudFunction.id}`;
|
|
68
75
|
}
|
|
69
76
|
exports.functionName = functionName;
|
|
70
|
-
exports.sameFunctionName = (func) => (test) => {
|
|
71
|
-
return func.id === test.id && func.region === test.region && func.project == test.project;
|
|
72
|
-
};
|
|
73
|
-
function scheduleName(schedule, appEngineLocation) {
|
|
74
|
-
return `projects/${schedule.project}/locations/${appEngineLocation}/jobs/${schedule.id}`;
|
|
75
|
-
}
|
|
76
|
-
exports.scheduleName = scheduleName;
|
|
77
|
-
function topicName(topic) {
|
|
78
|
-
return `projects/${topic.project}/topics/${topic.id}`;
|
|
79
|
-
}
|
|
80
|
-
exports.topicName = topicName;
|
|
81
77
|
function scheduleIdForFunction(cloudFunction) {
|
|
82
78
|
return `firebase-schedule-${cloudFunction.id}-${cloudFunction.region}`;
|
|
83
79
|
}
|
|
@@ -91,94 +87,39 @@ async function existingBackend(context, forceRefresh) {
|
|
|
91
87
|
}
|
|
92
88
|
exports.existingBackend = existingBackend;
|
|
93
89
|
async function loadExistingBackend(ctx) {
|
|
94
|
-
var _a
|
|
90
|
+
var _a;
|
|
95
91
|
ctx.loadedExistingBackend = true;
|
|
96
|
-
ctx.existingBackend = {
|
|
97
|
-
requiredAPIs: {},
|
|
98
|
-
cloudFunctions: [],
|
|
99
|
-
schedules: [],
|
|
100
|
-
topics: [],
|
|
101
|
-
environmentVariables: {},
|
|
102
|
-
};
|
|
92
|
+
ctx.existingBackend = Object.assign({}, empty());
|
|
103
93
|
ctx.unreachableRegions = {
|
|
104
94
|
gcfV1: [],
|
|
105
95
|
gcfV2: [],
|
|
106
96
|
};
|
|
107
97
|
const gcfV1Results = await gcf.listAllFunctions(ctx.projectId);
|
|
108
98
|
for (const apiFunction of gcfV1Results.functions) {
|
|
109
|
-
const
|
|
110
|
-
ctx.existingBackend.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const id = scheduleIdForFunction(specFunction);
|
|
114
|
-
ctx.existingBackend.schedules.push({
|
|
115
|
-
id,
|
|
116
|
-
project: specFunction.project,
|
|
117
|
-
transport: "pubsub",
|
|
118
|
-
targetService: {
|
|
119
|
-
id: specFunction.id,
|
|
120
|
-
region: specFunction.region,
|
|
121
|
-
project: specFunction.project,
|
|
122
|
-
},
|
|
123
|
-
});
|
|
124
|
-
ctx.existingBackend.topics.push({
|
|
125
|
-
id,
|
|
126
|
-
project: specFunction.project,
|
|
127
|
-
labels: exports.SCHEDULED_FUNCTION_LABEL,
|
|
128
|
-
targetService: {
|
|
129
|
-
id: specFunction.id,
|
|
130
|
-
region: specFunction.region,
|
|
131
|
-
project: specFunction.project,
|
|
132
|
-
},
|
|
133
|
-
});
|
|
134
|
-
}
|
|
99
|
+
const endpoint = gcf.endpointFromFunction(apiFunction);
|
|
100
|
+
ctx.existingBackend.endpoints[endpoint.region] =
|
|
101
|
+
ctx.existingBackend.endpoints[endpoint.region] || {};
|
|
102
|
+
ctx.existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
|
|
135
103
|
}
|
|
136
104
|
ctx.unreachableRegions.gcfV1 = gcfV1Results.unreachable;
|
|
137
105
|
if (!previews_1.previews.functionsv2) {
|
|
138
106
|
return;
|
|
139
107
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const id = scheduleIdForFunction(specFunction);
|
|
148
|
-
ctx.existingBackend.schedules.push({
|
|
149
|
-
id,
|
|
150
|
-
project: specFunction.project,
|
|
151
|
-
transport: "pubsub",
|
|
152
|
-
targetService: {
|
|
153
|
-
id: specFunction.id,
|
|
154
|
-
region: specFunction.region,
|
|
155
|
-
project: specFunction.project,
|
|
156
|
-
},
|
|
157
|
-
});
|
|
158
|
-
ctx.existingBackend.topics.push({
|
|
159
|
-
id,
|
|
160
|
-
project: specFunction.project,
|
|
161
|
-
labels: exports.SCHEDULED_FUNCTION_LABEL,
|
|
162
|
-
targetService: {
|
|
163
|
-
id: specFunction.id,
|
|
164
|
-
region: specFunction.region,
|
|
165
|
-
project: specFunction.project,
|
|
166
|
-
},
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
if (httpsScheduled) {
|
|
170
|
-
const id = scheduleIdForFunction(specFunction);
|
|
171
|
-
ctx.existingBackend.schedules.push({
|
|
172
|
-
id,
|
|
173
|
-
project: specFunction.project,
|
|
174
|
-
transport: "https",
|
|
175
|
-
targetService: {
|
|
176
|
-
id: specFunction.id,
|
|
177
|
-
region: specFunction.region,
|
|
178
|
-
project: specFunction.project,
|
|
179
|
-
},
|
|
180
|
-
});
|
|
108
|
+
let gcfV2Results;
|
|
109
|
+
try {
|
|
110
|
+
gcfV2Results = await gcfV2.listAllFunctions(ctx.projectId);
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
if (err.status === 404 && ((_a = err.message) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes("method not found"))) {
|
|
114
|
+
return;
|
|
181
115
|
}
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
for (const apiFunction of gcfV2Results.functions) {
|
|
119
|
+
const endpoint = gcfV2.endpointFromFunction(apiFunction);
|
|
120
|
+
ctx.existingBackend.endpoints[endpoint.region] =
|
|
121
|
+
ctx.existingBackend.endpoints[endpoint.region] || {};
|
|
122
|
+
ctx.existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
|
|
182
123
|
}
|
|
183
124
|
ctx.unreachableRegions.gcfV2 = gcfV2Results.unreachable;
|
|
184
125
|
}
|
|
@@ -189,12 +130,12 @@ async function checkAvailability(context, want) {
|
|
|
189
130
|
}
|
|
190
131
|
const gcfV1Regions = new Set();
|
|
191
132
|
const gcfV2Regions = new Set();
|
|
192
|
-
for (const
|
|
193
|
-
if (
|
|
194
|
-
gcfV1Regions.add(
|
|
133
|
+
for (const ep of allEndpoints(want)) {
|
|
134
|
+
if (ep.platform == "gcfv1") {
|
|
135
|
+
gcfV1Regions.add(ep.region);
|
|
195
136
|
}
|
|
196
137
|
else {
|
|
197
|
-
gcfV2Regions.add(
|
|
138
|
+
gcfV2Regions.add(ep.region);
|
|
198
139
|
}
|
|
199
140
|
}
|
|
200
141
|
const neededUnreachableV1 = ctx.unreachableRegions.gcfV1.filter((region) => gcfV1Regions.has(region));
|
|
@@ -221,3 +162,59 @@ async function checkAvailability(context, want) {
|
|
|
221
162
|
}
|
|
222
163
|
}
|
|
223
164
|
exports.checkAvailability = checkAvailability;
|
|
165
|
+
function allEndpoints(backend) {
|
|
166
|
+
return Object.values(backend.endpoints).reduce((accum, perRegion) => {
|
|
167
|
+
return [...accum, ...Object.values(perRegion)];
|
|
168
|
+
}, []);
|
|
169
|
+
}
|
|
170
|
+
exports.allEndpoints = allEndpoints;
|
|
171
|
+
function someEndpoint(backend, predicate) {
|
|
172
|
+
for (const endpoints of Object.values(backend.endpoints)) {
|
|
173
|
+
if (Object.values(endpoints).some(predicate)) {
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
exports.someEndpoint = someEndpoint;
|
|
180
|
+
function matchingBackend(backend, predicate) {
|
|
181
|
+
const filtered = Object.assign({}, empty());
|
|
182
|
+
for (const endpoint of allEndpoints(backend)) {
|
|
183
|
+
if (!predicate(endpoint)) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
filtered.endpoints[endpoint.region] = filtered.endpoints[endpoint.region] || {};
|
|
187
|
+
filtered.endpoints[endpoint.region][endpoint.id] = endpoint;
|
|
188
|
+
}
|
|
189
|
+
return filtered;
|
|
190
|
+
}
|
|
191
|
+
exports.matchingBackend = matchingBackend;
|
|
192
|
+
function regionalEndpoints(backend, region) {
|
|
193
|
+
return backend.endpoints[region] ? Object.values(backend.endpoints[region]) : [];
|
|
194
|
+
}
|
|
195
|
+
exports.regionalEndpoints = regionalEndpoints;
|
|
196
|
+
exports.hasEndpoint = (backend) => (endpoint) => {
|
|
197
|
+
return !!backend.endpoints[endpoint.region] && !!backend.endpoints[endpoint.region][endpoint.id];
|
|
198
|
+
};
|
|
199
|
+
exports.missingEndpoint = (backend) => (endpoint) => {
|
|
200
|
+
return !exports.hasEndpoint(backend)(endpoint);
|
|
201
|
+
};
|
|
202
|
+
function compareFunctions(left, right) {
|
|
203
|
+
if (left.platform != right.platform) {
|
|
204
|
+
return right.platform < left.platform ? -1 : 1;
|
|
205
|
+
}
|
|
206
|
+
if (left.region < right.region) {
|
|
207
|
+
return -1;
|
|
208
|
+
}
|
|
209
|
+
if (left.region > right.region) {
|
|
210
|
+
return 1;
|
|
211
|
+
}
|
|
212
|
+
if (left.id < right.id) {
|
|
213
|
+
return -1;
|
|
214
|
+
}
|
|
215
|
+
if (left.id > right.id) {
|
|
216
|
+
return 1;
|
|
217
|
+
}
|
|
218
|
+
return 0;
|
|
219
|
+
}
|
|
220
|
+
exports.compareFunctions = compareFunctions;
|
|
@@ -28,17 +28,17 @@ async function checkServiceAccountIam(projectId) {
|
|
|
28
28
|
}
|
|
29
29
|
exports.checkServiceAccountIam = checkServiceAccountIam;
|
|
30
30
|
async function checkHttpIam(context, options, payload) {
|
|
31
|
-
const functions = payload.functions.backend.cloudFunctions;
|
|
32
31
|
const filterGroups = context.filters || functionsDeployHelper_1.getFilterGroups(options);
|
|
33
|
-
const
|
|
34
|
-
.
|
|
32
|
+
const httpEndpoints = backend
|
|
33
|
+
.allEndpoints(payload.functions.backend)
|
|
34
|
+
.filter(backend.isHttpsTriggered)
|
|
35
35
|
.filter((f) => functionsDeployHelper_1.functionMatchesAnyGroup(f, filterGroups));
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
36
|
+
const existing = await backend.existingBackend(context);
|
|
37
|
+
const newHttpsEndpoints = httpEndpoints.filter(backend.missingEndpoint(existing));
|
|
38
|
+
if (newHttpsEndpoints.length === 0) {
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
|
-
logger_1.logger.debug("[functions] found",
|
|
41
|
+
logger_1.logger.debug("[functions] found", newHttpsEndpoints.length, "new HTTP functions, testing setIamPolicy permission...");
|
|
42
42
|
let passed = true;
|
|
43
43
|
try {
|
|
44
44
|
const iamResult = await iam_1.testIamPermissions(context.projectId, [PERMISSION]);
|
|
@@ -51,7 +51,7 @@ async function checkHttpIam(context, options, payload) {
|
|
|
51
51
|
if (!passed) {
|
|
52
52
|
track("Error (User)", "deploy:functions:http_create_missing_iam");
|
|
53
53
|
throw new error_1.FirebaseError(`Missing required permission on project ${cli_color_1.bold(context.projectId)} to deploy new HTTPS functions. The permission ${cli_color_1.bold(PERMISSION)} is required to deploy the following functions:\n\n- ` +
|
|
54
|
-
|
|
54
|
+
newHttpsEndpoints.map((func) => func.id).join("\n- ") +
|
|
55
55
|
`\n\nTo address this error, please ask a project Owner to assign your account the "Cloud Functions Admin" role at the following URL:\n\nhttps://console.cloud.google.com/iam-admin/iam?project=${context.projectId}`);
|
|
56
56
|
}
|
|
57
57
|
logger_1.logger.debug("[functions] found setIamPolicy permission, proceeding with deploy");
|
|
@@ -37,10 +37,14 @@ async function retry(func) {
|
|
|
37
37
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
38
38
|
const MAX_RETRIES = 3;
|
|
39
39
|
const INITIAL_BACKOFF = 100;
|
|
40
|
+
const TIMEOUT_MS = 10000;
|
|
40
41
|
let retry = 0;
|
|
41
42
|
while (true) {
|
|
42
43
|
try {
|
|
43
|
-
|
|
44
|
+
const timeout = new Promise((resolve, reject) => {
|
|
45
|
+
setTimeout(() => reject(new Error("Timeout")), TIMEOUT_MS);
|
|
46
|
+
});
|
|
47
|
+
return await Promise.race([func(), timeout]);
|
|
44
48
|
}
|
|
45
49
|
catch (error) {
|
|
46
50
|
logger_1.logger.debug("Failed docker command with error", error);
|
|
@@ -11,6 +11,7 @@ const gcs = require("../../gcp/storage");
|
|
|
11
11
|
const gcf = require("../../gcp/cloudfunctions");
|
|
12
12
|
const gcfv2 = require("../../gcp/cloudfunctionsv2");
|
|
13
13
|
const utils = require("../../utils");
|
|
14
|
+
const backend = require("./backend");
|
|
14
15
|
const GCP_REGION = api_1.functionsUploadRegion;
|
|
15
16
|
tmp_1.setGracefulCleanup();
|
|
16
17
|
async function uploadSourceV1(context) {
|
|
@@ -37,34 +38,29 @@ async function deploy(context, options, payload) {
|
|
|
37
38
|
if (!options.config.src.functions) {
|
|
38
39
|
return;
|
|
39
40
|
}
|
|
40
|
-
await checkIam_1.checkHttpIam(context, options, payload);
|
|
41
41
|
if (!context.functionsSourceV1 && !context.functionsSourceV2) {
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
|
+
await checkIam_1.checkHttpIam(context, options, payload);
|
|
44
45
|
try {
|
|
45
46
|
const want = payload.functions.backend;
|
|
46
47
|
const uploads = [];
|
|
47
|
-
if (want.
|
|
48
|
+
if (backend.allEndpoints(want).some((endpoint) => endpoint.platform === "gcfv1")) {
|
|
48
49
|
uploads.push(uploadSourceV1(context));
|
|
49
50
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const regions = [];
|
|
53
|
-
for (const func of functions) {
|
|
54
|
-
if (func.platform === "gcfv2" && -1 === regions.indexOf(func.region)) {
|
|
55
|
-
regions.push(func.region);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
for (const region of regions) {
|
|
51
|
+
for (const region of Object.keys(want.endpoints)) {
|
|
52
|
+
if (backend.regionalEndpoints(want, region).some((e) => e.platform === "gcfv2")) {
|
|
59
53
|
uploads.push(uploadSourceV2(context, region));
|
|
60
54
|
}
|
|
61
55
|
}
|
|
62
56
|
await Promise.all(uploads);
|
|
63
57
|
utils.assertDefined(options.config.src.functions.source, "Error: 'functions.source' is not defined");
|
|
64
|
-
|
|
65
|
-
"
|
|
66
|
-
|
|
67
|
-
|
|
58
|
+
if (uploads.length) {
|
|
59
|
+
utils_1.logSuccess(clc.green.bold("functions:") +
|
|
60
|
+
" " +
|
|
61
|
+
clc.bold(options.config.src.functions.source) +
|
|
62
|
+
" folder uploaded successfully");
|
|
63
|
+
}
|
|
68
64
|
}
|
|
69
65
|
catch (err) {
|
|
70
66
|
utils_1.logWarning(clc.yellow("functions:") + " Upload Error: " + err.message);
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
const clc = require("cli-color");
|
|
5
|
-
const logger_1 = require("../../logger");
|
|
6
|
-
const backend = require("./backend");
|
|
7
|
-
const track = require("../../track");
|
|
8
|
-
const utils = require("../../utils");
|
|
3
|
+
exports.getFunctionLabel = exports.getFilterGroups = exports.functionMatchesGroup = exports.functionMatchesAnyGroup = void 0;
|
|
9
4
|
function functionMatchesAnyGroup(func, filterGroups) {
|
|
10
5
|
if (!filterGroups.length) {
|
|
11
6
|
return true;
|
|
@@ -40,67 +35,7 @@ function getFilterGroups(options) {
|
|
|
40
35
|
});
|
|
41
36
|
}
|
|
42
37
|
exports.getFilterGroups = getFilterGroups;
|
|
43
|
-
function
|
|
44
|
-
return
|
|
45
|
-
}
|
|
46
|
-
exports.getFunctionId = getFunctionId;
|
|
47
|
-
function getRegion(fullName) {
|
|
48
|
-
return fullName.split("/")[3];
|
|
49
|
-
}
|
|
50
|
-
function getFunctionLabel(fnOrName) {
|
|
51
|
-
if (typeof fnOrName === "string") {
|
|
52
|
-
return getFunctionId(fnOrName) + "(" + getRegion(fnOrName) + ")";
|
|
53
|
-
}
|
|
54
|
-
else {
|
|
55
|
-
return `${fnOrName.id}(${fnOrName.region})`;
|
|
56
|
-
}
|
|
38
|
+
function getFunctionLabel(fn) {
|
|
39
|
+
return `${fn.id}(${fn.region})`;
|
|
57
40
|
}
|
|
58
41
|
exports.getFunctionLabel = getFunctionLabel;
|
|
59
|
-
function logAndTrackDeployStats(queue, errorHandler) {
|
|
60
|
-
const stats = queue.stats();
|
|
61
|
-
logger_1.logger.debug(`Total Function Deployment time: ${stats.elapsed}`);
|
|
62
|
-
logger_1.logger.debug(`${stats.total} Functions Deployed`);
|
|
63
|
-
logger_1.logger.debug(`${errorHandler.errors.length} Functions Errored`);
|
|
64
|
-
logger_1.logger.debug(`Average Function Deployment time: ${stats.avg}`);
|
|
65
|
-
if (stats.total > 0) {
|
|
66
|
-
if (errorHandler.errors.length === 0) {
|
|
67
|
-
track("functions_deploy_result", "success", stats.total);
|
|
68
|
-
}
|
|
69
|
-
else if (errorHandler.errors.length < stats.total) {
|
|
70
|
-
track("functions_deploy_result", "partial_success", stats.total - errorHandler.errors.length);
|
|
71
|
-
track("functions_deploy_result", "partial_failure", errorHandler.errors.length);
|
|
72
|
-
track("functions_deploy_result", "partial_error_ratio", errorHandler.errors.length / stats.total);
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
track("functions_deploy_result", "failure", stats.total);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
exports.logAndTrackDeployStats = logAndTrackDeployStats;
|
|
80
|
-
function printSuccess(func, type) {
|
|
81
|
-
utils.logSuccess(clc.bold.green("functions[" + getFunctionLabel(func) + "]: ") +
|
|
82
|
-
"Successful " +
|
|
83
|
-
type +
|
|
84
|
-
" operation. ");
|
|
85
|
-
}
|
|
86
|
-
exports.printSuccess = printSuccess;
|
|
87
|
-
async function printTriggerUrls(context, want) {
|
|
88
|
-
const have = await backend.existingBackend(context, true);
|
|
89
|
-
const httpsFunctions = have.cloudFunctions.filter((fn) => {
|
|
90
|
-
if (backend.isEventTrigger(fn.trigger)) {
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
return want.cloudFunctions.some(backend.sameFunctionName(fn));
|
|
94
|
-
});
|
|
95
|
-
if (httpsFunctions.length === 0) {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
for (const httpsFunc of httpsFunctions) {
|
|
99
|
-
if (!httpsFunc.uri) {
|
|
100
|
-
logger_1.logger.debug("Missing URI for HTTPS function in printTriggerUrls. This shouldn't happen");
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
logger_1.logger.info(clc.bold("Function URL"), `(${getFunctionLabel(httpsFunc)}):`, httpsFunc.uri);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
exports.printTriggerUrls = printTriggerUrls;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.prepare = void 0;
|
|
3
|
+
exports.inferDetailsFromExisting = exports.prepare = void 0;
|
|
4
4
|
const clc = require("cli-color");
|
|
5
5
|
const ensureCloudBuildEnabled_1 = require("./ensureCloudBuildEnabled");
|
|
6
6
|
const functionsDeployHelper_1 = require("./functionsDeployHelper");
|
|
@@ -18,6 +18,7 @@ const runtimes = require("./runtimes");
|
|
|
18
18
|
const validate = require("./validate");
|
|
19
19
|
const utils = require("../../utils");
|
|
20
20
|
const logger_1 = require("../../logger");
|
|
21
|
+
const triggerRegionHelper_1 = require("./triggerRegionHelper");
|
|
21
22
|
function hasUserConfig(config) {
|
|
22
23
|
return Object.keys(config).length > 1;
|
|
23
24
|
}
|
|
@@ -52,57 +53,90 @@ async function prepare(context, options, payload) {
|
|
|
52
53
|
projectAlias: options.projectAlias,
|
|
53
54
|
};
|
|
54
55
|
const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt);
|
|
56
|
+
const usedDotenv = hasDotenv(userEnvOpt);
|
|
55
57
|
const tag = hasUserConfig(runtimeConfig)
|
|
56
|
-
?
|
|
58
|
+
? usedDotenv
|
|
57
59
|
? "mixed"
|
|
58
60
|
: "runtime_config"
|
|
59
|
-
:
|
|
61
|
+
: usedDotenv
|
|
60
62
|
? "dotenv"
|
|
61
63
|
: "none";
|
|
62
|
-
track_1.track("functions_codebase_deploy_env_method", tag);
|
|
64
|
+
await track_1.track("functions_codebase_deploy_env_method", tag);
|
|
63
65
|
logger_1.logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`);
|
|
64
66
|
const wantBackend = await runtimeDelegate.discoverSpec(runtimeConfig, firebaseEnvs);
|
|
65
67
|
wantBackend.environmentVariables = Object.assign(Object.assign({}, userEnvs), firebaseEnvs);
|
|
66
68
|
payload.functions = { backend: wantBackend };
|
|
67
|
-
if (backend.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const enablements = Object.entries(V2_APIS).map(([tag, api]) => {
|
|
78
|
-
return ensureApiEnabled.ensure(context.projectId, api, tag);
|
|
69
|
+
if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) {
|
|
70
|
+
const V2_APIS = [
|
|
71
|
+
"artifactregistry.googleapis.com",
|
|
72
|
+
"run.googleapis.com",
|
|
73
|
+
"eventarc.googleapis.com",
|
|
74
|
+
"pubsub.googleapis.com",
|
|
75
|
+
"storage.googleapis.com",
|
|
76
|
+
];
|
|
77
|
+
const enablements = V2_APIS.map((api) => {
|
|
78
|
+
return ensureApiEnabled.ensure(context.projectId, api, "functions");
|
|
79
79
|
});
|
|
80
80
|
await Promise.all(enablements);
|
|
81
81
|
}
|
|
82
|
-
|
|
83
|
-
"
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
if (backend.someEndpoint(wantBackend, () => true)) {
|
|
83
|
+
utils_1.logBullet(clc.cyan.bold("functions:") +
|
|
84
|
+
" preparing " +
|
|
85
|
+
clc.bold(options.config.src.functions.source) +
|
|
86
|
+
" directory for uploading...");
|
|
87
|
+
}
|
|
88
|
+
if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv1")) {
|
|
87
89
|
context.functionsSourceV1 = await prepareFunctionsUpload_1.prepareFunctionsUpload(runtimeConfig, options);
|
|
88
90
|
}
|
|
89
|
-
if (
|
|
91
|
+
if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) {
|
|
90
92
|
context.functionsSourceV2 = await prepareFunctionsUpload_1.prepareFunctionsUpload(undefined, options);
|
|
91
93
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
await Promise.all(Object.
|
|
96
|
-
ensureApiEnabled.ensure(projectId,
|
|
94
|
+
for (const endpoint of backend.allEndpoints(wantBackend)) {
|
|
95
|
+
endpoint.environmentVariables = wantBackend.environmentVariables;
|
|
96
|
+
}
|
|
97
|
+
await Promise.all(Object.values(wantBackend.requiredAPIs).map((api) => {
|
|
98
|
+
return ensureApiEnabled.ensure(projectId, api, "functions", false);
|
|
97
99
|
}));
|
|
98
|
-
validate.functionIdsAreValid(wantBackend
|
|
100
|
+
validate.functionIdsAreValid(backend.allEndpoints(wantBackend));
|
|
99
101
|
context.filters = functionsDeployHelper_1.getFilterGroups(options);
|
|
100
|
-
const
|
|
101
|
-
return functionsDeployHelper_1.functionMatchesAnyGroup(
|
|
102
|
+
const matchingBackend = backend.matchingBackend(wantBackend, (endpoint) => {
|
|
103
|
+
return functionsDeployHelper_1.functionMatchesAnyGroup(endpoint, context.filters);
|
|
102
104
|
});
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
await
|
|
105
|
+
const haveBackend = await backend.existingBackend(context);
|
|
106
|
+
inferDetailsFromExisting(wantBackend, haveBackend, usedDotenv);
|
|
107
|
+
await triggerRegionHelper_1.lookupMissingTriggerRegions(wantBackend);
|
|
108
|
+
await prompts_1.promptForFailurePolicies(options, matchingBackend, haveBackend);
|
|
109
|
+
await prompts_1.promptForMinInstances(options, matchingBackend, haveBackend);
|
|
106
110
|
await backend.checkAvailability(context, wantBackend);
|
|
107
111
|
}
|
|
108
112
|
exports.prepare = prepare;
|
|
113
|
+
function inferDetailsFromExisting(want, have, usedDotenv) {
|
|
114
|
+
var _a;
|
|
115
|
+
for (const wantE of backend.allEndpoints(want)) {
|
|
116
|
+
const haveE = (_a = have.endpoints[wantE.region]) === null || _a === void 0 ? void 0 : _a[wantE.id];
|
|
117
|
+
if (!haveE) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (!usedDotenv) {
|
|
121
|
+
wantE.environmentVariables = Object.assign(Object.assign({}, haveE.environmentVariables), wantE.environmentVariables);
|
|
122
|
+
}
|
|
123
|
+
if (!wantE.availableMemoryMb && haveE.availableMemoryMb) {
|
|
124
|
+
wantE.availableMemoryMb = haveE.availableMemoryMb;
|
|
125
|
+
}
|
|
126
|
+
maybeCopyTriggerRegion(wantE, haveE);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
exports.inferDetailsFromExisting = inferDetailsFromExisting;
|
|
130
|
+
function maybeCopyTriggerRegion(wantE, haveE) {
|
|
131
|
+
if (!backend.isEventTriggered(wantE) || !backend.isEventTriggered(haveE)) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (wantE.eventTrigger.region || !haveE.eventTrigger.region) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (JSON.stringify(haveE.eventTrigger.eventFilters) !==
|
|
138
|
+
JSON.stringify(wantE.eventTrigger.eventFilters)) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
wantE.eventTrigger.region = haveE.eventTrigger.region;
|
|
142
|
+
}
|