Add github webhooks endpoints to create a job from a comment on a PR

This commit is contained in:
Alexandre Aubin 2021-01-13 01:44:39 +01:00
parent 0f05bba6ec
commit d97747eed0

87
run.py
View file

@ -11,6 +11,9 @@ import traceback
import itertools import itertools
import tracemalloc import tracemalloc
import hmac
import hashlib
from datetime import datetime, date from datetime import datetime, date
from collections import defaultdict from collections import defaultdict
from functools import wraps from functools import wraps
@ -25,7 +28,7 @@ from websockets.exceptions import ConnectionClosed
from websockets import WebSocketCommonProtocol from websockets import WebSocketCommonProtocol
from sanic import Sanic, response from sanic import Sanic, response
from sanic.exceptions import NotFound from sanic.exceptions import NotFound, abort
from sanic.log import LOGGING_CONFIG_DEFAULTS from sanic.log import LOGGING_CONFIG_DEFAULTS
from sanic.response import json from sanic.response import json
@ -944,6 +947,88 @@ async def monitor(request):
}) })
@app.route("/github", methods=["GET"])
async def github_get(request):
return response.text(
"You aren't supposed to go on this page using a browser, it's for webhooks push instead."
)
@app.route("/github", methods=["POST"])
async def github(request):
# Abort directly if no secret opened
# (which also allows to only enable this feature if
# we define the webhook secret)
if not os.path.exists("./github_webhook_secret"):
abort(403)
# Only SHA1 is supported
header_signature = request.headers.get("X-Hub-Signature")
if header_signature is None:
print("no header X-Hub-Signature")
abort(403)
sha_name, signature = header_signature.split("=")
if sha_name != "sha1":
print("signing algo isn't sha1, it's '%s'" % sha_name)
abort(501)
secret = open("./github_webhook_secret", "r").read().strip()
# HMAC requires the key to be bytes, but data is string
mac = hmac.new(secret.encode(), msg=request.body, digestmod=hashlib.sha1)
if not hmac.compare_digest(str(mac.hexdigest()), str(signature)):
abort(403)
hook_type = request.headers.get("X-Github-Event")
hook_infos = request.json
# We expect issue comments (issue = also PR in github stuff...)
# - *New* comments
# - On issue/PRs which are still open
if hook_type != "issue_comment" \
or hook_infos["action"] != "created" \
or hook_infos["issue"]["state"] != "open": \
# idk what code we want to return
abort(400)
# Check the comment contains proper keyword trigger
body = hook_infos["comment"]["body"].strip()[:100].lower()
triggers = ["!testme", "!gogogadgetoci", "By the power of systemd, I invoke The Great App CI to test this Pull Request!"]
if not any(trigger.lower() in body for trigger in triggers):
# idk what code we want to return
abort(403)
# We only accept this from people which are member/owner of the org/repo
# https://docs.github.com/en/free-pro-team@latest/graphql/reference/enums#commentauthorassociation
if hook_infos["comment"]["author_association"] not in ["MEMBER", "OWNER"]:
# idk what code we want to return
abort(403)
# Fetch the PR infos (yeah they ain't in the initial infos we get @_@)
pr_infos_url = hook_infos["issue"]["pull_request"]["url"]
async with aiohttp.ClientSession() as session:
async with session.get(pr_infos_url) as resp:
pr_infos = await resp.json()
branch_name = pr_infos["head"]["ref"]
repo = pr_infos["head"]["repo"]["html_url"]
url_to_test = f"{repo}/tree/{branch_name}"
app_id = pr_infos["base"]["repo"]["name"].rstrip("")
if app_id.endswith("_ynh"):
app_id = app_id[:-len("_ynh")]
pr_id = str(pr_infos["number"])
# Add the job for the corresponding app (with the branch url)
await create_job(app_id, url_to_test, job_comment=f"PR #{pr_id}, {branch_name}")
# TODO : write a comment back using yunobot with a jenkins-like badge + link to the created job
return response.text("ok")
def show_coro(c): def show_coro(c):
data = { data = {
'txt': str(c), 'txt': str(c),