diff --git a/.projen/deps.json b/.projen/deps.json index e4c4705..fc3a24e 100644 --- a/.projen/deps.json +++ b/.projen/deps.json @@ -27,7 +27,7 @@ }, { "name": "aws-cdk-lib", - "version": "2.24.1", + "version": "2.44.0", "type": "build" }, { @@ -107,7 +107,7 @@ }, { "name": "aws-cdk-lib", - "version": "^2.24.1", + "version": "^2.44.0", "type": "peer" }, { diff --git a/.projenrc.js b/.projenrc.js index 9606100..1a1f3a6 100644 --- a/.projenrc.js +++ b/.projenrc.js @@ -4,7 +4,7 @@ const project = new awscdk.AwsCdkConstructLibrary({ authorUrl: 'https://aws.amazon.com', authorOrganization: true, keywords: ['aws', 'cdk', 'powertools', 'python', 'layer', 'lambda', 'devax', 'typescript', 'nodejs'], - cdkVersion: '2.24.1', + cdkVersion: '2.44.0', defaultReleaseBranch: 'main', majorVersion: 2, name: 'cdk-aws-lambda-powertools-layer', diff --git a/API.md b/API.md index 503bf10..2e26260 100644 --- a/API.md +++ b/API.md @@ -95,13 +95,26 @@ const powertoolsLayerProps: PowertoolsLayerProps = { ... } | **Name** | **Type** | **Description** | | --- | --- | --- | -| [`includeExtras`](#cdkawslambdapowertoolslayerpowertoolslayerpropspropertyincludeextras) | `boolean` | A flag for the pydantic extras dependency, used for parsing. | +| [`compatibleArchitectures`](#cdkawslambdapowertoolslayerpowertoolslayerpropspropertycompatiblearchitectures) | [`aws-cdk-lib.aws_lambda.Architecture`](#aws-cdk-lib.aws_lambda.Architecture)[] | The compatible architectures for the layer. | +| [`includeExtras`](#cdkawslambdapowertoolslayerpowertoolslayerpropspropertyincludeextras) | `boolean` | A flag for the extras dependencies (pydantic, aws-xray-sdk, etc.) This will increase the size of the layer significantly. If you don't use parsing, ignore it. | | [`layerVersionName`](#cdkawslambdapowertoolslayerpowertoolslayerpropspropertylayerversionname) | `string` | the name of the layer, will be randomised if empty. | | [`runtimeFamily`](#cdkawslambdapowertoolslayerpowertoolslayerpropspropertyruntimefamily) | [`aws-cdk-lib.aws_lambda.RuntimeFamily`](#aws-cdk-lib.aws_lambda.RuntimeFamily) | the runtime of the layer. | | [`version`](#cdkawslambdapowertoolslayerpowertoolslayerpropspropertyversion) | `string` | The powertools package version from pypi repository. | --- +##### `compatibleArchitectures`Optional + +```typescript +public readonly compatibleArchitectures: Architecture[]; +``` + +- *Type:* [`aws-cdk-lib.aws_lambda.Architecture`](#aws-cdk-lib.aws_lambda.Architecture)[] + +The compatible architectures for the layer. + +--- + ##### `includeExtras`Optional ```typescript @@ -110,9 +123,7 @@ public readonly includeExtras: boolean; - *Type:* `boolean` -A flag for the pydantic extras dependency, used for parsing. - -This will increase the size of the layer significantly. If you don't use parsing, ignore it. +A flag for the extras dependencies (pydantic, aws-xray-sdk, etc.) This will increase the size of the layer significantly. If you don't use parsing, ignore it. --- diff --git a/layer/Python/Dockerfile b/layer/Python/Dockerfile index f488f00..682c596 100644 --- a/layer/Python/Dockerfile +++ b/layer/Python/Dockerfile @@ -1,19 +1,28 @@ FROM public.ecr.aws/lambda/python:3.8 - - ARG PACKAGE_SUFFIX='' USER root WORKDIR /tmp - -# PACKAGE_SUFFIX = '[pydantic]==1.23.0' -# PACKAGE_SUFFIX = '[pydantic]' -# PACKAGE_SUFFIX = '=='1.23.0' +# PACKAGE_SUFFIX = '[all]==2.0.0' +# PACKAGE_SUFFIX = '[all]' +# PACKAGE_SUFFIX = '=='2.0.0' # PACKAGE_SUFFIX = '' - -RUN yum update -y && yum install -y zip unzip wget tar gzip - -RUN pip install -t /asset/python aws-lambda-powertools$PACKAGE_SUFFIX \ No newline at end of file +RUN yum update -y && yum install -y zip unzip wget tar gzip binutils + +RUN pip install -t /asset/python aws-lambda-powertools$PACKAGE_SUFFIX + +# Removing nonessential files +RUN cd /asset && \ + # remove boto3 and botocore (already available in Lambda Runtime) + rm -rf python/boto* && \ + # remove boto3 dependencies + rm -rf python/s3transfer* python/*dateutil* python/urllib3* python/six* && \ + # remove debugging symbols + find python -name '*.so' -type f -exec strip "{}" \; && \ + # remove tests + find python -wholename "*/tests/*" -type f -delete && \ + # remove python bytecode + find python -regex '^.*\(__pycache__\|\.py[co]\)$' -delete \ No newline at end of file diff --git a/package.json b/package.json index b91aabc..9151d12 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "@types/prettier": "2.6.0", "@typescript-eslint/eslint-plugin": "^5", "@typescript-eslint/parser": "^5", - "aws-cdk-lib": "2.24.1", + "aws-cdk-lib": "2.44.0", "constructs": "10.0.5", "eslint": "^8", "eslint-import-resolver-node": "^0.3.6", @@ -62,7 +62,7 @@ "typescript": "^4.8.2" }, "peerDependencies": { - "aws-cdk-lib": "^2.24.1", + "aws-cdk-lib": "^2.44.0", "constructs": "^10.0.5" }, "keywords": [ diff --git a/src/lambda-powertools-layer.ts b/src/lambda-powertools-layer.ts index d9e362f..a7d060d 100644 --- a/src/lambda-powertools-layer.ts +++ b/src/lambda-powertools-layer.ts @@ -1,5 +1,6 @@ import * as path from 'path'; import { aws_lambda as lambda } from 'aws-cdk-lib'; +import { Architecture } from 'aws-cdk-lib/aws-lambda'; import { Construct } from 'constructs'; /** @@ -10,8 +11,9 @@ export interface PowertoolsLayerProps { * The powertools package version from pypi repository. */ readonly version?: string; + /** - * A flag for the pydantic extras dependency, used for parsing. + * A flag for the extras dependencies (pydantic, aws-xray-sdk, etc.) * This will increase the size of the layer significantly. If you don't use parsing, ignore it. */ readonly includeExtras?: boolean; @@ -25,6 +27,11 @@ export interface PowertoolsLayerProps { * the runtime of the layer */ readonly runtimeFamily?: lambda.RuntimeFamily; + + /** + * The compatible architectures for the layer + */ + readonly compatibleArchitectures?: lambda.Architecture[]; } /** @@ -36,7 +43,7 @@ export class LambdaPowertoolsLayer extends lambda.LayerVersion { * There are multiple combinations between version and extras package that results in different suffix for the installation. * With and without version, with and without extras flag. * We construct one suffix here because it is easier to do in code than inside the Dockerfile with bash commands. - * For example, if we set extras=true and version=1.22.0 we get '[pydantic]==1.22.0'. + * For example, if we set `includeExtras=true` and `version=1.22.0` we get '[all]==1.22.0'. * */ static constructBuildArgs( @@ -48,7 +55,7 @@ export class LambdaPowertoolsLayer extends lambda.LayerVersion { switch (runtimeFamily) { case lambda.RuntimeFamily.PYTHON: if (includeExtras) { - suffix = '[pydantic]'; + suffix = '[all]'; } if (version) { suffix = `${suffix}==${version}`; @@ -69,6 +76,9 @@ export class LambdaPowertoolsLayer extends lambda.LayerVersion { const runtimeFamily = props?.runtimeFamily ?? lambda.RuntimeFamily.PYTHON; const languageName = getLanguageNameFromRuntimeFamily(runtimeFamily); const dockerFilePath = path.join(__dirname, `../layer/${languageName}`); + const compatibleArchitectures = props?.compatibleArchitectures ?? [lambda.Architecture.X86_64]; + const compatibleArchitecturesDescription = compatibleArchitectures.map((arch) => arch.name).join(', '); + console.log(`path ${dockerFilePath}`); super(scope, id, { code: lambda.Code.fromDockerBuild(dockerFilePath, { @@ -79,12 +89,15 @@ export class LambdaPowertoolsLayer extends lambda.LayerVersion { props?.version, ), }, + // supports cross-platform docker build + platform: getDockerPlatformNameFromArchitectures(compatibleArchitectures), }), layerVersionName: props?.layerVersionName ? props?.layerVersionName : undefined, license: 'MIT-0', compatibleRuntimes: getRuntimesFromRuntimeFamily(runtimeFamily), - description: `Lambda Powertools for ${languageName}${ - props?.includeExtras ? ' with Pydantic' : '' + compatibleArchitectures, + description: `Lambda Powertools for ${languageName} [${compatibleArchitecturesDescription}]${ + props?.includeExtras ? ' with extra dependencies' : '' } ${props?.version ? `version ${props?.version}` : 'latest version'}`.trim(), }); } @@ -94,7 +107,6 @@ function getRuntimesFromRuntimeFamily(runtimeFamily: lambda.RuntimeFamily): lamb switch (runtimeFamily) { case lambda.RuntimeFamily.PYTHON: return [ - lambda.Runtime.PYTHON_3_6, lambda.Runtime.PYTHON_3_7, lambda.Runtime.PYTHON_3_8, lambda.Runtime.PYTHON_3_9, @@ -120,3 +132,16 @@ function getLanguageNameFromRuntimeFamily(runtimeFamily: lambda.RuntimeFamily): return 'Unknown'; } } + +// Docker expects a single string for the --platform option. +// getDockerPlatformNameFromArchitectures converts the Architecture enum to a string. +function getDockerPlatformNameFromArchitectures(architectures: lambda.Architecture[]): string { + if (architectures.length == 1) { + return architectures[0].dockerPlatform; + } else { + // if we have multiple architectures, we default to x86_64, hoping for the + // layer not to have any architecture specific code or at least contain + // binary code for all architectures + return Architecture.X86_64.dockerPlatform; + } +} \ No newline at end of file diff --git a/test/lambda-powertools-python-layer.test.ts b/test/lambda-powertools-python-layer.test.ts index 6ea4d04..b629098 100644 --- a/test/lambda-powertools-python-layer.test.ts +++ b/test/lambda-powertools-python-layer.test.ts @@ -1,6 +1,6 @@ import { Stack } from 'aws-cdk-lib'; import { Template } from 'aws-cdk-lib/assertions'; -import { RuntimeFamily } from 'aws-cdk-lib/aws-lambda'; +import { Architecture, RuntimeFamily } from 'aws-cdk-lib/aws-lambda'; import { LambdaPowertoolsLayer } from '../src'; @@ -10,7 +10,7 @@ describe('with no configuration the construct', () => { const template = Template.fromStack(stack); test('synthesizes successfully', () => { template.hasResourceProperties('AWS::Lambda::LayerVersion', { - Description: 'Lambda Powertools for Python latest version', + Description: 'Lambda Powertools for Python [x86_64] latest version', }); }); @@ -23,7 +23,6 @@ describe('with no configuration the construct', () => { test('matches the python 3.x runtimes', () => { template.hasResourceProperties('AWS::Lambda::LayerVersion', { CompatibleRuntimes: [ - 'python3.6', 'python3.7', 'python3.8', 'python3.9', @@ -32,6 +31,21 @@ describe('with no configuration the construct', () => { }); }); +describe('with arm64 architecture', () => { + const stack = new Stack(); + new LambdaPowertoolsLayer(stack, 'PowertoolsLayer', { + runtimeFamily: RuntimeFamily.PYTHON, + compatibleArchitectures: [Architecture.ARM_64], + }); + const template = Template.fromStack(stack); + test('synthesizes successfully', () => { + template.hasResourceProperties('AWS::Lambda::LayerVersion', { + Description: 'Lambda Powertools for Python [arm64] latest version', + CompatibleArchitectures: ['arm64'], + }); + }); +}); + describe('for layerVersionName configuraiton the construct', () => { test('synthisizes to a layer with provided name', () => { const stack = new Stack(); @@ -54,7 +68,7 @@ describe('with version configuration the construct', () => { Template.fromStack(stack).hasResourceProperties('AWS::Lambda::LayerVersion', { - Description: 'Lambda Powertools for Python version 1.21.0', + Description: 'Lambda Powertools for Python [x86_64] version 1.21.0', }); }); @@ -73,34 +87,34 @@ describe('with version configuration the construct', () => { }); Template.fromStack(stack).hasResourceProperties('AWS::Lambda::LayerVersion', { - Description: 'Lambda Powertools for Python with Pydantic version 1.22.0', + Description: 'Lambda Powertools for Python [x86_64] with extra dependencies version 1.22.0', }); }); - test('synthesizes with pyndatic and latest version', () => { + test('synthesizes with extras and latest version', () => { const stack = new Stack(); new LambdaPowertoolsLayer(stack, 'LayerExtrasNoVersion', { includeExtras: true, }); Template.fromStack(stack).hasResourceProperties('AWS::Lambda::LayerVersion', { - Description: 'Lambda Powertools for Python with Pydantic latest version', + Description: 'Lambda Powertools for Python [x86_64] with extra dependencies latest version', }); }); }); describe('construct build args for Dockerfile', () => { - test('returns pydantic and version', () => { + test('returns extras and version', () => { const args = LambdaPowertoolsLayer.constructBuildArgs(RuntimeFamily.PYTHON, true, '1.21.0'); - expect(args).toEqual('[pydantic]==1.21.0'); + expect(args).toEqual('[all]==1.21.0'); }); - test('returns only pydantic when no version provided', () => { + test('returns only extras when no version provided', () => { const args = LambdaPowertoolsLayer.constructBuildArgs(RuntimeFamily.PYTHON, true, undefined); - expect(args).toEqual('[pydantic]'); + expect(args).toEqual('[all]'); }); test('returns only version when no extras flag provided', () => { diff --git a/test/lambda-powertools-typescript-layer.test.ts b/test/lambda-powertools-typescript-layer.test.ts index 7ce9c5b..1b3dfa3 100644 --- a/test/lambda-powertools-typescript-layer.test.ts +++ b/test/lambda-powertools-typescript-layer.test.ts @@ -12,7 +12,7 @@ describe('with minimal configuration the construct', () => { const template = Template.fromStack(stack); test('synthesizes successfully', () => { template.hasResourceProperties('AWS::Lambda::LayerVersion', { - Description: 'Lambda Powertools for TypeScript latest version', + Description: 'Lambda Powertools for TypeScript [x86_64] latest version', }); }); @@ -57,7 +57,7 @@ describe('with version configuration the construct', () => { Template.fromStack(stack).hasResourceProperties('AWS::Lambda::LayerVersion', { - Description: `Lambda Powertools for TypeScript version ${version}`, + Description: `Lambda Powertools for TypeScript [x86_64] version ${version}`, }); }); diff --git a/yarn.lock b/yarn.lock index 6c775d4..4ab41d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1220,16 +1220,16 @@ at-least-node@^1.0.0: resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== -aws-cdk-lib@2.24.1: - version "2.24.1" - resolved "https://registry.yarnpkg.com/aws-cdk-lib/-/aws-cdk-lib-2.24.1.tgz#197d10216b384850c01205acc8e4818838964302" - integrity sha512-xGeEX+9wPGppSiIUdf/JTLMmqMikGhlSi7bjijl3lwncZtySkdjX0j+W2A1fuKp0S8Yd2axkwVkltIMxzNH/gw== +aws-cdk-lib@2.44.0: + version "2.44.0" + resolved "https://registry.yarnpkg.com/aws-cdk-lib/-/aws-cdk-lib-2.44.0.tgz#e6157df7a72b8d1a4528f6fb9f9b0ed7aa404bcc" + integrity sha512-h0lCcS3t2TPF5FIpkA7OcE2t4vChtz/FGcZ5jVaORj21quiUz84eOhGk2BeRoKqfSp1Zqu2QxQUk6p6YpAOrRA== dependencies: "@balena/dockerignore" "^1.0.2" case "1.6.3" fs-extra "^9.1.0" ignore "^5.2.0" - jsonschema "^1.4.0" + jsonschema "^1.4.1" minimatch "^3.1.2" punycode "^2.1.1" semver "^7.3.7" @@ -3993,7 +3993,7 @@ jsonparse@^1.2.0, jsonparse@^1.3.1: resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== -jsonschema@^1.4.0: +jsonschema@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.1.tgz#cc4c3f0077fb4542982973d8a083b6b34f482dab" integrity sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==