95 行
3.5 KiB
Python
95 行
3.5 KiB
Python
from __future__ import annotations
|
|
|
|
import time
|
|
from pathlib import Path
|
|
from typing import Any, Dict
|
|
|
|
import requests
|
|
|
|
from lab.compose import compose_payload, generate_compose
|
|
from lab.utils import command_available, run, write_json
|
|
|
|
|
|
def prepare(profile: Dict[str, Any], run_dir: Path, dry_run: bool = False) -> Dict[str, Any]:
|
|
payload = compose_payload(profile)
|
|
compose_path = run_dir / "compose" / "compose.yaml"
|
|
result = {
|
|
"compose_path": str(compose_path),
|
|
"service_count": len(payload.get("services", {})),
|
|
"compose_preview": payload,
|
|
"docker_available": command_available("docker"),
|
|
"status": "ready",
|
|
}
|
|
if dry_run:
|
|
result["status"] = "planned"
|
|
return result
|
|
|
|
compose_path, payload = generate_compose(profile, run_dir)
|
|
if not result["docker_available"]:
|
|
result["status"] = "blocked-artifact"
|
|
result["blocked_reason"] = "docker unavailable on this machine"
|
|
return result
|
|
|
|
config = run(["docker", "compose", "-f", str(compose_path), "config"], cwd=run_dir)
|
|
result["compose_config_rc"] = config.returncode
|
|
if config.returncode != 0:
|
|
result["status"] = "blocked-artifact"
|
|
result["blocked_reason"] = config.stderr.strip() or "docker compose config failed"
|
|
return result
|
|
|
|
up = run(["docker", "compose", "-f", str(compose_path), "up", "-d", "--wait"], cwd=run_dir)
|
|
result["compose_up_rc"] = up.returncode
|
|
if up.returncode != 0:
|
|
result["status"] = "blocked-artifact"
|
|
result["blocked_reason"] = up.stderr.strip() or up.stdout.strip() or "docker compose up failed"
|
|
return result
|
|
return result
|
|
|
|
|
|
def wait_ready(profile: Dict[str, Any], run_dir: Path, compose_path: Path) -> Dict[str, Any]:
|
|
timeout_seconds = int(profile.get("ready_timeout_seconds") or 45)
|
|
baseline_urls = profile.get("baseline_urls", []) or []
|
|
started = time.monotonic()
|
|
observations = []
|
|
status = "completed"
|
|
detail = f"baseline urls ready ({len(baseline_urls)})"
|
|
|
|
while True:
|
|
observations = []
|
|
ready = True
|
|
for url in baseline_urls:
|
|
try:
|
|
response = requests.get(url, timeout=4)
|
|
observations.append({"url": url, "status_code": response.status_code})
|
|
if response.status_code >= 500:
|
|
ready = False
|
|
except Exception as exc:
|
|
observations.append({"url": url, "error": str(exc)})
|
|
ready = False
|
|
if ready:
|
|
break
|
|
if time.monotonic() - started >= timeout_seconds:
|
|
status = "failed"
|
|
detail = f"services not ready within {timeout_seconds}s"
|
|
break
|
|
time.sleep(1)
|
|
|
|
payload = {
|
|
"status": status,
|
|
"detail": detail,
|
|
"elapsed_seconds": round(time.monotonic() - started, 1),
|
|
"observations": observations,
|
|
"compose_path": str(compose_path),
|
|
}
|
|
write_json(run_dir / "logs" / "ready.json", payload)
|
|
return payload
|
|
|
|
|
|
def teardown(run_dir: Path, compose_path: Path) -> Dict[str, Any]:
|
|
if not command_available("docker") or not compose_path.exists():
|
|
return {"status": "skipped", "detail": "docker unavailable or compose file missing"}
|
|
down = run(["docker", "compose", "-f", str(compose_path), "down", "-v", "--remove-orphans"], cwd=run_dir)
|
|
if down.returncode != 0:
|
|
return {"status": "failed", "detail": down.stderr.strip() or down.stdout.strip() or "docker compose down failed"}
|
|
return {"status": "completed", "detail": "docker compose down completed"}
|