From ca595a38461e7c806658986646b3c3eacbf95699 Mon Sep 17 00:00:00 2001 From: Lenka Segura Date: Thu, 11 Jul 2024 15:09:30 +0200 Subject: [PATCH] koji_retired_packages: modify koji session and make it testable locally Signed-off-by: Lenka Segura --- tests/plugins/test_koji_block_retired.py | 46 ++++++---- toddlers.toml.example | 4 + toddlers/plugins/koji_block_retired.py | 102 ++++++++++++++++++++--- 3 files changed, 124 insertions(+), 28 deletions(-) diff --git a/tests/plugins/test_koji_block_retired.py b/tests/plugins/test_koji_block_retired.py index bf9c635..888d8db 100644 --- a/tests/plugins/test_koji_block_retired.py +++ b/tests/plugins/test_koji_block_retired.py @@ -3,7 +3,7 @@ Unit tests for `toddler.plugins.koji_block_retired` """ import logging -from unittest.mock import MagicMock +from unittest import mock import koji import pytest @@ -56,7 +56,7 @@ class TestProcess: config = {"key": "value"} message = "Example message" - self.toddler_cls.process_block_retired = MagicMock() + self.toddler_cls.process_block_retired = mock.MagicMock() self.toddler_cls.process(config, message) self.toddler_cls.process_block_retired.assert_called_once_with(config, message) @@ -70,16 +70,18 @@ class TestProcessBlockRetired: def setup_method(self): """Initialize toddler.""" self.toddler_cls = koji_block_retired.KojiBlockRetired() - self.toddler_cls.koji_session = MagicMock() + self.toddler_cls.koji_session = mock.MagicMock() def test_no_dead_package_file(self, caplog): """ Assert that if no dead package was added the plugin stops. """ caplog.set_level(logging.INFO) - message = MagicMock() + message = mock.MagicMock() message.body = {"commit": {"stats": {"files": {}}}} - self.toddler_cls.process_block_retired({}, message) + config = mock.MagicMock() + config = {"profile": "stg"} + self.toddler_cls.process_block_retired(config, message) assert caplog.records[-1].message == "No dead.package in the commit, bailing" def test_dead_package_not_added(self, caplog): @@ -87,13 +89,15 @@ class TestProcessBlockRetired: Assert if in commit dead package wasn't added the plugin stops. """ caplog.set_level(logging.INFO) - message = MagicMock() + message = mock.MagicMock() message.body = { "commit": { "stats": {"files": {"dead.package": {"additions": 0, "deletions": 1}}} } } - self.toddler_cls.process_block_retired({}, message) + config = mock.MagicMock() + config = {"profile": "stg"} + self.toddler_cls.process_block_retired(config, message) assert caplog.records[-1].message == "dead.package file was not added, bailing" def test_dead_package_added_to_main_branch(self, caplog): @@ -101,7 +105,8 @@ class TestProcessBlockRetired: Assert that that main branch will be changed to rawhide tag """ caplog.set_level(logging.INFO) - message = MagicMock() + + message = mock.MagicMock() message.body = { "commit": { "stats": {"files": {"dead.package": {"additions": 1, "deletions": 0}}}, @@ -110,7 +115,9 @@ class TestProcessBlockRetired: "namespace": "example_ns", } } - self.toddler_cls.process_block_retired({}, message) + config = mock.MagicMock() + config = {"profile": "stg"} + self.toddler_cls.process_block_retired(config, message) self.toddler_cls.koji_session.packageListBlock.assert_called_once_with( taginfo="rawhide", pkginfo="example-repo", @@ -121,7 +128,7 @@ class TestProcessBlockRetired: Assert that method will process correctly with different branches. """ caplog.set_level(logging.INFO) - message = MagicMock() + message = mock.MagicMock() message.body = { "commit": { "stats": {"files": {"dead.package": {"additions": 1, "deletions": 0}}}, @@ -130,7 +137,9 @@ class TestProcessBlockRetired: "namespace": "example_ns", } } - self.toddler_cls.process_block_retired({}, message) + config = mock.MagicMock() + config = {"profile": "stg"} + self.toddler_cls.process_block_retired(config, message) self.toddler_cls.koji_session.packageListBlock.assert_called_once_with( taginfo="f38", pkginfo="example-repo", @@ -140,7 +149,7 @@ class TestProcessBlockRetired: """ Assert that Koji generic error will be handled correctly. """ - message = MagicMock() + message = mock.MagicMock() message.body = { "commit": { "stats": {"files": {"dead.package": {"additions": 1, "deletions": 0}}}, @@ -149,6 +158,13 @@ class TestProcessBlockRetired: "namespace": "example_ns", } } - self.toddler_cls.koji_session.packageListBlock.side_effect = koji.GenericError - self.toddler_cls.process_block_retired({}, message) - assert caplog.records[-1].message == "Failed to process Koji block retired" + config = mock.MagicMock() + config = {"profile": "stg"} + self.toddler_cls.koji_session.packageListBlock.side_effect = koji.GenericError( + "Failed" + ) + with pytest.raises(koji.GenericError): + self.toddler_cls.process_block_retired(config, message) + assert ( + caplog.records[-1].message == "Failed to process Koji block retired: Failed" + ) diff --git a/toddlers.toml.example b/toddlers.toml.example index 0051dc0..0888890 100644 --- a/toddlers.toml.example +++ b/toddlers.toml.example @@ -176,6 +176,10 @@ email_overrides_url = "https://pagure.io/fedora-infra/ansible/raw/master/f/roles [consumer_config.clean_retired_packages] pdc_active_branches = "https://pdc.fedoraproject.org/extras/active_branches.json" +[consumer_config.koji_block_retired] +# Use "stg" for testing, "koji" for prod +profile = "stg" + [consumer_config.packagers_without_bugzilla] ignorable_namespaces = ["tests"] diff --git a/toddlers/plugins/koji_block_retired.py b/toddlers/plugins/koji_block_retired.py index cb15b6b..df3aaef 100644 --- a/toddlers/plugins/koji_block_retired.py +++ b/toddlers/plugins/koji_block_retired.py @@ -4,17 +4,18 @@ When a branch is retired, automatically blocks the package on Koji Authors: Anton Medvedev """ +import argparse +import json import logging +import sys +from fedora_messaging_git_hook_messages import CommitV1 import koji +import toml + from ..base import ToddlerBase -# Koji hub url for creating ClientSession -KOJIHUB_URL = "https://koji.fedoraproject.org/kojihub" -# Koji hub staging url for creating ClientSession -KOJIHUB_STG_URL = "https://koji.stg.fedoraproject.org/kojihub" - _log = logging.getLogger(__name__) @@ -32,15 +33,15 @@ class KojiBlockRetired(ToddlerBase): def __init__(self): self.koji_session = None + def _create_session(self, profile): + """Makes a koji session, that handles logging in""" + koji_module = koji.get_profile_module(profile) + self.koji_session = koji_module.ClientSession(koji_module.config.server) + def accepts_topic(self, topic): """Returns a boolean whether this toddler is interested in messages from this specific topic. """ - if topic.startswith("org.fedoraproject.stg"): - self.koji_session = koji.ClientSession(KOJIHUB_STG_URL) - else: - self.koji_session = koji.ClientSession(KOJIHUB_URL) - return topic.startswith("org.fedoraproject.") and topic.endswith("git.receive") def process(self, config, message): @@ -55,6 +56,8 @@ class KojiBlockRetired(ToddlerBase): """ msg = message.body + profile = config["profile"] + # If there is no dead.package file in commit, then it can be ignored if "dead.package" not in msg["commit"]["stats"]["files"]: _log.info("No dead.package in the commit, bailing") @@ -77,8 +80,81 @@ class KojiBlockRetired(ToddlerBase): _log.info("Processing Koji block retired for %s", repo) + if self.koji_session is None: + self._create_session(profile) + try: self.koji_session.packageListBlock(taginfo=branch_name, pkginfo=repo) - except koji.GenericError: - _log.exception("Failed to process Koji block retired") - return + except (koji.GenericError, koji.ActionNotAllowed) as e: + _log.exception(f"Failed to process Koji block retired: {e}") + raise + + +def get_arguments(args): + """Load and parse the CLI arguments.""" + parser = argparse.ArgumentParser( + description="Checks that packagers have a valid bugzilla account" + ) + parser.add_argument( + "conf", + help="Configuration file", + ) + parser.add_argument( + "-m", + dest="message", + help="In case you want a certain message to be processed " + "for local testing, pass it as a file", + ) + log_level_group = parser.add_mutually_exclusive_group() + log_level_group.add_argument( + "-q", + "--quiet", + action="store_const", + dest="log_level", + const=logging.WARNING, + default=logging.INFO, + help="Be less talkative", + ) + log_level_group.add_argument( + "--debug", + action="store_const", + dest="log_level", + const=logging.DEBUG, + help="Enable debugging output", + ) + + return parser.parse_args(args) + + +def main(args): + """Schedule the first test and run the scheduler.""" + args = get_arguments(args) + logging.StreamHandler(stream=sys.stdout) + config = toml.load(args.conf) + logging.basicConfig(level=args.log_level) + msg_file = args.message + + # For local testing: In case you want to run a specific message + if msg_file: + # Either from a file + with open(msg_file, "r") as stream: + msg = json.load(stream) + commit_msg = CommitV1(body=msg["body"], headers=msg["headers"], topic=msg["topic"]) + else: + # Or edit this dict to match your needs + body = {"commit": {"branch": "rawhide", "stats": {"files": {}}}, "repo": "repo"} + commit_msg = CommitV1(body=body) + + logging.StreamHandler(stream=sys.stdout) + + KojiBlockRetired().process( + config=config.get("consumer_config", {}).get("koji_block_retired", {}), + message=commit_msg, + ) + + +if __name__ == "__main__": # pragma: no cover + try: + main(sys.argv[1:]) + except KeyboardInterrupt: + pass