[inactive-gitlab-users] Import the main script from sysadmin-bin



commit 16bcad259ec77699b299ef487b4ae7d75e0179e7
Author: Bartłomiej Piotrowski <bpiotrowski gnome org>
Date:   Wed Dec 8 21:29:51 2021 +0100

    Import the main script from sysadmin-bin

 Dockerfile               |   6 +-
 inactive-gitlab-users.py | 292 +++++++++++++++++++++++++++++++++++++++++++++++
 inactive-gitlab-users.sh |  11 +-
 3 files changed, 299 insertions(+), 10 deletions(-)
---
diff --git a/Dockerfile b/Dockerfile
index 158e794..1712cd4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,9 +3,7 @@ FROM python:3
 COPY requirements.txt ./
 RUN pip install --no-cache-dir -r requirements.txt
 
-RUN mkdir /usr/local/sysadmin-bin && \ 
-    git clone https://gitlab.gnome.org/Infrastructure/sysadmin-bin.git /usr/local/sysadmin-bin
-
-COPY inactive-gitlab-users.sh /usr/local/bin
+ADD inactive-gitlab-users.py /usr/local/bin/inactive-gitlab-users.py
+ADD inactive-gitlab-users.sh /usr/local/bin/inactive-gitlab-users.sh
 
 ENTRYPOINT ["/usr/local/bin/inactive-gitlab-users.sh"]
diff --git a/inactive-gitlab-users.py b/inactive-gitlab-users.py
new file mode 100644
index 0000000..f2e005c
--- /dev/null
+++ b/inactive-gitlab-users.py
@@ -0,0 +1,292 @@
+#!/usr/bin/python
+
+from __future__ import print_function
+
+import argparse
+import datetime
+import json
+import os
+import re
+
+import gitlab
+import pytz
+from dateutil.parser import parse as dateparser
+from dateutil.relativedelta import relativedelta
+
+
+def timestamp2date(timestamp):
+    if timestamp:
+        return timestamp.split("T")[0]
+    else:
+        return None
+
+
+def is_user_trusted(user):
+    trusted_domains = [
+        "archlinux.org",
+        "canonical.com",
+        "debian.org",
+        "endlessm.com",
+        "fedoraproject.org",
+        "gentoo.org",
+        "gitlab.gnome.org",
+        "gnome.org",
+        "igalia.com",
+        "kde.org",
+        "opensuse.org",
+        "redhat.com",
+        "suse.com",
+        "ubuntu.com",
+    ]
+
+    attrs = user.attributes
+    identities = [identity["provider"] for identity in attrs["identities"]]
+
+    if "ldapmain" in identities:
+        user.customattributes.set("trusted", "true")
+        return True
+
+    if attrs["email"].split("@")[1] in trusted_domains:
+        user.customattributes.set("trusted", "true")
+        return True
+
+    if attrs["two_factor_enabled"]:
+        user.customattributes.set("trusted", "true")
+        return True
+
+    if user.keys.list() or user.gpgkeys.list():
+        user.customattributes.set("trusted", "true")
+        return True
+
+    if len(user.identities) >= 2:
+        user.customattributes.set("trusted", "true")
+        return True
+
+    events = user.events.list(all=True)
+    target_types = ('DiffNote', 'MergeRequest', 'DiscussionNote', 'MergeRequest', 'Note', 'Issue')
+    for event in events:
+        if event.action_name == 'pushed to' or event.target_type in target_types:
+            project_id = event.project_id
+            project = gl.projects.get(project_id).path_with_namespace
+            if project.split('/')[0] in ('GNOME', 'Infrastructure', 'Teams'):
+                user.customattributes.set("trusted", "true")
+                return True
+
+    return False
+
+
+def get_inactive_users(gl, timedelta_unit, timedelta_value):
+    fields = [
+        "username",
+        "email",
+        "id",
+        "bio",
+        "website_url",
+        "created_at",
+        "current_sign_in_at",
+        "last_activity_on",
+    ]
+
+    trusted_users = gl.users.list(custom_attributes={"trusted": "true"}, all=True)
+    users = gl.users.list(as_list=False, order_by="created_at", sort="asc")
+    results = []
+
+    relativedelta_kwargs = {timedelta_unit: int(timedelta_value)}
+    timedelta = datetime.datetime.now(pytz.utc) - relativedelta(**relativedelta_kwargs)
+
+    for user in users:
+        attrs = user.attributes
+        userdata = {field: str(attrs[field]) for field in fields}
+
+        if user in trusted_users:
+            continue
+
+        if is_user_trusted(user):
+            continue
+
+        # Skip user if registered within timedelta
+        if dateparser(attrs["created_at"]) > timedelta:
+            continue
+
+        created_at = timestamp2date(attrs["created_at"])
+        current_sign_in_at = timestamp2date(attrs["current_sign_in_at"])
+        last_activity_on = timestamp2date(attrs["last_activity_on"])
+
+        # If user logged in only once or never, check if they made any action.
+        if (created_at == current_sign_in_at == last_activity_on) or not (
+            current_sign_in_at or last_activity_on
+        ):
+            events = user.events.list(all=True, lazy=True)
+            if len(events) == 0:
+                userdata["reason"] = "inactivity"
+                results.append(userdata)
+                continue
+            else:
+                user_projects = user.projects.list(all=True)
+                if len(user_projects) == 1:
+                    userproj = user_projects[0]
+                    if not userproj.attributes.get("forked_from_project"):
+                        proj = gl.projects.get(userproj.id)
+                        if len(proj.commits.list()) <= 1:
+                            userdata["reason"] = "inactivity"
+                            results.append(userdata)
+                            continue
+                if len(user_projects) > 1:
+                    continue
+
+    return results
+
+
+def delete_snippets_only_users(gl):
+    trusted_users = gl.users.list(custom_attributes={"trusted": "true"}, all=True)
+    snippets = gl.snippets.public(per_page=100)
+
+    snippets_users = {x.attributes["author"]["id"] for x in snippets}
+    for user in snippets_users:
+        user = gl.users.get(user)
+        if user in trusted_users:
+            continue
+
+        if is_user_trusted(user):
+            continue
+
+        events = user.events.list(all=True, lazy=True)
+        if len(events) == 0:
+            delete_user(gl, user.id, "snippets")
+
+
+def trust_user(gl, user_id):
+    user = gl.users.get(user_id, lazy=True)
+    user.customattributes.set("trusted", "true")
+    print(user_id)
+
+
+def untrust_user(gl, user_id):
+    user = gl.users.get(user_id, lazy=True)
+    user.customattributes.delete("trusted")
+    print(user_id)
+
+
+def delete_user(gl, user_id, reason=""):
+    try:
+        user = gl.users.get(user_id)
+        print(f"{user.username},{user.email},{reason}")
+        gl.users.delete(user_id)
+    except gitlab.exceptions.GitlabDeleteError:
+        pass
+
+
+def block_user(gl, user_id):
+    try:
+        user = gl.users.get(user_id)
+        print(f"{user.username},{user.email},spam")
+        user.block()
+    except gitlab.exceptions.GitlabBlockError:
+        pass
+
+
+def trust_all_groups(gl):
+    groups = gl.groups.list(all=True, visibility="public")
+    parent_groups = [grp for grp in groups if not grp.attributes["parent_id"]]
+    members = set()
+
+    for group in parent_groups:
+        group_members = group.members.all(all=True)
+        members.update(group_members)
+
+    for user in members:
+        trust_user(gl, user.id)
+
+
+def trust_2fa_users(gl):
+    users = gl.users.list(all=True, two_factor="enabled", as_list=False)
+
+    for user in users:
+        trust_user(gl, user.id)
+
+
+if __name__ == "__main__":
+    GITLAB_TOKEN = os.getenv("GITLAB_TOKEN")
+    if GITLAB_TOKEN is None:
+        with open("/home/admin/secret/gitlab_rw") as f:
+            tokenfile = f.readline()
+        GITLAB_TOKEN = tokenfile.rstrip().split("=")[1]
+
+    gl = gitlab.Gitlab(
+        "https://gitlab.gnome.org";, private_token=GITLAB_TOKEN, per_page=100
+    )
+    gl.auth()
+
+    parser = argparse.ArgumentParser()
+    subparsers = parser.add_subparsers(dest="command")
+
+    trust_groups = subparsers.add_parser(
+        "trust-groups", help="mark users which are member of any group as trusted"
+    )
+    trust_2fa = subparsers.add_parser(
+        "trust-2fa",
+        help="mark users which have two factor authenticated enabled as trusted",
+    )
+
+    inactive = subparsers.add_parser("get-inactive", help="get inactive users")
+    inactive.add_argument(
+        "-t",
+        "--timedelta",
+        default="months:1",
+        help="grace period for users to become active (unit:value)",
+    )
+
+    trust = subparsers.add_parser("trust", help="mark users as trusted")
+    trust.add_argument("user_id", nargs="+", help="user IDs to mark as trusted")
+
+    untrust = subparsers.add_parser("untrust", help="mark users as untrusted")
+    untrust.add_argument("user_id", nargs="+", help="user IDs to mark as untrusted")
+
+    delete = subparsers.add_parser("delete", help="delete users")
+    delete.add_argument("user_id", nargs="+", help="user IDs to delete")
+
+    delete_from_file = subparsers.add_parser(
+        "delete-from-json",
+        help="delete users from json file generated with get-inactive",
+    )
+    delete_from_file.add_argument("filename", help="path to json file")
+
+    snippets = subparsers.add_parser(
+        "snippets",
+        help="remove users who have no activity other than posting snippets"
+    )
+
+    args = parser.parse_args()
+
+    if args.command == "get-inactive":
+        unit, value = args.timedelta.split(":")
+        inactive = get_inactive_users(gl, unit, value)
+        with open("/tmp/inactive.json", "w") as f:
+            json.dump(inactive, f)
+    elif args.command == "trust":
+        for id in args.user_id:
+            trust_user(gl, id)
+    elif args.command == "untrust":
+        for id in args.user_id:
+            untrust_user(gl, id)
+    elif args.command == "delete":
+        for id in args.user_id:
+            delete_user(gl, id)
+    elif args.command == "delete-from-json":
+        with open(args.filename, "r") as f:
+            users = json.load(f)
+
+        for user in users:
+            if user["reason"] == "spam":
+                block_user(gl, user["id"])
+                continue
+
+            delete_user(gl, user["id"], user["reason"])
+    elif args.command == "trust-groups":
+        trust_all_groups(gl)
+    elif args.command == "trust-2fa":
+        trust_2fa_users(gl)
+    elif args.command == "snippets":
+        delete_snippets_only_users(gl)
+    else:
+        parser.print_help()
diff --git a/inactive-gitlab-users.sh b/inactive-gitlab-users.sh
index 934ad60..2bd3c2c 100755
--- a/inactive-gitlab-users.sh
+++ b/inactive-gitlab-users.sh
@@ -4,9 +4,8 @@ set -e
 echo "remove after ${TIMEDELTA_VALUE} ${TIMEDELTA_UNIT}"
 echo
 
-cd /usr/local/sysadmin-bin/gitlab
-python3 ./inactive-gitlab-users.py trust-groups >/dev/null
-python3 ./inactive-gitlab-users.py trust-2fa >/dev/null
-python3 ./inactive-gitlab-users.py snippets
-python3 ./inactive-gitlab-users.py get-inactive -t ${TIMEDELTA_UNIT}:${TIMEDELTA_VALUE} > /tmp/inactive.json
-python3 ./inactive-gitlab-users.py delete-from-json /tmp/inactive.json
+python3 /usr/local/bin/inactive-gitlab-users.py trust-groups >/dev/null
+python3 /usr/local/bin/inactive-gitlab-users.py trust-2fa >/dev/null
+python3 /usr/local/bin/inactive-gitlab-users.py snippets
+python3 /usr/local/bin/inactive-gitlab-users.py get-inactive -t ${TIMEDELTA_UNIT}:${TIMEDELTA_VALUE} > 
/tmp/inactive.json
+python3 /usr/local/bin/inactive-gitlab-users.py delete-from-json /tmp/inactive.json


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]