diff options
author | Tristan Maat <tristan.maat@codethink.co.uk> | 2019-07-09 15:56:41 +0100 |
---|---|---|
committer | Tristan Maat <tristan.maat@codethink.co.uk> | 2019-07-09 17:05:50 +0100 |
commit | d7b1fd1755bf21f8dba6aac99c8dc4523c60d8b6 (patch) | |
tree | 65a258c6576b59fa0c69b04c33b9d7eb0addb85a | |
parent | 6c6966d740c49d2b5d6c9a0d985d8f8b25e2788e (diff) | |
download | buildstream-d7b1fd1755bf21f8dba6aac99c8dc4523c60d8b6.tar.gz |
contrib/update_committers: Add script to update committers
-rw-r--r-- | COMMITTERS.rst | 46 | ||||
-rwxr-xr-x | contrib/update_committers | 170 |
2 files changed, 193 insertions, 23 deletions
diff --git a/COMMITTERS.rst b/COMMITTERS.rst index 9b74d8f94..35d5b9eff 100644 --- a/COMMITTERS.rst +++ b/COMMITTERS.rst @@ -15,27 +15,27 @@ If you have a question or comment, it's probably best to email the BuildStream mailing list, rather than any of these people directly. -+-------------------------+------------------------------------------+ -| Full Name | Email Address | -+=========================+==========================================+ -| Tristan Van Berkom | <tristan.vanberkom@codethink.co.uk | -+-------------------------+------------------------------------------+ -| Juerg Billeter | <juerg.billeter@codethink.co.uk> | -+-------------------------+------------------------------------------+ -| Ben Schubert | <bschubert15@bloomberg.net> | -+-------------------------+------------------------------------------+ -| Chandan Singh | <csingh43@bloomberg.net> | -+-------------------------+------------------------------------------+ -| Angelos Evripiotis | <jevripiotis@bloomberg.net> | -+-------------------------+------------------------------------------+ -| James Ennis | <james.ennis@codethink.co.uk> | -+-------------------------+------------------------------------------+ -| Tristan Maat | <tristan.maat@codethink.co.uk> | -+-------------------------+------------------------------------------+ -| Jonathan Maw | <jonathan.maw@codethink.co.uk> | -+-------------------------+------------------------------------------+ -| Will Salmon | <will.salmon@codethink.co.uk> | -+-------------------------+------------------------------------------+ -| Valentin David | <valentin.david@codethink.co.uk> | -+-------------------------+------------------------------------------+ ++-------------------------+------------------------------------------+------------------------------------------+ +| Full Name |Email Address |GitLab User | ++=========================+==========================================+==========================================+ +| Tristan Van Berkom | <tristan.vanberkom@codethink.co.uk> | tristanvb | ++-------------------------+------------------------------------------+------------------------------------------+ +| Juerg Billeter | <juerg.billeter@codethink.co.uk> | juergbi | ++-------------------------+------------------------------------------+------------------------------------------+ +| Ben Schubert | <bschubert15@bloomberg.net> | BenjaminSchubert | ++-------------------------+------------------------------------------+------------------------------------------+ +| Chandan Singh | <csingh43@bloomberg.net> | cs-shadow | ++-------------------------+------------------------------------------+------------------------------------------+ +| Angelos Evripiotis | <jevripiotis@bloomberg.net> | aevri | ++-------------------------+------------------------------------------+------------------------------------------+ +| James Ennis | <james.ennis@codethink.co.uk> | jennis | ++-------------------------+------------------------------------------+------------------------------------------+ +| Tristan Maat | <tristan.maat@codethink.co.uk> | tlater | ++-------------------------+------------------------------------------+------------------------------------------+ +| Jonathan Maw | <jonathan.maw@codethink.co.uk> | jonathanmaw | ++-------------------------+------------------------------------------+------------------------------------------+ +| Will Salmon | <will.salmon@codethink.co.uk> | willsalmon | ++-------------------------+------------------------------------------+------------------------------------------+ +| Valentin David | <valentin.david@codethink.co.uk> | valentindavid | ++-------------------------+------------------------------------------+------------------------------------------+ diff --git a/contrib/update_committers b/contrib/update_committers new file mode 100755 index 000000000..e8fa6f251 --- /dev/null +++ b/contrib/update_committers @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 +"""A script to set gitlab committers according to COMMITTERS.rst.""" + +import argparse +import json +import logging +import pathlib +import sys +import re +import urllib.request +import urllib.parse + +from pprint import pformat +from typing import Any, Dict, List, Tuple + + +API_BASE = "https://gitlab.com/api/v4" + + +def main(): + """Parse CLI arguments and set up application state.""" + + parser = argparse.ArgumentParser( + description="Update gitlab committers according to COMMITTERS.rst" + ) + parser.add_argument( + "token", type=str, + help="Your private access token. See https://gitlab.com/profile/personal_access_tokens." + ) + parser.add_argument( + "committers", type=pathlib.Path, + help="The path to COMMITTERS.rst. Will try " + "to find the one in the local git repository by default.", + nargs="?", + default=find_repository_root() / "COMMITTERS.rst" + ) + parser.add_argument( + "--repository", "-r", type=str, + help="The repository whose committers to set.", + default="BuildStream/buildstream" + ) + parser.add_argument( + "--dry-run", "-n", + help="Do not update the actual member list.", + action="store_false" + ) + parser.add_argument( + "--quiet", "-q", + help="Run quietly", + action="store_true" + ) + parser.add_argument( + "--debug", "-d", + help="Show debug messages (WARNING: This *will* display the private token).", + action="store_true" + ) + args = parser.parse_args() + + if args.debug: + logging.basicConfig(level=logging.DEBUG) + elif not args.quiet: + logging.basicConfig(level=logging.INFO) + + set_committers(args.repository, args.committers.read_text(), args.token, args.dry_run) + + +def set_committers(repository: str, committer_file: str, token: str, commit: bool): + """Set GitLab members as defined in the committer_file.""" + new_committers = [get_user_by_username(committer[2], token) + for committer in parse_committer_list(committer_file)] + old_committers = get_project_committers(repository, token) + + new_set = set(committer["id"] for committer in new_committers) + old_set = set(committer["id"] for committer in old_committers) + + added = [committer for committer in new_committers if committer["id"] in new_set - old_set] + removed = [committer for committer in new_committers if committer["id"] in old_set - new_set] + + logging.info("Adding:\n%s", pformat(added)) + logging.info("Removing:\n%s", pformat(removed)) + + if commit: + for committer in added: + set_user_access_level(repository, committer, 40, token) + for committer in removed: + set_user_access_level(repository, committer, 30, token) + + +#################################################################################################### +# Utility functions # +#################################################################################################### + +class RepositoryException(Exception): + """An exception raised when we can't deal with the repository.""" + + +def find_repository_root() -> pathlib.Path: + """Find the root directory of a git repository, starting at cwd().""" + root = pathlib.Path.cwd() + while not any(f.name == ".git" for f in root.iterdir()): + if root == root.parent: + raise RepositoryException("'{}' is not in a git repository.".format(pathlib.Path.cwd())) + root = root.parent + return root + + +def parse_committer_list(committer_text: str) -> List[Tuple[str, str, str]]: + """Parse COMMITTERS.rst and retrieve a map of names, email addresses and usernames.""" + return [(committer[0].strip(), committer[1].strip(" <>"), committer[2].strip()) for committer in + re.findall(r"\|([^|]+)\|([^|]+)\|([^|]+)\|\n\+-", committer_text)] + + +#################################################################################################### +# GitLab API # +#################################################################################################### + +def make_request_url(*args: Tuple[str], **kwargs: Dict[str, str]) -> str: + """Create a request url for the GitLab API.""" + return API_BASE + "/" + "/".join(args) + "?" + urllib.parse.urlencode(kwargs, safe='@') + + +def make_project_url(project: str, *args: Tuple[str], **kwargs: Dict[str, str]) -> str: + """Create a request url for the given project.""" + return make_request_url("projects", urllib.parse.quote(project, safe=''), *args, **kwargs) + + +def urlopen(url: str, token: str, data=None, method='GET') -> Any: + """Perform a paginated query to the GitLab API.""" + page = 1 + res = None + result = [] + while not res or page: + req = urllib.request.Request(url=url + "&" + urllib.parse.urlencode({"page": page}), + data=data, method=method) + req.add_header('PRIVATE-TOKEN', token) + logging.debug("Making API request: %s", pformat(req.__dict__)) + try: + res = urllib.request.urlopen(req) + except urllib.error.HTTPError as err: + logging.error("Could not access '%s': %s", url, err) + sys.exit(1) + result.extend(json.load(res)) + page = res.getheader('X-Next-Page') + return result + + +def get_project_committers(project: str, token: str) -> List[Dict]: + """Get a list of current committers.""" + return [committer for committer in + urlopen(make_project_url(project, "members", "all"), token) + if committer["access_level"] >= 40] + + +def get_user_by_username(username: str, token: str) -> Dict: + """Get a user ID from their email address.""" + return urlopen(make_request_url("users", + username=username), + token)[0] + + +def set_user_access_level(project: str, user: Dict, level: int, token: str) -> Dict: + """Set a user's project access level.""" + return urlopen(make_project_url(project, "members", str(user["id"]), + access_level=str(level)), + token, + method="PUT") + + +if __name__ == '__main__': + main() |