feat: consolidate legacy workflows into gyxx-flow
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
"""Read-only and transient preflight diagnostics for operators."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import sys
|
||||
import tempfile
|
||||
from dataclasses import asdict, dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from gyxx_flow.catalog import WorkflowCatalog
|
||||
from gyxx_flow.core.config import Settings
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class DiagnosticCheck:
|
||||
name: str
|
||||
passed: bool
|
||||
message: str
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class DoctorReport:
|
||||
checks: tuple[DiagnosticCheck, ...]
|
||||
|
||||
@property
|
||||
def is_healthy(self) -> bool:
|
||||
return all(check.passed for check in self.checks)
|
||||
|
||||
def as_dict(self) -> dict[str, object]:
|
||||
return {
|
||||
"is_healthy": self.is_healthy,
|
||||
"checks": [asdict(check) for check in self.checks],
|
||||
}
|
||||
|
||||
|
||||
def run_doctor(settings: Settings) -> DoctorReport:
|
||||
checks = [
|
||||
DiagnosticCheck(
|
||||
"python",
|
||||
sys.version_info >= (3, 12),
|
||||
f"Python {sys.version_info.major}.{sys.version_info.minor}",
|
||||
),
|
||||
DiagnosticCheck(
|
||||
"cli",
|
||||
importlib.util.find_spec("gyxx_flow.__main__") is not None,
|
||||
"gyxx_flow module entry point is importable",
|
||||
),
|
||||
DiagnosticCheck(
|
||||
"project_root",
|
||||
settings.project_root.is_dir(),
|
||||
"project root exists" if settings.project_root.is_dir() else "project root is missing",
|
||||
),
|
||||
]
|
||||
try:
|
||||
catalog = WorkflowCatalog.load(settings.project_root / "config")
|
||||
catalog_ok = len(catalog.scheduled_workflows()) == 21
|
||||
catalog_message = f"catalog has {len(catalog.scheduled_workflows())} scheduled workflows"
|
||||
except Exception:
|
||||
catalog_ok = False
|
||||
catalog_message = "catalog cannot be validated"
|
||||
checks.append(DiagnosticCheck("catalog", catalog_ok, catalog_message))
|
||||
|
||||
data_root_ok = settings.data_root.is_absolute() and settings.data_root != Path(
|
||||
settings.data_root.anchor
|
||||
)
|
||||
checks.append(
|
||||
DiagnosticCheck(
|
||||
"data_root",
|
||||
data_root_ok,
|
||||
"data root is an explicit non-root path" if data_root_ok else "unsafe data root",
|
||||
)
|
||||
)
|
||||
writable, message = _probe_write_permission(settings.data_root)
|
||||
checks.append(DiagnosticCheck("data_root_write", writable, message))
|
||||
return DoctorReport(tuple(checks))
|
||||
|
||||
|
||||
def _probe_write_permission(path: Path) -> tuple[bool, str]:
|
||||
candidate = Path(path)
|
||||
while not candidate.exists() and candidate.parent != candidate:
|
||||
candidate = candidate.parent
|
||||
if not candidate.is_dir():
|
||||
return False, "no existing data-root parent directory"
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode="wb", prefix=".gyxx-doctor-probe-", dir=candidate, delete=True
|
||||
) as stream:
|
||||
stream.write(b"probe")
|
||||
stream.flush()
|
||||
except OSError:
|
||||
return False, "data-root parent is not writable"
|
||||
return True, "data-root parent write probe passed"
|
||||
|
||||
Reference in New Issue
Block a user