Files
aipackage/autoinstall/offline/validate-python-runtime-profile.py

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())