Duo-Guardian / src /webhook.py
Daksh C Jain
added split nodes
392da6f
import os
import sys
from fastapi import FastAPI, Request, BackgroundTasks, HTTPException
import uvicorn
from contextlib import asynccontextmanager
# Import the core agent workflow we built earlier
from src.main import build_and_run_graph
@asynccontextmanager
async def lifespan(app: FastAPI):
print("πŸš€ Context Brain Webhook Server Starting up...")
yield
print("πŸ›‘ Context Brain Webhook Server Shutting down...")
app = FastAPI(title="GitLab Context Brain Webhook", lifespan=lifespan)
def run_agent_safely(project_id: str, mr_iid: str):
"""
Wrapper to run the LangGraph pipeline. It intentionally catches SystemExit
because the original main.py script uses sys.exit(1) to fail the CI Job.
In a webhook server context, we don't want sys.exit() to kill the entire FastAPI server!
"""
print(f"\n[BACKGROUND THREAD] Starting Agent for Project {project_id} / MR {mr_iid}")
try:
build_and_run_graph(project_id, mr_iid)
except SystemExit as e:
print(f"[BACKGROUND THREAD] Agent finished logic. (Caught graceful exit code: {e.code})")
except Exception as e:
print(f"[BACKGROUND THREAD] Agent encountered a fatal error: {e}")
finally:
print(f"[BACKGROUND THREAD] Finished processing Project {project_id} / MR {mr_iid}\n")
@app.get("/health")
async def health_check():
return {"status": "ok", "message": "Context Brain Webhook is running!"}
@app.post("/webhook/gitlab")
async def gitlab_webhook(request: Request, background_tasks: BackgroundTasks):
"""
GitLab will POST to this endpoint whenever an event occurs.
"""
# 1. Parse JSON Payload
try:
payload = await request.json()
except Exception:
raise HTTPException(status_code=400, detail="Invalid JSON Payload")
# 2. Filter for Merge Request Events Only
object_kind = payload.get("object_kind")
if object_kind != "merge_request":
return {"status": "ignored", "reason": f"Event type '{object_kind}' is not a merge request event."}
# 3. Extract IDs needed for the API
project = payload.get("project", {})
project_id = str(project.get("id"))
attributes = payload.get("object_attributes", {})
mr_iid = str(attributes.get("iid"))
action = attributes.get("action") # "open", "update", "close"
if not project_id or not mr_iid:
raise HTTPException(status_code=400, detail="Missing project.id or object_attributes.iid in payload")
# We typically only care about new or updated MRs
if action not in ["open", "update", "reopen"]:
return {"status": "ignored", "reason": f"MR action '{action}' doesn't require analysis."}
if action == "update" and "oldrev" not in attributes:
# Ignore updates that don't change code (e.g. label changes or comment edits)
# This prevents an infinite loop where the agent labels the MR, triggering itself!
return {"status": "ignored", "reason": "Update event contains no code changes."}
print(f"\nπŸ”” Webhook Triggered! Project: {project_id} | MR: {mr_iid} | Action: {action}")
# 4. Offload heavy lifting to a background task
# We must respond to GitLab immediately (within 10 seconds), otherwise it marks the webhook as failed.
background_tasks.add_task(run_agent_safely, project_id, mr_iid)
return {
"status": "accepted",
"message": f"Processing Project {project_id} MR {mr_iid} in the background."
}
if __name__ == "__main__":
uvicorn.run("webhook:app", host="0.0.0.0", port=8000, reload=True)