Skip to content

Commit fe626ea

Browse files
committed
notify.py: add email notifier script, compatible with new backend
1 parent 03ae778 commit fe626ea

File tree

1 file changed

+141
-0
lines changed

1 file changed

+141
-0
lines changed

notify.py

+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import argparse
2+
import asyncio
3+
import os
4+
import sys
5+
from email.message import EmailMessage
6+
from typing import Optional
7+
8+
import aiosmtplib
9+
import httpx
10+
import yaml
11+
12+
from pydantic import BaseModel, error_wrappers
13+
14+
15+
CONFIGFILE_DEFAULT = os.path.join(os.getcwd(), "config.yml")
16+
17+
18+
class MailConfig(BaseModel):
19+
mailto: str
20+
server: str
21+
port: int
22+
use_tls: bool
23+
username: str
24+
password: str
25+
26+
27+
class NotifyConfig(BaseModel):
28+
api_url: Optional[str] = "http://localhost:8000"
29+
mail: Optional[MailConfig]
30+
31+
32+
class MailNotifier:
33+
34+
def __init__(self, config):
35+
self.mailto = config.mailto
36+
self.server = config.server
37+
self.port = config.port
38+
self.use_tls = config.use_tls
39+
self.username = config.username
40+
self.password = config.password
41+
42+
async def notify(self, title, content):
43+
message = EmailMessage()
44+
message["From"] = "[email protected]"
45+
message["To"] = self.mailto
46+
message["Subject"] = title
47+
message.set_content(content)
48+
49+
try:
50+
await aiosmtplib.send(
51+
message,
52+
hostname=self.server,
53+
port=self.port,
54+
use_tls=self.use_tls,
55+
username=self.username,
56+
password=self.password,
57+
)
58+
except aiosmtplib.errors.SMTPAuthenticationError as exc:
59+
print(f"Cannot send email: {exc}")
60+
print("Notification email sent")
61+
62+
63+
async def fetch_finished_jobs(branch, api_url, limit=5):
64+
async with httpx.AsyncClient() as client:
65+
response = await client.get(
66+
f"{api_url}/jobs/finished?limit={limit}&is_branch=1&branch={branch}",
67+
headers={
68+
"Accept": "application/json",
69+
},
70+
)
71+
if response.status_code != 200:
72+
sys.exit(1)
73+
74+
return response.json()
75+
76+
77+
def parse_config_file(config_filename):
78+
with open(config_filename) as config_file:
79+
content = config_file.read()
80+
try:
81+
config = yaml.load(content, Loader=yaml.FullLoader)
82+
except yaml.YAMLError as exc:
83+
print(f"Invalid yml config file: {exc}")
84+
sys.exit(1)
85+
86+
try:
87+
return NotifyConfig(**config)
88+
except error_wrappers.ValidationError as exc:
89+
print(
90+
"Missing server configuration field:\n"
91+
f"{error_wrappers.display_errors(exc.errors())}"
92+
)
93+
sys.exit(1)
94+
95+
96+
NOTIFIERS = {
97+
"mail": MailNotifier,
98+
}
99+
100+
101+
async def notify(branch, job_uid, job_result, configfile):
102+
config = parse_config_file(configfile)
103+
finished_jobs = await fetch_finished_jobs(branch, config.api_url)
104+
105+
job_state = "passed" if int(job_result) == 0 else "errored"
106+
if finished_jobs and finished_jobs[0]["state"] == job_state:
107+
print(f"No result change for branch '{branch}', don't notify")
108+
return
109+
110+
title = f"Murdock job {job_state}: {branch}"
111+
content = f"""Murdock job {job_uid} {job_state}!
112+
113+
Results: {config.api_url}/results/{job_uid}
114+
"""
115+
116+
for notifier_type, notifier_cls in NOTIFIERS.items():
117+
notifier = notifier_cls(getattr(config, notifier_type))
118+
await notifier.notify(title, content)
119+
120+
121+
def main():
122+
parser = argparse.ArgumentParser()
123+
parser.add_argument(
124+
"branch", type=str, help="Name of the branch to notify results from"
125+
)
126+
parser.add_argument("--job-uid", type=str, help="UID of the Murdock job")
127+
parser.add_argument("--job-result", type=int, help="Result of the Murdock job")
128+
parser.add_argument(
129+
"-f",
130+
"--configfile",
131+
type=str,
132+
default=CONFIGFILE_DEFAULT,
133+
help="SMTP configuration file",
134+
)
135+
args = parser.parse_args()
136+
event_loop = asyncio.get_event_loop()
137+
event_loop.run_until_complete(notify(**vars(args)))
138+
139+
140+
if __name__ == "__main__":
141+
main()

0 commit comments

Comments
 (0)