mirror of
https://github.com/YunoHost/webhooks.git
synced 2024-09-03 19:56:54 +02:00
542 lines
21 KiB
Python
542 lines
21 KiB
Python
# import os
|
|
import hmac
|
|
import hashlib
|
|
import asyncio
|
|
|
|
# import subprocess
|
|
|
|
from sanic import Sanic
|
|
from sanic.response import text
|
|
from sanic.exceptions import abort
|
|
|
|
|
|
app = Sanic(name="Webhooks")
|
|
|
|
secret = open("./github_webhook_secret", "r").read().strip()
|
|
gitbot_password = open("./gitbot_password", "r").read().strip()
|
|
|
|
other_chans = {
|
|
"doc": "doc",
|
|
"apps": "apps",
|
|
"package_linter": "apps",
|
|
"CI_package_check": "apps",
|
|
"package_check": "apps",
|
|
"test_apps": "apps",
|
|
}
|
|
|
|
# TODO
|
|
# * choper tous les templates de notification
|
|
# * choper tous les evenements à suivre
|
|
# * fusionner les 2
|
|
# * déployer
|
|
|
|
|
|
async def notify(message, repository="dev"):
|
|
if repository.endswith("_ynh"):
|
|
chan = "apps"
|
|
else:
|
|
chan = other_chans.get(repository, "dev")
|
|
|
|
print(f"{chan} -> {message}")
|
|
|
|
for char in ["'", "`", "!", ";", "$"]:
|
|
message = message.replace(char, "")
|
|
|
|
proc = await asyncio.create_subprocess_shell(
|
|
f"/var/www/webhooks/matrix-commander --markdown -m '{message}' -c /var/www/webhooks/credentials.json --store /var/www/webhooks/store --room 'yunohost-{chan}'"
|
|
)
|
|
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}"
|
|
)
|
|
|
|
|
|
@app.route("/github", methods=["GET"])
|
|
async def github_get(request):
|
|
return 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):
|
|
# 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)
|
|
|
|
# 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")
|
|
|
|
print()
|
|
print(f"Hook type: {hook_type}")
|
|
|
|
repository = request.json["repository"]["name"]
|
|
|
|
# do not notify if the repo is 'apps_translations'
|
|
if repository == "apps_translations":
|
|
return
|
|
|
|
try:
|
|
# https://developer.github.com/v3/activity/events/types/#pushevent
|
|
if hook_type == "push":
|
|
commits = request.json["commits"]
|
|
user = request.json["pusher"]["name"]
|
|
branch = request.json["ref"].split("/", 2)[2]
|
|
|
|
if len(commits) == 1:
|
|
url = commits[0]["url"]
|
|
commit_message = (
|
|
commits[0]["message"].replace("\r\n", " ").replace("\n", " ")
|
|
)
|
|
|
|
if len(commit_message) > 120:
|
|
commit_message = commit_message[:120] + "..."
|
|
|
|
commit_id = url.split("/")[-1][:8]
|
|
await notify(
|
|
f"[{repository}] {user} pushed {len(commits)} commit to {branch}: {commit_message} ([{commit_id}]({url}))",
|
|
repository=repository,
|
|
)
|
|
elif len(commits) > 1:
|
|
url = request.json["compare"]
|
|
commit_ids = url.split("/")[-1]
|
|
await notify(
|
|
f"[{repository}] {user} pushed {len(commits)} commits to {branch} ([{commit_ids}]({url}))",
|
|
repository=repository,
|
|
)
|
|
for commit in commits[-3:]:
|
|
author = commit["author"]["name"]
|
|
commit_message = (
|
|
commit["message"].replace("\r\n", " ").replace("\n", " ")
|
|
)
|
|
|
|
if len(commit_message) > 120:
|
|
commit_message = commit_message[:120] + "..."
|
|
|
|
await notify(
|
|
f"[{repository}/{branch}] {commit_message} - {author}",
|
|
repository=repository,
|
|
)
|
|
else:
|
|
... # case of 0 which means branch deletion
|
|
|
|
# https://developer.github.com/v3/activity/events/types/#commitcommentevent
|
|
elif hook_type == "commit_comment":
|
|
user = request.json["comment"]["user"]["login"]
|
|
commit_short_id = request.json["comment"]["commit_id"][:7]
|
|
comment = request.json["comment"]["body"].replace("\r\n", " ")
|
|
url = request.json["comment"]["html_url"]
|
|
|
|
await notify(
|
|
f"[{repository}] {user} [comment]({url}) on commit {commit_short_id}: {comment}",
|
|
repository=repository,
|
|
)
|
|
|
|
# https://developer.github.com/v3/activity/events/types/#createevent
|
|
elif hook_type == "create":
|
|
kind = request.json["ref_type"]
|
|
user = request.json["sender"]["login"]
|
|
|
|
if kind == "repository":
|
|
await notify(
|
|
f"{user} created new repository {repository}",
|
|
repository=repository,
|
|
)
|
|
elif kind == "branch":
|
|
branch = request.json["ref"]
|
|
await notify(
|
|
f"[{repository}] {user} created new branch {branch}",
|
|
repository=repository,
|
|
)
|
|
elif kind == "tag":
|
|
tag = request.json["ref"]
|
|
await notify(
|
|
f"[{repository}] {user} created new tag {tag}",
|
|
repository=repository,
|
|
)
|
|
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"]
|
|
|
|
ref = request.json["ref"]
|
|
await notify(
|
|
f"[{repository}] {user} deleted {kind} {ref}", repository=repository
|
|
)
|
|
|
|
# https://developer.github.com/v3/activity/events/types/#forkevent
|
|
elif hook_type == "fork":
|
|
forked_repository = request.json["forkee"]["full_name"]
|
|
user = request.json["sender"]["login"]
|
|
url = request.json["forkee"]["html_url"]
|
|
|
|
await notify(
|
|
f"{user} forked {repository} to [{forked_repository}]({url})",
|
|
repository=repository,
|
|
)
|
|
|
|
# https://developer.github.com/v3/activity/events/types/#issuecommentevent
|
|
elif hook_type == "issue_comment":
|
|
user = request.json["sender"]["login"]
|
|
url = request.json["comment"]["html_url"]
|
|
issue_url = request.json["issue"]["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] + "..."
|
|
|
|
await notify(
|
|
f"[{repository}] {user} [commented]({url}) on [issue #{issue_number}]({issue_url}) {issue_title}: {comment}",
|
|
repository=repository,
|
|
)
|
|
|
|
# https://developer.github.com/v3/activity/events/types/#issuesevent
|
|
elif hook_type == "issues":
|
|
action = request.json["action"]
|
|
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":
|
|
await notify(
|
|
f"[{repository}] {user} {action} [issue #{issue_number}]({url}): {issue_title}",
|
|
repository=repository,
|
|
)
|
|
|
|
elif action in (
|
|
"edited",
|
|
"deleted",
|
|
"transferred",
|
|
"pinned",
|
|
"unpinned",
|
|
"closed",
|
|
"reopened",
|
|
):
|
|
await notify(
|
|
f"[{repository}] {user} {action} [issue #{issue_number}]({url}): {issue_title}",
|
|
repository=repository,
|
|
)
|
|
|
|
elif action in ("assigned", "unassigned"):
|
|
assigned_user = request.json["assignee"]["login"]
|
|
await notify(
|
|
f"[{repository}] {user} {action} {assigned_user} on [issue #{issue_number}]({url}): {issue_title}",
|
|
repository=repository,
|
|
)
|
|
|
|
elif action in ("labeled", "unlabeled"):
|
|
label = request.json["label"]["name"]
|
|
await notify(
|
|
f"[{repository}] {user} {action} {label} on [issue #{issue_number}]({url}): {issue_title}",
|
|
repository=repository,
|
|
)
|
|
|
|
elif action == "milestoned":
|
|
milestone = request.json["issue"]["milestone"]["title"]
|
|
await notify(
|
|
f"[{repository}] {user} set {milestone} on [issue #{issue_number}]({url}): {issue_title}",
|
|
repository=repository,
|
|
)
|
|
|
|
elif action == "demilestoned":
|
|
await notify(
|
|
f"[{repository}] {user} {action} [issue #{issue_number}]({url}): {issue_title}",
|
|
repository=repository,
|
|
)
|
|
|
|
else:
|
|
await notify(
|
|
f"[{repository}] WARNING: unknown 'issues' action: {action}",
|
|
repository=repository,
|
|
)
|
|
|
|
# https://developer.github.com/v3/activity/events/types/#labelevent
|
|
elif hook_type == "label":
|
|
action = request.json["action"]
|
|
label = request.json["label"]["name"]
|
|
user = request.json["sender"]["login"]
|
|
|
|
await notify(
|
|
f"[{repository}] {user} {action} label {label}", repository=repository
|
|
)
|
|
|
|
# https://developer.github.com/v3/activity/events/types/#milestoneevent
|
|
elif hook_type == "milestone":
|
|
action = request.json["action"]
|
|
user = request.json["sender"]["login"]
|
|
milestone = request.json["milestone"]["title"]
|
|
|
|
await notify(
|
|
f"[{repository}] {user} {action} milestone {milestone}",
|
|
repository=repository,
|
|
)
|
|
|
|
# https://developer.github.com/v3/activity/events/types/#pullrequestreviewcommentevent
|
|
elif hook_type == "pull_request_review_comment":
|
|
action = request.json["action"]
|
|
user = request.json["sender"]["login"]
|
|
pull_request_number = request.json["pull_request"]["number"]
|
|
pull_request_title = request.json["pull_request"]["title"]
|
|
comment = request.json["comment"]["body"].replace("\r\n", " ")
|
|
url = request.json["comment"]["html_url"]
|
|
|
|
if len(comment) > 120:
|
|
comment = comment[:120] + "..."
|
|
|
|
if action == "created":
|
|
await notify(
|
|
f"[{repository}] {user} [commented]({url}) on pull request #{pull_request_number} {pull_request_title}: {comment}",
|
|
repository=repository,
|
|
)
|
|
else:
|
|
await notify(
|
|
f"[{repository}] {user} {action} a [comment]({url}) on pull request #{pull_request_number} {pull_request_title}: {comment}",
|
|
repository=repository,
|
|
)
|
|
|
|
# https://developer.github.com/v3/activity/events/types/#pullrequestreviewevent
|
|
elif hook_type == "pull_request_review":
|
|
action = request.json["action"]
|
|
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", " ")
|
|
|
|
# to avoid duplicated with pull_request_review_comment event
|
|
if state == "commented" and not comment:
|
|
pass
|
|
else:
|
|
await notify(
|
|
f"[{repository}] {user} {state} [pull request #{pull_request_number}]({url}) {pull_request_title}{comment}",
|
|
repository=repository,
|
|
)
|
|
|
|
else:
|
|
await notify(
|
|
f"[{repository}] {user} {action} review [pull request #{pull_request_number}]({url}): {pull_request_title}",
|
|
repository=repository,
|
|
)
|
|
|
|
# https://developer.github.com/v3/activity/events/types/#pullrequestevent
|
|
elif hook_type == "pull_request":
|
|
action = request.json["action"]
|
|
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"]
|
|
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", " ")
|
|
|
|
if action in (
|
|
"opened",
|
|
"edited",
|
|
"deleted",
|
|
"transferred",
|
|
"pinned",
|
|
"unpinned",
|
|
"reopened",
|
|
):
|
|
await notify(
|
|
f"[{repository}] {user} {action} [pull request #{pull_request_number}]({url}): {pull_request_title}",
|
|
repository=repository,
|
|
)
|
|
|
|
elif action in ("labeled", "unlabeled"):
|
|
label = request.json["label"]["name"]
|
|
await notify(
|
|
f"[{repository}] {user} {action} {label} on [pull request #{pull_request_number}]({url}): {pull_request_title}",
|
|
repository=repository,
|
|
)
|
|
|
|
elif action == "closed":
|
|
if request.json["pull_request"]["merged"]:
|
|
action = "merged"
|
|
await notify(
|
|
f"[{repository}] {user} {action} [pull request #{pull_request_number}]({url}): {pull_request_title}",
|
|
repository=repository,
|
|
)
|
|
|
|
elif action == "ready_for_review":
|
|
await notify(
|
|
f"[{repository}] {user} just made [pull request #{pull_request_number}]({url}) ready for review: {pull_request_title}",
|
|
repository=repository,
|
|
)
|
|
|
|
# super weird, this action is not supposed to be possible for pull_request :|
|
|
elif action == "milestoned":
|
|
milestone = request.json["pull_request"]["milestone"]
|
|
await notify(
|
|
f"[{repository}] {user} set {milestone} [pull request #{pull_request_number}]({url}): {pull_request_title}",
|
|
repository=repository,
|
|
)
|
|
|
|
# super weird, this action is not supposed to be possible for pull_request :|
|
|
elif action == "demilestoned":
|
|
await notify(
|
|
f"[{repository}] {user} {action} [pull request #{pull_request_number}]({url}): {pull_request_title}",
|
|
repository=repository,
|
|
)
|
|
|
|
elif action == "converted_to_draft":
|
|
await notify(
|
|
f"[{repository}] {user} converted to draft the [pull request #{pull_request_number}]({url}): {pull_request_title}",
|
|
repository=repository,
|
|
)
|
|
|
|
elif action == "assigned":
|
|
assigned_user = request.json["assignee"]["login"]
|
|
await notify(
|
|
f"[{repository}] {user} {action} {assigned_user} on [pull request #{pull_request_number}]({url}): {pull_request_title}",
|
|
repository=repository,
|
|
)
|
|
|
|
elif action == "auto_merge_enabled":
|
|
await notify(
|
|
f"[{repository}] Auto-merge has been enabled by {user} on [pull request #{pull_request_number}]({url}): {pull_request_title}",
|
|
repository=repository,
|
|
)
|
|
|
|
elif action in (
|
|
"review_requested",
|
|
"review_request_removed",
|
|
"synchronize",
|
|
):
|
|
pass # we don't care about those...
|
|
|
|
else:
|
|
await notify(
|
|
f"WARNING: unknown 'pull_request' action: {action}",
|
|
repository=repository,
|
|
)
|
|
|
|
# https://developer.github.com/v3/activity/events/types/#repositoryevent
|
|
elif hook_type == "repository":
|
|
action = request.json["action"]
|
|
user = request.json["sender"]["login"]
|
|
url = request.json["repository"]["html_url"]
|
|
description = request.json["repository"]["description"]
|
|
|
|
if not description:
|
|
description = ""
|
|
else:
|
|
description = ": " + description
|
|
|
|
await notify(
|
|
f"{user} {action} repository {repository}{description} {url}",
|
|
repository=repository,
|
|
)
|
|
|
|
# https://developer.github.com/v3/activity/events/types/#releaseevent
|
|
elif hook_type == "release":
|
|
action = request.json["action"]
|
|
user = request.json["sender"]["login"]
|
|
url = request.json["release"]["html_url"]
|
|
release_tag = request.json["release"]["tag_name"]
|
|
release_title = request.json["release"]["name"]
|
|
|
|
await notify(
|
|
f"[repository] {user} {action} [new release #{release_tag}]({url}) {release_title}",
|
|
repository=repository,
|
|
)
|
|
|
|
# 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"]
|
|
user = request.json["sender"]["login"]
|
|
url = request.json["commit"]["html_url"]
|
|
commit_message = request.json["commit"]["commit"]["message"].replace(
|
|
"\n", " "
|
|
)
|
|
if request.json["commit"]["commit"]["committer"]:
|
|
commit_author = request.json["commit"]["commit"]["committer"]["name"]
|
|
else:
|
|
commit_author = request.json["commit"]["commit"]["author"]["name"]
|
|
branches = ", ".join((x["name"] for x in request.json["branches"]))
|
|
|
|
if state not in ("success", "pending"):
|
|
if description == "Pipeline failed on GitLab":
|
|
pipeline_id = target_url.split("/")[-1]
|
|
await notify(
|
|
f"[{repository}] 🔴 Pipeline [#{pipeline_id}]({target_url}) failed on branch {branches}"
|
|
)
|
|
elif description == "Pipeline canceled on GitLab":
|
|
pipeline_id = target_url.split("/")[-1]
|
|
await notify(
|
|
f"[{repository}] ✖️ 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}'
|
|
)
|
|
else:
|
|
print(
|
|
f"Status weird stuff: [{repository}] {user} state: {state}, description: {description}, target_url: {target_url} - {url}"
|
|
)
|
|
|
|
return text("ok")
|
|
|
|
except Exception as e:
|
|
import traceback
|
|
|
|
traceback.print_exc()
|
|
|
|
try:
|
|
print(request.json)
|
|
except Exception():
|
|
pass
|
|
|
|
await notify(
|
|
f"Error in Webhooks: exception {e} on {hook_type} webhooks, please see logs"
|
|
)
|
|
abort(500)
|
|
|
|
|
|
@app.route("/")
|
|
async def index(request):
|
|
return text("Webhooks server.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app.run("127.0.0.1", port="4567")
|