freeleaps-ops/apps/gitea-webhook-ambassador-python/app/main_demo.py

438 lines
13 KiB
Python

from fastapi import FastAPI, Request, Depends, HTTPException, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import structlog
from datetime import datetime, timedelta
from typing import Dict, Any, Optional, List
from pydantic import BaseModel
import secrets
from app.config import get_settings
# Configure logging
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.stdlib.PositionalArgumentsFormatter(),
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.processors.UnicodeDecoder(),
structlog.processors.JSONRenderer()
],
context_class=dict,
logger_factory=structlog.stdlib.LoggerFactory(),
wrapper_class=structlog.stdlib.BoundLogger,
cache_logger_on_first_use=True,
)
logger = structlog.get_logger()
# Create FastAPI application
app = FastAPI(
title="Gitea Webhook Ambassador (Demo)",
description="High-performance webhook service from Gitea to Jenkins - Demo Version",
version="1.0.0"
)
# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Security configuration
security = HTTPBearer(auto_error=False)
# Demo data storage
api_keys = {
"demo_admin_key": {
"name": "Demo Admin Key",
"key_hash": "demo_admin_key",
"key_prefix": "demo_adm",
"created_at": datetime.utcnow(),
"last_used": datetime.utcnow(),
"is_active": True,
"role": "admin"
},
"demo_user_key": {
"name": "Demo User Key",
"key_hash": "demo_user_key",
"key_prefix": "demo_usr",
"created_at": datetime.utcnow(),
"last_used": datetime.utcnow(),
"is_active": True,
"role": "user"
}
}
trigger_logs = [
{
"id": 1,
"repository_name": "freeleaps/test-project",
"branch_name": "main",
"commit_sha": "abc123def456",
"job_name": "test-project-build",
"status": "success",
"error_message": None,
"created_at": datetime.utcnow() - timedelta(hours=2)
},
{
"id": 2,
"repository_name": "freeleaps/another-project",
"branch_name": "dev",
"commit_sha": "def456ghi789",
"job_name": "another-project-dev",
"status": "success",
"error_message": None,
"created_at": datetime.utcnow() - timedelta(hours=1)
},
{
"id": 3,
"repository_name": "freeleaps/test-project",
"branch_name": "feature/new-feature",
"commit_sha": "ghi789jkl012",
"job_name": "test-project-feature",
"status": "failed",
"error_message": "Build timeout",
"created_at": datetime.utcnow() - timedelta(minutes=30)
}
]
project_mappings = {
1: {
"repository_name": "freeleaps/test-project",
"default_job": "test-project-build",
"branch_jobs": [
{"branch": "dev", "job": "test-project-dev"},
{"branch": "staging", "job": "test-project-staging"}
],
"branch_patterns": [
{"pattern": "feature/*", "job": "test-project-feature"},
{"pattern": "hotfix/*", "job": "test-project-hotfix"}
],
"created_at": datetime.utcnow() - timedelta(days=1),
"updated_at": datetime.utcnow() - timedelta(hours=6)
}
}
# Request/response models
class HealthResponse(BaseModel):
status: str
service: str
version: str
timestamp: datetime
jenkins: Dict[str, Any]
worker_pool: Dict[str, Any]
database: Dict[str, Any]
class TriggerLogResponse(BaseModel):
id: int
repository_name: str
branch_name: str
commit_sha: str
job_name: str
status: str
error_message: Optional[str] = None
created_at: datetime
class APIKeyResponse(BaseModel):
id: str
name: str
key_prefix: str
created_at: datetime
last_used: datetime
is_active: bool
role: str
class ProjectMappingResponse(BaseModel):
id: int
repository_name: str
default_job: str
branch_jobs: List[dict]
branch_patterns: List[dict]
created_at: datetime
updated_at: datetime
# Authentication functions
def verify_api_key(api_key: str):
"""Verify API key"""
for key_id, key_data in api_keys.items():
if key_data["key_hash"] == api_key and key_data["is_active"]:
# Update last used time
key_data["last_used"] = datetime.utcnow()
return key_data
return None
async def get_current_user(
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security)
):
"""Get current user (supports API key authentication)"""
if not credentials:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Authentication token required",
headers={"WWW-Authenticate": "Bearer"},
)
token = credentials.credentials
# Verify API key
api_key_data = verify_api_key(token)
if api_key_data:
return {
"username": api_key_data["name"],
"auth_type": "api_key",
"role": api_key_data["role"]
}
# Authentication failed
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication token",
headers={"WWW-Authenticate": "Bearer"},
)
# Public endpoints
@app.get("/health", response_model=HealthResponse)
async def health_check():
"""Health check endpoint"""
settings = get_settings()
return HealthResponse(
status="healthy",
service="Gitea Webhook Ambassador (Demo)",
version=settings.version,
timestamp=datetime.utcnow(),
jenkins={"status": "connected"},
worker_pool={
"active_workers": 2,
"queue_size": 0,
"total_processed": len(trigger_logs),
"total_failed": len([log for log in trigger_logs if log["status"] == "failed"])
},
database={"status": "connected"}
)
@app.get("/")
async def root():
"""Root path"""
return {
"name": "Gitea Webhook Ambassador (Demo)",
"version": "1.0.0",
"description": "High-performance webhook service from Gitea to Jenkins - Demo Version",
"endpoints": {
"webhook": "/webhook/gitea",
"health": "/health",
"logs": "/api/logs",
"admin": "/api/admin"
},
"demo_keys": {
"admin": "demo_admin_key",
"user": "demo_user_key"
}
}
@app.post("/webhook/gitea")
async def handle_gitea_webhook(request: Request):
"""Handle Gitea webhook request"""
try:
body = await request.body()
# Log webhook request
logger.info("Received Gitea webhook",
body_size=len(body),
headers=dict(request.headers))
# Add new trigger log
log_entry = {
"id": len(trigger_logs) + 1,
"repository_name": "demo-repo",
"branch_name": "main",
"commit_sha": "demo123",
"job_name": "demo-job",
"status": "success",
"error_message": None,
"created_at": datetime.utcnow()
}
trigger_logs.append(log_entry)
return {
"success": True,
"message": "Webhook received successfully",
"data": {
"body_size": len(body),
"timestamp": datetime.utcnow().isoformat()
}
}
except Exception as e:
logger.error("Webhook processing failed", error=str(e))
return JSONResponse(
status_code=500,
content={
"success": False,
"message": "Webhook processing failed",
"error": str(e)
}
)
# Authenticated endpoints
@app.get("/api/logs", response_model=List[TriggerLogResponse])
async def get_trigger_logs(
repository: Optional[str] = None,
branch: Optional[str] = None,
limit: int = 100,
current_user: dict = Depends(get_current_user)
):
"""Get trigger logs"""
print(f"User {current_user['username']} accessed logs endpoint")
filtered_logs = trigger_logs.copy()
if repository:
filtered_logs = [log for log in filtered_logs if log["repository_name"] == repository]
if branch:
filtered_logs = [log for log in filtered_logs if log["branch_name"] == branch]
# Sort by time descending and limit
filtered_logs.sort(key=lambda x: x["created_at"], reverse=True)
return filtered_logs[:limit]
@app.get("/api/logs/stats")
async def get_log_stats(current_user: dict = Depends(get_current_user)):
"""Get log statistics"""
print(f"User {current_user['username']} accessed log statistics")
total_logs = len(trigger_logs)
successful_logs = len([log for log in trigger_logs if log["status"] == "success"])
failed_logs = len([log for log in trigger_logs if log["status"] == "failed"])
# Logs in the last 24 hours
yesterday = datetime.utcnow() - timedelta(days=1)
recent_logs = len([log for log in trigger_logs if log["created_at"] >= yesterday])
# Grouped by repository
repo_stats = {}
for log in trigger_logs:
repo = log["repository_name"]
repo_stats[repo] = repo_stats.get(repo, 0) + 1
return {
"total_logs": total_logs,
"successful_logs": successful_logs,
"failed_logs": failed_logs,
"recent_logs_24h": recent_logs,
"repository_stats": [
{"repository": repo, "count": count}
for repo, count in repo_stats.items()
]
}
@app.get("/api/admin/api-keys", response_model=List[APIKeyResponse])
async def list_api_keys(current_user: dict = Depends(get_current_user)):
"""List all API keys (admin only)"""
if current_user["role"] != "admin":
raise HTTPException(status_code=403, detail="Admin privileges required")
print(f"Admin {current_user['username']} viewed API key list")
return [
APIKeyResponse(
id=key_id,
name=key_data["name"],
key_prefix=key_data["key_prefix"],
created_at=key_data["created_at"],
last_used=key_data["last_used"],
is_active=key_data["is_active"],
role=key_data["role"]
)
for key_id, key_data in api_keys.items()
]
@app.get("/api/admin/projects", response_model=List[ProjectMappingResponse])
async def list_project_mappings(current_user: dict = Depends(get_current_user)):
"""List all project mappings"""
print(f"User {current_user['username']} viewed project mappings")
return [
ProjectMappingResponse(
id=mapping_id,
repository_name=mapping_data["repository_name"],
default_job=mapping_data["default_job"],
branch_jobs=mapping_data["branch_jobs"],
branch_patterns=mapping_data["branch_patterns"],
created_at=mapping_data["created_at"],
updated_at=mapping_data["updated_at"]
)
for mapping_id, mapping_data in project_mappings.items()
]
@app.get("/api/admin/stats")
async def get_admin_stats(current_user: dict = Depends(get_current_user)):
"""Get admin statistics"""
print(f"User {current_user['username']} viewed admin statistics")
total_keys = len(api_keys)
active_keys = len([key for key in api_keys.values() if key["is_active"]])
# Recently used keys
week_ago = datetime.utcnow() - timedelta(days=7)
recent_keys = len([
key for key in api_keys.values()
if key["last_used"] >= week_ago
])
total_mappings = len(project_mappings)
return {
"api_keys": {
"total": total_keys,
"active": active_keys,
"recently_used": recent_keys
},
"project_mappings": {
"total": total_mappings
}
}
# Middleware
@app.middleware("http")
async def log_requests(request: Request, call_next):
"""Request logging middleware"""
start_time = datetime.utcnow()
response = await call_next(request)
process_time = (datetime.utcnow() - start_time).total_seconds()
response.headers["X-Process-Time"] = str(process_time)
return response
if __name__ == "__main__":
import uvicorn
settings = get_settings()
print("🚀 Starting Gitea Webhook Ambassador Demo Version")
print("=" * 60)
print("📋 Demo API Keys:")
print(" Admin key: demo_admin_key")
print(" User key: demo_user_key")
print()
print("🔧 Usage examples:")
print(" curl -H 'Authorization: Bearer demo_admin_key' http://localhost:8000/api/admin/api-keys")
print(" curl -H 'Authorization: Bearer demo_user_key' http://localhost:8000/api/logs")
print("=" * 60)
uvicorn.run(
"app.main_demo:app",
host=settings.server.host,
port=settings.server.port,
reload=settings.server.reload
)