@xen-orchestra/fs 0.19.2 → 1.0.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/dist/s3.js CHANGED
@@ -5,27 +5,72 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = void 0;
7
7
 
8
- var _awsSdk = _interopRequireDefault(require("@sullux/aws-sdk"));
8
+ var _clientS = require("@aws-sdk/client-s3");
9
+
10
+ var _libStorage = require("@aws-sdk/lib-storage");
11
+
12
+ var _nodeHttpHandler = require("@aws-sdk/node-http-handler");
9
13
 
10
14
  var _assert = _interopRequireDefault(require("assert"));
11
15
 
12
- var _http = _interopRequireDefault(require("http"));
16
+ var _http = require("http");
17
+
18
+ var _https = require("https");
19
+
20
+ var _retry = _interopRequireDefault(require("promise-toolbox/retry"));
21
+
22
+ var _log = require("@xen-orchestra/log");
23
+
24
+ var _decorateWith = require("@vates/decorate-with");
13
25
 
14
- var _https = _interopRequireDefault(require("https"));
26
+ var _stream = require("stream");
15
27
 
16
28
  var _xoRemoteParser = require("xo-remote-parser");
17
29
 
30
+ var _copyStreamToBuffer = _interopRequireDefault(require("./_copyStreamToBuffer.js"));
31
+
32
+ var _createBufferFromStream = _interopRequireDefault(require("./_createBufferFromStream.js"));
33
+
34
+ var _guessAwsRegion = _interopRequireDefault(require("./_guessAwsRegion.js"));
35
+
18
36
  var _abstract = _interopRequireDefault(require("./abstract"));
19
37
 
38
+ var _path = require("./_path");
39
+
40
+ var _asyncEach = require("@vates/async-each");
41
+
42
+ var _dec, _class;
43
+
20
44
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
21
45
 
46
+ function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { var desc = {}; Object.keys(descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ('value' in desc || desc.initializer) { desc.writable = true; } desc = decorators.slice().reverse().reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object.defineProperty(target, property, desc); desc = null; } return desc; }
47
+
22
48
  const MIN_PART_SIZE = 1024 * 1024 * 5;
23
49
  const MAX_PART_SIZE = 1024 * 1024 * 1024 * 5;
24
50
  const MAX_PARTS_COUNT = 10000;
25
51
  const MAX_OBJECT_SIZE = 1024 * 1024 * 1024 * 1024 * 5;
26
52
  const IDEAL_FRAGMENT_SIZE = Math.ceil(MAX_OBJECT_SIZE / MAX_PARTS_COUNT);
53
+ const {
54
+ warn
55
+ } = (0, _log.createLogger)('xo:fs:s3');
56
+ let S3Handler = (_dec = (0, _decorateWith.decorateWith)(_retry.default.wrap, {
57
+ delays: [100, 200, 500, 1000, 2000],
58
+ when: e => {
59
+ var _e$$metadata;
60
+
61
+ return ((_e$$metadata = e.$metadata) === null || _e$$metadata === void 0 ? void 0 : _e$$metadata.httpStatusCode) === 500;
62
+ },
63
+
64
+ onRetry(error) {
65
+ warn('retrying writing file', {
66
+ attemptNumber: this.attemptNumber,
67
+ delay: this.delay,
68
+ error,
69
+ file: this.arguments[0]
70
+ });
71
+ }
27
72
 
28
- class S3Handler extends _abstract.default {
73
+ }), (_class = class S3Handler extends _abstract.default {
29
74
  constructor(remote, _opts) {
30
75
  super(remote);
31
76
  const {
@@ -35,105 +80,127 @@ class S3Handler extends _abstract.default {
35
80
  username,
36
81
  password,
37
82
  protocol,
38
- region
83
+ region = (0, _guessAwsRegion.default)(host)
39
84
  } = (0, _xoRemoteParser.parse)(remote.url);
40
- const params = {
41
- accessKeyId: username,
85
+ this._s3 = new _clientS.S3Client({
42
86
  apiVersion: '2006-03-01',
43
- endpoint: host,
44
- s3ForcePathStyle: true,
45
- secretAccessKey: password,
46
- signatureVersion: 'v4',
47
- httpOptions: {
48
- timeout: 600000
49
- }
50
- };
87
+ endpoint: `${protocol}://${host}`,
88
+ forcePathStyle: true,
89
+ credentials: {
90
+ accessKeyId: username,
91
+ secretAccessKey: password
92
+ },
93
+ tls: protocol === 'https',
94
+ region,
95
+ requestHandler: new _nodeHttpHandler.NodeHttpHandler({
96
+ socketTimeout: 600000,
97
+ httpAgent: new _http.Agent({
98
+ keepAlive: true
99
+ }),
100
+ httpsAgent: new _https.Agent({
101
+ rejectUnauthorized: !allowUnauthorized,
102
+ keepAlive: true
103
+ })
104
+ })
105
+ });
106
+ const parts = (0, _path.split)(path);
107
+ this._bucket = parts.shift();
108
+ this._dir = (0, _path.join)(...parts);
109
+ }
51
110
 
52
- if (protocol === 'http') {
53
- params.httpOptions.agent = new _http.default.Agent({
54
- keepAlive: true
55
- });
56
- params.sslEnabled = false;
57
- } else if (protocol === 'https') {
58
- params.httpOptions.agent = new _https.default.Agent({
59
- rejectUnauthorized: !allowUnauthorized,
60
- keepAlive: true
61
- });
62
- }
111
+ get type() {
112
+ return 's3';
113
+ }
63
114
 
64
- if (region !== undefined) {
65
- params.region = region;
66
- }
115
+ _makeCopySource(path) {
116
+ return (0, _path.join)(this._bucket, this._dir, path);
117
+ }
67
118
 
68
- this._s3 = (0, _awsSdk.default)(params).s3;
69
- const splitPath = path.split('/').filter(s => s.length);
70
- this._bucket = splitPath.shift();
71
- this._dir = splitPath.join('/');
119
+ _makeKey(file) {
120
+ return (0, _path.join)(this._dir, file);
72
121
  }
73
122
 
74
- get type() {
75
- return 's3';
123
+ _makePrefix(dir) {
124
+ return (0, _path.join)(this._dir, dir, '/');
76
125
  }
77
126
 
78
127
  _createParams(file) {
79
128
  return {
80
129
  Bucket: this._bucket,
81
- Key: this._dir + file
130
+ Key: this._makeKey(file)
82
131
  };
83
132
  }
84
133
 
85
- async _copy(oldPath, newPath) {
134
+ async _multipartCopy(oldPath, newPath) {
86
135
  const size = await this._getSize(oldPath);
87
- const multipartParams = await this._s3.createMultipartUpload({ ...this._createParams(newPath)
88
- });
89
- const param2 = { ...multipartParams,
90
- CopySource: `/${this._bucket}/${this._dir}${oldPath}`
91
- };
136
+
137
+ const CopySource = this._makeCopySource(oldPath);
138
+
139
+ const multipartParams = await this._s3.send(new _clientS.CreateMultipartUploadCommand({ ...this._createParams(newPath)
140
+ }));
92
141
 
93
142
  try {
94
143
  const parts = [];
95
144
  let start = 0;
96
145
 
97
146
  while (start < size) {
98
- const range = `bytes=${start}-${Math.min(start + MAX_PART_SIZE, size) - 1}`;
99
- const partParams = { ...param2,
100
- PartNumber: parts.length + 1,
101
- CopySourceRange: range
102
- };
103
- const upload = await this._s3.uploadPartCopy(partParams);
147
+ const partNumber = parts.length + 1;
148
+ const upload = await this._s3.send(new _clientS.UploadPartCopyCommand({ ...multipartParams,
149
+ CopySource,
150
+ CopySourceRange: `bytes=${start}-${Math.min(start + MAX_PART_SIZE, size) - 1}`,
151
+ PartNumber: partNumber
152
+ }));
104
153
  parts.push({
105
154
  ETag: upload.CopyPartResult.ETag,
106
- PartNumber: partParams.PartNumber
155
+ PartNumber: partNumber
107
156
  });
108
157
  start += MAX_PART_SIZE;
109
158
  }
110
159
 
111
- await this._s3.completeMultipartUpload({ ...multipartParams,
160
+ await this._s3.send(new _clientS.CompleteMultipartUploadCommand({ ...multipartParams,
112
161
  MultipartUpload: {
113
162
  Parts: parts
114
163
  }
115
- });
164
+ }));
116
165
  } catch (e) {
117
- await this._s3.abortMultipartUpload(multipartParams);
166
+ await this._s3.send(new _clientS.AbortMultipartUploadCommand(multipartParams));
167
+ throw e;
168
+ }
169
+ }
170
+
171
+ async _copy(oldPath, newPath) {
172
+ const CopySource = this._makeCopySource(oldPath);
173
+
174
+ try {
175
+ await this._s3.send(new _clientS.CopyObjectCommand({ ...this._createParams(newPath),
176
+ CopySource
177
+ }));
178
+ } catch (e) {
179
+ if (e.name === 'EntityTooLarge') {
180
+ return this._multipartCopy(oldPath, newPath);
181
+ }
182
+
118
183
  throw e;
119
184
  }
120
185
  }
121
186
 
122
187
  async _isNotEmptyDir(path) {
123
- const result = await this._s3.listObjectsV2({
188
+ var _result$Contents;
189
+
190
+ const result = await this._s3.send(new _clientS.ListObjectsV2Command({
124
191
  Bucket: this._bucket,
125
192
  MaxKeys: 1,
126
- Prefix: this._dir + path + '/'
127
- });
128
- return result.Contents.length !== 0;
193
+ Prefix: this._makePrefix(path)
194
+ }));
195
+ return ((_result$Contents = result.Contents) === null || _result$Contents === void 0 ? void 0 : _result$Contents.length) > 0;
129
196
  }
130
197
 
131
198
  async _isFile(path) {
132
199
  try {
133
- await this._s3.headObject(this._createParams(path));
200
+ await this._s3.send(new _clientS.HeadObjectCommand(this._createParams(path)));
134
201
  return true;
135
202
  } catch (error) {
136
- if (error.code === 'NotFound') {
203
+ if (error.name === 'NotFound') {
137
204
  return false;
138
205
  }
139
206
 
@@ -144,12 +211,17 @@ class S3Handler extends _abstract.default {
144
211
  async _outputStream(path, input, {
145
212
  validator
146
213
  }) {
147
- await this._s3.upload({ ...this._createParams(path),
148
- Body: input
149
- }, {
214
+ const Body = new _stream.PassThrough();
215
+ (0, _stream.pipeline)(input, Body, () => {});
216
+ const upload = new _libStorage.Upload({
217
+ client: this._s3,
218
+ queueSize: 1,
150
219
  partSize: IDEAL_FRAGMENT_SIZE,
151
- queueSize: 1
220
+ params: { ...this._createParams(path),
221
+ Body
222
+ }
152
223
  });
224
+ await upload.done();
153
225
 
154
226
  if (validator !== undefined) {
155
227
  try {
@@ -162,9 +234,9 @@ class S3Handler extends _abstract.default {
162
234
  }
163
235
 
164
236
  async _writeFile(file, data, options) {
165
- return this._s3.putObject({ ...this._createParams(file),
237
+ return this._s3.send(new _clientS.PutObjectCommand({ ...this._createParams(file),
166
238
  Body: data
167
- });
239
+ }));
168
240
  }
169
241
 
170
242
  async _createReadStream(path, options) {
@@ -175,11 +247,11 @@ class S3Handler extends _abstract.default {
175
247
  throw error;
176
248
  }
177
249
 
178
- return this._s3.getObject.raw(this._createParams(path)).createReadStream();
250
+ return (await this._s3.send(new _clientS.GetObjectCommand(this._createParams(path)))).Body;
179
251
  }
180
252
 
181
253
  async _unlink(path) {
182
- await this._s3.deleteObject(this._createParams(path));
254
+ await this._s3.send(new _clientS.DeleteObjectCommand(this._createParams(path)));
183
255
 
184
256
  if (await this._isNotEmptyDir(path)) {
185
257
  const error = new Error(`EISDIR: illegal operation on a directory, unlink '${path}'`);
@@ -190,37 +262,36 @@ class S3Handler extends _abstract.default {
190
262
  }
191
263
 
192
264
  async _list(dir) {
193
- function splitPath(path) {
194
- return path.split('/').filter(d => d.length);
195
- }
265
+ let NextContinuationToken;
266
+ const uniq = new Set();
196
267
 
197
- const prefix = [this._dir, dir].join('/');
198
- const splitPrefix = splitPath(prefix);
199
- const result = await this._s3.listObjectsV2({
200
- Bucket: this._bucket,
201
- Prefix: splitPrefix.join('/') + '/',
202
- Delimiter: '/'
203
- });
268
+ const Prefix = this._makePrefix(dir);
204
269
 
205
- if (result.isTruncated) {
206
- const error = new Error('more than 1000 objects, unsupported in this implementation');
207
- error.dir = dir;
208
- throw error;
209
- }
270
+ do {
271
+ const result = await this._s3.send(new _clientS.ListObjectsV2Command({
272
+ Bucket: this._bucket,
273
+ Prefix,
274
+ Delimiter: '/',
275
+ ContinuationToken: NextContinuationToken
276
+ }));
210
277
 
211
- const uniq = [];
278
+ if (result.IsTruncated) {
279
+ warn(`need pagination to browse the directory ${dir} completely`);
280
+ NextContinuationToken = result.NextContinuationToken;
281
+ } else {
282
+ NextContinuationToken = undefined;
283
+ }
212
284
 
213
- for (const entry of result.CommonPrefixes) {
214
- const line = splitPath(entry.Prefix);
215
- uniq.push(line[line.length - 1]);
216
- }
285
+ for (const entry of result.CommonPrefixes ?? []) {
286
+ uniq.add((0, _path.basename)(entry.Prefix));
287
+ }
217
288
 
218
- for (const entry of result.Contents) {
219
- const line = splitPath(entry.Key);
220
- uniq.push(line[line.length - 1]);
221
- }
289
+ for (const entry of result.Contents ?? []) {
290
+ uniq.add((0, _path.basename)(entry.Key));
291
+ }
292
+ } while (NextContinuationToken !== undefined);
222
293
 
223
- return uniq;
294
+ return [...uniq];
224
295
  }
225
296
 
226
297
  async _mkdir(path) {
@@ -234,7 +305,7 @@ class S3Handler extends _abstract.default {
234
305
 
235
306
  async _rename(oldPath, newPath) {
236
307
  await this.copy(oldPath, newPath);
237
- await this._s3.deleteObject(this._createParams(oldPath));
308
+ await this._s3.send(new _clientS.DeleteObjectCommand(this._createParams(oldPath)));
238
309
  }
239
310
 
240
311
  async _getSize(file) {
@@ -242,7 +313,7 @@ class S3Handler extends _abstract.default {
242
313
  file = file.fd;
243
314
  }
244
315
 
245
- const result = await this._s3.headObject(this._createParams(file));
316
+ const result = await this._s3.send(new _clientS.HeadObjectCommand(this._createParams(file)));
246
317
  return +result.ContentLength;
247
318
  }
248
319
 
@@ -256,14 +327,14 @@ class S3Handler extends _abstract.default {
256
327
  params.Range = `bytes=${position}-${position + buffer.length - 1}`;
257
328
 
258
329
  try {
259
- const result = await this._s3.getObject(params);
260
- result.Body.copy(buffer);
330
+ const result = await this._s3.send(new _clientS.GetObjectCommand(params));
331
+ const bytesRead = await (0, _copyStreamToBuffer.default)(result.Body, buffer);
261
332
  return {
262
- bytesRead: result.Body.length,
333
+ bytesRead,
263
334
  buffer
264
335
  };
265
336
  } catch (e) {
266
- if (e.code === 'NoSuchKey') {
337
+ if (e.name === 'NoSuchKey') {
267
338
  if (await this._isNotEmptyDir(file)) {
268
339
  const error = new Error(`${file} is a directory`);
269
340
  error.code = 'EISDIR';
@@ -288,23 +359,26 @@ class S3Handler extends _abstract.default {
288
359
  async _rmtree(path) {
289
360
  let NextContinuationToken;
290
361
 
362
+ const Prefix = this._makePrefix(path);
363
+
291
364
  do {
292
- const result = await this._s3.listObjectsV2({
365
+ const result = await this._s3.send(new _clientS.ListObjectsV2Command({
293
366
  Bucket: this._bucket,
294
- Prefix: this._dir + path + '/',
367
+ Prefix,
295
368
  ContinuationToken: NextContinuationToken
296
- });
297
- NextContinuationToken = result.isTruncated ? null : result.NextContinuationToken;
298
-
299
- for (const {
369
+ }));
370
+ NextContinuationToken = result.IsTruncated ? result.NextContinuationToken : undefined;
371
+ await (0, _asyncEach.asyncEach)(result.Contents ?? [], async ({
300
372
  Key
301
- } of result.Contents) {
302
- await this._s3.deleteObject({
373
+ }) => {
374
+ await this._s3.send(new _clientS.DeleteObjectCommand({
303
375
  Bucket: this._bucket,
304
376
  Key
305
- });
306
- }
307
- } while (NextContinuationToken !== null);
377
+ }));
378
+ }, {
379
+ concurrency: 16
380
+ });
381
+ } while (NextContinuationToken !== undefined);
308
382
  }
309
383
 
310
384
  async _write(file, buffer, position) {
@@ -317,9 +391,9 @@ class S3Handler extends _abstract.default {
317
391
  let fileSize;
318
392
 
319
393
  try {
320
- fileSize = +(await this._s3.headObject(uploadParams)).ContentLength;
394
+ fileSize = +(await this._s3.send(new _clientS.HeadObjectCommand(uploadParams))).ContentLength;
321
395
  } catch (e) {
322
- if (e.code === 'NotFound') {
396
+ if (e.name === 'NotFound') {
323
397
  fileSize = 0;
324
398
  } else {
325
399
  throw e;
@@ -328,20 +402,26 @@ class S3Handler extends _abstract.default {
328
402
 
329
403
  if (fileSize < MIN_PART_SIZE) {
330
404
  const resultBuffer = Buffer.alloc(Math.max(fileSize, position + buffer.length));
331
- const fileContent = fileSize !== 0 ? (await this._s3.getObject(uploadParams)).Body : Buffer.alloc(0);
332
- fileContent.copy(resultBuffer);
405
+
406
+ if (fileSize !== 0) {
407
+ const result = await this._s3.send(new _clientS.GetObjectCommand(uploadParams));
408
+ await (0, _copyStreamToBuffer.default)(result.Body, resultBuffer);
409
+ } else {
410
+ Buffer.alloc(0).copy(resultBuffer);
411
+ }
412
+
333
413
  buffer.copy(resultBuffer, position);
334
- await this._s3.putObject({ ...uploadParams,
414
+ await this._s3.send(new _clientS.PutObjectCommand({ ...uploadParams,
335
415
  Body: resultBuffer
336
- });
416
+ }));
337
417
  return {
338
418
  buffer,
339
419
  bytesWritten: buffer.length
340
420
  };
341
421
  } else {
342
- const multipartParams = await this._s3.createMultipartUpload(uploadParams);
422
+ const multipartParams = await this._s3.send(new _clientS.CreateMultipartUploadCommand(uploadParams));
343
423
  const copyMultipartParams = { ...multipartParams,
344
- CopySource: `/${this._bucket}/${this._dir + file}`
424
+ CopySource: this._makeCopySource(file)
345
425
  };
346
426
 
347
427
  try {
@@ -373,7 +453,7 @@ class S3Handler extends _abstract.default {
373
453
  PartNumber: partNumber++,
374
454
  CopySourceRange: range
375
455
  };
376
- const part = await this._s3.uploadPartCopy(copyPrefixParams);
456
+ const part = await this._s3.send(new _clientS.UploadPartCopyCommand(copyPrefixParams));
377
457
  parts.push({
378
458
  ETag: part.CopyPartResult.ETag,
379
459
  PartNumber: copyPrefixParams.PartNumber
@@ -385,7 +465,15 @@ class S3Handler extends _abstract.default {
385
465
  const downloadParams = { ...uploadParams,
386
466
  Range: `bytes=${prefixPosition}-${prefixSize - 1}`
387
467
  };
388
- const prefixBuffer = prefixSize > 0 ? (await this._s3.getObject(downloadParams)).Body : Buffer.alloc(0);
468
+ let prefixBuffer;
469
+
470
+ if (prefixSize > 0) {
471
+ const result = await this._s3.send(new _clientS.GetObjectCommand(downloadParams));
472
+ prefixBuffer = await (0, _createBufferFromStream.default)(result.Body);
473
+ } else {
474
+ prefixBuffer = Buffer.alloc(0);
475
+ }
476
+
389
477
  editBuffer = Buffer.concat([prefixBuffer, buffer]);
390
478
  editBufferOffset -= prefixLastFragmentSize;
391
479
  }
@@ -400,7 +488,8 @@ class S3Handler extends _abstract.default {
400
488
  const downloadParams = { ...uploadParams,
401
489
  Range: prefixRange
402
490
  };
403
- const complementBuffer = (await this._s3.getObject(downloadParams)).Body;
491
+ const result = await this._s3.send(new _clientS.GetObjectCommand(downloadParams));
492
+ const complementBuffer = await (0, _createBufferFromStream.default)(result.Body);
404
493
  editBuffer = Buffer.concat([editBuffer, complementBuffer]);
405
494
  }
406
495
 
@@ -408,7 +497,7 @@ class S3Handler extends _abstract.default {
408
497
  Body: editBuffer,
409
498
  PartNumber: partNumber++
410
499
  };
411
- const editPart = await this._s3.uploadPart(editParams);
500
+ const editPart = await this._s3.send(new _clientS.UploadPartCommand(editParams));
412
501
  parts.push({
413
502
  ETag: editPart.ETag,
414
503
  PartNumber: editParams.PartNumber
@@ -428,7 +517,7 @@ class S3Handler extends _abstract.default {
428
517
  PartNumber: partNumber++,
429
518
  CopySourceRange: suffixRange
430
519
  };
431
- const suffixPart = (await this._s3.uploadPartCopy(copySuffixParams)).CopyPartResult;
520
+ const suffixPart = (await this._s3.send(new _clientS.UploadPartCopyCommand(copySuffixParams))).CopyPartResult;
432
521
  parts.push({
433
522
  ETag: suffixPart.ETag,
434
523
  PartNumber: copySuffixParams.PartNumber
@@ -437,13 +526,13 @@ class S3Handler extends _abstract.default {
437
526
  }
438
527
  }
439
528
 
440
- await this._s3.completeMultipartUpload({ ...multipartParams,
529
+ await this._s3.send(new _clientS.CompleteMultipartUploadCommand({ ...multipartParams,
441
530
  MultipartUpload: {
442
531
  Parts: parts
443
532
  }
444
- });
533
+ }));
445
534
  } catch (e) {
446
- await this._s3.abortMultipartUpload(multipartParams);
535
+ await this._s3.send(new _clientS.AbortMultipartUploadCommand(multipartParams));
447
536
  throw e;
448
537
  }
449
538
  }
@@ -455,7 +544,6 @@ class S3Handler extends _abstract.default {
455
544
 
456
545
  async _closeFile(fd) {}
457
546
 
458
- }
459
-
547
+ }, (_applyDecoratedDescriptor(_class.prototype, "_writeFile", [_dec], Object.getOwnPropertyDescriptor(_class.prototype, "_writeFile"), _class.prototype)), _class));
460
548
  exports.default = S3Handler;
461
549
  //# sourceMappingURL=s3.js.map