Skip to content

Commit 927a661

Browse files
stsewdCopilot
andauthored
Integrations: skip processing webhook events for GitHub App projects (#12542)
Having a webhook on projects already linked to our GH App leads to duplicate builds. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 582aa15 commit 927a661

File tree

4 files changed

+102
-0
lines changed

4 files changed

+102
-0
lines changed

readthedocs/api/v2/views/integrations.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
from readthedocs.core.views.hooks import trigger_sync_versions
3333
from readthedocs.integrations.models import HttpExchange
3434
from readthedocs.integrations.models import Integration
35+
from readthedocs.notifications.models import Notification
3536
from readthedocs.projects.models import Project
37+
from readthedocs.projects.notifications import MESSAGE_PROJECT_DEPRECATED_WEBHOOK
3638
from readthedocs.vcs_support.backends.git import parse_version_from_ref
3739

3840

@@ -448,6 +450,29 @@ def handle_webhook(self):
448450
See https://developer.github.com/v3/activity/events/types/
449451
450452
"""
453+
if self.project.is_github_app_project:
454+
Notification.objects.add(
455+
message_id=MESSAGE_PROJECT_DEPRECATED_WEBHOOK,
456+
attached_to=self.project,
457+
dismissable=True,
458+
)
459+
return Response(
460+
{
461+
"detail": " ".join(
462+
dedent(
463+
"""
464+
This project is connected to our GitHub App and doesn't require a separate webhook, ignoring webhook event.
465+
Remove the deprecated webhook from your repository to avoid duplicate events,
466+
see https://docs.readthedocs.com/platform/stable/reference/git-integration.html#manually-migrating-a-project.
467+
"""
468+
)
469+
.strip()
470+
.splitlines()
471+
)
472+
},
473+
status=status.HTTP_400_BAD_REQUEST,
474+
)
475+
451476
# Get event and trigger other webhook events
452477
action = self.data.get("action", None)
453478
created = self.data.get("created", False)

readthedocs/projects/notifications.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
MESSAGE_PROJECT_SKIP_BUILDS = "project:invalid:skip-builds"
1919
MESSAGE_PROJECT_ADDONS_BY_DEFAULT = "project:addons:by-default"
2020
MESSAGE_PROJECT_SSH_KEY_WITH_WRITE_ACCESS = "project:ssh-key-with-write-access"
21+
MESSAGE_PROJECT_DEPRECATED_WEBHOOK = "project:webhooks:deprecated"
2122

2223
messages = [
2324
Message(
@@ -193,5 +194,19 @@
193194
),
194195
type=WARNING,
195196
),
197+
Message(
198+
id=MESSAGE_PROJECT_DEPRECATED_WEBHOOK,
199+
header=_("Remove deprecated webhook"),
200+
body=_(
201+
textwrap.dedent(
202+
"""
203+
This project is connected to our GitHub App and doesn't require a separate webhook.
204+
<a href="https://docs.readthedocs.com/platform/stable/reference/git-integration.html#manually-migrating-a-project">Remove the deprecated webhook from your repository</a>
205+
to avoid duplicate events.
206+
"""
207+
).strip(),
208+
),
209+
type=INFO,
210+
),
196211
]
197212
registry.add(messages)

readthedocs/projects/views/private.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
from readthedocs.projects.models import Project
7575
from readthedocs.projects.models import ProjectRelationship
7676
from readthedocs.projects.models import WebHook
77+
from readthedocs.projects.notifications import MESSAGE_PROJECT_DEPRECATED_WEBHOOK
7778
from readthedocs.projects.tasks.utils import clean_project_resources
7879
from readthedocs.projects.utils import get_csv_file
7980
from readthedocs.projects.views.base import ProjectAdminMixin
@@ -967,6 +968,22 @@ class IntegrationDelete(IntegrationMixin, DeleteViewWithMessage):
967968
success_message = _("Integration deleted")
968969
http_method_names = ["post"]
969970

971+
def post(self, request, *args, **kwargs):
972+
resp = super().post(request, *args, **kwargs)
973+
# Dismiss notification about removing the GitHub webhook.
974+
project = self.get_project()
975+
if (
976+
project.is_github_app_project
977+
and not project.integrations.filter(
978+
integration_type=Integration.GITHUB_WEBHOOK
979+
).exists()
980+
):
981+
Notification.objects.cancel(
982+
attached_to=project,
983+
message_id=MESSAGE_PROJECT_DEPRECATED_WEBHOOK,
984+
)
985+
return resp
986+
970987

971988
class IntegrationExchangeDetail(IntegrationMixin, DetailView):
972989
model = HttpExchange

readthedocs/rtd_tests/tests/test_api.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
Project,
7676
)
7777
from readthedocs.aws.security_token_service import AWSS3TemporaryCredentials
78+
from readthedocs.projects.notifications import MESSAGE_PROJECT_DEPRECATED_WEBHOOK
7879
from readthedocs.subscriptions.constants import TYPE_CONCURRENT_BUILDS
7980
from readthedocs.subscriptions.products import RTDProductFeature
8081
from readthedocs.vcs_support.backends.git import parse_version_from_ref
@@ -2628,6 +2629,50 @@ def test_github_get_external_version_data(self, trigger_build):
26282629
self.assertEqual(version_data.source_branch, "source_branch")
26292630
self.assertEqual(version_data.base_branch, "master")
26302631

2632+
def test_github_skip_githubapp_projects(self, trigger_build):
2633+
installation = get(
2634+
GitHubAppInstallation,
2635+
installation_id=1111,
2636+
target_id=1111,
2637+
target_type=GitHubAccountType.USER,
2638+
)
2639+
remote_repository = get(
2640+
RemoteRepository,
2641+
remote_id="1234",
2642+
name="repo",
2643+
full_name="user/repo",
2644+
vcs_provider=GitHubAppProvider.id,
2645+
github_app_installation=installation,
2646+
)
2647+
self.project.remote_repository = remote_repository
2648+
self.project.save()
2649+
2650+
assert self.project.is_github_app_project
2651+
assert self.project.notifications.count() == 0
2652+
2653+
client = APIClient()
2654+
payload = '{"ref":"refs/heads/master"}'
2655+
signature = get_signature(
2656+
self.github_integration,
2657+
payload,
2658+
)
2659+
headers = {
2660+
GITHUB_EVENT_HEADER: GITHUB_PUSH,
2661+
GITHUB_SIGNATURE_HEADER: signature,
2662+
}
2663+
resp = client.post(
2664+
reverse("api_webhook_github", kwargs={"project_slug": self.project.slug}),
2665+
json.loads(payload),
2666+
format="json",
2667+
headers=headers,
2668+
)
2669+
assert resp.status_code == 400
2670+
assert "This project is connected to our GitHub App" in resp.data["detail"]
2671+
2672+
notification = self.project.notifications.first()
2673+
assert notification is not None
2674+
assert notification.message_id == MESSAGE_PROJECT_DEPRECATED_WEBHOOK
2675+
26312676
def test_gitlab_webhook_for_branches(self, trigger_build):
26322677
"""GitLab webhook API."""
26332678
client = APIClient()

0 commit comments

Comments
 (0)