From 619bdeb6aa8891745df73af1d573de0268956119 Mon Sep 17 00:00:00 2001 From: Mickael Bourgois Date: Fri, 13 Mar 2026 20:31:41 +0100 Subject: [PATCH 1/4] CLDSRV-835: Fix flaky async await provideRawOutput Fix concurrence with cleanup triggering 404 instead of 200 because the function was not awaited and triggered the afterEach immediately --- .../lib/utility/provideRawOutput.js | 6 +++++ .../aws-node-sdk/test/object/put.js | 23 +++++++++---------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/tests/functional/aws-node-sdk/lib/utility/provideRawOutput.js b/tests/functional/aws-node-sdk/lib/utility/provideRawOutput.js index 403d31777d..9e508f0c44 100644 --- a/tests/functional/aws-node-sdk/lib/utility/provideRawOutput.js +++ b/tests/functional/aws-node-sdk/lib/utility/provideRawOutput.js @@ -1,4 +1,5 @@ const cp = require('child_process'); +const util = require('util'); const conf = require('../../../../../lib/Config').config; @@ -44,4 +45,9 @@ function provideRawOutput(args, cb) { }); } +provideRawOutput[util.promisify.custom] = args => + new Promise(resolve => + provideRawOutput(args, (httpCode, rawOutput) => resolve({ httpCode, rawOutput })) + ); + module.exports = provideRawOutput; diff --git a/tests/functional/aws-node-sdk/test/object/put.js b/tests/functional/aws-node-sdk/test/object/put.js index c7910a14e6..ddd7d045e6 100644 --- a/tests/functional/aws-node-sdk/test/object/put.js +++ b/tests/functional/aws-node-sdk/test/object/put.js @@ -1,6 +1,7 @@ const assert = require('assert'); const fs = require('fs'); const path = require('path'); +const util = require('util'); const { CreateBucketCommand, PutObjectCommand, GetObjectAclCommand, @@ -11,6 +12,7 @@ const withV4 = require('../support/withV4'); const BucketUtility = require('../../lib/utility/bucket-util'); const checkError = require('../../lib/utility/checkError'); const provideRawOutput = require('../../lib/utility/provideRawOutput'); +const provideRawOutputAsync = util.promisify(provideRawOutput); const { taggingTests, generateMultipleTagQuery } = require('../../lib/utility/tagging'); const genMaxSizeMetaHeaders @@ -54,18 +56,15 @@ describe('PUT object', () => { const command = new PutObjectCommand(params); const url = await getSignedUrl(s3, command); - provideRawOutput(['-verbose', '-X', 'PUT', url, - '--upload-file', tempFile], httpCode => { - fs.unlinkSync(tempFile); - assert.strictEqual(httpCode, '200 OK'); - s3.send(new GetObjectAclCommand({ Bucket: bucket, Key: 'key' })) - .then(result => { - assert.deepStrictEqual(result.Grants[1], { Grantee: - { Type: 'Group', URI: - 'http://acs.amazonaws.com/groups/global/AllUsers', - }, Permission: 'READ' }); - }); - }); + const { httpCode } = await provideRawOutputAsync(['-verbose', '-X', 'PUT', url, + '--upload-file', tempFile]); + fs.unlinkSync(tempFile); + assert.strictEqual(httpCode, '200 OK'); + const result = await s3.send(new GetObjectAclCommand({ Bucket: bucket, Key: 'key' })); + assert.deepStrictEqual(result.Grants[1], { Grantee: + { Type: 'Group', URI: + 'http://acs.amazonaws.com/groups/global/AllUsers', + }, Permission: 'READ' }); }); it('should put an object with key slash', From 9471b0cadbe464e44789715cb0737cd599f9eba5 Mon Sep 17 00:00:00 2001 From: Mickael Bourgois Date: Fri, 13 Mar 2026 20:31:41 +0100 Subject: [PATCH 2/4] CLDSRV-835: Normalize async provideRawOutput --- .../test/legacy/authV2QueryTests.js | 74 ++++-------- .../test/legacy/authV4QueryTests.js | 109 ++++++------------ 2 files changed, 60 insertions(+), 123 deletions(-) diff --git a/tests/functional/aws-node-sdk/test/legacy/authV2QueryTests.js b/tests/functional/aws-node-sdk/test/legacy/authV2QueryTests.js index 90eb2f9343..683857f1d7 100644 --- a/tests/functional/aws-node-sdk/test/legacy/authV2QueryTests.js +++ b/tests/functional/aws-node-sdk/test/legacy/authV2QueryTests.js @@ -1,6 +1,8 @@ const assert = require('assert'); const process = require('node:process'); const cp = require('child_process'); +const util = require('util'); +const timers = require('timers/promises'); const { S3Client, CreateBucketCommand, @@ -12,6 +14,7 @@ const { const { getSignedUrl } = require('@aws-sdk/s3-request-presigner'); const getConfig = require('../support/config'); const provideRawOutput = require('../../lib/utility/provideRawOutput'); +const provideRawOutputAsync = util.promisify(provideRawOutput); const random = Math.round(Math.random() * 100).toString(); const bucket = `mybucket-${random}`; @@ -60,49 +63,32 @@ describe('aws-node-sdk v2auth query tests', function testSuite() { const futureExpiry = Math.floor(Date.now() / 1000) + 604810; // 10 seconds more than limit urlObj.searchParams.set('Expires', futureExpiry.toString()); const invalidUrl = urlObj.toString(); - await new Promise(resolve => { - provideRawOutput(['-verbose', '-X', 'PUT', invalidUrl], httpCode => { - assert.strictEqual(httpCode, '403 FORBIDDEN'); - resolve(); - }); - }); + const { httpCode } = await provideRawOutputAsync(['-verbose', '-X', 'PUT', invalidUrl]); + assert.strictEqual(httpCode, '403 FORBIDDEN'); }); it('should return an error code if request occurs after expiry', async () => { const command = new CreateBucketCommand({ Bucket: bucket }); const url = await getSignedUrl(s3, command, { expiresIn: 1 }); - await new Promise(resolve => { - setTimeout(() => { - provideRawOutput(['-verbose', '-X', 'PUT', url], httpCode => { - assert.strictEqual(httpCode, '403 FORBIDDEN'); - resolve(); - }); - }, 1500); - }); + await timers.setTimeout(1500); + const { httpCode } = await provideRawOutputAsync(['-verbose', '-X', 'PUT', url]); + assert.strictEqual(httpCode, '403 FORBIDDEN'); }); it('should create a bucket', async () => { const command = new CreateBucketCommand({ Bucket: bucket }); const url = await getSignedUrl(s3, command, { expiresIn: almostOutsideTime }); - await new Promise(resolve => { - provideRawOutput(['-verbose', '-X', 'PUT', url], httpCode => { - assert.strictEqual(httpCode, '200 OK'); - resolve(); - }); - }); + const { httpCode } = await provideRawOutputAsync(['-verbose', '-X', 'PUT', url]); + assert.strictEqual(httpCode, '200 OK'); }); it('should put an object', async () => { const command = new PutObjectCommand({ Bucket: bucket, Key: 'key' }); const url = await getSignedUrl(s3, command, { expiresIn: almostOutsideTime }); - await new Promise(resolve => { - provideRawOutput(['-verbose', '-X', 'PUT', url, - '--upload-file', 'uploadFile'], httpCode => { - assert.strictEqual(httpCode, '200 OK'); - resolve(); - }); - }); + const { httpCode } = await provideRawOutputAsync(['-verbose', '-X', 'PUT', url, + '--upload-file', 'uploadFile']); + assert.strictEqual(httpCode, '200 OK'); }); it('should put an object with an acl setting and a storage class setting', async () => { @@ -118,25 +104,17 @@ describe('aws-node-sdk v2auth query tests', function testSuite() { StorageClass: 'STANDARD' }); const url = await getSignedUrl(s3, command); - await new Promise(resolve => { - provideRawOutput(['-verbose', '-X', 'PUT', url, - '--upload-file', 'uploadFile'], httpCode => { - assert.strictEqual(httpCode, '200 OK'); - resolve(); - }); - }); + const { httpCode } = await provideRawOutputAsync(['-verbose', '-X', 'PUT', url, + '--upload-file', 'uploadFile']); + assert.strictEqual(httpCode, '200 OK'); }); it('should get an object', async () => { const command = new GetObjectCommand({ Bucket: bucket, Key: 'key' }); const url = await getSignedUrl(s3, command, { expiresIn: almostOutsideTime }); - await new Promise(resolve => { - provideRawOutput(['-verbose', '-o', 'download', url], httpCode => { - assert.strictEqual(httpCode, '200 OK'); - resolve(); - }); - }); + const { httpCode } = await provideRawOutputAsync(['-verbose', '-o', 'download', url]); + assert.strictEqual(httpCode, '200 OK'); }); it('downloaded file should equal file that was put', done => { @@ -148,23 +126,15 @@ describe('aws-node-sdk v2auth query tests', function testSuite() { it('should delete an object', async () => { const command = new DeleteObjectCommand({ Bucket: bucket, Key: 'key' }); const url = await getSignedUrl(s3, command, { expiresIn: almostOutsideTime }); - await new Promise(resolve => { - provideRawOutput(['-verbose', '-X', 'DELETE', url], httpCode => { - assert.strictEqual(httpCode, '204 NO CONTENT'); - resolve(); - }); - }); + const { httpCode } = await provideRawOutputAsync(['-verbose', '-X', 'DELETE', url]); + assert.strictEqual(httpCode, '204 NO CONTENT'); }); it('should delete a bucket', async () => { const command = new DeleteBucketCommand({ Bucket: bucket }); const url = await getSignedUrl(s3, command, { expiresIn: almostOutsideTime }); - await new Promise(resolve => { - provideRawOutput(['-verbose', '-X', 'DELETE', url], httpCode => { - assert.strictEqual(httpCode, '204 NO CONTENT'); - resolve(); - }); - }); + const { httpCode } = await provideRawOutputAsync(['-verbose', '-X', 'DELETE', url]); + assert.strictEqual(httpCode, '204 NO CONTENT'); }); }); diff --git a/tests/functional/aws-node-sdk/test/legacy/authV4QueryTests.js b/tests/functional/aws-node-sdk/test/legacy/authV4QueryTests.js index 271c3a8de1..23df1352cd 100644 --- a/tests/functional/aws-node-sdk/test/legacy/authV4QueryTests.js +++ b/tests/functional/aws-node-sdk/test/legacy/authV4QueryTests.js @@ -1,7 +1,9 @@ const assert = require('assert'); const process = require('node:process'); const cp = require('child_process'); +const util = require('util'); const { parseString } = require('xml2js'); +const parseStringAsync = util.promisify(parseString); const { S3Client, @@ -16,6 +18,7 @@ const { const { getSignedUrl } = require('@aws-sdk/s3-request-presigner'); const getConfig = require('../support/config'); const provideRawOutput = require('../../lib/utility/provideRawOutput'); +const provideRawOutputAsync = util.promisify(provideRawOutput); const random = Math.round(Math.random() * 100).toString(); const bucket = `mybucket-${random}`; @@ -46,10 +49,8 @@ describe('aws-node-sdk v4auth query tests', function testSuite() { it('should do an empty bucket listing', async () => { const url = await getSignedUrl(s3, new ListBucketsCommand({}), { expiresIn: 900 }); - await new Promise(resolve => provideRawOutput(['-verbose', url], httpCode => { - assert.strictEqual(httpCode, '200 OK'); - resolve(); - })); + const { httpCode } = await provideRawOutputAsync(['-verbose', url]); + assert.strictEqual(httpCode, '200 OK'); }); it('should create a bucket', async () => { @@ -59,37 +60,26 @@ describe('aws-node-sdk v4auth query tests', function testSuite() { new CreateBucketCommand(params), { expiresIn: 900 } ); - await new Promise(resolve => provideRawOutput(['-verbose', '-X', 'PUT', url], httpCode => { - assert.strictEqual(httpCode, '200 OK'); - resolve(); - })); + const { httpCode } = await provideRawOutputAsync(['-verbose', '-X', 'PUT', url]); + assert.strictEqual(httpCode, '200 OK'); }); it('should do a bucket listing with result', async () => { const url = await getSignedUrl(s3, new ListBucketsCommand({}), { expiresIn: 900 }); - await new Promise(resolve => provideRawOutput(['-verbose', url], (httpCode, rawOutput) => { - assert.strictEqual(httpCode, '200 OK'); - parseString(rawOutput.stdout, (err, xml) => { - if (err) { - assert.ifError(err); - } - const bucketNames = xml.ListAllMyBucketsResult - .Buckets[0].Bucket.map(item => item.Name[0]); - const whereIsMyBucket = bucketNames.indexOf(bucket); - assert(whereIsMyBucket > -1); - resolve(); - }); - })); + const { httpCode, rawOutput } = await provideRawOutputAsync(['-verbose', url]); + assert.strictEqual(httpCode, '200 OK'); + const xml = await parseStringAsync(rawOutput.stdout); + const bucketNames = xml.ListAllMyBucketsResult + .Buckets[0].Bucket.map(item => item.Name[0]); + assert(bucketNames.indexOf(bucket) > -1); }); it('should put an object', async () => { const params = { Bucket: bucket, Key: 'key' }; const url = await getSignedUrl(s3, new PutObjectCommand(params), { expiresIn: 900 }); - await new Promise(resolve => provideRawOutput(['-verbose', '-X', 'PUT', url, - '--upload-file', 'uploadFile'], httpCode => { - assert.strictEqual(httpCode, '200 OK'); - resolve(); - })); + const { httpCode } = await provideRawOutputAsync(['-verbose', '-X', 'PUT', url, + '--upload-file', 'uploadFile']); + assert.strictEqual(httpCode, '200 OK'); }); it('should put an object with an acl setting and a storage class setting', async () => { @@ -101,11 +91,9 @@ describe('aws-node-sdk v4auth query tests', function testSuite() { ContentType: 'text/plain', }; const url = await getSignedUrl(s3, new PutObjectCommand(params), { expiresIn: 900 }); - await new Promise(resolve => provideRawOutput(['-verbose', '-X', 'PUT', url, - '--upload-file', 'uploadFile'], httpCode => { - assert.strictEqual(httpCode, '200 OK'); - resolve(); - })); + const { httpCode } = await provideRawOutputAsync(['-verbose', '-X', 'PUT', url, + '--upload-file', 'uploadFile']); + assert.strictEqual(httpCode, '200 OK'); }); it('should put an object with native characters', async () => { @@ -113,38 +101,27 @@ describe('aws-node-sdk v4auth query tests', function testSuite() { 'português-বাংলা-русский-日本語-ਪੰਜਾਬੀ-한국어-தமிழ்'; const params = { Bucket: bucket, Key }; const url = await getSignedUrl(s3, new PutObjectCommand(params), { expiresIn: 900 }); - await new Promise(resolve => provideRawOutput(['-verbose', '-X', 'PUT', url, - '--upload-file', 'uploadFile'], httpCode => { - assert.strictEqual(httpCode, '200 OK'); - resolve(); - })); + const { httpCode } = await provideRawOutputAsync(['-verbose', '-X', 'PUT', url, + '--upload-file', 'uploadFile']); + assert.strictEqual(httpCode, '200 OK'); }); // listObjects test it('should list objects in bucket', async () => { const params = { Bucket: bucket }; const url = await getSignedUrl(s3, new ListObjectsCommand(params), { expiresIn: 900 }); - await new Promise(resolve => provideRawOutput(['-verbose', url], (httpCode, rawOutput) => { - assert.strictEqual(httpCode, '200 OK'); - parseString(rawOutput.stdout, (err, result) => { - if (err) { - assert.ifError(err); - } - assert.strictEqual(result.ListBucketResult - .Contents[0].Key[0], 'key'); - resolve(); - }); - })); + const { httpCode, rawOutput } = await provideRawOutputAsync(['-verbose', url]); + assert.strictEqual(httpCode, '200 OK'); + const result = await parseStringAsync(rawOutput.stdout); + assert.strictEqual(result.ListBucketResult.Contents[0].Key[0], 'key'); }); // getObject test it('should get an object', async () => { const params = { Bucket: bucket, Key: 'key' }; const url = await getSignedUrl(s3, new GetObjectCommand(params), { expiresIn: 900 }); - await new Promise(resolve => provideRawOutput(['-verbose', '-o', 'download', url], httpCode => { - assert.strictEqual(httpCode, '200 OK'); - resolve(); - })); + const { httpCode } = await provideRawOutputAsync(['-verbose', '-o', 'download', url]); + assert.strictEqual(httpCode, '200 OK'); }); it('downloaded file should equal file that was put', done => { @@ -157,28 +134,22 @@ describe('aws-node-sdk v4auth query tests', function testSuite() { it('should delete an object', async () => { const params = { Bucket: bucket, Key: 'key' }; const url = await getSignedUrl(s3, new DeleteObjectCommand(params), { expiresIn: 900 }); - await new Promise(resolve => provideRawOutput(['-verbose', '-X', 'DELETE', url], httpCode => { - assert.strictEqual(httpCode, '204 NO CONTENT'); - resolve(); - })); + const { httpCode } = await provideRawOutputAsync(['-verbose', '-X', 'DELETE', url]); + assert.strictEqual(httpCode, '204 NO CONTENT'); }); it('should return a 204 on delete of an already deleted object', async () => { const params = { Bucket: bucket, Key: 'key' }; const url = await getSignedUrl(s3, new DeleteObjectCommand(params), { expiresIn: 900 }); - await new Promise(resolve => provideRawOutput(['-verbose', '-X', 'DELETE', url], httpCode => { - assert.strictEqual(httpCode, '204 NO CONTENT'); - resolve(); - })); + const { httpCode } = await provideRawOutputAsync(['-verbose', '-X', 'DELETE', url]); + assert.strictEqual(httpCode, '204 NO CONTENT'); }); it('should return 204 on delete of non-existing object', async () => { const params = { Bucket: bucket, Key: 'randomObject' }; const url = await getSignedUrl(s3, new DeleteObjectCommand(params), { expiresIn: 900 }); - await new Promise(resolve => provideRawOutput(['-verbose', '-X', 'DELETE', url], httpCode => { - assert.strictEqual(httpCode, '204 NO CONTENT'); - resolve(); - })); + const { httpCode } = await provideRawOutputAsync(['-verbose', '-X', 'DELETE', url]); + assert.strictEqual(httpCode, '204 NO CONTENT'); }); it('should delete an object with native characters', async () => { @@ -186,18 +157,14 @@ describe('aws-node-sdk v4auth query tests', function testSuite() { 'português-বাংলা-русский-日本語-ਪੰਜਾਬੀ-한국어-தமிழ்'; const params = { Bucket: bucket, Key }; const url = await getSignedUrl(s3, new DeleteObjectCommand(params), { expiresIn: 900 }); - await new Promise(resolve => provideRawOutput(['-verbose', '-X', 'DELETE', url], httpCode => { - assert.strictEqual(httpCode, '204 NO CONTENT'); - resolve(); - })); + const { httpCode } = await provideRawOutputAsync(['-verbose', '-X', 'DELETE', url]); + assert.strictEqual(httpCode, '204 NO CONTENT'); }); it('should delete a bucket', async () => { const params = { Bucket: bucket }; const url = await getSignedUrl(s3, new DeleteBucketCommand(params), { expiresIn: 900 }); - await new Promise(resolve => provideRawOutput(['-verbose', '-X', 'DELETE', url], httpCode => { - assert.strictEqual(httpCode, '204 NO CONTENT'); - resolve(); - })); + const { httpCode } = await provideRawOutputAsync(['-verbose', '-X', 'DELETE', url]); + assert.strictEqual(httpCode, '204 NO CONTENT'); }); }); From 12097a6344e7a740dcf7e5ee291f5a735f04fbaf Mon Sep 17 00:00:00 2001 From: Mickael Bourgois Date: Fri, 13 Mar 2026 20:31:41 +0100 Subject: [PATCH 3/4] CLDSRV-835: Isolate bucket name for put object To avoid cascading issues between describe and avoid BucketAlreadyExists --- tests/functional/aws-node-sdk/test/object/put.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/functional/aws-node-sdk/test/object/put.js b/tests/functional/aws-node-sdk/test/object/put.js index ddd7d045e6..35fb245f69 100644 --- a/tests/functional/aws-node-sdk/test/object/put.js +++ b/tests/functional/aws-node-sdk/test/object/put.js @@ -19,10 +19,10 @@ const genMaxSizeMetaHeaders = require('../../lib/utility/genMaxSizeMetaHeaders'); const changeObjectLock = require('../../../../utilities/objectLock-util'); -const bucket = 'bucket2putstuffin4324242'; const object = 'object2putstuffin'; describe('PUT object', () => { + const bucket = 'bucket2putstuffin4324242'; withV4(sigCfg => { let bucketUtil; let s3; @@ -317,6 +317,7 @@ const isCEPH = process.env.CI_CEPH !== undefined; const describeSkipIfCeph = isCEPH ? describe.skip : describe; describeSkipIfCeph('PUT object with object lock', () => { + const bucket = 'bucket2putstuffin4324242-lock'; withV4(sigCfg => { let bucketUtil; let s3; From 3a5f77ada06e20e069e13b74d556965a3a416c44 Mon Sep 17 00:00:00 2001 From: Mickael Bourgois Date: Fri, 13 Mar 2026 20:31:41 +0100 Subject: [PATCH 4/4] CLDSRV-835: Improve bucket utility empty function No need to separate trailing slash keys. Use multi object delete instead of parallel single delete List all pagination Keep a fall back to individual deletes action for sse kms migration tests that use an older cloudserver version before migration --- .../aws-node-sdk/lib/utility/bucket-util.js | 90 +++++++++++-------- 1 file changed, 54 insertions(+), 36 deletions(-) diff --git a/tests/functional/aws-node-sdk/lib/utility/bucket-util.js b/tests/functional/aws-node-sdk/lib/utility/bucket-util.js index b02802bf4e..f214540e30 100644 --- a/tests/functional/aws-node-sdk/lib/utility/bucket-util.js +++ b/tests/functional/aws-node-sdk/lib/utility/bucket-util.js @@ -4,6 +4,7 @@ const { CreateBucketCommand, DeleteBucketCommand, ListObjectVersionsCommand, + DeleteObjectsCommand, DeleteObjectCommand, ListBucketsCommand, } = require('@aws-sdk/client-s3'); @@ -90,48 +91,65 @@ class BucketUtility { * @param bucketName * @returns {Promise.} */ - empty(bucketName, BypassGovernanceRetention = false) { - const param = { - Bucket: bucketName, - }; - - return this.s3.send(new ListObjectVersionsCommand(param)) - .then(data => Promise.all( - (data.Versions || []) - .filter(object => !object.Key.endsWith('/')) - .map(object => - this.s3.send(new DeleteObjectCommand({ + async empty(bucketName, BypassGovernanceRetention = false) { + let keyMarker = undefined; + let versionIdMarker = undefined; + let isTruncated = true; + + while (isTruncated) { + const response = await this.s3.send(new ListObjectVersionsCommand({ + Bucket: bucketName, + KeyMarker: keyMarker, + VersionIdMarker: versionIdMarker, + })); + + const objects = [ + ...(response.Versions || []), + ...(response.DeleteMarkers || []), + ].map(({ Key, VersionId }) => ({ Key, VersionId })); + + if (objects.length > 0) { + try { + const result = await this.s3.send(new DeleteObjectsCommand({ Bucket: bucketName, - Key: object.Key, - VersionId: object.VersionId, + Delete: { + Objects: objects, + Quiet: true + }, ...(BypassGovernanceRetention && { BypassGovernanceRetention }), - })).then(() => object) - ) - .concat((data.Versions || []) - .filter(object => object.Key.endsWith('/')) - .map(object => + })); + if (result.Errors && result.Errors.length > 0) { + for (const e of result.Errors) { + // eslint-disable-next-line no-console + console.warn( + `Warning BucketUtility.empty(): failed to delete s3://${bucketName}/${e.Key}` + + ` (versionId=${e.VersionId}): ${e.Code} - ${e.Message}` + ); + } + } + } catch (err) { + // Older cloudserver versions reject DeleteObjects with BadDigest + // due to a Content-MD5 integrity check issue. Fall back to individual deletes. + if (err.name !== 'BadDigest') { + throw err; + } + await Promise.all(objects.map(({ Key, VersionId }) => this.s3.send(new DeleteObjectCommand({ Bucket: bucketName, - Key: object.Key, - VersionId: object.VersionId, + Key, + VersionId, ...(BypassGovernanceRetention && { BypassGovernanceRetention }), })) - .then(() => object) - ) - ) - .concat((data.DeleteMarkers || []) - .map(object => - this.s3.send(new DeleteObjectCommand({ - Bucket: bucketName, - Key: object.Key, - VersionId: object.VersionId, - ...(BypassGovernanceRetention && { BypassGovernanceRetention }), - })) - .then(() => object) - ) - ) - ) - ); + )); + } + } + + isTruncated = response.IsTruncated; + if (isTruncated) { + keyMarker = response.NextKeyMarker; + versionIdMarker = response.NextVersionIdMarker; + } + } } emptyMany(bucketNames) {