Skip to content

Commit 897d399

Browse files
committed
branch_worker: refactor send_email()
Change send_email() to decouple it from a Series object. send_email() will now unconditionally build and send an email message to provided recipients. The to/cc list filtering logic is moved to a new send_ci_results_email() function, which calls send_email(). Signed-off-by: Ihor Solodrai <[email protected]>
1 parent ab03ca8 commit 897d399

File tree

2 files changed

+68
-28
lines changed

2 files changed

+68
-28
lines changed

kernel_patches_daemon/branch_worker.py

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from collections import namedtuple
2222
from contextlib import contextmanager
2323
from datetime import datetime, timedelta, timezone
24-
from email.mime.application import MIMEApplication
24+
from email.message import EmailMessage
2525
from email.mime.multipart import MIMEMultipart
2626
from email.mime.text import MIMEText
2727
from enum import Enum
@@ -248,6 +248,11 @@ def bump_email_status_counters(status: Status):
248248

249249
def generate_msg_id(host: str) -> str:
250250
"""Generate an email message ID based on the provided host."""
251+
# RFC 822 style message ID to allow for easier referencing of this
252+
# message. Note that it's not entirely correct for us to refer to a host
253+
# that is not entirely under our control, but we don't want to expose our
254+
# actual host name either. Collisions of a sha256 hash are assumed to be
255+
# unlikely in many contexts, so we do the same.
251256
checksum = hashlib.sha256(str(time.time()).encode("utf-8")).hexdigest()
252257
return f"{checksum}@{host}"
253258

@@ -265,11 +270,13 @@ def email_in_submitter_allowlist(email: str, allowlist: Sequence[re.Pattern]) ->
265270

266271
def build_email(
267272
config: EmailConfig,
268-
series: Series,
273+
to_list: List[str],
274+
cc_list: List[str],
269275
subject: str,
270276
msg_id: str,
271277
body: str,
272-
boundary: str = "",
278+
boundary: Optional[str] = None,
279+
in_reply_to: Optional[str] = None,
273280
) -> Tuple[List[str], str]:
274281
"""
275282
Builds complete email (including headers) to be sent along with curl command
@@ -297,36 +304,24 @@ def build_email(
297304
"-",
298305
]
299306

300-
to_list = copy.copy(config.smtp_to)
301-
cc_list = copy.copy(config.smtp_cc)
302-
303-
if config.ignore_allowlist or email_in_submitter_allowlist(
304-
series.submitter_email, config.submitter_allowlist
305-
):
306-
to_list += [series.submitter_email]
307-
308307
for to in to_list + cc_list:
309308
args += ["--mail-rcpt", to]
310309

311310
if config.smtp_http_proxy is not None:
312311
args += ["--proxy", config.smtp_http_proxy]
313312

314313
msg = MIMEMultipart()
315-
# Add some RFC 822 style message ID to allow for easier referencing of this
316-
# message. Note that it's not entirely correct for us to refer to a host
317-
# that is not entirely under our control, but we don't want to expose our
318-
# actual host name either. Collisions of a sha256 hash are assumed to be
319-
# unlikely in many contexts, so we do the same.
320314
msg["Message-Id"] = f"<{msg_id}>"
321-
msg["In-Reply-To"] = get_ci_base(series)["msgid"]
315+
if in_reply_to is not None:
316+
msg["In-Reply-To"] = in_reply_to
322317
msg["References"] = msg["In-Reply-To"]
323318
msg["Subject"] = subject
324319
msg["From"] = config.smtp_from
325320
if to_list:
326321
msg["To"] = ",".join(to_list)
327322
if cc_list:
328323
msg["Cc"] = ",".join(cc_list)
329-
if boundary:
324+
if boundary is not None:
330325
msg.set_boundary(boundary)
331326
msg.attach(MIMEText(body, "plain"))
332327

@@ -335,14 +330,17 @@ def build_email(
335330

336331
async def send_email(
337332
config: EmailConfig,
338-
series: Series,
333+
to_list: List[str],
334+
cc_list: List[str],
339335
subject: str,
340336
body: str,
337+
in_reply_to: Optional[str] = None,
341338
):
342339
"""Send an email."""
343340
msg_id = generate_msg_id(config.smtp_host)
344-
curl_args, msg = build_email(config, series, subject, msg_id, body)
345-
341+
curl_args, msg = build_email(
342+
config, to_list, cc_list, subject, msg_id, body, in_reply_to=in_reply_to
343+
)
346344
proc = await asyncio.create_subprocess_exec(
347345
*curl_args, stdin=PIPE, stdout=PIPE, stderr=PIPE
348346
)
@@ -353,6 +351,30 @@ async def send_email(
353351
email_send_fail_counter.add(1)
354352

355353

354+
def ci_results_email_recipients(
355+
config: EmailConfig, series: Series
356+
) -> Tuple[List[str], List[str]]:
357+
to_list = copy.copy(config.smtp_to)
358+
cc_list = copy.copy(config.smtp_cc)
359+
if config.ignore_allowlist or email_in_submitter_allowlist(
360+
series.submitter_email, config.submitter_allowlist
361+
):
362+
to_list += [series.submitter_email]
363+
364+
return (to_list, cc_list)
365+
366+
367+
async def send_ci_results_email(
368+
config: EmailConfig,
369+
series: Series,
370+
subject: str,
371+
body: str,
372+
):
373+
(to_list, cc_list) = ci_results_email_recipients(config, series)
374+
in_reply_to = get_ci_base(series)["msgid"]
375+
await send_email(config, to_list, cc_list, subject, body, in_reply_to)
376+
377+
356378
def pr_has_label(pr: PullRequest, label: str) -> bool:
357379
for pr_label in pr.get_labels():
358380
if pr_label.name == label:
@@ -559,7 +581,7 @@ def __init__(
559581
)
560582

561583
self.patchwork = patchwork
562-
self.email = email
584+
self.email_config = email
563585

564586
self.log_extractor = log_extractor
565587
self.ci_repo_url = ci_repo_url
@@ -1204,8 +1226,7 @@ async def evaluate_ci_result(
12041226
self, status: Status, series: Series, pr: PullRequest, jobs: List[WorkflowJob]
12051227
) -> None:
12061228
"""Evaluate the result of a CI run and send an email as necessary."""
1207-
email = self.email
1208-
if email is None:
1229+
if self.email_config is None:
12091230
logger.info("No email configuration present; skipping sending...")
12101231
return
12111232

@@ -1245,7 +1266,7 @@ async def evaluate_ci_result(
12451266
subject = await get_ci_email_subject(series)
12461267
ctx = build_email_body_context(self.repo, pr, status, series, inline_logs)
12471268
body = furnish_ci_email_body(ctx)
1248-
await send_email(email, series, subject, body)
1269+
await send_ci_results_email(self.email_config, series, subject, body)
12491270
bump_email_status_counters(status)
12501271

12511272
def expire_branches(self) -> None:

tests/test_branch_worker.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@
2929
BRANCH_TTL,
3030
BranchWorker,
3131
build_email,
32+
ci_results_email_recipients,
3233
create_color_labels,
3334
email_in_submitter_allowlist,
3435
EmailBodyContext,
3536
furnish_ci_email_body,
37+
get_ci_base,
3638
parse_pr_ref,
3739
prs_for_the_same_series,
3840
same_series_different_target,
@@ -1316,13 +1318,17 @@ def test_email_submitter_in_allowlist(self):
13161318
]
13171319
expected_email = read_fixture("test_email_submitter_in_allowlist.golden")
13181320

1321+
(to_list, cc_list) = ci_results_email_recipients(config, series)
1322+
in_reply_to = get_ci_base(series)["msgid"]
13191323
cmd, email = build_email(
13201324
config,
1321-
series,
1325+
to_list,
1326+
cc_list,
13221327
"[PATCH bpf] my subject",
13231328
"my-id",
13241329
"body body body",
13251330
boundary=self.BOUNDARY,
1331+
in_reply_to=in_reply_to,
13261332
)
13271333
self.assertEqual(expected_cmd, cmd)
13281334
self.assertEqual(expected_email, email)
@@ -1371,8 +1377,17 @@ def test_email_submitter_not_in_allowlist(self):
13711377
]
13721378
expected_email = read_fixture("test_email_submitter_not_in_allowlist.golden")
13731379

1380+
(to_list, cc_list) = ci_results_email_recipients(config, series)
1381+
in_reply_to = get_ci_base(series)["msgid"]
13741382
cmd, email = build_email(
1375-
config, series, "my subject", "my-id", "body body", boundary=self.BOUNDARY
1383+
config,
1384+
to_list,
1385+
cc_list,
1386+
"my subject",
1387+
"my-id",
1388+
"body body",
1389+
boundary=self.BOUNDARY,
1390+
in_reply_to=in_reply_to,
13761391
)
13771392
self.assertEqual(expected_cmd, cmd)
13781393
self.assertEqual(expected_email, email)
@@ -1425,13 +1440,17 @@ def test_email_submitter_not_in_allowlist_and_allowlist_disabled(self):
14251440
"test_email_submitter_not_in_allowlist_and_allowlist_disabled.golden"
14261441
)
14271442

1443+
(to_list, cc_list) = ci_results_email_recipients(config, series)
1444+
in_reply_to = get_ci_base(series)["msgid"]
14281445
cmd, email = build_email(
14291446
config,
1430-
series,
1447+
to_list,
1448+
cc_list,
14311449
"[PATCH bpf-next] some-subject",
14321450
"my-id",
14331451
"zzzzz\nzz",
14341452
boundary=self.BOUNDARY,
1453+
in_reply_to=in_reply_to,
14351454
)
14361455
self.assertEqual(expected_cmd, cmd)
14371456
self.assertEqual(expected_email, email)

0 commit comments

Comments
 (0)