更新: 421 个文件 - 2026-03-17 18:30:02

这个提交包含在:
hao
2026-03-17 18:30:02 -07:00
父节点 29c3faaa28
当前提交 a3edc88834
修改 421 个文件,包含 12474 行新增5845 行删除

131
scripts/lab/doctor.py 普通文件
查看文件

@@ -0,0 +1,131 @@
from __future__ import annotations
import socket
from contextlib import closing
from typing import Any, Dict, Iterable, List, Tuple
from lab.utils import command_available, run
def _result(name: str, ok: bool, detail: str, **extra: Any) -> Dict[str, Any]:
payload = {"name": name, "ok": ok, "detail": detail}
payload.update(extra)
return payload
def _parse_host_ports(profiles: Iterable[Dict[str, Any]]) -> List[Dict[str, Any]]:
ports: List[Dict[str, Any]] = []
for profile in profiles:
for service_name, service in (profile.get("services") or {}).items():
for binding in service.get("ports", []) or []:
host_port = None
value = str(binding)
parts = value.split(":")
if len(parts) == 3 and parts[1].isdigit():
host_port = int(parts[1])
elif len(parts) >= 2 and parts[0].isdigit():
host_port = int(parts[0])
elif value.isdigit():
host_port = int(value)
if host_port is None:
continue
ports.append(
{
"profile_id": profile.get("profile_id"),
"service": service_name,
"binding": value,
"port": host_port,
}
)
return ports
def _port_available(port: int) -> bool:
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
sock.bind(("127.0.0.1", port))
except OSError:
return False
return True
def _check_docker_cli() -> Dict[str, Any]:
ok = command_available("docker")
return _result("docker-cli", ok, "docker CLI available" if ok else "docker CLI is not installed")
def _check_docker_daemon() -> Dict[str, Any]:
if not command_available("docker"):
return _result("docker-daemon", False, "docker CLI unavailable")
context = run(["docker", "context", "show"], check=False)
info = run(["docker", "info"], check=False)
detail = f"context={context.stdout.strip() or 'unknown'}"
if info.returncode != 0:
detail = info.stderr.strip() or info.stdout.strip() or "docker daemon unavailable"
return _result("docker-daemon", False, detail)
return _result("docker-daemon", True, detail or "docker daemon reachable")
def _check_playwright_import() -> Dict[str, Any]:
try:
from playwright.sync_api import sync_playwright # noqa: F401
except Exception as exc:
return _result("playwright-import", False, f"playwright import failed: {exc}")
return _result("playwright-import", True, "playwright Python package import passed")
def _check_chromium_launch() -> Dict[str, Any]:
try:
from playwright.sync_api import sync_playwright
except Exception as exc:
return _result("playwright-browser", False, f"playwright import failed: {exc}")
try:
with sync_playwright() as playwright:
browser = playwright.chromium.launch(headless=True)
page = browser.new_page()
page.set_content("<html><body>ok</body></html>")
browser.close()
except Exception as exc:
return _result("playwright-browser", False, f"chromium launch failed: {exc}")
return _result("playwright-browser", True, "chromium runtime launch passed")
def _check_ports(profiles: Iterable[Dict[str, Any]]) -> Dict[str, Any]:
requested = _parse_host_ports(profiles)
if not requested:
return _result("ports", True, "no host ports declared")
conflicts: List[Dict[str, Any]] = []
for item in requested:
if not _port_available(item["port"]):
conflicts.append(item)
if conflicts:
detail = ", ".join(
f"{item['port']}({item['profile_id']}::{item['service']})" for item in conflicts
)
return _result("ports", False, f"host ports already in use: {detail}", conflicts=conflicts)
return _result("ports", True, f"checked {len(requested)} host port bindings", bindings=requested)
def run_checks(profiles: Iterable[Dict[str, Any]] | None = None) -> Dict[str, Any]:
selected = list(profiles or [])
checks = [
_check_docker_cli(),
_check_docker_daemon(),
_check_playwright_import(),
_check_chromium_launch(),
_check_ports(selected),
]
ok = all(item["ok"] for item in checks)
failures = [item for item in checks if not item["ok"]]
return {
"status": "passed" if ok else "failed",
"ok": ok,
"checks": checks,
"profile_ids": [item.get("profile_id") for item in selected if item.get("profile_id")],
"failure_count": len(failures),
"summary": "; ".join(item["detail"] for item in failures) if failures else "all checks passed",
}