mirror of
https://github.com/blackboxprogramming/BlackRoad-Operating-System.git
synced 2026-03-17 05:57:21 -05:00
feat: Phase LIVE integration - Production automation and deployment fixes
## Railway Deployment Fixes - Fix railway.toml startCommand (remove incorrect 'cd backend') - Enhance Dockerfile with security, health checks, and proper user permissions - Add comprehensive deployment fix documentation (RAILWAY_DEPLOY_FIX.md) ## Phase Q/Q2 Integration - Add GitHub webhooks router (/api/webhooks/github) for PR automation - Integrate Prism Console at /prism endpoint - Add GITHUB_TOKEN and GITHUB_WEBHOOK_SECRET to config - Update .env.example with webhook secret ## Documentation - Create comprehensive GitHub setup guide (GITHUB_SETUP_GUIDE.md) - Document branch protection, merge queue, and webhook configuration - Include troubleshooting and testing procedures ## Related - Phase Q: Merge Queue & Automation (PR #78 - merged) - Phase Q2: PR Action Intelligence (PR #85 - open) - Phase 2.5: Infrastructure decisions (PR #63 - open) This brings the automation stack online and stabilizes Railway deployments.
This commit is contained in:
152
backend/app/routers/webhooks.py
Normal file
152
backend/app/routers/webhooks.py
Normal file
@@ -0,0 +1,152 @@
|
||||
"""
|
||||
GitHub Webhooks Router
|
||||
|
||||
Receives and processes GitHub webhook events for PR automation.
|
||||
Part of Phase Q/Q2 - Merge Queue & Automation Strategy.
|
||||
|
||||
Related docs: OPERATOR_PR_EVENT_HANDLERS.md, MERGE_QUEUE_PLAN.md
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Request, HTTPException, Depends, Header
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
import hmac
|
||||
import hashlib
|
||||
from typing import Optional
|
||||
import logging
|
||||
|
||||
from ..database import get_db
|
||||
from ..services import github_events
|
||||
from ..config import settings
|
||||
|
||||
router = APIRouter(prefix="/api/webhooks", tags=["Webhooks"])
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def verify_github_signature(payload: bytes, signature: str, secret: str) -> bool:
|
||||
"""Verify GitHub webhook signature"""
|
||||
if not signature or not signature.startswith("sha256="):
|
||||
return False
|
||||
|
||||
expected_signature = "sha256=" + hmac.new(
|
||||
secret.encode(),
|
||||
payload,
|
||||
hashlib.sha256
|
||||
).hexdigest()
|
||||
|
||||
return hmac.compare_digest(signature, expected_signature)
|
||||
|
||||
|
||||
@router.post("/github")
|
||||
async def github_webhook(
|
||||
request: Request,
|
||||
x_hub_signature_256: Optional[str] = Header(None),
|
||||
x_github_event: Optional[str] = Header(None),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Receive GitHub webhook events
|
||||
|
||||
This endpoint receives webhook events from GitHub for:
|
||||
- Pull requests (opened, closed, merged, labeled, etc.)
|
||||
- Pull request reviews (submitted, approved, changes_requested)
|
||||
- Status checks (check_run, check_suite completed)
|
||||
|
||||
Events are processed by the github_events service and logged to the database.
|
||||
|
||||
**Security**: Webhooks are verified using HMAC-SHA256 signature.
|
||||
|
||||
**Setup**: In GitHub repository settings → Webhooks:
|
||||
- Payload URL: https://your-domain.com/api/webhooks/github
|
||||
- Content type: application/json
|
||||
- Secret: Set GITHUB_WEBHOOK_SECRET environment variable
|
||||
- Events: Pull requests, Pull request reviews, Statuses, Check runs
|
||||
"""
|
||||
|
||||
# Read request body
|
||||
body = await request.body()
|
||||
|
||||
# Verify signature if secret is configured
|
||||
webhook_secret = getattr(settings, 'GITHUB_WEBHOOK_SECRET', None)
|
||||
if webhook_secret and x_hub_signature_256:
|
||||
if not verify_github_signature(body, x_hub_signature_256, webhook_secret):
|
||||
logger.warning("Invalid GitHub webhook signature")
|
||||
raise HTTPException(status_code=401, detail="Invalid signature")
|
||||
elif webhook_secret and not x_hub_signature_256:
|
||||
logger.warning("Missing GitHub webhook signature")
|
||||
raise HTTPException(status_code=401, detail="Missing signature")
|
||||
|
||||
# Verify event type header
|
||||
if not x_github_event:
|
||||
raise HTTPException(status_code=400, detail="Missing X-GitHub-Event header")
|
||||
|
||||
# Parse JSON payload
|
||||
try:
|
||||
payload = await request.json()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to parse webhook payload: {e}")
|
||||
raise HTTPException(status_code=400, detail="Invalid JSON payload")
|
||||
|
||||
# Log webhook received
|
||||
logger.info(
|
||||
f"GitHub webhook received: {x_github_event} | "
|
||||
f"Action: {payload.get('action')} | "
|
||||
f"PR: #{payload.get('pull_request', {}).get('number', 'N/A')}"
|
||||
)
|
||||
|
||||
# Process event asynchronously
|
||||
try:
|
||||
await github_events.handle_event(
|
||||
event_type=x_github_event,
|
||||
payload=payload,
|
||||
db=db
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing GitHub event: {e}", exc_info=True)
|
||||
# Return 200 even on processing errors to avoid GitHub retries
|
||||
# Errors are logged for debugging
|
||||
return {"status": "received", "processing_error": str(e)}
|
||||
|
||||
return {
|
||||
"status": "received",
|
||||
"event": x_github_event,
|
||||
"action": payload.get("action"),
|
||||
"pr_number": payload.get("pull_request", {}).get("number")
|
||||
}
|
||||
|
||||
|
||||
@router.get("/github/ping")
|
||||
async def github_webhook_ping():
|
||||
"""
|
||||
Ping endpoint for testing GitHub webhook configuration
|
||||
|
||||
Use this to verify your webhook is reachable before setting it up in GitHub.
|
||||
"""
|
||||
return {
|
||||
"status": "ok",
|
||||
"message": "GitHub webhook endpoint is reachable",
|
||||
"configured": hasattr(settings, 'GITHUB_WEBHOOK_SECRET') and settings.GITHUB_WEBHOOK_SECRET is not None
|
||||
}
|
||||
|
||||
|
||||
@router.get("/status")
|
||||
async def webhooks_status():
|
||||
"""
|
||||
Get webhook configuration status
|
||||
|
||||
Returns information about which webhooks are configured and ready.
|
||||
"""
|
||||
return {
|
||||
"github": {
|
||||
"endpoint": "/api/webhooks/github",
|
||||
"configured": hasattr(settings, 'GITHUB_WEBHOOK_SECRET') and settings.GITHUB_WEBHOOK_SECRET is not None,
|
||||
"events_supported": [
|
||||
"pull_request",
|
||||
"pull_request_review",
|
||||
"pull_request_review_comment",
|
||||
"status",
|
||||
"check_run",
|
||||
"check_suite"
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user