mirror of
https://github.com/zulip/zulip.git
synced 2025-11-04 14:03:30 +00:00
These are the straightforward ones. Note that there is a line in zerver.lib.test_classes.build_webhook_url that lost test coverage. That's because most of our tests test using stream messages so the webhook URLs being tested always have a query parameter. So the line that accounts for there being no query parameters never gets called, which is fine, but we should still keep it.
352 lines
13 KiB
Python
352 lines
13 KiB
Python
from functools import partial
|
|
from typing import Any, Dict, Iterable, Optional, Text
|
|
import re
|
|
|
|
from django.http import HttpRequest, HttpResponse
|
|
|
|
from zerver.decorator import api_key_only_webhook_view
|
|
from zerver.lib.request import REQ, has_request_variables
|
|
from zerver.lib.response import json_success
|
|
from zerver.lib.webhooks.common import check_send_webhook_message
|
|
from zerver.lib.webhooks.git import EMPTY_SHA, \
|
|
SUBJECT_WITH_PR_OR_ISSUE_INFO_TEMPLATE, \
|
|
get_commits_comment_action_message, get_issue_event_message, \
|
|
get_pull_request_event_message, get_push_commits_event_message, \
|
|
get_push_tag_event_message, get_remove_branch_event_message
|
|
from zerver.models import UserProfile
|
|
|
|
class UnknownEventType(Exception):
|
|
pass
|
|
|
|
|
|
def get_push_event_body(payload: Dict[str, Any]) -> Text:
|
|
if payload.get('after') == EMPTY_SHA:
|
|
return get_remove_branch_event_body(payload)
|
|
return get_normal_push_event_body(payload)
|
|
|
|
def get_normal_push_event_body(payload: Dict[str, Any]) -> Text:
|
|
compare_url = u'{}/compare/{}...{}'.format(
|
|
get_repository_homepage(payload),
|
|
payload['before'],
|
|
payload['after']
|
|
)
|
|
|
|
commits = [
|
|
{
|
|
'name': commit.get('author').get('name'),
|
|
'sha': commit.get('id'),
|
|
'message': commit.get('message'),
|
|
'url': commit.get('url')
|
|
}
|
|
for commit in payload['commits']
|
|
]
|
|
|
|
return get_push_commits_event_message(
|
|
get_user_name(payload),
|
|
compare_url,
|
|
get_branch_name(payload),
|
|
commits
|
|
)
|
|
|
|
def get_remove_branch_event_body(payload: Dict[str, Any]) -> Text:
|
|
return get_remove_branch_event_message(
|
|
get_user_name(payload),
|
|
get_branch_name(payload)
|
|
)
|
|
|
|
def get_tag_push_event_body(payload: Dict[str, Any]) -> Text:
|
|
return get_push_tag_event_message(
|
|
get_user_name(payload),
|
|
get_tag_name(payload),
|
|
action="pushed" if payload.get('checkout_sha') else "removed"
|
|
)
|
|
|
|
def get_issue_created_event_body(payload: Dict[str, Any]) -> Text:
|
|
description = payload['object_attributes'].get('description')
|
|
# Filter out multiline hidden comments
|
|
description = re.sub('<!--.*?-->', '', description, 0, re.DOTALL)
|
|
description = description.rstrip()
|
|
return get_issue_event_message(
|
|
get_issue_user_name(payload),
|
|
'created',
|
|
get_object_url(payload),
|
|
payload['object_attributes'].get('iid'),
|
|
description,
|
|
get_objects_assignee(payload)
|
|
)
|
|
|
|
def get_issue_event_body(payload: Dict[str, Any], action: Text) -> Text:
|
|
return get_issue_event_message(
|
|
get_issue_user_name(payload),
|
|
action,
|
|
get_object_url(payload),
|
|
payload['object_attributes'].get('iid'),
|
|
)
|
|
|
|
def get_merge_request_updated_event_body(payload: Dict[str, Any]) -> Text:
|
|
if payload['object_attributes'].get('oldrev'):
|
|
return get_merge_request_event_body(payload, "added commit(s) to")
|
|
return get_merge_request_open_or_updated_body(payload, "updated")
|
|
|
|
def get_merge_request_event_body(payload: Dict[str, Any], action: Text) -> Text:
|
|
pull_request = payload['object_attributes']
|
|
return get_pull_request_event_message(
|
|
get_issue_user_name(payload),
|
|
action,
|
|
pull_request.get('url'),
|
|
pull_request.get('iid'),
|
|
type='MR',
|
|
)
|
|
|
|
def get_merge_request_open_or_updated_body(payload: Dict[str, Any], action: Text) -> Text:
|
|
pull_request = payload['object_attributes']
|
|
return get_pull_request_event_message(
|
|
get_issue_user_name(payload),
|
|
action,
|
|
pull_request.get('url'),
|
|
pull_request.get('iid'),
|
|
pull_request.get('source_branch'),
|
|
pull_request.get('target_branch'),
|
|
pull_request.get('description'),
|
|
get_objects_assignee(payload),
|
|
type='MR',
|
|
)
|
|
|
|
def get_objects_assignee(payload: Dict[str, Any]) -> Optional[Text]:
|
|
assignee_object = payload.get('assignee')
|
|
if assignee_object:
|
|
return assignee_object.get('name')
|
|
return None
|
|
|
|
def get_commented_commit_event_body(payload: Dict[str, Any]) -> Text:
|
|
comment = payload['object_attributes']
|
|
action = u'[commented]({})'.format(comment['url'])
|
|
return get_commits_comment_action_message(
|
|
get_issue_user_name(payload),
|
|
action,
|
|
payload['commit'].get('url'),
|
|
payload['commit'].get('id'),
|
|
comment['note'],
|
|
)
|
|
|
|
def get_commented_merge_request_event_body(payload: Dict[str, Any]) -> Text:
|
|
comment = payload['object_attributes']
|
|
action = u'[commented]({}) on'.format(comment['url'])
|
|
url = u'{}/merge_requests/{}'.format(
|
|
payload['project'].get('web_url'),
|
|
payload['merge_request'].get('iid')
|
|
)
|
|
return get_pull_request_event_message(
|
|
get_issue_user_name(payload),
|
|
action,
|
|
url,
|
|
payload['merge_request'].get('iid'),
|
|
message=comment['note'],
|
|
type='MR'
|
|
)
|
|
|
|
def get_commented_issue_event_body(payload: Dict[str, Any]) -> Text:
|
|
comment = payload['object_attributes']
|
|
action = u'[commented]({}) on'.format(comment['url'])
|
|
url = u'{}/issues/{}'.format(
|
|
payload['project'].get('web_url'),
|
|
payload['issue'].get('iid')
|
|
)
|
|
return get_pull_request_event_message(
|
|
get_issue_user_name(payload),
|
|
action,
|
|
url,
|
|
payload['issue'].get('iid'),
|
|
message=comment['note'],
|
|
type='Issue'
|
|
)
|
|
|
|
def get_commented_snippet_event_body(payload: Dict[str, Any]) -> Text:
|
|
comment = payload['object_attributes']
|
|
action = u'[commented]({}) on'.format(comment['url'])
|
|
url = u'{}/snippets/{}'.format(
|
|
payload['project'].get('web_url'),
|
|
payload['snippet'].get('id')
|
|
)
|
|
return get_pull_request_event_message(
|
|
get_issue_user_name(payload),
|
|
action,
|
|
url,
|
|
payload['snippet'].get('id'),
|
|
message=comment['note'],
|
|
type='Snippet'
|
|
)
|
|
|
|
def get_wiki_page_event_body(payload: Dict[str, Any], action: Text) -> Text:
|
|
return u"{} {} [Wiki Page \"{}\"]({}).".format(
|
|
get_issue_user_name(payload),
|
|
action,
|
|
payload['object_attributes'].get('title'),
|
|
payload['object_attributes'].get('url'),
|
|
)
|
|
|
|
def get_build_hook_event_body(payload: Dict[str, Any]) -> Text:
|
|
build_status = payload.get('build_status')
|
|
if build_status == 'created':
|
|
action = 'was created'
|
|
elif build_status == 'running':
|
|
action = 'started'
|
|
else:
|
|
action = 'changed status to {}'.format(build_status)
|
|
return u"Build {} from {} stage {}.".format(
|
|
payload.get('build_name'),
|
|
payload.get('build_stage'),
|
|
action
|
|
)
|
|
|
|
def get_test_event_body(payload: Dict[str, Any]) -> Text:
|
|
return u"Webhook for **{repo}** has been configured successfully! :tada:".format(
|
|
repo=get_repo_name(payload))
|
|
|
|
def get_pipeline_event_body(payload: Dict[str, Any]) -> Text:
|
|
pipeline_status = payload['object_attributes'].get('status')
|
|
if pipeline_status == 'pending':
|
|
action = 'was created'
|
|
elif pipeline_status == 'running':
|
|
action = 'started'
|
|
else:
|
|
action = 'changed status to {}'.format(pipeline_status)
|
|
|
|
builds_status = u""
|
|
for build in payload['builds']:
|
|
builds_status += u"* {} - {}\n".format(build.get('name'), build.get('status'))
|
|
return u"Pipeline {} with build(s):\n{}.".format(action, builds_status[:-1])
|
|
|
|
def get_repo_name(payload: Dict[str, Any]) -> Text:
|
|
return payload['project']['name']
|
|
|
|
def get_user_name(payload: Dict[str, Any]) -> Text:
|
|
return payload['user_name']
|
|
|
|
def get_issue_user_name(payload: Dict[str, Any]) -> Text:
|
|
return payload['user']['name']
|
|
|
|
def get_repository_homepage(payload: Dict[str, Any]) -> Text:
|
|
return payload['repository']['homepage']
|
|
|
|
def get_branch_name(payload: Dict[str, Any]) -> Text:
|
|
return payload['ref'].replace('refs/heads/', '')
|
|
|
|
def get_tag_name(payload: Dict[str, Any]) -> Text:
|
|
return payload['ref'].replace('refs/tags/', '')
|
|
|
|
def get_object_iid(payload: Dict[str, Any]) -> Text:
|
|
return payload['object_attributes']['iid']
|
|
|
|
def get_object_url(payload: Dict[str, Any]) -> Text:
|
|
return payload['object_attributes']['url']
|
|
|
|
EVENT_FUNCTION_MAPPER = {
|
|
'Push Hook': get_push_event_body,
|
|
'Tag Push Hook': get_tag_push_event_body,
|
|
'Test Hook': get_test_event_body,
|
|
'Issue Hook open': get_issue_created_event_body,
|
|
'Issue Hook close': partial(get_issue_event_body, action='closed'),
|
|
'Issue Hook reopen': partial(get_issue_event_body, action='reopened'),
|
|
'Issue Hook update': partial(get_issue_event_body, action='updated'),
|
|
'Note Hook Commit': get_commented_commit_event_body,
|
|
'Note Hook MergeRequest': get_commented_merge_request_event_body,
|
|
'Note Hook Issue': get_commented_issue_event_body,
|
|
'Note Hook Snippet': get_commented_snippet_event_body,
|
|
'Merge Request Hook approved': partial(get_merge_request_event_body, action='approved'),
|
|
'Merge Request Hook open': partial(get_merge_request_open_or_updated_body, action='created'),
|
|
'Merge Request Hook update': get_merge_request_updated_event_body,
|
|
'Merge Request Hook merge': partial(get_merge_request_event_body, action='merged'),
|
|
'Merge Request Hook close': partial(get_merge_request_event_body, action='closed'),
|
|
'Merge Request Hook reopen': partial(get_merge_request_event_body, action='reopened'),
|
|
'Wiki Page Hook create': partial(get_wiki_page_event_body, action='created'),
|
|
'Wiki Page Hook update': partial(get_wiki_page_event_body, action='updated'),
|
|
'Job Hook': get_build_hook_event_body,
|
|
'Build Hook': get_build_hook_event_body,
|
|
'Pipeline Hook': get_pipeline_event_body,
|
|
}
|
|
|
|
@api_key_only_webhook_view("Gitlab")
|
|
@has_request_variables
|
|
def api_gitlab_webhook(request: HttpRequest, user_profile: UserProfile,
|
|
payload: Dict[str, Any]=REQ(argument_type='body'),
|
|
branches: Optional[Text]=REQ(default=None)) -> HttpResponse:
|
|
event = get_event(request, payload, branches)
|
|
if event is not None:
|
|
body = get_body_based_on_event(event)(payload)
|
|
topic = get_subject_based_on_event(event, payload)
|
|
check_send_webhook_message(request, user_profile, topic, body)
|
|
return json_success()
|
|
|
|
def get_body_based_on_event(event: str) -> Any:
|
|
return EVENT_FUNCTION_MAPPER[event]
|
|
|
|
def get_subject_based_on_event(event: str, payload: Dict[str, Any]) -> Text:
|
|
if event == 'Push Hook':
|
|
return u"{} / {}".format(get_repo_name(payload), get_branch_name(payload))
|
|
elif event == 'Job Hook' or event == 'Build Hook':
|
|
return u"{} / {}".format(payload['repository'].get('name'), get_branch_name(payload))
|
|
elif event == 'Pipeline Hook':
|
|
return u"{} / {}".format(
|
|
get_repo_name(payload),
|
|
payload['object_attributes'].get('ref').replace('refs/heads/', ''))
|
|
elif event.startswith('Merge Request Hook'):
|
|
return SUBJECT_WITH_PR_OR_ISSUE_INFO_TEMPLATE.format(
|
|
repo=get_repo_name(payload),
|
|
type='MR',
|
|
id=payload['object_attributes'].get('iid'),
|
|
title=payload['object_attributes'].get('title')
|
|
)
|
|
elif event.startswith('Issue Hook'):
|
|
return SUBJECT_WITH_PR_OR_ISSUE_INFO_TEMPLATE.format(
|
|
repo=get_repo_name(payload),
|
|
type='Issue',
|
|
id=payload['object_attributes'].get('iid'),
|
|
title=payload['object_attributes'].get('title')
|
|
)
|
|
elif event == 'Note Hook Issue':
|
|
return SUBJECT_WITH_PR_OR_ISSUE_INFO_TEMPLATE.format(
|
|
repo=get_repo_name(payload),
|
|
type='Issue',
|
|
id=payload['issue'].get('iid'),
|
|
title=payload['issue'].get('title')
|
|
)
|
|
elif event == 'Note Hook MergeRequest':
|
|
return SUBJECT_WITH_PR_OR_ISSUE_INFO_TEMPLATE.format(
|
|
repo=get_repo_name(payload),
|
|
type='MR',
|
|
id=payload['merge_request'].get('iid'),
|
|
title=payload['merge_request'].get('title')
|
|
)
|
|
|
|
elif event == 'Note Hook Snippet':
|
|
return SUBJECT_WITH_PR_OR_ISSUE_INFO_TEMPLATE.format(
|
|
repo=get_repo_name(payload),
|
|
type='Snippet',
|
|
id=payload['snippet'].get('id'),
|
|
title=payload['snippet'].get('title')
|
|
)
|
|
return get_repo_name(payload)
|
|
|
|
def get_event(request: HttpRequest, payload: Dict[str, Any], branches: Optional[Text]) -> Optional[str]:
|
|
# if there is no 'action' attribute, then this is a test payload
|
|
# and we should ignore it
|
|
event = request.META['HTTP_X_GITLAB_EVENT']
|
|
if event in ['Issue Hook', 'Merge Request Hook', 'Wiki Page Hook']:
|
|
action = payload['object_attributes'].get('action')
|
|
if action is None:
|
|
return 'Test Hook'
|
|
event = "{} {}".format(event, action)
|
|
elif event == 'Note Hook':
|
|
action = payload['object_attributes'].get('noteable_type')
|
|
event = "{} {}".format(event, action)
|
|
elif event == 'Push Hook':
|
|
if branches is not None:
|
|
branch = get_branch_name(payload)
|
|
if branches.find(branch) == -1:
|
|
return None
|
|
|
|
if event in list(EVENT_FUNCTION_MAPPER.keys()):
|
|
return event
|
|
raise UnknownEventType(u'Event {} is unknown and cannot be handled'.format(event))
|