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(lambda-python-alpha): cache python lambda dependencies usig lambda layer #30157

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions packages/@aws-cdk/aws-lambda-python-alpha/lib/bundling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ export interface BundlingProps extends BundlingOptions {
* @default - BundlingFileAccess.BIND_MOUNT
*/
bundlingFileAccess?: BundlingFileAccess;

/**
* Whether or not to install the dependencies
* @default true
*/
readonly installDependencies?: boolean;
}

/**
Expand All @@ -73,6 +79,7 @@ export class Bundling implements CdkBundlingOptions {
public readonly securityOpt?: string;
public readonly network?: string;
public readonly bundlingFileAccess?: BundlingFileAccess;
public readonly installDependencies?: boolean;

constructor(props: BundlingProps) {
const {
Expand All @@ -85,6 +92,7 @@ export class Bundling implements CdkBundlingOptions {
poetryWithoutUrls,
commandHooks,
assetExcludes = [],
installDependencies = true,
} = props;

const outputPath = path.posix.join(AssetStaging.BUNDLING_OUTPUT_DIR, outputPathSuffix);
Expand All @@ -97,6 +105,7 @@ export class Bundling implements CdkBundlingOptions {
poetryWithoutUrls,
commandHooks,
assetExcludes,
installDependencies,
});

this.image = image ?? DockerImage.fromBuild(path.join(__dirname, '..', 'lib'), {
Expand All @@ -119,17 +128,19 @@ export class Bundling implements CdkBundlingOptions {
}

private createBundlingCommand(options: BundlingCommandOptions): string[] {
const packaging = Packaging.fromEntry(options.entry, options.poetryIncludeHashes, options.poetryWithoutUrls);
let bundlingCommands: string[] = [];
bundlingCommands.push(...options.commandHooks?.beforeBundling(options.inputDir, options.outputDir) ?? []);
const exclusionStr = options.assetExcludes?.map(item => `--exclude='${item}'`).join(' ');
bundlingCommands.push([
'rsync', '-rLv', exclusionStr ?? '', `${options.inputDir}/`, options.outputDir,
].filter(item => item).join(' '));
bundlingCommands.push(`cd ${options.outputDir}`);
bundlingCommands.push(packaging.exportCommand ?? '');
if (packaging.dependenciesFile) {
bundlingCommands.push(`python -m pip install -r ${DependenciesFile.PIP} -t ${options.outputDir}`);
if (options.installDependencies) {
const packaging = Packaging.fromEntry(options.entry, options.poetryIncludeHashes, options.poetryWithoutUrls);
bundlingCommands.push(`cd ${options.outputDir}`);
bundlingCommands.push(packaging.exportCommand ?? '');
if (packaging.dependenciesFile) {
bundlingCommands.push(`python -m pip install -r ${DependenciesFile.PIP} -t ${options.outputDir}`);
}
}
bundlingCommands.push(...options.commandHooks?.afterBundling(options.inputDir, options.outputDir) ?? []);
return bundlingCommands;
Expand All @@ -144,6 +155,7 @@ interface BundlingCommandOptions {
readonly poetryIncludeHashes?: boolean;
readonly poetryWithoutUrls?: boolean;
readonly commandHooks?: ICommandHooks;
readonly installDependencies: boolean;
}

/**
Expand Down
36 changes: 33 additions & 3 deletions packages/@aws-cdk/aws-lambda-python-alpha/lib/function.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import * as fs from 'fs';
import * as path from 'path';
import { Function, FunctionOptions, Runtime, RuntimeFamily } from 'aws-cdk-lib/aws-lambda';
import { Stack } from 'aws-cdk-lib/core';
import { Architecture, Function, FunctionOptions, Runtime, RuntimeFamily } from 'aws-cdk-lib/aws-lambda';
import { AssetHashType, Stack } from 'aws-cdk-lib/core';
import { Construct } from 'constructs';
import { Bundling } from './bundling';
import { BundlingOptions } from './types';
import { PythonLayerVersion } from './layer';
import { Packaging } from './packaging';

/**
* Properties for a PythonFunction
Expand Down Expand Up @@ -42,6 +44,12 @@ export interface PythonFunctionProps extends FunctionOptions {
* @default - Use the default bundling Docker image, with x86_64 architecture.
*/
readonly bundling?: BundlingOptions;

/**
* Whether or not to create a layer for the function's dependencies.
* @default - No layer is created.
*/
readonly layer?: boolean;
}

/**
Expand All @@ -60,13 +68,30 @@ export class PythonFunction extends Function {
if (!fs.existsSync(resolvedIndex)) {
throw new Error(`Cannot find index file at ${resolvedIndex}`);
}

const resolvedHandler =`${index.slice(0, -3)}.${handler}`.replace(/\//g, '.');

if (props.runtime && props.runtime.family !== RuntimeFamily.PYTHON) {
throw new Error('Only `PYTHON` runtimes are supported.');
}

// Layer
let layer: PythonLayerVersion | undefined;
if (props.layer) {
layer = new PythonLayerVersion(scope, `${id}Layer`, {
entry,
compatibleRuntimes: [props.runtime],
compatibleArchitectures: [props.architecture ?? Architecture.X86_64],
bundling: {
...props.bundling,
installDependencies: true,
// assetExcludes: ["TODO: exclude everything except the dependencies file"]
assetHashType: AssetHashType.CUSTOM,
assetHash: Packaging.dependenciesHash(entry),
},
});
}

super(scope, id, {
...props,
runtime,
Expand All @@ -77,8 +102,13 @@ export class PythonFunction extends Function {
// define architecture based on the target architecture of the function, possibly overriden in bundling options
architecture: props.architecture,
...props.bundling,
installDependencies: layer ? false : true,
}),
handler: resolvedHandler,
layers: layer ? [
...(props.layers ?? []),
layer,
] : props.layers,
});
}
}
42 changes: 42 additions & 0 deletions packages/@aws-cdk/aws-lambda-python-alpha/lib/packaging.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as fs from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';

export enum DependenciesFile {
PIP = 'requirements.txt',
Expand Down Expand Up @@ -99,6 +100,47 @@ export class Packaging {
}
}

public static getAssetHash(entry: string): string | undefined {
const hash = crypto.createHash('sha256');
if (fs.existsSync(path.join(entry, DependenciesFile.PIPENV))) {
const dependenciesFile = fs.readFileSync(path.join(entry, DependenciesFile.PIPENV));
hash.update(dependenciesFile);
return hash.digest('hex');
} if (fs.existsSync(path.join(entry, DependenciesFile.POETRY))) {
const dependenciesFile = fs.readFileSync(path.join(entry, DependenciesFile.POETRY));
hash.update(dependenciesFile);
return hash.digest('hex');
} else if (fs.existsSync(path.join(entry, DependenciesFile.PIP))) {
const dependenciesFile = fs.readFileSync(path.join(entry, DependenciesFile.PIP));
hash.update(dependenciesFile);
return hash.digest('hex');
} else {
throw new Error('No dependencies file found');
}
}

/**
* Compute the asset hash of the dependencies file.
*/
public static dependenciesHash(entry: string): string | undefined {
const hash = crypto.createHash('sha256');
if (fs.existsSync(path.join(entry, DependenciesFile.PIPENV))) {
const dependenciesFile = fs.readFileSync(path.join(entry, DependenciesFile.PIPENV));
hash.update(dependenciesFile);
return hash.digest('hex');
} if (fs.existsSync(path.join(entry, DependenciesFile.POETRY))) {
const dependenciesFile = fs.readFileSync(path.join(entry, DependenciesFile.POETRY));
hash.update(dependenciesFile);
return hash.digest('hex');
} else if (fs.existsSync(path.join(entry, DependenciesFile.PIP))) {
const dependenciesFile = fs.readFileSync(path.join(entry, DependenciesFile.PIP));
hash.update(dependenciesFile);
return hash.digest('hex');
} else {
return
}
}

public readonly dependenciesFile: string;
public readonly exportCommand?: string;
constructor(props: PackagingProps) {
Expand Down
6 changes: 6 additions & 0 deletions packages/@aws-cdk/aws-lambda-python-alpha/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ export interface BundlingOptions extends DockerRunOptions {
* @default - BundlingFileAccess.BIND_MOUNT
*/
readonly bundlingFileAccess?: BundlingFileAccess;

/**
* Whether or not to install the dependencies
* @default true
*/
readonly installDependencies?: boolean;
}

/**
Expand Down
Loading