zcbot/core/sandbox/package_scans.py

58 lines
2.9 KiB
Python

"""Sandbox 临时 Python 包扫描与 best-effort 持久化。"""
from __future__ import annotations
import json
import subprocess
from datetime import datetime, timezone
from typing import Optional
from uuid import UUID
def scan_container(container: str) -> Optional[dict]:
try:
scan = subprocess.run(["docker", "exec", "--user", "zcbot", "--workdir", "/sandbox", container,
"python", "-I", "-S", "/sandbox/package_scan.py"], capture_output=True,
text=True, timeout=30)
if scan.returncode != 0:
raise RuntimeError(scan.stderr.strip())
packages = json.loads(scan.stdout)
if not isinstance(packages, list):
raise ValueError("scanner output is not a package list")
inspect = subprocess.run(["docker", "inspect", "--format={{.Image}}", container],
capture_output=True, text=True, timeout=15)
py = subprocess.run(["docker", "exec", "--workdir", "/sandbox", container, "python", "-I", "-S", "-c",
"import sys;print('.'.join(map(str,sys.version_info[:3])))"],
capture_output=True, text=True, timeout=15)
if inspect.returncode != 0 or py.returncode != 0:
raise RuntimeError("failed to inspect image or Python version")
return {"packages": packages, "image_digest": inspect.stdout.strip(),
"python_version": py.stdout.strip(),
"total_installed_bytes": sum(int(x.get("installed_bytes") or 0) for x in packages)}
except Exception as exc:
print(f"[sandbox-package-scan] failed container={container}: {type(exc).__name__}: {exc}")
return None
def persist_scan(*, container_session_id: str, user_id: UUID, execution_kind: str,
result: Optional[dict]) -> None:
if not result or not result.get("packages"):
return
try:
from core.storage import session_scope
from core.storage.models import SandboxPackageScan
with session_scope() as s:
s.merge(SandboxPackageScan(
container_session_id=container_session_id, user_id=user_id,
execution_kind=execution_kind, image_digest=result["image_digest"],
python_version=result["python_version"], packages=result["packages"],
package_count=len(result["packages"]),
total_installed_bytes=result["total_installed_bytes"],
finished_at=datetime.now(timezone.utc),
))
except Exception as exc:
print(f"[sandbox-package-scan] persist failed session={container_session_id}: {type(exc).__name__}: {exc}")
def scan_and_persist(container: str, user_id: UUID, kind: str, session_id: str) -> None:
persist_scan(container_session_id=session_id, user_id=user_id,
execution_kind=kind, result=scan_container(container))