Skip to content

Commit

Permalink
[ci] Add bot to post welcome comment
Browse files Browse the repository at this point in the history
This would post the comment that the tests bot and the docs comment bot
uses straightaway when a PR is posted. This will contain links to
generic info about posting PRs (and obviate the
`.github/PULL_REQUEST_TEMPLATE.md`) as well as dynamic info about the
specific PR (filled in later by the respective bots). This would make
things like the auto-cc bot more transparent since it would have a link
to the relevant issue.
  • Loading branch information
driazati committed Sep 6, 2022
1 parent d4201a9 commit 8f07732
Show file tree
Hide file tree
Showing 13 changed files with 739 additions and 286 deletions.
1 change: 0 additions & 1 deletion .github/PULL_REQUEST_TEMPLATE.md

This file was deleted.

18 changes: 0 additions & 18 deletions .github/workflows/docs_bot.yml

This file was deleted.

55 changes: 55 additions & 0 deletions .github/workflows/pr_comment_bot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

name: comment-bot
on:
pull_request_target:
types: [opened, reopened, edited, ready_for_review, labeled]
status:

concurrency:
group: pr-comment-${{ github.event.number }}-${{ github.event.target_url }}
cancel-in-progress: true

jobs:
run-comment-bot:
if: ${{ github.repository == 'apache/tvm' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Comment bot comment (pr)
if: ${{ github.event.number }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.number }}
run: |
set -eux
python ci/scripts/github_pr_comment.py --pr "$PR_NUMBER"
- name: Comment bot comment (status)
if: ${{ github.event.state }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
URL: ${{ github.event.target_url }}
run: |
set -eux
if [[ "$URL" == *"PR-"* ]]; then
echo "PR status, sending comment"
PR_NUMBER=$(echo $URL | sed 's/.*PR-//g' | sed 's/\/.*//g')
python ci/scripts/github_pr_comment.py --pr "$PR_NUMBER"
else
echo "Not a PR status, skipping"
fi
7 changes: 0 additions & 7 deletions .github/workflows/tag_teams.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,9 @@
# specific language governing permissions and limitations
# under the License.

# GH actions.
# We use it to cover windows and mac builds
# Jenkins is still the primary CI

name: Teams

on:
# See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target
pull_request_target:
types: [opened, reopened, edited, ready_for_review, labeled]
issues:
types: [opened, edited, reopened, labeled]

Expand Down
21 changes: 0 additions & 21 deletions .github/workflows/tests_bot.yml

This file was deleted.

19 changes: 19 additions & 0 deletions ci/scripts/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Package to enable testing of CI scripts"""

from . import github_skipped_tests_comment, github_pr_comment, github_tag_teams, github_docs_comment
59 changes: 50 additions & 9 deletions ci/scripts/git_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@
import json
import subprocess
import re
import os
import base64
import logging
from urllib import request, error
from typing import Dict, Tuple, Any, Optional, List

DRY_RUN = object()


def compress_query(query: str) -> str:
query = query.replace("\n", "")
Expand All @@ -32,7 +35,7 @@ def compress_query(query: str) -> str:


def post(url: str, body: Optional[Any] = None, auth: Optional[Tuple[str, str]] = None):
print(f"Requesting POST to", url, "with", body)
logging.info(f"Requesting POST to", url, "with", body)
headers = {}
req = request.Request(url, headers=headers, method="POST")
if auth is not None:
Expand All @@ -51,34 +54,62 @@ def post(url: str, body: Optional[Any] = None, auth: Optional[Tuple[str, str]] =
return response.read()


def dry_run_token(is_dry_run: bool) -> Any:
if is_dry_run:
return DRY_RUN
return os.environ["GITHUB_TOKEN"]


class GitHubRepo:
def __init__(self, user, repo, token):
def __init__(self, user, repo, token, test_data=None):
self.token = token
self.user = user
self.repo = repo
self.test_data = test_data
self.num_calls = 0
self.base = f"https://api.github.com/repos/{user}/{repo}/"

def headers(self):
return {
"Authorization": f"Bearer {self.token}",
}

def dry_run(self) -> bool:
return self.token == DRY_RUN

def graphql(self, query: str, variables: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
query = compress_query(query)
if variables is None:
variables = {}

url = "https://api.github.com/graphql"
response = self._request(
"https://api.github.com/graphql",
url,
{"query": query, "variables": variables},
method="POST",
)
if self.dry_run():
return self.testing_response("POST", url)

if "data" not in response:
msg = f"Error fetching data with query:\n{query}\n\nvariables:\n{variables}\n\nerror:\n{json.dumps(response, indent=2)}"
raise RuntimeError(msg)
return response

def testing_response(self, method: str, url: str) -> Any:
self.num_calls += 1
key = f"[{self.num_calls}] {method} - {url}"
if self.test_data is not None and key in self.test_data:
return self.test_data[key]
logging.info(f"Unknown URL in dry run: {key}")
return {}

def _request(self, full_url: str, body: Dict[str, Any], method: str) -> Dict[str, Any]:
print(f"Requesting {method} to", full_url, "with", body)
if self.dry_run():
logging.info(f"Dry run, would have requested a {method} to {full_url} with {body}")
return self.testing_response(method, full_url)

logging.info(f"Requesting {method} to {full_url} with {body}")
req = request.Request(full_url, headers=self.headers(), method=method.upper())
req.add_header("Content-Type", "application/json; charset=utf-8")
data = json.dumps(body)
Expand Down Expand Up @@ -111,16 +142,22 @@ def post(self, url: str, data: Dict[str, Any]) -> Dict[str, Any]:
return self._request(self.base + url, data, method="POST")

def get(self, url: str) -> Dict[str, Any]:
if self.dry_run():
logging.info(f"Dry run, would have requested a GET to {url}")
return self.testing_response("GET", url)
url = self.base + url
print("Requesting GET to", url)
logging.info(f"Requesting GET to {url}")
req = request.Request(url, headers=self.headers())
with request.urlopen(req) as response:
response = json.loads(response.read())
return response

def delete(self, url: str) -> Dict[str, Any]:
if self.dry_run():
logging.info(f"Dry run, would have requested a DELETE to {url}")
return self.testing_response("DELETE", url)
url = self.base + url
print("Requesting DELETE to", url)
logging.info(f"Requesting DELETE to {url}")
req = request.Request(url, headers=self.headers(), method="DELETE")
with request.urlopen(req) as response:
response = json.loads(response.read())
Expand All @@ -136,18 +173,22 @@ def parse_remote(remote: str) -> Tuple[str, str]:
parts = remote.split("/")
if len(parts) < 2:
raise RuntimeError(f"Unable to parse remote '{remote}'")
return parts[-2], parts[-1].replace(".git", "")
user, repo = parts[-2], parts[-1].replace(".git", "")
else:
# Parse SSH remote
m = re.search(r":(.*)/(.*)\.git", remote)
if m is None or len(m.groups()) != 2:
raise RuntimeError(f"Unable to parse remote '{remote}'")
return m.groups()
user, repo = m.groups()

user = os.getenv("DEBUG_USER", user)
repo = os.getenv("DEBUG_REPO", repo)
return user, repo


def git(command, **kwargs):
command = ["git"] + command
print("Running", command)
logging.info(f"Running {command}")
proc = subprocess.run(command, stdout=subprocess.PIPE, encoding="utf-8", **kwargs)
if proc.returncode != 0:
raise RuntimeError(f"Command failed {command}:\nstdout:\n{proc.stdout}")
Expand Down
Loading

0 comments on commit 8f07732

Please sign in to comment.