Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(python)!: add v2 layer and drop python 3.6 support #4

Merged
merged 12 commits into from
Oct 3, 2022
Merged
4 changes: 2 additions & 2 deletions .projen/deps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .projenrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
19 changes: 15 additions & 4 deletions API.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 19 additions & 10 deletions layer/Python/Dockerfile
Original file line number Diff line number Diff line change
@@ -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
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
rubenfonseca marked this conversation as resolved.
Show resolved Hide resolved
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 "{}" \; && \
rubenfonseca marked this conversation as resolved.
Show resolved Hide resolved
# remove tests
find python -wholename "*/tests/*" -type f -delete && \
# remove python bytecode
find python -regex '^.*\(__pycache__\|\.py[co]\)$' -delete
4 changes: 2 additions & 2 deletions package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 31 additions & 6 deletions src/lambda-powertools-layer.ts
Original file line number Diff line number Diff line change
@@ -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';

/**
Expand All @@ -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;
Expand All @@ -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[];
}

/**
Expand All @@ -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(
Expand All @@ -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}`;
Expand All @@ -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, {
Expand All @@ -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(),
});
}
Expand All @@ -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,
Expand All @@ -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;
}
}
36 changes: 25 additions & 11 deletions test/lambda-powertools-python-layer.test.ts
Original file line number Diff line number Diff line change
@@ -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';


Expand All @@ -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',
});
});

Expand All @@ -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',
Expand All @@ -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();
Expand All @@ -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',
});
});

Expand All @@ -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', () => {
Expand Down
4 changes: 2 additions & 2 deletions test/lambda-powertools-typescript-layer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
});
});

Expand Down Expand Up @@ -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}`,
});
});

Expand Down
12 changes: 6 additions & 6 deletions yarn.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.