forked from ChelseaKR/fare-policy-assistant
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuild_lambda_zip.py
More file actions
88 lines (72 loc) · 3.17 KB
/
Copy pathbuild_lambda_zip.py
File metadata and controls
88 lines (72 loc) · 3.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
"""Build a byte-reproducible AWS Lambda ZIP from a prepared bundle directory.
The deploy bundle contains platform wheels whose extracted mtimes vary between
installs. Ordinary ``zip`` archives preserve those mtimes (and filesystem
metadata), so rebuilding an unchanged Git revision can produce a different
Lambda ``CodeSha256`` and an unnecessary numbered release.
This builder writes only regular files, in lexical POSIX-path order, with a
fixed ZIP timestamp and mode. Python bytecode caches and wheel ``RECORD``
files remain excluded exactly as they were by the historical shell command.
Unused top-level dependency console scripts are omitted because installers
rewrite their shebangs with checkout-specific interpreter paths.
"""
from __future__ import annotations
import argparse
import stat
import zipfile
from pathlib import Path, PurePosixPath
ZIP_EPOCH = (1980, 1, 1, 0, 0, 0)
REGULAR_FILE_MODE = stat.S_IFREG | 0o644
def _is_excluded(relative: PurePosixPath) -> bool:
return (
(bool(relative.parts) and relative.parts[0] == "bin")
or ("__pycache__" in relative.parts)
or (
relative.name == "RECORD"
and len(relative.parts) >= 2
and relative.parts[-2].endswith(".dist-info")
)
)
def _bundle_files(source: Path) -> list[tuple[PurePosixPath, Path]]:
files: list[tuple[PurePosixPath, Path]] = []
for path in source.rglob("*"):
relative = PurePosixPath(path.relative_to(source).as_posix())
if _is_excluded(relative):
continue
if path.is_symlink():
raise ValueError(f"bundle contains unsupported symlink: {relative}")
if path.is_dir():
continue
if not path.is_file():
raise ValueError(f"bundle contains unsupported filesystem entry: {relative}")
files.append((relative, path))
return sorted(files, key=lambda item: item[0].as_posix())
def build_zip(source: Path, output: Path) -> None:
"""Write ``source`` to ``output`` with stable metadata and entry ordering."""
source = source.resolve(strict=True)
if not source.is_dir():
raise ValueError(f"bundle source is not a directory: {source}")
files = _bundle_files(source)
if not files:
raise ValueError(f"bundle source contains no files: {source}")
output.parent.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(
output,
mode="w",
compression=zipfile.ZIP_DEFLATED,
compresslevel=9,
strict_timestamps=True,
) as archive:
for relative, path in files:
info = zipfile.ZipInfo(relative.as_posix(), date_time=ZIP_EPOCH)
info.create_system = 3
info.compress_type = zipfile.ZIP_DEFLATED
info.external_attr = REGULAR_FILE_MODE << 16
archive.writestr(info, path.read_bytes(), compresslevel=9)
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("source", type=Path, help="prepared Lambda bundle directory")
parser.add_argument("output", type=Path, help="destination ZIP path")
args = parser.parse_args()
build_zip(args.source, args.output)
if __name__ == "__main__":
main()