61 lines
2.0 KiB
Python
61 lines
2.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Fail closed unless a selected Python wheel directory obeys its runtime profile."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
|
|
|
|
def sha256(path: Path) -> str:
|
|
digest = hashlib.sha256()
|
|
with path.open("rb") as stream:
|
|
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
|
digest.update(chunk)
|
|
return digest.hexdigest()
|
|
|
|
|
|
def validate(contract: dict, profile_name: str, wheel_dir: Path) -> list[str]:
|
|
errors: list[str] = []
|
|
profiles = contract.get("profiles", {})
|
|
if profile_name not in profiles:
|
|
return [f"unknown profile: {profile_name}"]
|
|
profile = profiles[profile_name]
|
|
wheels = {path.name: path for path in wheel_dir.glob("*.whl") if path.is_file()}
|
|
if not wheels:
|
|
errors.append("selected wheel directory is empty")
|
|
for pattern in profile.get("forbidden_filename_patterns", []):
|
|
matches = sorted(name for name in wheels if re.search(pattern, name, re.IGNORECASE))
|
|
if matches:
|
|
errors.append(f"forbidden pattern {pattern!r}: {', '.join(matches)}")
|
|
for name, expected in profile.get("required", {}).items():
|
|
path = wheels.get(name)
|
|
if path is None:
|
|
errors.append(f"missing required wheel: {name}")
|
|
elif sha256(path) != expected:
|
|
errors.append(f"digest mismatch: {name}")
|
|
return errors
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("contract", type=Path)
|
|
parser.add_argument("profile")
|
|
parser.add_argument("wheel_dir", type=Path)
|
|
args = parser.parse_args()
|
|
contract = json.loads(args.contract.read_text(encoding="utf-8"))
|
|
errors = validate(contract, args.profile, args.wheel_dir)
|
|
if errors:
|
|
for error in errors:
|
|
print(f"ERROR: {error}")
|
|
return 1
|
|
print(f"profile valid: {args.profile}; wheels={len(list(args.wheel_dir.glob('*.whl')))}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|