Skip to content
This repository was archived by the owner on Jun 13, 2025. It is now read-only.

Commit 3bd830d

Browse files
authored
Merge branch 'main' into feb_03_gql_error
2 parents 4d04612 + e885187 commit 3bd830d

File tree

5 files changed

+156
-15
lines changed

5 files changed

+156
-15
lines changed

webhook_handlers/views/__init__.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from shared.metrics import Counter
2+
3+
WEBHOOKS_RECEIVED = Counter(
4+
"api_webhooks_received",
5+
"Incoming webhooks, broken down by service, event type, and action",
6+
[
7+
"service",
8+
"event",
9+
"action",
10+
],
11+
)
12+
13+
WEBHOOKS_ERRORED = Counter(
14+
"api_webhooks_errored",
15+
"Webhooks that cannot be processed, broken down by service and error reason",
16+
[
17+
"service",
18+
"event",
19+
"action",
20+
"error_reason",
21+
],
22+
)

webhook_handlers/views/bitbucket.py

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,47 @@
1414
WebhookHandlerErrorMessages,
1515
)
1616

17+
from . import WEBHOOKS_ERRORED, WEBHOOKS_RECEIVED
18+
1719
log = logging.getLogger(__name__)
1820

1921

2022
class BitbucketWebhookHandler(APIView):
2123
permission_classes = [AllowAny]
24+
service_name = "bitbucket"
25+
26+
def _inc_recv(self):
27+
event, _, action = self.event.partition(":")
28+
WEBHOOKS_RECEIVED.labels(
29+
service=self.service_name, event=event, action=action
30+
).inc()
31+
32+
def _inc_err(self, reason: str):
33+
event, _, action = self.event.partition(":")
34+
WEBHOOKS_ERRORED.labels(
35+
service=self.service_name,
36+
event=event,
37+
action=action,
38+
error_reason=reason,
39+
).inc()
2240

2341
def post(self, request, *args, **kwargs):
2442
self.event = self.request.META.get(BitbucketHTTPHeaders.EVENT)
2543
event_hook_id = self.request.META.get(BitbucketHTTPHeaders.UUID)
2644

27-
repo = get_object_or_404(
28-
Repository,
29-
author__service="bitbucket",
30-
service_id=self.request.data["repository"]["uuid"][1:-1],
31-
hookid=event_hook_id,
32-
)
45+
try:
46+
repo = get_object_or_404(
47+
Repository,
48+
author__service="bitbucket",
49+
service_id=self.request.data["repository"]["uuid"][1:-1],
50+
hookid=event_hook_id,
51+
)
52+
except Exception as e:
53+
self._inc_err("repo_not_found")
54+
raise e
55+
3356
if not repo.active:
57+
self._inc_err("repo_not_active")
3458
return Response(data=WebhookHandlerErrorMessages.SKIP_NOT_ACTIVE)
3559

3660
log.info(
@@ -39,20 +63,25 @@ def post(self, request, *args, **kwargs):
3963
)
4064

4165
if self.event == BitbucketWebhookEvents.PULL_REQUEST_CREATED:
66+
self._inc_recv()
4267
return self._handle_pull_request_created_event(repo)
4368
elif self.event in (
4469
BitbucketWebhookEvents.PULL_REQUEST_FULFILLED,
4570
BitbucketWebhookEvents.PULL_REQUEST_REJECTED,
4671
):
72+
self._inc_recv()
4773
return self._handle_pull_request_state_change(repo)
4874
elif self.event == BitbucketWebhookEvents.REPO_PUSH:
75+
self._inc_recv()
4976
return self._handle_repo_push_event(repo)
5077
elif self.event in (
5178
BitbucketWebhookEvents.REPO_COMMIT_STATUS_CREATED,
5279
BitbucketWebhookEvents.REPO_COMMIT_STATUS_UPDATED,
5380
):
81+
self._inc_recv()
5482
return self._handle_repo_commit_status_change(repo)
5583

84+
self._inc_err("unhandled_event")
5685
return Response()
5786

5887
def _handle_pull_request_created_event(self, repo):

webhook_handlers/views/bitbucket_server.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,52 @@
1313
WebhookHandlerErrorMessages,
1414
)
1515

16+
from . import WEBHOOKS_ERRORED, WEBHOOKS_RECEIVED
17+
1618
log = logging.getLogger(__name__)
1719

1820

1921
class BitbucketServerWebhookHandler(APIView):
2022
# https://confluence.atlassian.com/bitbucketserver/event-payload-938025882.html
2123
permission_classes = [AllowAny]
24+
service_name = "bitbucket_server"
25+
26+
def _inc_recv(self):
27+
event, _, action = self.event.partition(":")
28+
WEBHOOKS_RECEIVED.labels(
29+
service=self.service_name, event=event, action=action
30+
).inc()
31+
32+
def _inc_err(self, reason: str):
33+
event, _, action = self.event.partition(":")
34+
WEBHOOKS_ERRORED.labels(
35+
service=self.service_name,
36+
event=event,
37+
action=action,
38+
error_reason=reason,
39+
).inc()
2240

2341
def _get_repo(self, event, body):
2442
if event.startswith("repo:"):
2543
repo_id = body["repository"]["id"]
2644
elif event.startswith("pr:"):
2745
repo_id = body["pullRequest"]["toRef"]["repository"]["id"]
2846

29-
return get_object_or_404(
30-
Repository, author__service="bitbucket_server", service_id=repo_id
31-
)
47+
try:
48+
return get_object_or_404(
49+
Repository, author__service="bitbucket_server", service_id=repo_id
50+
)
51+
except Exception as e:
52+
self._inc_err("repo_not_found")
53+
raise e
3254

3355
def post(self, request, *args, **kwargs):
3456
self.event = self.request.META.get(BitbucketServerHTTPHeaders.EVENT)
3557
event_hook_id = self.request.META.get(BitbucketServerHTTPHeaders.UUID)
3658

3759
repo = self._get_repo(self.event, self.request.data)
3860
if not repo.active:
61+
self._inc_err("repo_not_active")
3962
return Response(data=WebhookHandlerErrorMessages.SKIP_NOT_ACTIVE)
4063

4164
log.info(
@@ -44,15 +67,19 @@ def post(self, request, *args, **kwargs):
4467
)
4568

4669
if self.event == BitbucketServerWebhookEvents.PULL_REQUEST_CREATED:
70+
self._inc_recv()
4771
return self._handle_pull_request_created_event(repo)
4872
elif self.event in (
4973
BitbucketServerWebhookEvents.PULL_REQUEST_MERGED,
5074
BitbucketServerWebhookEvents.PULL_REQUEST_REJECTED,
5175
):
76+
self._inc_recv()
5277
return self._handle_pull_request_state_change(repo)
5378
elif self.event == BitbucketServerWebhookEvents.REPO_REFS_CHANGED:
79+
self._inc_recv()
5480
return self._handle_repo_refs_change(repo)
5581

82+
self._inc_err("unhandled_event")
5683
return Response()
5784

5885
def _handle_pull_request_created_event(self, repo):

webhook_handlers/views/github.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
WebhookHandlerErrorMessages,
3131
)
3232

33+
from . import WEBHOOKS_ERRORED, WEBHOOKS_RECEIVED
34+
3335
log = logging.getLogger(__name__)
3436

3537

@@ -49,6 +51,21 @@ class GithubWebhookHandler(APIView):
4951

5052
service_name = "github"
5153

54+
def _inc_recv(self):
55+
action = self.request.data.get("action", "")
56+
WEBHOOKS_RECEIVED.labels(
57+
service=self.service_name, event=self.event, action=action
58+
).inc()
59+
60+
def _inc_err(self, reason: str):
61+
action = self.request.data.get("action", "")
62+
WEBHOOKS_ERRORED.labels(
63+
service=self.service_name,
64+
event=self.event,
65+
action=action,
66+
error_reason=reason,
67+
).inc()
68+
5269
def validate_signature(self, request):
5370
key = get_config(
5471
self.service_name,
@@ -79,9 +96,11 @@ def validate_signature(self, request):
7996
or len(computed_sig) != len(expected_sig)
8097
or not constant_time_compare(computed_sig, expected_sig)
8198
):
99+
self._inc_err("validation_failed")
82100
raise PermissionDenied()
83101

84102
def unhandled_webhook_event(self, request, *args, **kwargs):
103+
self._inc_err("unhandled_event")
85104
return Response(data=WebhookHandlerErrorMessages.UNSUPPORTED_EVENT)
86105

87106
def _get_repo(self, request):
@@ -117,6 +136,7 @@ def _get_repo(self, request):
117136
"Received event for non-existent repository",
118137
extra=dict(repo_service_id=repo_service_id, repo_slug=repo_slug),
119138
)
139+
self._inc_err("repo_not_found")
120140
raise NotFound("Repository does not exist")
121141
else:
122142
try:
@@ -142,6 +162,7 @@ def _get_repo(self, request):
142162
"Received event for non-existent repository",
143163
extra=dict(repo_service_id=repo_service_id, repo_slug=repo_slug),
144164
)
165+
self._inc_err("repo_not_found")
145166
raise NotFound("Repository does not exist")
146167

147168
def ping(self, request, *args, **kwargs):
@@ -708,8 +729,12 @@ def post(self, request, *args, **kwargs):
708729

709730
self.validate_signature(request)
710731

711-
handler = getattr(self, self.event, self.unhandled_webhook_event)
712-
return handler(request, *args, **kwargs)
732+
if handler := getattr(self, self.event, None):
733+
self._inc_recv()
734+
return handler(request, *args, **kwargs)
735+
else:
736+
self._inc_err("unhandled_event")
737+
return self.unhandled_webhook_event(request, *args, **kwargs)
713738

714739

715740
class GithubEnterpriseWebhookHandler(GithubWebhookHandler):

webhook_handlers/views/gitlab.py

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,38 @@
1919
WebhookHandlerErrorMessages,
2020
)
2121

22+
from . import WEBHOOKS_ERRORED, WEBHOOKS_RECEIVED
23+
2224
log = logging.getLogger(__name__)
2325

2426

2527
class GitLabWebhookHandler(APIView):
2628
permission_classes = [AllowAny]
2729
service_name = "gitlab"
2830

31+
def _inc_recv(self):
32+
event_name = self.request.data.get("event_name")
33+
if not event_name:
34+
event_name = self.request.data.get("object_kind")
35+
action = self.request.data.get("object_attributes", {}).get("action", "")
36+
37+
WEBHOOKS_RECEIVED.labels(
38+
service=self.service_name, event=event_name, action=action
39+
).inc()
40+
41+
def _inc_err(self, reason: str):
42+
event_name = self.request.data.get("event_name")
43+
if not event_name:
44+
event_name = self.request.data.get("object_kind")
45+
action = self.request.data.get("object_attributes", {}).get("action", "")
46+
47+
WEBHOOKS_ERRORED.labels(
48+
service=self.service_name,
49+
event=event_name,
50+
action=action,
51+
error_reason=reason,
52+
).inc()
53+
2954
def post(self, request, *args, **kwargs):
3055
"""
3156
Helpful docs for working with GitLab webhooks
@@ -50,14 +75,20 @@ def post(self, request, *args, **kwargs):
5075
# special case - only event that doesn't have a repo yet
5176
if event_name == "project_create":
5277
if event == GitLabWebhookEvents.SYSTEM and is_enterprise:
78+
self._inc_recv()
5379
return self._handle_system_project_create_hook_event()
5480
else:
81+
self._inc_err("permission_denied")
5582
raise PermissionDenied()
5683

57-
# all other events should correspond to a repo in the db
58-
repo = get_object_or_404(
59-
Repository, author__service=self.service_name, service_id=project_id
60-
)
84+
try:
85+
# all other events should correspond to a repo in the db
86+
repo = get_object_or_404(
87+
Repository, author__service=self.service_name, service_id=project_id
88+
)
89+
except Exception as e:
90+
self._inc_err("repo_not_found")
91+
raise e
6192

6293
webhook_validation = bool(
6394
get_config(
@@ -68,17 +99,23 @@ def post(self, request, *args, **kwargs):
6899
self._validate_secret(request, repo.webhook_secret)
69100

70101
if event == GitLabWebhookEvents.PUSH:
102+
self._inc_recv()
71103
return self._handle_push_event(repo)
72104
elif event == GitLabWebhookEvents.JOB:
105+
self._inc_recv()
73106
return self._handle_job_event(repo)
74107
elif event == GitLabWebhookEvents.MERGE_REQUEST:
108+
self._inc_recv()
75109
return self._handle_merge_request_event(repo)
76110
elif event == GitLabWebhookEvents.SYSTEM:
77111
# SYSTEM events have always been gated behind is_enterprise, requires an enterprise_license
78112
if not is_enterprise:
113+
self._inc_err("permission_denied")
79114
raise PermissionDenied()
115+
self._inc_recv()
80116
return self._handle_system_hook_event(repo, event_name)
81117

118+
self._inc_err("unhandled_event")
82119
return Response()
83120

84121
def _handle_push_event(self, repo):
@@ -251,6 +288,7 @@ def _validate_secret(self, request: HttpRequest, webhook_secret: str):
251288
if token and webhook_secret:
252289
if constant_time_compare(webhook_secret, token):
253290
return
291+
self._inc_err("validation_failed")
254292
raise PermissionDenied()
255293

256294

0 commit comments

Comments
 (0)