feat: add approved stack lifecycle endpoints
This commit is contained in:
@@ -10,7 +10,7 @@ from pathlib import Path
|
||||
|
||||
import httpx
|
||||
from fastapi import BackgroundTasks, FastAPI, HTTPException, Request
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [qyburn] %(message)s")
|
||||
@@ -709,3 +709,83 @@ async def receive_handoff(job_id: str, request: Request):
|
||||
logger.info(f"[{job_id[:8]}] handoff received — pending review")
|
||||
_write_last_run()
|
||||
return {"status": "pending_review", "job_id": job_id}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Jon Snow approval gate — rebuild/restart endpoints (pure subprocess, no LLM)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@app.post("/rebuild/{stack_name}")
|
||||
async def rebuild_stack(stack_name: str):
|
||||
"""Rebuild a Docker Compose stack (compose up -d --build). Called by Jon Snow approval gate.
|
||||
No LLM involved — pure subprocess execution."""
|
||||
import re
|
||||
if not re.match(r'^[a-zA-Z0-9_-]+$', stack_name):
|
||||
return JSONResponse({"ok": False, "error": "invalid stack name"}, status_code=400)
|
||||
|
||||
stack_path = STACKS_DIR / stack_name
|
||||
if not stack_path.exists():
|
||||
return JSONResponse({"ok": False, "error": f"stack not found: {stack_name}"}, status_code=404)
|
||||
|
||||
logger.info(f"[rebuild] {stack_name} — starting compose up -d --build")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["docker", "compose", "up", "-d", "--build"],
|
||||
cwd=str(stack_path),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=300,
|
||||
)
|
||||
ok = result.returncode == 0
|
||||
logger.info(f"[rebuild] {stack_name} — {'ok' if ok else 'failed'} (rc={result.returncode})")
|
||||
return JSONResponse({
|
||||
"ok": ok,
|
||||
"stack": stack_name,
|
||||
"returncode": result.returncode,
|
||||
"stdout": result.stdout[-2000:] if result.stdout else "",
|
||||
"stderr": result.stderr[-2000:] if result.stderr else "",
|
||||
})
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.error(f"[rebuild] {stack_name} — timed out after 300s")
|
||||
return JSONResponse({"ok": False, "error": "rebuild timed out (300s)"}, status_code=408)
|
||||
except Exception as e:
|
||||
logger.error(f"[rebuild] {stack_name} — error: {e}")
|
||||
return JSONResponse({"ok": False, "error": str(e)}, status_code=500)
|
||||
|
||||
|
||||
@app.post("/restart/{stack_name}")
|
||||
async def restart_stack(stack_name: str):
|
||||
"""Restart a Docker Compose stack without rebuilding (compose restart). Called by Jon Snow approval gate.
|
||||
No LLM involved — pure subprocess execution."""
|
||||
import re
|
||||
if not re.match(r'^[a-zA-Z0-9_-]+$', stack_name):
|
||||
return JSONResponse({"ok": False, "error": "invalid stack name"}, status_code=400)
|
||||
|
||||
stack_path = STACKS_DIR / stack_name
|
||||
if not stack_path.exists():
|
||||
return JSONResponse({"ok": False, "error": f"stack not found: {stack_name}"}, status_code=404)
|
||||
|
||||
logger.info(f"[restart] {stack_name} — starting compose restart")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["docker", "compose", "restart"],
|
||||
cwd=str(stack_path),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120,
|
||||
)
|
||||
ok = result.returncode == 0
|
||||
logger.info(f"[restart] {stack_name} — {'ok' if ok else 'failed'} (rc={result.returncode})")
|
||||
return JSONResponse({
|
||||
"ok": ok,
|
||||
"stack": stack_name,
|
||||
"returncode": result.returncode,
|
||||
"stdout": result.stdout[-2000:] if result.stdout else "",
|
||||
"stderr": result.stderr[-2000:] if result.stderr else "",
|
||||
})
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.error(f"[restart] {stack_name} — timed out after 120s")
|
||||
return JSONResponse({"ok": False, "error": "restart timed out (120s)"}, status_code=408)
|
||||
except Exception as e:
|
||||
logger.error(f"[restart] {stack_name} — error: {e}")
|
||||
return JSONResponse({"ok": False, "error": str(e)}, status_code=500)
|
||||
|
||||
Reference in New Issue
Block a user