Skip to content

Commit fcfe5d6

Browse files
authored
fix: Upgrade to AWS SDK v3 (#1201)
AWS SDK v2 will enter maintenance mode in September and will become unmaintained in 2025, as announced here: https://aws.amazon.com/blogs/developer/announcing-end-of-support-for-aws-sdk-for-javascript-v2/ Documentation for AWS SDK v3 can be found at https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide however it is often not particularly helpful unless you know exactly what you're looking for. The upgrade guide here is helpful for filling in some of the gaps: https://github.com/aws/aws-sdk-js-v3/blob/main/UPGRADING.md This upgrade should not introduce behavioural changes. It removes the peer dependency on `aws-sdk`, and I've added the new AWS SDK packages as direct dependencies instead. This will make the update process as simple as possible (we're still using AWS SDK v2 in most of our services) and adding new peer dependencies would create a breaking change. Jira: [ENG-3211]
1 parent 79a5489 commit fcfe5d6

8 files changed

Lines changed: 1433 additions & 329 deletions

File tree

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
"@types/xml2js": "^0.4.14",
3333
"@typescript-eslint/eslint-plugin": "^5.33.0",
3434
"@typescript-eslint/parser": "^5.33.0",
35-
"aws-sdk": "^2.1568.0",
3635
"eslint": "^8.57.0",
3736
"eslint-plugin-import": "^2.25.2",
3837
"eslint-plugin-jsdoc": "^39.3.2",
@@ -45,12 +44,13 @@
4544
"tsconfig-paths": "^4.2.0",
4645
"typescript": "^4.9.5"
4746
},
48-
"peerDependencies": {
49-
"aws-sdk": "^2.831.0"
50-
},
5147
"dependencies": {
48+
"@aws-sdk/client-lambda": "^3.540.0",
49+
"@aws-sdk/client-s3": "^3.540.0",
50+
"@aws-sdk/client-sqs": "^3.540.0",
5251
"@lumigo/tracer": "^1.91.0",
5352
"@sentry/node": "^6.19.7",
53+
"@smithy/node-http-handler": "^2.5.0",
5454
"@types/aws-lambda": "^8.10.134",
5555
"alai": "1.0.3",
5656
"async": "^3.2.5",

src/models/SQSMessageModel.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { SQS } from 'aws-sdk';
1+
import type { Message } from '@aws-sdk/client-sqs';
22

33
/**
44
* Model for message received from SQS.
@@ -24,7 +24,7 @@ export default class SQSMessageModel {
2424

2525
forDeletion = false;
2626

27-
constructor(message: SQS.Message) {
27+
constructor(message: Message) {
2828
if (!message.MessageId) {
2929
throw new TypeError('Message does not have a MessageId');
3030
}

src/services/BaseConfigService.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { S3 } from 'aws-sdk';
1+
import {
2+
DeleteObjectCommand,
3+
GetObjectCommand,
4+
PutObjectCommand,
5+
S3Client,
6+
} from '@aws-sdk/client-s3';
27

38
import DependencyAwareClass from '../core/DependencyAwareClass';
49
import LambdaTermination from '../utils/LambdaTermination';
@@ -64,7 +69,7 @@ export default class BaseConfigService extends DependencyAwareClass {
6469
* Returns an S3 client.
6570
*/
6671
static get client() {
67-
return new S3({
72+
return new S3Client({
6873
region: process.env.REGION,
6974
});
7075
}
@@ -80,9 +85,9 @@ export default class BaseConfigService extends DependencyAwareClass {
8085
* Deletes the configuration stored on S3. Helpful in feature tests.
8186
*/
8287
async delete() {
83-
return this.client.deleteObject(
88+
return this.client.send(new DeleteObjectCommand(
8489
(this.constructor as typeof BaseConfigService).s3config,
85-
).promise();
90+
));
8691
}
8792

8893
/**
@@ -91,10 +96,10 @@ export default class BaseConfigService extends DependencyAwareClass {
9196
* @param config
9297
*/
9398
async put<T>(config: T): Promise<T> {
94-
await this.client.putObject({
99+
await this.client.send(new PutObjectCommand({
95100
...(this.constructor as typeof BaseConfigService).s3config,
96101
Body: JSON.stringify(config),
97-
}).promise();
102+
}));
98103

99104
return config;
100105
}
@@ -103,9 +108,9 @@ export default class BaseConfigService extends DependencyAwareClass {
103108
* Gets the service configuration.
104109
*/
105110
async get(): Promise<unknown> {
106-
const response = await this.client.getObject(
111+
const response = await this.client.send(new GetObjectCommand(
107112
(this.constructor as typeof BaseConfigService).s3config,
108-
).promise();
113+
));
109114
const body = String(response.Body);
110115

111116
if (!body) {

src/services/SQSService.ts

Lines changed: 86 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
1+
import {
2+
InvokeCommand,
3+
LambdaClient,
4+
} from '@aws-sdk/client-lambda';
5+
import {
6+
DeleteMessageBatchCommand,
7+
GetQueueAttributesCommand,
8+
ListQueuesCommand,
9+
ReceiveMessageCommand,
10+
SQSClient,
11+
SendMessageCommand,
12+
SendMessageCommandInput,
13+
} from '@aws-sdk/client-sqs';
14+
import { NodeHttpHandler } from '@smithy/node-http-handler';
115
import alai from 'alai';
216
import { each } from 'async';
3-
import AWS from 'aws-sdk';
417
import { v4 as uuid } from 'uuid';
518

619
import DependencyAwareClass from '../core/DependencyAwareClass';
@@ -210,9 +223,9 @@ export default class SQSService<
210223

211224
readonly queueUrls: Record<QueueName<TConfig>, string>;
212225

213-
private $sqs?: AWS.SQS;
226+
private $sqs?: SQSClient;
214227

215-
private $lambda?: AWS.Lambda;
228+
private $lambda?: LambdaClient;
216229

217230
constructor(di: DependencyInjection<TConfig>) {
218231
super(di);
@@ -252,14 +265,14 @@ export default class SQSService<
252265
*/
253266
get sqs() {
254267
if (!this.$sqs) {
255-
this.$sqs = new AWS.SQS({
268+
this.$sqs = new SQSClient({
256269
region: process.env.REGION,
257-
httpOptions: {
270+
requestHandler: new NodeHttpHandler({
258271
// longest publish on NOTV took 5 seconds
259-
connectTimeout: 8 * 1000,
260-
timeout: 8 * 1000,
261-
},
262-
maxRetries: 3, // default is 3, we can change that
272+
connectionTimeout: 8 * 1000,
273+
socketTimeout: 8 * 1000,
274+
}),
275+
maxAttempts: 4, // default from AWS SDK v2 was 3 retries for SQS
263276
});
264277
}
265278

@@ -278,7 +291,7 @@ export default class SQSService<
278291
}
279292

280293
// move to subprocess
281-
this.$lambda = new AWS.Lambda({
294+
this.$lambda = new LambdaClient({
282295
region: process.env.AWS_REGION,
283296
endpoint,
284297
});
@@ -331,21 +344,16 @@ export default class SQSService<
331344
resolve();
332345
}
333346

334-
this.sqs.deleteMessageBatch(
335-
{
336-
Entries: messagesForDeletion,
337-
QueueUrl: queueUrl,
338-
},
339-
(error) => {
340-
timer.stop(timerId);
341-
342-
if (error) {
343-
logger.error(error);
344-
}
345-
346-
resolve();
347-
},
348-
);
347+
this.sqs.send(new DeleteMessageBatchCommand({
348+
Entries: messagesForDeletion,
349+
QueueUrl: queueUrl,
350+
})).finally(() => {
351+
timer.stop(timerId);
352+
}).catch((error) => {
353+
logger.error(error);
354+
}).then(() => {
355+
resolve();
356+
});
349357
},
350358
);
351359
});
@@ -362,25 +370,27 @@ export default class SQSService<
362370
return new Promise((resolve) => {
363371
timer.start(timerId);
364372

365-
this.sqs.listQueues({}, (error, data) => {
366-
timer.stop(timerId);
367-
368-
let status: Status = 'OK';
373+
let status: Status = 'OK';
369374

370-
if (error) {
375+
this.sqs.send(new ListQueuesCommand())
376+
.finally(() => {
377+
timer.stop(timerId);
378+
})
379+
.then((data) => {
380+
if (typeof data.QueueUrls === 'undefined' || data.QueueUrls.length === 0) {
381+
status = 'APPLICATION_FAILURE';
382+
}
383+
})
384+
.catch((error) => {
371385
logger.error(error);
372386
status = 'APPLICATION_FAILURE';
373-
}
374-
375-
if (typeof data.QueueUrls === 'undefined' || data.QueueUrls.length === 0) {
376-
status = 'APPLICATION_FAILURE';
377-
}
378-
379-
resolve({
380-
service: 'SQS',
381-
status,
387+
})
388+
.then(() => {
389+
resolve({
390+
service: 'SQS',
391+
status,
392+
});
382393
});
383-
});
384394
});
385395
}
386396

@@ -398,23 +408,18 @@ export default class SQSService<
398408
return new Promise((resolve) => {
399409
timer.start(timerId);
400410

401-
this.sqs.getQueueAttributes(
402-
{
403-
AttributeNames: ['ApproximateNumberOfMessages'],
404-
QueueUrl: queueUrl,
405-
},
406-
(error, data) => {
407-
timer.stop(timerId);
408-
409-
if (error) {
410-
logger.error(error);
411-
resolve(0);
412-
}
413-
414-
const messageCount = data.Attributes?.ApproximateNumberOfMessages || '0';
415-
resolve(Number.parseInt(messageCount, 10));
416-
},
417-
);
411+
this.sqs.send(new GetQueueAttributesCommand({
412+
AttributeNames: ['ApproximateNumberOfMessages'],
413+
QueueUrl: queueUrl,
414+
})).finally(() => {
415+
timer.stop(timerId);
416+
}).then((data) => {
417+
const messageCount = data.Attributes?.ApproximateNumberOfMessages || '0';
418+
resolve(Number.parseInt(messageCount, 10));
419+
}).catch((error) => {
420+
logger.error(error);
421+
resolve(0);
422+
});
418423
});
419424
}
420425

@@ -444,7 +449,7 @@ export default class SQSService<
444449

445450
timer.start(timerId);
446451

447-
const messageParameters: AWS.SQS.SendMessageRequest = {
452+
const messageParameters: SendMessageCommandInput = {
448453
MessageBody: JSON.stringify(messageObject),
449454
QueueUrl: queueUrl,
450455
};
@@ -458,7 +463,7 @@ export default class SQSService<
458463
if (this.di.isOffline && SQSService.offlineMode === SQS_OFFLINE_MODES.DIRECT) {
459464
await this.publishOffline(queue, messageParameters);
460465
} else {
461-
await this.sqs.sendMessage(messageParameters).promise();
466+
await this.sqs.send(new SendMessageCommand(messageParameters));
462467
}
463468
} catch (error) {
464469
if (failureMode === SQS_PUBLISH_FAILURE_MODES.CATCH) {
@@ -481,7 +486,7 @@ export default class SQSService<
481486
* @param queue
482487
* @param messageParameters
483488
*/
484-
async publishOffline(queue: QueueName<TConfig>, messageParameters: AWS.SQS.SendMessageRequest) {
489+
async publishOffline(queue: QueueName<TConfig>, messageParameters: SendMessageCommandInput) {
485490
if (!this.di.isOffline) {
486491
throw new Error('Can only publishOffline while running serverless offline.');
487492
}
@@ -505,9 +510,11 @@ export default class SQSService<
505510
],
506511
});
507512

508-
const parameters = { FunctionName, InvocationType, Payload };
509-
510-
await this.lambda.invoke(parameters).promise();
513+
await this.lambda.send(new InvokeCommand({
514+
FunctionName,
515+
InvocationType,
516+
Payload,
517+
}));
511518
}
512519

513520
/**
@@ -525,27 +532,22 @@ export default class SQSService<
525532
return new Promise((resolve, reject) => {
526533
timer.start(timerId);
527534

528-
this.sqs.receiveMessage(
529-
{
530-
QueueUrl: queueUrl,
531-
VisibilityTimeout: timeout,
532-
MaxNumberOfMessages: 10,
533-
},
534-
(error, data) => {
535-
timer.stop(timerId);
536-
537-
if (error) {
538-
logger.error(error);
539-
return reject(error);
540-
}
541-
542-
if (typeof data.Messages === 'undefined') {
543-
return resolve([]);
544-
}
545-
546-
return resolve(data.Messages.map((message) => new SQSMessageModel(message)));
547-
},
548-
);
535+
this.sqs.send(new ReceiveMessageCommand({
536+
QueueUrl: queueUrl,
537+
VisibilityTimeout: timeout,
538+
MaxNumberOfMessages: 10,
539+
})).finally(() => {
540+
timer.stop(timerId);
541+
}).then((data) => {
542+
if (typeof data.Messages === 'undefined') {
543+
resolve([]);
544+
} else {
545+
resolve(data.Messages.map((message) => new SQSMessageModel(message)));
546+
}
547+
}).catch((error) => {
548+
logger.error(error);
549+
reject(error);
550+
});
549551
});
550552
}
551553
}

0 commit comments

Comments
 (0)