Skip to content

Commit 564b480

Browse files
committed
chore: merge main into opentelemetry workflow update
2 parents 489dfd0 + d43377c commit 564b480

75 files changed

Lines changed: 5506 additions & 1678 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.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,
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#!/usr/bin/env python3
2+
from __future__ import annotations
3+
4+
import argparse
5+
import email
6+
import zipfile
7+
from pathlib import Path
8+
9+
from packaging.requirements import Requirement
10+
from packaging.utils import canonicalize_name
11+
12+
13+
EXPECTED_STANDALONE_DEPENDENCIES = {
14+
"opentelemetry-api",
15+
"opentelemetry-propagator-aws-xray",
16+
"opentelemetry-sdk",
17+
}
18+
19+
20+
def check_otel_wheel_dependencies(wheel: Path) -> None:
21+
"""Validate that OpenTelemetry dependencies are layer-provided by default."""
22+
requirements = _read_requirements(wheel)
23+
base_otel_dependencies = {
24+
canonicalize_name(requirement.name)
25+
for requirement in requirements
26+
if canonicalize_name(requirement.name).startswith("opentelemetry-")
27+
and (requirement.marker is None or requirement.marker.evaluate({"extra": ""}))
28+
}
29+
if base_otel_dependencies:
30+
names = ", ".join(sorted(base_otel_dependencies))
31+
raise ValueError(
32+
f"{wheel.name} installs OpenTelemetry dependencies by default: {names}"
33+
)
34+
35+
standalone_dependencies = {
36+
canonicalize_name(requirement.name)
37+
for requirement in requirements
38+
if canonicalize_name(requirement.name).startswith("opentelemetry-")
39+
and requirement.marker is not None
40+
and requirement.marker.evaluate({"extra": "standalone"})
41+
}
42+
if standalone_dependencies != EXPECTED_STANDALONE_DEPENDENCIES:
43+
expected = ", ".join(sorted(EXPECTED_STANDALONE_DEPENDENCIES))
44+
actual = ", ".join(sorted(standalone_dependencies)) or "none"
45+
raise ValueError(
46+
f"{wheel.name} standalone OpenTelemetry dependencies must be "
47+
f"{expected}; found {actual}"
48+
)
49+
50+
51+
def _read_requirements(wheel: Path) -> list[Requirement]:
52+
if not wheel.is_file():
53+
raise FileNotFoundError(wheel)
54+
55+
with zipfile.ZipFile(wheel) as archive:
56+
metadata_files = [
57+
name for name in archive.namelist() if name.endswith(".dist-info/METADATA")
58+
]
59+
if len(metadata_files) != 1:
60+
raise ValueError(
61+
f"{wheel.name} must contain exactly one dist-info/METADATA file"
62+
)
63+
metadata = email.message_from_bytes(archive.read(metadata_files[0]))
64+
65+
return [
66+
Requirement(value) for value in metadata.get_all("Requires-Dist", failobj=[])
67+
]
68+
69+
70+
def main(argv: list[str] | None = None) -> int:
71+
parser = argparse.ArgumentParser(
72+
description="Validate the OTel plugin wheel dependency contract."
73+
)
74+
parser.add_argument("wheel", type=Path)
75+
args = parser.parse_args(argv)
76+
77+
check_otel_wheel_dependencies(args.wheel)
78+
print(args.wheel)
79+
return 0
80+
81+
82+
if __name__ == "__main__":
83+
raise SystemExit(main())

0 commit comments

Comments
 (0)