Netdata.cloud bot for Zulip
1"""Pydantic models for Netdata Cloud webhook payloads.""" 2 3from datetime import datetime 4from enum import Enum 5from typing import Optional, Union 6 7from pydantic import BaseModel, Field, field_validator, ConfigDict 8 9 10class AlertSeverity(str, Enum): 11 """Alert severity levels.""" 12 WARNING = "warning" 13 CRITICAL = "critical" 14 CLEAR = "clear" 15 16 17class ReachabilitySeverity(str, Enum): 18 """Reachability severity levels.""" 19 INFO = "info" 20 CRITICAL = "critical" 21 22 23class ReachabilityStatus(BaseModel): 24 """Reachability status information.""" 25 reachable: bool 26 text: str # "reachable" or "unreachable" 27 28 29class AlertNotification(BaseModel): 30 """Alert notification payload from Netdata Cloud.""" 31 message: str 32 alert: str 33 info: str 34 chart: str 35 context: str 36 space: str 37 severity: AlertSeverity 38 date: datetime 39 alert_url: str 40 # Additional fields from full schema 41 Rooms: Optional[dict] = None 42 family: Optional[str] = None 43 class_: Optional[str] = Field(None, alias="class") # 'class' is a Python keyword 44 duration: Optional[str] = None 45 additional_active_critical_alerts: Optional[int] = None 46 additional_active_warning_alerts: Optional[int] = None 47 48 @field_validator('date', mode='before') 49 @classmethod 50 def parse_date(cls, v): 51 if isinstance(v, str): 52 return datetime.fromisoformat(v.replace('Z', '+00:00')) 53 return v 54 55 56class ReachabilityNotification(BaseModel): 57 """Reachability notification payload from Netdata Cloud.""" 58 message: str 59 url: str 60 host: str 61 severity: ReachabilitySeverity 62 status: ReachabilityStatus 63 64 65class TestNotification(BaseModel): 66 """Test notification payload from Netdata Cloud.""" 67 message: str 68 69 70class WebhookPayload(BaseModel): 71 """Union type for webhook payloads.""" 72 73 @classmethod 74 def parse(cls, data: dict) -> Union[AlertNotification, ReachabilityNotification, TestNotification]: 75 """Parse webhook payload and determine notification type.""" 76 if 'alert' in data and 'chart' in data: 77 return AlertNotification(**data) 78 elif 'status' in data and 'host' in data: 79 return ReachabilityNotification(**data) 80 elif len(data) == 1 and 'message' in data: 81 # Test notification - only has a message field 82 return TestNotification(**data) 83 else: 84 raise ValueError(f"Unknown notification type: {data}") 85 86 87class ZulipConfig(BaseModel): 88 """Zulip bot configuration.""" 89 site: Optional[str] = None 90 email: Optional[str] = None 91 api_key: Optional[str] = None 92 stream: str = "netdata-alerts" 93 94 model_config = ConfigDict(env_prefix="ZULIP_") 95 96 97class ServerConfig(BaseModel): 98 """Server configuration.""" 99 host: str = "0.0.0.0" 100 port: int = 8080 # Default HTTP port 101 challenge_secret: Optional[str] = None # Netdata webhook challenge secret 102 103 model_config = ConfigDict(env_prefix="SERVER_")