Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions .github/scripts/cleanupOldGCPBuckets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/* eslint-disable no-console */
'use strict';

const {
S3Client,
ListBucketsCommand,
ListObjectsV2Command,
DeleteObjectCommand,
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);
}

const client = new S3Client({
endpoint: GCP_ENDPOINT,
region: 'us-east-1',
credentials: { accessKeyId, secretAccessKey },
forcePathStyle: true,
disableS3ExpressSessionAuth: true,
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) {
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)...`);
for (const obj of objects) {
await client.send(new DeleteObjectCommand({
Bucket: bucketName,
Key: obj.Key,
}));
}
}

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);
});
23 changes: 23 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 .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",
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/raw-node/test/GCP/bucket/get.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/raw-node/test/GCP/bucket/getVersioning.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand All @@ -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({
Expand Down
6 changes: 3 additions & 3 deletions tests/functional/raw-node/test/GCP/bucket/head.js
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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() {
Expand All @@ -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(
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/raw-node/test/GCP/bucket/putVersioning.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 3 additions & 2 deletions tests/functional/raw-node/test/GCP/object/completeMpu.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const { GCP, GcpUtils } = arsenal.storage.data.external.GCP;
const {
gcpMpuSetup,
genUniqID,
genBucketName,
gcpRetry,
waitForBucketReady,
} = require('../../../utils/gcpUtils');
Expand All @@ -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',
},
};
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/raw-node/test/GCP/object/copy.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/raw-node/test/GCP/object/delete.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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()}`;

Expand Down
6 changes: 3 additions & 3 deletions tests/functional/raw-node/test/GCP/object/deleteMpu.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/raw-node/test/GCP/object/deleteTagging.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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;

Expand Down
4 changes: 2 additions & 2 deletions tests/functional/raw-node/test/GCP/object/get.js
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/raw-node/test/GCP/object/getTagging.js
Original file line number Diff line number Diff line change
@@ -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');
Expand All @@ -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', () => {
Expand Down
Loading
Loading