From e7418879ab4638d38e3853eb7e4687a2d0e1e911 Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Mon, 9 Mar 2026 17:31:06 +0100 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=85=20add=20Github=20ACTION=20id=20in?= =?UTF-8?q?=20the=20bucket=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: CLDSRV-867 --- package.json | 1 + tests/functional/raw-node/test/GCP/bucket/get.js | 4 ++-- .../raw-node/test/GCP/bucket/getVersioning.js | 4 ++-- tests/functional/raw-node/test/GCP/bucket/head.js | 6 +++--- .../raw-node/test/GCP/bucket/putVersioning.js | 4 ++-- .../functional/raw-node/test/GCP/object/completeMpu.js | 5 +++-- tests/functional/raw-node/test/GCP/object/copy.js | 4 ++-- tests/functional/raw-node/test/GCP/object/delete.js | 4 ++-- tests/functional/raw-node/test/GCP/object/deleteMpu.js | 6 +++--- .../raw-node/test/GCP/object/deleteTagging.js | 4 ++-- tests/functional/raw-node/test/GCP/object/get.js | 4 ++-- .../functional/raw-node/test/GCP/object/getTagging.js | 4 ++-- tests/functional/raw-node/test/GCP/object/head.js | 4 ++-- .../functional/raw-node/test/GCP/object/initiateMpu.js | 5 +++-- tests/functional/raw-node/test/GCP/object/put.js | 4 ++-- .../functional/raw-node/test/GCP/object/putTagging.js | 4 ++-- tests/functional/raw-node/test/GCP/object/upload.js | 6 +++--- tests/functional/raw-node/utils/gcpUtils.js | 10 ++++++++-- 18 files changed, 46 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index 635b0791b6..be9c7ad9ca 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,7 @@ "ft_node_routes": "cd tests/functional/raw-node && yarn run test-routes", "ft_route_backbeat": "cd tests/multipleBackend/routes && mocha --reporter mocha-multi-reporters --reporter-options configFile=$INIT_CWD/tests/reporter-config.json -t 40000 routeBackbeat.js routeBackbeatForReplication.js --exit", "ft_gcp": "cd tests/functional/raw-node && yarn run test-gcp", + "cleanup_gcp_buckets": "node scripts/cleanupOldGCPBuckets.js", "ft_healthchecks": "cd tests/functional/healthchecks && yarn test", "ft_s3cmd": "cd tests/functional/s3cmd && mocha --reporter mocha-multi-reporters --reporter-options configFile=$INIT_CWD/tests/reporter-config.json -t 40000 *.js --exit", "ft_s3curl": "cd tests/functional/s3curl && mocha --reporter mocha-multi-reporters --reporter-options configFile=$INIT_CWD/tests/reporter-config.json -t 40000 *.js --exit", diff --git a/tests/functional/raw-node/test/GCP/bucket/get.js b/tests/functional/raw-node/test/GCP/bucket/get.js index f537d3c7df..c3491f593c 100644 --- a/tests/functional/raw-node/test/GCP/bucket/get.js +++ b/tests/functional/raw-node/test/GCP/bucket/get.js @@ -10,13 +10,13 @@ const { DeleteObjectCommand, } = require('@aws-sdk/client-s3'); const { GCP } = arsenal.storage.data.external.GCP; -const { genUniqID, gcpRetry } = require('../../../utils/gcpUtils'); +const { genUniqID, genBucketName, gcpRetry } = require('../../../utils/gcpUtils'); const { getRealAwsConfig } = require('../../../../aws-node-sdk/test/support/awsConfig'); const { listingHardLimit } = require('../../../../../../constants'); const credentialOne = 'gcpbackend'; -const bucketName = `cldsrvci-get-${genUniqID()}`; +const bucketName = genBucketName('get'); const smallSize = 20; const bigSize = listingHardLimit + 1; const config = getRealAwsConfig(credentialOne); diff --git a/tests/functional/raw-node/test/GCP/bucket/getVersioning.js b/tests/functional/raw-node/test/GCP/bucket/getVersioning.js index 5c39c3a273..2cbb852020 100644 --- a/tests/functional/raw-node/test/GCP/bucket/getVersioning.js +++ b/tests/functional/raw-node/test/GCP/bucket/getVersioning.js @@ -7,7 +7,7 @@ const { DeleteBucketCommand, } = require('@aws-sdk/client-s3'); const { GCP } = arsenal.storage.data.external.GCP; -const { genUniqID, gcpRetry } = require('../../../utils/gcpUtils'); +const { genBucketName, gcpRetry } = require('../../../utils/gcpUtils'); const { getRealAwsConfig } = require('../../../../aws-node-sdk/test/support/awsConfig'); @@ -20,7 +20,7 @@ describe('GCP: GET Bucket Versioning', () => { const gcpClient = new GCP(config); beforeEach(async function beforeFn() { - this.currentTest.bucketName = `cldsrvci-getversioning-${genUniqID()}`; + this.currentTest.bucketName = genBucketName('getversioning'); await gcpRetry( gcpClient, new CreateBucketCommand({ diff --git a/tests/functional/raw-node/test/GCP/bucket/head.js b/tests/functional/raw-node/test/GCP/bucket/head.js index 3056cbcbea..32b8809603 100644 --- a/tests/functional/raw-node/test/GCP/bucket/head.js +++ b/tests/functional/raw-node/test/GCP/bucket/head.js @@ -1,7 +1,7 @@ const assert = require('assert'); const arsenal = require('arsenal'); const { GCP } = arsenal.storage.data.external.GCP; -const { genUniqID, gcpRetry } = require('../../../utils/gcpUtils'); +const { genBucketName, gcpRetry } = require('../../../utils/gcpUtils'); const { getRealAwsConfig } = require('../../../../aws-node-sdk/test/support/awsConfig'); const { @@ -17,7 +17,7 @@ describe('GCP: HEAD Bucket', () => { describe('without existing bucket', () => { beforeEach(async function beforeFn() { - this.currentTest.bucketName = `cldsrvci-head-${genUniqID()}`; + this.currentTest.bucketName = genBucketName('head'); }); it('should return 404', async function testFn() { @@ -37,7 +37,7 @@ describe('GCP: HEAD Bucket', () => { describe('with existing bucket', () => { beforeEach(async function beforeFn() { - this.currentTest.bucketName = `cldsrvci-head-${genUniqID()}`; + this.currentTest.bucketName = genBucketName('head'); process.stdout .write(`Creating test bucket ${this.currentTest.bucketName}\n`); await gcpRetry( diff --git a/tests/functional/raw-node/test/GCP/bucket/putVersioning.js b/tests/functional/raw-node/test/GCP/bucket/putVersioning.js index 6999dcd1b6..d9d4955984 100644 --- a/tests/functional/raw-node/test/GCP/bucket/putVersioning.js +++ b/tests/functional/raw-node/test/GCP/bucket/putVersioning.js @@ -7,14 +7,14 @@ const { DeleteBucketCommand, } = require('@aws-sdk/client-s3'); const { GCP } = arsenal.storage.data.external.GCP; -const { genUniqID, gcpRetry } = require('../../../utils/gcpUtils'); +const { genBucketName, gcpRetry } = require('../../../utils/gcpUtils'); const { getRealAwsConfig } = require('../../../../aws-node-sdk/test/support/awsConfig'); const credentialOne = 'gcpbackend'; const verEnabledStatus = 'Enabled'; const verDisabledStatus = 'Suspended'; -const bucketName = `cldsrvci-putversioning-${genUniqID()}`; +const bucketName = genBucketName('putversioning'); describe('GCP: PUT Bucket Versioning', () => { const config = getRealAwsConfig(credentialOne); diff --git a/tests/functional/raw-node/test/GCP/object/completeMpu.js b/tests/functional/raw-node/test/GCP/object/completeMpu.js index 109c4a2ee8..c839985ffa 100644 --- a/tests/functional/raw-node/test/GCP/object/completeMpu.js +++ b/tests/functional/raw-node/test/GCP/object/completeMpu.js @@ -7,6 +7,7 @@ const { GCP, GcpUtils } = arsenal.storage.data.external.GCP; const { gcpMpuSetup, genUniqID, + genBucketName, gcpRetry, waitForBucketReady, } = require('../../../utils/gcpUtils'); @@ -20,11 +21,11 @@ const { const credentialOne = 'gcpbackend'; const bucketNames = { main: { - Name: `cldsrvci-completempu-${genUniqID()}`, + Name: genBucketName('completempu'), Type: 'MULTI_REGIONAL', }, mpu: { - Name: `cldsrvci-mpu-completempu-${genUniqID()}`, + Name: genBucketName('mpu-completempu'), Type: 'MULTI_REGIONAL', }, }; diff --git a/tests/functional/raw-node/test/GCP/object/copy.js b/tests/functional/raw-node/test/GCP/object/copy.js index ac3a3a3859..06eac31a43 100644 --- a/tests/functional/raw-node/test/GCP/object/copy.js +++ b/tests/functional/raw-node/test/GCP/object/copy.js @@ -2,7 +2,7 @@ const assert = require('assert'); const async = require('async'); const arsenal = require('arsenal'); const { GCP } = arsenal.storage.data.external.GCP; -const { genUniqID, gcpRetry } = require('../../../utils/gcpUtils'); +const { genUniqID, genBucketName, gcpRetry } = require('../../../utils/gcpUtils'); const { getRealAwsConfig } = require('../../../../aws-node-sdk/test/support/awsConfig'); const { @@ -12,7 +12,7 @@ const { } = require('@aws-sdk/client-s3'); const credentialOne = 'gcpbackend'; -const bucketName = `cldsrvci-copy-${genUniqID()}`; +const bucketName = genBucketName('copy'); describe('GCP: COPY Object', function testSuite() { this.timeout(180000); diff --git a/tests/functional/raw-node/test/GCP/object/delete.js b/tests/functional/raw-node/test/GCP/object/delete.js index 477d8a57d1..06bba2d2d9 100644 --- a/tests/functional/raw-node/test/GCP/object/delete.js +++ b/tests/functional/raw-node/test/GCP/object/delete.js @@ -2,7 +2,7 @@ const assert = require('assert'); const async = require('async'); const arsenal = require('arsenal'); const { GCP } = arsenal.storage.data.external.GCP; -const { genUniqID, gcpRetry } = require('../../../utils/gcpUtils'); +const { genUniqID, genBucketName, gcpRetry } = require('../../../utils/gcpUtils'); const { getRealAwsConfig } = require('../../../../aws-node-sdk/test/support/awsConfig'); const { @@ -13,7 +13,7 @@ const { } = require('@aws-sdk/client-s3'); const credentialOne = 'gcpbackend'; -const bucketName = `cldsrvci-deleteobj-${genUniqID()}`; +const bucketName = genBucketName('deleteobj'); const objectKey = `somekey-${genUniqID()}`; const badObjectKey = `nonexistingkey-${genUniqID()}`; diff --git a/tests/functional/raw-node/test/GCP/object/deleteMpu.js b/tests/functional/raw-node/test/GCP/object/deleteMpu.js index 6762faefde..597fdd6228 100644 --- a/tests/functional/raw-node/test/GCP/object/deleteMpu.js +++ b/tests/functional/raw-node/test/GCP/object/deleteMpu.js @@ -2,7 +2,7 @@ const assert = require('assert'); const async = require('async'); const arsenal = require('arsenal'); const { GCP } = arsenal.storage.data.external.GCP; -const { gcpMpuSetup, genUniqID, gcpRetry, waitForBucketReady } = +const { gcpMpuSetup, genUniqID, genBucketName, gcpRetry, waitForBucketReady } = require('../../../utils/gcpUtils'); const { getRealAwsConfig } = require('../../../../aws-node-sdk/test/support/awsConfig'); @@ -15,10 +15,10 @@ const { const credentialOne = 'gcpbackend'; const bucketNames = { main: { - Name: `cldsrvci-deletempu-${genUniqID()}`, + Name: genBucketName('deletempu'), }, mpu: { - Name: `cldsrvci-mpu-deletempu-${genUniqID()}`, + Name: genBucketName('mpu-deletempu'), }, }; const numParts = 10; diff --git a/tests/functional/raw-node/test/GCP/object/deleteTagging.js b/tests/functional/raw-node/test/GCP/object/deleteTagging.js index 7ad44da3c9..1647d8d6a6 100644 --- a/tests/functional/raw-node/test/GCP/object/deleteTagging.js +++ b/tests/functional/raw-node/test/GCP/object/deleteTagging.js @@ -2,7 +2,7 @@ const assert = require('assert'); const async = require('async'); const arsenal = require('arsenal'); const { GCP } = arsenal.storage.data.external.GCP; -const { genDelTagObj, genUniqID, gcpRetry } = +const { genDelTagObj, genUniqID, genBucketName, gcpRetry } = require('../../../utils/gcpUtils'); const { getRealAwsConfig } = require('../../../../aws-node-sdk/test/support/awsConfig'); @@ -14,7 +14,7 @@ const { } = require('@aws-sdk/client-s3'); const credentialOne = 'gcpbackend'; -const bucketName = `cldsrvci-deletetagging-${genUniqID()}`; +const bucketName = genBucketName('deletetagging'); let config; let gcpClient; diff --git a/tests/functional/raw-node/test/GCP/object/get.js b/tests/functional/raw-node/test/GCP/object/get.js index 5a5704513c..841511e979 100644 --- a/tests/functional/raw-node/test/GCP/object/get.js +++ b/tests/functional/raw-node/test/GCP/object/get.js @@ -1,7 +1,7 @@ const assert = require('assert'); const arsenal = require('arsenal'); const { GCP } = arsenal.storage.data.external.GCP; -const { genUniqID, gcpRetry } = require('../../../utils/gcpUtils'); +const { genUniqID, genBucketName, gcpRetry } = require('../../../utils/gcpUtils'); const { getRealAwsConfig } = require('../../../../aws-node-sdk/test/support/awsConfig'); const { @@ -13,7 +13,7 @@ const { } = require('@aws-sdk/client-s3'); const credentialOne = 'gcpbackend'; -const bucketName = `cldsrvci-get-${genUniqID()}`; +const bucketName = genBucketName('get'); describe('GCP: GET Object', function testSuite() { this.timeout(30000); diff --git a/tests/functional/raw-node/test/GCP/object/getTagging.js b/tests/functional/raw-node/test/GCP/object/getTagging.js index 59e2b2e908..96b640c656 100644 --- a/tests/functional/raw-node/test/GCP/object/getTagging.js +++ b/tests/functional/raw-node/test/GCP/object/getTagging.js @@ -1,7 +1,7 @@ const assert = require('assert'); const arsenal = require('arsenal'); const { GCP } = arsenal.storage.data.external.GCP; -const { genGetTagObj, genUniqID, gcpRetry } = +const { genGetTagObj, genUniqID, genBucketName, gcpRetry } = require('../../../utils/gcpUtils'); const { getRealAwsConfig } = require('../../../../aws-node-sdk/test/support/awsConfig'); @@ -13,7 +13,7 @@ const { } = require('@aws-sdk/client-s3'); const credentialOne = 'gcpbackend'; -const bucketName = `cldsrvci-gettagging-${genUniqID()}`; +const bucketName = genBucketName('gettagging'); const tagSize = 10; describe('GCP: GET Object Tagging', () => { diff --git a/tests/functional/raw-node/test/GCP/object/head.js b/tests/functional/raw-node/test/GCP/object/head.js index ae46c52723..a168994e91 100644 --- a/tests/functional/raw-node/test/GCP/object/head.js +++ b/tests/functional/raw-node/test/GCP/object/head.js @@ -1,7 +1,7 @@ const assert = require('assert'); const arsenal = require('arsenal'); const { GCP } = arsenal.storage.data.external.GCP; -const { genUniqID, gcpRetry } = require('../../../utils/gcpUtils'); +const { genUniqID, genBucketName, gcpRetry } = require('../../../utils/gcpUtils'); const { getRealAwsConfig } = require('../../../../aws-node-sdk/test/support/awsConfig'); const { @@ -12,7 +12,7 @@ const { } = require('@aws-sdk/client-s3'); const credentialOne = 'gcpbackend'; -const bucketName = `cldsrvci-head-${genUniqID()}`; +const bucketName = genBucketName('head'); describe('GCP: HEAD Object', function testSuite() { this.timeout(30000); diff --git a/tests/functional/raw-node/test/GCP/object/initiateMpu.js b/tests/functional/raw-node/test/GCP/object/initiateMpu.js index 1f752f60df..95349f3ade 100644 --- a/tests/functional/raw-node/test/GCP/object/initiateMpu.js +++ b/tests/functional/raw-node/test/GCP/object/initiateMpu.js @@ -4,6 +4,7 @@ const arsenal = require('arsenal'); const { GCP } = arsenal.storage.data.external.GCP; const { genUniqID, + genBucketName, gcpRetry, gcpCreateMultipartUploadWithRetry, waitForBucketReady, @@ -18,10 +19,10 @@ const { const credentialOne = 'gcpbackend'; const bucketNames = { main: { - Name: `cldsrvci-initiatempu-${genUniqID()}`, + Name: genBucketName('initiatempu'), }, mpu: { - Name: `cldsrvci-mpu-initiatempu-${genUniqID()}`, + Name: genBucketName('mpu-initiatempu'), }, }; diff --git a/tests/functional/raw-node/test/GCP/object/put.js b/tests/functional/raw-node/test/GCP/object/put.js index a4d1ee0c73..6747401447 100644 --- a/tests/functional/raw-node/test/GCP/object/put.js +++ b/tests/functional/raw-node/test/GCP/object/put.js @@ -1,7 +1,7 @@ const assert = require('assert'); const arsenal = require('arsenal'); const { GCP } = arsenal.storage.data.external.GCP; -const { genUniqID, gcpRetry } = require('../../../utils/gcpUtils'); +const { genUniqID, genBucketName, gcpRetry } = require('../../../utils/gcpUtils'); const { getRealAwsConfig } = require('../../../../aws-node-sdk/test/support/awsConfig'); const { @@ -11,7 +11,7 @@ const { } = require('@aws-sdk/client-s3'); const credentialOne = 'gcpbackend'; -const bucketName = `cldsrvci-put-${genUniqID()}`; +const bucketName = genBucketName('put'); describe('GCP: PUT Object', function testSuite() { this.timeout(30000); diff --git a/tests/functional/raw-node/test/GCP/object/putTagging.js b/tests/functional/raw-node/test/GCP/object/putTagging.js index b588d91389..a5d1d68960 100644 --- a/tests/functional/raw-node/test/GCP/object/putTagging.js +++ b/tests/functional/raw-node/test/GCP/object/putTagging.js @@ -2,7 +2,7 @@ const assert = require('assert'); const async = require('async'); const arsenal = require('arsenal'); const { GCP } = arsenal.storage.data.external.GCP; -const { genPutTagObj, genUniqID, gcpRetry } = +const { genPutTagObj, genUniqID, genBucketName, gcpRetry } = require('../../../utils/gcpUtils'); const { getRealAwsConfig } = require('../../../../aws-node-sdk/test/support/awsConfig'); @@ -14,7 +14,7 @@ const { } = require('@aws-sdk/client-s3'); const credentialOne = 'gcpbackend'; -const bucketName = `cldsrvci-puttagging-${genUniqID()}`; +const bucketName = genBucketName('puttagging'); describe('GCP: PUT Object Tagging', () => { let config; diff --git a/tests/functional/raw-node/test/GCP/object/upload.js b/tests/functional/raw-node/test/GCP/object/upload.js index 40253c31e9..8b4445a296 100644 --- a/tests/functional/raw-node/test/GCP/object/upload.js +++ b/tests/functional/raw-node/test/GCP/object/upload.js @@ -2,7 +2,7 @@ const assert = require('assert'); const async = require('async'); const arsenal = require('arsenal'); const { GCP } = arsenal.storage.data.external.GCP; -const { genUniqID, gcpRetry, gcpUploadWithRetry, waitForBucketReady } = +const { genUniqID, genBucketName, gcpRetry, gcpUploadWithRetry, waitForBucketReady } = require('../../../utils/gcpUtils'); const { getRealAwsConfig } = require('../../../../aws-node-sdk/test/support/awsConfig'); @@ -15,10 +15,10 @@ const { const credentialOne = 'gcpbackend'; const bucketNames = { main: { - Name: `cldsrvci-upload-${genUniqID()}`, + Name: genBucketName('upload'), }, mpu: { - Name: `cldsrvci-mpu-upload-${genUniqID()}`, + Name: genBucketName('mpu-upload'), }, }; diff --git a/tests/functional/raw-node/utils/gcpUtils.js b/tests/functional/raw-node/utils/gcpUtils.js index c60d6d396d..c0a5104fb9 100644 --- a/tests/functional/raw-node/utils/gcpUtils.js +++ b/tests/functional/raw-node/utils/gcpUtils.js @@ -3,7 +3,13 @@ const { callbackify } = require('util'); const { v4: uuidv4 } = require('uuid'); const { HeadBucketCommand } = require('@aws-sdk/client-s3'); -const genUniqID = () => uuidv4().replace(/-/g, ''); +const genUniqID = () => { + const runId = process.env.GITHUB_RUN_ID; + const suffix = uuidv4().replace(/-/g, '').slice(0, 8); + return runId ? `${runId}-${suffix}` : uuidv4().replace(/-/g, ''); +}; + +const genBucketName = testName => `cldsrvci-${testName}-${genUniqID()}`; const defaultShouldRetry = err => err && (err.name === 'SlowDown' || err.$metadata?.httpStatusCode === 429); @@ -18,7 +24,6 @@ async function gcpRetryCall(callFn, retryOptions) { let lastError; for (let attempt = 0; attempt < maxAttempts; attempt++) { try { - return await callFn(); } catch (err) { lastError = err; @@ -219,6 +224,7 @@ module.exports = { genGetTagObj, genDelTagObj, genUniqID, + genBucketName, gcpRetryCall, gcpRetry, gcpCreateMultipartUploadWithRetry, From fc49bd4388d54f1597cda4f720815e29b34e08d9 Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Mon, 9 Mar 2026 17:36:24 +0100 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=91=B7=20create=20a=20new=20script=20?= =?UTF-8?q?to=20clean=20old=20bucket=20from=20GCP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: CLDSRV-867 --- .github/scripts/cleanupOldGCPBuckets.js | 137 ++++++++++++++++++++++++ .github/workflows/tests.yaml | 23 ++++ package.json | 2 +- 3 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 .github/scripts/cleanupOldGCPBuckets.js diff --git a/.github/scripts/cleanupOldGCPBuckets.js b/.github/scripts/cleanupOldGCPBuckets.js new file mode 100644 index 0000000000..a03898aff8 --- /dev/null +++ b/.github/scripts/cleanupOldGCPBuckets.js @@ -0,0 +1,137 @@ +/* eslint-disable no-console */ +'use strict'; + +const { + S3Client, + ListBucketsCommand, + ListObjectsV2Command, + DeleteObjectsCommand, + DeleteBucketCommand, + ListMultipartUploadsCommand, + AbortMultipartUploadCommand, +} = require('@aws-sdk/client-s3'); + +const GCP_ENDPOINT = 'https://storage.googleapis.com'; +const BUCKET_PREFIX = 'cldsrvci-'; +const ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000; + +function buildClient() { + const accessKeyId = process.env.AWS_GCP_BACKEND_ACCESS_KEY; + const secretAccessKey = process.env.AWS_GCP_BACKEND_SECRET_KEY; + + if (!accessKeyId || !secretAccessKey) { + console.error( + 'Missing required environment variables: ' + + 'AWS_GCP_BACKEND_ACCESS_KEY and AWS_GCP_BACKEND_SECRET_KEY' + ); + process.exit(1); + } + + return new S3Client({ + endpoint: GCP_ENDPOINT, + region: 'us-east-1', + credentials: { accessKeyId, secretAccessKey }, + forcePathStyle: true, + disableS3ExpressSessionAuth: true, + requestChecksumCalculation: 'WHEN_REQUIRED', + responseChecksumValidation: 'WHEN_REQUIRED', + }); +} + +async function abortMultipartUploads(client, bucketName) { + let uploadIdMarker; + let keyMarker; + + do { + const res = await client.send(new ListMultipartUploadsCommand({ + Bucket: bucketName, + UploadIdMarker: uploadIdMarker, + KeyMarker: keyMarker, + })); + + for (const upload of res.Uploads || []) { + console.log( + ` Aborting MPU: ${upload.Key} (${upload.UploadId})` + ); + await client.send(new AbortMultipartUploadCommand({ + Bucket: bucketName, + Key: upload.Key, + UploadId: upload.UploadId, + })); + } + + uploadIdMarker = res.NextUploadIdMarker; + keyMarker = res.NextKeyMarker; + } while (uploadIdMarker); +} + +async function deleteAllObjects(client, bucketName) { + let continuationToken; + + do { + const res = await client.send(new ListObjectsV2Command({ + Bucket: bucketName, + ContinuationToken: continuationToken, + })); + + const objects = res.Contents || []; + if (objects.length > 0) { + console.log(` Deleting ${objects.length} object(s)...`); + await client.send(new DeleteObjectsCommand({ + Bucket: bucketName, + Delete: { + Objects: objects.map(o => ({ Key: o.Key })), + Quiet: true, + }, + })); + } + + continuationToken = res.NextContinuationToken; + } while (continuationToken); +} + +async function cleanupBucket(client, bucketName) { + console.log(`Cleaning up bucket: ${bucketName}`); + try { + await abortMultipartUploads(client, bucketName); + await deleteAllObjects(client, bucketName); + await client.send(new DeleteBucketCommand({ Bucket: bucketName })); + console.log(`Deleted bucket: ${bucketName}`); + } catch (err) { + console.error(`Failed to delete bucket ${bucketName}: ${err.message}`); + } +} + +async function main() { + const client = buildClient(); + + console.log('Listing GCP buckets...'); + const { Buckets = [] } = await client.send(new ListBucketsCommand({})); + + const now = Date.now(); + const stale = Buckets.filter(b => + b.Name.startsWith(BUCKET_PREFIX) && + now - new Date(b.CreationDate).getTime() > ONE_WEEK_MS + ); + + if (stale.length === 0) { + console.log('No stale GCP CI buckets found.'); + return; + } + + console.log( + `Found ${stale.length} stale GCP CI bucket(s) to clean up: ${ + stale.map(b => b.Name).join(', ')}` + ); + + for (const bucket of stale) { + await cleanupBucket(client, bucket.Name); + } + + console.log('GCP CI bucket cleanup complete.'); +} + +main().catch(err => { + console.error('GCP cleanup script failed:', err); + process.exit(1); +}); diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index d2efe78c63..cb03872555 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -1026,3 +1026,26 @@ jobs: password: ${{ secrets.ARTIFACTS_PASSWORD }} source: /tmp/artifacts if: always() + + cleanup-gcp-buckets: + runs-on: ubuntu-24.04 + needs: + - multiple-backend + - mongo-v0-ft-tests + - mongo-v1-ft-tests + - file-ft-tests + if: always() + steps: + - name: Checkout + uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: yarn + - name: Install dependencies + run: yarn install --frozen-lockfile --network-concurrency 1 + - name: Delete stale GCP CI buckets + run: yarn run cleanup_gcp_buckets + env: + AWS_GCP_BACKEND_ACCESS_KEY: ${{ secrets.AWS_GCP_BACKEND_ACCESS_KEY }} + AWS_GCP_BACKEND_SECRET_KEY: ${{ secrets.AWS_GCP_BACKEND_SECRET_KEY }} diff --git a/package.json b/package.json index be9c7ad9ca..96646c65a4 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "ft_node_routes": "cd tests/functional/raw-node && yarn run test-routes", "ft_route_backbeat": "cd tests/multipleBackend/routes && mocha --reporter mocha-multi-reporters --reporter-options configFile=$INIT_CWD/tests/reporter-config.json -t 40000 routeBackbeat.js routeBackbeatForReplication.js --exit", "ft_gcp": "cd tests/functional/raw-node && yarn run test-gcp", - "cleanup_gcp_buckets": "node scripts/cleanupOldGCPBuckets.js", + "cleanup_gcp_buckets": "node .github/scripts/cleanupOldGCPBuckets.js", "ft_healthchecks": "cd tests/functional/healthchecks && yarn test", "ft_s3cmd": "cd tests/functional/s3cmd && mocha --reporter mocha-multi-reporters --reporter-options configFile=$INIT_CWD/tests/reporter-config.json -t 40000 *.js --exit", "ft_s3curl": "cd tests/functional/s3curl && mocha --reporter mocha-multi-reporters --reporter-options configFile=$INIT_CWD/tests/reporter-config.json -t 40000 *.js --exit", From a1961e08fe12334477fbe4e5eb281a8b6fe49c72 Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Tue, 10 Mar 2026 11:38:00 +0100 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=91=B7=20remove=20x-id=20parameters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: CLDSRV-867 --- .github/scripts/cleanupOldGCPBuckets.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/scripts/cleanupOldGCPBuckets.js b/.github/scripts/cleanupOldGCPBuckets.js index a03898aff8..37727ab1c5 100644 --- a/.github/scripts/cleanupOldGCPBuckets.js +++ b/.github/scripts/cleanupOldGCPBuckets.js @@ -27,7 +27,7 @@ function buildClient() { process.exit(1); } - return new S3Client({ + const client = new S3Client({ endpoint: GCP_ENDPOINT, region: 'us-east-1', credentials: { accessKeyId, secretAccessKey }, @@ -36,6 +36,19 @@ function buildClient() { requestChecksumCalculation: 'WHEN_REQUIRED', responseChecksumValidation: 'WHEN_REQUIRED', }); + + // GCP's S3-compatible API rejects the x-id query parameter that newer + // versions of @aws-sdk/client-s3 append to every request URL. + client.middlewareStack.add( + next => async args => { + // eslint-disable-next-line no-param-reassign + delete args.request.query['x-id']; + return next(args); + }, + { step: 'build', name: 'removeXIdParam', priority: 'low' }, + ); + + return client; } async function abortMultipartUploads(client, bucketName) { From 24831d632fbf6bc69e05b6572239c75493a87eca Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Tue, 17 Mar 2026 14:31:43 +0100 Subject: [PATCH 4/4] =?UTF-8?q?=E2=9C=A8=20use=20single=20delete=20object?= =?UTF-8?q?=20command=20to=20be=20compliant=20with=20GCP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: CLDSRV-867 --- .github/scripts/cleanupOldGCPBuckets.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/scripts/cleanupOldGCPBuckets.js b/.github/scripts/cleanupOldGCPBuckets.js index 37727ab1c5..146fc7bf2a 100644 --- a/.github/scripts/cleanupOldGCPBuckets.js +++ b/.github/scripts/cleanupOldGCPBuckets.js @@ -5,7 +5,7 @@ const { S3Client, ListBucketsCommand, ListObjectsV2Command, - DeleteObjectsCommand, + DeleteObjectCommand, DeleteBucketCommand, ListMultipartUploadsCommand, AbortMultipartUploadCommand, @@ -90,13 +90,12 @@ async function deleteAllObjects(client, bucketName) { const objects = res.Contents || []; if (objects.length > 0) { console.log(` Deleting ${objects.length} object(s)...`); - await client.send(new DeleteObjectsCommand({ - Bucket: bucketName, - Delete: { - Objects: objects.map(o => ({ Key: o.Key })), - Quiet: true, - }, - })); + for (const obj of objects) { + await client.send(new DeleteObjectCommand({ + Bucket: bucketName, + Key: obj.Key, + })); + } } continuationToken = res.NextContinuationToken;