#!/usr/bin/env python3 """ ๐Ÿš€ AWOOOI Phase 2 ๅฐŽๅฝˆ่…ณๆœฌ - fire_test_alert.py =============================================== ๅ‘็ณป็ตฑๆณจๅ…ฅๆจกๆ“ฌๅ‘Š่ญฆ๏ผŒ่งธ็™ผ ClawBot AI ๅˆ†ๆžๆต็จ‹ ็”จ้€”: - ้ฉ—่ญ‰ๅ…จ้ˆ่ทฏ (Webhook โ†’ ClawBot โ†’ ApprovalCard) - ๆธฌ่ฉฆๆˆฐๆƒ…ๅฎคๅ‰็ซฏๆ˜ฏๅฆๅณๆ™‚ๅฝˆๅ‡บๆŽˆๆฌŠๅก็‰‡ - ้–‹็™ผ้™ค้Œฏ็”จ (็„ก้œ€็œŸๅฏฆ็›ฃๆŽง็ณป็ตฑ) ๅŸท่กŒๆ–นๅผ: cd apps/api python -m scripts.fire_test_alert # ๆŒ‡ๅฎšๅ‘Š่ญฆ้กžๅž‹ python -m scripts.fire_test_alert --type db_connection_timeout python -m scripts.fire_test_alert --type k8s_pod_crash --severity critical Author: Claude Code Date: 2026-03-21 """ import argparse import asyncio import sys from datetime import datetime from pathlib import Path # Add src to path sys.path.insert(0, str(Path(__file__).parent.parent / "src")) import httpx # ============================================================================= # Config # ============================================================================= API_BASE_URL = "http://localhost:8000" WEBHOOK_ENDPOINT = f"{API_BASE_URL}/api/v1/webhooks/alerts" # ============================================================================= # ้ ๅฎš็พฉๅ‘Š่ญฆๅ ดๆ™ฏ (High-Fidelity Mock Alerts) # ============================================================================= ALERT_SCENARIOS = { "db_connection_timeout": { "alert_type": "db_connection_timeout", "severity": "critical", "source": "prometheus-alertmanager", "target_resource": "postgres-primary-0", "namespace": "database", "message": "PostgreSQL Database OOM - Connection pool exhausted, 47 waiting queries", "metrics": { "connection_count": 100, "waiting_queries": 47, "cpu_percent": 89, "memory_percent": 95, "sigma_deviation": 4.2, }, "labels": { "app": "postgres", "team": "dba", "tier": "critical", }, }, "k8s_pod_crash": { "alert_type": "k8s_pod_crash", "severity": "warning", "source": "k8s-event-watcher", "target_resource": "harbor-core-7d4b8c9f5-xk2m3", "namespace": "harbor", "message": "Pod CrashLoopBackOff detected - OOMKilled after 5 restarts", "metrics": { "restart_count": 5, "last_exit_code": 137, "cpu_percent": 95, "memory_percent": 100, "sigma_deviation": 3.8, }, "labels": { "app": "harbor-core", "team": "devops", }, }, "high_cpu": { "alert_type": "high_cpu", "severity": "warning", "source": "node-exporter", "target_resource": "api-backend-deployment", "namespace": "production", "message": "Payment API Latency Spike - CPU at 94%, response time > 2s", "metrics": { "cpu_percent": 94, "memory_percent": 72, "response_time_ms": 2340, "sigma_deviation": 3.2, }, "labels": { "app": "payment-api", "team": "backend", "sla": "critical", }, }, "disk_full": { "alert_type": "disk_full", "severity": "critical", "source": "node-exporter", "target_resource": "logging-node-01", "namespace": "kube-system", "message": "Disk usage at 97% - /var/log nearly full, risk of logging failure", "metrics": { "disk_percent": 97, "available_gb": 2.3, "inode_percent": 89, }, "labels": { "node": "logging-node-01", "team": "sre", }, }, "ssl_expiry": { "alert_type": "ssl_expiry", "severity": "warning", "source": "cert-manager", "target_resource": "awoooi.wooo.work", "namespace": "cert-manager", "message": "SSL Certificate expiring in 7 days - auto-renewal failed", "metrics": { "days_until_expiry": 7, }, "labels": { "domain": "awoooi.wooo.work", "issuer": "letsencrypt", }, }, } # ============================================================================= # Terminal Output Helpers (ๆผ‚ไบฎ็š„ Log) # ============================================================================= class Colors: """ANSI Color Codes""" HEADER = '\033[95m' BLUE = '\033[94m' CYAN = '\033[96m' GREEN = '\033[92m' YELLOW = '\033[93m' RED = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' DIM = '\033[2m' def print_banner(): """Print AWOOOI ASCII Banner""" banner = f""" {Colors.CYAN}{Colors.BOLD} โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ•”โ•โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•šโ•โ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• {Colors.ENDC} {Colors.DIM} ๐Ÿš€ Phase 2 ๅฐŽๅฝˆ่…ณๆœฌ - Test Alert Injector{Colors.ENDC} {Colors.DIM} โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€{Colors.ENDC} """ print(banner) def print_section(title: str): """Print section header""" print(f"\n{Colors.BLUE}{Colors.BOLD}โ–ถ {title}{Colors.ENDC}") print(f"{Colors.DIM}{'โ”€' * 50}{Colors.ENDC}") def print_alert_info(alert: dict): """Print alert payload info""" print(f" {Colors.YELLOW}ๅ‘Š่ญฆ้กžๅž‹:{Colors.ENDC} {alert['alert_type']}") print(f" {Colors.YELLOW}ๅšด้‡ๅบฆ:{Colors.ENDC} {alert['severity']}") print(f" {Colors.YELLOW}็›ฎๆจ™่ณ‡ๆบ:{Colors.ENDC} {alert['target_resource']}") print(f" {Colors.YELLOW}ๅ‘ฝๅ็ฉบ้–“:{Colors.ENDC} {alert['namespace']}") print(f" {Colors.YELLOW}่จŠๆฏ:{Colors.ENDC} {alert['message']}") if alert.get('metrics'): print(f" {Colors.YELLOW}ๆŒ‡ๆจ™:{Colors.ENDC}") for k, v in alert['metrics'].items(): print(f" โ€ข {k}: {v}") def print_response(response: dict, status_code: int): """Print API response""" if status_code == 200 and response.get('success'): print(f"\n{Colors.GREEN}{Colors.BOLD}โœ“ ๅ‘Š่ญฆ็™ผๅฐ„ๆˆๅŠŸ๏ผ{Colors.ENDC}") print(f" {Colors.CYAN}Approval ID:{Colors.ENDC} {response.get('approval_id', 'N/A')}") print(f" {Colors.CYAN}้ขจ้šช็ญ‰็ดš:{Colors.ENDC} {response.get('risk_level', 'N/A')}") print(f" {Colors.CYAN}ๅปบ่ญฐๅ‹•ไฝœ:{Colors.ENDC} {response.get('suggested_action', 'N/A')}") print(f" {Colors.CYAN}่šๅˆๆฌกๆ•ธ:{Colors.ENDC} {response.get('hit_count', 1)}") if response.get('converged'): print(f" {Colors.YELLOW}โšก ๅ‘Š่ญฆๅทฒๆ”ถๆ–‚ (่ทณ้Ž LLM){Colors.ENDC}") else: print(f"\n{Colors.RED}{Colors.BOLD}โœ— ๅ‘Š่ญฆ็™ผๅฐ„ๅคฑๆ•—๏ผ{Colors.ENDC}") print(f" {Colors.RED}็‹€ๆ…‹็ขผ:{Colors.ENDC} {status_code}") print(f" {Colors.RED}ๅ›žๆ‡‰:{Colors.ENDC} {response}") def print_footer(): """Print footer with instructions""" print(f"\n{Colors.DIM}{'โ”€' * 50}{Colors.ENDC}") print(f"{Colors.GREEN}๐Ÿ“บ ่ซ‹ๆŸฅ็œ‹ๆˆฐๆƒ…ๅฎคๅ‰็ซฏ๏ผš{Colors.ENDC} http://localhost:3000") print(f"{Colors.GREEN}๐Ÿ“‹ ๅณๅด้ขๆฟๆ‡‰้กฏ็คบๆ–ฐ็š„ ApprovalCard{Colors.ENDC}") print(f"{Colors.DIM}ๆ™‚้–“: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}{Colors.ENDC}\n") # ============================================================================= # Main Logic # ============================================================================= async def fire_alert(alert_type: str, severity: str | None = None) -> bool: """ ็™ผๅฐ„ๆธฌ่ฉฆๅ‘Š่ญฆ Args: alert_type: ๅ‘Š่ญฆ้กžๅž‹ (db_connection_timeout, k8s_pod_crash, etc.) severity: ่ฆ†่“‹ๅšด้‡ๅบฆ (optional) Returns: bool: ๆ˜ฏๅฆๆˆๅŠŸ """ # ๅ–ๅพ—ๅ‘Š่ญฆๅ ดๆ™ฏ if alert_type not in ALERT_SCENARIOS: print(f"{Colors.RED}โŒ ๆœช็Ÿฅๅ‘Š่ญฆ้กžๅž‹: {alert_type}{Colors.ENDC}") print(f"{Colors.DIM}ๅฏ็”จ้กžๅž‹: {', '.join(ALERT_SCENARIOS.keys())}{Colors.ENDC}") return False alert = ALERT_SCENARIOS[alert_type].copy() # ่ฆ†่“‹ๅšด้‡ๅบฆ if severity: alert['severity'] = severity print_section("ๅ‘Š่ญฆ Payload") print_alert_info(alert) print_section("็™ผๅฐ„ๅ‘Š่ญฆ่‡ณ Webhook API") print(f" {Colors.CYAN}็ซฏ้ปž:{Colors.ENDC} {WEBHOOK_ENDPOINT}") try: async with httpx.AsyncClient(timeout=30.0) as client: response = await client.post( WEBHOOK_ENDPOINT, json=alert, headers={"Content-Type": "application/json"}, ) result = response.json() print_response(result, response.status_code) return response.status_code == 200 except httpx.ConnectError: print(f"\n{Colors.RED}{Colors.BOLD}โœ— ้€ฃ็ทšๅคฑๆ•—๏ผ{Colors.ENDC}") print(f" {Colors.RED}่ซ‹็ขบ่ชๅพŒ็ซฏ API ๆญฃๅœจ้‹่กŒ:{Colors.ENDC}") print(f" {Colors.DIM}cd apps/api && uvicorn src.main:app --reload{Colors.ENDC}") return False except Exception as e: print(f"\n{Colors.RED}{Colors.BOLD}โœ— ็™ผ็”Ÿ้Œฏ่ชค๏ผš{e}{Colors.ENDC}") return False def main(): """CLI Entry Point""" parser = argparse.ArgumentParser( description="๐Ÿš€ AWOOOI Phase 2 ๅฐŽๅฝˆ่…ณๆœฌ - ็™ผๅฐ„ๆธฌ่ฉฆๅ‘Š่ญฆ", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" ๅฏ็”จๅ‘Š่ญฆ้กžๅž‹: db_connection_timeout PostgreSQL Database OOM (CRITICAL) k8s_pod_crash Pod CrashLoopBackOff (MEDIUM) high_cpu CPU Spike / Latency (MEDIUM) disk_full Disk Full Warning (CRITICAL) ssl_expiry SSL Certificate Expiry (LOW) ็ฏ„ไพ‹: python -m scripts.fire_test_alert python -m scripts.fire_test_alert --type db_connection_timeout python -m scripts.fire_test_alert --type k8s_pod_crash --severity critical """, ) parser.add_argument( "--type", "-t", type=str, default="db_connection_timeout", choices=list(ALERT_SCENARIOS.keys()), help="ๅ‘Š่ญฆ้กžๅž‹ (้ ่จญ: db_connection_timeout)", ) parser.add_argument( "--severity", "-s", type=str, choices=["info", "warning", "critical"], help="่ฆ†่“‹ๅšด้‡ๅบฆ (้ ่จญไฝฟ็”จๅ ดๆ™ฏ้ ่จญๅ€ผ)", ) args = parser.parse_args() print_banner() success = asyncio.run(fire_alert(args.type, args.severity)) print_footer() sys.exit(0 if success else 1) if __name__ == "__main__": main()