Skip to content

Commit c3c4a97

Browse files
authored
feat(lambda-python): support for providing a custom bundling docker image (#18082)
This refactors the bundling process to match the NodeJs and Go Lambda functions and allows providing a custom bundling docker image. Changes: - refactor bundling to use `cdk.BundlingOptions` - Use updated `Bundling` class - Update tests to use updated `Bundling` class Fixes #10298, #12949, #15391, #16234, #15306 BREAKING CHANGE: `assetHashType` and `assetHash` properties moved to new `bundling` property. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 7d0680a commit c3c4a97

34 files changed

+1020
-476
lines changed

packages/@aws-cdk/aws-lambda-python/README.md

+103-24
Original file line numberDiff line numberDiff line change
@@ -27,29 +27,61 @@ Define a `PythonFunction`:
2727
```ts
2828
new lambda.PythonFunction(this, 'MyFunction', {
2929
entry: '/path/to/my/function', // required
30-
runtime: Runtime.PYTHON_3_6, // required
30+
runtime: Runtime.PYTHON_3_8, // required
3131
index: 'my_index.py', // optional, defaults to 'index.py'
3232
handler: 'my_exported_func', // optional, defaults to 'handler'
3333
});
3434
```
3535

3636
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).
3737

38-
## Module Dependencies
38+
## Python Layer
3939

40-
If `requirements.txt` or `Pipfile` exists at the entry path, the construct will handle installing
41-
all required modules in a [Lambda compatible Docker container](https://gallery.ecr.aws/sam/build-python3.7)
42-
according to the `runtime` and with the Docker platform based on the target architecture of the Lambda function.
40+
You may create a python-based lambda layer with `PythonLayerVersion`. If `PythonLayerVersion` detects a `requirements.txt`
41+
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
42+
layer.
43+
44+
Define a `PythonLayerVersion`:
45+
46+
```ts
47+
new lambda.PythonLayerVersion(this, 'MyLayer', {
48+
entry: '/path/to/my/layer', // point this to your library's directory
49+
})
50+
```
51+
52+
A layer can also be used as a part of a `PythonFunction`:
53+
54+
```ts
55+
new lambda.PythonFunction(this, 'MyFunction', {
56+
entry: '/path/to/my/function',
57+
runtime: Runtime.PYTHON_3_8,
58+
layers: [
59+
new lambda.PythonLayerVersion(this, 'MyLayer', {
60+
entry: '/path/to/my/layer', // point this to your library's directory
61+
}),
62+
],
63+
});
64+
```
65+
66+
## Packaging
67+
68+
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.
4369

4470
Python bundles are only recreated and published when a file in a source directory has changed.
4571
Therefore (and as a general best-practice), it is highly recommended to commit a lockfile with a
46-
list of all transitive dependencies and their exact versions.
47-
This will ensure that when any dependency version is updated, the bundle asset is recreated and uploaded.
72+
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.
73+
74+
To that end, we recommend using [`pipenv`] or [`poetry`] which have lockfile support.
75+
76+
- [`pipenv`](https://pipenv-fork.readthedocs.io/en/latest/basics.html#example-pipfile-lock)
77+
- [`poetry`](https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control)
4878

49-
To that end, we recommend using [`pipenv`] or [`poetry`] which has lockfile support.
79+
Packaging is executed using the `Packaging` class, which:
5080

51-
[`pipenv`]: https://pipenv-fork.readthedocs.io/en/latest/basics.html#example-pipfile-lock
52-
[`poetry`]: https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
81+
1. Infers the packaging type based on the files present.
82+
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).
83+
3. Installs dependencies using `pip`.
84+
4. Copies the dependencies into an asset that is bundled for the Lambda package.
5385

5486
**Lambda with a requirements.txt**
5587

@@ -73,24 +105,71 @@ To that end, we recommend using [`pipenv`] or [`poetry`] which has lockfile supp
73105
```plaintext
74106
.
75107
├── lambda_function.py # exports a function named 'handler'
76-
├── pyproject.toml # has to be present at the entry path
77-
├── poetry.lock # your poetry lock file
108+
├── pyproject.toml # your poetry project definition
109+
├── poetry.lock # your poetry lock file has to be present at the entry path
78110
```
79111

80-
**Lambda Layer Support**
112+
## Custom Bundling
81113

82-
You may create a python-based lambda layer with `PythonLayerVersion`. If `PythonLayerVersion` detects a `requirements.txt`
83-
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
84-
layer.
114+
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:
115+
116+
- `PIP_INDEX_URL`
117+
- `PIP_EXTRA_INDEX_URL`
118+
- `HTTPS_PROXY`
119+
120+
Additional build args for bundling that refer to PyPI indexes can be specified as:
85121

86122
```ts
87-
new lambda.PythonFunction(this, 'MyFunction', {
88-
entry: '/path/to/my/function',
89-
runtime: Runtime.PYTHON_3_6,
90-
layers: [
91-
new lambda.PythonLayerVersion(this, 'MyLayer', {
92-
entry: '/path/to/my/layer', // point this to your library's directory
93-
}),
94-
],
123+
const entry = '/path/to/function';
124+
const image = DockerImage.fromBuild(entry);
125+
126+
new lambda.PythonFunction(this, 'function', {
127+
entry,
128+
runtime: Runtime.PYTHON_3_8,
129+
bundling: {
130+
buildArgs: { PIP_INDEX_URL: "https://your.index.url/simple/", PIP_EXTRA_INDEX_URL: "https://your.extra-index.url/simple/" },
131+
},
95132
});
96133
```
134+
135+
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:
136+
137+
```ts
138+
const entry = '/path/to/function';
139+
const image = DockerImage.fromBuild(entry);
140+
141+
new lambda.PythonFunction(this, 'function', {
142+
entry,
143+
runtime: Runtime.PYTHON_3_8,
144+
bundling: { image },
145+
});
146+
```
147+
148+
## Custom Bundling with Code Artifact
149+
150+
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):
151+
152+
```ts
153+
import { execSync } from 'child_process';
154+
155+
const entry = '/path/to/function';
156+
const image = DockerImage.fromBuild(entry);
157+
158+
const domain = 'my-domain';
159+
const domainOwner = '111122223333';
160+
const repoName = 'my_repo';
161+
const region = 'us-east-1';
162+
const codeArtifactAuthToken = execSync(`aws codeartifact get-authorization-token --domain ${domain} --domain-owner ${domainOwner} --query authorizationToken --output text`).toString().trim();
163+
164+
const indexUrl = `https://aws:${codeArtifactAuthToken}@${domain}-${domainOwner}.d.codeartifact.${region}.amazonaws.com/pypi/${repoName}/simple/`;
165+
166+
new lambda.PythonFunction(this, 'function', {
167+
entry,
168+
runtime: Runtime.PYTHON_3_8,
169+
bundling: {
170+
buildArgs: { PIP_INDEX_URL: indexUrl },
171+
},
172+
});
173+
```
174+
175+
This type of an example should work for `pip` and `poetry` based dependencies, but will not work for `pipenv`.

packages/@aws-cdk/aws-lambda-python/lib/Dockerfile

+7-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
ARG IMAGE=public.ecr.aws/sam/build-python3.7
44
FROM $IMAGE
55

6-
# Ensure rsync is installed
7-
RUN yum -q list installed rsync &>/dev/null || yum install -y rsync
6+
ARG PIP_INDEX_URL
7+
ARG PIP_EXTRA_INDEX_URL
8+
ARG HTTPS_PROXY
9+
10+
# Upgrade pip (required by cryptography v3.4 and above, which is a dependency of poetry)
11+
RUN pip install --upgrade pip
12+
RUN pip install pipenv poetry
813

914
CMD [ "python" ]

packages/@aws-cdk/aws-lambda-python/lib/Dockerfile.dependencies

-22
This file was deleted.

0 commit comments

Comments
 (0)