Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build-and-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
registry_username: ${{ secrets.QUAY_IMAGE_SCLORG_BUILDER_USERNAME }}
registry_token: ${{ secrets.QUAY_IMAGE_SCLORG_BUILDER_TOKEN }}
dockerfile: Dockerfile.daily-tests
tag: "0.10.4"
tag: "0.10.5"
image_name: "upstream-daily-tests"
quay_application_token: ${{ secrets.QUAY_IMAGE_SCLORG_UPDATE_DESC }}

Expand All @@ -31,6 +31,6 @@ jobs:
registry_username: ${{ secrets.QUAY_IMAGE_SCLORG_BUILDER_USERNAME }}
registry_token: ${{ secrets.QUAY_IMAGE_SCLORG_BUILDER_TOKEN }}
dockerfile: Dockerfile.eol-checker
tag: "0.10.4"
tag: "0.10.5"
image_name: "upstream-eol-checker"
quay_application_token: ${{ secrets.QUAY_IMAGE_SCLORG_UPDATE_DESC }}
2 changes: 1 addition & 1 deletion Dockerfile.daily-tests
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ FROM quay.io/fedora/fedora:42

ENV SHARED_DIR="/var/ci-scripts" \
VERSION="42" \
RELEASE_UPSTREAM="0.10.4" \
RELEASE_UPSTREAM="0.10.5" \
UPSTREAM_TMT_REPO="https://github.com/sclorg/sclorg-testing-farm" \
UPSTREAM_TMT_DIR="sclorg-testing-farm" \
HOME="/home/nightly" \
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.eol-checker
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FROM quay.io/fedora/fedora:42

ENV VERSION="42" \
RELEASE_UPSTREAM="0.10.4" \
RELEASE_UPSTREAM="0.10.5" \
HOME="/home/eol-checker" \
SUMMARY="EOL checker for SCL org projects" \
DESCRIPTION="This image is used to run EOL checker for SCL org projects in CI." \
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ shellcheck:
build_images: daily_tests eol_checker

daily_tests:
podman build -t quay.io/sclorg/upstream-daily-tests:0.10.4 -f Dockerfile.daily-tests .
podman build -t quay.io/sclorg/upstream-daily-tests:0.10.5 -f Dockerfile.daily-tests .

eol_checker:
podman build -t quay.io/sclorg/upstream-eol-checker:0.10.4 -f Dockerfile.eol-checker .
podman build -t quay.io/sclorg/upstream-eol-checker:0.10.5 -f Dockerfile.eol-checker .
1 change: 0 additions & 1 deletion eol-checker/eol-checker
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ def main(args):
Returns:
The exit code. 0 if successful, 1 if error.
"""
print(f"Arguments: {args}")
checker = ContainerEolChecker(debug=args.debug, send_email=args.send_email)
checker.run()
sys.exit(0)
Expand Down
78 changes: 39 additions & 39 deletions eol-checker/eol_checker/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
is_eol_version,
get_lifecycles,
load_mails_from_environment,
get_env_variable,
)
from eol_checker.constants import OS_NAMES, CONTAINER_NAMES

Expand All @@ -60,21 +59,21 @@ class ContainerEolChecker(object):

def __init__(self, debug: bool = False, send_email: bool = False):
self.today = date.today()
# Used for OpenShift CronJob
env_debug = os.getenv("DEBUG")
debug_enabled = (
debug if env_debug is None else env_debug.strip().lower() in {"1", "true", "yes", "on"}
)
self._setup_logger(debug=debug_enabled)
self.lifecycle_data: Any = None
self.eol_images: dict = {}
self.already_eol_images: dict = {}
self.approaching_eol_images: dict = {}
self.os_name: str = ""
self.default_mails: List[str] = os.getenv("DEFAULT_EMAILS", "").split(",")
self.default_mails: List[str] = os.getenv("DEFAULT_EMAILS").split(",")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Crash when DEFAULT_EMAILS is unset.

os.getenv("DEFAULT_EMAILS") returns None when the variable is missing, so .split(",") raises AttributeError and __init__ fails. The tests always set DEFAULT_EMAILS, masking this in CI, but the OpenShift CronJob will crash if it is not configured. Provide a default and drop empty entries.

🐛 Proposed fix
-        self.default_mails: List[str] = os.getenv("DEFAULT_EMAILS").split(",")
+        self.default_mails: List[str] = [
+            mail for mail in os.getenv("DEFAULT_EMAILS", "").split(",") if mail
+        ]
🤖 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` at line 73, The constructor currently
calls os.getenv("DEFAULT_EMAILS").split(",") which crashes when DEFAULT_EMAILS
is unset; update the initialization of self.default_mails in __init__ to read
the env var with a safe default (e.g. os.getenv("DEFAULT_EMAILS", "")), split on
commas, strip whitespace from each entry and filter out any empty strings so
self.default_mails is always a list (possibly empty); reference the existing
symbol self.default_mails to locate and replace the line.

self.container_to_analyze: str = ""
self.jira_fetcher = JiraFetcher()
self.eol_sme_mails = load_mails_from_environment()
# Used for OpenShift CronJob
env_debug = os.getenv("DEBUG")
debug_enabled = (
debug if env_debug is None else env_debug.strip().lower() in {"1", "true", "yes", "on"}
)
self._setup_logger(debug=debug_enabled)

env_send_email = os.getenv("SEND_EMAIL")
self.send_email = (
Expand Down Expand Up @@ -150,7 +149,7 @@ def analyze_lifecycle_yaml(self, data: Any) -> None:
for lifecycle in get_lifecycles(data):
self.check_enddate(lifecycle)

def _get_jira_msg(self, report_type: str, enddate: str) -> str:
def _get_jira_msg(self, report_type: str, enddate: str, jira_id: str = "") -> str:
"""
Generate a Jira message.
Args:
Expand All @@ -159,12 +158,15 @@ def _get_jira_msg(self, report_type: str, enddate: str) -> str:
Returns:
The Jira message.
"""
jira_msg = (
"Connection to Jira not available"
if self.jira_fetcher.jira is None
else "Jira ticket is not filled. Use Jira issue template:"
)
return self.bold_line + f"{report_type} in {enddate}" + self.bold_line_end + f". {jira_msg}"
jira_url = get_jira_ticket_url(jira_issue_id=self.jira_fetcher.jira_deprecation_ticket)
url = f"<a href='{jira_url}'>{jira_url}</a>" if self.send_email else jira_url
msg = "Jira ticket is not filled. Use this template: "
if jira_id != "":
jira_url = get_jira_ticket_url(jira_issue_id=jira_id)
url = f"<a href='{jira_url}'>{jira_url}</a>" if self.send_email else jira_url
msg = "Jira ticket is already filed: "
Comment thread
phracek marked this conversation as resolved.
jira_msg = f"{msg} {url}"
return self.bold_line + f"{report_type} in {enddate}. " + self.bold_line_end + jira_msg

def summary_for_images(self, images: dict, os_name: str, eol_type: bool = True) -> str:
"""
Expand All @@ -188,27 +190,25 @@ def summary_for_images(self, images: dict, os_name: str, eol_type: bool = True)
logger.info("Processing container: '%s' with values: '%s'", container_name, values)
stream_name = values["name"]
if self.send_email:
for mail in self.eol_sme_mails[container_name]:
mails = [
self.eol_sme_mails[group]
for group in self.eol_sme_mails.keys()
if container_name.startswith(group)
]
logger.debug("Mails: '%s' for container: '%s'", mails, container_name)
for mail in mails:
if mail and mail not in self.default_mails:
self.default_mails.append(mail)
self.default_mails.extend(mail)
Comment thread
phracek marked this conversation as resolved.
if self.jira_fetcher.jira is None:
logger.error("Connection to Jira failed")
jira_msg = self._get_jira_msg(report_type=report_type, enddate=values["enddate"])
jira_id = self.jira_fetcher.jira_deprecation_ticket
jira_url = get_jira_ticket_url(jira_issue_id=jira_id)
url = f"<a href='{jira_url}'>{jira_url}</a>" if self.send_email else jira_url
report += f"{stream_name} for {os_name} {jira_msg} {url}{self.end_line}"
report += f"{stream_name} for {os_name}.{jira_msg} {self.end_line}"
continue
jira_msg = self._get_jira_msg(report_type=report_type, enddate=values["enddate"])
jira_msg += "Jira ticket is already filed:"
jira_id = self.jira_fetcher.is_jira_filled_for_container(stream_name=stream_name)
if jira_id == "":
jira_msg = self._get_jira_msg(report_type=report_type, enddate=values["enddate"])
jira_id = self.jira_fetcher.jira_deprecation_ticket
jira_url = get_jira_ticket_url(jira_issue_id=jira_id)
url = f"<a href='{jira_url}'>{jira_url}</a>" if self.send_email else jira_url
report += f"{stream_name} for {os_name} {jira_msg} {url}{self.end_line}"
report += "\n"
jira_id = self.jira_fetcher.is_jira_filed_for_container(stream_name=stream_name)
jira_msg = self._get_jira_msg(
report_type=report_type, enddate=values["enddate"], jira_id=jira_id
)
report += f"{stream_name} for {os_name} {jira_msg} {self.end_line}\n"

return report

Expand Down Expand Up @@ -265,26 +265,25 @@ def send_emails(self):
Send emails with the container EOL information.
"""
logger.debug("Sending emails is enabled")
logger.debug(", ".join(self.default_mails))
self.smtp_server = get_env_variable("SMTP_SERVER", "smtp.redhat.com")
self.smtp_port = int(get_env_variable("SMTP_PORT", "25"))
logger.debug(self.default_mails)
self.smtp_server = os.getenv("SMTP_SERVER", "smtp.redhat.com")
self.smtp_port = int(os.getenv("SMTP_PORT", "25"))

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["To"] = ",".join(self.default_mails)
self.mime_msg["Subject"] = "Container EOL Checker Report"
logger.debug(
"Sending email with subject: 'Container EOL Checker Report' to: '%s'",
send_to,
self.default_mails,
)
logger.debug("Email body: '%s'", self.body)
logger.debug("Message: '%s'", self.mime_msg)
self.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, self.default_mails, self.mime_msg.as_string())
except smtplib.SMTPRecipientsRefused as e:
logger.error("Error sending email(SMTPRecipientsRefused): %s", e.strerror)
except smtplib.SMTPException as e:
Expand All @@ -297,12 +296,13 @@ def run(self):
"""
Run the container EOL checker.
"""
logger.debug("Variables:\n%s", vars(self))
logger.info("Running container EOL checker")
if self.jira_fetcher.jira is None:
logger.error("Connection to Jira failed")
else:
self.jira_fetcher.get_jira_deprecation_details()
self.jira_fetcher.check_if_jira_is_filled()
self.jira_fetcher.check_if_jira_is_filed()
self.analyze_containers()
self.body = self.summary_report()
logger.info(self.body)
Expand Down
3 changes: 2 additions & 1 deletion eol-checker/eol_checker/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

JIRA_URL = "https://redhat.atlassian.net"
OS_NAMES = ["RHEL8", "RHEL9", "RHEL10"]
ALLOWED_STATUSES = ["Open", "In Progress", "To Do"]
ALLOWED_STATUSES = ["New", "Open", "In Progress", "To Do"]
CONTAINER_NAMES = [
"nodejs",
"httpd",
Expand All @@ -40,6 +40,7 @@
"python39",
"python311",
"python312",
"python314",
"ruby",
]
JIRA_DEPRECATION_TICKET = "RHELMISC-20810"
12 changes: 12 additions & 0 deletions eol-checker/eol_checker/custom_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@


class ColoredFormatter(logging.Formatter):
"""
Colored formatter for the logger.
"""

COLORS = {
"DEBUG": Fore.LIGHTBLUE_EX,
"INFO": Fore.GREEN,
Expand All @@ -50,6 +54,14 @@ def format(self, record):


def setup_logger(logger_name: str = "eol_checker", level=logging.INFO):
"""
Setup the logger.
Args:
logger_name: The name of the logger.
level: The level of the logger.
Returns:
The logger.
"""
logger = logging.getLogger(logger_name)

# Check if handlers already exist (to avoid duplicate logs)
Expand Down
15 changes: 12 additions & 3 deletions eol-checker/eol_checker/jira.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,26 @@ def get_jira_deprecation_details(self):
except HTTPError as e:
logger.error("Error occurred while fetching JIRA issue: %s", e)
self.jira_details = None
logger.debug("JIRA details: '%s'", self.jira_details)

def is_jira_filled_for_container(self, stream_name: str) -> str:
def is_jira_filed_for_container(self, stream_name: str) -> str:
"""
Check if the JIRA ticket is filled for a container.
Args:
stream_name: The stream name.
Returns:
The JIRA issue ID if the JIRA ticket is filled, empty string otherwise.
"""
jira_id = ""
for issue in self.jira_deprecated_opened_issues:
logger.info("Check is stream '%s' in issue '%s'", stream_name, issue)
logger.info("Check if stream '%s' in issue '%s'", stream_name, issue)
if "summary" in issue and stream_name in issue["summary"]:
jira_id = issue["jira_issue_id"]
logger.debug("Jira is already filed for container '%s'", stream_name)
break
return jira_id

def check_if_jira_is_filled(self) -> bool:
def check_if_jira_is_filed(self) -> bool:
"""
Check if the JIRA ticket is filled.
Returns:
Expand Down
44 changes: 20 additions & 24 deletions eol-checker/eol_checker/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import logging
import os

from typing import Any, Iterable, Dict
from typing import Any, Iterable, Dict, List
from datetime import date

from eol_checker.constants import JIRA_URL
Expand Down Expand Up @@ -72,7 +72,7 @@ def get_jira_ticket_url(jira_issue_id: str) -> str:
return f"{JIRA_URL}/browse/{jira_issue_id}"


def get_env_variable(var_name: str, default_value: str = "") -> str:
def get_env_variable(var_name: str, default_value: str = "") -> List[str]:
"""
Get environment variable value or return default value if not set.
:param var_name: Name of the environment variable
Expand All @@ -81,33 +81,29 @@ def get_env_variable(var_name: str, default_value: str = "") -> str:
"""
if var_name in os.environ:
value = os.getenv(var_name, default_value)
print(f"Environment variable '{var_name}': '{value}'")
return value
return default_value
if value:
return value.split(",")
return []


def load_mails_from_environment():
"""
Load email addresses from environment variables.
"""
sclorg_mails = {}
sclorg_mails["mariadb"] = get_env_variable("DB_MAILS").split(",")
sclorg_mails["mysql"] = get_env_variable("DB_MAILS").split(",")
sclorg_mails["postgresql"] = get_env_variable("DB_MAILS").split(",")
sclorg_mails["ruby"] = get_env_variable("RUBY_MAILS").split(",")
python_mails = get_env_variable("PYTHON_MAILS").split(",")
sclorg_mails["python"] = python_mails
sclorg_mails["python36"] = python_mails
sclorg_mails["python38"] = python_mails
sclorg_mails["python39"] = python_mails
sclorg_mails["python311"] = python_mails
sclorg_mails["python312"] = python_mails
sclorg_mails["nodejs"] = get_env_variable("NODEJS_MAILS").split(",")
sclorg_mails["perl"] = get_env_variable("PERL_MAILS").split(",")
sclorg_mails["php"] = get_env_variable("PHP_MAILS").split(",")
sclorg_mails["redis"] = get_env_variable("REDIS_MAILS").split(",")
sclorg_mails["varnish"] = get_env_variable("VARNISH_MAILS").split(",")
sclorg_mails["valkey"] = get_env_variable("VALKEY_MAILS").split(",")
sclorg_mails["httpd"] = get_env_variable("HTTPD_MAILS").split(",")
sclorg_mails["nginx"] = get_env_variable("NGINX_MAILS").split(",")
db_mails = get_env_variable("DB_EMAILS")
sclorg_mails["mariadb"] = db_mails
sclorg_mails["mysql"] = db_mails
sclorg_mails["postgresql"] = db_mails
sclorg_mails["ruby"] = get_env_variable("RUBY_EMAILS")
sclorg_mails["python"] = get_env_variable("PYTHON_EMAILS")
sclorg_mails["nodejs"] = get_env_variable("NODEJS_EMAILS")
sclorg_mails["perl"] = get_env_variable("PERL_EMAILS")
sclorg_mails["php"] = get_env_variable("PHP_EMAILS")
sclorg_mails["redis"] = get_env_variable("REDIS_EMAILS")
sclorg_mails["varnish"] = get_env_variable("VARNISH_EMAILS")
sclorg_mails["valkey"] = get_env_variable("VALKEY_EMAILS")
sclorg_mails["httpd"] = get_env_variable("HTTPD_EMAILS")
sclorg_mails["nginx"] = get_env_variable("NGINX_EMAILS")
logger.debug("SCLorg mails: '%s'", sclorg_mails)
return sclorg_mails
Loading
Loading