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":

Reply via email to