@xen-orchestra/fs 0.20.0 → 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,13 +5,17 @@ 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");
13
17
 
14
- var _https = _interopRequireDefault(require("https"));
18
+ var _https = require("https");
15
19
 
16
20
  var _retry = _interopRequireDefault(require("promise-toolbox/retry"));
17
21
 
@@ -19,10 +23,20 @@ var _log = require("@xen-orchestra/log");
19
23
 
20
24
  var _decorateWith = require("@vates/decorate-with");
21
25
 
26
+ var _stream = require("stream");
27
+
22
28
  var _xoRemoteParser = require("xo-remote-parser");
23
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
+
24
36
  var _abstract = _interopRequireDefault(require("./abstract"));
25
37
 
38
+ var _path = require("./_path");
39
+
26
40
  var _asyncEach = require("@vates/async-each");
27
41
 
28
42
  var _dec, _class;
@@ -41,7 +55,11 @@ const {
41
55
  } = (0, _log.createLogger)('xo:fs:s3');
42
56
  let S3Handler = (_dec = (0, _decorateWith.decorateWith)(_retry.default.wrap, {
43
57
  delays: [100, 200, 500, 1000, 2000],
44
- when: e => e.code === 'InternalError',
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
+ },
45
63
 
46
64
  onRetry(error) {
47
65
  warn('retrying writing file', {
@@ -62,100 +80,103 @@ let S3Handler = (_dec = (0, _decorateWith.decorateWith)(_retry.default.wrap, {
62
80
  username,
63
81
  password,
64
82
  protocol,
65
- region
83
+ region = (0, _guessAwsRegion.default)(host)
66
84
  } = (0, _xoRemoteParser.parse)(remote.url);
67
- const params = {
68
- accessKeyId: username,
85
+ this._s3 = new _clientS.S3Client({
69
86
  apiVersion: '2006-03-01',
70
- endpoint: host,
71
- s3ForcePathStyle: true,
72
- secretAccessKey: password,
73
- signatureVersion: 'v4',
74
- httpOptions: {
75
- timeout: 600000
76
- }
77
- };
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
+ }
78
110
 
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
- }
111
+ get type() {
112
+ return 's3';
113
+ }
90
114
 
91
- if (region !== undefined) {
92
- params.region = region;
93
- }
115
+ _makeCopySource(path) {
116
+ return (0, _path.join)(this._bucket, this._dir, path);
117
+ }
94
118
 
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('/');
119
+ _makeKey(file) {
120
+ return (0, _path.join)(this._dir, file);
99
121
  }
100
122
 
101
- get type() {
102
- return 's3';
123
+ _makePrefix(dir) {
124
+ return (0, _path.join)(this._dir, dir, '/');
103
125
  }
104
126
 
105
127
  _createParams(file) {
106
128
  return {
107
129
  Bucket: this._bucket,
108
- Key: this._dir + file
130
+ Key: this._makeKey(file)
109
131
  };
110
132
  }
111
133
 
112
134
  async _multipartCopy(oldPath, newPath) {
113
135
  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
- };
136
+
137
+ const CopySource = this._makeCopySource(oldPath);
138
+
139
+ const multipartParams = await this._s3.send(new _clientS.CreateMultipartUploadCommand({ ...this._createParams(newPath)
140
+ }));
120
141
 
121
142
  try {
122
143
  const parts = [];
123
144
  let start = 0;
124
145
 
125
146
  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);
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
+ }));
132
153
  parts.push({
133
154
  ETag: upload.CopyPartResult.ETag,
134
- PartNumber: partParams.PartNumber
155
+ PartNumber: partNumber
135
156
  });
136
157
  start += MAX_PART_SIZE;
137
158
  }
138
159
 
139
- await this._s3.completeMultipartUpload({ ...multipartParams,
160
+ await this._s3.send(new _clientS.CompleteMultipartUploadCommand({ ...multipartParams,
140
161
  MultipartUpload: {
141
162
  Parts: parts
142
163
  }
143
- });
164
+ }));
144
165
  } catch (e) {
145
- await this._s3.abortMultipartUpload(multipartParams);
166
+ await this._s3.send(new _clientS.AbortMultipartUploadCommand(multipartParams));
146
167
  throw e;
147
168
  }
148
169
  }
149
170
 
150
171
  async _copy(oldPath, newPath) {
151
- const CopySource = `/${this._bucket}/${this._dir}${oldPath}`;
172
+ const CopySource = this._makeCopySource(oldPath);
152
173
 
153
174
  try {
154
- await this._s3.copyObject({ ...this._createParams(newPath),
175
+ await this._s3.send(new _clientS.CopyObjectCommand({ ...this._createParams(newPath),
155
176
  CopySource
156
- });
177
+ }));
157
178
  } catch (e) {
158
- if (e.code === 'EntityTooLarge') {
179
+ if (e.name === 'EntityTooLarge') {
159
180
  return this._multipartCopy(oldPath, newPath);
160
181
  }
161
182
 
@@ -164,20 +185,22 @@ let S3Handler = (_dec = (0, _decorateWith.decorateWith)(_retry.default.wrap, {
164
185
  }
165
186
 
166
187
  async _isNotEmptyDir(path) {
167
- const result = await this._s3.listObjectsV2({
188
+ var _result$Contents;
189
+
190
+ const result = await this._s3.send(new _clientS.ListObjectsV2Command({
168
191
  Bucket: this._bucket,
169
192
  MaxKeys: 1,
170
- Prefix: this._dir + path + '/'
171
- });
172
- 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;
173
196
  }
174
197
 
175
198
  async _isFile(path) {
176
199
  try {
177
- await this._s3.headObject(this._createParams(path));
200
+ await this._s3.send(new _clientS.HeadObjectCommand(this._createParams(path)));
178
201
  return true;
179
202
  } catch (error) {
180
- if (error.code === 'NotFound') {
203
+ if (error.name === 'NotFound') {
181
204
  return false;
182
205
  }
183
206
 
@@ -188,12 +211,17 @@ let S3Handler = (_dec = (0, _decorateWith.decorateWith)(_retry.default.wrap, {
188
211
  async _outputStream(path, input, {
189
212
  validator
190
213
  }) {
191
- await this._s3.upload({ ...this._createParams(path),
192
- Body: input
193
- }, {
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,
194
219
  partSize: IDEAL_FRAGMENT_SIZE,
195
- queueSize: 1
220
+ params: { ...this._createParams(path),
221
+ Body
222
+ }
196
223
  });
224
+ await upload.done();
197
225
 
198
226
  if (validator !== undefined) {
199
227
  try {
@@ -206,9 +234,9 @@ let S3Handler = (_dec = (0, _decorateWith.decorateWith)(_retry.default.wrap, {
206
234
  }
207
235
 
208
236
  async _writeFile(file, data, options) {
209
- return this._s3.putObject({ ...this._createParams(file),
237
+ return this._s3.send(new _clientS.PutObjectCommand({ ...this._createParams(file),
210
238
  Body: data
211
- });
239
+ }));
212
240
  }
213
241
 
214
242
  async _createReadStream(path, options) {
@@ -219,11 +247,11 @@ let S3Handler = (_dec = (0, _decorateWith.decorateWith)(_retry.default.wrap, {
219
247
  throw error;
220
248
  }
221
249
 
222
- return this._s3.getObject.raw(this._createParams(path)).createReadStream();
250
+ return (await this._s3.send(new _clientS.GetObjectCommand(this._createParams(path)))).Body;
223
251
  }
224
252
 
225
253
  async _unlink(path) {
226
- await this._s3.deleteObject(this._createParams(path));
254
+ await this._s3.send(new _clientS.DeleteObjectCommand(this._createParams(path)));
227
255
 
228
256
  if (await this._isNotEmptyDir(path)) {
229
257
  const error = new Error(`EISDIR: illegal operation on a directory, unlink '${path}'`);
@@ -234,37 +262,36 @@ let S3Handler = (_dec = (0, _decorateWith.decorateWith)(_retry.default.wrap, {
234
262
  }
235
263
 
236
264
  async _list(dir) {
237
- function splitPath(path) {
238
- return path.split('/').filter(d => d.length);
239
- }
265
+ let NextContinuationToken;
266
+ const uniq = new Set();
240
267
 
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
- });
268
+ const Prefix = this._makePrefix(dir);
248
269
 
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
- }
270
+ do {
271
+ const result = await this._s3.send(new _clientS.ListObjectsV2Command({
272
+ Bucket: this._bucket,
273
+ Prefix,
274
+ Delimiter: '/',
275
+ ContinuationToken: NextContinuationToken
276
+ }));
254
277
 
255
- 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
+ }
256
284
 
257
- for (const entry of result.CommonPrefixes) {
258
- const line = splitPath(entry.Prefix);
259
- uniq.push(line[line.length - 1]);
260
- }
285
+ for (const entry of result.CommonPrefixes ?? []) {
286
+ uniq.add((0, _path.basename)(entry.Prefix));
287
+ }
261
288
 
262
- for (const entry of result.Contents) {
263
- const line = splitPath(entry.Key);
264
- uniq.push(line[line.length - 1]);
265
- }
289
+ for (const entry of result.Contents ?? []) {
290
+ uniq.add((0, _path.basename)(entry.Key));
291
+ }
292
+ } while (NextContinuationToken !== undefined);
266
293
 
267
- return uniq;
294
+ return [...uniq];
268
295
  }
269
296
 
270
297
  async _mkdir(path) {
@@ -278,7 +305,7 @@ let S3Handler = (_dec = (0, _decorateWith.decorateWith)(_retry.default.wrap, {
278
305
 
279
306
  async _rename(oldPath, newPath) {
280
307
  await this.copy(oldPath, newPath);
281
- await this._s3.deleteObject(this._createParams(oldPath));
308
+ await this._s3.send(new _clientS.DeleteObjectCommand(this._createParams(oldPath)));
282
309
  }
283
310
 
284
311
  async _getSize(file) {
@@ -286,7 +313,7 @@ let S3Handler = (_dec = (0, _decorateWith.decorateWith)(_retry.default.wrap, {
286
313
  file = file.fd;
287
314
  }
288
315
 
289
- const result = await this._s3.headObject(this._createParams(file));
316
+ const result = await this._s3.send(new _clientS.HeadObjectCommand(this._createParams(file)));
290
317
  return +result.ContentLength;
291
318
  }
292
319
 
@@ -300,14 +327,14 @@ let S3Handler = (_dec = (0, _decorateWith.decorateWith)(_retry.default.wrap, {
300
327
  params.Range = `bytes=${position}-${position + buffer.length - 1}`;
301
328
 
302
329
  try {
303
- const result = await this._s3.getObject(params);
304
- 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);
305
332
  return {
306
- bytesRead: result.Body.length,
333
+ bytesRead,
307
334
  buffer
308
335
  };
309
336
  } catch (e) {
310
- if (e.code === 'NoSuchKey') {
337
+ if (e.name === 'NoSuchKey') {
311
338
  if (await this._isNotEmptyDir(file)) {
312
339
  const error = new Error(`${file} is a directory`);
313
340
  error.code = 'EISDIR';
@@ -332,20 +359,22 @@ let S3Handler = (_dec = (0, _decorateWith.decorateWith)(_retry.default.wrap, {
332
359
  async _rmtree(path) {
333
360
  let NextContinuationToken;
334
361
 
362
+ const Prefix = this._makePrefix(path);
363
+
335
364
  do {
336
- const result = await this._s3.listObjectsV2({
365
+ const result = await this._s3.send(new _clientS.ListObjectsV2Command({
337
366
  Bucket: this._bucket,
338
- Prefix: this._dir + path + '/',
367
+ Prefix,
339
368
  ContinuationToken: NextContinuationToken
340
- });
369
+ }));
341
370
  NextContinuationToken = result.IsTruncated ? result.NextContinuationToken : undefined;
342
- await (0, _asyncEach.asyncEach)(result.Contents, async ({
371
+ await (0, _asyncEach.asyncEach)(result.Contents ?? [], async ({
343
372
  Key
344
373
  }) => {
345
- await this._s3.deleteObject({
374
+ await this._s3.send(new _clientS.DeleteObjectCommand({
346
375
  Bucket: this._bucket,
347
376
  Key
348
- });
377
+ }));
349
378
  }, {
350
379
  concurrency: 16
351
380
  });
@@ -362,9 +391,9 @@ let S3Handler = (_dec = (0, _decorateWith.decorateWith)(_retry.default.wrap, {
362
391
  let fileSize;
363
392
 
364
393
  try {
365
- fileSize = +(await this._s3.headObject(uploadParams)).ContentLength;
394
+ fileSize = +(await this._s3.send(new _clientS.HeadObjectCommand(uploadParams))).ContentLength;
366
395
  } catch (e) {
367
- if (e.code === 'NotFound') {
396
+ if (e.name === 'NotFound') {
368
397
  fileSize = 0;
369
398
  } else {
370
399
  throw e;
@@ -373,20 +402,26 @@ let S3Handler = (_dec = (0, _decorateWith.decorateWith)(_retry.default.wrap, {
373
402
 
374
403
  if (fileSize < MIN_PART_SIZE) {
375
404
  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);
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
+
378
413
  buffer.copy(resultBuffer, position);
379
- await this._s3.putObject({ ...uploadParams,
414
+ await this._s3.send(new _clientS.PutObjectCommand({ ...uploadParams,
380
415
  Body: resultBuffer
381
- });
416
+ }));
382
417
  return {
383
418
  buffer,
384
419
  bytesWritten: buffer.length
385
420
  };
386
421
  } else {
387
- const multipartParams = await this._s3.createMultipartUpload(uploadParams);
422
+ const multipartParams = await this._s3.send(new _clientS.CreateMultipartUploadCommand(uploadParams));
388
423
  const copyMultipartParams = { ...multipartParams,
389
- CopySource: `/${this._bucket}/${this._dir + file}`
424
+ CopySource: this._makeCopySource(file)
390
425
  };
391
426
 
392
427
  try {
@@ -418,7 +453,7 @@ let S3Handler = (_dec = (0, _decorateWith.decorateWith)(_retry.default.wrap, {
418
453
  PartNumber: partNumber++,
419
454
  CopySourceRange: range
420
455
  };
421
- const part = await this._s3.uploadPartCopy(copyPrefixParams);
456
+ const part = await this._s3.send(new _clientS.UploadPartCopyCommand(copyPrefixParams));
422
457
  parts.push({
423
458
  ETag: part.CopyPartResult.ETag,
424
459
  PartNumber: copyPrefixParams.PartNumber
@@ -430,7 +465,15 @@ let S3Handler = (_dec = (0, _decorateWith.decorateWith)(_retry.default.wrap, {
430
465
  const downloadParams = { ...uploadParams,
431
466
  Range: `bytes=${prefixPosition}-${prefixSize - 1}`
432
467
  };
433
- 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
+
434
477
  editBuffer = Buffer.concat([prefixBuffer, buffer]);
435
478
  editBufferOffset -= prefixLastFragmentSize;
436
479
  }
@@ -445,7 +488,8 @@ let S3Handler = (_dec = (0, _decorateWith.decorateWith)(_retry.default.wrap, {
445
488
  const downloadParams = { ...uploadParams,
446
489
  Range: prefixRange
447
490
  };
448
- 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);
449
493
  editBuffer = Buffer.concat([editBuffer, complementBuffer]);
450
494
  }
451
495
 
@@ -453,7 +497,7 @@ let S3Handler = (_dec = (0, _decorateWith.decorateWith)(_retry.default.wrap, {
453
497
  Body: editBuffer,
454
498
  PartNumber: partNumber++
455
499
  };
456
- const editPart = await this._s3.uploadPart(editParams);
500
+ const editPart = await this._s3.send(new _clientS.UploadPartCommand(editParams));
457
501
  parts.push({
458
502
  ETag: editPart.ETag,
459
503
  PartNumber: editParams.PartNumber
@@ -473,7 +517,7 @@ let S3Handler = (_dec = (0, _decorateWith.decorateWith)(_retry.default.wrap, {
473
517
  PartNumber: partNumber++,
474
518
  CopySourceRange: suffixRange
475
519
  };
476
- const suffixPart = (await this._s3.uploadPartCopy(copySuffixParams)).CopyPartResult;
520
+ const suffixPart = (await this._s3.send(new _clientS.UploadPartCopyCommand(copySuffixParams))).CopyPartResult;
477
521
  parts.push({
478
522
  ETag: suffixPart.ETag,
479
523
  PartNumber: copySuffixParams.PartNumber
@@ -482,13 +526,13 @@ let S3Handler = (_dec = (0, _decorateWith.decorateWith)(_retry.default.wrap, {
482
526
  }
483
527
  }
484
528
 
485
- await this._s3.completeMultipartUpload({ ...multipartParams,
529
+ await this._s3.send(new _clientS.CompleteMultipartUploadCommand({ ...multipartParams,
486
530
  MultipartUpload: {
487
531
  Parts: parts
488
532
  }
489
- });
533
+ }));
490
534
  } catch (e) {
491
- await this._s3.abortMultipartUpload(multipartParams);
535
+ await this._s3.send(new _clientS.AbortMultipartUploadCommand(multipartParams));
492
536
  throw e;
493
537
  }
494
538
  }