webhooks/server.py

527 lines
20 KiB
Python
Raw Normal View History

2021-04-15 18:22:38 +02:00
# import os
2019-02-05 20:51:40 +01:00
import hmac
import hashlib
import asyncio
2021-04-15 18:22:38 +02:00
# import subprocess
2019-02-05 20:51:40 +01:00
from sanic import Sanic
from sanic.response import text
from sanic.exceptions import abort
app = Sanic()
secret = open("./github_webhook_secret", "r").read().strip()
2019-02-06 00:58:50 +01:00
gitbot_password = open("./gitbot_password", "r").read().strip()
2019-02-05 20:51:40 +01:00
other_chans = {
"doc": "doc",
2019-02-06 17:23:49 +01:00
"apps": "apps",
"package_linter": "apps",
"CI_package_check": "apps",
"package_check": "apps",
"test_apps": "apps",
2019-02-05 20:51:40 +01:00
}
# TODO
# * choper tous les templates de notification
# * choper tous les evenements à suivre
# * fusionner les 2
# * déployer
2020-05-04 14:16:45 +02:00
async def notify(message, repository="dev"):
2019-02-06 17:23:49 +01:00
if repository.endswith("_ynh"):
chan = "apps"
else:
chan = other_chans.get(repository, "dev")
2019-02-05 20:51:40 +01:00
print(f"{chan} -> {message}")
2023-01-10 16:27:30 +01:00
for char in ["'", "`", "!", ";", "$"]:
message = message.replace(char, "")
2020-05-04 14:17:42 +02:00
proc = await asyncio.create_subprocess_shell(
2023-01-20 12:41:58 +01:00
f"/var/www/webhooks/matrix-commander --markdown -m '{message}' -c /var/www/webhooks/credentials.json --store /var/www/webhooks/store --room 'yunohost-{chan}'"
2020-05-04 14:17:42 +02:00
)
2023-01-10 16:27:30 +01:00
try:
await proc.communicate()
await proc.wait()
except Exception as e:
if type(e).__name__ == "CancelledError":
pass
else:
raise Exception(f" {type(e).__name__} while trying to notify about commit '{commit_message}' on {repository}/{branch}: {e}")
2019-02-05 20:51:40 +01:00
2020-05-04 14:17:42 +02:00
@app.route("/github", methods=["GET"])
async def github_get(request):
2020-05-04 14:17:42 +02:00
return text(
"You aren't supposed to go on this page using a browser, it's for webhooks push instead."
)
2020-05-04 14:17:42 +02:00
@app.route("/github", methods=["POST"])
2019-02-05 20:51:40 +01:00
async def github(request):
# Only SHA1 is supported
2020-05-04 14:17:42 +02:00
header_signature = request.headers.get("X-Hub-Signature")
2019-02-05 20:51:40 +01:00
if header_signature is None:
print("no header X-Hub-Signature")
abort(403)
2020-05-04 14:17:42 +02:00
sha_name, signature = header_signature.split("=")
if sha_name != "sha1":
2019-02-05 20:51:40 +01:00
print("signing algo isn't sha1, it's '%s'" % sha_name)
abort(501)
# HMAC requires the key to be bytes, but data is string
2019-02-05 23:17:23 +01:00
mac = hmac.new(secret.encode(), msg=request.body, digestmod=hashlib.sha1)
2019-02-05 20:51:40 +01:00
if not hmac.compare_digest(str(mac.hexdigest()), str(signature)):
abort(403)
hook_type = request.headers.get("X-Github-Event")
2019-02-06 13:53:57 +01:00
print()
2019-02-05 20:51:40 +01:00
print(f"Hook type: {hook_type}")
2019-02-06 13:56:49 +01:00
try:
# https://developer.github.com/v3/activity/events/types/#pushevent
if hook_type == "push":
repository = request.json["repository"]["name"]
commits = request.json["commits"]
user = request.json["pusher"]["name"]
branch = request.json["ref"].split("/", 2)[2]
if len(commits) == 1:
url = commits[0]["url"]
2020-05-04 14:17:42 +02:00
commit_message = (
commits[0]["message"].replace("\r\n", " ").replace("\n", " ")
)
if len(commit_message) > 120:
commit_message = commit_message[:120] + "..."
2023-01-20 12:45:09 +01:00
commit_id = url.split("/")[-1]
2020-05-04 14:17:42 +02:00
await notify(
2023-01-20 12:45:09 +01:00
f"[{repository}] @{user} pushed {len(commits)} commit to {branch}: {commit_message} ([{commit_id}]({url}))",
2020-05-04 14:17:42 +02:00
repository=repository,
)
2019-02-06 23:12:11 +01:00
elif len(commits) > 1:
2019-02-06 13:56:49 +01:00
url = request.json["compare"]
2023-01-20 12:45:09 +01:00
commit_ids = url.split("/")[-1]
2020-05-04 14:17:42 +02:00
await notify(
2023-01-20 12:45:09 +01:00
f"[{repository}] @{user} pushed {len(commits)} commits to {branch} ([{commit_ids}]({url}))",
2020-05-04 14:17:42 +02:00
repository=repository,
)
for commit in commits[-3:]:
2019-02-06 13:56:49 +01:00
author = commit["author"]["name"]
2020-05-04 14:17:42 +02:00
commit_message = (
commit["message"].replace("\r\n", " ").replace("\n", " ")
)
2019-02-06 13:56:49 +01:00
if len(commit_message) > 120:
commit_message = commit_message[:120] + "..."
2019-02-06 13:56:49 +01:00
2020-05-04 14:17:42 +02:00
await notify(
f"[{repository}/{branch}] {commit_message} - {author}",
repository=repository,
)
2019-02-06 23:12:11 +01:00
else:
... # case of 0 which means branch deletion
2019-02-06 13:56:49 +01:00
# https://developer.github.com/v3/activity/events/types/#commitcommentevent
elif hook_type == "commit_comment":
repository = request.json["repository"]["name"]
user = request.json["comment"]["user"]["login"]
commit_short_id = request.json["comment"]["commit_id"][:7]
comment = request.json["comment"]["body"].replace("\r\n", " ")
2019-02-13 22:02:42 +01:00
url = request.json["comment"]["html_url"]
2019-02-06 13:56:49 +01:00
2020-05-04 14:17:42 +02:00
await notify(
f"[{repository}] @{user} comment on commit {commit_short_id}: {comment} {url}",
repository=repository,
)
2019-02-06 13:56:49 +01:00
# https://developer.github.com/v3/activity/events/types/#createevent
elif hook_type == "create":
kind = request.json["ref_type"]
user = request.json["sender"]["login"]
repository = request.json["repository"]["name"]
if kind == "repository":
2020-05-04 14:17:42 +02:00
await notify(
f"@{user} created new repository {repository}: {url}",
repository=repository,
)
2019-02-06 13:56:49 +01:00
elif kind == "branch":
branch = request.json["ref"]
2020-05-04 14:17:42 +02:00
await notify(
f"[{repository}] @{user} created new branch {branch}",
repository=repository,
)
2019-02-06 13:56:49 +01:00
elif kind == "tag":
tag = request.json["ref"]
2020-05-04 14:17:42 +02:00
await notify(
f"[{repository}] @{user} created new tag {tag}",
repository=repository,
)
2019-02-06 13:56:49 +01:00
else:
print(f"WARNING: unknown 'create' event kind: {kind}")
# https://developer.github.com/v3/activity/events/types/#createevent
elif hook_type == "delete":
kind = request.json["ref_type"]
user = request.json["sender"]["login"]
repository = request.json["repository"]["name"]
ref = request.json["ref"]
2020-05-04 14:17:42 +02:00
await notify(
f"[{repository}] @{user} deleted {kind} {ref}", repository=repository
)
2019-02-06 13:56:49 +01:00
# https://developer.github.com/v3/activity/events/types/#forkevent
elif hook_type == "fork":
repository = request.json["repository"]["name"]
forked_repository = request.json["forkee"]["full_name"]
user = request.json["sender"]["login"]
url = request.json["forkee"]["html_url"]
2020-05-04 14:17:42 +02:00
await notify(
f"@{user} forked {repository} to {forked_repository}: {url}",
repository=repository,
)
2019-02-06 13:56:49 +01:00
# https://developer.github.com/v3/activity/events/types/#issuecommentevent
elif hook_type == "issue_comment":
repository = request.json["repository"]["name"]
user = request.json["sender"]["login"]
url = request.json["comment"]["html_url"]
issue_number = request.json["issue"]["number"]
issue_title = request.json["issue"]["title"]
comment = request.json["comment"]["body"].replace("\r\n", " ")
if len(comment) > 120:
comment = comment[:120] + "..."
2020-05-04 14:17:42 +02:00
await notify(
f"[{repository}] @{user} commented on issue #{issue_number} {issue_title}: {comment} {url}",
repository=repository,
)
2019-02-06 13:56:49 +01:00
# https://developer.github.com/v3/activity/events/types/#issuesevent
elif hook_type == "issues":
action = request.json["action"]
repository = request.json["repository"]["name"]
user = request.json["sender"]["login"]
issue_number = request.json["issue"]["number"]
url = request.json["issue"]["html_url"]
issue_title = request.json["issue"]["title"]
if action == "opened":
2020-05-04 14:17:42 +02:00
await notify(
f"[{repository}] @{user} {action} issue #{issue_number}: {issue_title} {url}",
repository=repository,
)
elif action in (
"edited",
"deleted",
"transferred",
"pinned",
"unpinned",
"closed",
"reopened",
):
await notify(
f"[{repository}] @{user} {action} issue #{issue_number}: {issue_title} {url}",
repository=repository,
)
2019-02-06 13:56:49 +01:00
elif action in ("assigned", "unassigned"):
2019-02-06 18:00:54 +01:00
assigned_user = request.json["assignee"]["login"]
2020-05-04 14:17:42 +02:00
await notify(
f"[{repository}] @{user} {action} {assigned_user} on issue #{issue_number}: {issue_title} {url}",
repository=repository,
)
2019-02-06 13:56:49 +01:00
elif action in ("labeled", "unlabeled"):
2019-02-08 16:42:14 +01:00
label = request.json["label"]["name"]
2020-05-04 14:17:42 +02:00
await notify(
f"[{repository}] @{user} {action} {label} on issue #{issue_number}: {issue_title} {url}",
repository=repository,
)
2019-02-06 13:56:49 +01:00
elif action == "milestoned":
2019-02-06 23:02:18 +01:00
milestone = request.json["issue"]["milestone"]["title"]
2020-05-04 14:17:42 +02:00
await notify(
f"[{repository}] @{user} set {milestone} on issue #{issue_number}: {issue_title} {url}",
repository=repository,
)
2019-02-06 13:56:49 +01:00
elif action == "demilestoned":
2020-05-04 14:17:42 +02:00
await notify(
f"[{repository}] @{user} {action} issue #{issue_number}: {issue_title} {url}",
repository=repository,
)
2019-02-06 13:00:17 +01:00
2019-02-06 13:56:49 +01:00
else:
2020-05-04 14:17:42 +02:00
await notify(
f"[{repository}] WARNING: unknown 'issues' action: {action}",
repository=repository,
)
2019-02-06 13:00:17 +01:00
2019-02-06 13:56:49 +01:00
# https://developer.github.com/v3/activity/events/types/#labelevent
elif hook_type == "label":
action = request.json["action"]
label = request.json["label"]["name"]
2019-02-06 13:56:49 +01:00
repository = request.json["repository"]["name"]
user = request.json["sender"]["login"]
2020-05-04 14:17:42 +02:00
await notify(
f"[{repository}] @{user} {action} label {label}", repository=repository
)
2019-02-06 13:56:49 +01:00
# https://developer.github.com/v3/activity/events/types/#milestoneevent
elif hook_type == "milestone":
action = request.json["action"]
repository = request.json["repository"]["name"]
user = request.json["sender"]["login"]
milestone = request.json["milestone"]["title"]
2020-05-04 14:17:42 +02:00
await notify(
f"[{repository}] @{user} {action} milestone {milestone}",
repository=repository,
)
2019-02-06 13:56:49 +01:00
# https://developer.github.com/v3/activity/events/types/#pullrequestreviewcommentevent
elif hook_type == "pull_request_review_comment":
action = request.json["action"]
repository = request.json["repository"]["name"]
user = request.json["sender"]["login"]
pull_request_number = request.json["pull_request"]["number"]
2019-02-08 16:10:40 +01:00
pull_request_title = request.json["pull_request"]["title"]
2019-02-06 13:56:49 +01:00
comment = request.json["comment"]["body"].replace("\r\n", " ")
url = request.json["comment"]["html_url"]
if len(comment) > 120:
comment = comment[:120] + "..."
2019-02-20 23:16:11 +01:00
if action == "created":
2020-05-04 14:17:42 +02:00
await notify(
f"[{repository}] @{user} commented on pull request #{pull_request_number} {pull_request_title}: {comment} {url}",
repository=repository,
)
2019-02-20 23:16:11 +01:00
else:
2020-05-04 14:17:42 +02:00
await notify(
f"[{repository}] @{user} {action} a comment on pull request #{pull_request_number} {pull_request_title}: {comment} {url}",
repository=repository,
)
2019-02-06 13:56:49 +01:00
# https://developer.github.com/v3/activity/events/types/#pullrequestreviewevent
elif hook_type == "pull_request_review":
action = request.json["action"]
repository = request.json["repository"]["name"]
user = request.json["sender"]["login"]
pull_request_number = request.json["pull_request"]["number"]
pull_request_title = request.json["pull_request"]["title"]
url = request.json["review"]["html_url"]
if action == "submitted":
state = request.json["review"]["state"]
comment = request.json["review"]["body"]
if comment and len(comment) > 120:
comment = ": " + comment[:120].replace("\r\n", " ") + "..."
elif not comment:
comment = ""
else:
comment = ": " + comment.replace("\r\n", " ")
2019-02-20 23:14:58 +01:00
# to avoid duplicated with pull_request_review_comment event
if state == "commented" and not comment:
pass
else:
2020-05-04 14:17:42 +02:00
await notify(
f"[{repository}] @{user} {state} pull request #{pull_request_number} {pull_request_title}{comment} {url}",
repository=repository,
)
2019-02-06 13:19:09 +01:00
2019-02-06 13:56:49 +01:00
else:
2020-05-04 14:17:42 +02:00
await notify(
f"[{repository}] @{user} {action} review pull request #{pull_request_number}: {pull_request_title} {url}",
repository=repository,
)
2019-02-06 13:56:49 +01:00
# https://developer.github.com/v3/activity/events/types/#pullrequestevent
elif hook_type == "pull_request":
action = request.json["action"]
repository = request.json["repository"]["name"]
user = request.json["sender"]["login"]
pull_request_number = request.json["pull_request"]["number"]
pull_request_title = request.json["pull_request"]["title"]
url = request.json["pull_request"]["html_url"]
2019-02-06 17:34:10 +01:00
comment = request.json["pull_request"]["body"]
if comment and len(comment) > 120:
comment = ": " + comment[:120].replace("\r\n", " ") + "..."
elif not comment:
comment = ""
else:
comment = ": " + comment.replace("\r\n", " ")
2019-02-06 13:56:49 +01:00
2020-05-04 14:17:42 +02:00
if action in (
"opened",
"edited",
"deleted",
"transferred",
"pinned",
"unpinned",
"reopened",
):
await notify(
f"[{repository}] @{user} {action} pull request #{pull_request_number}: {pull_request_title} {url}",
repository=repository,
)
2019-02-06 13:56:49 +01:00
elif action in ("labeled", "unlabeled"):
2019-02-08 16:42:14 +01:00
label = request.json["label"]["name"]
2020-05-04 14:17:42 +02:00
await notify(
f"[{repository}] @{user} {action} {label} on pull request #{pull_request_number}: {pull_request_title} {url}",
repository=repository,
)
2019-02-06 13:56:49 +01:00
elif action == "closed":
if request.json["pull_request"]["merged"]:
action = "merged"
2020-05-04 14:17:42 +02:00
await notify(
f"[{repository}] @{user} {action} pull request #{pull_request_number}: {pull_request_title} {url}",
repository=repository,
)
2019-02-06 13:56:49 +01:00
elif action == "ready_for_review":
2020-05-04 14:17:42 +02:00
await notify(
f"[{repository}] @{user} just made pull request #{pull_request_number} ready for review: {pull_request_title} {url}",
repository=repository,
)
2019-02-06 13:56:49 +01:00
# super weird, this action is not supposed to be possible for pull_request :|
elif action == "milestoned":
milestone = request.json["pull_request"]["milestone"]
2020-05-04 14:17:42 +02:00
await notify(
f"[{repository}] @{user} set {milestone} pull request #{pull_request_number}: {pull_request_title} {url}",
repository=repository,
)
2019-02-06 13:56:49 +01:00
# super weird, this action is not supposed to be possible for pull_request :|
elif action == "demilestoned":
2020-05-04 14:17:42 +02:00
await notify(
f"[{repository}] @{user} {action} pull request #{pull_request_number}: {pull_request_title} {url}",
repository=repository,
)
elif action in (
"review_requested",
"review_request_removed",
"synchronize",
):
2019-02-06 13:56:49 +01:00
pass # we don't care about those...
2019-02-06 13:19:09 +01:00
2019-02-06 13:56:49 +01:00
else:
2020-05-04 14:17:42 +02:00
await notify(
f"WARNING: unknown 'pull_request' action: {action}",
repository=repository,
)
2019-02-06 13:56:49 +01:00
# https://developer.github.com/v3/activity/events/types/#repositoryevent
elif hook_type == "repository":
action = request.json["action"]
repository = request.json["repository"]["name"]
user = request.json["sender"]["login"]
url = request.json["repository"]["html_url"]
description = request.json["repository"]["description"]
if not description:
description = ""
else:
description = ": " + description
2020-05-04 14:17:42 +02:00
await notify(
f"@{user} {action} repository {repository}{description} {url}",
repository=repository,
)
2019-02-06 13:56:49 +01:00
# https://developer.github.com/v3/activity/events/types/#releaseevent
elif hook_type == "release":
action = request.json["action"]
repository = request.json["repository"]["name"]
user = request.json["sender"]["login"]
url = request.json["release"]["html_url"]
release_tag = request.json["release"]["tag_name"]
release_title = request.json["release"]["name"]
2020-05-04 14:17:42 +02:00
await notify(
f"[repository] @{user} {action} new release #{release_tag} {release_title} {url}",
repository=repository,
)
2019-02-06 13:56:49 +01:00
# https://developer.github.com/v3/activity/events/types/#statusevent
elif hook_type == "status":
state = request.json["state"]
description = request.json["description"]
target_url = request.json["target_url"]
repository = request.json["repository"]["name"]
user = request.json["sender"]["login"]
url = request.json["commit"]["html_url"]
2020-05-06 21:59:24 +02:00
commit_message = request.json["commit"]["commit"]["message"].replace("\n", " ")
2020-06-15 13:26:52 +02:00
if request.json["commit"]["commit"]["committer"]:
commit_author = request.json["commit"]["commit"]["committer"]["name"]
else:
2020-06-14 23:27:05 +02:00
commit_author = request.json["commit"]["commit"]["author"]["name"]
2020-05-07 00:12:34 +02:00
branches = ", ".join((x["name"] for x in request.json["branches"]))
2019-02-06 13:56:49 +01:00
2019-02-20 23:09:52 +01:00
if state not in ("success", "pending"):
if description == "Pipeline failed on GitLab":
pipeline_id = target_url.split("/")[-1]
await notify(
2023-01-20 12:46:35 +01:00
f'[{repository}] :red_circle: Pipeline [#{pipeline_id}]({target_url}) failed on branch {branches}'
)
elif description == "Pipeline canceled on GitLab":
2023-01-20 12:38:26 +01:00
pipeline_id = target_url.split("/")[-1]
await notify(
2023-01-20 12:46:35 +01:00
f'[{repository}] :heavy_multiplication_x: Pipeline [#{pipeline_id}]({target_url}) canceled on branch {branches}'
)
else:
await notify(
f'[{repository}] {description} {target_url} on commit {url} "{commit_message}" by @{commit_author} on branch{"es" if len(branches) > 1 else ""} {branches}'
)
2019-02-20 23:06:47 +01:00
else:
2020-05-04 14:17:42 +02:00
print(
f"Status weird stuff: [{repository}] @{user} state: {state}, description: {description}, target_url: {target_url} - {url}"
)
2019-02-06 13:56:49 +01:00
return text("ok")
except Exception as e:
import traceback
2020-05-04 14:17:42 +02:00
2019-02-06 13:56:49 +01:00
traceback.print_exc()
try:
print(request.json)
except Exception():
pass
2020-05-04 14:17:42 +02:00
await notify(
f"Error in Webhooks: exception {e} on {hook_type} webhooks, please see logs"
)
2019-02-06 13:56:49 +01:00
abort(500)
2019-02-05 20:51:40 +01:00
@app.route("/")
async def index(request):
return text("Webhooks server.")
2020-05-04 14:17:42 +02:00
if __name__ == "__main__":
2023-01-10 16:27:30 +01:00
app.run('127.0.0.1', port="4567")