82669b22d5
test / integration-docker (push) Successful in 20s
prd-number / assign-numbers (push) Failing after 24s
test / unit (push) Successful in 57s
lint / lint (push) Successful in 1m1s
Update Quality Badges / update-badges (push) Failing after 54s
test / integration-firecracker (push) Successful in 5m7s
test / coverage (push) Successful in 27s
test / publish-infra (push) Successful in 2m34s
Addresses the third review round on PR #481. - `bot-bottle doctor` now checks `is_backend_ready()` (a full backend status() probe: daemon reachable, network pool present, KVM usable) instead of the cheap PATH-only `is_backend_available()`. A host with a stopped Docker daemon or half-configured Firecracker no longer reports `ok: backend` / exit 0 when `start` can't actually work; each not-ready backend prints its own diagnostics, and doctor passes only if at least one backend is ready. - `install.sh` resolves the pip `--user` scripts directory from the interpreter (`sysconfig.get_path("scripts", get_preferred_scheme("user"))`) instead of hardcoding `~/.local/bin`, which is wrong on a python.org macOS interpreter (`~/Library/Python/<X.Y>/bin`). The PATH guidance now prints the actual directory. Tests: doctor tests mock `is_backend_ready` (the readiness contract) and cover the not-ready → fail path; a new install-script test drives the macOS `osx_framework_user` scheme and asserts it resolves a non-~/.local/bin directory. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
89 lines
2.8 KiB
Python
89 lines
2.8 KiB
Python
"""`doctor` CLI command — validate host prerequisites for running
|
|
bot-bottle and report what's ready.
|
|
|
|
Fails (non-zero exit) only on the two hard requirements: a new-enough
|
|
Python and at least one backend that is *ready* (passes its full status
|
|
checks, so `start` can actually work). The config directory is a soft
|
|
check — `install.sh` creates it, but a missing one only warrants a note,
|
|
not a failure, since `start` provisions what it needs on first run.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from ...backend import is_backend_ready, known_backend_names
|
|
from ..constants import PROG
|
|
|
|
MIN_PYTHON = (3, 11)
|
|
CONFIG_DIR = ".bot-bottle"
|
|
|
|
|
|
def _ok(label: str, detail: str) -> None:
|
|
print(f"ok: {label}: {detail}")
|
|
|
|
|
|
def _warn(label: str, detail: str) -> None:
|
|
print(f"warn: {label}: {detail}")
|
|
|
|
|
|
def _fail(label: str, detail: str) -> None:
|
|
print(f"fail: {label}: {detail}")
|
|
|
|
|
|
def _check_python() -> bool:
|
|
v = sys.version_info
|
|
detail = f"{v.major}.{v.minor}.{v.micro}"
|
|
if (v.major, v.minor) >= MIN_PYTHON:
|
|
_ok("python", detail)
|
|
return True
|
|
_fail("python", f"{detail}; need {MIN_PYTHON[0]}.{MIN_PYTHON[1]} or newer")
|
|
return False
|
|
|
|
|
|
def _check_backends() -> bool:
|
|
"""At least one backend must be *ready* to run a bottle — i.e. pass its
|
|
full status() checks (daemon reachable, network pool present, KVM usable),
|
|
not merely have a binary on PATH. A binary-only check would report `ok`
|
|
on a host with a stopped Docker daemon or a half-configured Firecracker,
|
|
where `start` still can't work. Each not-ready backend prints its own
|
|
diagnostics (quiet=False) so the operator sees exactly what's missing."""
|
|
ready = []
|
|
for name in known_backend_names():
|
|
if is_backend_ready(name, quiet=False):
|
|
_ok("backend", f"{name}: ready")
|
|
ready.append(name)
|
|
else:
|
|
_warn("backend", f"{name}: not ready (see diagnostics above)")
|
|
if ready:
|
|
return True
|
|
_fail(
|
|
"backend",
|
|
"no backend is ready to run a bottle; start Docker, or finish "
|
|
"Apple Container (macOS) / Firecracker (Linux) setup",
|
|
)
|
|
return False
|
|
|
|
|
|
def _check_config_dir() -> None:
|
|
config = Path.home() / CONFIG_DIR
|
|
if config.is_dir():
|
|
_ok("config", str(config))
|
|
else:
|
|
_warn("config", f"{config} does not exist yet (created on first use)")
|
|
|
|
|
|
def cmd_doctor(argv: list[str]) -> int:
|
|
parser = argparse.ArgumentParser(
|
|
prog=f"{PROG} doctor",
|
|
description="Check host prerequisites for running bot-bottle.",
|
|
)
|
|
parser.parse_args(argv)
|
|
|
|
# Hard requirements gate the exit code; the config note is advisory.
|
|
required = [_check_python(), _check_backends()]
|
|
_check_config_dir()
|
|
return 0 if all(required) else 1
|