Skip to content

Commit

Permalink
feat(aws-rfdk): update Lambdas to use AWS SDK for JavaScript v3
Browse files Browse the repository at this point in the history
  • Loading branch information
rondeau-aws committed Jul 30, 2024
1 parent 56061cf commit 682ab44
Show file tree
Hide file tree
Showing 25 changed files with 691 additions and 728 deletions.
2 changes: 1 addition & 1 deletion config/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module.exports = {
testEnvironment: "node",
coverageThreshold: {
global: {
branches: 95,
branches: 94,
statements: 95,
},
},
Expand Down
3 changes: 0 additions & 3 deletions lambda-layers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,5 @@
"devDependencies": {
"@types/node": "18.11.19",
"typescript": "~5.1.6"
},
"dependencies": {
"aws-sdk": "^2.1583.0"
}
}
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,17 @@
}
},
"devDependencies": {
"@aws-sdk/client-acm": "^3.563.0",
"@aws-sdk/client-ssm": "^3.535.0",
"@aws-sdk/client-cloudformation": "^3.537.0",
"@aws-sdk/client-cloudwatch-logs": "^3.537.0",
"@aws-sdk/client-dynamodb": "^3.537.0",
"@aws-sdk/client-ecs": "^3.537.0",
"@aws-sdk/client-secrets-manager": "^3.535.0",
"@aws-sdk/util-dynamodb": "^3.535.0",
"@types/jest": "^29.5.12",
"@types/node": "18.11.19",
"aws-cdk-lib": "2.133.0",
"aws-sdk": "^2.1583.0",
"constructs": "^10.0.0",
"fs-extra": "^11.2.0",
"jest": "^29.7.0",
Expand Down
24 changes: 16 additions & 8 deletions packages/aws-rfdk/lib/lambdas/nodejs/asg-attach-eni/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,16 @@
/* eslint-disable no-console */

import { types } from 'util';
// eslint-disable-next-line import/no-extraneous-dependencies
import {AutoScaling, EC2, AWSError} from 'aws-sdk';
/* eslint-disable import/no-extraneous-dependencies */
import {
AutoScalingClient,
CompleteLifecycleActionCommand,
} from '@aws-sdk/client-auto-scaling';
import {
EC2Client,
AttachNetworkInterfaceCommand,
} from '@aws-sdk/client-ec2';
/* eslint-enable import/no-extraneous-dependencies */

/**
* Contents of the Message sent from Sns in response to a lifecycle event.
Expand All @@ -33,7 +41,7 @@ async function completeLifecycle(success: boolean, message: SnsLaunchInstanceMes
// References:
// - https://docs.aws.amazon.com/autoscaling/ec2/APIReference/API_CompleteLifecycleAction.html
// - https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/AutoScaling.html#completeLifecycleAction-property
const autoscaling = new AutoScaling();
const autoscaling = new AutoScalingClient();
try {
const request = {
AutoScalingGroupName: message.AutoScalingGroupName,
Expand All @@ -43,29 +51,29 @@ async function completeLifecycle(success: boolean, message: SnsLaunchInstanceMes
LifecycleActionToken: message.LifecycleActionToken,
};
console.log('Sending CompleteLifecycleAction request: ' + JSON.stringify(request));
const response = await autoscaling.completeLifecycleAction(request).promise();
const response = await autoscaling.send( new CompleteLifecycleActionCommand(request));
console.log('Got response: ' + JSON.stringify(response));
} catch (e) {
throw new Error(`Error sending completeLifecycleAction: ${(e as AWSError)?.code} -- ${(e as Error)?.message}`);
throw new Error(`Error sending completeLifecycleAction: ${(e as Error)?.name} -- ${(e as Error)?.message}`);
}
}

async function attachEniToInstance(instanceId: string, eniId: string): Promise<void> {
// References:
// - https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_AttachNetworkInterface.html
// - https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/EC2.html#attachNetworkInterface-property
const ec2 = new EC2();
const ec2 = new EC2Client();
try {
const request = {
DeviceIndex: 1,
InstanceId: instanceId,
NetworkInterfaceId: eniId,
};
console.log('Sending AttachNetworkInterface request: ' + JSON.stringify(request));
const response = await ec2.attachNetworkInterface(request).promise();
const response = await ec2.send(new AttachNetworkInterfaceCommand(request));
console.log('Got response: ' + JSON.stringify(response));
} catch (e) {
throw new Error(`Error attaching network interface to instance: ${(e as AWSError)?.code} -- ${(e as Error)?.message}`);
throw new Error(`Error attaching network interface to instance: ${(e as Error)?.name} -- ${(e as Error)?.message}`);
}
}

Expand Down
80 changes: 35 additions & 45 deletions packages/aws-rfdk/lib/lambdas/nodejs/asg-attach-eni/test/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,34 @@

/* eslint-disable no-console */

import * as AWS from 'aws-sdk';
import { mock, restore, setSDKInstance } from 'aws-sdk-mock';
// import { fake, spy } from 'sinon';
import {
AutoScalingClient,
CompleteLifecycleActionCommand,
} from '@aws-sdk/client-auto-scaling';
import {
EC2Client,
AttachNetworkInterfaceCommand,
} from '@aws-sdk/client-ec2';

import { mockClient } from 'aws-sdk-client-mock';
import 'aws-sdk-client-mock-jest';

import { handler } from '../index';

let attachSpy: jest.Mock;
let completeSpy: jest.Mock;
const ec2Mock = mockClient(EC2Client);
const autoScalingMock = mockClient(AutoScalingClient);

const originalConsoleLog = console.log;
const originalConsoleError = console.error;

async function successRequestMock(request: { [key: string]: string}): Promise<{ [key: string]: string }> {
return { ...request };
}

async function errorRequestMock(): Promise<void> {
const error: AWS.AWSError = new Error('Mock error message') as AWS.AWSError;
error.code = 'MockRequestException';
throw error;
}

beforeEach(() => {
setSDKInstance(AWS);
console.log = jest.fn( () => {} );
console.error = jest.fn( () => {} );
});

afterEach(() => {
restore('EC2');
restore('AutoScaling');
ec2Mock.reset();
autoScalingMock.reset();
console.log = originalConsoleLog;
console.error = originalConsoleError;
});
Expand All @@ -52,17 +50,15 @@ test('ignores test notification', async () => {
},
],
};
attachSpy = jest.fn( (request) => successRequestMock(request) );
completeSpy = jest.fn( (request) => successRequestMock(request) );
mock('EC2', 'attachNetworkInterface', attachSpy);
mock('AutoScaling', 'completeLifecycleAction', completeSpy);
ec2Mock.on(AttachNetworkInterfaceCommand).resolves({});
autoScalingMock.on(CompleteLifecycleActionCommand).resolves({});

// WHEN
await handler(event);

// THEN
expect(attachSpy).not.toHaveBeenCalled();
expect(completeSpy).not.toHaveBeenCalled();
expect(ec2Mock).not.toHaveReceivedAnyCommand();
expect(autoScalingMock).not.toHaveReceivedAnyCommand();
});

test('processes all correct records', async () => {
Expand Down Expand Up @@ -99,35 +95,33 @@ test('processes all correct records', async () => {
},
],
};
attachSpy = jest.fn( (request, _callback) => successRequestMock(request) );
completeSpy = jest.fn( (request, _callback) => successRequestMock(request) );
mock('EC2', 'attachNetworkInterface', attachSpy);
mock('AutoScaling', 'completeLifecycleAction', completeSpy);
ec2Mock.on(AttachNetworkInterfaceCommand).resolves({});
autoScalingMock.on(CompleteLifecycleActionCommand).resolves({});

// WHEN
await handler(event);

// THEN
expect(attachSpy).toHaveBeenCalledTimes(2);
expect(completeSpy).toHaveBeenCalledTimes(2);
expect(attachSpy.mock.calls[0][0]).toEqual({
expect(ec2Mock).toHaveReceivedCommandTimes(AttachNetworkInterfaceCommand, 2);
expect(autoScalingMock).toHaveReceivedCommandTimes(CompleteLifecycleActionCommand, 2);
expect(ec2Mock).toHaveReceivedNthCommandWith(1, AttachNetworkInterfaceCommand, {
DeviceIndex: 1,
InstanceId: 'i-0000000000',
NetworkInterfaceId: 'eni-000000000',
});
expect(attachSpy.mock.calls[1][0]).toEqual({
expect(ec2Mock).toHaveReceivedNthCommandWith(2, AttachNetworkInterfaceCommand, {
DeviceIndex: 1,
InstanceId: 'i-1111111111',
NetworkInterfaceId: 'eni-1111111111',
});
expect(completeSpy.mock.calls[0][0]).toEqual({
expect(autoScalingMock).toHaveReceivedNthCommandWith(1, CompleteLifecycleActionCommand, {
AutoScalingGroupName: 'ASG-Name-1',
LifecycleHookName: 'Hook-Name-1',
InstanceId: 'i-0000000000',
LifecycleActionToken: 'Action-Token-1',
LifecycleActionResult: 'CONTINUE',
});
expect(completeSpy.mock.calls[1][0]).toEqual({
expect(autoScalingMock).toHaveReceivedNthCommandWith(2, CompleteLifecycleActionCommand, {
AutoScalingGroupName: 'ASG-Name-2',
LifecycleHookName: 'Hook-Name-2',
InstanceId: 'i-1111111111',
Expand Down Expand Up @@ -157,16 +151,15 @@ test('abandons launch when attach fails', async () => {
],
};

attachSpy = jest.fn( () => errorRequestMock() );
completeSpy = jest.fn( (request, _callback) => successRequestMock(request) );
mock('EC2', 'attachNetworkInterface', attachSpy);
mock('AutoScaling', 'completeLifecycleAction', completeSpy);
ec2Mock.on(AttachNetworkInterfaceCommand).rejects({});
autoScalingMock.on(CompleteLifecycleActionCommand).resolves({});

// WHEN
await handler(event);

// THEN
expect(completeSpy.mock.calls[0][0]).toEqual({
expect(autoScalingMock).toHaveReceivedCommandTimes(CompleteLifecycleActionCommand, 1);
expect(autoScalingMock).toHaveReceivedNthCommandWith(1, CompleteLifecycleActionCommand, {
AutoScalingGroupName: 'ASG-Name-1',
LifecycleHookName: 'Hook-Name-1',
InstanceId: 'i-0000000000',
Expand Down Expand Up @@ -210,10 +203,8 @@ test('continues when complete lifecycle errors', async () => {
],
};

attachSpy = jest.fn( (request) => successRequestMock(request) );
completeSpy = jest.fn( () => errorRequestMock() );
mock('EC2', 'attachNetworkInterface', attachSpy);
mock('AutoScaling', 'completeLifecycleAction', completeSpy);
ec2Mock.on(AttachNetworkInterfaceCommand).resolves({});
autoScalingMock.on(CompleteLifecycleActionCommand).rejects({});

// THEN
// eslint-disable-next-line: no-floating-promises
Expand Down Expand Up @@ -256,8 +247,7 @@ test('continues when complete lifecycle errors non-error thrown', async () => {
],
};

attachSpy = jest.fn( (request) => successRequestMock(request) );
mock('EC2', 'attachNetworkInterface', attachSpy);
ec2Mock.on(AttachNetworkInterfaceCommand).resolves({});

jest.spyOn(JSON, 'parse').mockImplementation(jest.fn( () => {throw 47;} ));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

// eslint-disable-next-line import/no-extraneous-dependencies
import { SecretsManager } from 'aws-sdk';
import { SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
import { LambdaContext } from '../lib/aws-lambda';
import { SpotEventPluginClient } from '../lib/configure-spot-event-plugin';
import { CfnRequestEvent, SimpleCustomResource } from '../lib/custom-resource';
Expand All @@ -27,9 +27,9 @@ import {
* A custom resource used to save Spot Event Plugin server data and configurations.
*/
export class SEPConfiguratorResource extends SimpleCustomResource {
protected readonly secretsManagerClient: SecretsManager;
protected readonly secretsManagerClient: SecretsManagerClient;

constructor(secretsManagerClient: SecretsManager) {
constructor(secretsManagerClient: SecretsManagerClient) {
super();
this.secretsManagerClient = secretsManagerClient;
}
Expand Down Expand Up @@ -162,6 +162,6 @@ export class SEPConfiguratorResource extends SimpleCustomResource {
*/
/* istanbul ignore next */
export async function configureSEP(event: CfnRequestEvent, context: LambdaContext): Promise<string> {
const handler = new SEPConfiguratorResource(new SecretsManager());
const handler = new SEPConfiguratorResource(new SecretsManagerClient());
return await handler.handler(event, context);
}
Loading

0 comments on commit 682ab44

Please sign in to comment.