Skip to content

Commit

Permalink
feat(custom-resources): use latest SDK in AwsCustomResource (#5442)
Browse files Browse the repository at this point in the history
Install the latest v2 of AWS SDK JS when a new container is initialized
for the Lambda function. Subsequent executions reusing this container will
skip installation.

Increase default timeout to 60 seconds.

Closes #2689
Closes #5063
  • Loading branch information
jogold authored and Elad Ben-Israel committed Dec 18, 2019
1 parent 1e20284 commit a111cdd
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export interface AwsCustomResourceProps {
/**
* The timeout for the Lambda function implementing this custom resource.
*
* @default Duration.seconds(30)
* @default Duration.seconds(60)
*/
readonly timeout?: cdk.Duration
}
Expand Down Expand Up @@ -178,7 +178,7 @@ export class AwsCustomResource extends cdk.Construct implements iam.IGrantable {
handler: 'index.handler',
uuid: '679f53fa-c002-430c-b0da-5b7982bd2287',
lambdaPurpose: 'AWS',
timeout: props.timeout || cdk.Duration.seconds(30),
timeout: props.timeout || cdk.Duration.seconds(60),
role: props.role,
});
this.grantPrincipal = provider.grantPrincipal;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// tslint:disable:no-console
// eslint-disable-next-line import/no-extraneous-dependencies
import * as AWS from 'aws-sdk';
import { execSync } from 'child_process';
import { AwsSdkCall } from '../aws-custom-resource';

/**
Expand Down Expand Up @@ -52,10 +51,40 @@ function filterKeys(object: object, pred: (key: string) => boolean) {
);
}

let latestSdkInstalled = false;

/**
* Installs latest AWS SDK v2
*/
function installLatestSdk(): void {
console.log('Installing latest AWS SDK v2');
// Both HOME and --prefix are needed here because /tmp is the only writable location
execSync('HOME=/tmp npm install aws-sdk@2 --production --no-package-lock --no-save --prefix /tmp');
latestSdkInstalled = true;
}

/* eslint-disable @typescript-eslint/no-require-imports, import/no-extraneous-dependencies */
export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent, context: AWSLambda.Context) {
try {
let AWS: any;
if (!latestSdkInstalled) {
try {
installLatestSdk();
AWS = require('/tmp/node_modules/aws-sdk');
} catch (e) {
console.log(`Failed to install latest AWS SDK v2: ${e}`);
AWS = require('aws-sdk'); // Fallback to pre-installed version
}
} else {
AWS = require('/tmp/node_modules/aws-sdk');
}

if (process.env.USE_NORMAL_SDK) { // For tests only
AWS = require('aws-sdk');
}

console.log(JSON.stringify(event));
console.log('AWS SDK VERSION: ' + (AWS as any).VERSION);
console.log('AWS SDK VERSION: ' + AWS.VERSION);

let physicalResourceId = (event as any).PhysicalResourceId;
let flatData: { [key: string]: string } = {};
Expand Down
4 changes: 3 additions & 1 deletion packages/@aws-cdk/custom-resources/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,14 @@
"@aws-cdk/aws-s3": "1.19.0",
"@aws-cdk/aws-ssm": "1.19.0",
"@types/aws-lambda": "^8.10.37",
"@types/fs-extra": "^8.0.1",
"@types/sinon": "^7.5.0",
"aws-sdk": "^2.590.0",
"aws-sdk-mock": "^4.5.0",
"cdk-build-tools": "1.19.0",
"cdk-integ-tools": "1.19.0",
"cfn2ts": "1.19.0",
"fs-extra": "^8.1.0",
"nock": "^11.7.0",
"pkglint": "1.19.0",
"sinon": "^7.5.0"
Expand Down Expand Up @@ -124,4 +126,4 @@
"props-default-doc:@aws-cdk/custom-resources.AwsSdkCall.parameters"
]
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as SDK from 'aws-sdk';
import * as AWS from 'aws-sdk-mock';
import * as fs from 'fs-extra';
import * as nock from 'nock';
import * as sinon from 'sinon';
import { AwsSdkCall } from '../../lib';
Expand All @@ -24,9 +25,14 @@ function createRequest(bodyPredicate: (body: AWSLambda.CloudFormationCustomResou
.reply(200);
}

beforeEach(() => {
process.env.USE_NORMAL_SDK = 'true';
});

afterEach(() => {
AWS.restore();
nock.cleanAll();
delete process.env.USE_NORMAL_SDK;
});

test('create event with physical resource id path', async () => {
Expand Down Expand Up @@ -338,3 +344,40 @@ test('flatten correctly flattens a nested object', () => {
'd.1.k.l': false
});
});

test('installs the latest SDK', async () => {
const tmpPath = '/tmp/node_modules/aws-sdk';

fs.remove(tmpPath);

const publishFake = sinon.fake.resolves({});

AWS.mock('SNS', 'publish', publishFake);

const event: AWSLambda.CloudFormationCustomResourceCreateEvent = {
...eventCommon,
RequestType: 'Create',
ResourceProperties: {
ServiceToken: 'token',
Create: {
service: 'SNS',
action: 'publish',
parameters: {
Message: 'message',
TopicArn: 'topic'
},
physicalResourceId: 'id',
} as AwsSdkCall
}
};

const request = createRequest(body =>
body.Status === 'SUCCESS'
);

await handler(event, {} as AWSLambda.Context);

expect(request.isDone()).toBeTruthy();

expect(() => require.resolve(tmpPath)).not.toThrow();
});
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ test('timeout defaults to 30 seconds', () => {

// THEN
expect(stack).toHaveResource('AWS::Lambda::Function', {
Timeout: 30
Timeout: 60
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
"Properties": {
"Code": {
"S3Bucket": {
"Ref": "AssetParameters18da46d8773b50c4a62f9c3c9acf80991f578d190ed091a3ced48ba9fb3d98f8S3BucketDA18872A"
"Ref": "AssetParameters138d5170d70070f442a09ab6b7c09fecfa7278d8b8c0e64fdc2addf284172a5eS3BucketC954C005"
},
"S3Key": {
"Fn::Join": [
Expand All @@ -122,7 +122,7 @@
"Fn::Split": [
"||",
{
"Ref": "AssetParameters18da46d8773b50c4a62f9c3c9acf80991f578d190ed091a3ced48ba9fb3d98f8S3VersionKey8DEA118B"
"Ref": "AssetParameters138d5170d70070f442a09ab6b7c09fecfa7278d8b8c0e64fdc2addf284172a5eS3VersionKey2D066AE2"
}
]
}
Expand All @@ -135,7 +135,7 @@
"Fn::Split": [
"||",
{
"Ref": "AssetParameters18da46d8773b50c4a62f9c3c9acf80991f578d190ed091a3ced48ba9fb3d98f8S3VersionKey8DEA118B"
"Ref": "AssetParameters138d5170d70070f442a09ab6b7c09fecfa7278d8b8c0e64fdc2addf284172a5eS3VersionKey2D066AE2"
}
]
}
Expand All @@ -153,7 +153,7 @@
]
},
"Runtime": "nodejs12.x",
"Timeout": 30
"Timeout": 60
},
"DependsOn": [
"AWS679f53fac002430cb0da5b7982bd2287ServiceRoleDefaultPolicyD28E1A5E",
Expand Down Expand Up @@ -230,17 +230,17 @@
}
},
"Parameters": {
"AssetParameters18da46d8773b50c4a62f9c3c9acf80991f578d190ed091a3ced48ba9fb3d98f8S3BucketDA18872A": {
"AssetParameters138d5170d70070f442a09ab6b7c09fecfa7278d8b8c0e64fdc2addf284172a5eS3BucketC954C005": {
"Type": "String",
"Description": "S3 bucket for asset \"18da46d8773b50c4a62f9c3c9acf80991f578d190ed091a3ced48ba9fb3d98f8\""
"Description": "S3 bucket for asset \"138d5170d70070f442a09ab6b7c09fecfa7278d8b8c0e64fdc2addf284172a5e\""
},
"AssetParameters18da46d8773b50c4a62f9c3c9acf80991f578d190ed091a3ced48ba9fb3d98f8S3VersionKey8DEA118B": {
"AssetParameters138d5170d70070f442a09ab6b7c09fecfa7278d8b8c0e64fdc2addf284172a5eS3VersionKey2D066AE2": {
"Type": "String",
"Description": "S3 key for asset version \"18da46d8773b50c4a62f9c3c9acf80991f578d190ed091a3ced48ba9fb3d98f8\""
"Description": "S3 key for asset version \"138d5170d70070f442a09ab6b7c09fecfa7278d8b8c0e64fdc2addf284172a5e\""
},
"AssetParameters18da46d8773b50c4a62f9c3c9acf80991f578d190ed091a3ced48ba9fb3d98f8ArtifactHash8C68AE30": {
"AssetParameters138d5170d70070f442a09ab6b7c09fecfa7278d8b8c0e64fdc2addf284172a5eArtifactHash5852F39A": {
"Type": "String",
"Description": "Artifact hash for asset \"18da46d8773b50c4a62f9c3c9acf80991f578d190ed091a3ced48ba9fb3d98f8\""
"Description": "Artifact hash for asset \"138d5170d70070f442a09ab6b7c09fecfa7278d8b8c0e64fdc2addf284172a5e\""
}
},
"Outputs": {
Expand Down

0 comments on commit a111cdd

Please sign in to comment.