Skip to content

Support sending emails for eol-checker#229

Merged
phracek merged 3 commits into
masterfrom
support_for_mails
May 26, 2026
Merged

Support sending emails for eol-checker#229
phracek merged 3 commits into
masterfrom
support_for_mails

Conversation

@phracek
Copy link
Copy Markdown
Member

@phracek phracek commented May 26, 2026

Support sending emails for eol-checker

Summary by CodeRabbit

  • New Features

    • Added configurable email notification support for container end-of-life reports.
    • Enhanced Jira integration for deprecation tracking and automated ticket messaging.
    • Improved end-of-life detection with categorized reporting (already EOL, approaching EOL).
    • Added support for notifying subject matter experts via email.
  • Tests

    • Expanded test coverage for email delivery and Jira integration scenarios.
  • Chores

    • Updated version to 0.10.0.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

Warning

Review limit reached

@phracek, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 49 minutes and 23 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6b124bf3-e9b6-4113-8f97-8fc5e92f274d

📥 Commits

Reviewing files that changed from the base of the PR and between 3067e33 and 22f350f.

📒 Files selected for processing (12)
  • .github/workflows/build-and-push.yml
  • .github/workflows/python-daily-tests.yml
  • .github/workflows/python-eol-checker.yml
  • .github/workflows/python-ocp-generator-tests.yml
  • Dockerfile.daily-tests
  • Makefile
  • codecov.yml
  • eol-checker/eol-checker
  • eol-checker/eol_checker/checker.py
  • eol-checker/eol_checker/utils.py
  • eol-checker/tests/test_checker.py
  • eol-checker/tests/test_jira.py
📝 Walkthrough

Walkthrough

This PR contains two independent features: a version bump of the upstream-daily-tests Docker image to 0.10.0 across build files, and a substantial refactoring of eol-checker to support configurable email sending with consolidated Jira ticket and summary reporting.

Changes

Upstream daily-tests version 0.10.0

Layer / File(s) Summary
Build pipeline version updates
.github/workflows/build-and-push.yml, Dockerfile.daily-tests, Makefile
Quay image tag and RELEASE_UPSTREAM environment variable updated from 0.9.0 to 0.10.0 across GitHub Actions workflow, container definition, and build tooling.

eol-checker email and Jira reporting refactoring

Layer / File(s) Summary
Utility functions for environment and Jira support
eol-checker/eol_checker/utils.py
New get_env_variable() and load_mails_from_environment() helpers added to read SMTP configuration and SME email lists from environment. is_eol_version() expanded to return distinct status code for previous-month end dates. os import added.
Checker imports and constructor initialization
eol-checker/eol_checker/checker.py
Imports reorganized to include email/SMTP modules. Constructor now accepts send_email parameter and initializes email state: recipient defaults, HTML formatting separators, SMTP configuration placeholders, and reusable MIME message body.
Lifecycle classification with shared container struct
eol-checker/eol_checker/checker.py
Enddate normalization added: lifecycle enddate coerced to string before datetime parsing. check_enddate() refactored to construct shared container_struct with stream name and enddate, routed into eol_images, approaching_eol_images, or already_eol_images. already_eol_images initialized per OS during container analysis.
Jira messaging and summary consolidation
eol-checker/eol_checker/checker.py
Two separate summary methods replaced with generic summary_for_images() helper. New _get_jira_msg() method composes Jira ticket URLs and messages. summary_for_images() handles EOL/approaching-EOL formatting, injects Jira details, applies HTML link formatting when email enabled, and aggregates SME email recipients. summary_report() refactored to call the unified formatter for three lifecycle states.
Email sending and run() orchestration
eol-checker/eol_checker/checker.py
New send_emails() method configures SMTP from environment and sends HTML MIME body. run() refactored to always compute body from summary_report(), log it, and conditionally invoke send_emails() based on send_email flag. Previous unconditional summary logging removed.
Comprehensive checker test updates
eol-checker/tests/test_checker.py
Fixture refactored with monkeypatch, environment-based DEFAULT_EMAILS, send_email=False, and pinned today. New _container_struct() helper added. Tests expanded to verify: initialization with email HTML formatting, environment email loading, check_enddate() classification into all three buckets with invalid-enddate skipping, Jira message composition with/without connectivity, summary_for_images() under Jira availability and filled status, SME mail injection, summary_report() category coverage, analyze_containers() YAML handling, and run() flow with Jira/email conditionals. already_eol_images assertions added throughout.
Jira test coverage for property and filled status
eol-checker/tests/test_jira.py
Jira property tests expanded to verify returns None when credentials missing or HTTP status not OK. Client-creation test updated to use monkeypatch for environment control. Deprecation-details tests refactored to inject mock Jira client before method call. New test added for fields-missing scenario. New check_if_jira_is_filled() test verifies disallowed inward-status issues are ignored.

Sequence Diagram(s)

sequenceDiagram
  participant ContainerEolChecker
  participant JiraFetcher
  participant summary_for_images as summary_for_images
  participant SMTP_Server as SMTP Server
  
  ContainerEolChecker->>ContainerEolChecker: check_enddate(lifecycle)
  ContainerEolChecker->>ContainerEolChecker: summary_report()
  
  loop for each OS and EOL state
    ContainerEolChecker->>summary_for_images: images dict, os_name, eol_type
    summary_for_images->>JiraFetcher: get Jira ticket URL/message
    JiraFetcher-->>summary_for_images: ticket details or None
    summary_for_images->>summary_for_images: format HTML with Jira/SME emails
    summary_for_images-->>ContainerEolChecker: formatted summary text
  end
  
  ContainerEolChecker->>ContainerEolChecker: body = summary_report()
  ContainerEolChecker->>ContainerEolChecker: log body
  
  alt send_email enabled
    ContainerEolChecker->>ContainerEolChecker: send_emails()
    ContainerEolChecker->>SMTP_Server: configure SMTP and send HTML body
    SMTP_Server-->>ContainerEolChecker: success or exception
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • sclorg/ci-scripts#228: Directly overlapping refactoring of ContainerEolChecker lifecycle enddate categorization and summary/reporting methods at the same class level.
  • sclorg/ci-scripts#226: Bumps upstream-daily-tests Docker image version in the same build pipeline files (.github/workflows, Dockerfile, Makefile).
  • sclorg/ci-scripts#225: Updates upstream-daily-tests Quay image version/tag in the same build pipeline files.

Suggested labels

ready for review

Poem

A rabbit bounds through version bumps with glee,
While checkers now send emails with Jira's key,
The lifecycle flows from near to past,
With SMTP springs and summaries amassed,
Tests verify each path runs true,
And 0.10.0 hops into view! 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.07% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective of the PR, which adds email sending capability to the eol-checker component across multiple files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch support_for_mails

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Signed-off-by: Petr "Stone" Hracek <phracek@redhat.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (1)
eol-checker/eol_checker/utils.py (1)

89-107: 💤 Low value

Empty environment variables produce [""] instead of [].

When an environment variable is not set, get_env_variable returns "", and "".split(",") produces [""] (a list containing one empty string), not an empty list. While the downstream code in checker.py filters out empty strings with if mail and mail not in self.default_mails, this is subtle and could cause issues if other code iterates over these lists without checking.

Also, consider adding a return type annotation for consistency.

Proposed fix
-def load_mails_from_environment():
+def load_mails_from_environment() -> Dict[str, List[str]]:
     """
     Load email addresses from environment variables.
+    Returns:
+        A dictionary mapping technology names to lists of email addresses.
     """
+    def split_emails(var_name: str) -> List[str]:
+        value = get_env_variable(var_name)
+        return [email.strip() for email in value.split(",") if email.strip()]
+
     sclorg_mails = {}
-    sclorg_mails["mariadb"] = get_env_variable("DB_SME").split(",")
+    sclorg_mails["mariadb"] = split_emails("DB_SME")
     # ... apply to other entries
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@eol-checker/eol_checker/utils.py` around lines 89 - 107,
load_mails_from_environment currently calls get_env_variable(...).split(",")
which yields [""] for empty env vars; change load_mails_from_environment to
normalize each SME value by splitting and then filtering out empty strings
(e.g., ignore items that are empty or whitespace) so you return [] for unset
variables, and add a return type annotation (-> Dict[str, List[str]]) to the
function signature for consistency; reference load_mails_from_environment and
get_env_variable and ensure compatibility with checker.py's default_mails
handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@eol-checker/eol_checker/checker.py`:
- Around line 298-307: The try/finally block can call smtp.close() when smtp was
never assigned and also uses a nonexistent .strerror on SMTPRecipientsRefused;
initialize smtp = None before the try, replace the SMTP(...) call and sendmail
in the try as-is, and in the finally only call smtp.close() if smtp is not None;
also change the first except to log e.recipients (or str(e)) instead of
e.strerror (reference symbols: SMTP(), smtp variable, smtp.close(), except
smtplib.SMTPRecipientsRefused).
- Line 284: The current assignment self.send_email =
bool(get_env_variable("SEND_EMAIL", "False")) is incorrect because bool("False")
is True; change it to evaluate the string value explicitly (e.g., call
get_env_variable("SEND_EMAIL", "False").strip().lower() and set self.send_email
= value in ("true","1","yes","y") or use a safe converter like
distutils.util.strtobool to convert the returned string into a real boolean) so
that the SEND_EMAIL environment variable correctly enables/disables email
sending.
- Around line 77-78: The MIMEMultipart instance (self.mime_msg) is created in
__init__ and then mutated in send_emails(), which causes duplicated headers and
accumulating attachments on repeated calls; move creation of the MIMEMultipart
object inside send_emails() (create a fresh local mime_msg each time), attach
the MIMEText body/new parts to that local mime_msg, and use it for sending so
self.mime_msg is not reused across invocations (adjust references in
send_emails() from self.mime_msg to the new local mime_msg and keep self.body
handling unchanged or make body local if appropriate).
- Around line 201-220: The code appends "Jira ticket is already filed:" to
jira_msg before checking jira_id, causing contradictory output when a ticket is
present; fix by moving that append and the report construction into the branch
that handles jira_id != "" and only build the filled-ticket message (use
self.jira_fetcher.is_jira_filled_for_container to get jira_id, then set jira_msg
= self._get_jira_msg(...)+ "Jira ticket is already filed:", compute jira_url via
get_jira_ticket_url(jira_issue_id=jira_id), format url using self.send_email,
append to report and return/continue), and keep the existing branch for jira_id
== "" to use self.jira_fetcher.jira_deprecation_ticket as before; update usages
of jira_msg, get_jira_ticket_url, jira_deprecation_ticket, send_email, and
end_line accordingly.

In `@eol-checker/eol_checker/utils.py`:
- Around line 82-85: The unconditional print of environment variables in the
block checking os.environ (the print(f"Environment variable '{var_name}':
'{value}'") call in the get/default logic) risks leaking secrets; replace that
print with a logger.debug call (e.g., use module logger =
logging.getLogger(__name__) if not present) or remove the output altogether so
the function still returns value but no sensitive data is written to stdout;
ensure you import/configure logging if you add logger.debug and retain the same
control flow around var_name, os.getenv, value and return.
- Around line 44-48: The month comparison logic using arithmetic on today.month
is broken at year boundaries; change the check that currently tests
"enddate.year == today.year and enddate.month == today.month + 1" and the
symmetric "- 1" case to use proper date arithmetic (e.g., compute a month-offset
date from today or compare year/month pairs after normalizing months by rolling
into next/previous year) so January of next year and December of previous year
are correctly detected when comparing enddate to today (use the existing
variables enddate and today and preserve the same return values 2 and 3); also
update the function docstring (the lines around the current docstring at lines
~40-41) to document the new return code 3.

In `@eol-checker/tests/test_jira.py`:
- Around line 54-61: The test uses a hardcoded password literal in the Jira
constructor expectation which triggers lint rule S106; update
test_jira_property_creates_client_once to store the password in a variable
(e.g., pwd = "secret"), use monkeypatch.setenv("JIRA_PASSWORD", pwd) and then
expect jira_module.Jira to be called with password=pwd instead of the literal,
keeping the rest of the flexmock expectation (url=fetcher.jira_url,
username="user@redhat.com") and the mock_client return intact.

---

Nitpick comments:
In `@eol-checker/eol_checker/utils.py`:
- Around line 89-107: load_mails_from_environment currently calls
get_env_variable(...).split(",") which yields [""] for empty env vars; change
load_mails_from_environment to normalize each SME value by splitting and then
filtering out empty strings (e.g., ignore items that are empty or whitespace) so
you return [] for unset variables, and add a return type annotation (->
Dict[str, List[str]]) to the function signature for consistency; reference
load_mails_from_environment and get_env_variable and ensure compatibility with
checker.py's default_mails handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4b1264eb-fd5c-4f5a-b8ef-10ca28e61098

📥 Commits

Reviewing files that changed from the base of the PR and between ee7cd89 and 3067e33.

📒 Files selected for processing (7)
  • .github/workflows/build-and-push.yml
  • Dockerfile.daily-tests
  • Makefile
  • eol-checker/eol_checker/checker.py
  • eol-checker/eol_checker/utils.py
  • eol-checker/tests/test_checker.py
  • eol-checker/tests/test_jira.py

Comment on lines +77 to +78
self.mime_msg = MIMEMultipart()
self.body = ""
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

MIMEMultipart instance reuse may cause issues on repeated sends.

self.mime_msg is initialized once in __init__ but modified in send_emails(). If send_emails() is called multiple times (or if the checker instance is reused), headers like From, To, and Subject will be duplicated, and multiple MIMEText attachments will accumulate. Consider creating the MIMEMultipart instance inside send_emails() instead.

Proposed fix
 def __init__(self, send_email: bool = False):
     # ... other initialization ...
-    self.mime_msg = MIMEMultipart()
     self.body = ""

 def send_emails(self):
     # ... setup code ...
+    mime_msg = MIMEMultipart()
     send_from = "phracek@redhat.com"
     send_to = self.default_mails
-    self.mime_msg["From"] = send_from
-    self.mime_msg["To"] = ", ".join(send_to)
-    self.mime_msg["Subject"] = "Container EOL Checker Report"
+    mime_msg["From"] = send_from
+    mime_msg["To"] = ", ".join(send_to)
+    mime_msg["Subject"] = "Container EOL Checker Report"
     # ... logging ...
-    self.mime_msg.attach(MIMEText(self.body, "html"))
+    mime_msg.attach(MIMEText(self.body, "html"))
     try:
         smtp = SMTP(self.smtp_server, int(self.smtp_port))
         smtp.set_debuglevel(5)
-        smtp.sendmail(send_from, send_to, self.mime_msg.as_string())
+        smtp.sendmail(send_from, send_to, mime_msg.as_string())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@eol-checker/eol_checker/checker.py` around lines 77 - 78, The MIMEMultipart
instance (self.mime_msg) is created in __init__ and then mutated in
send_emails(), which causes duplicated headers and accumulating attachments on
repeated calls; move creation of the MIMEMultipart object inside send_emails()
(create a fresh local mime_msg each time), attach the MIMEText body/new parts to
that local mime_msg, and use it for sending so self.mime_msg is not reused
across invocations (adjust references in send_emails() from self.mime_msg to the
new local mime_msg and keep self.body handling unchanged or make body local if
appropriate).

Comment thread eol-checker/eol_checker/checker.py
Comment thread eol-checker/eol_checker/checker.py
Comment thread eol-checker/eol_checker/checker.py
Comment thread eol-checker/eol_checker/utils.py
Comment thread eol-checker/eol_checker/utils.py
Comment thread eol-checker/tests/test_jira.py
Signed-off-by: Petr "Stone" Hracek <phracek@redhat.com>
@phracek phracek force-pushed the support_for_mails branch from 3067e33 to 724894d Compare May 26, 2026 12:44
Signed-off-by: Petr "Stone" Hracek <phracek@redhat.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented May 26, 2026

Codecov Report

❌ Patch coverage is 96.80000% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.31%. Comparing base (ee7cd89) to head (22f350f).

Files with missing lines Patch % Lines
eol-checker/eol_checker/utils.py 88.88% 3 Missing ⚠️
eol-checker/eol_checker/checker.py 98.97% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #229      +/-   ##
==========================================
+ Coverage   50.69%   60.31%   +9.61%     
==========================================
  Files           7       13       +6     
  Lines        1006     1333     +327     
==========================================
+ Hits          510      804     +294     
- Misses        496      529      +33     
Flag Coverage Δ
daily-tests-unit 44.26% <ø> (ø)
eol-checker-unit 89.90% <96.80%> (?)
ocp-stream-generator-unit 84.47% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
eol-checker/eol_checker/checker.py 99.40% <98.97%> (ø)
eol-checker/eol_checker/utils.py 92.85% <88.88%> (ø)

... and 4 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@phracek phracek merged commit f4a239e into master May 26, 2026
17 of 27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant