Skip to content

Commit 02f238a

Browse files
authored
fix: avoid vendoring adot layer dependencies (#628)
1 parent fd39aff commit 02f238a

3 files changed

Lines changed: 124 additions & 173 deletions

File tree

.github/scripts/build_lambda_layer.py

Lines changed: 10 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,19 @@
1414
from pathlib import Path
1515

1616

17-
ARCHITECTURE_PLATFORMS = {
18-
"x86_64": "manylinux2014_x86_64",
19-
"arm64": "manylinux2014_aarch64",
20-
}
21-
SUPPORTED_PYTHON_VERSIONS = ("3.11", "3.12", "3.13", "3.14")
17+
UNIVERSAL_WHEEL_SUFFIX = "-py3-none-any.whl"
2218

2319

2420
@dataclass(frozen=True)
2521
class BuildConfig:
2622
output: Path
27-
target_python: str
28-
architecture: str
2923
sdk_distribution: Path
3024
otel_distribution: Path
3125
build_dir: Path | None = None
3226

3327

3428
def build_layer(config: BuildConfig) -> Path:
35-
"""Build a Lambda layer containing the SDK and OpenTelemetry plugin."""
29+
"""Build a universal Lambda layer containing the SDK and OTel plugin."""
3630

3731
_validate_config(config)
3832

@@ -44,42 +38,28 @@ def build_layer(config: BuildConfig) -> Path:
4438
layer_python_dir = work_dir / "python"
4539
layer_python_dir.mkdir(parents=True)
4640

47-
_install_layer_dependencies(config, layer_python_dir)
41+
_install_layer_distributions(config, layer_python_dir)
4842
_write_zip(config.output, work_dir)
4943

5044
return config.output
5145

5246

5347
def _validate_config(config: BuildConfig) -> None:
54-
if config.architecture not in ARCHITECTURE_PLATFORMS:
55-
supported = ", ".join(sorted(ARCHITECTURE_PLATFORMS))
56-
raise ValueError(
57-
f"Unsupported architecture: {config.architecture}. "
58-
f"Supported architectures: {supported}"
59-
)
60-
61-
if config.target_python not in SUPPORTED_PYTHON_VERSIONS:
62-
supported = ", ".join(SUPPORTED_PYTHON_VERSIONS)
63-
raise ValueError(
64-
f"Unsupported Python version: {config.target_python}. "
65-
f"Supported versions: {supported}"
66-
)
67-
6848
for distribution in (config.sdk_distribution, config.otel_distribution):
6949
if not distribution.is_file():
7050
raise FileNotFoundError(distribution)
51+
if not distribution.name.endswith(UNIVERSAL_WHEEL_SUFFIX):
52+
raise ValueError(
53+
f"Layer distributions must be universal wheels: {distribution}"
54+
)
7155

7256

7357
def _validate_output_location(output: Path, build_dir: Path) -> None:
7458
if output.resolve().is_relative_to(build_dir.resolve()):
7559
raise ValueError("Layer output must be outside the build directory")
7660

7761

78-
def _install_layer_dependencies(config: BuildConfig, target_dir: Path) -> None:
79-
python_version = config.target_python
80-
abi = f"cp{python_version.replace('.', '')}"
81-
platform = ARCHITECTURE_PLATFORMS[config.architecture]
82-
62+
def _install_layer_distributions(config: BuildConfig, target_dir: Path) -> None:
8363
command = [
8464
sys.executable,
8565
"-m",
@@ -88,17 +68,10 @@ def _install_layer_dependencies(config: BuildConfig, target_dir: Path) -> None:
8868
"--upgrade",
8969
"--target",
9070
str(target_dir),
91-
"--platform",
92-
platform,
93-
"--implementation",
94-
"cp",
95-
"--python-version",
96-
python_version,
97-
"--abi",
98-
abi,
9971
"--only-binary",
10072
":all:",
10173
"--no-compile",
74+
"--no-deps",
10275
str(config.sdk_distribution),
10376
str(config.otel_distribution),
10477
]
@@ -119,21 +92,9 @@ def _write_zip(output: Path, layer_root: Path) -> None:
11992
def main(argv: list[str] | None = None) -> int:
12093
parser = argparse.ArgumentParser(
12194
prog="build_lambda_layer.py",
122-
description="Build the AWS Durable Execution SDK OTel plugin Lambda layer.",
95+
description="Build the universal AWS Durable Execution SDK OTel plugin layer.",
12396
)
12497
parser.add_argument("--output", type=Path, required=True)
125-
parser.add_argument(
126-
"--target-python",
127-
choices=SUPPORTED_PYTHON_VERSIONS,
128-
required=True,
129-
help="Lambda Python minor version.",
130-
)
131-
parser.add_argument(
132-
"--architecture",
133-
choices=sorted(ARCHITECTURE_PLATFORMS),
134-
required=True,
135-
help="Lambda instruction set architecture.",
136-
)
13798
parser.add_argument("--sdk-distribution", type=Path, required=True)
13899
parser.add_argument("--otel-distribution", type=Path, required=True)
139100
parser.add_argument(
@@ -146,8 +107,6 @@ def main(argv: list[str] | None = None) -> int:
146107
output = build_layer(
147108
BuildConfig(
148109
output=args.output,
149-
target_python=args.target_python,
150-
architecture=args.architecture,
151110
sdk_distribution=args.sdk_distribution,
152111
otel_distribution=args.otel_distribution,
153112
build_dir=args.build_dir,

.github/scripts/tests/test_build_lambda_layer.py

Lines changed: 103 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,45 @@
1414
from build_lambda_layer import BuildConfig, build_layer
1515

1616

17-
def test_build_layer_installs_dependencies_and_zips_lambda_layout(
17+
def _write_test_wheel(
18+
directory: Path,
19+
distribution: str,
20+
package_files: tuple[str, ...],
21+
dependencies: tuple[str, ...] = (),
22+
) -> Path:
23+
normalized_distribution = distribution.replace("-", "_")
24+
wheel = directory / f"{normalized_distribution}-1.0.0-py3-none-any.whl"
25+
dist_info = f"{normalized_distribution}-1.0.0.dist-info"
26+
metadata = [
27+
"Metadata-Version: 2.1",
28+
f"Name: {distribution}",
29+
"Version: 1.0.0",
30+
*(f"Requires-Dist: {dependency}==1.0.0" for dependency in dependencies),
31+
"",
32+
]
33+
34+
with zipfile.ZipFile(wheel, "w") as archive:
35+
for package_file in package_files:
36+
archive.writestr(package_file, "")
37+
archive.writestr(f"{dist_info}/METADATA", "\n".join(metadata))
38+
archive.writestr(
39+
f"{dist_info}/WHEEL",
40+
"\n".join(
41+
(
42+
"Wheel-Version: 1.0",
43+
"Generator: test",
44+
"Root-Is-Purelib: true",
45+
"Tag: py3-none-any",
46+
"",
47+
)
48+
),
49+
)
50+
archive.writestr(f"{dist_info}/RECORD", "")
51+
52+
return wheel
53+
54+
55+
def test_build_layer_installs_distributions_and_zips_lambda_layout(
1856
monkeypatch: pytest.MonkeyPatch,
1957
tmp_path: Path,
2058
) -> None:
@@ -42,17 +80,17 @@ def fake_run(command: list[str], check: bool) -> subprocess.CompletedProcess[str
4280
output = build_layer(
4381
BuildConfig(
4482
output=tmp_path / "layer.zip",
45-
target_python="3.12",
46-
architecture="arm64",
4783
sdk_distribution=sdk_wheel,
4884
otel_distribution=otel_wheel,
4985
)
5086
)
5187

5288
assert output == tmp_path / "layer.zip"
53-
assert commands[0][commands[0].index("--platform") + 1] == "manylinux2014_aarch64"
54-
assert commands[0][commands[0].index("--abi") + 1] == "cp312"
5589
assert "--no-compile" in commands[0]
90+
assert "--no-deps" in commands[0]
91+
assert "--platform" not in commands[0]
92+
assert "--python-version" not in commands[0]
93+
assert "--abi" not in commands[0]
5694
assert str(sdk_wheel) in commands[0]
5795
assert str(otel_wheel) in commands[0]
5896

@@ -66,52 +104,84 @@ def fake_run(command: list[str], check: bool) -> subprocess.CompletedProcess[str
66104
)
67105

68106

69-
@pytest.mark.parametrize(
70-
("target_python", "architecture", "error"),
71-
[
72-
("3.10", "x86_64", "Unsupported Python version"),
73-
("3.12", "sparc", "Unsupported architecture"),
74-
],
75-
)
76-
def test_build_layer_rejects_unsupported_targets(
77-
target_python: str,
78-
architecture: str,
79-
error: str,
107+
def test_build_layer_excludes_adot_and_runtime_dependencies(
108+
monkeypatch: pytest.MonkeyPatch,
80109
tmp_path: Path,
81110
) -> None:
82-
sdk_wheel = tmp_path / "sdk.whl"
83-
otel_wheel = tmp_path / "otel.whl"
84-
sdk_wheel.write_text("sdk")
85-
otel_wheel.write_text("otel")
111+
sdk_wheel = _write_test_wheel(
112+
tmp_path,
113+
"aws-durable-execution-sdk-python",
114+
("aws_durable_execution_sdk_python/__init__.py",),
115+
("boto3",),
116+
)
117+
otel_wheel = _write_test_wheel(
118+
tmp_path,
119+
"aws-durable-execution-sdk-python-otel",
120+
("aws_durable_execution_sdk_python_otel/__init__.py",),
121+
("aws-opentelemetry-distro", "opentelemetry-api"),
122+
)
123+
_write_test_wheel(tmp_path, "boto3", ("boto3/__init__.py",))
124+
_write_test_wheel(
125+
tmp_path,
126+
"aws-opentelemetry-distro",
127+
("amazon/opentelemetry/distro/__init__.py",),
128+
)
129+
_write_test_wheel(
130+
tmp_path,
131+
"opentelemetry-api",
132+
("opentelemetry/__init__.py",),
133+
)
134+
monkeypatch.setenv("PIP_FIND_LINKS", str(tmp_path))
135+
monkeypatch.setenv("PIP_NO_INDEX", "1")
86136

87-
with pytest.raises(ValueError, match=error):
88-
build_layer(
89-
BuildConfig(
90-
output=tmp_path / "layer.zip",
91-
target_python=target_python,
92-
architecture=architecture,
93-
sdk_distribution=sdk_wheel,
94-
otel_distribution=otel_wheel,
95-
)
137+
output = build_layer(
138+
BuildConfig(
139+
output=tmp_path / "layer.zip",
140+
sdk_distribution=sdk_wheel,
141+
otel_distribution=otel_wheel,
96142
)
143+
)
144+
145+
with zipfile.ZipFile(output) as archive:
146+
names = archive.namelist()
147+
148+
assert "python/aws_durable_execution_sdk_python/__init__.py" in names
149+
assert "python/aws_durable_execution_sdk_python_otel/__init__.py" in names
150+
assert not any(name.startswith("python/amazon/") for name in names)
151+
assert not any(name.startswith("python/boto3/") for name in names)
152+
assert not any(name.startswith("python/opentelemetry/") for name in names)
97153

98154

99155
def test_build_layer_requires_built_distributions(tmp_path: Path) -> None:
100156
with pytest.raises(FileNotFoundError):
101157
build_layer(
102158
BuildConfig(
103159
output=tmp_path / "layer.zip",
104-
target_python="3.13",
105-
architecture="x86_64",
106160
sdk_distribution=tmp_path / "missing-sdk.whl",
107161
otel_distribution=tmp_path / "missing-otel.whl",
108162
)
109163
)
110164

111165

166+
def test_build_layer_requires_universal_wheels(tmp_path: Path) -> None:
167+
sdk_wheel = tmp_path / "sdk-1.0.0-cp311-cp311-manylinux2014_x86_64.whl"
168+
otel_wheel = tmp_path / "otel-1.0.0-py3-none-any.whl"
169+
sdk_wheel.write_text("sdk")
170+
otel_wheel.write_text("otel")
171+
172+
with pytest.raises(ValueError, match="must be universal wheels"):
173+
build_layer(
174+
BuildConfig(
175+
output=tmp_path / "layer.zip",
176+
sdk_distribution=sdk_wheel,
177+
otel_distribution=otel_wheel,
178+
)
179+
)
180+
181+
112182
def test_build_layer_rejects_output_inside_build_directory(tmp_path: Path) -> None:
113-
sdk_wheel = tmp_path / "sdk.whl"
114-
otel_wheel = tmp_path / "otel.whl"
183+
sdk_wheel = tmp_path / "sdk-1.0.0-py3-none-any.whl"
184+
otel_wheel = tmp_path / "otel-1.0.0-py3-none-any.whl"
115185
sdk_wheel.write_text("sdk")
116186
otel_wheel.write_text("otel")
117187
build_dir = tmp_path / "layer"
@@ -120,8 +190,6 @@ def test_build_layer_rejects_output_inside_build_directory(tmp_path: Path) -> No
120190
build_layer(
121191
BuildConfig(
122192
output=build_dir / "layer.zip",
123-
target_python="3.13",
124-
architecture="x86_64",
125193
sdk_distribution=sdk_wheel,
126194
otel_distribution=otel_wheel,
127195
build_dir=build_dir,

0 commit comments

Comments
 (0)