Skip to content

Commit 63bc6ae

Browse files
authored
Rollup merge of rust-lang#64119 - pietroalbini:validate-toolstate-maintainers, r=kennytm
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. r? @kennytm
2 parents be327a8 + ce451b9 commit 63bc6ae

File tree

2 files changed

+87
-19
lines changed

2 files changed

+87
-19
lines changed

src/ci/azure-pipelines/steps/run.yml

+7
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,15 @@ steps:
147147
git clone --depth=1 https://github.com/rust-lang-nursery/rust-toolstate.git
148148
cd rust-toolstate
149149
python2.7 "$BUILD_SOURCESDIRECTORY/src/tools/publish_toolstate.py" "$(git rev-parse HEAD)" "$(git log --format=%s -n1 HEAD)" "" ""
150+
# Only check maintainers if this build is supposed to publish toolstate.
151+
# Builds that are not supposed to publish don't have the access token.
152+
if [ -n "${TOOLSTATE_PUBLISH+is_set}" ]; then
153+
TOOLSTATE_VALIDATE_MAINTAINERS_REPO=rust-lang/rust python2.7 "${BUILD_SOURCESDIRECTORY}/src/tools/publish_toolstate.py"
154+
fi
150155
cd ..
151156
rm -rf rust-toolstate
157+
env:
158+
TOOLSTATE_REPO_ACCESS_TOKEN: $(TOOLSTATE_REPO_ACCESS_TOKEN)
152159
condition: and(succeeded(), not(variables.SKIP_JOB), eq(variables['IMAGE'], 'mingw-check'))
153160
displayName: Verify the publish_toolstate script works
154161

src/tools/publish_toolstate.py

+80-19
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
## It is set as callback for `src/ci/docker/x86_64-gnu-tools/repo.sh` by the CI scripts
88
## when a new commit lands on `master` (i.e., after it passed all checks on `auto`).
99

10+
from __future__ import print_function
11+
1012
import sys
1113
import re
1214
import os
@@ -20,21 +22,26 @@
2022
import urllib.request as urllib2
2123

2224
# List of people to ping when the status of a tool or a book changed.
25+
# These should be collaborators of the rust-lang/rust repository (with at least
26+
# read privileges on it). CI will fail otherwise.
2327
MAINTAINERS = {
24-
'miri': '@oli-obk @RalfJung @eddyb',
25-
'clippy-driver': '@Manishearth @llogiq @mcarton @oli-obk @phansch @flip1995 @yaahc',
26-
'rls': '@Xanewok',
27-
'rustfmt': '@topecongiro',
28-
'book': '@carols10cents @steveklabnik',
29-
'nomicon': '@frewsxcv @Gankro',
30-
'reference': '@steveklabnik @Havvy @matthewjasper @ehuss',
31-
'rust-by-example': '@steveklabnik @marioidival @projektir',
32-
'embedded-book': (
33-
'@adamgreig @andre-richter @jamesmunns @korken89 '
34-
'@ryankurte @thejpster @therealprof'
35-
),
36-
'edition-guide': '@ehuss @Centril @steveklabnik',
37-
'rustc-guide': '@mark-i-m @spastorino @amanjeev'
28+
'miri': {'oli-obk', 'RalfJung', 'eddyb'},
29+
'clippy-driver': {
30+
'Manishearth', 'llogiq', 'mcarton', 'oli-obk', 'phansch', 'flip1995',
31+
'yaahc',
32+
},
33+
'rls': {'Xanewok'},
34+
'rustfmt': {'topecongiro'},
35+
'book': {'carols10cents', 'steveklabnik'},
36+
'nomicon': {'frewsxcv', 'Gankra'},
37+
'reference': {'steveklabnik', 'Havvy', 'matthewjasper', 'ehuss'},
38+
'rust-by-example': {'steveklabnik', 'marioidival'},
39+
'embedded-book': {
40+
'adamgreig', 'andre-richter', 'jamesmunns', 'korken89',
41+
'ryankurte', 'thejpster', 'therealprof',
42+
},
43+
'edition-guide': {'ehuss', 'Centril', 'steveklabnik'},
44+
'rustc-guide': {'mark-i-m', 'spastorino', 'amanjeev'},
3845
}
3946

4047
REPOS = {
@@ -52,6 +59,50 @@
5259
}
5360

5461

62+
def validate_maintainers(repo, github_token):
63+
'''Ensure all maintainers are assignable on a GitHub repo'''
64+
next_link_re = re.compile(r'<([^>]+)>; rel="next"')
65+
66+
# Load the list of assignable people in the GitHub repo
67+
assignable = []
68+
url = 'https://api.github.com/repos/%s/collaborators?per_page=100' % repo
69+
while url is not None:
70+
response = urllib2.urlopen(urllib2.Request(url, headers={
71+
'Authorization': 'token ' + github_token,
72+
# Properly load nested teams.
73+
'Accept': 'application/vnd.github.hellcat-preview+json',
74+
}))
75+
assignable.extend(user['login'] for user in json.load(response))
76+
# Load the next page if available
77+
url = None
78+
link_header = response.headers.get('Link')
79+
if link_header:
80+
matches = next_link_re.match(link_header)
81+
if matches is not None:
82+
url = matches.group(1)
83+
84+
errors = False
85+
for tool, maintainers in MAINTAINERS.items():
86+
for maintainer in maintainers:
87+
if maintainer not in assignable:
88+
errors = True
89+
print(
90+
"error: %s maintainer @%s is not assignable in the %s repo"
91+
% (tool, maintainer, repo),
92+
)
93+
94+
if errors:
95+
print()
96+
print(" To be assignable, a person needs to be explicitly listed as a")
97+
print(" collaborator in the repository settings. The simple way to")
98+
print(" fix this is to ask someone with 'admin' privileges on the repo")
99+
print(" to add the person or whole team as a collaborator with 'read'")
100+
print(" privileges. Those privileges don't grant any extra permissions")
101+
print(" so it's safe to apply them.")
102+
print()
103+
print("The build will fail due to this.")
104+
exit(1)
105+
55106
def read_current_status(current_commit, path):
56107
'''Reads build status of `current_commit` from content of `history/*.tsv`
57108
'''
@@ -73,13 +124,12 @@ def maybe_delink(message):
73124
def issue(
74125
tool,
75126
status,
76-
maintainers,
127+
assignees,
77128
relevant_pr_number,
78129
relevant_pr_user,
79130
pr_reviewer,
80131
):
81132
# Open an issue about the toolstate failure.
82-
assignees = [x.strip() for x in maintainers.split('@') if x != '']
83133
if status == 'test-fail':
84134
status_description = 'has failing tests'
85135
else:
@@ -100,7 +150,7 @@ def issue(
100150
REPOS.get(tool), relevant_pr_user, pr_reviewer
101151
)),
102152
'title': '`{}` no longer builds after {}'.format(tool, relevant_pr_number),
103-
'assignees': assignees,
153+
'assignees': list(assignees),
104154
'labels': ['T-compiler', 'I-nominated'],
105155
})
106156
print("Creating issue:\n{}".format(request))
@@ -150,18 +200,19 @@ def update_latest(
150200
old = status[os]
151201
new = s.get(tool, old)
152202
status[os] = new
203+
maintainers = ' '.join('@'+name for name in MAINTAINERS[tool])
153204
if new > old: # comparing the strings, but they are ordered appropriately!
154205
# things got fixed or at least the status quo improved
155206
changed = True
156207
message += '🎉 {} on {}: {} → {} (cc {}, @rust-lang/infra).\n' \
157-
.format(tool, os, old, new, MAINTAINERS.get(tool))
208+
.format(tool, os, old, new, maintainers)
158209
elif new < old:
159210
# tests or builds are failing and were not failing before
160211
changed = True
161212
title = '💔 {} on {}: {} → {}' \
162213
.format(tool, os, old, new)
163214
message += '{} (cc {}, @rust-lang/infra).\n' \
164-
.format(title, MAINTAINERS.get(tool))
215+
.format(title, maintainers)
165216
# Most tools only create issues for build failures.
166217
# Other failures can be spurious.
167218
if new == 'build-fail' or (tool == 'miri' and new == 'test-fail'):
@@ -200,6 +251,16 @@ def update_latest(
200251

201252

202253
if __name__ == '__main__':
254+
repo = os.environ.get('TOOLSTATE_VALIDATE_MAINTAINERS_REPO')
255+
if repo:
256+
github_token = os.environ.get('TOOLSTATE_REPO_ACCESS_TOKEN')
257+
if github_token:
258+
validate_maintainers(repo, github_token)
259+
else:
260+
print('skipping toolstate maintainers validation since no GitHub token is present')
261+
# When validating maintainers don't run the full script.
262+
exit(0)
263+
203264
cur_commit = sys.argv[1]
204265
cur_datetime = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
205266
cur_commit_msg = sys.argv[2]

0 commit comments

Comments
 (0)