File size: 3,574 Bytes
137da61
 
 
 
 
 
 
2110b7e
137da61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
664a453
 
 
 
137da61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
392da6f
 
 
 
 
137da61
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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)