Skip to content

Commit 0522897

Browse files
authored
Merge pull request #1536 from salesforcecli/d/W-20084586-2
chore: updating to v3 of s3 API @W-20084586@
2 parents 2e52342 + 0646910 commit 0522897

File tree

7 files changed

+666
-239
lines changed

7 files changed

+666
-239
lines changed

.eslintrc.cjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,13 @@ module.exports = {
99
rules: {
1010
'no-shadow': 'off',
1111
'@typescript-eslint/no-shadow': ['error'],
12+
'@typescript-eslint/no-unused-vars': [
13+
'error',
14+
{
15+
argsIgnorePattern: '^_',
16+
varsIgnorePattern: '^_',
17+
caughtErrorsIgnorePattern: '^_',
18+
},
19+
],
1220
},
1321
};

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"sf-release": "bin/run.js"
1010
},
1111
"dependencies": {
12+
"@aws-sdk/client-s3": "^3.929.0",
1213
"@oclif/core": "^4.8.0",
1314
"@octokit/core": "^6.1.6",
1415
"@octokit/plugin-paginate-rest": "^11.6.0",
@@ -20,7 +21,8 @@
2021
"@salesforce/plugin-trust": "^3.7.89",
2122
"@salesforce/sf-plugins-core": "^12.2.5",
2223
"@salesforce/ts-types": "^2.0.10",
23-
"aws-sdk": "^2.1692.0",
24+
"@smithy/node-http-handler": "^4.4.5",
25+
"@smithy/types": "^4.8.1",
2426
"chalk": "^5.6.0",
2527
"commit-and-tag-version": "^12.6.0",
2628
"fast-glob": "^3.3.3",
@@ -38,7 +40,7 @@
3840
"@salesforce/ts-sinon": "^1.4.31",
3941
"@types/semver": "^7.7.0",
4042
"@types/shelljs": "^0.8.17",
41-
"aws-sdk-mock": "^5.9.0",
43+
"aws-sdk-client-mock": "^4.1.0",
4244
"eslint-plugin-sf-plugin": "^1.20.33",
4345
"oclif": "^4.22.44",
4446
"ts-node": "^10.9.2",

src/amazonS3.ts

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,14 @@ import { ux } from '@oclif/core';
1111
import got from 'got';
1212
import { SfError } from '@salesforce/core';
1313
import chalk from 'chalk';
14-
import AWS from 'aws-sdk';
15-
import { S3, WebIdentityCredentials } from 'aws-sdk';
16-
import { CredentialsOptions } from 'aws-sdk/lib/credentials.js';
14+
import { _Object, ListObjectsV2CommandOutput, S3, GetObjectRequest, GetObjectOutput } from '@aws-sdk/client-s3';
15+
import { NodeHttpHandler } from '@smithy/node-http-handler';
16+
import { AwsCredentialIdentity } from '@smithy/types';
1717
import { isString } from '@salesforce/ts-types';
1818

19-
import { GetObjectRequest, GetObjectOutput } from 'aws-sdk/clients/s3.js';
2019
import { Channel, CLI, S3Manifest, ServiceAvailability } from './types.js';
2120
import { api } from './codeSigning/packAndSign.js';
2221

23-
import ClientConfiguration = WebIdentityCredentials.ClientConfiguration;
24-
2522
const BASE_URL = 'https://developer.salesforce.com';
2623
const BUCKET = 'dfc-data-production';
2724

@@ -32,7 +29,7 @@ type AmazonS3Options = {
3229
cli: CLI;
3330
channel?: Channel;
3431
baseUrl?: string;
35-
credentials?: CredentialsOptions;
32+
credentials?: AwsCredentialIdentity;
3633
baseKey?: string;
3734
};
3835

@@ -41,14 +38,15 @@ export class AmazonS3 {
4138

4239
public directory: string;
4340
private s3: S3;
44-
private baseKey: string;
41+
private readonly baseKey: string;
4542

4643
public constructor(private options: AmazonS3Options) {
4744
this.directory = `https://developer.salesforce.com/media/salesforce-cli/${this.options.cli ?? ''}`;
4845
this.baseKey = this.directory.replace(BASE_URL, '').replace(/^\//, '');
49-
this.s3 = new AWS.S3({
46+
this.s3 = new S3({
47+
region: 'us-east-1',
5048
...resolveCredentials(options.credentials),
51-
...buildHttpOptions(),
49+
requestHandler: buildRequestHandler(),
5250
});
5351
}
5452

@@ -65,36 +63,31 @@ export class AmazonS3 {
6563
}
6664

6765
public async getObject(options: GetObjectOption): Promise<GetObjectOutput> {
68-
const object = (await this.s3
69-
.getObject({
70-
...options,
71-
Key: options.Key.replace(BASE_URL, '').replace(/^\//, ''),
72-
...{ Bucket: this.options.bucket ?? BUCKET },
73-
})
74-
.promise()) as GetObjectOutput;
75-
return object;
66+
return this.s3.getObject({
67+
...options,
68+
Key: options.Key?.replace(BASE_URL, '').replace(/^\//, ''),
69+
...{ Bucket: this.options.bucket ?? BUCKET },
70+
});
7671
}
7772

7873
// Paginates listObjectV2 and returns both Contents and CommonPrefixes
79-
public async listAllObjects(key: string): Promise<{ contents: S3.ObjectList; commonPrefixes: string[] }> {
74+
public async listAllObjects(key: string): Promise<{ contents: _Object[]; commonPrefixes: string[] }> {
8075
const prefix = key.startsWith(this.baseKey) ? key : `${this.baseKey}/${key}/`;
8176
const bucket = this.options.bucket ?? BUCKET;
8277
let continuationToken;
83-
const allContents: S3.ObjectList = [];
78+
const allContents: _Object[] = [];
8479
const allCommonPrefixes: string[] = [];
8580

8681
// Use maximum iteration to ensure termination
8782
const MAX_ITERATIONS = 100;
8883
for (let i = 1; i <= MAX_ITERATIONS; i++) {
8984
// eslint-disable-next-line no-await-in-loop
90-
const response = await this.s3
91-
.listObjectsV2({
92-
Bucket: bucket,
93-
Delimiter: '/',
94-
Prefix: prefix,
95-
ContinuationToken: continuationToken,
96-
})
97-
.promise();
85+
const response: ListObjectsV2CommandOutput = await this.s3.listObjectsV2({
86+
Bucket: bucket,
87+
Delimiter: '/',
88+
Prefix: prefix,
89+
ContinuationToken: continuationToken,
90+
});
9891

9992
if (response.Contents) {
10093
allContents.push(...response.Contents);
@@ -117,7 +110,7 @@ export class AmazonS3 {
117110
return result.commonPrefixes;
118111
}
119112

120-
public async listKeyContents(key: string): Promise<S3.ObjectList> {
113+
public async listKeyContents(key: string): Promise<_Object[]> {
121114
const result = await this.listAllObjects(key);
122115
return result.contents;
123116
}
@@ -157,8 +150,8 @@ const getFileAtUrl = async (url: string): Promise<string> => {
157150
};
158151

159152
const resolveCredentials = (
160-
credentialOptions?: CredentialsOptions
161-
): { credentials: CredentialsOptions } | Record<string, string> => {
153+
credentialOptions?: AwsCredentialIdentity
154+
): { credentials: AwsCredentialIdentity } | Record<string, string> => {
162155
if (credentialOptions) {
163156
return { credentials: credentialOptions };
164157
}
@@ -174,7 +167,13 @@ const fileIsAvailable = async (url: string): Promise<ServiceAvailability> => {
174167
return { service: 'file', name: url, available: statusCode >= 200 && statusCode < 300 };
175168
};
176169

177-
const buildHttpOptions = (): { httpOptions: ClientConfiguration['httpOptions'] } | Record<string, never> => {
170+
const buildRequestHandler = (): NodeHttpHandler => {
178171
const agent = api.getAgentForUri('https://s3.amazonaws.com');
179-
return agent && agent.http ? { httpOptions: { agent: agent.http } } : {};
172+
const options =
173+
agent && agent.http
174+
? {
175+
httpAgent: agent.http,
176+
}
177+
: {};
178+
return new NodeHttpHandler(options);
180179
};

src/codeSigning/SimplifiedSigning.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@
2323
*/
2424
import { generateKeyPair, createSign, createVerify } from 'node:crypto';
2525
import { createReadStream } from 'node:fs';
26-
import { S3 } from 'aws-sdk';
27-
import { putObject } from '../codeSigning/upload.js';
26+
import { PutObjectCommandOutput } from '@aws-sdk/client-s3';
2827
import { PackageJsonSfdxProperty } from '../package.js';
28+
import { putObject } from './upload.js';
2929
const CRYPTO_LEVEL = 'RSA-SHA256';
3030
const BUCKET = 'dfc-data-production';
3131
export const BASE_URL = 'https://developer.salesforce.com';
@@ -98,7 +98,7 @@ export const signVerifyUpload = async (signingRequest: SigningRequest): Promise<
9898
/**
9999
* Save the security items (publicKey and .sig file) to AWS based on the generates filenames
100100
*/
101-
const upload = async (input: SigningResponse): Promise<S3.PutObjectOutput[]> =>
101+
const upload = async (input: SigningResponse): Promise<PutObjectCommandOutput[]> =>
102102
Promise.all([
103103
// signature file
104104
putObject(BUCKET, input.packageJsonSfdxProperty.signatureUrl.replace(`${BASE_URL}/`, ''), input.signatureContents),

src/codeSigning/upload.ts

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,19 @@
44
* Licensed under the BSD 3-Clause license.
55
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66
*/
7-
8-
import AWS from 'aws-sdk';
7+
import { PutObjectCommand, PutObjectCommandOutput, S3 } from '@aws-sdk/client-s3';
8+
import { NodeHttpHandler, NodeHttpHandlerOptions } from '@smithy/node-http-handler';
99
import { Agents } from 'got';
10-
import { WebIdentityCredentials } from 'aws-sdk';
1110
import { api } from './packAndSign.js';
12-
import ClientConfiguration = WebIdentityCredentials.ClientConfiguration;
1311

14-
export async function putObject(bucket: string, key: string, body: string): Promise<AWS.S3.PutObjectOutput> {
15-
return new Promise((resolve, reject) => {
16-
const agent = api.getAgentForUri('https://s3.amazonaws.com') as Agents;
17-
const s3 = new AWS.S3({
18-
httpOptions: { agent: agent.http },
19-
httpsOptions: { agent: agent.https },
20-
} as ClientConfiguration);
21-
s3.putObject({ Bucket: bucket, Key: key, Body: body }, (err, resp) => {
22-
if (err) reject(err);
23-
if (resp) resolve(resp);
24-
});
12+
export async function putObject(bucket: string, key: string, body: string): Promise<PutObjectCommandOutput> {
13+
const agent = api.getAgentForUri('https://s3.amazonaws.com') as Agents;
14+
const s3 = new S3({
15+
region: 'us-east-1',
16+
requestHandler: new NodeHttpHandler({
17+
httpAgent: agent.http,
18+
httpsAgent: agent.https,
19+
} as NodeHttpHandlerOptions),
2520
});
21+
return s3.send(new PutObjectCommand({ Bucket: bucket, Key: key, Body: body }));
2622
}

test/codeSigning/upload.test.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,29 @@
44
* Licensed under the BSD 3-Clause license.
55
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66
*/
7-
import AWS from 'aws-sdk';
8-
import AWSMock from 'aws-sdk-mock';
7+
import { CreateMultipartUploadCommand, UploadPartCommand, S3, PutObjectCommand } from '@aws-sdk/client-s3';
8+
import { AwsClientStub, mockClient } from 'aws-sdk-client-mock';
99
import { expect } from 'chai';
1010
import { TestContext } from '@salesforce/core/testSetup';
1111
import { putObject } from '../../src/codeSigning/upload.js';
1212

1313
describe('Upload', () => {
1414
const $$ = new TestContext();
15+
let clientMock: AwsClientStub<S3>;
1516

1617
beforeEach(() => {
17-
AWSMock.setSDKInstance(AWS);
18+
clientMock = mockClient(S3);
19+
clientMock.on(PutObjectCommand).resolves({ ETag: '12345' });
20+
clientMock.on(CreateMultipartUploadCommand).resolves({ UploadId: '1' });
21+
clientMock.on(UploadPartCommand).resolves({ ETag: '12345' });
1822
});
1923

2024
afterEach(() => {
2125
$$.SANDBOX.restore();
22-
AWSMock.restore('S3');
26+
clientMock.restore();
2327
});
2428

2529
it('should upload an object to S3', async () => {
26-
AWSMock.mock('S3', 'putObject', (params, callback) => {
27-
callback(undefined, { ETag: '12345' });
28-
});
2930
const response = await putObject('my-plugin-1.0.0.sig', 'my-bucket', 'media/signatures');
3031
expect(response).to.deep.equal({ ETag: '12345' });
3132
});

0 commit comments

Comments
 (0)