@xen-orchestra/fs 0.20.0 → 1.0.2

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