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