2016-06-17 19:53:21 +02:00
|
|
|
# Webhooks for external integrations.
|
2022-06-27 00:29:19 +02:00
|
|
|
from typing import Optional, Tuple
|
2023-12-05 21:25:00 +01:00
|
|
|
from urllib.parse import urlsplit
|
2017-11-16 00:43:10 +01:00
|
|
|
|
2016-06-17 19:53:21 +02:00
|
|
|
from django.http import HttpRequest, HttpResponse
|
|
|
|
|
2020-08-20 00:32:15 +02:00
|
|
|
from zerver.decorator import webhook_view
|
2019-02-02 23:53:55 +01:00
|
|
|
from zerver.lib.response import json_success
|
2023-09-27 19:01:31 +02:00
|
|
|
from zerver.lib.typed_endpoint import JsonBodyPayload, typed_endpoint
|
2023-08-12 09:34:31 +02:00
|
|
|
from zerver.lib.validator import WildValue, check_int, check_string
|
2018-03-16 22:53:50 +01:00
|
|
|
from zerver.lib.webhooks.common import check_send_webhook_message
|
2022-11-07 21:24:35 +01:00
|
|
|
from zerver.lib.webhooks.git import get_short_sha
|
2019-02-02 23:53:55 +01:00
|
|
|
from zerver.models import UserProfile
|
2016-06-17 19:53:21 +02:00
|
|
|
|
2021-05-10 07:02:14 +02:00
|
|
|
# Semaphore Classic templates
|
2020-03-14 13:12:03 +01:00
|
|
|
|
2019-04-17 03:57:31 +02:00
|
|
|
BUILD_TEMPLATE = """
|
|
|
|
[Build {build_number}]({build_url}) {status}:
|
|
|
|
* **Commit**: [{commit_hash}: {commit_message}]({commit_url})
|
|
|
|
* **Author**: {email}
|
|
|
|
""".strip()
|
|
|
|
|
|
|
|
DEPLOY_TEMPLATE = """
|
|
|
|
[Deploy {deploy_number}]({deploy_url}) of [build {build_number}]({build_url}) {status}:
|
|
|
|
* **Commit**: [{commit_hash}: {commit_message}]({commit_url})
|
|
|
|
* **Author**: {email}
|
|
|
|
* **Server**: {server_name}
|
|
|
|
""".strip()
|
|
|
|
|
2021-05-10 07:02:14 +02:00
|
|
|
# Semaphore 2.0 templates
|
2020-03-14 13:12:03 +01:00
|
|
|
|
|
|
|
# Currently, Semaphore 2.0 only supports GitHub, while Semaphore Classic
|
|
|
|
# supports Bitbucket too. The payload does not have URLs for commits, tags,
|
|
|
|
# pull requests, etc. So, we use separate templates for GitHub and construct
|
|
|
|
# the URLs ourselves. For any other repository hosting services we use
|
|
|
|
# templates that don't have any links in them.
|
|
|
|
|
|
|
|
GH_PUSH_TEMPLATE = """
|
|
|
|
[{pipeline_name}]({workflow_url}) pipeline **{pipeline_result}**:
|
|
|
|
* **Commit**: [({commit_hash})]({commit_url}) {commit_message}
|
|
|
|
* **Branch**: {branch_name}
|
|
|
|
* **Author**: [{author_name}]({author_url})
|
|
|
|
""".strip()
|
|
|
|
|
|
|
|
PUSH_TEMPLATE = """
|
|
|
|
[{pipeline_name}]({workflow_url}) pipeline **{pipeline_result}**:
|
|
|
|
* **Commit**: ({commit_hash}) {commit_message}
|
|
|
|
* **Branch**: {branch_name}
|
|
|
|
* **Author**: {author_name}
|
|
|
|
""".strip()
|
|
|
|
|
|
|
|
GH_PULL_REQUEST_TEMPLATE = """
|
|
|
|
[{pipeline_name}]({workflow_url}) pipeline **{pipeline_result}**:
|
2021-05-10 07:02:14 +02:00
|
|
|
* **Pull request**: [{pull_request_title}]({pull_request_url})
|
2020-03-14 13:12:03 +01:00
|
|
|
* **Branch**: {branch_name}
|
|
|
|
* **Author**: [{author_name}]({author_url})
|
|
|
|
""".strip()
|
|
|
|
|
|
|
|
PULL_REQUEST_TEMPLATE = """
|
|
|
|
[{pipeline_name}]({workflow_url}) pipeline **{pipeline_result}**:
|
2021-05-10 07:02:14 +02:00
|
|
|
* **Pull request**: {pull_request_title} (#{pull_request_number})
|
2020-03-14 13:12:03 +01:00
|
|
|
* **Branch**: {branch_name}
|
|
|
|
* **Author**: {author_name}
|
|
|
|
""".strip()
|
|
|
|
|
|
|
|
GH_TAG_TEMPLATE = """
|
|
|
|
[{pipeline_name}]({workflow_url}) pipeline **{pipeline_result}**:
|
|
|
|
* **Tag**: [{tag_name}]({tag_url})
|
|
|
|
* **Author**: [{author_name}]({author_url})
|
|
|
|
""".strip()
|
|
|
|
|
|
|
|
TAG_TEMPLATE = """
|
|
|
|
[{pipeline_name}]({workflow_url}) pipeline **{pipeline_result}**:
|
|
|
|
* **Tag**: {tag_name}
|
|
|
|
* **Author**: {author_name}
|
|
|
|
""".strip()
|
|
|
|
|
|
|
|
DEFAULT_TEMPLATE = """
|
|
|
|
[{pipeline_name}]({workflow_url}) pipeline **{pipeline_result}** for {event_name} event
|
|
|
|
""".strip()
|
|
|
|
|
2019-04-17 03:57:31 +02:00
|
|
|
TOPIC_TEMPLATE = "{project}/{branch}"
|
|
|
|
|
2020-03-14 13:12:03 +01:00
|
|
|
GITHUB_URL_TEMPLATES = {
|
2021-02-12 08:20:45 +01:00
|
|
|
"commit": "{repo_url}/commit/{commit_id}",
|
|
|
|
"pull_request": "{repo_url}/pull/{pr_number}",
|
|
|
|
"tag": "{repo_url}/tree/{tag_name}",
|
|
|
|
"user": "https://github.com/{username}",
|
2020-03-14 13:12:03 +01:00
|
|
|
}
|
|
|
|
|
2021-07-16 11:40:46 +02:00
|
|
|
ALL_EVENT_TYPES = ["build", "tag", "unknown", "branch", "deploy", "pull_request"]
|
2020-03-14 13:12:03 +01:00
|
|
|
|
2021-07-16 11:40:46 +02:00
|
|
|
|
|
|
|
@webhook_view("Semaphore", all_event_types=ALL_EVENT_TYPES)
|
2023-08-12 09:34:31 +02:00
|
|
|
@typed_endpoint
|
2021-02-12 08:19:30 +01:00
|
|
|
def api_semaphore_webhook(
|
|
|
|
request: HttpRequest,
|
|
|
|
user_profile: UserProfile,
|
2023-08-12 09:34:31 +02:00
|
|
|
*,
|
2023-09-27 19:01:31 +02:00
|
|
|
payload: JsonBodyPayload[WildValue],
|
2021-02-12 08:19:30 +01:00
|
|
|
) -> HttpResponse:
|
2021-07-16 11:40:46 +02:00
|
|
|
content, project_name, branch_name, event = (
|
2021-02-12 08:20:45 +01:00
|
|
|
semaphore_classic(payload) if "event" in payload else semaphore_2(payload)
|
2020-03-14 13:12:03 +01:00
|
|
|
)
|
2024-01-17 15:53:30 +01:00
|
|
|
topic_name = (
|
2021-02-12 08:19:30 +01:00
|
|
|
TOPIC_TEMPLATE.format(project=project_name, branch=branch_name)
|
|
|
|
if branch_name
|
|
|
|
else project_name
|
2020-03-14 13:12:03 +01:00
|
|
|
)
|
2024-01-17 15:53:30 +01:00
|
|
|
check_send_webhook_message(request, user_profile, topic_name, content, event)
|
2022-01-31 13:44:02 +01:00
|
|
|
return json_success(request)
|
2020-03-14 13:12:03 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2022-06-27 00:29:19 +02:00
|
|
|
def semaphore_classic(payload: WildValue) -> Tuple[str, str, str, str]:
|
2016-06-17 19:53:21 +02:00
|
|
|
# semaphore only gives the last commit, even if there were multiple commits
|
|
|
|
# since the last build
|
2022-06-27 00:29:19 +02:00
|
|
|
branch_name = payload["branch_name"].tame(check_string)
|
|
|
|
project_name = payload["project_name"].tame(check_string)
|
|
|
|
result = payload["result"].tame(check_string)
|
|
|
|
event = payload["event"].tame(check_string)
|
|
|
|
commit_id = payload["commit"]["id"].tame(check_string)
|
|
|
|
commit_url = payload["commit"]["url"].tame(check_string)
|
|
|
|
author_email = payload["commit"]["author_email"].tame(check_string)
|
|
|
|
message = summary_line(payload["commit"]["message"].tame(check_string))
|
2016-06-17 19:53:21 +02:00
|
|
|
|
|
|
|
if event == "build":
|
2022-06-27 00:29:19 +02:00
|
|
|
build_url = payload["build_url"].tame(check_string)
|
|
|
|
build_number = payload["build_number"].tame(check_int)
|
2019-04-17 03:57:31 +02:00
|
|
|
content = BUILD_TEMPLATE.format(
|
|
|
|
build_number=build_number,
|
|
|
|
build_url=build_url,
|
|
|
|
status=result,
|
2022-11-07 21:24:35 +01:00
|
|
|
commit_hash=get_short_sha(commit_id),
|
2019-04-17 03:57:31 +02:00
|
|
|
commit_message=message,
|
|
|
|
commit_url=commit_url,
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
email=author_email,
|
2019-04-17 03:57:31 +02:00
|
|
|
)
|
2016-06-17 19:53:21 +02:00
|
|
|
|
|
|
|
elif event == "deploy":
|
2022-06-27 00:29:19 +02:00
|
|
|
build_url = payload["build_html_url"].tame(check_string)
|
|
|
|
build_number = payload["build_number"].tame(check_int)
|
|
|
|
deploy_url = payload["html_url"].tame(check_string)
|
|
|
|
deploy_number = payload["number"].tame(check_int)
|
|
|
|
server_name = payload["server_name"].tame(check_string)
|
2019-04-17 03:57:31 +02:00
|
|
|
content = DEPLOY_TEMPLATE.format(
|
|
|
|
deploy_number=deploy_number,
|
|
|
|
deploy_url=deploy_url,
|
|
|
|
build_number=build_number,
|
|
|
|
build_url=build_url,
|
|
|
|
status=result,
|
2022-11-07 21:24:35 +01:00
|
|
|
commit_hash=get_short_sha(commit_id),
|
2019-04-17 03:57:31 +02:00
|
|
|
commit_message=message,
|
|
|
|
commit_url=commit_url,
|
|
|
|
email=author_email,
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
server_name=server_name,
|
2019-04-17 03:57:31 +02:00
|
|
|
)
|
2016-06-17 19:53:21 +02:00
|
|
|
|
2017-05-07 21:37:02 +02:00
|
|
|
else: # should never get here
|
2020-06-10 06:40:53 +02:00
|
|
|
content = f"{event}: {result}"
|
2016-06-17 19:53:21 +02:00
|
|
|
|
2021-07-16 11:40:46 +02:00
|
|
|
return content, project_name, branch_name, event
|
2020-03-14 13:12:03 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2022-06-27 00:29:19 +02:00
|
|
|
def semaphore_2(payload: WildValue) -> Tuple[str, str, Optional[str], str]:
|
|
|
|
repo_url = payload["repository"]["url"].tame(check_string)
|
|
|
|
project_name = payload["project"]["name"].tame(check_string)
|
|
|
|
organization_name = payload["organization"]["name"].tame(check_string)
|
|
|
|
author_name = payload["revision"]["sender"]["login"].tame(check_string)
|
|
|
|
workflow_id = payload["workflow"]["id"].tame(check_string)
|
2020-03-14 13:12:03 +01:00
|
|
|
context = dict(
|
|
|
|
author_name=author_name,
|
2021-02-12 08:20:45 +01:00
|
|
|
author_url=GITHUB_URL_TEMPLATES["user"].format(repo_url=repo_url, username=author_name),
|
2022-06-27 00:29:19 +02:00
|
|
|
pipeline_name=payload["pipeline"]["name"].tame(check_string),
|
|
|
|
pipeline_result=payload["pipeline"]["result"].tame(check_string),
|
2021-02-12 08:20:45 +01:00
|
|
|
workflow_url=f"https://{organization_name}.semaphoreci.com/workflows/{workflow_id}",
|
2019-04-17 03:57:31 +02:00
|
|
|
)
|
2022-06-27 00:29:19 +02:00
|
|
|
event = payload["revision"]["reference_type"].tame(check_string)
|
2016-06-17 19:53:21 +02:00
|
|
|
|
2022-06-27 00:29:19 +02:00
|
|
|
if event == "branch": # push event
|
|
|
|
commit_id = payload["revision"]["commit_sha"].tame(check_string)
|
|
|
|
branch_name = payload["revision"]["branch"]["name"].tame(check_string)
|
2020-03-14 13:12:03 +01:00
|
|
|
context.update(
|
|
|
|
branch_name=branch_name,
|
|
|
|
commit_id=commit_id,
|
2022-11-07 21:24:35 +01:00
|
|
|
commit_hash=get_short_sha(commit_id),
|
2022-06-27 00:29:19 +02:00
|
|
|
commit_message=summary_line(payload["revision"]["commit_message"].tame(check_string)),
|
2021-02-12 08:20:45 +01:00
|
|
|
commit_url=GITHUB_URL_TEMPLATES["commit"].format(
|
2021-02-12 08:19:30 +01:00
|
|
|
repo_url=repo_url, commit_id=commit_id
|
|
|
|
),
|
2020-03-14 13:12:03 +01:00
|
|
|
)
|
|
|
|
template = GH_PUSH_TEMPLATE if is_github_repo(repo_url) else PUSH_TEMPLATE
|
|
|
|
content = template.format(**context)
|
2022-06-27 00:29:19 +02:00
|
|
|
elif event == "pull_request":
|
2020-03-14 13:12:03 +01:00
|
|
|
pull_request = payload["revision"]["pull_request"]
|
2022-06-27 00:29:19 +02:00
|
|
|
branch_name = pull_request["branch_name"].tame(check_string)
|
|
|
|
pull_request_title = pull_request["name"].tame(check_string)
|
|
|
|
pull_request_number = pull_request["number"].tame(check_string)
|
2021-02-12 08:20:45 +01:00
|
|
|
pull_request_url = GITHUB_URL_TEMPLATES["pull_request"].format(
|
2021-02-12 08:19:30 +01:00
|
|
|
repo_url=repo_url, pr_number=pull_request_number
|
|
|
|
)
|
2020-03-14 13:12:03 +01:00
|
|
|
context.update(
|
|
|
|
branch_name=branch_name,
|
|
|
|
pull_request_title=pull_request_title,
|
|
|
|
pull_request_url=pull_request_url,
|
|
|
|
pull_request_number=pull_request_number,
|
|
|
|
)
|
|
|
|
template = GH_PULL_REQUEST_TEMPLATE if is_github_repo(repo_url) else PULL_REQUEST_TEMPLATE
|
|
|
|
content = template.format(**context)
|
2022-06-27 00:29:19 +02:00
|
|
|
elif event == "tag":
|
2021-02-12 08:20:45 +01:00
|
|
|
branch_name = ""
|
2022-06-27 00:29:19 +02:00
|
|
|
tag_name = payload["revision"]["tag"]["name"].tame(check_string)
|
2021-02-12 08:20:45 +01:00
|
|
|
tag_url = GITHUB_URL_TEMPLATES["tag"].format(repo_url=repo_url, tag_name=tag_name)
|
2020-03-14 13:12:03 +01:00
|
|
|
context.update(
|
|
|
|
tag_name=tag_name,
|
|
|
|
tag_url=tag_url,
|
|
|
|
)
|
|
|
|
template = GH_TAG_TEMPLATE if is_github_repo(repo_url) else TAG_TEMPLATE
|
|
|
|
content = template.format(**context)
|
|
|
|
else: # should never get here: unknown event
|
2021-02-12 08:20:45 +01:00
|
|
|
branch_name = ""
|
2022-06-27 00:29:19 +02:00
|
|
|
context.update(event_name=event)
|
2020-03-14 13:12:03 +01:00
|
|
|
content = DEFAULT_TEMPLATE.format(**context)
|
2021-07-16 11:40:46 +02:00
|
|
|
return content, project_name, branch_name, event
|
2020-03-14 13:12:03 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-03-14 13:12:03 +01:00
|
|
|
def is_github_repo(repo_url: str) -> bool:
|
2023-12-05 21:25:00 +01:00
|
|
|
return urlsplit(repo_url).hostname == "github.com"
|
2020-04-14 19:04:01 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-04-14 19:04:01 +02:00
|
|
|
def summary_line(message: str) -> str:
|
|
|
|
return message.splitlines()[0]
|