From 6423f642ee40cca60b4adc961028a146a3fc87bd Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 18 Nov 2019 16:39:55 +0100 Subject: [PATCH] feat(lambda-layers): add AWS SDK JS layer Add a new package `@aws-cdk/lambda-layers` that offers pre-built layers. Layer building is currently only supported for Node.js and works as follows: * List static members of the `Layer` class (= layers to build) * Create a dummy `package.json` file containing only dependencies for this layer in the `layers` folder. Dependencies versions are extracted from the `package.json` of `@aws-cdk/lambda-layers`. * Run `npm install` As layer dependencies are extracted from the main `package.json` they can be picked up by `dependabot`, automatically updating the layer when needed. The `getLayerVersion()` method returns a singleton `lambda.LayerVersion` that can be used in Lambda functions. The first layer is `AWS_SDK_JS` which offers a more recent version of the AWS SDK than the one included in the Lambda runtime (it will also be updated more frequently). Use the `AWS_SDK_JS` layer in `AwsCustomResource` and `AwsApi` event target. Closes #2689 Closes #5063 --- .../aws-events-targets/lib/aws-api.ts | 2 + .../@aws-cdk/aws-events-targets/package.json | 8 +- .../test/aws-api/integ.aws-api.expected.json | 66 +++++- .../aws-custom-resource.ts | 2 + .../integ.aws-custom-resource.expected.json | 66 +++++- packages/@aws-cdk/lambda-layers/.gitignore | 17 ++ packages/@aws-cdk/lambda-layers/.npmignore | 18 ++ packages/@aws-cdk/lambda-layers/LICENSE | 201 ++++++++++++++++++ packages/@aws-cdk/lambda-layers/NOTICE | 2 + .../lambda-layers/build-tools/build.ts | 70 ++++++ packages/@aws-cdk/lambda-layers/lib/index.ts | 1 + packages/@aws-cdk/lambda-layers/lib/layers.ts | 99 +++++++++ packages/@aws-cdk/lambda-layers/package.json | 102 +++++++++ .../lambda-layers/test/handler/index.ts | 5 + .../test/integ.aws-sdk-js-layer.expected.json | 167 +++++++++++++++ .../test/integ.aws-sdk-js-layer.ts | 24 +++ .../@aws-cdk/lambda-layers/test/layer.test.ts | 17 ++ packages/decdk/package.json | 1 + 18 files changed, 863 insertions(+), 5 deletions(-) create mode 100644 packages/@aws-cdk/lambda-layers/.gitignore create mode 100644 packages/@aws-cdk/lambda-layers/.npmignore create mode 100644 packages/@aws-cdk/lambda-layers/LICENSE create mode 100644 packages/@aws-cdk/lambda-layers/NOTICE create mode 100644 packages/@aws-cdk/lambda-layers/build-tools/build.ts create mode 100644 packages/@aws-cdk/lambda-layers/lib/index.ts create mode 100644 packages/@aws-cdk/lambda-layers/lib/layers.ts create mode 100644 packages/@aws-cdk/lambda-layers/package.json create mode 100644 packages/@aws-cdk/lambda-layers/test/handler/index.ts create mode 100644 packages/@aws-cdk/lambda-layers/test/integ.aws-sdk-js-layer.expected.json create mode 100644 packages/@aws-cdk/lambda-layers/test/integ.aws-sdk-js-layer.ts create mode 100644 packages/@aws-cdk/lambda-layers/test/layer.test.ts diff --git a/packages/@aws-cdk/aws-events-targets/lib/aws-api.ts b/packages/@aws-cdk/aws-events-targets/lib/aws-api.ts index 3eab3d00321de..62ada4cb41783 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/aws-api.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/aws-api.ts @@ -1,6 +1,7 @@ import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); import lambda = require('@aws-cdk/aws-lambda'); +import { Layer } from '@aws-cdk/lambda-layers'; import path = require('path'); import metadata = require('./sdk-api-metadata.json'); import { addLambdaPermission } from './util'; @@ -87,6 +88,7 @@ export class AwsApi implements events.IRuleTarget { handler: 'index.handler', uuid: 'b4cf1abd-4e4f-4bc6-9944-1af7ccd9ec37', lambdaPurpose: 'AWS', + layers: [Layer.AWS_SDK_JS.getLayerVersion(rule as events.Rule)] }); if (this.props.policyStatement) { diff --git a/packages/@aws-cdk/aws-events-targets/package.json b/packages/@aws-cdk/aws-events-targets/package.json index 0d114b8d56ce7..7714d3b24c5cc 100644 --- a/packages/@aws-cdk/aws-events-targets/package.json +++ b/packages/@aws-cdk/aws-events-targets/package.json @@ -102,7 +102,8 @@ "@aws-cdk/aws-sns-subscriptions": "1.16.1", "@aws-cdk/aws-sqs": "1.16.1", "@aws-cdk/aws-stepfunctions": "1.16.1", - "@aws-cdk/core": "1.16.1" + "@aws-cdk/core": "1.16.1", + "@aws-cdk/lambda-layers": "1.16.1" }, "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { @@ -118,7 +119,8 @@ "@aws-cdk/aws-sns-subscriptions": "1.16.1", "@aws-cdk/aws-sqs": "1.16.1", "@aws-cdk/aws-stepfunctions": "1.16.1", - "@aws-cdk/core": "1.16.1" + "@aws-cdk/core": "1.16.1", + "@aws-cdk/lambda-layers": "1.16.1" }, "engines": { "node": ">= 10.3.0" @@ -135,4 +137,4 @@ "props-default-doc:@aws-cdk/aws-events-targets.EcsTaskProps.containerOverrides" ] } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-events-targets/test/aws-api/integ.aws-api.expected.json b/packages/@aws-cdk/aws-events-targets/test/aws-api/integ.aws-api.expected.json index 9a9169e68a2b3..1ae2a84325810 100644 --- a/packages/@aws-cdk/aws-events-targets/test/aws-api/integ.aws-api.expected.json +++ b/packages/@aws-cdk/aws-events-targets/test/aws-api/integ.aws-api.expected.json @@ -29,6 +29,53 @@ ] } }, + "layerawssdkjsB2ED5E7B": { + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": { + "Ref": "AssetParameters1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7S3Bucket9111837A" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7S3VersionKey5C696394" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7S3VersionKey5C696394" + } + ] + } + ] + } + ] + ] + } + }, + "CompatibleRuntimes": [ + "nodejs10.x" + ], + "Description": "AWS SDK JS" + } + }, "AWSb4cf1abd4e4f4bc699441af7ccd9ec37ServiceRole9FFE9C50": { "Type": "AWS::IAM::Role", "Properties": { @@ -139,7 +186,12 @@ "Arn" ] }, - "Runtime": "nodejs10.x" + "Runtime": "nodejs10.x", + "Layers": [ + { + "Ref": "layerawssdkjsB2ED5E7B" + } + ] }, "DependsOn": [ "AWSb4cf1abd4e4f4bc699441af7ccd9ec37ServiceRoleDefaultPolicy4D43A7C1", @@ -219,6 +271,18 @@ } }, "Parameters": { + "AssetParameters1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7S3Bucket9111837A": { + "Type": "String", + "Description": "S3 bucket for asset \"1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7\"" + }, + "AssetParameters1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7S3VersionKey5C696394": { + "Type": "String", + "Description": "S3 key for asset version \"1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7\"" + }, + "AssetParameters1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7ArtifactHash24D48A0B": { + "Type": "String", + "Description": "Artifact hash for asset \"1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7\"" + }, "AssetParameters7f96a9f879348c0e50ac55d67a74edede0cc96cf0615355938c45801708cd063S3Bucket05714AA7": { "Type": "String", "Description": "S3 bucket for asset \"7f96a9f879348c0e50ac55d67a74edede0cc96cf0615355938c45801708cd063\"" 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 912eb03c8a3c7..822380af53186 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 @@ -2,6 +2,7 @@ import { CustomResource, CustomResourceProvider } from '@aws-cdk/aws-cloudformat import iam = require('@aws-cdk/aws-iam'); import lambda = require('@aws-cdk/aws-lambda'); import cdk = require('@aws-cdk/core'); +import { Layer } from '@aws-cdk/lambda-layers'; import fs = require('fs'); import path = require('path'); @@ -180,6 +181,7 @@ export class AwsCustomResource extends cdk.Construct implements iam.IGrantable { lambdaPurpose: 'AWS', timeout: props.timeout || cdk.Duration.seconds(30), role: props.role, + layers: [Layer.AWS_SDK_JS.getLayerVersion(this)] }); this.grantPrincipal = provider.grantPrincipal; 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 b214ee560ec67..8d8824c7c1ae9 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 @@ -42,6 +42,53 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, + "layerawssdkjsB2ED5E7B": { + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": { + "Ref": "AssetParameters1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7S3Bucket9111837A" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7S3VersionKey5C696394" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7S3VersionKey5C696394" + } + ] + } + ] + } + ] + ] + } + }, + "CompatibleRuntimes": [ + "nodejs10.x" + ], + "Description": "AWS SDK JS" + } + }, "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2": { "Type": "AWS::IAM::Role", "Properties": { @@ -153,6 +200,11 @@ ] }, "Runtime": "nodejs10.x", + "Layers": [ + { + "Ref": "layerawssdkjsB2ED5E7B" + } + ], "Timeout": 30 }, "DependsOn": [ @@ -230,6 +282,18 @@ } }, "Parameters": { + "AssetParameters1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7S3Bucket9111837A": { + "Type": "String", + "Description": "S3 bucket for asset \"1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7\"" + }, + "AssetParameters1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7S3VersionKey5C696394": { + "Type": "String", + "Description": "S3 key for asset version \"1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7\"" + }, + "AssetParameters1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7ArtifactHash24D48A0B": { + "Type": "String", + "Description": "Artifact hash for asset \"1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7\"" + }, "AssetParametersba1b614bb55ac13ae885f2efd32be195ffd984700d9dafcccd3102bdba2f9915S3Bucket7871AF0F": { "Type": "String", "Description": "S3 bucket for asset \"ba1b614bb55ac13ae885f2efd32be195ffd984700d9dafcccd3102bdba2f9915\"" @@ -269,4 +333,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/lambda-layers/.gitignore b/packages/@aws-cdk/lambda-layers/.gitignore new file mode 100644 index 0000000000000..8b648931c4520 --- /dev/null +++ b/packages/@aws-cdk/lambda-layers/.gitignore @@ -0,0 +1,17 @@ +*.js +*.js.map +*.d.ts +node_modules +dist +tsconfig.json +tslint.json +.jsii + +.LAST_BUILD +.LAST_PACKAGE +*.snk +.nyc_output +coverage +.nycrc + +layers diff --git a/packages/@aws-cdk/lambda-layers/.npmignore b/packages/@aws-cdk/lambda-layers/.npmignore new file mode 100644 index 0000000000000..f5a63a96df103 --- /dev/null +++ b/packages/@aws-cdk/lambda-layers/.npmignore @@ -0,0 +1,18 @@ +# Don't include original .ts files when doing `npm pack` +*.ts +!*.d.ts +coverage +.nyc_output +*.tgz + +dist +.LAST_PACKAGE +.LAST_BUILD +!*.js + +# Include .jsii +!.jsii + +*.snk + +*.tsbuildinfo diff --git a/packages/@aws-cdk/lambda-layers/LICENSE b/packages/@aws-cdk/lambda-layers/LICENSE new file mode 100644 index 0000000000000..46c185646b439 --- /dev/null +++ b/packages/@aws-cdk/lambda-layers/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/@aws-cdk/lambda-layers/NOTICE b/packages/@aws-cdk/lambda-layers/NOTICE new file mode 100644 index 0000000000000..8585168af8b7d --- /dev/null +++ b/packages/@aws-cdk/lambda-layers/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/lambda-layers/build-tools/build.ts b/packages/@aws-cdk/lambda-layers/build-tools/build.ts new file mode 100644 index 0000000000000..f5c7fae37d862 --- /dev/null +++ b/packages/@aws-cdk/lambda-layers/build-tools/build.ts @@ -0,0 +1,70 @@ +import { RuntimeFamily } from '@aws-cdk/aws-lambda'; +import child_process = require('child_process'); +import fs = require('fs-extra'); +import path = require('path'); +import { Layer } from '../lib'; + +interface PackageJson { + devDependencies: { [key: string]: string }; +} + +async function buildLayer(layer: Layer, pkg: PackageJson) { + switch (layer.runtimeFamily) { + case RuntimeFamily.NODEJS: + await buildJsLayer(layer, pkg); + break; + default: + throw new Error('This runtime family is not supported'); + } +} + +async function buildJsLayer(layer: Layer, pkg: PackageJson) { + const basePath = path.join('layers', layer.name, 'nodejs'); + + // Create folder for layer + fs.ensureDirSync(basePath); + + // Create a dummy package.json with only 'dependencies' key + const dependencies: { [key: string]: string } = {}; + for (const dep of layer.dependencies) { + if (!pkg.devDependencies[dep]) { + throw new Error(`Dependency '${dep}' used in layer '${layer.name}' must be declared in the 'devDependencies' of 'package.json'`); + } + dependencies[dep] = pkg.devDependencies[dep].replace(/[^\d.]/g, ''); // Lookup version in main `package.json` and use fixed version + } + + fs.writeFileSync(path.join(basePath, 'package.json'), JSON.stringify({ dependencies })); + + // Install packages + await npmInstall(basePath); +} + +async function npmInstall(layerPath: string): Promise { + const child = child_process.spawn('npm', ['i', '--no-package-lock'], { + stdio: [ 'inherit', 'inherit', 'inherit' ], + cwd: layerPath, + }); + return new Promise((ok, ko) => { + child.once('exit', (status: any) => { + if (status === 0) { + return ok(); + } else { + return ko(new Error(`Faild to install layer dependencies for layer located at ${layerPath}`)); + } + }); + child.once('error', ko); + }); +} + +async function main() { + const pkg: PackageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8')); + for (const layer of Object.values(Layer)) { + await buildLayer(layer, pkg); + } +} + +main().catch(e => { + // tslint:disable-next-line: no-console + console.error(e); + process.exit(-1); +}); diff --git a/packages/@aws-cdk/lambda-layers/lib/index.ts b/packages/@aws-cdk/lambda-layers/lib/index.ts new file mode 100644 index 0000000000000..d91c9cf0ced8c --- /dev/null +++ b/packages/@aws-cdk/lambda-layers/lib/index.ts @@ -0,0 +1 @@ +export * from './layers'; diff --git a/packages/@aws-cdk/lambda-layers/lib/layers.ts b/packages/@aws-cdk/lambda-layers/lib/layers.ts new file mode 100644 index 0000000000000..5ce0e22f6e940 --- /dev/null +++ b/packages/@aws-cdk/lambda-layers/lib/layers.ts @@ -0,0 +1,99 @@ +import lambda = require('@aws-cdk/aws-lambda'); +import { Construct, Stack } from '@aws-cdk/core'; +import path = require('path'); + +/** + * Properties for a layer + */ +export interface LayerProps { + /** + * The name of the layer + */ + readonly name: string; + + /** + * Dependencies for the layer + */ + readonly dependencies: string[]; + + /** + * The runtime family of the layer + * + * @default RuntimeFamily.NODEJS + */ + readonly runtimeFamily?: lambda.RuntimeFamily + + /** + * The description of the layer + * + * @default - no description + */ + readonly description?: string; + + /** + * Compatible runtimes for the layer + * + * @default [Runtime.NODEJS_10_X] + */ + readonly compatibleRuntimes?: lambda.Runtime[]; +} + +/** + * A layer + */ +export class Layer { + /** + * AWS SDK JS layer + * + * Use this layer to have a more recent version of the AWS SDK than the one + * included in the Lambda runtime. + */ + public static readonly AWS_SDK_JS = new Layer({ + name: 'aws-sdk-js', + description: 'AWS SDK JS', + dependencies: ['aws-sdk'], + }); + + /** + * The name of the layer + */ + public readonly name: string; + + /** + * Dependencies of the layer + */ + public readonly dependencies: string[]; + + /** + * The runtime family of the layer + */ + public readonly runtimeFamily: lambda.RuntimeFamily; + + private readonly compatibleRuntimes: lambda.Runtime[]; + private readonly description?: string; + + constructor(props: LayerProps) { + this.name = props.name; + this.dependencies = props.dependencies; + this.runtimeFamily = props.runtimeFamily || lambda.RuntimeFamily.NODEJS; + this.compatibleRuntimes = props.compatibleRuntimes || [lambda.Runtime.NODEJS_10_X], + this.description = props.description; + } + + /** + * Returns a singleton layer version for this layer + */ + public getLayerVersion(scope: Construct) { + const id = `layer-${this.name}`; + const layer = Stack.of(scope).node.tryFindChild(id) as lambda.LayerVersion; + if (layer) { + return layer; + } + + return new lambda.LayerVersion(Stack.of(scope), id, { + code: lambda.Code.fromAsset(path.join(require.resolve('@aws-cdk/lambda-layers'), '../../layers', this.name)), + compatibleRuntimes: this.compatibleRuntimes, + description: this.description + }); + } +} diff --git a/packages/@aws-cdk/lambda-layers/package.json b/packages/@aws-cdk/lambda-layers/package.json new file mode 100644 index 0000000000000..38e4b08373a32 --- /dev/null +++ b/packages/@aws-cdk/lambda-layers/package.json @@ -0,0 +1,102 @@ +{ + "name": "@aws-cdk/lambda-layers", + "version": "1.16.1", + "description": "Useful Lambda layers", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awscdk.lambda.layers", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "cdk-lambda-layers", + "versionSuffix": ".DEVPREVIEW" + } + }, + "dotnet": { + "namespace": "Amazon.CDK.LambdaLayers", + "packageId": "Amazon.CDK.LambdaLayers", + "signAssembly": true, + "assemblyOriginatorKeyFile": "../../key.snk", + "versionSuffix": "-devpreview", + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "python": { + "distName": "aws-cdk.lambda-layers", + "module": "aws_cdk.lambda_layers" + } + } + }, + "pkglint": { + "exclude": ["package-info/scripts/build"] + }, + "cdk-build": { + "pre": [ + "rm -rf layers && mkdir -p layers" + ] + }, + "scripts": { + "build": "cdk-build && node build-tools/build", + "watch": "cdk-watch", + "lint": "cdk-lint", + "test": "cdk-test", + "integ": "cdk-integ", + "pkglint": "pkglint -f", + "awslint": "cdk-awslint", + "package": "cdk-package", + "build+test+package": "npm run build+test && npm run package", + "build+test": "npm run build && npm test", + "compat": "cdk-compat" + }, + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "aws-sdk": "^2.573.0", + "cdk-build-tools": "1.16.1", + "cdk-integ-tools": "1.16.1", + "fs-extra": "^8.1.0", + "pkglint": "1.16.1" + }, + "dependencies": { + "@aws-cdk/aws-lambda": "1.16.1", + "@aws-cdk/core": "1.16.1" + }, + "peerDependencies": { + "@aws-cdk/aws-lambda": "1.16.1", + "@aws-cdk/core": "1.16.1" + }, + "repository": { + "url": "https://github.com/aws/aws-cdk.git", + "type": "git", + "directory": "packages/@aws-cdk/lambda-layers" + }, + "keywords": [ + "aws", + "cdk", + "sdk", + "layer", + "lambda" + ], + "homepage": "https://github.com/aws/aws-cdk", + "jest": { + "moduleFileExtensions": [ + "js" + ], + "coverageThreshold": { + "global": { + "branches": 70, + "statements": 80 + } + } + }, + "engines": { + "node": ">= 10.3.0" + }, + "stability": "experimental" +} diff --git a/packages/@aws-cdk/lambda-layers/test/handler/index.ts b/packages/@aws-cdk/lambda-layers/test/handler/index.ts new file mode 100644 index 0000000000000..ba12bfaf54ba2 --- /dev/null +++ b/packages/@aws-cdk/lambda-layers/test/handler/index.ts @@ -0,0 +1,5 @@ +import AWS = require('aws-sdk'); + +export async function handler() { + console.log(`AWS SDK VERSION: ${(AWS as any).VERSION}`); // tslint:disable-line no-console +} diff --git a/packages/@aws-cdk/lambda-layers/test/integ.aws-sdk-js-layer.expected.json b/packages/@aws-cdk/lambda-layers/test/integ.aws-sdk-js-layer.expected.json new file mode 100644 index 0000000000000..74562fe3cc684 --- /dev/null +++ b/packages/@aws-cdk/lambda-layers/test/integ.aws-sdk-js-layer.expected.json @@ -0,0 +1,167 @@ +{ + "Resources": { + "layerawssdkjsB2ED5E7B": { + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": { + "Ref": "AssetParameters1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7S3Bucket9111837A" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7S3VersionKey5C696394" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7S3VersionKey5C696394" + } + ] + } + ] + } + ] + ] + } + }, + "CompatibleRuntimes": [ + "nodejs10.x" + ], + "Description": "AWS SDK JS" + } + }, + "FnServiceRoleB9001A96": { + "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" + ] + ] + } + ] + } + }, + "Fn9270CBC0": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters3029bf99cdb719d5ae547a23ca24c266876979a11abd44a7c45713a399b108ffS3Bucket6330678D" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters3029bf99cdb719d5ae547a23ca24c266876979a11abd44a7c45713a399b108ffS3VersionKey49DDA46C" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters3029bf99cdb719d5ae547a23ca24c266876979a11abd44a7c45713a399b108ffS3VersionKey49DDA46C" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "FnServiceRoleB9001A96", + "Arn" + ] + }, + "Runtime": "nodejs10.x", + "Layers": [ + { + "Ref": "layerawssdkjsB2ED5E7B" + } + ] + }, + "DependsOn": [ + "FnServiceRoleB9001A96" + ] + } + }, + "Parameters": { + "AssetParameters1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7S3Bucket9111837A": { + "Type": "String", + "Description": "S3 bucket for asset \"1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7\"" + }, + "AssetParameters1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7S3VersionKey5C696394": { + "Type": "String", + "Description": "S3 key for asset version \"1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7\"" + }, + "AssetParameters1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7ArtifactHash24D48A0B": { + "Type": "String", + "Description": "Artifact hash for asset \"1225cfb84ab98458f900aca0912b08536c3fc65797fbc5fa9bea7f85e756a1c7\"" + }, + "AssetParameters3029bf99cdb719d5ae547a23ca24c266876979a11abd44a7c45713a399b108ffS3Bucket6330678D": { + "Type": "String", + "Description": "S3 bucket for asset \"3029bf99cdb719d5ae547a23ca24c266876979a11abd44a7c45713a399b108ff\"" + }, + "AssetParameters3029bf99cdb719d5ae547a23ca24c266876979a11abd44a7c45713a399b108ffS3VersionKey49DDA46C": { + "Type": "String", + "Description": "S3 key for asset version \"3029bf99cdb719d5ae547a23ca24c266876979a11abd44a7c45713a399b108ff\"" + }, + "AssetParameters3029bf99cdb719d5ae547a23ca24c266876979a11abd44a7c45713a399b108ffArtifactHashF817E368": { + "Type": "String", + "Description": "Artifact hash for asset \"3029bf99cdb719d5ae547a23ca24c266876979a11abd44a7c45713a399b108ff\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/lambda-layers/test/integ.aws-sdk-js-layer.ts b/packages/@aws-cdk/lambda-layers/test/integ.aws-sdk-js-layer.ts new file mode 100644 index 0000000000000..2fe35102d62d6 --- /dev/null +++ b/packages/@aws-cdk/lambda-layers/test/integ.aws-sdk-js-layer.ts @@ -0,0 +1,24 @@ +#!/usr/bin/env node +import lambda = require('@aws-cdk/aws-lambda'); +import { App, Construct, Stack } from '@aws-cdk/core'; +import path = require('path'); +import layers = require('../lib'); + +class TestStack extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + new lambda.Function(this, 'Fn', { + code: lambda.Code.fromAsset(path.join(__dirname, 'handler')), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + layers: [layers.Layer.AWS_SDK_JS.getLayerVersion(this)] + }); + } +} + +const app = new App(); + +new TestStack(app, 'integ-aws-cdk-sdk-js-layer'); + +app.synth(); diff --git a/packages/@aws-cdk/lambda-layers/test/layer.test.ts b/packages/@aws-cdk/lambda-layers/test/layer.test.ts new file mode 100644 index 0000000000000..c1c06c4b3d4c3 --- /dev/null +++ b/packages/@aws-cdk/lambda-layers/test/layer.test.ts @@ -0,0 +1,17 @@ +import '@aws-cdk/assert/jest'; +import cdk = require('@aws-cdk/core'); +import layers = require('../lib'); + +test('AWS SDK JS layer', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + layers.Layer.AWS_SDK_JS.getLayerVersion(stack); + + // THEN + expect(stack).toHaveResource('AWS::Lambda::LayerVersion', { + CompatibleRuntimes: ['nodejs10.x'], + Description: 'AWS SDK JS', + }); +}); diff --git a/packages/decdk/package.json b/packages/decdk/package.json index 7064929d002bb..2754c38695d58 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -152,6 +152,7 @@ "@aws-cdk/core": "1.16.1", "@aws-cdk/custom-resources": "1.16.1", "@aws-cdk/cx-api": "1.16.1", + "@aws-cdk/lambda-layers": "1.16.1", "@aws-cdk/region-info": "1.16.1", "fs-extra": "^8.1.0", "jsii-reflect": "^0.20.3",