This is an automated email from the ASF dual-hosted git repository.
driazati pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git
The following commit(s) were added to refs/heads/main by this push:
new 78b3fc26a0 [ci][tvmbot] Enable re-run for GitHub Actions (#12295)
78b3fc26a0 is described below
commit 78b3fc26a0211cd7956ed727d0fabbe5404d5c8c
Author: driazati <[email protected]>
AuthorDate: Thu Aug 4 10:29:15 2022 -0700
[ci][tvmbot] Enable re-run for GitHub Actions (#12295)
This adds the right permissions so anyone associated with the repo can
trigger a re-run (GitHub hasn't flagged all committers as repo `COLLABORATORS`
for some reason so it's difficult to determine from a username who has commit
rights) and makes it so `@tvm-bot rerun` also re-runs all the Actions on a PR.
---
.github/workflows/tvmbot.yml | 2 +-
.../python/ci/{test_mergebot.py => test_tvmbot.py} | 11 +-
tests/scripts/git_utils.py | 6 +-
tests/scripts/github_tvmbot.py | 137 +++++++++++++++++----
4 files changed, 123 insertions(+), 33 deletions(-)
diff --git a/.github/workflows/tvmbot.yml b/.github/workflows/tvmbot.yml
index 792977f92e..87292ec211 100644
--- a/.github/workflows/tvmbot.yml
+++ b/.github/workflows/tvmbot.yml
@@ -1,7 +1,6 @@
name: tvm-bot
on:
- status:
pull_request_review:
types:
- submitted
@@ -28,6 +27,7 @@ jobs:
- name: Run tvm-bot
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GH_ACTIONS_TOKEN: ${{ secrets.GH_ACTIONS_TOKEN }}
TVM_BOT_JENKINS_TOKEN: ${{ secrets.TVM_BOT_JENKINS_TOKEN }}
PR_NUMBER: ${{ github.event.issue.number }}
ISSUE_COMMENT: ${{ toJson(github.event.comment) }}
diff --git a/tests/python/ci/test_mergebot.py b/tests/python/ci/test_tvmbot.py
similarity index 95%
rename from tests/python/ci/test_mergebot.py
rename to tests/python/ci/test_tvmbot.py
index ccdfdc6539..0b55bdaa29 100644
--- a/tests/python/ci/test_mergebot.py
+++ b/tests/python/ci/test_tvmbot.py
@@ -81,7 +81,7 @@ TEST_DATA = {
"invalid-author": {
"number": 10786,
"filename": "pr10786-invalid-author.json",
- "expected": "Comment is not from from PR author or collaborator,
quitting",
+ "expected": "Failed auth check 'collaborators', quitting",
"comment": "@tvm-bot merge",
"user": "not-abc",
"detail": "Merge requester is not a committer and cannot merge",
@@ -89,7 +89,7 @@ TEST_DATA = {
"unauthorized-comment": {
"number": 11244,
"filename": "pr11244-unauthorized-comment.json",
- "expected": "Comment is not from from PR author or collaborator,
quitting",
+ "expected": "Failed auth check 'collaborators'",
"comment": "@tvm-bot merge",
"user": "not-abc2",
"detail": "Check that a merge comment not from a CONTRIBUTOR is
rejected",
@@ -135,7 +135,7 @@ TEST_DATA = {
[tuple(d.values()) for d in TEST_DATA.values()],
ids=TEST_DATA.keys(),
)
-def test_mergebot(tmpdir_factory, number, filename, expected, comment, user,
detail):
+def test_tvmbot(tmpdir_factory, number, filename, expected, comment, user,
detail):
"""
Test the mergebot test cases
"""
@@ -156,7 +156,7 @@ def test_mergebot(tmpdir_factory, number, filename,
expected, comment, user, det
"login": user,
},
}
- collaborators = []
+ collaborators = ["abc"]
proc = subprocess.run(
[
@@ -170,6 +170,8 @@ def test_mergebot(tmpdir_factory, number, filename,
expected, comment, user, det
json.dumps(test_data),
"--testing-collaborators-json",
json.dumps(collaborators),
+ "--testing-mentionable-users-json",
+ json.dumps(collaborators),
"--trigger-comment-json",
json.dumps(comment),
],
@@ -178,6 +180,7 @@ def test_mergebot(tmpdir_factory, number, filename,
expected, comment, user, det
encoding="utf-8",
env={
"TVM_BOT_JENKINS_TOKEN": "123",
+ "GH_ACTIONS_TOKEN": "123",
},
cwd=git.cwd,
check=False,
diff --git a/tests/scripts/git_utils.py b/tests/scripts/git_utils.py
index f0d300e2f0..cb639178c3 100644
--- a/tests/scripts/git_utils.py
+++ b/tests/scripts/git_utils.py
@@ -89,9 +89,9 @@ class GitHubRepo:
with request.urlopen(req, data) as response:
content = response.read()
except error.HTTPError as e:
- logging.info(f"Error response: {e.read().decode()}")
- e.seek(0)
- raise e
+ msg = str(e)
+ error_data = e.read().decode()
+ raise RuntimeError(f"Error response: {msg}\n{error_data}")
logging.info(f"Got response from {full_url}: {content}")
try:
diff --git a/tests/scripts/github_tvmbot.py b/tests/scripts/github_tvmbot.py
index e83318e18e..3f7db60d33 100755
--- a/tests/scripts/github_tvmbot.py
+++ b/tests/scripts/github_tvmbot.py
@@ -37,6 +37,7 @@ CommentChecker = Callable[[Comment], bool]
EXPECTED_JOBS = ["tvm-ci/pr-head"]
TVM_BOT_JENKINS_TOKEN = os.environ["TVM_BOT_JENKINS_TOKEN"]
+GH_ACTIONS_TOKEN = os.environ["GH_ACTIONS_TOKEN"]
JENKINS_URL = "https://ci.tlcpack.ai/"
THANKS_MESSAGE = r"(\s*)Thanks for contributing to TVM! Please refer to
guideline https://tvm.apache.org/docs/contribute/ for useful information and
tips. After the pull request is submitted, please request code reviews from
\[Reviewers\]\(https://github.com/apache/incubator-tvm/blob/master/CONTRIBUTORS.md#reviewers\)
by them in the pull request thread.(\s*)"
@@ -57,6 +58,18 @@ query ($owner: String!, $name: String!, $user: String!) {
}
"""
+MENTIONABLE_QUERY = """
+query ($owner: String!, $name: String!, $user: String!) {
+ repository(owner: $owner, name: $name) {
+ mentionableUsers(query: $user, first: 1) {
+ nodes {
+ login
+ }
+ }
+ }
+}
+"""
+
PR_QUERY = """
query ($owner: String!, $name: String!, $number: Int!) {
@@ -106,6 +119,7 @@ PR_QUERY = """
nodes {
... on CheckRun {
name
+ databaseId
checkSuite {
workflowRun {
workflow {
@@ -221,12 +235,12 @@ class PR:
def __repr__(self):
return json.dumps(self.raw, indent=2)
- def plus_one(self, comment: Dict[str, Any]):
+ def react(self, comment: Dict[str, Any], content: str):
"""
React with a thumbs up to a comment
"""
url = f"issues/comments/{comment['id']}/reactions"
- data = {"content": "+1"}
+ data = {"content": content}
if self.dry_run:
logging.info(f"Dry run, would have +1'ed to {url} with {data}")
else:
@@ -326,14 +340,20 @@ class PR:
"""
Query GitHub for collaborators matching 'user'
"""
+ return self.search_users(user,
COLLABORATORS_QUERY)["collaborators"]["nodes"]
+
+ def search_users(self, user: str, query: str) -> List[Dict[str, Any]]:
return self.github.graphql(
- query=COLLABORATORS_QUERY,
+ query=query,
variables={
"owner": self.owner,
"name": self.repo_name,
"user": user,
},
- )["data"]["repository"]["collaborators"]["nodes"]
+ )["data"]["repository"]
+
+ def search_mentionable_users(self, user: str) -> List[Dict[str, Any]]:
+ return self.search_users(user,
MENTIONABLE_QUERY)["mentionableUsers"]["nodes"]
def comment(self, text: str) -> None:
"""
@@ -503,6 +523,30 @@ class PR:
else:
post(url, auth=("tvm-bot", TVM_BOT_JENKINS_TOKEN))
+ def rerun_github_actions(self) -> None:
+ job_ids = []
+ for item in
self.head_commit()["statusCheckRollup"]["contexts"]["nodes"]:
+ if "checkSuite" in item:
+ job_ids.append(item["databaseId"])
+
+ logging.info(f"Rerunning GitHub Actions jobs with IDs: {job_ids}")
+ actions_github = GitHubRepo(
+ user=self.github.user, repo=self.github.repo,
token=GH_ACTIONS_TOKEN
+ )
+ for job_id in job_ids:
+ if self.dry_run:
+ try:
+ actions_github.post(f"actions/jobs/{job_id}/rerun",
data={})
+ except RuntimeError as e:
+ # Ignore errors about jobs that are part of the same
workflow to avoid
+ # having to figure out which jobs are in which workflows
ahead of time
+ if "The workflow run containing this job is already
running" in str(e):
+ pass
+ else:
+ raise e
+ else:
+ logging.info(f"Dry run, not restarting {job_id}")
+
def comment_failure(self, msg: str, exception: Exception):
if not self.dry_run:
exception_msg = traceback.format_exc()
@@ -514,6 +558,49 @@ class PR:
return exception
+def check_author(pr, triggering_comment, args):
+ comment_author = triggering_comment["user"]["login"]
+ if pr.author() == comment_author:
+ logging.info("Comment user is PR author, continuing")
+ return True
+ return False
+
+
+def check_collaborator(pr, triggering_comment, args):
+ logging.info("Checking collaborators")
+ # Get the list of collaborators for the repo filtered by the comment
+ # author
+ commment_author = triggering_comment["user"]["login"]
+ if args.testing_collaborators_json:
+ collaborators = json.loads(args.testing_collaborators_json)
+ else:
+ collaborators = pr.search_collaborator(commment_author)
+ logging.info(f"Found collaborators: {collaborators}")
+
+ return len(collaborators) > 0 and commment_author in collaborators
+
+
+def check_mentionable_users(pr, triggering_comment, args):
+ logging.info("Checking mentionable users")
+ commment_author = triggering_comment["user"]["login"]
+ if args.testing_mentionable_users_json:
+ mentionable_users = json.loads(args.testing_mentionable_users_json)
+ else:
+ mentionable_users = pr.search_mentionable_users(commment_author)
+ logging.info(f"Found mentionable_users: {mentionable_users}")
+
+ return len(mentionable_users) > 0 and commment_author in mentionable_users
+
+
+AUTH_CHECKS = {
+ "metionable_users": check_mentionable_users,
+ "collaborators": check_collaborator,
+ "author": check_author,
+}
+# Stash the keys so they're accessible from the values
+AUTH_CHECKS = {k: (k, v) for k, v in AUTH_CHECKS.items()}
+
+
class Merge:
triggers = [
"merge",
@@ -521,6 +608,8 @@ class Merge:
"merge this pr",
]
+ auth = [AUTH_CHECKS["collaborators"], AUTH_CHECKS["author"]]
+
@staticmethod
def run(pr: PR):
info = None
@@ -548,9 +637,15 @@ class Rerun:
"run ci",
]
+ auth = [AUTH_CHECKS["metionable_users"]]
+
@staticmethod
def run(pr: PR):
- pr.rerun_jenkins_ci()
+ try:
+ pr.rerun_jenkins_ci()
+ pr.rerun_github_actions()
+ except Exception as e:
+ pr.comment_failure("Failed to re-run CI", e)
if __name__ == "__main__":
@@ -566,6 +661,9 @@ if __name__ == "__main__":
parser.add_argument(
"--testing-collaborators-json", help="(testing only) manual data for
testing"
)
+ parser.add_argument(
+ "--testing-mentionable-users-json", help="(testing only) manual data
for testing"
+ )
parser.add_argument(
"--dry-run",
action="store_true",
@@ -615,29 +713,18 @@ if __name__ == "__main__":
else:
pr = PR(number=int(args.pr), owner=owner, repo=repo,
dry_run=args.dry_run)
- # Acknowledge the comment with a react
- pr.plus_one(comment)
-
- # Check the comment author
- comment_author = comment["user"]["login"]
- if pr.author() == comment_author:
- logging.info("Comment user is PR author, continuing")
- else:
- logging.info("Comment is not from PR author, checking collaborators")
- # Get the list of collaborators for the repo filtered by the comment
- # author
- if args.testing_collaborators_json:
- collaborators = json.loads(args.testing_collaborators_json)
- else:
- collaborators = pr.search_collaborator(comment_author)
- logging.info(f"Found collaborators: {collaborators}")
-
- if len(collaborators) > 0:
- logging.info("Comment is from collaborator")
+ for name, check in command_to_run.auth:
+ if check(pr, comment, args):
+ logging.info(f"Passed auth check '{name}', continuing")
else:
- logging.info("Comment is not from from PR author or collaborator,
quitting")
+ logging.info(f"Failed auth check '{name}', quitting")
+ # Add a sad face
+ pr.react(comment, "confused")
exit(0)
+ # Acknowledge the comment with a react
+ pr.react(comment, "+1")
+
state = pr.state()
if state != "OPEN":