From 572bfedd8f14649ae1ccb31c327283e815e2f697 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 16 Dec 2019 20:41:45 +0100 Subject: [PATCH 1/3] feat(custom-resources): option to use latest SDK in AwsCustomResource Add a `useLatestSdk` prop to `AwsCustomResource`. When set to `true`, the latest v2 of AWS SDK JS will be installed when a new container is initialized for the Lambda function. Subsequent executions resuing this container will skip installation. Increase default timeout to 60 seconds when `useLatestSdk` is set to `true`. Closes #2689 Closes #5063 --- .../aws-custom-resource.ts | 26 ++- .../lib/aws-custom-resource/runtime/index.ts | 15 +- .../@aws-cdk/custom-resources/package.json | 4 +- .../aws-custom-resource-provider.test.ts | 47 +++- .../aws-custom-resource.test.ts | 25 +++ .../integ.aws-custom-resource.expected.json | 20 +- .../integ.latest-sdk.expected.json | 207 ++++++++++++++++++ .../aws-custom-resource/integ.latest-sdk.ts | 45 ++++ 8 files changed, 373 insertions(+), 16 deletions(-) create mode 100644 packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.latest-sdk.expected.json create mode 100644 packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.latest-sdk.ts diff --git a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts index c8cacd2296c1d..f95fada45b90a 100644 --- a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts +++ b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts @@ -149,9 +149,26 @@ export interface AwsCustomResourceProps { /** * The timeout for the Lambda function implementing this custom resource. * - * @default Duration.seconds(30) + * @default Duration.seconds(30) when `useLatestSdk` is false, + * Duration.seconds(60) otherwise. */ readonly timeout?: cdk.Duration + + /** + * Install and use the latest version of AWS SDK JS. + * + * When set to `true`, the latest v2 of AWS SDK JS will be installed when a + * new container is initialized for the Lambda function implementing this + * custom resource. Subsequent executions reusing this container will skip + * installation. + * + * As this custom resource uses a singleton Lambda function, it's important + * to note that this will apply to **all** `AwsCustomResource`s in the stack + * even if only one instance sets this parameters to `true`. + * + * @default false + */ + readonly useLatestSdk?: boolean; } export class AwsCustomResource extends cdk.Construct implements iam.IGrantable { @@ -178,8 +195,13 @@ 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 || (props.useLatestSdk ? cdk.Duration.seconds(60) : cdk.Duration.seconds(30)), role: props.role, + environment: props.useLatestSdk + ? { + USE_LATEST_SDK: 'true' + } + : {} }); this.grantPrincipal = provider.grantPrincipal; diff --git a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts index 8f65465d1ced3..1299004b51a1a 100644 --- a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts +++ b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts @@ -1,5 +1,5 @@ // tslint:disable:no-console -import AWS = require('aws-sdk'); +import { execSync } from 'child_process'; import { AwsSdkCall } from '../aws-custom-resource'; /** @@ -51,8 +51,21 @@ function filterKeys(object: object, pred: (key: string) => boolean) { ); } +let latestSdkInstalled = false; + +function installLatestSdk(): void { + console.log('Installing latest AWS SDK v2'); + execSync('HOME=/tmp npm install aws-sdk@2 --production --no-package-lock --prefix /tmp'); + latestSdkInstalled = true; +} + export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent, context: AWSLambda.Context) { try { + if (process.env.USE_LATEST_SDK === 'true' && !latestSdkInstalled) { + installLatestSdk(); + } + + const AWS = process.env.USE_LATEST_SDK ? require('/tmp/node_modules/aws-sdk') : require('aws-sdk'); console.log(JSON.stringify(event)); console.log('AWS SDK VERSION: ' + (AWS as any).VERSION); diff --git a/packages/@aws-cdk/custom-resources/package.json b/packages/@aws-cdk/custom-resources/package.json index 22a6fcb874ac7..4400d03ca58c9 100644 --- a/packages/@aws-cdk/custom-resources/package.json +++ b/packages/@aws-cdk/custom-resources/package.json @@ -71,12 +71,14 @@ "@aws-cdk/aws-s3": "1.18.0", "@aws-cdk/aws-ssm": "1.18.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.18.0", "cdk-integ-tools": "1.18.0", "cfn2ts": "1.18.0", + "fs-extra": "^8.1.0", "nock": "^11.7.0", "pkglint": "1.18.0", "sinon": "^7.5.0" @@ -124,4 +126,4 @@ "props-default-doc:@aws-cdk/custom-resources.AwsSdkCall.parameters" ] } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource-provider.test.ts b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource-provider.test.ts index 09a96923048a1..9f702c24641fd 100644 --- a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource-provider.test.ts +++ b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource-provider.test.ts @@ -1,12 +1,11 @@ import SDK = require('aws-sdk'); import AWS = require('aws-sdk-mock'); +import fs = require('fs-extra'); import nock = require('nock'); import sinon = require('sinon'); import { AwsSdkCall } from '../../lib'; import { flatten, handler } from '../../lib/aws-custom-resource/runtime'; -AWS.setSDK(require.resolve('aws-sdk')); - console.log = jest.fn(); // tslint:disable-line no-console const eventCommon = { @@ -24,9 +23,14 @@ function createRequest(bodyPredicate: (body: AWSLambda.CloudFormationCustomResou .reply(200); } +beforeEach(() => { + AWS.setSDK(require.resolve('aws-sdk')); +}); + afterEach(() => { AWS.restore(); nock.cleanAll(); + delete process.env.USE_LATEST_SDK; }); test('create event with physical resource id path', async () => { @@ -338,3 +342,42 @@ test('flatten correctly flattens a nested object', () => { 'd.1.k.l': false }); }); + +test('installs latest sdk when needed', async () => { + AWS.setSDK('/tmp/node_modules/aws-sdk'); + + fs.removeSync('/tmp/node_modules/aws-sdk'); + + process.env.USE_LATEST_SDK = 'true'; + + 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(fs.existsSync('/tmp/node_modules/aws-sdk')).toBe(true); +}); diff --git a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource.test.ts b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource.test.ts index a552dfc43fa90..61588f8370daf 100644 --- a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource.test.ts +++ b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource.test.ts @@ -307,3 +307,28 @@ test('can use existing role', () => { expect(stack).not.toHaveResource('AWS::IAM::Role'); }); + +test('useLatestSdk sets environment variable and increases default timeout', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new AwsCustomResource(stack, 'AwsSdk', { + onCreate: { + service: 'service', + action: 'action', + physicalResourceId: 'id' + }, + useLatestSdk: true, + }); + + // THEN + expect(stack).toHaveResource('AWS::Lambda::Function', { + Environment: { + Variables: { + USE_LATEST_SDK: 'true', + }, + }, + Timeout: 60, + }); +}); diff --git a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.expected.json b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.expected.json index a1f6cd6a495ea..396a87c4255ee 100644 --- a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.expected.json +++ b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.expected.json @@ -109,7 +109,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersba1b614bb55ac13ae885f2efd32be195ffd984700d9dafcccd3102bdba2f9915S3Bucket7871AF0F" + "Ref": "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51S3BucketF60BDFE6" }, "S3Key": { "Fn::Join": [ @@ -122,7 +122,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersba1b614bb55ac13ae885f2efd32be195ffd984700d9dafcccd3102bdba2f9915S3VersionKey106B28FA" + "Ref": "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51S3VersionKeyA09666EC" } ] } @@ -135,7 +135,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersba1b614bb55ac13ae885f2efd32be195ffd984700d9dafcccd3102bdba2f9915S3VersionKey106B28FA" + "Ref": "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51S3VersionKeyA09666EC" } ] } @@ -230,17 +230,17 @@ } }, "Parameters": { - "AssetParametersba1b614bb55ac13ae885f2efd32be195ffd984700d9dafcccd3102bdba2f9915S3Bucket7871AF0F": { + "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51S3BucketF60BDFE6": { "Type": "String", - "Description": "S3 bucket for asset \"ba1b614bb55ac13ae885f2efd32be195ffd984700d9dafcccd3102bdba2f9915\"" + "Description": "S3 bucket for asset \"e4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51\"" }, - "AssetParametersba1b614bb55ac13ae885f2efd32be195ffd984700d9dafcccd3102bdba2f9915S3VersionKey106B28FA": { + "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51S3VersionKeyA09666EC": { "Type": "String", - "Description": "S3 key for asset version \"ba1b614bb55ac13ae885f2efd32be195ffd984700d9dafcccd3102bdba2f9915\"" + "Description": "S3 key for asset version \"e4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51\"" }, - "AssetParametersba1b614bb55ac13ae885f2efd32be195ffd984700d9dafcccd3102bdba2f9915ArtifactHashADA31147": { + "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51ArtifactHash0BDF74C8": { "Type": "String", - "Description": "Artifact hash for asset \"ba1b614bb55ac13ae885f2efd32be195ffd984700d9dafcccd3102bdba2f9915\"" + "Description": "Artifact hash for asset \"e4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51\"" } }, "Outputs": { @@ -269,4 +269,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.latest-sdk.expected.json b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.latest-sdk.expected.json new file mode 100644 index 0000000000000..592ca86f10420 --- /dev/null +++ b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.latest-sdk.expected.json @@ -0,0 +1,207 @@ +{ + "Resources": { + "TopicBFC7AF6E": { + "Type": "AWS::SNS::Topic" + }, + "Hello4A628BD4": { + "Type": "Custom::AWS", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "AWS679f53fac002430cb0da5b7982bd22872D164C4C", + "Arn" + ] + }, + "Create": { + "service": "SNS", + "action": "publish", + "parameters": { + "Message": "hello", + "TopicArn": { + "Ref": "TopicBFC7AF6E" + } + }, + "physicalResourceId": "hello" + }, + "Update": { + "service": "SNS", + "action": "publish", + "parameters": { + "Message": "hello", + "TopicArn": { + "Ref": "TopicBFC7AF6E" + } + }, + "physicalResourceId": "hello" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleDefaultPolicyD28E1A5E": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:Publish", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleDefaultPolicyD28E1A5E", + "Roles": [ + { + "Ref": "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2" + } + ] + } + }, + "AWS679f53fac002430cb0da5b7982bd22872D164C4C": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51S3BucketF60BDFE6" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51S3VersionKeyA09666EC" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51S3VersionKeyA09666EC" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Environment": { + "Variables": { + "USE_LATEST_SDK": "true" + } + }, + "Timeout": 60 + }, + "DependsOn": [ + "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleDefaultPolicyD28E1A5E", + "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2" + ] + }, + "Bye6C95E5B3": { + "Type": "Custom::AWS", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "AWS679f53fac002430cb0da5b7982bd22872D164C4C", + "Arn" + ] + }, + "Create": { + "service": "SNS", + "action": "publish", + "parameters": { + "Message": "bye", + "TopicArn": { + "Ref": "TopicBFC7AF6E" + } + }, + "physicalResourceId": "bye" + }, + "Update": { + "service": "SNS", + "action": "publish", + "parameters": { + "Message": "bye", + "TopicArn": { + "Ref": "TopicBFC7AF6E" + } + }, + "physicalResourceId": "bye" + } + }, + "DependsOn": [ + "Hello4A628BD4" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + }, + "Parameters": { + "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51S3BucketF60BDFE6": { + "Type": "String", + "Description": "S3 bucket for asset \"e4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51\"" + }, + "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51S3VersionKeyA09666EC": { + "Type": "String", + "Description": "S3 key for asset version \"e4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51\"" + }, + "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51ArtifactHash0BDF74C8": { + "Type": "String", + "Description": "Artifact hash for asset \"e4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.latest-sdk.ts b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.latest-sdk.ts new file mode 100644 index 0000000000000..5d05fd3851542 --- /dev/null +++ b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.latest-sdk.ts @@ -0,0 +1,45 @@ +#!/usr/bin/env node +import sns = require('@aws-cdk/aws-sns'); +import { App, Construct, Stack } from '@aws-cdk/core'; +import { AwsCustomResource } from '../../lib'; + +class TestStack extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + const topic = new sns.Topic(this, 'Topic'); + + const hello = new AwsCustomResource(this, 'Hello', { + onUpdate: { + service: 'SNS', + action: 'publish', + parameters: { + Message: 'hello', + TopicArn: topic.topicArn + }, + physicalResourceId: 'hello', + }, + useLatestSdk: true, + }); + + const bye = new AwsCustomResource(this, 'Bye', { + onUpdate: { + service: 'SNS', + action: 'publish', + parameters: { + Message: 'bye', + TopicArn: topic.topicArn + }, + physicalResourceId: 'bye', + }, + useLatestSdk: true, + }); + bye.node.addDependency(hello); // SDK installation should be skipped in `bye` + } +} + +const app = new App(); + +new TestStack(app, 'aws-cdk-sdk-js-latest'); + +app.synth(); From d8f4aafd627ca28a038f68973f8c8a32349ca96d Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Tue, 17 Dec 2019 14:20:42 +0100 Subject: [PATCH 2/3] always use latest SDK --- .../aws-custom-resource.ts | 26 +-- .../lib/aws-custom-resource/runtime/index.ts | 26 ++- .../aws-custom-resource-provider.test.ts | 16 +- .../aws-custom-resource.test.ts | 27 +-- .../integ.aws-custom-resource.expected.json | 20 +- .../integ.latest-sdk.expected.json | 207 ------------------ .../aws-custom-resource/integ.latest-sdk.ts | 45 ---- 7 files changed, 42 insertions(+), 325 deletions(-) delete mode 100644 packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.latest-sdk.expected.json delete mode 100644 packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.latest-sdk.ts diff --git a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts index f95fada45b90a..3696a303db783 100644 --- a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts +++ b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts @@ -149,26 +149,9 @@ export interface AwsCustomResourceProps { /** * The timeout for the Lambda function implementing this custom resource. * - * @default Duration.seconds(30) when `useLatestSdk` is false, - * Duration.seconds(60) otherwise. + * @default Duration.seconds(60) */ readonly timeout?: cdk.Duration - - /** - * Install and use the latest version of AWS SDK JS. - * - * When set to `true`, the latest v2 of AWS SDK JS will be installed when a - * new container is initialized for the Lambda function implementing this - * custom resource. Subsequent executions reusing this container will skip - * installation. - * - * As this custom resource uses a singleton Lambda function, it's important - * to note that this will apply to **all** `AwsCustomResource`s in the stack - * even if only one instance sets this parameters to `true`. - * - * @default false - */ - readonly useLatestSdk?: boolean; } export class AwsCustomResource extends cdk.Construct implements iam.IGrantable { @@ -195,13 +178,8 @@ export class AwsCustomResource extends cdk.Construct implements iam.IGrantable { handler: 'index.handler', uuid: '679f53fa-c002-430c-b0da-5b7982bd2287', lambdaPurpose: 'AWS', - timeout: props.timeout || (props.useLatestSdk ? cdk.Duration.seconds(60) : cdk.Duration.seconds(30)), + timeout: props.timeout || cdk.Duration.seconds(60), role: props.role, - environment: props.useLatestSdk - ? { - USE_LATEST_SDK: 'true' - } - : {} }); this.grantPrincipal = provider.grantPrincipal; diff --git a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts index 1299004b51a1a..5605e5f7d34ea 100644 --- a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts +++ b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts @@ -53,21 +53,37 @@ 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'); - execSync('HOME=/tmp npm install aws-sdk@2 --production --no-package-lock --prefix /tmp'); + // 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; } export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent, context: AWSLambda.Context) { try { - if (process.env.USE_LATEST_SDK === 'true' && !latestSdkInstalled) { - installLatestSdk(); + 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'); } - const AWS = process.env.USE_LATEST_SDK ? require('/tmp/node_modules/aws-sdk') : 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 } = {}; diff --git a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource-provider.test.ts b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource-provider.test.ts index 9f702c24641fd..82efb5a977d09 100644 --- a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource-provider.test.ts +++ b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource-provider.test.ts @@ -6,6 +6,8 @@ import sinon = require('sinon'); import { AwsSdkCall } from '../../lib'; import { flatten, handler } from '../../lib/aws-custom-resource/runtime'; +AWS.setSDK(require.resolve('aws-sdk')); + console.log = jest.fn(); // tslint:disable-line no-console const eventCommon = { @@ -24,13 +26,13 @@ function createRequest(bodyPredicate: (body: AWSLambda.CloudFormationCustomResou } beforeEach(() => { - AWS.setSDK(require.resolve('aws-sdk')); + process.env.USE_NORMAL_SDK = 'true'; }); afterEach(() => { AWS.restore(); nock.cleanAll(); - delete process.env.USE_LATEST_SDK; + delete process.env.USE_NORMAL_SDK; }); test('create event with physical resource id path', async () => { @@ -343,12 +345,10 @@ test('flatten correctly flattens a nested object', () => { }); }); -test('installs latest sdk when needed', async () => { - AWS.setSDK('/tmp/node_modules/aws-sdk'); - - fs.removeSync('/tmp/node_modules/aws-sdk'); +test('installs the latest SDK', async () => { + const tmpPath = '/tmp/node_modules/aws-sdk'; - process.env.USE_LATEST_SDK = 'true'; + fs.remove(tmpPath); const publishFake = sinon.fake.resolves({}); @@ -379,5 +379,5 @@ test('installs latest sdk when needed', async () => { expect(request.isDone()).toBeTruthy(); - expect(fs.existsSync('/tmp/node_modules/aws-sdk')).toBe(true); + expect(() => require.resolve(tmpPath)).not.toThrow(); }); diff --git a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource.test.ts b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource.test.ts index 61588f8370daf..06a88a166afe0 100644 --- a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource.test.ts +++ b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource.test.ts @@ -220,7 +220,7 @@ test('timeout defaults to 30 seconds', () => { // THEN expect(stack).toHaveResource('AWS::Lambda::Function', { - Timeout: 30 + Timeout: 60 }); }); @@ -307,28 +307,3 @@ test('can use existing role', () => { expect(stack).not.toHaveResource('AWS::IAM::Role'); }); - -test('useLatestSdk sets environment variable and increases default timeout', () => { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new AwsCustomResource(stack, 'AwsSdk', { - onCreate: { - service: 'service', - action: 'action', - physicalResourceId: 'id' - }, - useLatestSdk: true, - }); - - // THEN - expect(stack).toHaveResource('AWS::Lambda::Function', { - Environment: { - Variables: { - USE_LATEST_SDK: 'true', - }, - }, - Timeout: 60, - }); -}); diff --git a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.expected.json b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.expected.json index 396a87c4255ee..94df33861bf1a 100644 --- a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.expected.json +++ b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.expected.json @@ -109,7 +109,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51S3BucketF60BDFE6" + "Ref": "AssetParameters683d5f66ffd9a473e40d396738e7f632af308796861f4c6495dd2eaf802aea52S3Bucket5D287406" }, "S3Key": { "Fn::Join": [ @@ -122,7 +122,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51S3VersionKeyA09666EC" + "Ref": "AssetParameters683d5f66ffd9a473e40d396738e7f632af308796861f4c6495dd2eaf802aea52S3VersionKey636C806A" } ] } @@ -135,7 +135,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51S3VersionKeyA09666EC" + "Ref": "AssetParameters683d5f66ffd9a473e40d396738e7f632af308796861f4c6495dd2eaf802aea52S3VersionKey636C806A" } ] } @@ -153,7 +153,7 @@ ] }, "Runtime": "nodejs12.x", - "Timeout": 30 + "Timeout": 60 }, "DependsOn": [ "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleDefaultPolicyD28E1A5E", @@ -230,17 +230,17 @@ } }, "Parameters": { - "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51S3BucketF60BDFE6": { + "AssetParameters683d5f66ffd9a473e40d396738e7f632af308796861f4c6495dd2eaf802aea52S3Bucket5D287406": { "Type": "String", - "Description": "S3 bucket for asset \"e4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51\"" + "Description": "S3 bucket for asset \"683d5f66ffd9a473e40d396738e7f632af308796861f4c6495dd2eaf802aea52\"" }, - "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51S3VersionKeyA09666EC": { + "AssetParameters683d5f66ffd9a473e40d396738e7f632af308796861f4c6495dd2eaf802aea52S3VersionKey636C806A": { "Type": "String", - "Description": "S3 key for asset version \"e4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51\"" + "Description": "S3 key for asset version \"683d5f66ffd9a473e40d396738e7f632af308796861f4c6495dd2eaf802aea52\"" }, - "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51ArtifactHash0BDF74C8": { + "AssetParameters683d5f66ffd9a473e40d396738e7f632af308796861f4c6495dd2eaf802aea52ArtifactHash8B257720": { "Type": "String", - "Description": "Artifact hash for asset \"e4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51\"" + "Description": "Artifact hash for asset \"683d5f66ffd9a473e40d396738e7f632af308796861f4c6495dd2eaf802aea52\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.latest-sdk.expected.json b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.latest-sdk.expected.json deleted file mode 100644 index 592ca86f10420..0000000000000 --- a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.latest-sdk.expected.json +++ /dev/null @@ -1,207 +0,0 @@ -{ - "Resources": { - "TopicBFC7AF6E": { - "Type": "AWS::SNS::Topic" - }, - "Hello4A628BD4": { - "Type": "Custom::AWS", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AWS679f53fac002430cb0da5b7982bd22872D164C4C", - "Arn" - ] - }, - "Create": { - "service": "SNS", - "action": "publish", - "parameters": { - "Message": "hello", - "TopicArn": { - "Ref": "TopicBFC7AF6E" - } - }, - "physicalResourceId": "hello" - }, - "Update": { - "service": "SNS", - "action": "publish", - "parameters": { - "Message": "hello", - "TopicArn": { - "Ref": "TopicBFC7AF6E" - } - }, - "physicalResourceId": "hello" - } - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, - "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] - } - }, - "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleDefaultPolicyD28E1A5E": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sns:Publish", - "Effect": "Allow", - "Resource": "*" - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleDefaultPolicyD28E1A5E", - "Roles": [ - { - "Ref": "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2" - } - ] - } - }, - "AWS679f53fac002430cb0da5b7982bd22872D164C4C": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51S3BucketF60BDFE6" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51S3VersionKeyA09666EC" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51S3VersionKeyA09666EC" - } - ] - } - ] - } - ] - ] - } - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2", - "Arn" - ] - }, - "Runtime": "nodejs12.x", - "Environment": { - "Variables": { - "USE_LATEST_SDK": "true" - } - }, - "Timeout": 60 - }, - "DependsOn": [ - "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleDefaultPolicyD28E1A5E", - "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2" - ] - }, - "Bye6C95E5B3": { - "Type": "Custom::AWS", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AWS679f53fac002430cb0da5b7982bd22872D164C4C", - "Arn" - ] - }, - "Create": { - "service": "SNS", - "action": "publish", - "parameters": { - "Message": "bye", - "TopicArn": { - "Ref": "TopicBFC7AF6E" - } - }, - "physicalResourceId": "bye" - }, - "Update": { - "service": "SNS", - "action": "publish", - "parameters": { - "Message": "bye", - "TopicArn": { - "Ref": "TopicBFC7AF6E" - } - }, - "physicalResourceId": "bye" - } - }, - "DependsOn": [ - "Hello4A628BD4" - ], - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - } - }, - "Parameters": { - "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51S3BucketF60BDFE6": { - "Type": "String", - "Description": "S3 bucket for asset \"e4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51\"" - }, - "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51S3VersionKeyA09666EC": { - "Type": "String", - "Description": "S3 key for asset version \"e4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51\"" - }, - "AssetParameterse4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51ArtifactHash0BDF74C8": { - "Type": "String", - "Description": "Artifact hash for asset \"e4b42299f3453e4ae0692c275de0a1710497056be58f61f84f605652e8233c51\"" - } - } -} \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.latest-sdk.ts b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.latest-sdk.ts deleted file mode 100644 index 5d05fd3851542..0000000000000 --- a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.latest-sdk.ts +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env node -import sns = require('@aws-cdk/aws-sns'); -import { App, Construct, Stack } from '@aws-cdk/core'; -import { AwsCustomResource } from '../../lib'; - -class TestStack extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const topic = new sns.Topic(this, 'Topic'); - - const hello = new AwsCustomResource(this, 'Hello', { - onUpdate: { - service: 'SNS', - action: 'publish', - parameters: { - Message: 'hello', - TopicArn: topic.topicArn - }, - physicalResourceId: 'hello', - }, - useLatestSdk: true, - }); - - const bye = new AwsCustomResource(this, 'Bye', { - onUpdate: { - service: 'SNS', - action: 'publish', - parameters: { - Message: 'bye', - TopicArn: topic.topicArn - }, - physicalResourceId: 'bye', - }, - useLatestSdk: true, - }); - bye.node.addDependency(hello); // SDK installation should be skipped in `bye` - } -} - -const app = new App(); - -new TestStack(app, 'aws-cdk-sdk-js-latest'); - -app.synth(); From 1d2115cc43894f4e43457e431a69d825c0bd5877 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 18 Dec 2019 09:59:03 +0100 Subject: [PATCH 3/3] eslint --- .../lib/aws-custom-resource/runtime/index.ts | 1 + .../integ.aws-custom-resource.expected.json | 20 +++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts index d22a5c36da27f..47e7e03319485 100644 --- a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts +++ b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts @@ -63,6 +63,7 @@ function installLatestSdk(): void { 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; diff --git a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.expected.json b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.expected.json index 6705306f7af6c..f7bc469e9b2b4 100644 --- a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.expected.json +++ b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.expected.json @@ -109,7 +109,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters683d5f66ffd9a473e40d396738e7f632af308796861f4c6495dd2eaf802aea52S3Bucket5D287406" + "Ref": "AssetParameters138d5170d70070f442a09ab6b7c09fecfa7278d8b8c0e64fdc2addf284172a5eS3BucketC954C005" }, "S3Key": { "Fn::Join": [ @@ -122,7 +122,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters683d5f66ffd9a473e40d396738e7f632af308796861f4c6495dd2eaf802aea52S3VersionKey636C806A" + "Ref": "AssetParameters138d5170d70070f442a09ab6b7c09fecfa7278d8b8c0e64fdc2addf284172a5eS3VersionKey2D066AE2" } ] } @@ -135,7 +135,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters683d5f66ffd9a473e40d396738e7f632af308796861f4c6495dd2eaf802aea52S3VersionKey636C806A" + "Ref": "AssetParameters138d5170d70070f442a09ab6b7c09fecfa7278d8b8c0e64fdc2addf284172a5eS3VersionKey2D066AE2" } ] } @@ -230,17 +230,17 @@ } }, "Parameters": { - "AssetParameters683d5f66ffd9a473e40d396738e7f632af308796861f4c6495dd2eaf802aea52S3Bucket5D287406": { + "AssetParameters138d5170d70070f442a09ab6b7c09fecfa7278d8b8c0e64fdc2addf284172a5eS3BucketC954C005": { "Type": "String", - "Description": "S3 bucket for asset \"683d5f66ffd9a473e40d396738e7f632af308796861f4c6495dd2eaf802aea52\"" + "Description": "S3 bucket for asset \"138d5170d70070f442a09ab6b7c09fecfa7278d8b8c0e64fdc2addf284172a5e\"" }, - "AssetParameters683d5f66ffd9a473e40d396738e7f632af308796861f4c6495dd2eaf802aea52S3VersionKey636C806A": { + "AssetParameters138d5170d70070f442a09ab6b7c09fecfa7278d8b8c0e64fdc2addf284172a5eS3VersionKey2D066AE2": { "Type": "String", - "Description": "S3 key for asset version \"683d5f66ffd9a473e40d396738e7f632af308796861f4c6495dd2eaf802aea52\"" + "Description": "S3 key for asset version \"138d5170d70070f442a09ab6b7c09fecfa7278d8b8c0e64fdc2addf284172a5e\"" }, - "AssetParameters683d5f66ffd9a473e40d396738e7f632af308796861f4c6495dd2eaf802aea52ArtifactHash8B257720": { + "AssetParameters138d5170d70070f442a09ab6b7c09fecfa7278d8b8c0e64fdc2addf284172a5eArtifactHash5852F39A": { "Type": "String", - "Description": "Artifact hash for asset \"683d5f66ffd9a473e40d396738e7f632af308796861f4c6495dd2eaf802aea52\"" + "Description": "Artifact hash for asset \"138d5170d70070f442a09ab6b7c09fecfa7278d8b8c0e64fdc2addf284172a5e\"" } }, "Outputs": { @@ -269,4 +269,4 @@ } } } -} +} \ No newline at end of file