Harden runtime distribution acceptance
This commit is contained in:
@@ -6,11 +6,13 @@ from __future__ import annotations
|
||||
import argparse
|
||||
from hashlib import sha256
|
||||
from pathlib import Path
|
||||
import zipapp
|
||||
from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
DEFAULT_OUTPUT = ROOT.parent.parent / "runtime" / "deployment" / "govoplan-deploy.pyz"
|
||||
ZIP_TIMESTAMP = (1980, 1, 1, 0, 0, 0)
|
||||
PYTHON_FILE_MODE = 0o100644
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
@@ -25,13 +27,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
temporary = output.with_name(f".{output.name}.tmp")
|
||||
if temporary.exists():
|
||||
temporary.unlink()
|
||||
zipapp.create_archive(
|
||||
ROOT,
|
||||
target=temporary,
|
||||
interpreter="/usr/bin/env python3",
|
||||
compressed=True,
|
||||
filter=_include_source,
|
||||
)
|
||||
_write_reproducible_zipapp(temporary)
|
||||
temporary.chmod(0o755)
|
||||
temporary.replace(output)
|
||||
digest = sha256(output.read_bytes()).hexdigest()
|
||||
@@ -39,6 +35,42 @@ def main(argv: list[str] | None = None) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def _write_reproducible_zipapp(target: Path) -> None:
|
||||
sources = tuple(
|
||||
path
|
||||
for path in sorted(ROOT.rglob("*"), key=lambda item: item.as_posix())
|
||||
if path.is_file() and _include_source(path.relative_to(ROOT))
|
||||
)
|
||||
if not any(path.relative_to(ROOT).as_posix() == "__main__.py" for path in sources):
|
||||
raise ValueError("deployment source has no __main__.py")
|
||||
for path in sources:
|
||||
if path.is_symlink():
|
||||
raise ValueError(f"deployment source must not contain symlinks: {path}")
|
||||
|
||||
with target.open("wb") as handle:
|
||||
handle.write(b"#!/usr/bin/env python3\n")
|
||||
with ZipFile(
|
||||
handle,
|
||||
mode="w",
|
||||
compression=ZIP_DEFLATED,
|
||||
compresslevel=9,
|
||||
strict_timestamps=True,
|
||||
) as archive:
|
||||
for source in sources:
|
||||
relative = source.relative_to(ROOT).as_posix()
|
||||
info = ZipInfo(relative, date_time=ZIP_TIMESTAMP)
|
||||
info.compress_type = ZIP_DEFLATED
|
||||
info.create_system = 3
|
||||
info.external_attr = PYTHON_FILE_MODE << 16
|
||||
info.flag_bits |= 0x800
|
||||
archive.writestr(
|
||||
info,
|
||||
source.read_bytes(),
|
||||
compress_type=ZIP_DEFLATED,
|
||||
compresslevel=9,
|
||||
)
|
||||
|
||||
|
||||
def _include_source(path: Path) -> bool:
|
||||
return (
|
||||
"__pycache__" not in path.parts
|
||||
|
||||
Reference in New Issue
Block a user