firebase-tools 11.5.0 → 11.8.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/lib/command.js +33 -7
- package/lib/commands/crashlytics-mappingfile-generateid.js +26 -0
- package/lib/commands/crashlytics-mappingfile-upload.js +46 -0
- package/lib/commands/crashlytics-symbols-upload.js +18 -87
- package/lib/commands/emulators-exec.js +4 -1
- package/lib/commands/emulators-export.js +5 -2
- package/lib/commands/emulators-start.js +23 -17
- package/lib/commands/ext-dev-publish.js +3 -0
- package/lib/commands/functions-delete.js +2 -0
- package/lib/commands/functions-secrets-get.js +2 -0
- package/lib/commands/index.js +3 -0
- package/lib/commands/login.js +2 -2
- package/lib/crashlytics/buildToolsJarHelper.js +51 -0
- package/lib/deploy/functions/backend.js +4 -4
- package/lib/deploy/functions/build.js +76 -9
- package/lib/deploy/functions/checkIam.js +6 -5
- package/lib/deploy/functions/params.js +22 -16
- package/lib/deploy/functions/prepare.js +1 -1
- package/lib/deploy/functions/release/fabricator.js +22 -5
- package/lib/deploy/functions/release/index.js +2 -0
- package/lib/deploy/functions/runtimes/discovery/index.js +1 -16
- package/lib/deploy/functions/runtimes/discovery/parsing.js +16 -0
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +59 -131
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +1 -1
- package/lib/emulator/auth/index.js +7 -2
- package/lib/emulator/auth/operations.js +10 -10
- package/lib/emulator/commandUtils.js +32 -15
- package/lib/emulator/constants.js +14 -6
- package/lib/emulator/controller.js +49 -17
- package/lib/emulator/downloadableEmulators.js +7 -7
- package/lib/emulator/eventarcEmulator.js +148 -0
- package/lib/emulator/extensionsEmulator.js +3 -1
- package/lib/emulator/functionsEmulator.js +44 -4
- package/lib/emulator/functionsEmulatorRuntime.js +12 -23
- package/lib/emulator/functionsEmulatorShared.js +6 -1
- package/lib/emulator/hub.js +7 -3
- package/lib/emulator/hubClient.js +2 -2
- package/lib/emulator/hubExport.js +22 -2
- package/lib/emulator/registry.js +1 -0
- package/lib/emulator/storage/apis/firebase.js +145 -129
- package/lib/emulator/storage/apis/gcloud.js +102 -42
- package/lib/emulator/storage/files.js +39 -17
- package/lib/emulator/storage/metadata.js +76 -55
- package/lib/emulator/storage/multipart.js +2 -2
- package/lib/emulator/storage/rules/runtime.js +12 -4
- package/lib/emulator/storage/server.js +2 -1
- package/lib/emulator/storage/upload.js +46 -9
- package/lib/emulator/types.js +3 -0
- package/lib/emulator/ui.js +7 -2
- package/lib/extensions/extensionsApi.js +2 -1
- package/lib/extensions/extensionsHelper.js +29 -1
- package/lib/functions/constants.js +14 -0
- package/lib/functions/env.js +9 -9
- package/lib/gcp/cloudfunctions.js +15 -18
- package/lib/gcp/cloudfunctionsv2.js +15 -18
- package/lib/gcp/cloudscheduler.js +32 -14
- package/lib/serve/index.js +15 -0
- package/lib/track.js +122 -3
- package/lib/utils.js +14 -1
- package/npm-shrinkwrap.json +542 -9
- package/package.json +5 -4
- package/schema/firebase-config.json +12 -0
- package/templates/extensions/CHANGELOG.md +1 -7
package/lib/emulator/registry.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createFirebaseEndpoints = void 0;
|
|
4
4
|
const emulatorLogger_1 = require("../../emulatorLogger");
|
|
5
5
|
const types_1 = require("../../types");
|
|
6
|
+
const uuid = require("uuid");
|
|
6
7
|
const zlib_1 = require("zlib");
|
|
7
8
|
const metadata_1 = require("../metadata");
|
|
8
9
|
const express_1 = require("express");
|
|
@@ -93,8 +94,8 @@ function createFirebaseEndpoints(emulator) {
|
|
|
93
94
|
}
|
|
94
95
|
throw err;
|
|
95
96
|
}
|
|
96
|
-
if (
|
|
97
|
-
metadata.addDownloadToken();
|
|
97
|
+
if (metadata.downloadTokens.length === 0) {
|
|
98
|
+
metadata.addDownloadToken(true);
|
|
98
99
|
}
|
|
99
100
|
if (req.query.alt === "media") {
|
|
100
101
|
const isGZipped = metadata.contentEncoding === "gzip";
|
|
@@ -102,7 +103,8 @@ function createFirebaseEndpoints(emulator) {
|
|
|
102
103
|
data = (0, zlib_1.gunzipSync)(data);
|
|
103
104
|
}
|
|
104
105
|
res.setHeader("Accept-Ranges", "bytes");
|
|
105
|
-
res.setHeader("Content-Type", metadata.contentType);
|
|
106
|
+
res.setHeader("Content-Type", metadata.contentType || "application/octet-stream");
|
|
107
|
+
res.setHeader("Content-Disposition", metadata.contentDisposition || "inline");
|
|
106
108
|
setObjectHeaders(res, metadata, { "Content-Encoding": isGZipped ? "identity" : undefined });
|
|
107
109
|
const byteRange = req.range(data.byteLength, { combine: true });
|
|
108
110
|
if (Array.isArray(byteRange) && byteRange.type === "bytes" && byteRange.length > 0) {
|
|
@@ -165,47 +167,26 @@ function createFirebaseEndpoints(emulator) {
|
|
|
165
167
|
});
|
|
166
168
|
});
|
|
167
169
|
const handleUpload = async (req, res) => {
|
|
168
|
-
|
|
169
|
-
res.sendStatus(400);
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
170
|
+
var _a, _b;
|
|
172
171
|
const bucketId = req.params.bucketId;
|
|
173
|
-
const objectId = req.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
172
|
+
const objectId = req.params.objectId
|
|
173
|
+
? decodeURIComponent(req.params.objectId)
|
|
174
|
+
: ((_a = req.query.name) === null || _a === void 0 ? void 0 : _a.toString()) || null;
|
|
175
|
+
const uploadType = (_b = req.header("x-goog-upload-protocol")) === null || _b === void 0 ? void 0 : _b.toString();
|
|
176
|
+
async function finalizeOneShotUpload(upload) {
|
|
177
|
+
var _a, _b, _c;
|
|
178
|
+
if (!((_b = (_a = upload.metadata) === null || _a === void 0 ? void 0 : _a.metadata) === null || _b === void 0 ? void 0 : _b.firebaseStorageDownloadTokens)) {
|
|
179
|
+
const customMetadata = Object.assign(Object.assign({}, (((_c = upload.metadata) === null || _c === void 0 ? void 0 : _c.metadata) || {})), { firebaseStorageDownloadTokens: uuid.v4() });
|
|
180
|
+
upload.metadata = Object.assign(Object.assign({}, (upload.metadata || {})), { metadata: customMetadata });
|
|
179
181
|
}
|
|
180
|
-
let metadataRaw;
|
|
181
|
-
let dataRaw;
|
|
182
|
-
try {
|
|
183
|
-
({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await (0, request_1.reqBodyToBuffer)(req)));
|
|
184
|
-
}
|
|
185
|
-
catch (err) {
|
|
186
|
-
if (err instanceof Error) {
|
|
187
|
-
return res.status(400).json({
|
|
188
|
-
error: {
|
|
189
|
-
code: 400,
|
|
190
|
-
message: err.message,
|
|
191
|
-
},
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
throw err;
|
|
195
|
-
}
|
|
196
|
-
const upload = uploadService.multipartUpload({
|
|
197
|
-
bucketId,
|
|
198
|
-
objectId,
|
|
199
|
-
metadataRaw,
|
|
200
|
-
dataRaw: dataRaw,
|
|
201
|
-
authorization: req.header("authorization"),
|
|
202
|
-
});
|
|
203
182
|
let metadata;
|
|
204
183
|
try {
|
|
205
184
|
metadata = await storageLayer.uploadObject(upload);
|
|
206
185
|
}
|
|
207
186
|
catch (err) {
|
|
208
187
|
if (err instanceof errors_1.ForbiddenError) {
|
|
188
|
+
res.header("x-goog-upload-status", "final");
|
|
189
|
+
uploadService.setResponseCode(upload.id, 403);
|
|
209
190
|
return res.status(403).json({
|
|
210
191
|
error: {
|
|
211
192
|
code: 403,
|
|
@@ -215,120 +196,153 @@ function createFirebaseEndpoints(emulator) {
|
|
|
215
196
|
}
|
|
216
197
|
throw err;
|
|
217
198
|
}
|
|
218
|
-
metadata.
|
|
199
|
+
if (!metadata.contentDisposition) {
|
|
200
|
+
metadata.contentDisposition = "inline";
|
|
201
|
+
}
|
|
219
202
|
return res.status(200).json(new metadata_1.OutgoingFirebaseMetadata(metadata));
|
|
220
203
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
authorization: req.header("authorization"),
|
|
232
|
-
});
|
|
233
|
-
res.header("x-goog-upload-chunk-granularity", "10000");
|
|
234
|
-
res.header("x-goog-upload-control-url", "");
|
|
235
|
-
res.header("x-goog-upload-status", "active");
|
|
236
|
-
res.header("x-gupload-uploadid", upload.id);
|
|
237
|
-
const uploadUrl = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE, req);
|
|
238
|
-
uploadUrl.pathname = `/v0/b/${bucketId}/o`;
|
|
239
|
-
uploadUrl.searchParams.set("name", objectId);
|
|
240
|
-
uploadUrl.searchParams.set("upload_id", upload.id);
|
|
241
|
-
uploadUrl.searchParams.set("upload_protocol", "resumable");
|
|
242
|
-
res.header("x-goog-upload-url", uploadUrl.toString());
|
|
243
|
-
return res.sendStatus(200);
|
|
244
|
-
}
|
|
245
|
-
if (!req.query.upload_id) {
|
|
246
|
-
return res.sendStatus(400);
|
|
247
|
-
}
|
|
248
|
-
const uploadId = req.query.upload_id.toString();
|
|
249
|
-
if (uploadCommand === "query") {
|
|
250
|
-
let upload;
|
|
251
|
-
try {
|
|
252
|
-
upload = uploadService.getResumableUpload(uploadId);
|
|
253
|
-
}
|
|
254
|
-
catch (err) {
|
|
255
|
-
if (err instanceof errors_1.NotFoundError) {
|
|
256
|
-
return res.sendStatus(404);
|
|
204
|
+
if (uploadType === "resumable") {
|
|
205
|
+
const uploadCommand = req.header("x-goog-upload-command");
|
|
206
|
+
if (!uploadCommand) {
|
|
207
|
+
res.sendStatus(400);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (uploadCommand === "start") {
|
|
211
|
+
if (!objectId) {
|
|
212
|
+
res.sendStatus(400);
|
|
213
|
+
return;
|
|
257
214
|
}
|
|
258
|
-
|
|
215
|
+
const upload = uploadService.startResumableUpload({
|
|
216
|
+
bucketId,
|
|
217
|
+
objectId,
|
|
218
|
+
metadataRaw: JSON.stringify(req.body),
|
|
219
|
+
authorization: req.header("authorization"),
|
|
220
|
+
});
|
|
221
|
+
res.header("x-goog-upload-chunk-granularity", "10000");
|
|
222
|
+
res.header("x-goog-upload-control-url", "");
|
|
223
|
+
res.header("x-goog-upload-status", "active");
|
|
224
|
+
res.header("x-gupload-uploadid", upload.id);
|
|
225
|
+
const uploadUrl = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE, req);
|
|
226
|
+
uploadUrl.pathname = `/v0/b/${bucketId}/o`;
|
|
227
|
+
uploadUrl.searchParams.set("name", objectId);
|
|
228
|
+
uploadUrl.searchParams.set("upload_id", upload.id);
|
|
229
|
+
uploadUrl.searchParams.set("upload_protocol", "resumable");
|
|
230
|
+
res.header("x-goog-upload-url", uploadUrl.toString());
|
|
231
|
+
return res.sendStatus(200);
|
|
259
232
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
if (uploadCommand === "cancel") {
|
|
264
|
-
try {
|
|
265
|
-
uploadService.cancelResumableUpload(uploadId);
|
|
233
|
+
if (!req.query.upload_id) {
|
|
234
|
+
return res.sendStatus(400);
|
|
266
235
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
236
|
+
const uploadId = req.query.upload_id.toString();
|
|
237
|
+
if (uploadCommand === "query") {
|
|
238
|
+
let upload;
|
|
239
|
+
try {
|
|
240
|
+
upload = uploadService.getResumableUpload(uploadId);
|
|
270
241
|
}
|
|
271
|
-
|
|
272
|
-
|
|
242
|
+
catch (err) {
|
|
243
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
244
|
+
return res.sendStatus(404);
|
|
245
|
+
}
|
|
246
|
+
throw err;
|
|
273
247
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
return res.sendStatus(200);
|
|
277
|
-
}
|
|
278
|
-
if (uploadCommand.includes("upload")) {
|
|
279
|
-
let upload;
|
|
280
|
-
try {
|
|
281
|
-
upload = uploadService.continueResumableUpload(uploadId, await (0, request_1.reqBodyToBuffer)(req));
|
|
248
|
+
res.header("X-Goog-Upload-Size-Received", upload.size.toString());
|
|
249
|
+
return res.sendStatus(200);
|
|
282
250
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
251
|
+
if (uploadCommand === "cancel") {
|
|
252
|
+
try {
|
|
253
|
+
uploadService.cancelResumableUpload(uploadId);
|
|
286
254
|
}
|
|
287
|
-
|
|
288
|
-
|
|
255
|
+
catch (err) {
|
|
256
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
257
|
+
return res.sendStatus(404);
|
|
258
|
+
}
|
|
259
|
+
else if (err instanceof upload_1.NotCancellableError) {
|
|
260
|
+
return res.sendStatus(400);
|
|
261
|
+
}
|
|
262
|
+
throw err;
|
|
289
263
|
}
|
|
290
|
-
throw err;
|
|
291
|
-
}
|
|
292
|
-
if (!uploadCommand.includes("finalize")) {
|
|
293
|
-
res.header("x-goog-upload-status", "active");
|
|
294
|
-
res.header("x-gupload-uploadid", upload.id);
|
|
295
264
|
return res.sendStatus(200);
|
|
296
265
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
266
|
+
if (uploadCommand.includes("upload")) {
|
|
267
|
+
let upload;
|
|
268
|
+
try {
|
|
269
|
+
upload = uploadService.continueResumableUpload(uploadId, await (0, request_1.reqBodyToBuffer)(req));
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
273
|
+
return res.sendStatus(404);
|
|
274
|
+
}
|
|
275
|
+
else if (err instanceof upload_1.UploadNotActiveError) {
|
|
276
|
+
return res.sendStatus(400);
|
|
277
|
+
}
|
|
278
|
+
throw err;
|
|
279
|
+
}
|
|
280
|
+
if (!uploadCommand.includes("finalize")) {
|
|
281
|
+
res.header("x-goog-upload-status", "active");
|
|
282
|
+
res.header("x-gupload-uploadid", upload.id);
|
|
283
|
+
return res.sendStatus(200);
|
|
284
|
+
}
|
|
302
285
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
286
|
+
if (uploadCommand.includes("finalize")) {
|
|
287
|
+
let upload;
|
|
288
|
+
try {
|
|
289
|
+
upload = uploadService.finalizeResumableUpload(uploadId);
|
|
306
290
|
}
|
|
307
|
-
|
|
308
|
-
|
|
291
|
+
catch (err) {
|
|
292
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
293
|
+
uploadService.setResponseCode(uploadId, 404);
|
|
294
|
+
return res.sendStatus(404);
|
|
295
|
+
}
|
|
296
|
+
else if (err instanceof upload_1.UploadNotActiveError) {
|
|
297
|
+
uploadService.setResponseCode(uploadId, 400);
|
|
298
|
+
return res.sendStatus(400);
|
|
299
|
+
}
|
|
300
|
+
else if (err instanceof upload_1.UploadPreviouslyFinalizedError) {
|
|
301
|
+
res.header("x-goog-upload-status", "final");
|
|
302
|
+
return res.sendStatus(uploadService.getPreviousResponseCode(uploadId));
|
|
303
|
+
}
|
|
304
|
+
throw err;
|
|
309
305
|
}
|
|
310
|
-
|
|
306
|
+
res.header("x-goog-upload-status", "final");
|
|
307
|
+
return await finalizeOneShotUpload(upload);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (!objectId) {
|
|
311
|
+
res.sendStatus(400);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (uploadType === "multipart") {
|
|
315
|
+
const contentTypeHeader = req.header("content-type");
|
|
316
|
+
if (!contentTypeHeader) {
|
|
317
|
+
return res.sendStatus(400);
|
|
311
318
|
}
|
|
312
|
-
let
|
|
319
|
+
let metadataRaw;
|
|
320
|
+
let dataRaw;
|
|
313
321
|
try {
|
|
314
|
-
|
|
322
|
+
({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await (0, request_1.reqBodyToBuffer)(req)));
|
|
315
323
|
}
|
|
316
324
|
catch (err) {
|
|
317
|
-
if (err instanceof
|
|
318
|
-
return res.status(
|
|
319
|
-
error: {
|
|
320
|
-
code: 403,
|
|
321
|
-
message: `Permission denied. No WRITE permission.`,
|
|
322
|
-
},
|
|
323
|
-
});
|
|
325
|
+
if (err instanceof Error) {
|
|
326
|
+
return res.status(400).send(err.message);
|
|
324
327
|
}
|
|
325
328
|
throw err;
|
|
326
329
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
+
const upload = uploadService.multipartUpload({
|
|
331
|
+
bucketId,
|
|
332
|
+
objectId,
|
|
333
|
+
metadataRaw,
|
|
334
|
+
dataRaw: dataRaw,
|
|
335
|
+
authorization: req.header("authorization"),
|
|
336
|
+
});
|
|
337
|
+
return await finalizeOneShotUpload(upload);
|
|
330
338
|
}
|
|
331
|
-
|
|
339
|
+
const upload = uploadService.mediaUpload({
|
|
340
|
+
bucketId: req.params.bucketId,
|
|
341
|
+
objectId: objectId,
|
|
342
|
+
dataRaw: await (0, request_1.reqBodyToBuffer)(req),
|
|
343
|
+
authorization: req.header("authorization"),
|
|
344
|
+
});
|
|
345
|
+
return await finalizeOneShotUpload(upload);
|
|
332
346
|
};
|
|
333
347
|
const handleTokenRequest = (req, res) => {
|
|
334
348
|
var _a, _b;
|
|
@@ -467,11 +481,13 @@ function createFirebaseEndpoints(emulator) {
|
|
|
467
481
|
}
|
|
468
482
|
exports.createFirebaseEndpoints = createFirebaseEndpoints;
|
|
469
483
|
function setObjectHeaders(res, metadata, headerOverride = { "Content-Encoding": undefined }) {
|
|
470
|
-
|
|
484
|
+
if (metadata.contentDisposition) {
|
|
485
|
+
res.setHeader("Content-Disposition", metadata.contentDisposition);
|
|
486
|
+
}
|
|
471
487
|
if (headerOverride["Content-Encoding"]) {
|
|
472
488
|
res.setHeader("Content-Encoding", headerOverride["Content-Encoding"]);
|
|
473
489
|
}
|
|
474
|
-
else {
|
|
490
|
+
else if (metadata.contentEncoding) {
|
|
475
491
|
res.setHeader("Content-Encoding", metadata.contentEncoding);
|
|
476
492
|
}
|
|
477
493
|
if (metadata.cacheControl) {
|
|
@@ -15,6 +15,45 @@ const request_1 = require("../../shared/request");
|
|
|
15
15
|
function createCloudEndpoints(emulator) {
|
|
16
16
|
const gcloudStorageAPI = (0, express_1.Router)();
|
|
17
17
|
const { adminStorageLayer, uploadService } = emulator;
|
|
18
|
+
if (process.env.STORAGE_EMULATOR_DEBUG) {
|
|
19
|
+
gcloudStorageAPI.use((req, res, next) => {
|
|
20
|
+
console.log("--------------INCOMING REQUEST--------------");
|
|
21
|
+
console.log(`${req.method.toUpperCase()} ${req.path}`);
|
|
22
|
+
console.log("-- query:");
|
|
23
|
+
console.log(JSON.stringify(req.query, undefined, 2));
|
|
24
|
+
console.log("-- headers:");
|
|
25
|
+
console.log(JSON.stringify(req.headers, undefined, 2));
|
|
26
|
+
console.log("-- body:");
|
|
27
|
+
if (req.body instanceof Buffer) {
|
|
28
|
+
console.log(`Buffer of ${req.body.length}`);
|
|
29
|
+
}
|
|
30
|
+
else if (req.body) {
|
|
31
|
+
console.log(req.body);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
console.log("Empty body (could be stream)");
|
|
35
|
+
}
|
|
36
|
+
const resJson = res.json.bind(res);
|
|
37
|
+
res.json = (...args) => {
|
|
38
|
+
console.log("-- response:");
|
|
39
|
+
args.forEach((data) => console.log(JSON.stringify(data, undefined, 2)));
|
|
40
|
+
return resJson.call(res, ...args);
|
|
41
|
+
};
|
|
42
|
+
const resSendStatus = res.sendStatus.bind(res);
|
|
43
|
+
res.sendStatus = (status) => {
|
|
44
|
+
console.log("-- response status:");
|
|
45
|
+
console.log(status);
|
|
46
|
+
return resSendStatus.call(res, status);
|
|
47
|
+
};
|
|
48
|
+
const resStatus = res.status.bind(res);
|
|
49
|
+
res.status = (status) => {
|
|
50
|
+
console.log("-- response status:");
|
|
51
|
+
console.log(status);
|
|
52
|
+
return resStatus.call(res, status);
|
|
53
|
+
};
|
|
54
|
+
next();
|
|
55
|
+
});
|
|
56
|
+
}
|
|
18
57
|
gcloudStorageAPI.use(/.*\/b\/(.+?)\/.*/, (req, res, next) => {
|
|
19
58
|
adminStorageLayer.createBucket(req.params[0]);
|
|
20
59
|
next();
|
|
@@ -25,7 +64,11 @@ function createCloudEndpoints(emulator) {
|
|
|
25
64
|
items: await adminStorageLayer.listBuckets(),
|
|
26
65
|
});
|
|
27
66
|
});
|
|
28
|
-
gcloudStorageAPI.get([
|
|
67
|
+
gcloudStorageAPI.get([
|
|
68
|
+
"/b/:bucketId/o/:objectId",
|
|
69
|
+
"/download/storage/v1/b/:bucketId/o/:objectId",
|
|
70
|
+
"/storage/v1/b/:bucketId/o/:objectId",
|
|
71
|
+
], async (req, res) => {
|
|
29
72
|
let getObjectResponse;
|
|
30
73
|
try {
|
|
31
74
|
getObjectResponse = await adminStorageLayer.getObject({
|
|
@@ -87,7 +130,7 @@ function createCloudEndpoints(emulator) {
|
|
|
87
130
|
throw err;
|
|
88
131
|
}
|
|
89
132
|
return res.status(200).json({
|
|
90
|
-
kind: "#
|
|
133
|
+
kind: "storage#objects",
|
|
91
134
|
nextPageToken: listResponse.nextPageToken,
|
|
92
135
|
prefixes: listResponse.prefixes,
|
|
93
136
|
items: (_a = listResponse.items) === null || _a === void 0 ? void 0 : _a.map((item) => new metadata_1.CloudStorageObjectMetadata(item)),
|
|
@@ -177,11 +220,8 @@ function createCloudEndpoints(emulator) {
|
|
|
177
220
|
});
|
|
178
221
|
});
|
|
179
222
|
gcloudStorageAPI.post("/upload/storage/v1/b/:bucketId/o", async (req, res) => {
|
|
180
|
-
const
|
|
181
|
-
if (
|
|
182
|
-
return res.sendStatus(400);
|
|
183
|
-
}
|
|
184
|
-
if (req.query.uploadType === "resumable") {
|
|
223
|
+
const uploadType = req.query.uploadType || req.header("X-Goog-Upload-Protocol");
|
|
224
|
+
if (uploadType === "resumable") {
|
|
185
225
|
const emulatorInfo = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE);
|
|
186
226
|
if (emulatorInfo === undefined) {
|
|
187
227
|
return res.sendStatus(500);
|
|
@@ -204,45 +244,65 @@ function createCloudEndpoints(emulator) {
|
|
|
204
244
|
uploadUrl.searchParams.set("upload_id", upload.id);
|
|
205
245
|
return res.header("location", uploadUrl.toString()).sendStatus(200);
|
|
206
246
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
247
|
+
async function finalizeOneShotUpload(upload) {
|
|
248
|
+
let metadata;
|
|
249
|
+
try {
|
|
250
|
+
metadata = await adminStorageLayer.uploadObject(upload);
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
if (err instanceof errors_1.ForbiddenError) {
|
|
254
|
+
return res.sendStatus(403);
|
|
255
|
+
}
|
|
256
|
+
throw err;
|
|
257
|
+
}
|
|
258
|
+
return res.status(200).json(new metadata_1.CloudStorageObjectMetadata(metadata));
|
|
211
259
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
code: 400,
|
|
217
|
-
message: err.message,
|
|
218
|
-
},
|
|
219
|
-
});
|
|
260
|
+
if (uploadType === "multipart") {
|
|
261
|
+
const contentTypeHeader = req.header("content-type") || req.header("x-upload-content-type");
|
|
262
|
+
if (!contentTypeHeader) {
|
|
263
|
+
return res.sendStatus(400);
|
|
220
264
|
}
|
|
221
|
-
|
|
265
|
+
let metadataRaw;
|
|
266
|
+
let dataRaw;
|
|
267
|
+
try {
|
|
268
|
+
({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await (0, request_1.reqBodyToBuffer)(req)));
|
|
269
|
+
}
|
|
270
|
+
catch (err) {
|
|
271
|
+
if (err instanceof Error) {
|
|
272
|
+
return res.status(400).json({
|
|
273
|
+
error: {
|
|
274
|
+
code: 400,
|
|
275
|
+
message: err.message,
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
throw err;
|
|
280
|
+
}
|
|
281
|
+
const name = getIncomingFileNameFromRequest(req.query, JSON.parse(metadataRaw));
|
|
282
|
+
if (name === undefined) {
|
|
283
|
+
res.sendStatus(400);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const upload = uploadService.multipartUpload({
|
|
287
|
+
bucketId: req.params.bucketId,
|
|
288
|
+
objectId: name,
|
|
289
|
+
metadataRaw: metadataRaw,
|
|
290
|
+
dataRaw: dataRaw,
|
|
291
|
+
authorization: req.header("authorization"),
|
|
292
|
+
});
|
|
293
|
+
return await finalizeOneShotUpload(upload);
|
|
222
294
|
}
|
|
223
|
-
const name =
|
|
224
|
-
if (name
|
|
295
|
+
const name = req.query.name;
|
|
296
|
+
if (!name) {
|
|
225
297
|
res.sendStatus(400);
|
|
226
|
-
return;
|
|
227
298
|
}
|
|
228
|
-
const upload = uploadService.
|
|
299
|
+
const upload = uploadService.mediaUpload({
|
|
229
300
|
bucketId: req.params.bucketId,
|
|
230
|
-
objectId: name,
|
|
231
|
-
|
|
232
|
-
dataRaw: dataRaw,
|
|
301
|
+
objectId: name.toString(),
|
|
302
|
+
dataRaw: await (0, request_1.reqBodyToBuffer)(req),
|
|
233
303
|
authorization: req.header("authorization"),
|
|
234
304
|
});
|
|
235
|
-
|
|
236
|
-
try {
|
|
237
|
-
metadata = await adminStorageLayer.uploadObject(upload);
|
|
238
|
-
}
|
|
239
|
-
catch (err) {
|
|
240
|
-
if (err instanceof errors_1.ForbiddenError) {
|
|
241
|
-
return res.sendStatus(403);
|
|
242
|
-
}
|
|
243
|
-
throw err;
|
|
244
|
-
}
|
|
245
|
-
return res.status(200).json(new metadata_1.CloudStorageObjectMetadata(metadata));
|
|
305
|
+
return await finalizeOneShotUpload(upload);
|
|
246
306
|
});
|
|
247
307
|
gcloudStorageAPI.get("/:bucketId/:objectId(**)", async (req, res) => {
|
|
248
308
|
let getObjectResponse;
|
|
@@ -324,11 +384,11 @@ function sendFileBytes(md, data, req, res) {
|
|
|
324
384
|
data = (0, zlib_1.gunzipSync)(data);
|
|
325
385
|
}
|
|
326
386
|
res.setHeader("Accept-Ranges", "bytes");
|
|
327
|
-
res.setHeader("Content-Type", md.contentType);
|
|
328
|
-
res.setHeader("Content-Disposition", md.contentDisposition);
|
|
329
|
-
res.setHeader("Content-Encoding", isGZipped ? "identity" : md.contentEncoding);
|
|
387
|
+
res.setHeader("Content-Type", md.contentType || "application/octet-stream");
|
|
388
|
+
res.setHeader("Content-Disposition", md.contentDisposition || "attachment");
|
|
389
|
+
res.setHeader("Content-Encoding", isGZipped ? "identity" : md.contentEncoding || "");
|
|
330
390
|
res.setHeader("ETag", md.etag);
|
|
331
|
-
res.setHeader("Cache-Control", md.cacheControl);
|
|
391
|
+
res.setHeader("Cache-Control", md.cacheControl || "");
|
|
332
392
|
res.setHeader("x-goog-generation", `${md.generation}`);
|
|
333
393
|
res.setHeader("x-goog-metadatageneration", `${md.metageneration}`);
|
|
334
394
|
res.setHeader("x-goog-storage-class", md.storageClass);
|
|
@@ -17,6 +17,8 @@ const logger_1 = require("../../logger");
|
|
|
17
17
|
const adminSdkConfig_1 = require("../adminSdkConfig");
|
|
18
18
|
const types_1 = require("./rules/types");
|
|
19
19
|
const upload_1 = require("./upload");
|
|
20
|
+
const track_1 = require("../../track");
|
|
21
|
+
const types_2 = require("../types");
|
|
20
22
|
class StoredFile {
|
|
21
23
|
constructor(metadata) {
|
|
22
24
|
this.metadata = metadata;
|
|
@@ -140,20 +142,26 @@ class StorageLayer {
|
|
|
140
142
|
}
|
|
141
143
|
const storedMetadata = this.getMetadata(upload.bucketId, upload.objectId);
|
|
142
144
|
const filePath = this.path(upload.bucketId, upload.objectId);
|
|
145
|
+
function getIncomingMetadata(field) {
|
|
146
|
+
if (!upload.metadata) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
const value = upload.metadata[field];
|
|
150
|
+
return value === null ? undefined : value;
|
|
151
|
+
}
|
|
143
152
|
const metadata = new metadata_1.StoredFileMetadata({
|
|
144
153
|
name: upload.objectId,
|
|
145
154
|
bucket: upload.bucketId,
|
|
146
|
-
contentType:
|
|
147
|
-
contentDisposition:
|
|
148
|
-
contentEncoding:
|
|
149
|
-
contentLanguage:
|
|
150
|
-
cacheControl:
|
|
151
|
-
customMetadata:
|
|
155
|
+
contentType: getIncomingMetadata("contentType"),
|
|
156
|
+
contentDisposition: getIncomingMetadata("contentDisposition"),
|
|
157
|
+
contentEncoding: getIncomingMetadata("contentEncoding"),
|
|
158
|
+
contentLanguage: getIncomingMetadata("contentLanguage"),
|
|
159
|
+
cacheControl: getIncomingMetadata("cacheControl"),
|
|
160
|
+
customMetadata: getIncomingMetadata("metadata"),
|
|
152
161
|
}, this._cloudFunctions, this._persistence.readBytes(upload.path, upload.size));
|
|
153
|
-
metadata.update(upload.metadata, false);
|
|
154
162
|
const authorized = await this._rulesValidator.validate(["b", upload.bucketId, "o", upload.objectId].join("/"), upload.bucketId, types_1.RulesetOperationMethod.CREATE, {
|
|
155
163
|
before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource(),
|
|
156
|
-
after: metadata
|
|
164
|
+
after: metadata.asRulesResource(),
|
|
157
165
|
}, upload.authorization);
|
|
158
166
|
if (!authorized) {
|
|
159
167
|
this._persistence.deleteFile(upload.path);
|
|
@@ -190,16 +198,20 @@ class StorageLayer {
|
|
|
190
198
|
newMetadata.metadata[k] = "";
|
|
191
199
|
}
|
|
192
200
|
}
|
|
201
|
+
function getMetadata(field) {
|
|
202
|
+
const value = newMetadata[field];
|
|
203
|
+
return value === null ? undefined : value;
|
|
204
|
+
}
|
|
193
205
|
const copiedFileMetadata = new metadata_1.StoredFileMetadata({
|
|
194
206
|
name: destinationObject,
|
|
195
207
|
bucket: destinationBucket,
|
|
196
|
-
contentType:
|
|
197
|
-
contentDisposition:
|
|
198
|
-
contentEncoding:
|
|
199
|
-
contentLanguage:
|
|
200
|
-
cacheControl:
|
|
201
|
-
customMetadata:
|
|
202
|
-
}, this._cloudFunctions, sourceBytes
|
|
208
|
+
contentType: getMetadata("contentType"),
|
|
209
|
+
contentDisposition: getMetadata("contentDisposition"),
|
|
210
|
+
contentEncoding: getMetadata("contentEncoding"),
|
|
211
|
+
contentLanguage: getMetadata("contentLanguage"),
|
|
212
|
+
cacheControl: getMetadata("cacheControl"),
|
|
213
|
+
customMetadata: getMetadata("metadata"),
|
|
214
|
+
}, this._cloudFunctions, sourceBytes);
|
|
203
215
|
const file = new StoredFile(copiedFileMetadata);
|
|
204
216
|
this._files.set(destinationFilePath, file);
|
|
205
217
|
this._cloudFunctions.dispatch("finalize", new metadata_1.CloudStorageObjectMetadata(file.metadata));
|
|
@@ -292,7 +304,7 @@ class StorageLayer {
|
|
|
292
304
|
get dirPath() {
|
|
293
305
|
return this._persistence.dirPath;
|
|
294
306
|
}
|
|
295
|
-
async export(storageExportPath) {
|
|
307
|
+
async export(storageExportPath, options) {
|
|
296
308
|
var e_1, _a;
|
|
297
309
|
const bucketsList = {
|
|
298
310
|
buckets: [],
|
|
@@ -300,6 +312,11 @@ class StorageLayer {
|
|
|
300
312
|
for (const b of await this.listBuckets()) {
|
|
301
313
|
bucketsList.buckets.push({ id: b.id });
|
|
302
314
|
}
|
|
315
|
+
void (0, track_1.trackEmulator)("emulator_export", {
|
|
316
|
+
initiated_by: options.initiatedBy,
|
|
317
|
+
emulator_name: types_2.Emulators.STORAGE,
|
|
318
|
+
count: bucketsList.buckets.length,
|
|
319
|
+
});
|
|
303
320
|
const bucketsFilePath = path.join(storageExportPath, "buckets.json");
|
|
304
321
|
await fse.writeFile(bucketsFilePath, JSON.stringify(bucketsList, undefined, 2));
|
|
305
322
|
const blobsDirPath = path.join(storageExportPath, "blobs");
|
|
@@ -323,9 +340,14 @@ class StorageLayer {
|
|
|
323
340
|
finally { if (e_1) throw e_1.error; }
|
|
324
341
|
}
|
|
325
342
|
}
|
|
326
|
-
import(storageExportPath) {
|
|
343
|
+
import(storageExportPath, options) {
|
|
327
344
|
const bucketsFile = path.join(storageExportPath, "buckets.json");
|
|
328
345
|
const bucketsList = JSON.parse((0, fs_1.readFileSync)(bucketsFile, "utf-8"));
|
|
346
|
+
void (0, track_1.trackEmulator)("emulator_import", {
|
|
347
|
+
initiated_by: options.initiatedBy,
|
|
348
|
+
emulator_name: types_2.Emulators.STORAGE,
|
|
349
|
+
count: bucketsList.buckets.length,
|
|
350
|
+
});
|
|
329
351
|
for (const b of bucketsList.buckets) {
|
|
330
352
|
const bucketMetadata = new metadata_1.CloudStorageBucketMetadata(b.id);
|
|
331
353
|
this._buckets.set(b.id, bucketMetadata);
|