#128 Add check_commit_rights plugin
Merged 2 years ago by zlopez. Opened 2 years ago by mattia.
fedora-infra/ mattia/toddlers packager_commit_rights  into  main

@@ -0,0 +1,324 @@ 

+ from unittest.mock import Mock, patch

+ 

+ import fedora_messaging.api

+ import pytest

+ 

+ import toddlers.plugins.check_commit_rights

+ 

+ 

+ class TestCheckCommitRightsToddler:

+     toddler_cls = toddlers.plugins.check_commit_rights.CheckCommitRights

+ 

+     def test_accepts_topic_invalid(self, toddler):

+         assert toddler.accepts_topic("foo.bar") is False

+ 

+     @pytest.mark.parametrize(

+         "topic",

+         [

+             "org.fedoraproject.*.toddlers.trigger.check_commit_rights",

+             "org.fedoraproject.prod.toddlers.trigger.check_commit_rights",

+             "org.fedoraproject.stg.toddlers.trigger.check_commit_rights",

+         ],

+     )

+     def test_accepts_topic_valid(self, topic, toddler):

+         assert toddler.accepts_topic(topic)

+ 

+     def test_process_config_missing_exclude_users(self, toddler):

+         msg = fedora_messaging.api.Message()

+         msg.id = 123

+         msg.topic = "org.fedoraproject.prod.toddlers.trigger.check_commit_rights"

+         msg.body = {"foo": "bar"}

+ 

+         with pytest.raises(

+             Exception,

+             match=r"Invalid toddler configuration, no `exclude_users` defined",

+         ):

+             assert toddler.process(config={}, message=msg) is None

+ 

+     def test_process_config_missing_notify_emails(self, toddler):

+         msg = fedora_messaging.api.Message()

+         msg.id = 123

+         msg.topic = "org.fedoraproject.prod.toddlers.trigger.check_commit_rights"

+         msg.body = {"foo": "bar"}

+ 

+         with pytest.raises(

+             Exception,

+             match=r"Invalid toddler configuration, no `notify_emails` defined",

+         ):

+             assert toddler.process(config={"exclude_users": []}, message=msg) is None

+ 

+     @patch("toddlers.utils.fedora_account.set_fasjson", new=Mock(return_value=True))

+     @patch("toddlers.utils.fedora_account.get_group_member")

+     def test_process_no_fas(self, get_group_member, toddler):

+         get_group_member.return_value = []

+ 

+         msg = fedora_messaging.api.Message()

+         msg.id = 123

+         msg.topic = "org.fedoraproject.prod.toddlers.trigger.check_commit_rights"

+         msg.body = {"foo": "bar"}

+ 

+         config = {

+             "exclude_users": "[]",

+             "notify_emails": "[]",

+         }

+ 

+         with pytest.raises(

+             Exception,

+             match=r"Something wrong occurred, I found no packagers. Exiting.",

+         ):

+             toddler.process(config=config, message=msg, send_email=False)

+ 

+     @patch("toddlers.utils.fedora_account.set_fasjson", new=Mock(return_value=True))

+     @patch("toddlers.plugins.check_commit_rights._log.info")

+     @patch("toddlers.utils.notify.send_email")

+     @patch("toddlers.utils.fedora_account.get_group_member")

+     def test_process_all_right(self, get_group_member, send_email, info, toddler):

+         req_json = {

+             "pagination": {

+                 "page": 1,

+                 "pages": 1,

+                 "per_page": 1,

+             },

+             "projects": [

+                 {

+                     "access_users": {

+                         "admin": ["admin_user", "packager_user"],

+                         "collaborator": ["collaborator_user"],

+                         "commit": ["commit_user"],

+                         "owner": ["owner_user"],

+                     },

+                     "fullname": "rpms/project_one",

+                 }

+             ],

+         }

+ 

+         req = Mock()

+         req.ok = True

+         req.json.return_value = req_json

+         toddler.requests_session.get.return_value = req

+ 

+         get_group_member.return_value = ["packager_user"]

+ 

+         msg = fedora_messaging.api.Message()

+         msg.id = 123

+         msg.topic = "org.fedoraproject.prod.toddlers.trigger.check_commit_rights"

+         msg.body = {"foo": "bar"}

+ 

+         config = {

+             "exclude_users": "[]",

+             "notify_emails": "[]",

+             "admin_email": "foo@bar",

+             "mail_server": "127.0.0.1",

+         }

+ 

+         assert toddler.process(config=config, message=msg) is None

+         info.assert_any_call(

+             "### Found 4 users with commit privileges but not in packager group ###"

+         )

+         info.assert_any_call("admin_user")

+         info.assert_any_call("collaborator_user")

+         info.assert_any_call("commit_user")

+         info.assert_any_call("owner_user")

+         with pytest.raises(AssertionError):

+             info.assert_any_call("packager_user")

+         send_email.assert_called()

+ 

+     @patch("toddlers.utils.fedora_account.set_fasjson", new=Mock(return_value=True))

+     @patch("toddlers.plugins.check_commit_rights._log.info")

+     @patch("toddlers.utils.notify.send_email")

+     @patch("toddlers.utils.fedora_account.get_group_member")

+     def test_process_all_right_multiple_pages(

+         self, get_group_member, send_email, info, toddler

+     ):

+         req_json = [

+             {

+                 "pagination": {

+                     "page": 1,

+                     "pages": 2,

+                     "per_page": 1,

+                 },

+                 "projects": [

+                     {

+                         "access_users": {

+                             "admin": ["admin_user", "packager_user"],

+                             "collaborator": [],

+                             "commit": [],

+                             "owner": [],

+                         },

+                         "fullname": "rpms/project_one",

+                     }

+                 ],

+             },

+             {

+                 "pagination": {

+                     "page": 2,

+                     "pages": 2,

+                     "per_page": 1,

+                 },

+                 "projects": [

+                     {

+                         "access_users": {

+                             "admin": [],

+                             "collaborator": [],

+                             "commit": [],

+                             "owner": ["owner_user"],

+                         },

+                         "fullname": "rpms/project_two",

+                     }

+                 ],

+             },

+         ]

+ 

+         req = Mock()

+         req.ok = True

+         req.json.side_effect = req_json

+         toddler.requests_session.get.return_value = req

+ 

+         get_group_member.return_value = ["packager_user"]

+ 

+         msg = fedora_messaging.api.Message()

+         msg.id = 123

+         msg.topic = "org.fedoraproject.prod.toddlers.trigger.check_commit_rights"

+         msg.body = {"foo": "bar"}

+ 

+         config = {

+             "exclude_users": "[]",

+             "notify_emails": "[]",

+             "admin_email": "foo@bar",

+             "mail_server": "127.0.0.1",

+         }

+ 

+         assert toddler.process(config=config, message=msg) is None

+         info.assert_any_call(

+             "### Found 2 users with commit privileges but not in packager group ###"

+         )

+         info.assert_any_call("admin_user")

+         info.assert_any_call("owner_user")

+         with pytest.raises(AssertionError):

+             info.assert_any_call("packager_user")

+         send_email.assert_called()

+ 

+     @patch("toddlers.utils.fedora_account.set_fasjson", new=Mock(return_value=True))

+     @patch("toddlers.plugins.check_commit_rights._log.info")

+     @patch("toddlers.utils.notify.send_email")

+     @patch("toddlers.utils.fedora_account.get_group_member")

+     def test_process_exclude_user(self, get_group_member, send_email, info, toddler):

+         req_json = {

+             "pagination": {

+                 "page": 1,

+                 "pages": 1,

+                 "per_page": 1,

+             },

+             "projects": [

+                 {

+                     "access_users": {

+                         "admin": ["admin_user", "packager_user"],

+                         "collaborator": ["collaborator_user"],

+                         "commit": ["commit_user"],

+                         "owner": ["owner_user"],

+                     },

+                     "fullname": "rpms/project_one",

+                 }

+             ],

+         }

+ 

+         req = Mock()

+         req.ok = True

+         req.json.return_value = req_json

+         toddler.requests_session.get.return_value = req

+ 

+         get_group_member.return_value = ["packager_user"]

+ 

+         msg = fedora_messaging.api.Message()

+         msg.id = 123

+         msg.topic = "org.fedoraproject.prod.toddlers.trigger.check_commit_rights"

+         msg.body = {"foo": "bar"}

+ 

+         config = {

+             "exclude_users": "['admin_user']",

+             "notify_emails": "[]",

+             "admin_email": "foo@bar",

+             "mail_server": "127.0.0.1",

+         }

+ 

+         assert toddler.process(config=config, message=msg) is None

+         info.assert_any_call(

+             "### Found 3 users with commit privileges but not in packager group ###"

+         )

+         info.assert_any_call("collaborator_user")

+         info.assert_any_call("commit_user")

+         info.assert_any_call("owner_user")

+         with pytest.raises(AssertionError):

+             info.assert_any_call("admin_user")

+         send_email.assert_called()

+ 

+     @patch("toddlers.utils.fedora_account.set_fasjson", new=Mock(return_value=True))

+     @patch("toddlers.plugins.check_commit_rights._log.info")

+     @patch("toddlers.utils.notify.send_email")

+     @patch("toddlers.utils.fedora_account.get_group_member")

+     def test_process_no_result(self, get_group_member, send_email, info, toddler):

+         req_json = {

+             "pagination": {

+                 "page": 1,

+                 "pages": 1,

+                 "per_page": 1,

+             },

+             "projects": [

+                 {

+                     "access_users": {

+                         "admin": ["packager_user"],

+                         "collaborator": [],

+                         "commit": [],

+                         "owner": ["packager_user"],

+                     },

+                     "fullname": "rpms/project_one",

+                 }

+             ],

+         }

+ 

+         req = Mock()

+         req.ok = True

+         req.json.return_value = req_json

+         toddler.requests_session.get.return_value = req

+ 

+         get_group_member.return_value = ["packager_user"]

+ 

+         msg = fedora_messaging.api.Message()

+         msg.id = 123

+         msg.topic = "org.fedoraproject.prod.toddlers.trigger.check_commit_rights"

+         msg.body = {"foo": "bar"}

+ 

+         config = {

+             "exclude_users": "[]",

+             "notify_emails": "[]",

+         }

+ 

+         assert toddler.process(config=config, message=msg) is None

+         info.assert_any_call(

+             "### Found 0 users with commit privileges but not in packager group ###"

+         )

+         send_email.assert_not_called()

+ 

+     def test_main_no_args(self, capsys):

+         with pytest.raises(SystemExit):

+             toddlers.plugins.check_commit_rights.main([])

+ 

+         out, err = capsys.readouterr()

+         assert out == ""

+         # Expecting something along these lines, but don't make the test too tight:

+         #

+         # usage: pytest [-h] [--dry-run] [-q | --debug] conf [username]

+         # pytest: error: the following arguments are required: conf

+         assert err.startswith("usage:")

+         assert "error: the following arguments are required:" in err

+ 

+     @patch("toml.load", new=Mock(return_value={}))

+     def test_main_no_exclude_users(self, capsys):

+         with pytest.raises(

+             Exception,

+             match=r"Invalid toddler configuration, no `exclude_users` defined",

+         ):

+             toddlers.plugins.check_commit_rights.main(["test.cfg"])

+         out, err = capsys.readouterr()

+         assert out == ""

+         assert err == ""

file modified
+7
@@ -335,3 +335,10 @@ 

  [log_config.root]

  level = "ERROR"

  handlers = ["console"]

+ 

+ # Configuration section for check_commit_rights

+ [consumer_config.check_commit_rights]

+ exclude_users = []

+ notify_emails = [

+     "root@localhost.localdomain",

+ ]

@@ -0,0 +1,204 @@ 

+ """

+ This script takes as input the fedmsg messages published under the topic

+ ``toddlers.trigger.check_commit_rights`` and searches for any user not

+ member of the packager group which still have commit rights.

+ 

+ Authors:    Mattia Verga <mattia.verga@proton.me>

+ 

+ """

+ 

+ import argparse

+ import logging

+ import sys

+ 

+ import toml

+ 

+ from toddlers.base import ToddlerBase

+ from toddlers.utils import fedora_account

+ from toddlers.utils import notify

+ from toddlers.utils.misc import merge_dicts

+ from toddlers.utils.requests import make_session

+ 

+ 

+ _log = logging.getLogger(__name__)

+ 

+ 

+ class CheckCommitRights(ToddlerBase):

+     """Listens to messages sent by playtime (which lives in toddlers) and searches

+     for any user not member of the packager group which still have commit rights.

+     """

+ 

+     name = "check_commit_rights"

+ 

+     amqp_topics = [

+         "org.fedoraproject.*.toddlers.trigger.check_commit_rights",

+     ]

+ 

+     def __init__(self):

+         self.requests_session = make_session()

+ 

+     def accepts_topic(self, topic):

+         """Returns a boolean whether this toddler is interested in messages

+         from this specific topic.

+         """

+         return topic.startswith("org.fedoraproject.") and topic.endswith(

+             "toddlers.trigger.check_commit_rights"

+         )

+ 

+     def process(self, config, message, send_email=True):

+         """Looks for users with commit rights not in packager group."""

+         exclude_users = config.get("exclude_users", None)

+         if exclude_users is None:

+             raise Exception("Invalid toddler configuration, no `exclude_users` defined")

+ 

+         notify_emails = config.get("notify_emails")

+         if not notify_emails and send_email:

+             raise Exception("Invalid toddler configuration, no `notify_emails` defined")

+ 

+         _log.info("### Setting up connection to FAS ###")

+         fedora_account.set_fasjson(config)

+ 

+         _log.info("### Getting packagers list ###")

+         packagers_list = fedora_account.get_group_member("packager")

+         _log.info(f"Found {len(packagers_list)} packagers")

+         if not packagers_list:

+             raise Exception("Something wrong occurred, I found no packagers. Exiting.")

+ 

+         _log.info("### Looking for users with commit rights ###")

+         page = 1

+         proj_num = 0

+         not_packagers = []

+         while True:

+             req = self.requests_session.get(

+                 "https://src.fedoraproject.org/api/0/projects?fork=false&namespace=rpms"

+                 f"&per_page=100&page={page}"

+             )

+             data = req.json()

+             projects = data.get("projects", [])

+             total_pages = data.get("pagination", dict()).get("pages", 1)

+             for proj in projects:

+                 proj_num += 1

+                 users = proj.get("access_users", dict())

+                 for user in (

+                     users.get("owner")

+                     + users.get("admin")

+                     + users.get("collaborator")

+                     + users.get("commit")

+                 ):

+                     if user in exclude_users:

+                         continue

+                     if user not in packagers_list:

+                         not_packagers.append(user)

+             _log.info(f"Done {page} pages out of {total_pages}.")

+             if page >= total_pages:

+                 break

+             page += 1

+ 

+         not_packagers_set = sorted(set(not_packagers))

+         _log.info(

+             f"### Found {len(not_packagers_set)} users with "

+             "commit privileges but not in packager group ###"

+         )

+         for u in not_packagers_set:

+             _log.info(u)

+ 

+         if len(not_packagers_set) and send_email:

+             _log.info("### Sending email to admins ###")

+             users_list = "\n".join([f"- {username}" for username in not_packagers_set])

+             message = f"""Dear Admin,

+ 

+ The periodic check on user commit rights on src.fp.o has identified {len(not_packagers_set)}

+ users which aren't member of the packager group. The list of them is here:

+ 

+ {users_list}

+ 

+ Please check is these rights are correct, they can be a leftover from when the users

+ were removed, or removed themselves, from the packager group.

+ 

+ Have a wonderful day and see you (maybe?) at the next run!

+ 

+ """

+             notify.send_email(

+                 to_addresses=config["notify_emails"],

+                 from_address=config["admin_email"],

+                 subject="Ex-packagers still having commit rights",

+                 content=message,

+                 mail_server=config["mail_server"],

+             )

+ 

+ 

+ # The following code allows to run this script stand-alone if needed.

+ 

+ 

+ def setup_logging(log_level: int):

+     handlers = []

+ 

+     _log.setLevel(log_level)

+     # We want all messages logged at level INFO or lower to be printed to stdout

+     info_handler = logging.StreamHandler(stream=sys.stdout)

+     handlers.append(info_handler)

+ 

+     if log_level == logging.INFO:

+         # In normal operation, don't decorate messages

+         for handler in handlers:

+             handler.setFormatter(logging.Formatter("%(message)s"))

+ 

+     logging.basicConfig(level=log_level, handlers=handlers)

+ 

+ 

+ def get_arguments(args):

+     """Load and parse the CLI arguments."""

+     parser = argparse.ArgumentParser(

+         description="Looks for users with commit rights not in packager group"

+     )

+     parser.add_argument(

+         "conf",

+         help="Configuration 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)

+     setup_logging(log_level=args.log_level)

+ 

+     base_config = toml.load(args.conf)

+ 

+     default_config = base_config.get("consumer_config", {}).get("default", {})

+     private_config = base_config.get("consumer_config", {}).get(

+         "check_commit_rights", {}

+     )

+     config = merge_dicts(default_config, private_config)

+ 

+     CheckCommitRights().process(

+         config=config,

+         message={},

+         send_email=False,

+     )

+ 

+ 

+ if __name__ == "__main__":  # pragma: no cover

+     try:

+         main(sys.argv[1:])

+     except KeyboardInterrupt:

+         pass

Following the discussion [¹] on devel mailing list, I wrote this toddler that searches for users which don't belong to the packager group, but have some sort of commit rights on src.fp.o projects.

It can be scheduled to run on regular bases and it will notify some email addresses of the results (if any).

Signed-off-by: Mattia Verga mattia.verga@proton.me

[¹] https://lists.fedoraproject.org/archives/list/devel@lists.fedoraproject.org/thread/WT3UVVVPPT5NUJAC3TYT4MQMSSL3DNKT/

Build failed. More information on how to proceed and troubleshoot errors available at https://fedoraproject.org/wiki/Zuul-based-ci

rebased onto d88033ccecdb258dd4bfc55986e6ffa5b3ce6367

2 years ago

Build succeeded.

rebased onto f29235216acd4085107ee0a3b46ccd3a84200b06

2 years ago

Build failed. More information on how to proceed and troubleshoot errors available at https://fedoraproject.org/wiki/Zuul-based-ci
https://fedora.softwarefactory-project.io/zuul/buildset/703eb669105c401fb4ac8eca3314f79e

rebased onto 0255fb2168a9664eaf4dcb596a7532e77f805361

2 years ago

Build failed. More information on how to proceed and troubleshoot errors available at https://fedoraproject.org/wiki/Zuul-based-ci
https://fedora.softwarefactory-project.io/zuul/buildset/2667e3f06b0e47eeb67ea477199c6676

rebased onto 2000abfeecd76e6e5fea8afde8ae72c7b4a0bcb6

2 years ago

Build failed. More information on how to proceed and troubleshoot errors available at https://fedoraproject.org/wiki/Zuul-based-ci
https://fedora.softwarefactory-project.io/zuul/buildset/66cc3daa65ef49d0823a454a5fc23071

@mattia Could you fix the formatting error?

rebased onto a71a02273af6c08eabe477eb64df51b7d768c919

2 years ago

@mattia Could you fix the formatting error?

I'll look ASAP. I've just rebased to trigger tests as the last results are no more available.

Build failed. More information on how to proceed and troubleshoot errors available at https://fedoraproject.org/wiki/Zuul-based-ci
https://fedora.softwarefactory-project.io/zuul/buildset/22e6ada49beb46d794e6e679d8eefe03

rebased onto 621c31e

2 years ago

Build succeeded.
https://fedora.softwarefactory-project.io/zuul/buildset/242fa122510043fd9693f218fbd90e0a

Let me merge this and deploy it on staging. You will need to change the toddlers role in ansible to configure it.

Pull-Request has been merged by zlopez

2 years ago

The new toddlers are now deployed on staging. Feel free to test it there.