2016-04-21 19:18:41 +02:00
|
|
|
# Webhooks for teamcity integration
|
2016-06-05 23:57:48 +02:00
|
|
|
|
2017-11-16 00:43:10 +01:00
|
|
|
import logging
|
|
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
|
|
|
|
import ujson
|
2016-04-21 19:18:41 +02:00
|
|
|
from django.db.models import Q
|
2016-09-12 17:30:42 +02:00
|
|
|
from django.http import HttpRequest, HttpResponse
|
2016-06-05 23:57:48 +02:00
|
|
|
|
2017-10-31 04:25:48 +01:00
|
|
|
from zerver.decorator import api_key_only_webhook_view
|
2018-08-01 21:03:30 +02:00
|
|
|
from zerver.lib.actions import check_send_private_message, \
|
|
|
|
send_rate_limited_pm_notification_to_bot_owner
|
|
|
|
from zerver.lib.send_email import FromAddress
|
2017-10-31 04:25:48 +01:00
|
|
|
from zerver.lib.request import REQ, has_request_variables
|
2017-11-16 00:43:10 +01:00
|
|
|
from zerver.lib.response import json_error, json_success
|
2018-03-17 18:26:27 +01:00
|
|
|
from zerver.lib.webhooks.common import check_send_webhook_message
|
2017-11-16 00:43:10 +01:00
|
|
|
from zerver.models import Realm, UserProfile
|
2016-04-21 19:18:41 +02:00
|
|
|
|
2018-08-01 21:03:30 +02:00
|
|
|
MISCONFIGURED_PAYLOAD_TYPE_ERROR_MESSAGE = """
|
|
|
|
Hi there! Your bot {bot_name} just received a TeamCity payload in a
|
|
|
|
format that Zulip doesn't recognize. This usually indicates a
|
|
|
|
configuration issue in your TeamCity webhook settings. Please make sure
|
|
|
|
that you set the **Payload Format** option to **Legacy Webhook (JSON)**
|
|
|
|
in your TeamCity webhook configuration. Contact {support_email} if you
|
|
|
|
need further help!
|
|
|
|
"""
|
|
|
|
|
2017-11-04 07:47:46 +01:00
|
|
|
def guess_zulip_user_from_teamcity(teamcity_username: str, realm: Realm) -> Optional[UserProfile]:
|
2016-04-21 19:18:41 +02:00
|
|
|
try:
|
|
|
|
# Try to find a matching user in Zulip
|
|
|
|
# We search a user's full name, short name,
|
|
|
|
# and beginning of email address
|
|
|
|
user = UserProfile.objects.filter(
|
2017-01-24 07:06:13 +01:00
|
|
|
Q(full_name__iexact=teamcity_username) |
|
|
|
|
Q(short_name__iexact=teamcity_username) |
|
|
|
|
Q(email__istartswith=teamcity_username),
|
|
|
|
is_active=True,
|
|
|
|
realm=realm).order_by("id")[0]
|
2016-04-21 19:18:41 +02:00
|
|
|
return user
|
|
|
|
except IndexError:
|
|
|
|
return None
|
|
|
|
|
2017-11-04 07:47:46 +01:00
|
|
|
def get_teamcity_property_value(property_list: List[Dict[str, str]], name: str) -> Optional[str]:
|
2016-04-21 19:18:41 +02:00
|
|
|
for property in property_list:
|
|
|
|
if property['name'] == name:
|
|
|
|
return property['value']
|
|
|
|
return None
|
|
|
|
|
2016-05-12 22:49:36 +02:00
|
|
|
@api_key_only_webhook_view('Teamcity')
|
2016-04-21 19:18:41 +02:00
|
|
|
@has_request_variables
|
2017-12-06 19:37:30 +01:00
|
|
|
def api_teamcity_webhook(request: HttpRequest, user_profile: UserProfile,
|
2018-03-17 18:26:27 +01:00
|
|
|
payload: Dict[str, Any]=REQ(argument_type='body')) -> HttpResponse:
|
2018-08-01 21:03:30 +02:00
|
|
|
message = payload.get('build')
|
|
|
|
if message is None:
|
|
|
|
# Ignore third-party specific (e.g. Slack/HipChat) payload formats
|
|
|
|
# and notify the bot owner
|
|
|
|
message = MISCONFIGURED_PAYLOAD_TYPE_ERROR_MESSAGE.format(
|
|
|
|
bot_name=user_profile.full_name,
|
|
|
|
support_email=FromAddress.SUPPORT,
|
|
|
|
).strip()
|
|
|
|
send_rate_limited_pm_notification_to_bot_owner(
|
|
|
|
user_profile, user_profile.realm, message)
|
|
|
|
|
|
|
|
return json_success()
|
2016-04-21 19:18:41 +02:00
|
|
|
|
|
|
|
build_name = message['buildFullName']
|
|
|
|
build_url = message['buildStatusUrl']
|
|
|
|
changes_url = build_url + '&tab=buildChangesDiv'
|
|
|
|
build_number = message['buildNumber']
|
|
|
|
build_result = message['buildResult']
|
|
|
|
build_result_delta = message['buildResultDelta']
|
|
|
|
build_status = message['buildStatus']
|
|
|
|
|
|
|
|
if build_result == 'success':
|
|
|
|
if build_result_delta == 'fixed':
|
2018-02-07 03:26:08 +01:00
|
|
|
status = 'has been fixed! :thumbs_up:'
|
2016-04-21 19:18:41 +02:00
|
|
|
else:
|
2018-02-07 03:26:08 +01:00
|
|
|
status = 'was successful! :thumbs_up:'
|
2016-04-21 19:18:41 +02:00
|
|
|
elif build_result == 'failure':
|
|
|
|
if build_result_delta == 'broken':
|
2018-02-07 03:26:08 +01:00
|
|
|
status = 'is broken with status %s! :thumbs_down:' % (build_status,)
|
2016-04-21 19:18:41 +02:00
|
|
|
else:
|
2018-02-07 03:26:08 +01:00
|
|
|
status = 'is still broken with status %s! :thumbs_down:' % (build_status,)
|
2016-04-21 19:18:41 +02:00
|
|
|
elif build_result == 'running':
|
|
|
|
status = 'has started.'
|
|
|
|
else:
|
2017-01-09 20:45:11 +01:00
|
|
|
status = '(has no message specified for status %s)' % (build_status,)
|
2016-04-21 19:18:41 +02:00
|
|
|
|
|
|
|
template = (
|
|
|
|
u'%s build %s %s\n'
|
|
|
|
u'Details: [changes](%s), [build log](%s)')
|
|
|
|
|
|
|
|
body = template % (build_name, build_number, status, changes_url, build_url)
|
2018-06-06 16:29:49 +02:00
|
|
|
|
|
|
|
if 'branchDisplayName' in message:
|
|
|
|
topic = build_name + ' (' + message['branchDisplayName'] + ')'
|
|
|
|
else:
|
|
|
|
topic = build_name
|
2016-04-21 19:18:41 +02:00
|
|
|
|
|
|
|
# Check if this is a personal build, and if so try to private message the user who triggered it.
|
|
|
|
if get_teamcity_property_value(message['teamcityProperties'], 'env.BUILD_IS_PERSONAL') == 'true':
|
2017-11-05 02:48:25 +01:00
|
|
|
# The triggeredBy field gives us the teamcity user full name, and the
|
|
|
|
# "teamcity.build.triggeredBy.username" property gives us the teamcity username.
|
|
|
|
# Let's try finding the user email from both.
|
2016-04-21 19:18:41 +02:00
|
|
|
teamcity_fullname = message['triggeredBy'].split(';')[0]
|
|
|
|
teamcity_user = guess_zulip_user_from_teamcity(teamcity_fullname, user_profile.realm)
|
|
|
|
|
|
|
|
if teamcity_user is None:
|
2016-07-17 19:53:43 +02:00
|
|
|
teamcity_shortname = get_teamcity_property_value(message['teamcityProperties'],
|
|
|
|
'teamcity.build.triggeredBy.username')
|
2016-04-21 19:18:41 +02:00
|
|
|
if teamcity_shortname is not None:
|
|
|
|
teamcity_user = guess_zulip_user_from_teamcity(teamcity_shortname, user_profile.realm)
|
|
|
|
|
|
|
|
if teamcity_user is None:
|
|
|
|
# We can't figure out who started this build - there's nothing we can do here.
|
2017-11-05 02:48:25 +01:00
|
|
|
logging.info("Teamcity webhook couldn't find a matching Zulip user for "
|
|
|
|
"Teamcity user '%s' or '%s'" % (teamcity_fullname, teamcity_shortname))
|
2016-04-21 19:18:41 +02:00
|
|
|
return json_success()
|
|
|
|
|
|
|
|
body = "Your personal build of " + body
|
2017-10-02 07:24:32 +02:00
|
|
|
check_send_private_message(user_profile, request.client, teamcity_user, body)
|
|
|
|
|
2016-04-21 19:18:41 +02:00
|
|
|
return json_success()
|
|
|
|
|
2018-03-17 18:26:27 +01:00
|
|
|
check_send_webhook_message(request, user_profile, topic, body)
|
2016-04-21 19:18:41 +02:00
|
|
|
return json_success()
|