diff --git a/.editorconfig b/.editorconfig index 5cf5aed0..b362f49d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -64,7 +64,7 @@ # SERIOUSLY. # # ------------------------------ -# Generated by edx-lint version: 5.2.5 +# Generated by edx-lint version: 5.3.4 # ------------------------------ [*] end_of_line = lf @@ -97,4 +97,4 @@ max_line_length = 72 [*.rst] max_line_length = 79 -# f2f02689fced7a2e0c62c2f9803184114dc2ae4b +# bbcbced841ed335dd8abb7456a6b13485d701b40 diff --git a/edx_repo_tools/audit_gh_users/README.rst b/edx_repo_tools/audit_gh_users/README.rst new file mode 100644 index 00000000..879550f0 --- /dev/null +++ b/edx_repo_tools/audit_gh_users/README.rst @@ -0,0 +1,45 @@ +Audit GitHub Users +################## + +This script will compare the list of users in a github org against a list of +users in a CSV and tell you which github users are not listed in the CSV. + +CSV Location and Format +*********************** + +The CSV is expected to be in a GitHub repo and it should contain a column name +"GitHub Username" that contains a GitHub username. + +Usage +***** + +You will need a GH pesonal access token with the following scopes: + +* read:org +* repo + +First, set up repo-tools as described in `the root README <../../README.rst>`_. +There are a few ways to do this; one way is:: + + export GITHUB_TOKEN="$(pass github-token)" # assumes you have passwordstore.org + + python3 -m venv venv + . venv/bin/activate + pip install -e .[audit_gh_users] + +Then, run the script:: + + audit_users + +Contributing +************ + +* Make changes on your branch. + +* CI will run tests for you, but not linting, so ensure your changes don't break pylint: ``pylint edx_repo_tools/audit_users``. + +* Ping `#ask-axim`__ on Slack for review. + +__ https://openedx.slack.com/archives/C0497NQCLBT + +* Once approved, apply and merge (non-Axim engineers: ask your Axim reviewer to do this part for you). diff --git a/edx_repo_tools/audit_gh_users/__init__.py b/edx_repo_tools/audit_gh_users/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/edx_repo_tools/audit_gh_users/audit_users.py b/edx_repo_tools/audit_gh_users/audit_users.py new file mode 100644 index 00000000..86b4ac64 --- /dev/null +++ b/edx_repo_tools/audit_gh_users/audit_users.py @@ -0,0 +1,72 @@ +""" +Audit github users in an org. Comparing the list of users to those in a CSV. + +See the README for more info. +""" + +import base64 +import csv +import io +from itertools import chain +import click +from ghapi.all import GhApi, paged + + +@click.command() +@click.option( + "--github-token", + "_github_token", + envvar="GITHUB_TOKEN", + required=True, + help="A github personal access token.", +) +@click.option( + "--org", + "org", + default="openedx", + help="The github org that you wish check.", +) +@click.option( + "--csv-repo", + "csv_repo", + default="openedx-webhooks-data", + help="The github repo that contains the CSV we should compare against.", +) +@click.option( + "--csv-path", + "csv_path", + default="salesforce-export.csv", + help="The path in the repo to the csv file. The file should contain a 'GitHub Username' column.", +) +def main(org, _github_token, csv_repo, csv_path): + """ + Entry point for command-line invocation. + """ + api = GhApi() + + # Get all github users in the org. + current_org_users = [ + member.login + for member in chain.from_iterable( + paged(api.orgs.list_members, org, per_page=100) + ) + ] + + # Get all github usernames from openedx-webhooks-data/salesforce-export.csv + csv_file = io.StringIO( + base64.decodebytes( + api.repos.get_content(org, csv_repo, csv_path).content.encode() + ).decode("utf-8") + ) + reader = csv.DictReader(csv_file) + csv_github_users = [row["GitHub Username"] for row in reader] + + # Find all the people that are in the org but not in sales force. + extra_org_users = set(current_org_users) - set(csv_github_users) + + # List the users we need to investigate + print("\n".join(sorted(extra_org_users))) + + +if __name__ == "__main__": + main() # pylint: disable=no-value-for-parameter diff --git a/edx_repo_tools/audit_gh_users/extra.in b/edx_repo_tools/audit_gh_users/extra.in new file mode 100644 index 00000000..d10f28bc --- /dev/null +++ b/edx_repo_tools/audit_gh_users/extra.in @@ -0,0 +1,4 @@ +-c ../../requirements/constraints.txt + +click +ghapi diff --git a/edx_repo_tools/audit_gh_users/extra.txt b/edx_repo_tools/audit_gh_users/extra.txt new file mode 100644 index 00000000..cdb8c98c --- /dev/null +++ b/edx_repo_tools/audit_gh_users/extra.txt @@ -0,0 +1,19 @@ +# +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: +# +# make upgrade +# +click==8.1.7 + # via -r edx_repo_tools/repo_checks/extra.in +fastcore==1.5.29 + # via ghapi +ghapi==1.0.4 + # via -r edx_repo_tools/repo_checks/extra.in +packaging==23.1 + # via + # fastcore + # ghapi + +# The following packages are considered to be unsafe in a requirements file: +# pip diff --git a/pylintrc b/pylintrc index 044bb0c3..7146214f 100644 --- a/pylintrc +++ b/pylintrc @@ -64,7 +64,7 @@ # SERIOUSLY. # # ------------------------------ -# Generated by edx-lint version: 5.2.5 +# Generated by edx-lint version: 5.3.4 # ------------------------------ [MASTER] ignore = @@ -259,6 +259,7 @@ enable = useless-suppression, disable = bad-indentation, + broad-exception-raised, consider-using-f-string, duplicate-code, file-ignored, @@ -380,6 +381,6 @@ ext-import-graph = int-import-graph = [EXCEPTIONS] -overgeneral-exceptions = Exception +overgeneral-exceptions = builtins.Exception -# 0bb4a6d612f83352ced91b8f50942dfac7d30cd2 +# 6b646c624a39204ce807909aabd80bf4c7d28116 diff --git a/setup.py b/setup.py index 0ece75b2..8f486f7f 100644 --- a/setup.py +++ b/setup.py @@ -87,6 +87,7 @@ def is_requirement(line): 'add_common_constraint = edx_repo_tools.add_common_constraint:main', 'add_dependabot_ecosystem = edx_repo_tools.dependabot_yml:main', 'add_django32_settings = edx_repo_tools.codemods.django3.add_new_django32_settings:main', + 'audit_users = edx_repo_tools.audit_gh_users.audit_users:main', 'clone_org = edx_repo_tools.dev.clone_org:main', 'conventional_commits = edx_repo_tools.conventional_commits.commitstats:main', 'find_dependencies = edx_repo_tools.find_dependencies.find_dependencies:main',