diff --git a/packages/@aws-cdk/aws-lambda-python/README.md b/packages/@aws-cdk/aws-lambda-python/README.md index ca173c7bb5666..2a8de2d34c4de 100644 --- a/packages/@aws-cdk/aws-lambda-python/README.md +++ b/packages/@aws-cdk/aws-lambda-python/README.md @@ -27,7 +27,7 @@ Define a `PythonFunction`: ```ts new lambda.PythonFunction(this, 'MyFunction', { entry: '/path/to/my/function', // required - runtime: Runtime.PYTHON_3_6, // required + runtime: Runtime.PYTHON_3_8, // required index: 'my_index.py', // optional, defaults to 'index.py' handler: 'my_exported_func', // optional, defaults to 'handler' }); @@ -35,21 +35,53 @@ new lambda.PythonFunction(this, 'MyFunction', { All other properties of `lambda.Function` are supported, see also the [AWS Lambda construct library](https://github.com/aws/aws-cdk/tree/master/packages/%40aws-cdk/aws-lambda). -## Module Dependencies +## Python Layer -If `requirements.txt` or `Pipfile` exists at the entry path, the construct will handle installing -all required modules in a [Lambda compatible Docker container](https://gallery.ecr.aws/sam/build-python3.7) -according to the `runtime` and with the Docker platform based on the target architecture of the Lambda function. +You may create a python-based lambda layer with `PythonLayerVersion`. If `PythonLayerVersion` detects a `requirements.txt` +or `Pipfile` or `poetry.lock` with the associated `pyproject.toml` at the entry path, then `PythonLayerVersion` will include the dependencies inline with your code in the +layer. + +Define a `PythonLayerVersion`: + +```ts +new lambda.PythonLayerVersion(this, 'MyLayer', { + entry: '/path/to/my/layer', // point this to your library's directory +}) +``` + +A layer can also be used as a part of a `PythonFunction`: + +```ts +new lambda.PythonFunction(this, 'MyFunction', { + entry: '/path/to/my/function', + runtime: Runtime.PYTHON_3_8, + layers: [ + new lambda.PythonLayerVersion(this, 'MyLayer', { + entry: '/path/to/my/layer', // point this to your library's directory + }), + ], +}); +``` + +## Packaging + +If `requirements.txt`, `Pipfile` or `poetry.lock` exists at the entry path, the construct will handle installing all required modules in a [Lambda compatible Docker container](https://gallery.ecr.aws/sam/build-python3.7) according to the `runtime` and with the Docker platform based on the target architecture of the Lambda function. Python bundles are only recreated and published when a file in a source directory has changed. Therefore (and as a general best-practice), it is highly recommended to commit a lockfile with a -list of all transitive dependencies and their exact versions. -This will ensure that when any dependency version is updated, the bundle asset is recreated and uploaded. +list of all transitive dependencies and their exact versions. This will ensure that when any dependency version is updated, the bundle asset is recreated and uploaded. + +To that end, we recommend using [`pipenv`] or [`poetry`] which have lockfile support. + +- [`pipenv`](https://pipenv-fork.readthedocs.io/en/latest/basics.html#example-pipfile-lock) +- [`poetry`](https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control) -To that end, we recommend using [`pipenv`] or [`poetry`] which has lockfile support. +Packaging is executed using the `Packaging` class, which: -[`pipenv`]: https://pipenv-fork.readthedocs.io/en/latest/basics.html#example-pipfile-lock -[`poetry`]: https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +1. Infers the packaging type based on the files present. +2. If it sees a `Pipfile` or a `poetry.lock` file, it exports it to a compatible `requirements.txt` file with credentials (if they're available in the source files or in the bundling container). +3. Installs dependencies using `pip`. +4. Copies the dependencies into an asset that is bundled for the Lambda package. **Lambda with a requirements.txt** @@ -73,24 +105,71 @@ To that end, we recommend using [`pipenv`] or [`poetry`] which has lockfile supp ```plaintext . ├── lambda_function.py # exports a function named 'handler' -├── pyproject.toml # has to be present at the entry path -├── poetry.lock # your poetry lock file +├── pyproject.toml # your poetry project definition +├── poetry.lock # your poetry lock file has to be present at the entry path ``` -**Lambda Layer Support** +## Custom Bundling -You may create a python-based lambda layer with `PythonLayerVersion`. If `PythonLayerVersion` detects a `requirements.txt` -or `Pipfile` or `poetry.lock` with the associated `pyproject.toml` at the entry path, then `PythonLayerVersion` will include the dependencies inline with your code in the -layer. +Custom bundling can be performed by passing in additional build arguments that point to index URLs to private repos, or by using an entirely custom Docker images for bundling dependencies. The build args currently supported are: + +- `PIP_INDEX_URL` +- `PIP_EXTRA_INDEX_URL` +- `HTTPS_PROXY` + +Additional build args for bundling that refer to PyPI indexes can be specified as: ```ts -new lambda.PythonFunction(this, 'MyFunction', { - entry: '/path/to/my/function', - runtime: Runtime.PYTHON_3_6, - layers: [ - new lambda.PythonLayerVersion(this, 'MyLayer', { - entry: '/path/to/my/layer', // point this to your library's directory - }), - ], +const entry = '/path/to/function'; +const image = DockerImage.fromBuild(entry); + +new lambda.PythonFunction(this, 'function', { + entry, + runtime: Runtime.PYTHON_3_8, + bundling: { + buildArgs: { PIP_INDEX_URL: "https://your.index.url/simple/", PIP_EXTRA_INDEX_URL: "https://your.extra-index.url/simple/" }, + }, }); ``` + +If using a custom Docker image for bundling, the dependencies are installed with `pip`, `pipenv` or `poetry` by using the `Packaging` class. A different bundling Docker image that is in the same directory as the function can be specified as: + + ```ts +const entry = '/path/to/function'; +const image = DockerImage.fromBuild(entry); + +new lambda.PythonFunction(this, 'function', { + entry, + runtime: Runtime.PYTHON_3_8, + bundling: { image }, +}); +``` + +## Custom Bundling with Code Artifact + +To use a Code Artifact PyPI repo, the `PIP_INDEX_URL` for bundling the function can be customized (requires AWS CLI in the build environment): + +```ts +import { execSync } from 'child_process'; + +const entry = '/path/to/function'; +const image = DockerImage.fromBuild(entry); + +const domain = 'my-domain'; +const domainOwner = '111122223333'; +const repoName = 'my_repo'; +const region = 'us-east-1'; +const codeArtifactAuthToken = execSync(`aws codeartifact get-authorization-token --domain ${domain} --domain-owner ${domainOwner} --query authorizationToken --output text`).toString().trim(); + +const indexUrl = `https://aws:${codeArtifactAuthToken}@${domain}-${domainOwner}.d.codeartifact.${region}.amazonaws.com/pypi/${repoName}/simple/`; + +new lambda.PythonFunction(this, 'function', { + entry, + runtime: Runtime.PYTHON_3_8, + bundling: { + buildArgs: { PIP_INDEX_URL: indexUrl }, + }, +}); +``` + +This type of an example should work for `pip` and `poetry` based dependencies, but will not work for `pipenv`. diff --git a/packages/@aws-cdk/aws-lambda-python/lib/Dockerfile b/packages/@aws-cdk/aws-lambda-python/lib/Dockerfile index ab45f0480735f..462d9c645fb9f 100644 --- a/packages/@aws-cdk/aws-lambda-python/lib/Dockerfile +++ b/packages/@aws-cdk/aws-lambda-python/lib/Dockerfile @@ -3,7 +3,12 @@ ARG IMAGE=public.ecr.aws/sam/build-python3.7 FROM $IMAGE -# Ensure rsync is installed -RUN yum -q list installed rsync &>/dev/null || yum install -y rsync +ARG PIP_INDEX_URL +ARG PIP_EXTRA_INDEX_URL +ARG HTTPS_PROXY + +# Upgrade pip (required by cryptography v3.4 and above, which is a dependency of poetry) +RUN pip install --upgrade pip +RUN pip install pipenv poetry CMD [ "python" ] diff --git a/packages/@aws-cdk/aws-lambda-python/lib/Dockerfile.dependencies b/packages/@aws-cdk/aws-lambda-python/lib/Dockerfile.dependencies deleted file mode 100644 index 6793987212603..0000000000000 --- a/packages/@aws-cdk/aws-lambda-python/lib/Dockerfile.dependencies +++ /dev/null @@ -1,22 +0,0 @@ -# The correct AWS SAM build image based on the runtime of the function will be -# passed as build arg. The default allows to do `docker build .` when testing. -ARG IMAGE=public.ecr.aws/sam/build-python3.7 -FROM $IMAGE - -# Ensure rsync is installed -RUN yum -q list installed rsync &>/dev/null || yum install -y rsync - -# Upgrade pip (required by cryptography v3.4 and above, which is a dependency of poetry) -RUN pip install --upgrade pip - -# Install pipenv and poetry so we can create a requirements.txt if we detect pipfile or poetry.lock respectively -RUN pip install pipenv poetry - -# Install the dependencies in a cacheable layer -WORKDIR /var/dependencies -COPY Pipfile* pyproject* poetry* requirements.tx[t] ./ -RUN [ -f 'Pipfile' ] && pipenv lock -r >requirements.txt; \ - [ -f 'poetry.lock' ] && poetry export --with-credentials --format requirements.txt --output requirements.txt; \ - [ -f 'requirements.txt' ] && pip install -r requirements.txt -t .; - -CMD [ "python" ] diff --git a/packages/@aws-cdk/aws-lambda-python/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-python/lib/bundling.ts index 722cd2d062fb6..37e52ac13435b 100644 --- a/packages/@aws-cdk/aws-lambda-python/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-python/lib/bundling.ts @@ -1,7 +1,8 @@ -import * as fs from 'fs'; import * as path from 'path'; -import * as lambda from '@aws-cdk/aws-lambda'; -import * as cdk from '@aws-cdk/core'; +import { Architecture, AssetCode, Code, Runtime } from '@aws-cdk/aws-lambda'; +import { AssetStaging, BundlingOptions as CdkBundlingOptions, DockerImage } from '@aws-cdk/core'; +import { Packaging, DependenciesFile } from './packaging'; +import { BundlingOptions } from './types'; /** * Dependency files to exclude from the asset hash. @@ -16,137 +17,90 @@ export const BUNDLER_DEPENDENCIES_CACHE = '/var/dependencies'; /** * Options for bundling */ -export interface BundlingOptions { +export interface BundlingProps extends BundlingOptions { /** * Entry path */ readonly entry: string; /** - * The runtime of the lambda function + * The runtime environment. */ - readonly runtime: lambda.Runtime; + readonly runtime: Runtime; /** * The system architecture of the lambda function - */ - readonly architecture: lambda.Architecture; - - /** - * Output path suffix ('python' for a layer, '.' otherwise) - */ - readonly outputPathSuffix: string; - - /** - * Determines how asset hash is calculated. Assets will get rebuild and - * uploaded only if their hash has changed. - * - * If asset hash is set to `SOURCE` (default), then only changes to the source - * directory will cause the asset to rebuild. This means, for example, that in - * order to pick up a new dependency version, a change must be made to the - * source tree. Ideally, this can be implemented by including a dependency - * lockfile in your source tree or using fixed dependencies. * - * If the asset hash is set to `OUTPUT`, the hash is calculated after - * bundling. This means that any change in the output will cause the asset to - * be invalidated and uploaded. Bear in mind that `pip` adds timestamps to - * dependencies it installs, which implies that in this mode Python bundles - * will _always_ get rebuild and uploaded. Normally this is an anti-pattern - * since build - * - * @default AssetHashType.SOURCE By default, hash is calculated based on the - * contents of the source directory. If `assetHash` is also specified, the - * default is `CUSTOM`. This means that only updates to the source will cause - * the asset to rebuild. + * @default Architecture.X86_64 */ - readonly assetHashType?: cdk.AssetHashType; - - /** - * Specify a custom hash for this asset. If `assetHashType` is set it must - * be set to `AssetHashType.CUSTOM`. For consistency, this custom hash will - * be SHA256 hashed and encoded as hex. The resulting hash will be the asset - * hash. - * - * NOTE: the hash is used in order to identify a specific revision of the asset, and - * used for optimizing and caching deployment activities related to this asset such as - * packaging, uploading to Amazon S3, etc. If you chose to customize the hash, you will - * need to make sure it is updated every time the asset changes, or otherwise it is - * possible that some deployments will not be invalidated. - * - * @default - based on `assetHashType` - */ - readonly assetHash?: string; + readonly architecture?: Architecture; } /** * Produce bundled Lambda asset code */ -export function bundle(options: BundlingOptions): lambda.Code { - const { entry, runtime, architecture, outputPathSuffix } = options; - - const stagedir = cdk.FileSystem.mkdtemp('python-bundling-'); - const hasDeps = stageDependencies(entry, stagedir); - - const depsCommand = chain([ - hasDeps ? `rsync -r ${BUNDLER_DEPENDENCIES_CACHE}/. ${cdk.AssetStaging.BUNDLING_OUTPUT_DIR}/${outputPathSuffix}` : '', - `rsync -r . ${cdk.AssetStaging.BUNDLING_OUTPUT_DIR}/${outputPathSuffix}`, - ]); - - // Determine which dockerfile to use. When dependencies are present, we use a - // Dockerfile that can create a cacheable layer. We can't use this Dockerfile - // if there aren't dependencies or the Dockerfile will complain about missing - // sources. - const dockerfile = hasDeps - ? 'Dockerfile.dependencies' - : 'Dockerfile'; - - // copy Dockerfile to workdir - fs.copyFileSync(path.join(__dirname, dockerfile), path.join(stagedir, dockerfile)); +export class Bundling implements CdkBundlingOptions { + public static bundle(options: BundlingProps): AssetCode { + return Code.fromAsset(options.entry, { + assetHash: options.assetHash, + assetHashType: options.assetHashType, + exclude: DEPENDENCY_EXCLUDES, + bundling: new Bundling(options), + }); + } - const image = cdk.DockerImage.fromBuild(stagedir, { - buildArgs: { - IMAGE: runtime.bundlingImage.image, - }, - platform: architecture.dockerPlatform, - file: dockerfile, - }); + public readonly image: DockerImage; + public readonly command: string[]; - return lambda.Code.fromAsset(entry, { - assetHashType: options.assetHashType, - assetHash: options.assetHash, - exclude: DEPENDENCY_EXCLUDES, - bundling: { + constructor(props: BundlingProps) { + const { + entry, + runtime, + architecture = Architecture.X86_64, + outputPathSuffix = '', image, - command: ['bash', '-c', depsCommand], - }, - }); -} - -/** - * Checks to see if the `entry` directory contains a type of dependency that - * we know how to install. - */ -export function stageDependencies(entry: string, stagedir: string): boolean { - const prefixes = [ - 'Pipfile', - 'pyproject', - 'poetry', - 'requirements.txt', - ]; + } = props; + + const outputPath = path.join(AssetStaging.BUNDLING_OUTPUT_DIR, outputPathSuffix); + + const bundlingCommands = this.createBundlingCommand({ + entry, + inputDir: AssetStaging.BUNDLING_INPUT_DIR, + outputDir: outputPath, + }); + + const defaultImage = DockerImage.fromBuild(path.join(__dirname, '../lib'), { + buildArgs: { + ...props.buildArgs ?? {}, + IMAGE: runtime.bundlingImage.image, + }, + platform: architecture.dockerPlatform, + }); + this.image = image ?? defaultImage; + this.command = ['bash', '-c', chain(bundlingCommands)]; + } - let found = false; - for (const file of fs.readdirSync(entry)) { - for (const prefix of prefixes) { - if (file.startsWith(prefix)) { - fs.copyFileSync(path.join(entry, file), path.join(stagedir, file)); - found = true; - } - } + private createBundlingCommand(options: BundlingCommandOptions): string[] { + const packaging = Packaging.fromEntry(options.entry); + let bundlingCommands: string[] = []; + bundlingCommands.push(packaging.exportCommand ?? ''); + if (packaging.dependenciesFile) { + bundlingCommands.push(`python -m pip install -r ${DependenciesFile.PIP} -t ${options.outputDir}`); + }; + bundlingCommands.push(`cp -R ${options.inputDir} ${options.outputDir}`); + return bundlingCommands; } +} - return found; +interface BundlingCommandOptions { + readonly entry: string; + readonly inputDir: string; + readonly outputDir: string; } +/** + * Chain commands + */ function chain(commands: string[]): string { return commands.filter(c => !!c).join(' && '); } diff --git a/packages/@aws-cdk/aws-lambda-python/lib/function.ts b/packages/@aws-cdk/aws-lambda-python/lib/function.ts index 1aafeb61149f3..7938c20219f73 100644 --- a/packages/@aws-cdk/aws-lambda-python/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda-python/lib/function.ts @@ -1,8 +1,9 @@ import * as fs from 'fs'; import * as path from 'path'; -import * as lambda from '@aws-cdk/aws-lambda'; -import { AssetHashType } from '@aws-cdk/core'; -import { bundle } from './bundling'; +import { Function, FunctionOptions, Runtime, RuntimeFamily } from '@aws-cdk/aws-lambda'; +import { Bundling } from './bundling'; +import { BundlingOptions } from './types'; + // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order @@ -11,12 +12,21 @@ import { Construct } from '@aws-cdk/core'; /** * Properties for a PythonFunction */ -export interface PythonFunctionProps extends lambda.FunctionOptions { +export interface PythonFunctionProps extends FunctionOptions { /** - * The path to the root directory of the function. + * Path to the source of the function or the location for dependencies. */ readonly entry: string; + + /** + * The runtime environment. Only runtimes of the Python family are + * supported. + * + * @default Runtime.PYTHON_3_7 + */ + readonly runtime: Runtime; + /** * The path (relative to entry) to the index file containing the exported handler. * @@ -32,88 +42,46 @@ export interface PythonFunctionProps extends lambda.FunctionOptions { readonly handler?: string; /** - * The runtime environment. Only runtimes of the Python family are - * supported. - */ - readonly runtime: lambda.Runtime; - - /** - * Determines how asset hash is calculated. Assets will get rebuild and - * uploaded only if their hash has changed. - * - * If asset hash is set to `SOURCE` (default), then only changes to the source - * directory will cause the asset to rebuild. This means, for example, that in - * order to pick up a new dependency version, a change must be made to the - * source tree. Ideally, this can be implemented by including a dependency - * lockfile in your source tree or using fixed dependencies. - * - * If the asset hash is set to `OUTPUT`, the hash is calculated after - * bundling. This means that any change in the output will cause the asset to - * be invalidated and uploaded. Bear in mind that `pip` adds timestamps to - * dependencies it installs, which implies that in this mode Python bundles - * will _always_ get rebuild and uploaded. Normally this is an anti-pattern - * since build - * - * @default AssetHashType.SOURCE By default, hash is calculated based on the - * contents of the source directory. This means that only updates to the - * source will cause the asset to rebuild. - */ - readonly assetHashType?: AssetHashType; - - /** - * Specify a custom hash for this asset. If `assetHashType` is set it must - * be set to `AssetHashType.CUSTOM`. For consistency, this custom hash will - * be SHA256 hashed and encoded as hex. The resulting hash will be the asset - * hash. - * - * NOTE: the hash is used in order to identify a specific revision of the asset, and - * used for optimizing and caching deployment activities related to this asset such as - * packaging, uploading to Amazon S3, etc. If you chose to customize the hash, you will - * need to make sure it is updated every time the asset changes, or otherwise it is - * possible that some deployments will not be invalidated. + * Bundling options to use for this function. Use this to specify custom bundling options like + * the bundling Docker image, asset hash type, custom hash, architecture, etc. * - * @default - based on `assetHashType` + * @default - Use the default bundling Docker image, with x86_64 architecture. */ - readonly assetHash?: string; + readonly bundling?: BundlingOptions; } /** * A Python Lambda function */ -export class PythonFunction extends lambda.Function { +export class PythonFunction extends Function { constructor(scope: Construct, id: string, props: PythonFunctionProps) { - if (props.runtime && props.runtime.family !== lambda.RuntimeFamily.PYTHON) { - throw new Error('Only `PYTHON` runtimes are supported.'); - } + const { index = 'index.py', handler = 'handler', runtime } = props; if (props.index && !/\.py$/.test(props.index)) { throw new Error('Only Python (.py) index files are supported.'); } - // Entry and defaults + // Entry const entry = path.resolve(props.entry); - const index = props.index ?? 'index.py'; - const resolvedIndex = path.resolve(entry, index); if (!fs.existsSync(resolvedIndex)) { throw new Error(`Cannot find index file at ${resolvedIndex}`); } - const handler = props.handler ?? 'handler'; - const runtime = props.runtime ?? lambda.Runtime.PYTHON_3_7; - const architecture = props.architecture ?? lambda.Architecture.X86_64; + const resolvedHandler =`${index.slice(0, -3)}.${handler}`.replace('/', '.'); + + if (props.runtime && props.runtime.family !== RuntimeFamily.PYTHON) { + throw new Error('Only `PYTHON` runtimes are supported.'); + } super(scope, id, { ...props, runtime, - code: bundle({ - runtime, - architecture, + code: Bundling.bundle({ entry, - outputPathSuffix: '.', - assetHashType: props.assetHashType, - assetHash: props.assetHash, + runtime, + ...props.bundling, }), - handler: `${index.slice(0, -3)}.${handler}`, + handler: resolvedHandler, }); } } diff --git a/packages/@aws-cdk/aws-lambda-python/lib/index.ts b/packages/@aws-cdk/aws-lambda-python/lib/index.ts index 5459e812abb91..891bdeae63c53 100644 --- a/packages/@aws-cdk/aws-lambda-python/lib/index.ts +++ b/packages/@aws-cdk/aws-lambda-python/lib/index.ts @@ -1,2 +1,3 @@ export * from './function'; export * from './layer'; +export * from './types'; diff --git a/packages/@aws-cdk/aws-lambda-python/lib/layer.ts b/packages/@aws-cdk/aws-lambda-python/lib/layer.ts index 4f247acc10bae..d0b9bc6d0f529 100644 --- a/packages/@aws-cdk/aws-lambda-python/lib/layer.ts +++ b/packages/@aws-cdk/aws-lambda-python/lib/layer.ts @@ -1,6 +1,7 @@ import * as path from 'path'; import * as lambda from '@aws-cdk/aws-lambda'; -import { bundle } from './bundling'; +import { Bundling } from './bundling'; +import { BundlingOptions } from './types'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order @@ -18,7 +19,7 @@ export interface PythonLayerVersionProps extends lambda.LayerVersionOptions { /** * The runtimes compatible with the python layer. * - * @default - All runtimes are supported. + * @default - Only Python 3.7 is supported. */ readonly compatibleRuntimes?: lambda.Runtime[]; @@ -27,6 +28,13 @@ export interface PythonLayerVersionProps extends lambda.LayerVersionOptions { * @default [Architecture.X86_64] */ readonly compatibleArchitectures?: lambda.Architecture[]; + /** + * Bundling options to use for this function. Use this to specify custom bundling options like + * the bundling Docker image, asset hash type, custom hash, architecture, etc. + * + * @default - Use the default bundling Docker image, with x86_64 architecture. + */ + readonly bundling?: BundlingOptions; } /** @@ -54,11 +62,12 @@ export class PythonLayerVersion extends lambda.LayerVersion { super(scope, id, { ...props, compatibleRuntimes, - code: bundle({ + code: Bundling.bundle({ entry, runtime, architecture, outputPathSuffix: 'python', + ...props.bundling, }), }); } diff --git a/packages/@aws-cdk/aws-lambda-python/lib/packaging.ts b/packages/@aws-cdk/aws-lambda-python/lib/packaging.ts new file mode 100644 index 0000000000000..55f9118cd3ff1 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/lib/packaging.ts @@ -0,0 +1,75 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +export enum DependenciesFile { + PIP = 'requirements.txt', + POETRY = 'poetry.lock', + PIPENV = 'Pipfile.lock', + NONE = '' +} + +export interface PackagingProps { + /** + * Dependency file for the type of packaging. + */ + readonly dependenciesFile: DependenciesFile; + /** + * Command to export the dependencies into a pip-compatible `requirements.txt` format. + * + * @default - No dependencies are exported. + */ + readonly exportCommand?: string; +} + +export class Packaging { + + /** + * Standard packaging with `pip`. + */ + public static readonly PIP = new Packaging({ + dependenciesFile: DependenciesFile.PIP, + }); + + /** + * Packaging with `pipenv`. + */ + public static readonly PIPENV = new Packaging({ + dependenciesFile: DependenciesFile.PIPENV, + // By default, pipenv creates a virtualenv in `/.local`, so we force it to create one in the package directory. + // At the end, we remove the virtualenv to avoid creating a duplicate copy in the Lambda package. + exportCommand: `PIPENV_VENV_IN_PROJECT=1 pipenv lock -r > ${DependenciesFile.PIP} && rm -rf .venv`, + }); + + /** + * Packaging with `poetry`. + */ + public static readonly POETRY = new Packaging({ + dependenciesFile: DependenciesFile.POETRY, + // Export dependencies with credentials avaiable in the bundling image. + exportCommand: `poetry export --with-credentials --format ${DependenciesFile.PIP} --output ${DependenciesFile.PIP}`, + }); + + /** + * No dependencies or packaging. + */ + public static readonly NONE = new Packaging({ dependenciesFile: DependenciesFile.NONE }); + + public static fromEntry(entry: string): Packaging { + if (fs.existsSync(path.join(entry, DependenciesFile.PIPENV))) { + return Packaging.PIPENV; + } if (fs.existsSync(path.join(entry, DependenciesFile.POETRY))) { + return Packaging.POETRY; + } else if (fs.existsSync(path.join(entry, DependenciesFile.PIP))) { + return Packaging.PIP; + } else { + return Packaging.NONE; + } + } + + public readonly dependenciesFile: string; + public readonly exportCommand?: string; + constructor(props: PackagingProps) { + this.dependenciesFile = props.dependenciesFile; + this.exportCommand = props.exportCommand; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-python/lib/types.ts b/packages/@aws-cdk/aws-lambda-python/lib/types.ts new file mode 100644 index 0000000000000..129487b9725db --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/lib/types.ts @@ -0,0 +1,72 @@ +import { AssetHashType, DockerImage } from '@aws-cdk/core'; + + +/** + * Options for bundling + */ +export interface BundlingOptions { + /** + * Output path suffix ('python' for a layer, '' otherwise) + * + * @default ''' + */ + readonly outputPathSuffix?: string; + + /** + * Docker image to use for bundling. If no options are provided, the default bundling image + * will be used. Dependencies will be installed using the default packaging commands + * and copied over from into the Lambda asset. + * + * @default - Default bundling image. + */ + readonly image?: DockerImage; + + /** + * Optional build arguments to pass to the default container. This can be used to customize + * the index URLs used for installing dependencies. + * This is not used if a custom image is provided. + * + * @default - No build arguments. + */ + readonly buildArgs?: { [key: string]: string }; + + /** + * Determines how asset hash is calculated. Assets will get rebuild and + * uploaded only if their hash has changed. + * + * If asset hash is set to `SOURCE` (default), then only changes to the source + * directory will cause the asset to rebuild. This means, for example, that in + * order to pick up a new dependency version, a change must be made to the + * source tree. Ideally, this can be implemented by including a dependency + * lockfile in your source tree or using fixed dependencies. + * + * If the asset hash is set to `OUTPUT`, the hash is calculated after + * bundling. This means that any change in the output will cause the asset to + * be invalidated and uploaded. Bear in mind that `pip` adds timestamps to + * dependencies it installs, which implies that in this mode Python bundles + * will _always_ get rebuild and uploaded. Normally this is an anti-pattern + * since build + * + * @default AssetHashType.SOURCE By default, hash is calculated based on the + * contents of the source directory. This means that only updates to the + * source will cause the asset to rebuild. + */ + + readonly assetHashType?: AssetHashType; + + /** + * Specify a custom hash for this asset. If `assetHashType` is set it must + * be set to `AssetHashType.CUSTOM`. For consistency, this custom hash will + * be SHA256 hashed and encoded as hex. The resulting hash will be the asset + * hash. + * + * NOTE: the hash is used in order to identify a specific revision of the asset, and + * used for optimizing and caching deployment activities related to this asset such as + * packaging, uploading to Amazon S3, etc. If you chose to customize the hash, you will + * need to make sure it is updated every time the asset changes, or otherwise it is + * possible that some deployments will not be invalidated. + * + * @default - Based on `assetHashType` + */ + readonly assetHash?: string; +} diff --git a/packages/@aws-cdk/aws-lambda-python/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-lambda-python/rosetta/default.ts-fixture index 516396a167e8e..44f98d42e9d09 100644 --- a/packages/@aws-cdk/aws-lambda-python/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-lambda-python/rosetta/default.ts-fixture @@ -1,6 +1,6 @@ // Fixture with packages imported, but nothing else import { Construct } from 'constructs'; -import { Stack } from '@aws-cdk/core'; +import { DockerImage, Stack } from '@aws-cdk/core'; import { Runtime } from '@aws-cdk/aws-lambda'; import * as lambda from '@aws-cdk/aws-lambda-python'; diff --git a/packages/@aws-cdk/aws-lambda-python/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-python/test/bundling.test.ts index 5043a1501853e..5e4cf194f5151 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-python/test/bundling.test.ts @@ -1,8 +1,7 @@ -import * as fs from 'fs'; import * as path from 'path'; import { Architecture, Code, Runtime } from '@aws-cdk/aws-lambda'; -import { DockerImage, FileSystem } from '@aws-cdk/core'; -import { stageDependencies, bundle } from '../lib/bundling'; +import { DockerImage } from '@aws-cdk/core'; +import { Bundling } from '../lib/bundling'; jest.spyOn(Code, 'fromAsset'); jest.spyOn(DockerImage, 'fromBuild'); @@ -26,11 +25,10 @@ beforeEach(() => { test('Bundling a function without dependencies', () => { const entry = path.join(__dirname, 'lambda-handler-nodeps'); - bundle({ + Bundling.bundle({ entry: entry, runtime: Runtime.PYTHON_3_7, architecture: Architecture.X86_64, - outputPathSuffix: '.', }); // Correctly bundles @@ -38,12 +36,12 @@ test('Bundling a function without dependencies', () => { bundling: expect.objectContaining({ command: [ 'bash', '-c', - 'rsync -r . /asset-output/.', + 'cp -R /asset-input /asset-output', ], }), })); - expect(DockerImage.fromBuild).toHaveBeenCalledWith(expect.stringMatching(/python-bundling/), expect.objectContaining({ + expect(DockerImage.fromBuild).toHaveBeenCalledWith(expect.stringMatching(path.join(__dirname, '../lib')), expect.objectContaining({ buildArgs: expect.objectContaining({ IMAGE: expect.stringMatching(/build-python/), }), @@ -51,13 +49,12 @@ test('Bundling a function without dependencies', () => { })); }); -test('Bundling a function with requirements.txt installed', () => { +test('Bundling a function with requirements.txt', () => { const entry = path.join(__dirname, 'lambda-handler'); - bundle({ + Bundling.bundle({ entry: entry, runtime: Runtime.PYTHON_3_7, architecture: Architecture.X86_64, - outputPathSuffix: '.', }); // Correctly bundles @@ -65,7 +62,7 @@ test('Bundling a function with requirements.txt installed', () => { bundling: expect.objectContaining({ command: [ 'bash', '-c', - 'rsync -r /var/dependencies/. /asset-output/. && rsync -r . /asset-output/.', + 'python -m pip install -r requirements.txt -t /asset-output && cp -R /asset-input /asset-output', ], }), })); @@ -73,11 +70,10 @@ test('Bundling a function with requirements.txt installed', () => { test('Bundling Python 2.7 with requirements.txt installed', () => { const entry = path.join(__dirname, 'lambda-handler'); - bundle({ + Bundling.bundle({ entry: entry, runtime: Runtime.PYTHON_2_7, architecture: Architecture.X86_64, - outputPathSuffix: '.', }); // Correctly bundles with requirements.txt pip installed @@ -85,7 +81,7 @@ test('Bundling Python 2.7 with requirements.txt installed', () => { bundling: expect.objectContaining({ command: [ 'bash', '-c', - 'rsync -r /var/dependencies/. /asset-output/. && rsync -r . /asset-output/.', + 'python -m pip install -r requirements.txt -t /asset-output && cp -R /asset-input /asset-output', ], }), })); @@ -94,7 +90,7 @@ test('Bundling Python 2.7 with requirements.txt installed', () => { test('Bundling a layer with dependencies', () => { const entry = path.join(__dirname, 'lambda-handler'); - bundle({ + Bundling.bundle({ entry: entry, runtime: Runtime.PYTHON_3_9, architecture: Architecture.X86_64, @@ -105,7 +101,7 @@ test('Bundling a layer with dependencies', () => { bundling: expect.objectContaining({ command: [ 'bash', '-c', - 'rsync -r /var/dependencies/. /asset-output/python && rsync -r . /asset-output/python', + 'python -m pip install -r requirements.txt -t /asset-output/python && cp -R /asset-input /asset-output/python', ], }), })); @@ -114,7 +110,7 @@ test('Bundling a layer with dependencies', () => { test('Bundling a python code layer', () => { const entry = path.join(__dirname, 'lambda-handler-nodeps'); - bundle({ + Bundling.bundle({ entry: path.join(entry, '.'), runtime: Runtime.PYTHON_3_9, architecture: Architecture.X86_64, @@ -125,30 +121,89 @@ test('Bundling a python code layer', () => { bundling: expect.objectContaining({ command: [ 'bash', '-c', - 'rsync -r . /asset-output/python', + 'cp -R /asset-input /asset-output/python', ], }), })); }); +test('Bundling a function with pipenv dependencies', () => { + const entry = path.join(__dirname, 'lambda-handler-pipenv'); -describe('Dependency detection', () => { - test.each(['Pipfile', 'poetry.lock', 'requirements.txt'])('detect dependency %p', filename => { - // GIVEN - const sourcedir = FileSystem.mkdtemp('source-'); - const stagedir = FileSystem.mkdtemp('stage-'); - fs.writeFileSync(path.join(sourcedir, filename), 'dummy!'); + Bundling.bundle({ + entry: path.join(entry, '.'), + runtime: Runtime.PYTHON_3_9, + architecture: Architecture.X86_64, + outputPathSuffix: 'python', + }); + + expect(Code.fromAsset).toHaveBeenCalledWith(entry, expect.objectContaining({ + bundling: expect.objectContaining({ + command: [ + 'bash', '-c', + 'PIPENV_VENV_IN_PROJECT=1 pipenv lock -r > requirements.txt && rm -rf .venv && python -m pip install -r requirements.txt -t /asset-output/python && cp -R /asset-input /asset-output/python', + ], + }), + })); +}); - // WHEN - const found = stageDependencies(sourcedir, stagedir); +test('Bundling a function with poetry dependencies', () => { + const entry = path.join(__dirname, 'lambda-handler-poetry'); - // THEN - expect(found).toBeTruthy(); - expect(fs.existsSync(path.join(stagedir, filename))).toBeTruthy(); + Bundling.bundle({ + entry: path.join(entry, '.'), + runtime: Runtime.PYTHON_3_9, + architecture: Architecture.X86_64, + outputPathSuffix: 'python', }); - test('No known dependencies', () => { - const sourcedir = FileSystem.mkdtemp('source-'); - expect(stageDependencies(sourcedir, '/dummy')).toEqual(false); + expect(Code.fromAsset).toHaveBeenCalledWith(entry, expect.objectContaining({ + bundling: expect.objectContaining({ + command: [ + 'bash', '-c', + 'poetry export --with-credentials --format requirements.txt --output requirements.txt && python -m pip install -r requirements.txt -t /asset-output/python && cp -R /asset-input /asset-output/python', + ], + }), + })); +}); + +test('Bundling a function with custom bundling image', () => { + const entry = path.join(__dirname, 'lambda-handler-custom-build'); + const image = DockerImage.fromBuild(path.join(entry)); + + Bundling.bundle({ + entry: path.join(entry, '.'), + runtime: Runtime.PYTHON_3_9, + architecture: Architecture.X86_64, + outputPathSuffix: 'python', + image, }); + + expect(Code.fromAsset).toHaveBeenCalledWith(entry, expect.objectContaining({ + bundling: expect.objectContaining({ + image, + command: [ + 'bash', '-c', + 'python -m pip install -r requirements.txt -t /asset-output/python && cp -R /asset-input /asset-output/python', + ], + }), + })); + + expect(DockerImage.fromBuild).toHaveBeenCalledWith(expect.stringMatching(entry)); +}); + +test('Bundling with custom build args', () => { + const entry = path.join(__dirname, 'lambda-handler'); + const testPypi = 'https://test.pypi.org/simple/'; + Bundling.bundle({ + entry: entry, + runtime: Runtime.PYTHON_3_7, + buildArgs: { PIP_INDEX_URL: testPypi }, + }); + + expect(DockerImage.fromBuild).toHaveBeenCalledWith(expect.stringMatching(path.join(__dirname, '../lib')), expect.objectContaining({ + buildArgs: expect.objectContaining({ + PIP_INDEX_URL: testPypi, + }), + })); }); diff --git a/packages/@aws-cdk/aws-lambda-python/test/function.test.ts b/packages/@aws-cdk/aws-lambda-python/test/function.test.ts index 31450f0a7c43c..7eac6ad3df9ec 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda-python/test/function.test.ts @@ -1,38 +1,41 @@ +import * as path from 'path'; import { Template } from '@aws-cdk/assertions'; import { Code, Runtime } from '@aws-cdk/aws-lambda'; -import { AssetHashType, AssetOptions, Stack } from '@aws-cdk/core'; +import { AssetHashType, DockerImage, Stack } from '@aws-cdk/core'; import { PythonFunction } from '../lib'; -import { bundle } from '../lib/bundling'; +import { Bundling, BundlingProps } from '../lib/bundling'; jest.mock('../lib/bundling', () => { return { - bundle: jest.fn().mockImplementation((options: AssetOptions): Code => { - const mockObjectKey = (() => { - const hashType = options.assetHashType ?? (options.assetHash ? 'custom' : 'source'); - switch (hashType) { - case 'source': return 'SOURCE_MOCK'; - case 'output': return 'OUTPUT_MOCK'; - case 'custom': { - if (!options.assetHash) { throw new Error('no custom hash'); } - return options.assetHash; + Bundling: { + bundle: jest.fn().mockImplementation((options: BundlingProps): Code => { + const mockObjectKey = (() => { + const hashType = options.assetHashType ?? (options.assetHash ? 'custom' : 'source'); + switch (hashType) { + case 'source': return 'SOURCE_MOCK'; + case 'output': return 'OUTPUT_MOCK'; + case 'custom': { + if (!options.assetHash) { throw new Error('no custom hash'); } + return options.assetHash; + } } - } - - throw new Error('unexpected asset hash type'); - })(); - - return { - isInline: false, - bind: () => ({ - s3Location: { - bucketName: 'mock-bucket-name', - objectKey: mockObjectKey, - }, - }), - bindToResource: () => { return; }, - }; - }), - hasDependencies: jest.fn().mockReturnValue(false), + + throw new Error('unexpected asset hash type'); + })(); + + return { + isInline: false, + bind: () => ({ + s3Location: { + bucketName: 'mock-bucket-name', + objectKey: mockObjectKey, + }, + }), + bindToResource: () => { return; }, + }; + }), + hasDependencies: jest.fn().mockReturnValue(false), + }, }; }); @@ -48,9 +51,8 @@ test('PythonFunction with defaults', () => { runtime: Runtime.PYTHON_3_8, }); - expect(bundle).toHaveBeenCalledWith(expect.objectContaining({ + expect(Bundling.bundle).toHaveBeenCalledWith(expect.objectContaining({ entry: expect.stringMatching(/aws-lambda-python\/test\/lambda-handler$/), - outputPathSuffix: '.', })); Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { @@ -66,13 +68,12 @@ test('PythonFunction with index in a subdirectory', () => { runtime: Runtime.PYTHON_3_8, }); - expect(bundle).toHaveBeenCalledWith(expect.objectContaining({ + expect(Bundling.bundle).toHaveBeenCalledWith(expect.objectContaining({ entry: expect.stringMatching(/aws-lambda-python\/test\/lambda-handler-sub$/), - outputPathSuffix: '.', })); Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { - Handler: 'inner/custom_index.custom_handler', + Handler: 'inner.custom_index.custom_handler', }); }); @@ -110,24 +111,24 @@ test('allows specifying hash type', () => { entry: 'test/lambda-handler-nodeps', index: 'index.py', handler: 'handler', - assetHashType: AssetHashType.SOURCE, runtime: Runtime.PYTHON_3_8, + bundling: { assetHashType: AssetHashType.SOURCE }, }); new PythonFunction(stack, 'output', { entry: 'test/lambda-handler-nodeps', index: 'index.py', handler: 'handler', - assetHashType: AssetHashType.OUTPUT, runtime: Runtime.PYTHON_3_8, + bundling: { assetHashType: AssetHashType.OUTPUT }, }); new PythonFunction(stack, 'custom', { entry: 'test/lambda-handler-nodeps', index: 'index.py', handler: 'handler', - assetHash: 'MY_CUSTOM_HASH', runtime: Runtime.PYTHON_3_8, + bundling: { assetHash: 'MY_CUSTOM_HASH' }, }); Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { @@ -151,3 +152,18 @@ test('allows specifying hash type', () => { }, }); }); + +test('Allows use of custom bundling image', () => { + const entry = path.join(__dirname, 'lambda-handler-custom-build'); + const image = DockerImage.fromBuild(path.join(entry)); + + new PythonFunction(stack, 'function', { + entry, + runtime: Runtime.PYTHON_3_8, + bundling: { image }, + }); + + expect(Bundling.bundle).toHaveBeenCalledWith(expect.objectContaining({ + image, + })); +}); diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.custom-build.expected.json similarity index 59% rename from packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.expected.json rename to packages/@aws-cdk/aws-lambda-python/test/integ.function.custom-build.expected.json index 7cfe2de877c4a..dd78e2d129e14 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.custom-build.expected.json @@ -1,6 +1,6 @@ { "Resources": { - "functionServiceRoleEF216095": { + "myhandlerServiceRole77891068": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -31,12 +31,12 @@ ] } }, - "functionF19B1A04": { + "myhandlerD202FA8E": { "Type": "AWS::Lambda::Function", "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters50b2fdbf0e4a082a383b55e783825b1158810c097a57717d8acb11b2e2db0bfaS3Bucket954AFCD2" + "Ref": "AssetParameters623127c548bfba764c605bdc57770784dee3a4e8255ae2ad2590a2f5d42e7abfS3BucketE101E6F9" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters50b2fdbf0e4a082a383b55e783825b1158810c097a57717d8acb11b2e2db0bfaS3VersionKeyDC672869" + "Ref": "AssetParameters623127c548bfba764c605bdc57770784dee3a4e8255ae2ad2590a2f5d42e7abfS3VersionKey08D4E5C6" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters50b2fdbf0e4a082a383b55e783825b1158810c097a57717d8acb11b2e2db0bfaS3VersionKeyDC672869" + "Ref": "AssetParameters623127c548bfba764c605bdc57770784dee3a4e8255ae2ad2590a2f5d42e7abfS3VersionKey08D4E5C6" } ] } @@ -74,36 +74,39 @@ }, "Role": { "Fn::GetAtt": [ - "functionServiceRoleEF216095", + "myhandlerServiceRole77891068", "Arn" ] }, "Handler": "index.handler", - "Runtime": "python3.9" + "Runtime": "python3.8" }, "DependsOn": [ - "functionServiceRoleEF216095" + "myhandlerServiceRole77891068" ] } }, "Parameters": { - "AssetParameters50b2fdbf0e4a082a383b55e783825b1158810c097a57717d8acb11b2e2db0bfaS3Bucket954AFCD2": { + "AssetParameters623127c548bfba764c605bdc57770784dee3a4e8255ae2ad2590a2f5d42e7abfS3BucketE101E6F9": { "Type": "String", - "Description": "S3 bucket for asset \"50b2fdbf0e4a082a383b55e783825b1158810c097a57717d8acb11b2e2db0bfa\"" + "Description": "S3 bucket for asset \"623127c548bfba764c605bdc57770784dee3a4e8255ae2ad2590a2f5d42e7abf\"" }, - "AssetParameters50b2fdbf0e4a082a383b55e783825b1158810c097a57717d8acb11b2e2db0bfaS3VersionKeyDC672869": { + "AssetParameters623127c548bfba764c605bdc57770784dee3a4e8255ae2ad2590a2f5d42e7abfS3VersionKey08D4E5C6": { "Type": "String", - "Description": "S3 key for asset version \"50b2fdbf0e4a082a383b55e783825b1158810c097a57717d8acb11b2e2db0bfa\"" + "Description": "S3 key for asset version \"623127c548bfba764c605bdc57770784dee3a4e8255ae2ad2590a2f5d42e7abf\"" }, - "AssetParameters50b2fdbf0e4a082a383b55e783825b1158810c097a57717d8acb11b2e2db0bfaArtifactHash06BB4065": { + "AssetParameters623127c548bfba764c605bdc57770784dee3a4e8255ae2ad2590a2f5d42e7abfArtifactHash2D0E1467": { "Type": "String", - "Description": "Artifact hash for asset \"50b2fdbf0e4a082a383b55e783825b1158810c097a57717d8acb11b2e2db0bfa\"" + "Description": "Artifact hash for asset \"623127c548bfba764c605bdc57770784dee3a4e8255ae2ad2590a2f5d42e7abf\"" } }, "Outputs": { - "Function": { + "FunctionArn": { "Value": { - "Ref": "functionF19B1A04" + "Fn::GetAtt": [ + "myhandlerD202FA8E", + "Arn" + ] } } } diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.custom-build.ts b/packages/@aws-cdk/aws-lambda-python/test/integ.function.custom-build.ts new file mode 100644 index 0000000000000..4553b3894cc02 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.custom-build.ts @@ -0,0 +1,31 @@ +import * as path from 'path'; +import { Runtime } from '@aws-cdk/aws-lambda'; +import { App, CfnOutput, DockerImage, Stack, StackProps } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import * as lambda from '../lib'; + +/* + * Stack verification steps: + * * aws lambda invoke --function-name --invocation-type Event --payload '"OK"' response.json + */ + +class TestStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const entry = path.join(__dirname, 'lambda-handler-custom-build'); + const fn = new lambda.PythonFunction(this, 'my_handler', { + entry: entry, + bundling: { image: DockerImage.fromBuild(path.join(entry)) }, + runtime: Runtime.PYTHON_3_8, + }); + + new CfnOutput(this, 'FunctionArn', { + value: fn.functionArn, + }); + } +} + +const app = new App(); +new TestStack(app, 'cdk-integ-lambda-custom-build'); +app.synth(); diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.expected.json index ed3d577fb40a3..bf6248e87c68e 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters3dc2f7b8375fbf383f44eb8f798d324f60d516946c9f829fca3c5f747f973374S3Bucket07AE44EE" + "Ref": "AssetParameters13be70bc2398416ddd6c8e123f99becf43b8b1c3d00cad2447f9f75cea39d055S3Bucket4083148B" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters3dc2f7b8375fbf383f44eb8f798d324f60d516946c9f829fca3c5f747f973374S3VersionKey01F8F2A1" + "Ref": "AssetParameters13be70bc2398416ddd6c8e123f99becf43b8b1c3d00cad2447f9f75cea39d055S3VersionKey32133DD4" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters3dc2f7b8375fbf383f44eb8f798d324f60d516946c9f829fca3c5f747f973374S3VersionKey01F8F2A1" + "Ref": "AssetParameters13be70bc2398416ddd6c8e123f99becf43b8b1c3d00cad2447f9f75cea39d055S3VersionKey32133DD4" } ] } @@ -87,17 +87,17 @@ } }, "Parameters": { - "AssetParameters3dc2f7b8375fbf383f44eb8f798d324f60d516946c9f829fca3c5f747f973374S3Bucket07AE44EE": { + "AssetParameters13be70bc2398416ddd6c8e123f99becf43b8b1c3d00cad2447f9f75cea39d055S3Bucket4083148B": { "Type": "String", - "Description": "S3 bucket for asset \"3dc2f7b8375fbf383f44eb8f798d324f60d516946c9f829fca3c5f747f973374\"" + "Description": "S3 bucket for asset \"13be70bc2398416ddd6c8e123f99becf43b8b1c3d00cad2447f9f75cea39d055\"" }, - "AssetParameters3dc2f7b8375fbf383f44eb8f798d324f60d516946c9f829fca3c5f747f973374S3VersionKey01F8F2A1": { + "AssetParameters13be70bc2398416ddd6c8e123f99becf43b8b1c3d00cad2447f9f75cea39d055S3VersionKey32133DD4": { "Type": "String", - "Description": "S3 key for asset version \"3dc2f7b8375fbf383f44eb8f798d324f60d516946c9f829fca3c5f747f973374\"" + "Description": "S3 key for asset version \"13be70bc2398416ddd6c8e123f99becf43b8b1c3d00cad2447f9f75cea39d055\"" }, - "AssetParameters3dc2f7b8375fbf383f44eb8f798d324f60d516946c9f829fca3c5f747f973374ArtifactHashDECBC32A": { + "AssetParameters13be70bc2398416ddd6c8e123f99becf43b8b1c3d00cad2447f9f75cea39d055ArtifactHash83828A10": { "Type": "String", - "Description": "Artifact hash for asset \"3dc2f7b8375fbf383f44eb8f798d324f60d516946c9f829fca3c5f747f973374\"" + "Description": "Artifact hash for asset \"13be70bc2398416ddd6c8e123f99becf43b8b1c3d00cad2447f9f75cea39d055\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.nodeps.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.nodeps.expected.json new file mode 100644 index 0000000000000..a12a5675b097f --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.nodeps.expected.json @@ -0,0 +1,113 @@ +{ + "Resources": { + "myhandlerServiceRole77891068": { + "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" + ] + ] + } + ] + } + }, + "myhandlerD202FA8E": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParametersadeacc0a6e55ff50a5243310913e886cc41725125e145a916ff3ec01369b2201S3BucketE6A02FFC" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersadeacc0a6e55ff50a5243310913e886cc41725125e145a916ff3ec01369b2201S3VersionKey78F64422" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersadeacc0a6e55ff50a5243310913e886cc41725125e145a916ff3ec01369b2201S3VersionKey78F64422" + } + ] + } + ] + } + ] + ] + } + }, + "Role": { + "Fn::GetAtt": [ + "myhandlerServiceRole77891068", + "Arn" + ] + }, + "Handler": "index.handler", + "Runtime": "python3.8" + }, + "DependsOn": [ + "myhandlerServiceRole77891068" + ] + } + }, + "Parameters": { + "AssetParametersadeacc0a6e55ff50a5243310913e886cc41725125e145a916ff3ec01369b2201S3BucketE6A02FFC": { + "Type": "String", + "Description": "S3 bucket for asset \"adeacc0a6e55ff50a5243310913e886cc41725125e145a916ff3ec01369b2201\"" + }, + "AssetParametersadeacc0a6e55ff50a5243310913e886cc41725125e145a916ff3ec01369b2201S3VersionKey78F64422": { + "Type": "String", + "Description": "S3 key for asset version \"adeacc0a6e55ff50a5243310913e886cc41725125e145a916ff3ec01369b2201\"" + }, + "AssetParametersadeacc0a6e55ff50a5243310913e886cc41725125e145a916ff3ec01369b2201ArtifactHash5EE39E2F": { + "Type": "String", + "Description": "Artifact hash for asset \"adeacc0a6e55ff50a5243310913e886cc41725125e145a916ff3ec01369b2201\"" + } + }, + "Outputs": { + "FunctionArn": { + "Value": { + "Fn::GetAtt": [ + "myhandlerD202FA8E", + "Arn" + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.nodeps.ts b/packages/@aws-cdk/aws-lambda-python/test/integ.function.nodeps.ts new file mode 100644 index 0000000000000..54d7eadd516c1 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.nodeps.ts @@ -0,0 +1,29 @@ +import * as path from 'path'; +import { Runtime } from '@aws-cdk/aws-lambda'; +import { App, CfnOutput, Stack, StackProps } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import * as lambda from '../lib'; + +/* + * Stack verification steps: + * * aws lambda invoke --function-name --invocation-type Event --payload '"OK"' response.json + */ + +class TestStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const fn = new lambda.PythonFunction(this, 'my_handler', { + entry: path.join(__dirname, 'lambda-handler-nodeps'), + runtime: Runtime.PYTHON_3_8, + }); + + new CfnOutput(this, 'FunctionArn', { + value: fn.functionArn, + }); + } +} + +const app = new App(); +new TestStack(app, 'cdk-integ-lambda-python'); +app.synth(); diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.expected.json index dc2c5477be700..80d1579481795 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters227b633cde31e51b833da8292d24ce0b348ba0a616dda185e8da6e0d37ff65f7S3BucketBE0FA4A0" + "Ref": "AssetParametersc850e159fa69da9edb39ca17a495c47ca137fb5ea2119efb9b01468b0a4934a2S3BucketC982114B" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters227b633cde31e51b833da8292d24ce0b348ba0a616dda185e8da6e0d37ff65f7S3VersionKeyD39BB444" + "Ref": "AssetParametersc850e159fa69da9edb39ca17a495c47ca137fb5ea2119efb9b01468b0a4934a2S3VersionKey6D9FF4C1" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters227b633cde31e51b833da8292d24ce0b348ba0a616dda185e8da6e0d37ff65f7S3VersionKeyD39BB444" + "Ref": "AssetParametersc850e159fa69da9edb39ca17a495c47ca137fb5ea2119efb9b01468b0a4934a2S3VersionKey6D9FF4C1" } ] } @@ -121,7 +121,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters02c6c3394cc8925c65f92837c1ff8d5db16f412453a6247ffaace60e6335d100S3Bucket6E69F943" + "Ref": "AssetParametersebc380ae5f94c7b58c30d780f064bc980ad95d026b4e753349d00efc56f40427S3Bucket42FB475E" }, "S3Key": { "Fn::Join": [ @@ -134,7 +134,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters02c6c3394cc8925c65f92837c1ff8d5db16f412453a6247ffaace60e6335d100S3VersionKey8350D955" + "Ref": "AssetParametersebc380ae5f94c7b58c30d780f064bc980ad95d026b4e753349d00efc56f40427S3VersionKeyFFD26447" } ] } @@ -147,7 +147,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters02c6c3394cc8925c65f92837c1ff8d5db16f412453a6247ffaace60e6335d100S3VersionKey8350D955" + "Ref": "AssetParametersebc380ae5f94c7b58c30d780f064bc980ad95d026b4e753349d00efc56f40427S3VersionKeyFFD26447" } ] } @@ -172,29 +172,29 @@ } }, "Parameters": { - "AssetParameters227b633cde31e51b833da8292d24ce0b348ba0a616dda185e8da6e0d37ff65f7S3BucketBE0FA4A0": { + "AssetParametersc850e159fa69da9edb39ca17a495c47ca137fb5ea2119efb9b01468b0a4934a2S3BucketC982114B": { "Type": "String", - "Description": "S3 bucket for asset \"227b633cde31e51b833da8292d24ce0b348ba0a616dda185e8da6e0d37ff65f7\"" + "Description": "S3 bucket for asset \"c850e159fa69da9edb39ca17a495c47ca137fb5ea2119efb9b01468b0a4934a2\"" }, - "AssetParameters227b633cde31e51b833da8292d24ce0b348ba0a616dda185e8da6e0d37ff65f7S3VersionKeyD39BB444": { + "AssetParametersc850e159fa69da9edb39ca17a495c47ca137fb5ea2119efb9b01468b0a4934a2S3VersionKey6D9FF4C1": { "Type": "String", - "Description": "S3 key for asset version \"227b633cde31e51b833da8292d24ce0b348ba0a616dda185e8da6e0d37ff65f7\"" + "Description": "S3 key for asset version \"c850e159fa69da9edb39ca17a495c47ca137fb5ea2119efb9b01468b0a4934a2\"" }, - "AssetParameters227b633cde31e51b833da8292d24ce0b348ba0a616dda185e8da6e0d37ff65f7ArtifactHash7E65B893": { + "AssetParametersc850e159fa69da9edb39ca17a495c47ca137fb5ea2119efb9b01468b0a4934a2ArtifactHash27EECEC5": { "Type": "String", - "Description": "Artifact hash for asset \"227b633cde31e51b833da8292d24ce0b348ba0a616dda185e8da6e0d37ff65f7\"" + "Description": "Artifact hash for asset \"c850e159fa69da9edb39ca17a495c47ca137fb5ea2119efb9b01468b0a4934a2\"" }, - "AssetParameters02c6c3394cc8925c65f92837c1ff8d5db16f412453a6247ffaace60e6335d100S3Bucket6E69F943": { + "AssetParametersebc380ae5f94c7b58c30d780f064bc980ad95d026b4e753349d00efc56f40427S3Bucket42FB475E": { "Type": "String", - "Description": "S3 bucket for asset \"02c6c3394cc8925c65f92837c1ff8d5db16f412453a6247ffaace60e6335d100\"" + "Description": "S3 bucket for asset \"ebc380ae5f94c7b58c30d780f064bc980ad95d026b4e753349d00efc56f40427\"" }, - "AssetParameters02c6c3394cc8925c65f92837c1ff8d5db16f412453a6247ffaace60e6335d100S3VersionKey8350D955": { + "AssetParametersebc380ae5f94c7b58c30d780f064bc980ad95d026b4e753349d00efc56f40427S3VersionKeyFFD26447": { "Type": "String", - "Description": "S3 key for asset version \"02c6c3394cc8925c65f92837c1ff8d5db16f412453a6247ffaace60e6335d100\"" + "Description": "S3 key for asset version \"ebc380ae5f94c7b58c30d780f064bc980ad95d026b4e753349d00efc56f40427\"" }, - "AssetParameters02c6c3394cc8925c65f92837c1ff8d5db16f412453a6247ffaace60e6335d100ArtifactHashFBCF65DE": { + "AssetParametersebc380ae5f94c7b58c30d780f064bc980ad95d026b4e753349d00efc56f40427ArtifactHashCC6CC552": { "Type": "String", - "Description": "Artifact hash for asset \"02c6c3394cc8925c65f92837c1ff8d5db16f412453a6247ffaace60e6335d100\"" + "Description": "Artifact hash for asset \"ebc380ae5f94c7b58c30d780f064bc980ad95d026b4e753349d00efc56f40427\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.poetry.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.poetry.expected.json index 0ed5358e2c8c3..868afeba6ff43 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.poetry.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.poetry.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters8c61809cd22a99ff94bd310623c77431c57aa8b1fd4d2ccfb76488d63a663302S3Bucket19CB0678" + "Ref": "AssetParametersab7f43c80b3b659f320744f583b7bfda3605f7018c253ab2e7615cfb667cb0daS3Bucket142AE375" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters8c61809cd22a99ff94bd310623c77431c57aa8b1fd4d2ccfb76488d63a663302S3VersionKeyA2FCDE76" + "Ref": "AssetParametersab7f43c80b3b659f320744f583b7bfda3605f7018c253ab2e7615cfb667cb0daS3VersionKeyDC1A62D5" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters8c61809cd22a99ff94bd310623c77431c57aa8b1fd4d2ccfb76488d63a663302S3VersionKeyA2FCDE76" + "Ref": "AssetParametersab7f43c80b3b659f320744f583b7bfda3605f7018c253ab2e7615cfb667cb0daS3VersionKeyDC1A62D5" } ] } @@ -121,7 +121,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters40c9006277807fed5dd60eb40b6160230d1966e5a491ff67e8f502b18009d9ebS3Bucket07693CB5" + "Ref": "AssetParameters67452e07162ae977faecaa7c71cf523f4442341f285bd53f84089624ce7fff1dS3BucketB5B7A82F" }, "S3Key": { "Fn::Join": [ @@ -134,7 +134,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters40c9006277807fed5dd60eb40b6160230d1966e5a491ff67e8f502b18009d9ebS3VersionKey453B45DF" + "Ref": "AssetParameters67452e07162ae977faecaa7c71cf523f4442341f285bd53f84089624ce7fff1dS3VersionKey06225DD1" } ] } @@ -147,7 +147,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters40c9006277807fed5dd60eb40b6160230d1966e5a491ff67e8f502b18009d9ebS3VersionKey453B45DF" + "Ref": "AssetParameters67452e07162ae977faecaa7c71cf523f4442341f285bd53f84089624ce7fff1dS3VersionKey06225DD1" } ] } @@ -172,29 +172,29 @@ } }, "Parameters": { - "AssetParameters8c61809cd22a99ff94bd310623c77431c57aa8b1fd4d2ccfb76488d63a663302S3Bucket19CB0678": { + "AssetParametersab7f43c80b3b659f320744f583b7bfda3605f7018c253ab2e7615cfb667cb0daS3Bucket142AE375": { "Type": "String", - "Description": "S3 bucket for asset \"8c61809cd22a99ff94bd310623c77431c57aa8b1fd4d2ccfb76488d63a663302\"" + "Description": "S3 bucket for asset \"ab7f43c80b3b659f320744f583b7bfda3605f7018c253ab2e7615cfb667cb0da\"" }, - "AssetParameters8c61809cd22a99ff94bd310623c77431c57aa8b1fd4d2ccfb76488d63a663302S3VersionKeyA2FCDE76": { + "AssetParametersab7f43c80b3b659f320744f583b7bfda3605f7018c253ab2e7615cfb667cb0daS3VersionKeyDC1A62D5": { "Type": "String", - "Description": "S3 key for asset version \"8c61809cd22a99ff94bd310623c77431c57aa8b1fd4d2ccfb76488d63a663302\"" + "Description": "S3 key for asset version \"ab7f43c80b3b659f320744f583b7bfda3605f7018c253ab2e7615cfb667cb0da\"" }, - "AssetParameters8c61809cd22a99ff94bd310623c77431c57aa8b1fd4d2ccfb76488d63a663302ArtifactHash7948D306": { + "AssetParametersab7f43c80b3b659f320744f583b7bfda3605f7018c253ab2e7615cfb667cb0daArtifactHash0EF1F0C3": { "Type": "String", - "Description": "Artifact hash for asset \"8c61809cd22a99ff94bd310623c77431c57aa8b1fd4d2ccfb76488d63a663302\"" + "Description": "Artifact hash for asset \"ab7f43c80b3b659f320744f583b7bfda3605f7018c253ab2e7615cfb667cb0da\"" }, - "AssetParameters40c9006277807fed5dd60eb40b6160230d1966e5a491ff67e8f502b18009d9ebS3Bucket07693CB5": { + "AssetParameters67452e07162ae977faecaa7c71cf523f4442341f285bd53f84089624ce7fff1dS3BucketB5B7A82F": { "Type": "String", - "Description": "S3 bucket for asset \"40c9006277807fed5dd60eb40b6160230d1966e5a491ff67e8f502b18009d9eb\"" + "Description": "S3 bucket for asset \"67452e07162ae977faecaa7c71cf523f4442341f285bd53f84089624ce7fff1d\"" }, - "AssetParameters40c9006277807fed5dd60eb40b6160230d1966e5a491ff67e8f502b18009d9ebS3VersionKey453B45DF": { + "AssetParameters67452e07162ae977faecaa7c71cf523f4442341f285bd53f84089624ce7fff1dS3VersionKey06225DD1": { "Type": "String", - "Description": "S3 key for asset version \"40c9006277807fed5dd60eb40b6160230d1966e5a491ff67e8f502b18009d9eb\"" + "Description": "S3 key for asset version \"67452e07162ae977faecaa7c71cf523f4442341f285bd53f84089624ce7fff1d\"" }, - "AssetParameters40c9006277807fed5dd60eb40b6160230d1966e5a491ff67e8f502b18009d9ebArtifactHashFAE08C9B": { + "AssetParameters67452e07162ae977faecaa7c71cf523f4442341f285bd53f84089624ce7fff1dArtifactHash253A552F": { "Type": "String", - "Description": "Artifact hash for asset \"40c9006277807fed5dd60eb40b6160230d1966e5a491ff67e8f502b18009d9eb\"" + "Description": "Artifact hash for asset \"67452e07162ae977faecaa7c71cf523f4442341f285bd53f84089624ce7fff1d\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.project.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.project.expected.json index 0e27a16d95cac..0f96e29246a70 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.project.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.project.expected.json @@ -5,7 +5,7 @@ "Properties": { "Content": { "S3Bucket": { - "Ref": "AssetParameterse6dde8a412edfc6d967ce7244c803ebd6523ebc30b4c5562919028c6edf8f137S3Bucket3B546DC9" + "Ref": "AssetParameters1f7d3c2f23a4820c4d01a0bce4add499802732068e570fb63c9f9ae0c2011949S3BucketE93E5D2C" }, "S3Key": { "Fn::Join": [ @@ -18,7 +18,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterse6dde8a412edfc6d967ce7244c803ebd6523ebc30b4c5562919028c6edf8f137S3VersionKeyAB88DB86" + "Ref": "AssetParameters1f7d3c2f23a4820c4d01a0bce4add499802732068e570fb63c9f9ae0c2011949S3VersionKey13A824E8" } ] } @@ -31,7 +31,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterse6dde8a412edfc6d967ce7244c803ebd6523ebc30b4c5562919028c6edf8f137S3VersionKeyAB88DB86" + "Ref": "AssetParameters1f7d3c2f23a4820c4d01a0bce4add499802732068e570fb63c9f9ae0c2011949S3VersionKey13A824E8" } ] } @@ -82,7 +82,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters4364b96840104e125d2c47166f8bada01e9a636f3c23d4cddf681c685d494856S3Bucket091DB419" + "Ref": "AssetParameters3164004f2e76531b3631d1b70c1bee3da1439011bf712a91211b8721868da676S3Bucket9F42D72A" }, "S3Key": { "Fn::Join": [ @@ -95,7 +95,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters4364b96840104e125d2c47166f8bada01e9a636f3c23d4cddf681c685d494856S3VersionKey635CFDCB" + "Ref": "AssetParameters3164004f2e76531b3631d1b70c1bee3da1439011bf712a91211b8721868da676S3VersionKey37C5ED38" } ] } @@ -108,7 +108,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters4364b96840104e125d2c47166f8bada01e9a636f3c23d4cddf681c685d494856S3VersionKey635CFDCB" + "Ref": "AssetParameters3164004f2e76531b3631d1b70c1bee3da1439011bf712a91211b8721868da676S3VersionKey37C5ED38" } ] } @@ -138,29 +138,29 @@ } }, "Parameters": { - "AssetParameterse6dde8a412edfc6d967ce7244c803ebd6523ebc30b4c5562919028c6edf8f137S3Bucket3B546DC9": { + "AssetParameters1f7d3c2f23a4820c4d01a0bce4add499802732068e570fb63c9f9ae0c2011949S3BucketE93E5D2C": { "Type": "String", - "Description": "S3 bucket for asset \"e6dde8a412edfc6d967ce7244c803ebd6523ebc30b4c5562919028c6edf8f137\"" + "Description": "S3 bucket for asset \"1f7d3c2f23a4820c4d01a0bce4add499802732068e570fb63c9f9ae0c2011949\"" }, - "AssetParameterse6dde8a412edfc6d967ce7244c803ebd6523ebc30b4c5562919028c6edf8f137S3VersionKeyAB88DB86": { + "AssetParameters1f7d3c2f23a4820c4d01a0bce4add499802732068e570fb63c9f9ae0c2011949S3VersionKey13A824E8": { "Type": "String", - "Description": "S3 key for asset version \"e6dde8a412edfc6d967ce7244c803ebd6523ebc30b4c5562919028c6edf8f137\"" + "Description": "S3 key for asset version \"1f7d3c2f23a4820c4d01a0bce4add499802732068e570fb63c9f9ae0c2011949\"" }, - "AssetParameterse6dde8a412edfc6d967ce7244c803ebd6523ebc30b4c5562919028c6edf8f137ArtifactHashE6CFFE39": { + "AssetParameters1f7d3c2f23a4820c4d01a0bce4add499802732068e570fb63c9f9ae0c2011949ArtifactHashD6269488": { "Type": "String", - "Description": "Artifact hash for asset \"e6dde8a412edfc6d967ce7244c803ebd6523ebc30b4c5562919028c6edf8f137\"" + "Description": "Artifact hash for asset \"1f7d3c2f23a4820c4d01a0bce4add499802732068e570fb63c9f9ae0c2011949\"" }, - "AssetParameters4364b96840104e125d2c47166f8bada01e9a636f3c23d4cddf681c685d494856S3Bucket091DB419": { + "AssetParameters3164004f2e76531b3631d1b70c1bee3da1439011bf712a91211b8721868da676S3Bucket9F42D72A": { "Type": "String", - "Description": "S3 bucket for asset \"4364b96840104e125d2c47166f8bada01e9a636f3c23d4cddf681c685d494856\"" + "Description": "S3 bucket for asset \"3164004f2e76531b3631d1b70c1bee3da1439011bf712a91211b8721868da676\"" }, - "AssetParameters4364b96840104e125d2c47166f8bada01e9a636f3c23d4cddf681c685d494856S3VersionKey635CFDCB": { + "AssetParameters3164004f2e76531b3631d1b70c1bee3da1439011bf712a91211b8721868da676S3VersionKey37C5ED38": { "Type": "String", - "Description": "S3 key for asset version \"4364b96840104e125d2c47166f8bada01e9a636f3c23d4cddf681c685d494856\"" + "Description": "S3 key for asset version \"3164004f2e76531b3631d1b70c1bee3da1439011bf712a91211b8721868da676\"" }, - "AssetParameters4364b96840104e125d2c47166f8bada01e9a636f3c23d4cddf681c685d494856ArtifactHashB70D4FAA": { + "AssetParameters3164004f2e76531b3631d1b70c1bee3da1439011bf712a91211b8721868da676ArtifactHash74C7DB3B": { "Type": "String", - "Description": "Artifact hash for asset \"4364b96840104e125d2c47166f8bada01e9a636f3c23d4cddf681c685d494856\"" + "Description": "Artifact hash for asset \"3164004f2e76531b3631d1b70c1bee3da1439011bf712a91211b8721868da676\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.py38.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.py38.expected.json index 41d154158be8a..8c028fc0afac0 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.py38.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.py38.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersc3efdfac6089b6c2ee8dce3aac310085091823af614fce0fb5e42799930f526dS3Bucket2D6AE647" + "Ref": "AssetParameters9004e881069342d6cd7cc95689e1c51eb68f9f5d8c0bdfb0c2c52d9aa301d1d6S3Bucket8DE4578D" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersc3efdfac6089b6c2ee8dce3aac310085091823af614fce0fb5e42799930f526dS3VersionKeyF8CA384F" + "Ref": "AssetParameters9004e881069342d6cd7cc95689e1c51eb68f9f5d8c0bdfb0c2c52d9aa301d1d6S3VersionKey86A8985D" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersc3efdfac6089b6c2ee8dce3aac310085091823af614fce0fb5e42799930f526dS3VersionKeyF8CA384F" + "Ref": "AssetParameters9004e881069342d6cd7cc95689e1c51eb68f9f5d8c0bdfb0c2c52d9aa301d1d6S3VersionKey86A8985D" } ] } @@ -87,17 +87,17 @@ } }, "Parameters": { - "AssetParametersc3efdfac6089b6c2ee8dce3aac310085091823af614fce0fb5e42799930f526dS3Bucket2D6AE647": { + "AssetParameters9004e881069342d6cd7cc95689e1c51eb68f9f5d8c0bdfb0c2c52d9aa301d1d6S3Bucket8DE4578D": { "Type": "String", - "Description": "S3 bucket for asset \"c3efdfac6089b6c2ee8dce3aac310085091823af614fce0fb5e42799930f526d\"" + "Description": "S3 bucket for asset \"9004e881069342d6cd7cc95689e1c51eb68f9f5d8c0bdfb0c2c52d9aa301d1d6\"" }, - "AssetParametersc3efdfac6089b6c2ee8dce3aac310085091823af614fce0fb5e42799930f526dS3VersionKeyF8CA384F": { + "AssetParameters9004e881069342d6cd7cc95689e1c51eb68f9f5d8c0bdfb0c2c52d9aa301d1d6S3VersionKey86A8985D": { "Type": "String", - "Description": "S3 key for asset version \"c3efdfac6089b6c2ee8dce3aac310085091823af614fce0fb5e42799930f526d\"" + "Description": "S3 key for asset version \"9004e881069342d6cd7cc95689e1c51eb68f9f5d8c0bdfb0c2c52d9aa301d1d6\"" }, - "AssetParametersc3efdfac6089b6c2ee8dce3aac310085091823af614fce0fb5e42799930f526dArtifactHash8B92B092": { + "AssetParameters9004e881069342d6cd7cc95689e1c51eb68f9f5d8c0bdfb0c2c52d9aa301d1d6ArtifactHash4E095FCC": { "Type": "String", - "Description": "Artifact hash for asset \"c3efdfac6089b6c2ee8dce3aac310085091823af614fce0fb5e42799930f526d\"" + "Description": "Artifact hash for asset \"9004e881069342d6cd7cc95689e1c51eb68f9f5d8c0bdfb0c2c52d9aa301d1d6\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.ts b/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.ts deleted file mode 100644 index ca0b62aceb950..0000000000000 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.ts +++ /dev/null @@ -1,69 +0,0 @@ -/// !cdk-integ pragma:ignore-assets -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import { Runtime } from '@aws-cdk/aws-lambda'; -import { App, CfnOutput, Stack, StackProps } from '@aws-cdk/core'; -import { Construct, ConstructOrder } from 'constructs'; -import * as lambda from '../lib'; - -/* - * Stack verification steps: - * aws lambda invoke --function-name --invocation-type Event --payload $(base64 <<<'"OK"') response.json - */ - -class TestStack extends Stack { - public readonly dependenciesAssetHash: string; - - constructor(scope: Construct, id: string, props?: StackProps) { - super(scope, id, props); - - const fn = new lambda.PythonFunction(this, 'function', { - entry: workDir, - runtime: Runtime.PYTHON_3_9, - }); - - new CfnOutput(this, 'Function', { - value: fn.functionName, - }); - - // Find the asset hash of the dependencies - this.dependenciesAssetHash = (fn.node.findAll(ConstructOrder.POSTORDER) - .find(c => c.node.path.endsWith('Code')) as any) - .assetHash; - } -} - -// This is a special integration test that synths twice to show that docker -// picks up a change in requirements.txt - -// Create a working directory for messing around with requirements.txt -const workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cdk-integ')); -fs.copyFileSync(path.join(__dirname, 'lambda-handler', 'index.py'), path.join(workDir, 'index.py')); -const requirementsTxtPath = path.join(workDir, 'requirements.txt'); - -// Write a requirements.txt with an extraneous dependency (colorama) -const beforeDeps = 'certifi==2020.6.20\nchardet==3.0.4\nidna==2.10\nurllib3==1.26.7\nrequests==2.26.0\nPillow==8.4.0\ncolorama==0.4.3\n'; -fs.writeFileSync(requirementsTxtPath, beforeDeps); - -// Synth the first time -const app = new App(); -const stack1 = new TestStack(app, 'cdk-integ-lambda-python-requirements-removed1'); -app.synth(); - -// Then, write a requirements.txt without the extraneous dependency and synth again -const afterDeps = 'certifi==2020.6.20\nchardet==3.0.4\nidna==2.10\nurllib3==1.26.7\nrequests==2.26.0\nPillow==8.4.0\n'; -fs.writeFileSync(requirementsTxtPath, afterDeps); - -// Synth the same stack a second time with different requirements.txt contents -const app2 = new App(); -const stack2 = new TestStack(app2, 'cdk-integ-lambda-python-requirements-removed2'); -app2.synth(); - -if (!stack1.dependenciesAssetHash || !stack2.dependenciesAssetHash) { - throw new Error('The asset hashes are not both truthy'); -} - -if (stack1.dependenciesAssetHash === stack2.dependenciesAssetHash) { - throw new Error('Removing a dependency did not change the asset hash'); -} diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.sub.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.sub.expected.json new file mode 100644 index 0000000000000..fb29f895b492e --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.sub.expected.json @@ -0,0 +1,113 @@ +{ + "Resources": { + "myhandlerServiceRole77891068": { + "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" + ] + ] + } + ] + } + }, + "myhandlerD202FA8E": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParametersccd39730103b259d263418443f3d426e109312f1f147710e2e5fffc2150b8647S3Bucket11B30F21" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersccd39730103b259d263418443f3d426e109312f1f147710e2e5fffc2150b8647S3VersionKey1D9AFDF5" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersccd39730103b259d263418443f3d426e109312f1f147710e2e5fffc2150b8647S3VersionKey1D9AFDF5" + } + ] + } + ] + } + ] + ] + } + }, + "Role": { + "Fn::GetAtt": [ + "myhandlerServiceRole77891068", + "Arn" + ] + }, + "Handler": "inner.custom_index.custom_handler", + "Runtime": "python3.8" + }, + "DependsOn": [ + "myhandlerServiceRole77891068" + ] + } + }, + "Parameters": { + "AssetParametersccd39730103b259d263418443f3d426e109312f1f147710e2e5fffc2150b8647S3Bucket11B30F21": { + "Type": "String", + "Description": "S3 bucket for asset \"ccd39730103b259d263418443f3d426e109312f1f147710e2e5fffc2150b8647\"" + }, + "AssetParametersccd39730103b259d263418443f3d426e109312f1f147710e2e5fffc2150b8647S3VersionKey1D9AFDF5": { + "Type": "String", + "Description": "S3 key for asset version \"ccd39730103b259d263418443f3d426e109312f1f147710e2e5fffc2150b8647\"" + }, + "AssetParametersccd39730103b259d263418443f3d426e109312f1f147710e2e5fffc2150b8647ArtifactHash997AD273": { + "Type": "String", + "Description": "Artifact hash for asset \"ccd39730103b259d263418443f3d426e109312f1f147710e2e5fffc2150b8647\"" + } + }, + "Outputs": { + "FunctionArn": { + "Value": { + "Fn::GetAtt": [ + "myhandlerD202FA8E", + "Arn" + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.sub.ts b/packages/@aws-cdk/aws-lambda-python/test/integ.function.sub.ts new file mode 100644 index 0000000000000..769fdc0306513 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.sub.ts @@ -0,0 +1,31 @@ +import * as path from 'path'; +import { Runtime } from '@aws-cdk/aws-lambda'; +import { App, CfnOutput, Stack, StackProps } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import * as lambda from '../lib'; + +/* + * Stack verification steps: + * * aws lambda invoke --function-name --invocation-type Event --payload '"OK"' response.json + */ + +class TestStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const fn = new lambda.PythonFunction(this, 'my_handler', { + entry: path.join(__dirname, 'lambda-handler-sub'), + index: 'inner/custom_index.py', + handler: 'custom_handler', + runtime: Runtime.PYTHON_3_8, + }); + + new CfnOutput(this, 'FunctionArn', { + value: fn.functionArn, + }); + } +} + +const app = new App(); +new TestStack(app, 'cdk-integ-lambda-python'); +app.synth(); diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.vpc.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.vpc.expected.json index 99705bba839a3..fb5aafb8c9c75 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.vpc.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.vpc.expected.json @@ -296,7 +296,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersfc7bfbf72c74b955f7bc25d2bb123c0eeec9557cda17481146d51672768907b2S3Bucket383ED51E" + "Ref": "AssetParameters28ffbbca5292e933d802ff7c495367b0d7fddab6f52a3777f67a52f14efc6b38S3BucketF4C94740" }, "S3Key": { "Fn::Join": [ @@ -309,7 +309,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersfc7bfbf72c74b955f7bc25d2bb123c0eeec9557cda17481146d51672768907b2S3VersionKeyA520554C" + "Ref": "AssetParameters28ffbbca5292e933d802ff7c495367b0d7fddab6f52a3777f67a52f14efc6b38S3VersionKey584C9092" } ] } @@ -322,7 +322,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersfc7bfbf72c74b955f7bc25d2bb123c0eeec9557cda17481146d51672768907b2S3VersionKeyA520554C" + "Ref": "AssetParameters28ffbbca5292e933d802ff7c495367b0d7fddab6f52a3777f67a52f14efc6b38S3VersionKey584C9092" } ] } @@ -368,17 +368,17 @@ } }, "Parameters": { - "AssetParametersfc7bfbf72c74b955f7bc25d2bb123c0eeec9557cda17481146d51672768907b2S3Bucket383ED51E": { + "AssetParameters28ffbbca5292e933d802ff7c495367b0d7fddab6f52a3777f67a52f14efc6b38S3BucketF4C94740": { "Type": "String", - "Description": "S3 bucket for asset \"fc7bfbf72c74b955f7bc25d2bb123c0eeec9557cda17481146d51672768907b2\"" + "Description": "S3 bucket for asset \"28ffbbca5292e933d802ff7c495367b0d7fddab6f52a3777f67a52f14efc6b38\"" }, - "AssetParametersfc7bfbf72c74b955f7bc25d2bb123c0eeec9557cda17481146d51672768907b2S3VersionKeyA520554C": { + "AssetParameters28ffbbca5292e933d802ff7c495367b0d7fddab6f52a3777f67a52f14efc6b38S3VersionKey584C9092": { "Type": "String", - "Description": "S3 key for asset version \"fc7bfbf72c74b955f7bc25d2bb123c0eeec9557cda17481146d51672768907b2\"" + "Description": "S3 key for asset version \"28ffbbca5292e933d802ff7c495367b0d7fddab6f52a3777f67a52f14efc6b38\"" }, - "AssetParametersfc7bfbf72c74b955f7bc25d2bb123c0eeec9557cda17481146d51672768907b2ArtifactHashB863A6ED": { + "AssetParameters28ffbbca5292e933d802ff7c495367b0d7fddab6f52a3777f67a52f14efc6b38ArtifactHashC0B5BADB": { "Type": "String", - "Description": "Artifact hash for asset \"fc7bfbf72c74b955f7bc25d2bb123c0eeec9557cda17481146d51672768907b2\"" + "Description": "Artifact hash for asset \"28ffbbca5292e933d802ff7c495367b0d7fddab6f52a3777f67a52f14efc6b38\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-custom-build/Dockerfile b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-custom-build/Dockerfile new file mode 100644 index 0000000000000..4204e9e4e3bd8 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-custom-build/Dockerfile @@ -0,0 +1,3 @@ +FROM public.ecr.aws/sam/build-python3.7 + +CMD [ "python" ] diff --git a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-custom-build/index.py b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-custom-build/index.py new file mode 100644 index 0000000000000..c033f37560534 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-custom-build/index.py @@ -0,0 +1,11 @@ +import requests +from PIL import Image + +def handler(event, context): + response = requests.get('https://a0.awsstatic.com/main/images/logos/aws_smile-header-desktop-en-white_59x35.png', stream=True) + img = Image.open(response.raw) + + print(response.status_code) + print(img.size) + + return response.status_code diff --git a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-custom-build/requirements.txt b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-custom-build/requirements.txt new file mode 100644 index 0000000000000..c636db83b8c9e --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-custom-build/requirements.txt @@ -0,0 +1,9 @@ +# Lock versions of pip packages +certifi==2020.6.20 +chardet==3.0.4 +idna==2.10 +urllib3==1.26.7 +# Requests used by this lambda +requests==2.26.0 +# Pillow 6.x so that python 2.7 and 3.x can both use this fixture +Pillow==8.4.0 diff --git a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-nodeps/index.py b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-nodeps/index.py index f118d0551afb8..d7b2ce8db00e9 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-nodeps/index.py +++ b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-nodeps/index.py @@ -1,2 +1,5 @@ +from http import HTTPStatus + def handler(event, context): print('No dependencies') + return HTTPStatus.OK.value diff --git a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-pipenv/.gitignore b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-pipenv/.gitignore new file mode 100644 index 0000000000000..c7f1fd9a72f12 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-pipenv/.gitignore @@ -0,0 +1,2 @@ +# Generated during tests. +requirements.txt diff --git a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-poetry/.gitignore b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-poetry/.gitignore new file mode 100644 index 0000000000000..c7f1fd9a72f12 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-poetry/.gitignore @@ -0,0 +1,2 @@ +# Generated during tests. +requirements.txt diff --git a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-sub/inner/custom_index.py b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-sub/inner/custom_index.py index e69de29bb2d1d..d7b2ce8db00e9 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-sub/inner/custom_index.py +++ b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-sub/inner/custom_index.py @@ -0,0 +1,5 @@ +from http import HTTPStatus + +def handler(event, context): + print('No dependencies') + return HTTPStatus.OK.value diff --git a/packages/@aws-cdk/aws-lambda-python/test/layer.test.ts b/packages/@aws-cdk/aws-lambda-python/test/layer.test.ts index 4f0199878a205..a254e82d48af6 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/layer.test.ts +++ b/packages/@aws-cdk/aws-lambda-python/test/layer.test.ts @@ -1,28 +1,28 @@ import * as path from 'path'; import { Runtime } from '@aws-cdk/aws-lambda'; -import { Stack } from '@aws-cdk/core'; -import { stageDependencies, bundle } from '../lib/bundling'; +import { DockerImage, Stack } from '@aws-cdk/core'; +import { Bundling } from '../lib/bundling'; import { PythonLayerVersion } from '../lib/layer'; jest.mock('../lib/bundling', () => { return { - bundle: jest.fn().mockReturnValue({ - bind: () => { - return { - s3Location: { - bucketName: 'bucket', - objectKey: 'key', - }, - }; - }, - bindToResource: () => { return; }, - }), + Bundling: { + bundle: jest.fn().mockReturnValue({ + bind: () => { + return { + s3Location: { + bucketName: 'bucket', + objectKey: 'key', + }, + }; + }, + bindToResource: () => { return; }, + }), + }, stageDependencies: jest.fn().mockReturnValue(true), }; }); -const hasDependenciesMock = (stageDependencies as jest.Mock); - let stack: Stack; beforeEach(() => { stack = new Stack(); @@ -30,14 +30,12 @@ beforeEach(() => { }); test('Bundling a layer from files', () => { - hasDependenciesMock.mockReturnValue(false); - const entry = path.join(__dirname, 'test/lambda-handler-project'); new PythonLayerVersion(stack, 'layer', { entry, }); - expect(bundle).toHaveBeenCalledWith(expect.objectContaining({ + expect(Bundling.bundle).toHaveBeenCalledWith(expect.objectContaining({ entry, outputPathSuffix: 'python', })); @@ -51,3 +49,17 @@ test('Fails when bundling a layer for a runtime not supported', () => { }); }).toThrow(/PYTHON.*support/); }); + +test('Allows use of custom bundling image', () => { + const entry = path.join(__dirname, 'lambda-handler-custom-build'); + const image = DockerImage.fromBuild(path.join(entry)); + + new PythonLayerVersion(stack, 'layer', { + entry, + bundling: { image }, + }); + + expect(Bundling.bundle).toHaveBeenCalledWith(expect.objectContaining({ + image, + })); +}); diff --git a/packages/@aws-cdk/aws-lambda-python/test/packaging.test.ts b/packages/@aws-cdk/aws-lambda-python/test/packaging.test.ts new file mode 100644 index 0000000000000..5cedd42b941bc --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/packaging.test.ts @@ -0,0 +1,34 @@ +import * as path from 'path'; +import { Packaging } from '../lib/packaging'; + +test('Packging with no dependencies', () => { + const entry = path.join(__dirname, 'lambda-handler-nodeps'); + const packaging = Packaging.fromEntry(entry); + + // pip packaging identified. + expect(packaging).toEqual(Packaging.NONE); +}); + +test('Packging with requirements.txt', () => { + const entry = path.join(__dirname, 'lambda-handler'); + const packaging = Packaging.fromEntry(entry); + + // pip packaging identified. + expect(packaging).toEqual(Packaging.PIP); +}); + +test('Packging with pipenv', () => { + const entry = path.join(__dirname, 'lambda-handler-pipenv'); + const packaging = Packaging.fromEntry(entry); + + // pip packaging identified. + expect(packaging).toEqual(Packaging.PIPENV); +}); + +test('Packging with poetry', () => { + const entry = path.join(__dirname, 'lambda-handler-poetry'); + const packaging = Packaging.fromEntry(entry); + + // pip packaging identified. + expect(packaging).toEqual(Packaging.POETRY); +});