From 1e083ddfe7fc99416070a24aee5891ac37e25268 Mon Sep 17 00:00:00 2001
From: Marco Emilio Poleggi <marco-emilio.poleggi@hesge.ch>
Date: Thu, 3 Aug 2023 12:41:02 +0200
Subject: [PATCH] Added memberid filter to skip project bots

---
 group-sandbox.py | 26 ++++++++++++++++++++------
 1 file changed, 20 insertions(+), 6 deletions(-)

diff --git a/group-sandbox.py b/group-sandbox.py
index bf578f6..baa7a95 100755
--- a/group-sandbox.py
+++ b/group-sandbox.py
@@ -126,7 +126,7 @@ What do you mean? ;-)
 * API requests that return a list of items do not support pagination yet --
   max 100 items in one single page are returned. Command `userlist``is one such
   offender.
-* There's no configuration file.
+* There's no configuration file. TO-DO: use configparse for this.
 
 
 Notes
@@ -265,13 +265,14 @@ def handle_command(command, g, api_url, src_project, dst_path,
         (api_url_endpoint, data) = pre_cmnd_ref['handler'](
             pre_command, g, api_url, src_project, dst_path, users
         )
+        skip_dryrun = command_xref[pre_command]['skip_dryrun'] if command_xref[pre_command]['skip_dryrun'] else false;
         (pstatus, pdata) = send_rest_request(
             pre_cmnd_ref['method'],
             api_url_endpoint,
             json.dumps(data) if data else None,
-            dry_run
+            dry_run and not skip_dryrun
         )
-        if dry_run:
+        if dry_run and not skip_dryrun:
             logger.info("[DRY RUN] {}: Using fake results as loop data".format(pre_command))
             pdata = command_xref[pre_command]['fake_results']
         else:
@@ -541,8 +542,6 @@ def _memberid(command, g, api_url, src_project, dst_path, users=None, jdata=None
     """Handle 'userdel' command calls as per `GitLab project
     mem API <https://docs.gitlab.com/ee/api/invitations.html>`_
 
-    Call :py:func: `_userlist` to get the members' ID and
-
     :returns tuple: 2 elements
         :tuple member_id, member_name: extracted from :param jdata: or None if
                 filtered out
@@ -556,12 +555,24 @@ def _memberid(command, g, api_url, src_project, dst_path, users=None, jdata=None
     :param obj jdata: JSON data to process
 
     the remaining ones are ignored.
+
+    Filters
+    *******
+
+    So far, two filters (AND-wise) are supported:
+
+        :int access_level: must be > than given
+        :str username: regex must match username -- usually a negative
+                lookahead to skip certain members, like bots, etc.
+
     """
     cmnd_ref = command_xref[command]
     member_id = jdata['id']
     member_name = jdata['username']
     max_access_level = cmnd_ref['filter']['access_level']
-    keep = True if jdata['access_level'] <= max_access_level else False
+    uname_regex = cmnd_ref['filter']['username']
+    keep = True \
+        if ( jdata['access_level'] <= max_access_level and re.match(uname_regex, jdata['username']) ) else False
 
     logger.debug("Member #{} ({}) is filtered {}".format(
         member_id, member_name, '_in_' if keep else '_out_')
@@ -679,6 +690,7 @@ command_xref = {
         'api_rendpoint' : 'members/all?per_page=100', # Warning! No pagination
                                                       # handled.
         'force'   : False,
+        'skip_dryrun' : True, # this is always safe to run
         'fake_results' : [ # for dry-run/debugging purpose
             {
                 "id": 0,
@@ -722,8 +734,10 @@ command_xref = {
                                  # ignored. This is supposed to be found in
                                  # some input data, usually coming from a
                                  # pre-handler call
+            'username' : '(?!project.+bot)' # negative lookahead regex: skip "bot" members
         },
         'force'   : False,
+        'skip_dryrun' : True,
     },
 }
 
-- 
GitLab