Skip to content

Conversation

benzekrimaha
Copy link
Contributor

@benzekrimaha benzekrimaha commented Aug 12, 2025

Issue: ARSN-514

Please note that a ticket has been creaed to add coverage not to make this PR more extended : https://scality.atlassian.net/browse/ARSN-524

@bert-e
Copy link
Contributor

bert-e commented Aug 12, 2025

Hello benzekrimaha,

My role is to assist you with the merge of this
pull request. Please type @bert-e help to get information
on this process, or consult the user documentation.

Available options
name description privileged authored
/after_pull_request Wait for the given pull request id to be merged before continuing with the current one.
/bypass_author_approval Bypass the pull request author's approval
/bypass_build_status Bypass the build and test status
/bypass_commit_size Bypass the check on the size of the changeset TBA
/bypass_incompatible_branch Bypass the check on the source branch prefix
/bypass_jira_check Bypass the Jira issue check
/bypass_peer_approval Bypass the pull request peers' approval
/bypass_leader_approval Bypass the pull request leaders' approval
/approve Instruct Bert-E that the author has approved the pull request. ✍️
/create_pull_requests Allow the creation of integration pull requests.
/create_integration_branches Allow the creation of integration branches.
/no_octopus Prevent Wall-E from doing any octopus merge and use multiple consecutive merge instead
/unanimity Change review acceptance criteria from one reviewer at least to all reviewers
/wait Instruct Bert-E not to run until further notice.
Available commands
name description privileged
/help Print Bert-E's manual in the pull request.
/status Print Bert-E's current status in the pull request TBA
/clear Remove all comments from Bert-E from the history TBA
/retry Re-start a fresh build TBA
/build Re-start a fresh build TBA
/force_reset Delete integration branches & pull requests, and restart merge process from the beginning.
/reset Try to remove integration branches unless there are commits on them which do not appear on the source branch.

Status report is not available.

@benzekrimaha benzekrimaha changed the base branch from development/8.2 to development/8.3 September 18, 2025 09:52
@bert-e
Copy link
Contributor

bert-e commented Sep 18, 2025

Waiting for approval

The following approvals are needed before I can proceed with the merge:

  • the author

  • 2 peers

@benzekrimaha benzekrimaha force-pushed the improvement/ARSN-514 branch 2 times, most recently from f8e5940 to 942aa25 Compare September 18, 2025 13:02
@benzekrimaha benzekrimaha marked this pull request as ready for review September 18, 2025 13:02
@benzekrimaha benzekrimaha changed the title Improvement/arsn 514 AWS-SDK Migration Sep 18, 2025
@scality scality deleted a comment from bert-e Sep 18, 2025
@scality scality deleted a comment from bert-e Sep 18, 2025
@scality scality deleted a comment from codecov bot Sep 18, 2025
Copy link

codecov bot commented Sep 18, 2025

Codecov Report

❌ Patch coverage is 40.90909% with 221 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.22%. Comparing base (2173361) to head (bdf2a5c).

Files with missing lines Patch % Lines
lib/storage/data/external/GCP/GcpClient.js 35.86% 118 Missing ⚠️
lib/storage/data/external/AwsClient.js 27.61% 76 Missing ⚠️
lib/storage/data/external/GcpClient.js 3.84% 25 Missing ⚠️
lib/network/kmsAWS/Client.ts 97.61% 1 Missing ⚠️
lib/storage/data/LocationConstraintParser.js 90.90% 1 Missing ⚠️
Additional details and impacted files
@@                 Coverage Diff                 @@
##           development/8.3    #2482      +/-   ##
===================================================
- Coverage            71.07%   70.22%   -0.85%     
===================================================
  Files                  220      218       -2     
  Lines                17736    17789      +53     
  Branches              3683     3669      -14     
===================================================
- Hits                 12605    12492     -113     
- Misses                5127     5293     +166     
  Partials                 4        4              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@SylvainSenechal
Copy link
Contributor

SylvainSenechal commented Sep 19, 2025

Issue: ARSN-514

Please note that a ticket has been creaed to add coverage not to make this PR more extended : https://scality.atlassian.net/browse/ARSN-524

Wait, you say you've got two Jira ticket 514/524, but they both point to this same PR 🤔

endpoint,
httpOptions,
...credentials,
requestHandler: undefined, // v3 handles agents differently
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't we dropping tls support here ? If you say it is still handled but differently, maybe reference where/how it is done. Or maybe it's just not needed ?
In Vault I was able to use it like this https://github.com/scality/vault2/pull/177/files#diff-a78b27e0448a01885b6f5220e7a97d50c0729ff792356c653a09bf67361446e9R154

Other example in s3utils : https://github.com/scality/s3utils/blob/development/1.16/VerifyReplication/storage/s3.js#L63

const allowedKmsErrorCodes = Object.keys(allowedKmsErrors) as unknown as (keyof typeof allowedKmsErrors)[];

// Local AWSError type for compatibility with v3 error handling
export type AWSError = Error & {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't tested/run the code but just wanna double check, you sure this file is ok this way ? I mean specifically, I'm looking at line 50, should err.code be replaced with err.name ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the isAWSError below. I think this part should be migrated as well, because they indeed use name now, plus, we should use instanceOf as documented here: https://aws.amazon.com/blogs/developer/service-error-handling-modular-aws-sdk-js/.

If we need this type then it means we either need to migrate to TS now to ensure we are not missing anything (it can be hard to see all affected places), or we should cover all error cases in the unit tests, to ensure the affected logic will still handle properly errors, even if we still use .code (because maybe we wrap the errors and use an arsenal one?)

if (keyContext.isDeleteMarker) {
return this._client.deleteObject(params, putCb);
return this._client.send(new DeleteObjectCommand(params)).then(res => putCb(null, res))
.catch(err => putCb(err));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.catch(err => putCb(err));
.catch(putCb);

Can double check other occurrences in this file & elsewhere

};
return callback(null, response);
}).catch(err => {
if (err.code === 'NoSuchKey' || err.code === 'NotFound') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't wanna leave the same comment on all occurence of this err.code vs err.name situation, but make sure this is ok. Because in the Zenko migration, I just checked and when I used getObjectCommand, i checked the err with err.name : https://github.com/scality/Zenko/blob/improvement/ZENKO-5054/tests/zenko_tests/node_tests/cloudserver/keyFormatVersion/tests/versionedBucket.js#L232

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a limitation of the lack of TS support. Maybe we can have some quick wins here. ANyway there is two kind of errors today: errors coming from drivers/sdk that are usually native JS errors objects or enriched like in aws sdk. And there are the Arsenal errors that are not very standard, where the code would typically have the error string...
Maybe we can type partially at first this file, so we ensure we are working with the right types of errors, and don't miss anything?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@benzekrimaha double checking after your changes :
I see you used xxxException, but I can't import them, instead I can import xxx without the 'Exception' as you can see for example in the screenshot : NoSuchKey exists, but NoSuchKeyException doesn't exists.
Not too sure if it's me who has a messed up IDE or something, but worth double checking

image

});
});

it('should create a new master encryption key', done => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a duplicate of the first test "should support default encryption key per account" 🤔

"ajv": "6.12.3",
"async": "~2.6.4",
"aws-sdk": "^2.1691.0",
"@aws-sdk/client-s3": "^3.540.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked the 3.540 release, it's already from Mar 22, 2024. Do you think we can pick something more recent ? I guess it should be compatible with current migrated code

.customizeDescription('You must specify at least one part');
return callback(error);
}
for (let ind = 1; ind < partList.length; ++ind) {
Copy link
Contributor

@SylvainSenechal SylvainSenechal Sep 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this is likely to happen in practice, but does that need stricter checking than simple ordering ? For example here, [1,2,10] would be valid here but maybe we want to enforce [1,2,3] (or [0,1,2]) ?
Maybe checking that that partList[i] = i + 1 (or i)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is not required, we can provide any number (a subset) of all uploaded parts during a complete, as long as they are in correct order and we are not providing a part number that was missing from the initial upload phase, this is accepted. So with |1,2,3,4,5,6,7,9,11] uploaded, we can complete with [1,3,5,11] if we want.

Copy link
Contributor

@williamlardier williamlardier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This completes Sylvain's review. I think I covered everything that makes sense given what should probably change, so I'll come back once it's adressed

Comment on lines 308 to 313
listObjects(params, callback) {
return this.send(new ListObjectsCommand(params))
.then(data => callback && callback(null, data))
.catch(err => callback && callback(err));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could make sense to switch this file to ts, but the callback should be required?

If no you can do that

Suggested change
listObjects(params, callback) {
return this.send(new ListObjectsCommand(params))
.then(data => callback && callback(null, data))
.catch(err => callback && callback(err));
}
listObjects(params, callback) {
return this.send(new ListObjectsCommand(params))
.then(data => callback?.(null, data))
.catch(err => callback?.(err));
}

But better to always call it?

};
return callback(null, response);
}).catch(err => {
if (err.code === 'NoSuchKey' || err.code === 'NotFound') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a limitation of the lack of TS support. Maybe we can have some quick wins here. ANyway there is two kind of errors today: errors coming from drivers/sdk that are usually native JS errors objects or enriched like in aws sdk. And there are the Arsenal errors that are not very standard, where the code would typically have the error string...
Maybe we can type partially at first this file, so we ensure we are working with the right types of errors, and don't miss anything?

.customizeDescription('You must specify at least one part');
return callback(error);
}
for (let ind = 1; ind < partList.length; ++ind) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is not required, we can provide any number (a subset) of all uploaded parts during a complete, as long as they are in correct order and we are not providing a part number that was missing from the initial upload phase, this is accepted. So with |1,2,3,4,5,6,7,9,11] uploaded, we can complete with [1,3,5,11] if we want.

// Prefer ARN, but fall back to KeyId if ARN is missing
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
keyId = keyMetadata?.Arn ?? keyMetadata?.KeyId!;
keyId = keyMetadata?.Arn ?? (keyMetadata?.KeyId || '');
Copy link
Contributor

@williamlardier williamlardier Sep 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is important top keep the comment about double arn prefix just below.

This one:

            // May produce double arn prefix: scality arn + aws arn
            // arn:scality:kms:external:aws_kms:custom:key/arn:aws:kms:region:accountId:key/cbd69d33-ba8e-4b56-8cfe
            // If this is a problem, a config flag should be used to hide the scality arn when returning the KMS KeyId
            // or aws arn when creating the KMS Key

maxAttempts: 1,
requestHandler: undefined, // v3 handles agents differently
// v3 does not use signatureVersion or sslEnabled directly
// v3 does not use customUserAgent directly
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we maybe point to where the logic now is? In the sdk, or elsewhere in the code, so we can know where to look later?

const params = {
KeyId: masterKeyId,
KeySpec: 'AES_256',
KeySpec: "AES_256" as const,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
KeySpec: "AES_256" as const,
KeySpec: 'AES_256' as const,

Or you can also create the variable as a const once and reuse it here

Comment on lines +288 to +296
/**
* List objects in a bucket.
* @param {object} params - S3 listObjects params
* @param {function} callback
*/
listObjects(params, callback) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we maybe deprecate the callback approach, as we currently support both?
Also what is returned is actually not a function but a promise, so doing an await listObjects(...) here will work, even if we don't provide any callback.

Or, if we want to only support callback, better not to return the promise at all, to avoid mixed ways of calling this function?

const allowedKmsErrorCodes = Object.keys(allowedKmsErrors) as unknown as (keyof typeof allowedKmsErrors)[];

// Local AWSError type for compatibility with v3 error handling
export type AWSError = Error & {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the isAWSError below. I think this part should be migrated as well, because they indeed use name now, plus, we should use instanceOf as documented here: https://aws.amazon.com/blogs/developer/service-error-handling-modular-aws-sdk-js/.

If we need this type then it means we either need to migrate to TS now to ensure we are not missing anything (it can be hard to see all affected places), or we should cover all error cases in the unit tests, to ensure the affected logic will still handle properly errors, even if we still use .code (because maybe we wrap the errors and use an arsenal one?)

@bert-e
Copy link
Contributor

bert-e commented Oct 1, 2025

Conflict

There is a conflict between your branch improvement/ARSN-514 and the
destination branch development/8.3.

Please resolve the conflict on the feature branch (improvement/ARSN-514).

git fetch && \
git checkout origin/improvement/ARSN-514 && \
git merge origin/development/8.3

Resolve merge conflicts and commit

git push origin HEAD:improvement/ARSN-514

@francoisferrand francoisferrand self-requested a review October 1, 2025 10:45
Comment on lines 199 to 205
this._client.send(new GetObjectCommand(params)).then(data => {
// Always return an object with .createReadStream for test compatibility
const stream = data.Body;
const abort = () => {
if (isAborted) return; // Prevent multiple abort calls
isAborted = true;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure to see where this variable is defined? We first initialize it in the next line, shouldn't we create it out of the scope of abort?

Suggested change
this._client.send(new GetObjectCommand(params)).then(data => {
// Always return an object with .createReadStream for test compatibility
const stream = data.Body;
const abort = () => {
if (isAborted) return; // Prevent multiple abort calls
isAborted = true;
this._client.send(new GetObjectCommand(params)).then(data => {
// Always return an object with .createReadStream for test compatibility
const stream = data.Body;
let isAborted = false;
const abort = () => {
if (isAborted) {
return; // Prevent multiple abort calls
}
isAborted = true;

Comment on lines 207 to 215
if (typeof stream.destroy === 'function') {
stream.destroy();
} else if (typeof stream.abort === 'function') {
stream.abort();
} else if (typeof stream.close === 'function') {
stream.close();
} else if (typeof stream.end === 'function') {
stream.end();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (typeof stream.destroy === 'function') {
stream.destroy();
} else if (typeof stream.abort === 'function') {
stream.abort();
} else if (typeof stream.close === 'function') {
stream.close();
} else if (typeof stream.end === 'function') {
stream.end();
}
(stream?.destroy || stream?.abort || stream?.close || stream?.end)?.();

?

Comment on lines 217 to 219
if (typeof stream.removeAllListeners === 'function') {
stream.removeAllListeners();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (typeof stream.removeAllListeners === 'function') {
stream.removeAllListeners();
}
stream.removeAllListeners?.();

Comment on lines 222 to 224
if (stream && !stream.abort) {
stream.abort = abort;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (stream && !stream.abort) {
stream.abort = abort;
}
if (!stream?.abort) {
stream.abort = abort;
}

Comment on lines 225 to 231
const response = {
createReadStream: () => stream,
Body: stream,
abort,
destroy: abort,
};
return callback(null, response);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const response = {
createReadStream: () => stream,
Body: stream,
abort,
destroy: abort,
};
return callback(null, response);
return callback(null, {
createReadStream: () => stream,
Body: stream,
abort,
destroy,
});

(with the new abort variable name)

endpoint: `${protocol}://${endpoint}`,
region,
requestHandler: new NodeHttpHandler({
socketTimeout: 0,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 411 to 416
// If error is NoSuchKey, bucket exists but key doesn't (which is expected)
if (err instanceof NoSuchKey || err instanceof NotFound) {
callback(null, {});
}
// Other errors likely mean bucket doesn't exist or access denied
callback(err);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

otherwise we call both callbacks

Suggested change
// If error is NoSuchKey, bucket exists but key doesn't (which is expected)
if (err instanceof NoSuchKey || err instanceof NotFound) {
callback(null, {});
}
// Other errors likely mean bucket doesn't exist or access denied
callback(err);
// If error is NoSuchKey, bucket exists but key doesn't (which is expected)
if (err instanceof NoSuchKey || err instanceof NotFound) {
return callback(null, {});
}
// Other errors likely mean bucket doesn't exist or access denied
return callback(err);

Comment on lines +512 to +516
const logger = { trace: () => {} };
const stringToSign = constructStringToSignV2(fakeRequest, data, logger, 'GCP');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should probably use a real class logger here, and properly pass the request IDs, as here it would remove any logs in case of real issues?
(can be in a followup ticket?)

awsResp[location] = { error: err, external: true };
return callback(null, awsResp);
}
}).then(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not the order of promise we want, as after calling the callback in the .catch, we will execute the .then?

`${this.type}: ${err.message}`),
);
}
}).then(completeMpuRes => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to keep the .then before the .catch here I think, same reason as above comment

With the migration we can no longer use the serviceDefine
property available with aws-sdk v2. This commit aims to extend
the s3Client class to override the methods that are not
compatible with GCP.

Issue: ARSN-514
@bert-e
Copy link
Contributor

bert-e commented Oct 2, 2025

Waiting for approval

The following approvals are needed before I can proceed with the merge:

  • the author

  • 2 peers

endpoint,
});
this._client.endpoint = new AWS.Endpoint(endpoint);
this._client = new S3Client({ ...this._s3Params, endpoint });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it needs region ?

Suggested change
this._client = new S3Client({ ...this._s3Params, endpoint });
this._client = new S3Client({ ...this._s3Params, region, endpoint });

// set regional endpoint
region = err.region;
} else if (res) {
if (res) {
Copy link
Contributor

@SylvainSenechal SylvainSenechal Oct 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty sure if (res) is not needed, but LocationConstraint can be undefined according to the typescript structure, should be fine though, if the region is undefined new S3Client handles it

(stream?.destroy || stream?.abort || stream?.close || stream?.end || stream?.removeAllListeners)?.();
};
if (!stream?.abort) {
stream.abort = abort;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, abort is not defined, should it be destroy ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah after reviewing William's comment (#2482 (comment)), I think you changed the function name was changed but this line wasn't changed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants