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
|
@@ -1,38 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.compareFunctions = 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.isTaskQueueTriggered = 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
|
-
return "
|
|
16
|
+
else if (isEventTriggered(endpoint)) {
|
|
17
|
+
return endpoint.eventTrigger.eventType;
|
|
18
|
+
}
|
|
19
|
+
else if (isTaskQueueTriggered(endpoint)) {
|
|
20
|
+
return "taskQueue";
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
throw new Error("Unexpected trigger type for endpoint " + JSON.stringify(endpoint));
|
|
32
24
|
}
|
|
33
|
-
return fn.trigger.eventType;
|
|
34
25
|
}
|
|
35
|
-
exports.
|
|
26
|
+
exports.endpointTriggerType = endpointTriggerType;
|
|
36
27
|
function memoryOptionDisplayName(option) {
|
|
37
28
|
return {
|
|
38
29
|
128: "128MB",
|
|
@@ -58,39 +49,38 @@ function isScheduleTriggered(triggered) {
|
|
|
58
49
|
return {}.hasOwnProperty.call(triggered, "scheduleTrigger");
|
|
59
50
|
}
|
|
60
51
|
exports.isScheduleTriggered = isScheduleTriggered;
|
|
52
|
+
function isTaskQueueTriggered(triggered) {
|
|
53
|
+
return {}.hasOwnProperty.call(triggered, "taskQueueTrigger");
|
|
54
|
+
}
|
|
55
|
+
exports.isTaskQueueTriggered = isTaskQueueTriggered;
|
|
61
56
|
function empty() {
|
|
62
57
|
return {
|
|
63
58
|
requiredAPIs: {},
|
|
64
|
-
endpoints:
|
|
65
|
-
cloudFunctions: [],
|
|
66
|
-
schedules: [],
|
|
67
|
-
topics: [],
|
|
59
|
+
endpoints: {},
|
|
68
60
|
environmentVariables: {},
|
|
69
61
|
};
|
|
70
62
|
}
|
|
71
63
|
exports.empty = empty;
|
|
64
|
+
function of(...endpoints) {
|
|
65
|
+
const bkend = Object.assign({}, empty());
|
|
66
|
+
for (const endpoint of endpoints) {
|
|
67
|
+
bkend.endpoints[endpoint.region] = bkend.endpoints[endpoint.region] || {};
|
|
68
|
+
if (bkend.endpoints[endpoint.region][endpoint.id]) {
|
|
69
|
+
throw new Error("Trying to create a backend with the same endpiont twice");
|
|
70
|
+
}
|
|
71
|
+
bkend.endpoints[endpoint.region][endpoint.id] = endpoint;
|
|
72
|
+
}
|
|
73
|
+
return bkend;
|
|
74
|
+
}
|
|
75
|
+
exports.of = of;
|
|
72
76
|
function isEmptyBackend(backend) {
|
|
73
|
-
return (Object.keys(backend.requiredAPIs).length == 0 &&
|
|
74
|
-
backend.cloudFunctions.length === 0 &&
|
|
75
|
-
backend.schedules.length === 0 &&
|
|
76
|
-
backend.topics.length === 0);
|
|
77
|
+
return (Object.keys(backend.requiredAPIs).length == 0 && Object.keys(backend.endpoints).length === 0);
|
|
77
78
|
}
|
|
78
79
|
exports.isEmptyBackend = isEmptyBackend;
|
|
79
80
|
function functionName(cloudFunction) {
|
|
80
81
|
return `projects/${cloudFunction.project}/locations/${cloudFunction.region}/functions/${cloudFunction.id}`;
|
|
81
82
|
}
|
|
82
83
|
exports.functionName = functionName;
|
|
83
|
-
exports.sameFunctionName = (func) => (test) => {
|
|
84
|
-
return func.id === test.id && func.region === test.region && func.project == test.project;
|
|
85
|
-
};
|
|
86
|
-
function scheduleName(schedule, appEngineLocation) {
|
|
87
|
-
return `projects/${schedule.project}/locations/${appEngineLocation}/jobs/${schedule.id}`;
|
|
88
|
-
}
|
|
89
|
-
exports.scheduleName = scheduleName;
|
|
90
|
-
function topicName(topic) {
|
|
91
|
-
return `projects/${topic.project}/topics/${topic.id}`;
|
|
92
|
-
}
|
|
93
|
-
exports.topicName = topicName;
|
|
94
84
|
function scheduleIdForFunction(cloudFunction) {
|
|
95
85
|
return `firebase-schedule-${cloudFunction.id}-${cloudFunction.region}`;
|
|
96
86
|
}
|
|
@@ -104,7 +94,7 @@ async function existingBackend(context, forceRefresh) {
|
|
|
104
94
|
}
|
|
105
95
|
exports.existingBackend = existingBackend;
|
|
106
96
|
async function loadExistingBackend(ctx) {
|
|
107
|
-
var _a
|
|
97
|
+
var _a;
|
|
108
98
|
ctx.loadedExistingBackend = true;
|
|
109
99
|
ctx.existingBackend = Object.assign({}, empty());
|
|
110
100
|
ctx.unreachableRegions = {
|
|
@@ -113,79 +103,30 @@ async function loadExistingBackend(ctx) {
|
|
|
113
103
|
};
|
|
114
104
|
const gcfV1Results = await gcf.listAllFunctions(ctx.projectId);
|
|
115
105
|
for (const apiFunction of gcfV1Results.functions) {
|
|
116
|
-
const
|
|
117
|
-
ctx.existingBackend.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const id = scheduleIdForFunction(specFunction);
|
|
121
|
-
ctx.existingBackend.schedules.push({
|
|
122
|
-
id,
|
|
123
|
-
project: specFunction.project,
|
|
124
|
-
transport: "pubsub",
|
|
125
|
-
targetService: {
|
|
126
|
-
id: specFunction.id,
|
|
127
|
-
region: specFunction.region,
|
|
128
|
-
project: specFunction.project,
|
|
129
|
-
},
|
|
130
|
-
});
|
|
131
|
-
ctx.existingBackend.topics.push({
|
|
132
|
-
id,
|
|
133
|
-
project: specFunction.project,
|
|
134
|
-
labels: exports.SCHEDULED_FUNCTION_LABEL,
|
|
135
|
-
targetService: {
|
|
136
|
-
id: specFunction.id,
|
|
137
|
-
region: specFunction.region,
|
|
138
|
-
project: specFunction.project,
|
|
139
|
-
},
|
|
140
|
-
});
|
|
141
|
-
}
|
|
106
|
+
const endpoint = gcf.endpointFromFunction(apiFunction);
|
|
107
|
+
ctx.existingBackend.endpoints[endpoint.region] =
|
|
108
|
+
ctx.existingBackend.endpoints[endpoint.region] || {};
|
|
109
|
+
ctx.existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
|
|
142
110
|
}
|
|
143
111
|
ctx.unreachableRegions.gcfV1 = gcfV1Results.unreachable;
|
|
144
112
|
if (!previews_1.previews.functionsv2) {
|
|
145
113
|
return;
|
|
146
114
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const id = scheduleIdForFunction(specFunction);
|
|
155
|
-
ctx.existingBackend.schedules.push({
|
|
156
|
-
id,
|
|
157
|
-
project: specFunction.project,
|
|
158
|
-
transport: "pubsub",
|
|
159
|
-
targetService: {
|
|
160
|
-
id: specFunction.id,
|
|
161
|
-
region: specFunction.region,
|
|
162
|
-
project: specFunction.project,
|
|
163
|
-
},
|
|
164
|
-
});
|
|
165
|
-
ctx.existingBackend.topics.push({
|
|
166
|
-
id,
|
|
167
|
-
project: specFunction.project,
|
|
168
|
-
labels: exports.SCHEDULED_FUNCTION_LABEL,
|
|
169
|
-
targetService: {
|
|
170
|
-
id: specFunction.id,
|
|
171
|
-
region: specFunction.region,
|
|
172
|
-
project: specFunction.project,
|
|
173
|
-
},
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
if (httpsScheduled) {
|
|
177
|
-
const id = scheduleIdForFunction(specFunction);
|
|
178
|
-
ctx.existingBackend.schedules.push({
|
|
179
|
-
id,
|
|
180
|
-
project: specFunction.project,
|
|
181
|
-
transport: "https",
|
|
182
|
-
targetService: {
|
|
183
|
-
id: specFunction.id,
|
|
184
|
-
region: specFunction.region,
|
|
185
|
-
project: specFunction.project,
|
|
186
|
-
},
|
|
187
|
-
});
|
|
115
|
+
let gcfV2Results;
|
|
116
|
+
try {
|
|
117
|
+
gcfV2Results = await gcfV2.listAllFunctions(ctx.projectId);
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
if (err.status === 404 && ((_a = err.message) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes("method not found"))) {
|
|
121
|
+
return;
|
|
188
122
|
}
|
|
123
|
+
throw err;
|
|
124
|
+
}
|
|
125
|
+
for (const apiFunction of gcfV2Results.functions) {
|
|
126
|
+
const endpoint = gcfV2.endpointFromFunction(apiFunction);
|
|
127
|
+
ctx.existingBackend.endpoints[endpoint.region] =
|
|
128
|
+
ctx.existingBackend.endpoints[endpoint.region] || {};
|
|
129
|
+
ctx.existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
|
|
189
130
|
}
|
|
190
131
|
ctx.unreachableRegions.gcfV2 = gcfV2Results.unreachable;
|
|
191
132
|
}
|
|
@@ -196,12 +137,12 @@ async function checkAvailability(context, want) {
|
|
|
196
137
|
}
|
|
197
138
|
const gcfV1Regions = new Set();
|
|
198
139
|
const gcfV2Regions = new Set();
|
|
199
|
-
for (const
|
|
200
|
-
if (
|
|
201
|
-
gcfV1Regions.add(
|
|
140
|
+
for (const ep of allEndpoints(want)) {
|
|
141
|
+
if (ep.platform == "gcfv1") {
|
|
142
|
+
gcfV1Regions.add(ep.region);
|
|
202
143
|
}
|
|
203
144
|
else {
|
|
204
|
-
gcfV2Regions.add(
|
|
145
|
+
gcfV2Regions.add(ep.region);
|
|
205
146
|
}
|
|
206
147
|
}
|
|
207
148
|
const neededUnreachableV1 = ctx.unreachableRegions.gcfV1.filter((region) => gcfV1Regions.has(region));
|
|
@@ -228,6 +169,43 @@ async function checkAvailability(context, want) {
|
|
|
228
169
|
}
|
|
229
170
|
}
|
|
230
171
|
exports.checkAvailability = checkAvailability;
|
|
172
|
+
function allEndpoints(backend) {
|
|
173
|
+
return Object.values(backend.endpoints).reduce((accum, perRegion) => {
|
|
174
|
+
return [...accum, ...Object.values(perRegion)];
|
|
175
|
+
}, []);
|
|
176
|
+
}
|
|
177
|
+
exports.allEndpoints = allEndpoints;
|
|
178
|
+
function someEndpoint(backend, predicate) {
|
|
179
|
+
for (const endpoints of Object.values(backend.endpoints)) {
|
|
180
|
+
if (Object.values(endpoints).some(predicate)) {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
exports.someEndpoint = someEndpoint;
|
|
187
|
+
function matchingBackend(backend, predicate) {
|
|
188
|
+
const filtered = Object.assign({}, empty());
|
|
189
|
+
for (const endpoint of allEndpoints(backend)) {
|
|
190
|
+
if (!predicate(endpoint)) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
filtered.endpoints[endpoint.region] = filtered.endpoints[endpoint.region] || {};
|
|
194
|
+
filtered.endpoints[endpoint.region][endpoint.id] = endpoint;
|
|
195
|
+
}
|
|
196
|
+
return filtered;
|
|
197
|
+
}
|
|
198
|
+
exports.matchingBackend = matchingBackend;
|
|
199
|
+
function regionalEndpoints(backend, region) {
|
|
200
|
+
return backend.endpoints[region] ? Object.values(backend.endpoints[region]) : [];
|
|
201
|
+
}
|
|
202
|
+
exports.regionalEndpoints = regionalEndpoints;
|
|
203
|
+
exports.hasEndpoint = (backend) => (endpoint) => {
|
|
204
|
+
return !!backend.endpoints[endpoint.region] && !!backend.endpoints[endpoint.region][endpoint.id];
|
|
205
|
+
};
|
|
206
|
+
exports.missingEndpoint = (backend) => (endpoint) => {
|
|
207
|
+
return !exports.hasEndpoint(backend)(endpoint);
|
|
208
|
+
};
|
|
231
209
|
function compareFunctions(left, right) {
|
|
232
210
|
if (left.platform != right.platform) {
|
|
233
211
|
return right.platform < left.platform ? -1 : 1;
|
|
@@ -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");
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DockerHelper = exports.deleteGcfArtifacts = exports.listGcfPaths = exports.ContainerRegistryCleaner = exports.cleanupBuildImages = exports.SUBDOMAIN_MAPPING = void 0;
|
|
3
|
+
exports.DockerHelper = exports.deleteGcfArtifacts = exports.listGcfPaths = exports.ContainerRegistryCleaner = exports.ArtifactRegistryCleaner = exports.cleanupBuildImages = exports.SUBDOMAIN_MAPPING = void 0;
|
|
4
4
|
const clc = require("cli-color");
|
|
5
|
+
const error_1 = require("../../error");
|
|
5
6
|
const api_1 = require("../../api");
|
|
6
7
|
const logger_1 = require("../../logger");
|
|
7
|
-
const
|
|
8
|
+
const artifactregistry = require("../../gcp/artifactregistry");
|
|
8
9
|
const backend = require("./backend");
|
|
10
|
+
const docker = require("../../gcp/docker");
|
|
9
11
|
const utils = require("../../utils");
|
|
10
|
-
const
|
|
12
|
+
const poller = require("../../operation-poller");
|
|
11
13
|
exports.SUBDOMAIN_MAPPING = {
|
|
12
14
|
"us-west2": "us",
|
|
13
15
|
"us-west3": "us",
|
|
@@ -47,7 +49,7 @@ async function retry(func) {
|
|
|
47
49
|
return await Promise.race([func(), timeout]);
|
|
48
50
|
}
|
|
49
51
|
catch (error) {
|
|
50
|
-
logger_1.logger.debug("Failed docker command with error", error);
|
|
52
|
+
logger_1.logger.debug("Failed docker command with error ", error);
|
|
51
53
|
retry += 1;
|
|
52
54
|
if (retry >= MAX_RETRIES) {
|
|
53
55
|
throw new error_1.FirebaseError("Failed to clean up artifacts", { original: error });
|
|
@@ -56,11 +58,31 @@ async function retry(func) {
|
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
60
|
}
|
|
59
|
-
async function cleanupBuildImages(
|
|
61
|
+
async function cleanupBuildImages(haveFunctions, deletedFunctions, cleaners = {}) {
|
|
60
62
|
utils.logBullet(clc.bold.cyan("functions: ") + "cleaning up build files...");
|
|
61
|
-
const gcrCleaner = new ContainerRegistryCleaner();
|
|
62
63
|
const failedDomains = new Set();
|
|
63
|
-
|
|
64
|
+
const cleanup = [];
|
|
65
|
+
const arCleaner = cleaners.ar || new ArtifactRegistryCleaner();
|
|
66
|
+
cleanup.push(...haveFunctions.map(async (func) => {
|
|
67
|
+
try {
|
|
68
|
+
await arCleaner.cleanupFunction(func);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
const path = `${func.project}/${func.region}/gcf-artifacts`;
|
|
72
|
+
failedDomains.add(`https://console.cloud.google.com/artifacts/docker/${path}`);
|
|
73
|
+
}
|
|
74
|
+
}));
|
|
75
|
+
cleanup.push(...deletedFunctions.map(async (func) => {
|
|
76
|
+
try {
|
|
77
|
+
await Promise.all([arCleaner.cleanupFunction(func), arCleaner.cleanupFunctionCache(func)]);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
const path = `${func.project}/${func.region}/gcf-artifacts`;
|
|
81
|
+
failedDomains.add(`https://console.cloud.google.com/artifacts/docker/${path}`);
|
|
82
|
+
}
|
|
83
|
+
}));
|
|
84
|
+
const gcrCleaner = cleaners.gcr || new ContainerRegistryCleaner();
|
|
85
|
+
cleanup.push(...[...haveFunctions, ...deletedFunctions].map(async (func) => {
|
|
64
86
|
try {
|
|
65
87
|
await gcrCleaner.cleanupFunction(func);
|
|
66
88
|
}
|
|
@@ -68,7 +90,8 @@ async function cleanupBuildImages(functions) {
|
|
|
68
90
|
const path = `${func.project}/${exports.SUBDOMAIN_MAPPING[func.region]}/gcf`;
|
|
69
91
|
failedDomains.add(`https://console.cloud.google.com/gcr/images/${path}`);
|
|
70
92
|
}
|
|
71
|
-
})
|
|
93
|
+
}));
|
|
94
|
+
await Promise.all(cleanup);
|
|
72
95
|
if (failedDomains.size) {
|
|
73
96
|
let message = "Unhandled error cleaning up build images. This could result in a small monthly bill if not corrected. ";
|
|
74
97
|
message +=
|
|
@@ -83,6 +106,40 @@ async function cleanupBuildImages(functions) {
|
|
|
83
106
|
}
|
|
84
107
|
}
|
|
85
108
|
exports.cleanupBuildImages = cleanupBuildImages;
|
|
109
|
+
class ArtifactRegistryCleaner {
|
|
110
|
+
static packagePath(func) {
|
|
111
|
+
return `projects/${func.project}/locations/${func.region}/repositories/gcf-artifacts/packages/${func.id}`;
|
|
112
|
+
}
|
|
113
|
+
async cleanupFunction(func) {
|
|
114
|
+
let op;
|
|
115
|
+
try {
|
|
116
|
+
op = await artifactregistry.deletePackage(ArtifactRegistryCleaner.packagePath(func));
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
if (err.status === 404) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
throw err;
|
|
123
|
+
}
|
|
124
|
+
if (op.done) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
await poller.pollOperation(Object.assign(Object.assign({}, ArtifactRegistryCleaner.POLLER_OPTIONS), { pollerName: `cleanup-${func.region}-${func.id}`, operationResourceName: op.name }));
|
|
128
|
+
}
|
|
129
|
+
async cleanupFunctionCache(func) {
|
|
130
|
+
const op = await artifactregistry.deletePackage(`${ArtifactRegistryCleaner.packagePath(func)}%2Fcache`);
|
|
131
|
+
if (op.done) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
await poller.pollOperation(Object.assign(Object.assign({}, ArtifactRegistryCleaner.POLLER_OPTIONS), { pollerName: `cleanup-cache-${func.region}-${func.id}`, operationResourceName: op.name }));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
exports.ArtifactRegistryCleaner = ArtifactRegistryCleaner;
|
|
138
|
+
ArtifactRegistryCleaner.POLLER_OPTIONS = {
|
|
139
|
+
apiOrigin: api_1.artifactRegistryDomain,
|
|
140
|
+
apiVersion: artifactregistry.API_VERSION,
|
|
141
|
+
masterTimeout: 5 * 60 * 1000,
|
|
142
|
+
};
|
|
86
143
|
class ContainerRegistryCleaner {
|
|
87
144
|
constructor() {
|
|
88
145
|
this.helpers = {};
|
|
@@ -217,7 +274,7 @@ class DockerHelper {
|
|
|
217
274
|
async rm(path) {
|
|
218
275
|
let toThrowLater = undefined;
|
|
219
276
|
const stat = await this.ls(path);
|
|
220
|
-
const recursive = stat.children.map(
|
|
277
|
+
const recursive = stat.children.map(async (child) => {
|
|
221
278
|
try {
|
|
222
279
|
await this.rm(`${path}/${child}`);
|
|
223
280
|
stat.children.splice(stat.children.indexOf(child), 1);
|
|
@@ -225,8 +282,8 @@ class DockerHelper {
|
|
|
225
282
|
catch (err) {
|
|
226
283
|
toThrowLater = err;
|
|
227
284
|
}
|
|
228
|
-
})
|
|
229
|
-
const deleteTags = stat.tags.map(
|
|
285
|
+
});
|
|
286
|
+
const deleteTags = stat.tags.map(async (tag) => {
|
|
230
287
|
try {
|
|
231
288
|
await retry(() => this.client.deleteTag(path, tag));
|
|
232
289
|
stat.tags.splice(stat.tags.indexOf(tag), 1);
|
|
@@ -235,9 +292,9 @@ class DockerHelper {
|
|
|
235
292
|
logger_1.logger.debug("Got error trying to remove docker tag:", err);
|
|
236
293
|
toThrowLater = err;
|
|
237
294
|
}
|
|
238
|
-
})
|
|
295
|
+
});
|
|
239
296
|
await Promise.all(deleteTags);
|
|
240
|
-
const deleteImages = stat.digests.map(
|
|
297
|
+
const deleteImages = stat.digests.map(async (digest) => {
|
|
241
298
|
try {
|
|
242
299
|
await retry(() => this.client.deleteImage(path, digest));
|
|
243
300
|
stat.digests.splice(stat.digests.indexOf(digest), 1);
|
|
@@ -246,7 +303,7 @@ class DockerHelper {
|
|
|
246
303
|
logger_1.logger.debug("Got error trying to remove docker image:", err);
|
|
247
304
|
toThrowLater = err;
|
|
248
305
|
}
|
|
249
|
-
})
|
|
306
|
+
});
|
|
250
307
|
await Promise.all(deleteImages);
|
|
251
308
|
await Promise.all(recursive);
|
|
252
309
|
if (toThrowLater) {
|
|
@@ -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) {
|
|
@@ -44,18 +45,11 @@ async function deploy(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
|
}
|
|
@@ -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;
|