Skip to content

Commit

Permalink
[ci] Add bot to post welcome comment (apache#12695)
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.

Tested live here: #21 (comment)
  • Loading branch information
driazati authored Sep 16, 2022
1 parent 5d0a167 commit e037ae4
Show file tree
Hide file tree
Showing 13 changed files with 741 additions and 283 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
60 changes: 51 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,63 @@ 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):
GRAPHQL_URL = "https://api.github.com/graphql"

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 = {}

response = self._request(
"https://api.github.com/graphql",
self.GRAPHQL_URL,
{"query": query, "variables": variables},
method="POST",
)
if self.dry_run():
return self.testing_response("POST", self.GRAPHQL_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 +143,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 +174,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 e037ae4

Please sign in to comment.