diff --git a/.github/workflows/build-and-push.yml b/.github/workflows/build-and-push.yml index 9e05b7f..d5dd999 100644 --- a/.github/workflows/build-and-push.yml +++ b/.github/workflows/build-and-push.yml @@ -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 }} @@ -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 }} diff --git a/Dockerfile.daily-tests b/Dockerfile.daily-tests index b77a26e..38f81e3 100644 --- a/Dockerfile.daily-tests +++ b/Dockerfile.daily-tests @@ -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" \ diff --git a/Dockerfile.eol-checker b/Dockerfile.eol-checker index 368e298..0d05327 100644 --- a/Dockerfile.eol-checker +++ b/Dockerfile.eol-checker @@ -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." \ diff --git a/Makefile b/Makefile index cb15c41..2ec3302 100644 --- a/Makefile +++ b/Makefile @@ -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 . diff --git a/eol-checker/eol-checker b/eol-checker/eol-checker index 231cc05..fd117c6 100755 --- a/eol-checker/eol-checker +++ b/eol-checker/eol-checker @@ -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) diff --git a/eol-checker/eol_checker/checker.py b/eol-checker/eol_checker/checker.py index 03965e3..f2054fc 100755 --- a/eol-checker/eol_checker/checker.py +++ b/eol-checker/eol_checker/checker.py @@ -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 @@ -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(",") 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 = ( @@ -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: @@ -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"{jira_url}" 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"{jira_url}" if self.send_email else jira_url + msg = "Jira ticket is already filed: " + 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: """ @@ -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) 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"{jira_url}" 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"{jira_url}" 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 @@ -265,18 +265,17 @@ 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) @@ -284,7 +283,7 @@ def send_emails(self): 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: @@ -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) diff --git a/eol-checker/eol_checker/constants.py b/eol-checker/eol_checker/constants.py index d1becb1..2463949 100644 --- a/eol-checker/eol_checker/constants.py +++ b/eol-checker/eol_checker/constants.py @@ -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", @@ -40,6 +40,7 @@ "python39", "python311", "python312", + "python314", "ruby", ] JIRA_DEPRECATION_TICKET = "RHELMISC-20810" diff --git a/eol-checker/eol_checker/custom_logger.py b/eol-checker/eol_checker/custom_logger.py index 04d697e..6355f6e 100644 --- a/eol-checker/eol_checker/custom_logger.py +++ b/eol-checker/eol_checker/custom_logger.py @@ -25,6 +25,10 @@ class ColoredFormatter(logging.Formatter): + """ + Colored formatter for the logger. + """ + COLORS = { "DEBUG": Fore.LIGHTBLUE_EX, "INFO": Fore.GREEN, @@ -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) diff --git a/eol-checker/eol_checker/jira.py b/eol-checker/eol_checker/jira.py index 11f6464..aa5430a 100644 --- a/eol-checker/eol_checker/jira.py +++ b/eol-checker/eol_checker/jira.py @@ -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: diff --git a/eol-checker/eol_checker/utils.py b/eol-checker/eol_checker/utils.py index aa590c5..940a70d 100644 --- a/eol-checker/eol_checker/utils.py +++ b/eol-checker/eol_checker/utils.py @@ -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 @@ -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 @@ -81,9 +81,9 @@ 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(): @@ -91,23 +91,19 @@ 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 diff --git a/eol-checker/tests/test_checker.py b/eol-checker/tests/test_checker.py index aa8d59a..fd53518 100644 --- a/eol-checker/tests/test_checker.py +++ b/eol-checker/tests/test_checker.py @@ -32,18 +32,22 @@ def _container_struct(name, enddate): def test_init_defaults(monkeypatch): - monkeypatch.delenv("DEFAULT_EMAILS", raising=False) + monkeypatch.setenv("DEFAULT_EMAILS", "default@redhat.com") + monkeypatch.delenv("DEBUG", raising=False) + monkeypatch.delenv("SEND_EMAIL", raising=False) instance = ContainerEolChecker() assert instance.today == date.today() assert instance.send_email is False assert instance.end_line == "\n" assert instance.bold_line == "" + assert instance.default_mails == ["default@redhat.com"] assert instance.eol_images == {} assert instance.body == "" -def test_init_send_email_formatting(): +def test_init_send_email_formatting(monkeypatch): + monkeypatch.setenv("DEFAULT_EMAILS", "default@redhat.com") instance = ContainerEolChecker(send_email=True) assert instance.end_line == "
" @@ -128,22 +132,31 @@ def test_analyze_lifecycle_yaml_processes_all_lifecycles(checker_with_os_context ) -def test_get_jira_msg_when_jira_unavailable(checker): - flexmock(checker.jira_fetcher).should_receive("jira").and_return(None) - +def test_get_jira_msg_without_filed_ticket(checker): message = checker._get_jira_msg("reached EOL", "20250501") assert "reached EOL in 20250501" in message - assert "Connection to Jira not available" in message - + assert "Jira ticket is not filled" in message + assert f"{JIRA_URL}/browse/{JIRA_DEPRECATION_TICKET}" in message -def test_get_jira_msg_when_jira_available(checker): - flexmock(checker.jira_fetcher).should_receive("jira").and_return(flexmock()) - message = checker._get_jira_msg("approaching EOL", "20250601") +def test_get_jira_msg_with_filed_ticket(checker): + message = checker._get_jira_msg("approaching EOL", "20250601", jira_id="RHELMISC-999") assert "approaching EOL in 20250601" in message - assert "Jira ticket is not filled" in message + assert "Jira ticket is already filed" in message + assert f"{JIRA_URL}/browse/RHELMISC-999" in message + + +def test_get_jira_msg_html_links_when_sending_email(checker): + checker.send_email = True + checker.bold_line = "" + checker.bold_line_end = "" + + message = checker._get_jira_msg("reached EOL", "20250501") + + assert "