about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorPietro Albini <pietro@pietroalbini.org>2019-09-03 11:33:29 +0200
committerPietro Albini <pietro@pietroalbini.org>2019-09-16 16:30:46 +0200
commitd3d7b58c3743b6146b5fb818508d9eda958e8cd9 (patch)
tree71fcf7d65e8247765c20bd5333a53cf16e99e3cd /src
parentb6269f27d99d7da9e95f0b3fdc53193dc8c42fbe (diff)
downloadrust-d3d7b58c3743b6146b5fb818508d9eda958e8cd9.tar.gz
rust-d3d7b58c3743b6146b5fb818508d9eda958e8cd9.zip
ci: ensure all tool maintainers are assignable on issues
GitHub only allows people explicitly listed as collaborators on the
repository or who commented on the issue/PR to be assignees, failing to
create the issue if non-assignable people are assigned.

This adds an extra check on CI to make sure all the people listed as
tool maintainers can be assigned to toolstate issues. The check won't be
executed on PR builds due to the lack of a valid token.
Diffstat (limited to 'src')
-rw-r--r--src/ci/azure-pipelines/steps/run.yml7
-rwxr-xr-xsrc/tools/publish_toolstate.py60
2 files changed, 67 insertions, 0 deletions
diff --git a/src/ci/azure-pipelines/steps/run.yml b/src/ci/azure-pipelines/steps/run.yml
index ac6b344a45e..da0a899ac85 100644
--- a/src/ci/azure-pipelines/steps/run.yml
+++ b/src/ci/azure-pipelines/steps/run.yml
@@ -147,8 +147,15 @@ steps:
     git clone --depth=1 https://github.com/rust-lang-nursery/rust-toolstate.git
     cd rust-toolstate
     python2.7 "$BUILD_SOURCESDIRECTORY/src/tools/publish_toolstate.py" "$(git rev-parse HEAD)" "$(git log --format=%s -n1 HEAD)" "" ""
+    # Only check maintainers if this build is supposed to publish toolstate.
+    # Builds that are not supposed to publish don't have the access token.
+    if [ -n "${TOOLSTATE_PUBLISH+is_set}" ]; then
+      TOOLSTATE_VALIDATE_MAINTAINERS_REPO=rust-lang/rust python2.7 "${BUILD_SOURCESDIRECTORY}/src/tools/publish_toolstate.py"
+    fi
     cd ..
     rm -rf rust-toolstate
+  env:
+    TOOLSTATE_REPO_ACCESS_TOKEN: $(TOOLSTATE_REPO_ACCESS_TOKEN)
   condition: and(succeeded(), not(variables.SKIP_JOB), eq(variables['IMAGE'], 'mingw-check'))
   displayName: Verify the publish_toolstate script works
 
diff --git a/src/tools/publish_toolstate.py b/src/tools/publish_toolstate.py
index 2e2505b7f02..bc00fbc90bf 100755
--- a/src/tools/publish_toolstate.py
+++ b/src/tools/publish_toolstate.py
@@ -7,6 +7,8 @@
 ## It is set as callback for `src/ci/docker/x86_64-gnu-tools/repo.sh` by the CI scripts
 ## when a new commit lands on `master` (i.e., after it passed all checks on `auto`).
 
+from __future__ import print_function
+
 import sys
 import re
 import os
@@ -20,6 +22,8 @@ except ImportError:
     import urllib.request as urllib2
 
 # List of people to ping when the status of a tool or a book changed.
+# These should be collaborators of the rust-lang/rust repository (with at least
+# read privileges on it). CI will fail otherwise.
 MAINTAINERS = {
     'miri': '@oli-obk @RalfJung @eddyb',
     'clippy-driver': '@Manishearth @llogiq @mcarton @oli-obk @phansch @flip1995 @yaahc',
@@ -52,6 +56,53 @@ REPOS = {
 }
 
 
+def validate_maintainers(repo, github_token):
+    '''Ensure all maintainers are assignable on a GitHub repo'''
+    next_link_re = re.compile(r'<([^>]+)>; rel="next"')
+
+    # Load the list of assignable people in the GitHub repo
+    assignable = []
+    url = 'https://api.github.com/repos/%s/collaborators?per_page=100' % repo
+    while url is not None:
+        response = urllib2.urlopen(urllib2.Request(url, headers={
+            'Authorization': 'token ' + github_token,
+            # Properly load nested teams.
+            'Accept': 'application/vnd.github.hellcat-preview+json',
+        }))
+        for user in json.loads(response.read()):
+            assignable.append(user['login'])
+        # Load the next page if available
+        if 'Link' in response.headers:
+            matches = next_link_re.match(response.headers['Link'])
+            if matches is not None:
+                url = matches.group(1)
+            else:
+                url = None
+
+    errors = False
+    for tool, maintainers in MAINTAINERS.items():
+        for maintainer in maintainers.split(' '):
+            if maintainer.startswith('@'):
+                maintainer = maintainer[1:]
+            if maintainer not in assignable:
+                errors = True
+                print(
+                    "error: %s maintainer @%s is not assignable in the %s repo"
+                    % (tool, maintainer, repo),
+                )
+
+    if errors:
+        print()
+        print("  To be assignable, a person needs to be explicitly listed as a")
+        print("  collaborator in the repository settings. The simple way to")
+        print("  fix this is to ask someone with 'admin' privileges on the repo")
+        print("  to add the person or whole team as a collaborator with 'read'")
+        print("  privileges. Those privileges don't grant any extra permissions")
+        print("  so it's safe to apply them.")
+        print()
+        print("The build will fail due to this.")
+        exit(1)
+
 def read_current_status(current_commit, path):
     '''Reads build status of `current_commit` from content of `history/*.tsv`
     '''
@@ -200,6 +251,15 @@ def update_latest(
 
 
 if __name__ == '__main__':
+    if 'TOOLSTATE_VALIDATE_MAINTAINERS_REPO' in os.environ:
+        repo = os.environ['TOOLSTATE_VALIDATE_MAINTAINERS_REPO']
+        if 'TOOLSTATE_REPO_ACCESS_TOKEN' in os.environ:
+            github_token = os.environ['TOOLSTATE_REPO_ACCESS_TOKEN']
+            validate_maintainers(repo, github_token)
+        else:
+            print('skipping toolstate maintainers validation since no GitHub token is present')
+        exit(0)
+
     cur_commit = sys.argv[1]
     cur_datetime = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
     cur_commit_msg = sys.argv[2]