Browse Source

chore(hybrid-cloud): Mark GitHub webhook endpoints as region only (#50218)

Alberto Leal 1 year ago
parent
commit
31445be85f

+ 7 - 3
src/sentry/integrations/github/webhook.py

@@ -12,10 +12,10 @@ from django.utils import timezone
 from django.utils.crypto import constant_time_compare
 from django.utils.decorators import method_decorator
 from django.views.decorators.csrf import csrf_exempt
-from django.views.generic import View
 from rest_framework.request import Request
 
 from sentry import options
+from sentry.api.base import Endpoint, region_silo_endpoint
 from sentry.constants import ObjectStatus
 from sentry.integrations.utils.scope import clear_tags_and_context
 from sentry.models import (
@@ -434,8 +434,11 @@ class PullRequestEventWebhook(Webhook):
             pass
 
 
-class GitHubWebhookBase(View):  # type: ignore
-    """https://developer.github.com/webhooks/"""
+class GitHubWebhookBase(Endpoint):  # type: ignore
+    """https://docs.github.com/en/webhooks-and-events/webhooks/about-webhooks"""
+
+    authentication_classes = ()
+    permission_classes = ()
 
     def get_handler(self, event_type: str) -> Callable[[], Callable[[JSONData], Any]] | None:
         handler: Callable[[], Callable[[JSONData], Any]] | None = self._handlers.get(event_type)
@@ -517,6 +520,7 @@ class GitHubWebhookBase(View):  # type: ignore
         return HttpResponse(status=204)
 
 
+@region_silo_endpoint
 class GitHubIntegrationsWebhookEndpoint(GitHubWebhookBase):
     _handlers = {
         "push": PushEventWebhook,

+ 16 - 11
src/sentry/integrations/github_enterprise/webhook.py

@@ -8,7 +8,6 @@ from django.http import HttpResponse
 from django.utils.crypto import constant_time_compare
 from django.utils.decorators import method_decorator
 from django.views.decorators.csrf import csrf_exempt
-from django.views.generic import View
 from rest_framework.request import Request
 from rest_framework.response import Response
 
@@ -18,24 +17,26 @@ from sentry.integrations.github.webhook import (
     PushEventWebhook,
 )
 from sentry.integrations.utils.scope import clear_tags_and_context
-from sentry.models import Integration
 from sentry.utils import json
 from sentry.utils.sdk import configure_scope
 
 from .repository import GitHubEnterpriseRepositoryProvider
 
 logger = logging.getLogger("sentry.webhooks")
+from sentry.api.base import Endpoint, region_silo_endpoint
+from sentry.services.hybrid_cloud.integration import integration_service
+from sentry.services.hybrid_cloud.integration.model import RpcIntegration
 
 
 def get_installation_metadata(event, host):
     if not host:
         return
-    try:
-        integration = Integration.objects.get(
-            external_id="{}:{}".format(host, event["installation"]["id"]),
-            provider="github_enterprise",
-        )
-    except Integration.DoesNotExist:
+
+    integration = integration_service.get_integration(
+        external_id="{}:{}".format(host, event["installation"]["id"]),
+        provider="github_enterprise",
+    )
+    if integration is None:
         logger.exception("Integration does not exist.")
         return
     return integration.metadata["installation"]
@@ -55,7 +56,7 @@ class GitHubEnterprisePushEventWebhook(PushEventWebhook):
     def get_external_id(self, username: str) -> str:
         return f"github_enterprise:{username}"
 
-    def get_idp_external_id(self, integration: Integration, host: str | None = None) -> str:
+    def get_idp_external_id(self, integration: RpcIntegration, host: str | None = None) -> str:
         return "{}:{}".format(host, integration.metadata["installation"]["id"])
 
     def should_ignore_commit(self, commit):
@@ -72,11 +73,14 @@ class GitHubEnterprisePullRequestEventWebhook(PullRequestEventWebhook):
     def get_external_id(self, username: str) -> str:
         return f"github_enterprise:{username}"
 
-    def get_idp_external_id(self, integration: Integration, host: str | None = None) -> str:
+    def get_idp_external_id(self, integration: RpcIntegration, host: str | None = None) -> str:
         return "{}:{}".format(host, integration.metadata["installation"]["id"])
 
 
-class GitHubEnterpriseWebhookBase(View):
+class GitHubEnterpriseWebhookBase(Endpoint):
+    authentication_classes = ()
+    permission_classes = ()
+
     # https://developer.github.com/webhooks/
     def get_handler(self, event_type):
         return self._handlers.get(event_type)
@@ -168,6 +172,7 @@ class GitHubEnterpriseWebhookBase(View):
             return HttpResponse(status=204)
 
 
+@region_silo_endpoint
 class GitHubEnterpriseWebhookEndpoint(GitHubEnterpriseWebhookBase):
     _handlers = {
         "push": GitHubEnterprisePushEventWebhook,

+ 57 - 49
tests/sentry/integrations/github/test_webhooks.py

@@ -13,9 +13,10 @@ from fixtures.github import (
 from sentry import options
 from sentry.models import Commit, CommitAuthor, GroupLink, Integration, PullRequest, Repository
 from sentry.testutils import APITestCase
-from sentry.testutils.silo import region_silo_test
+from sentry.testutils.silo import exempt_from_silo_limits, region_silo_test
 
 
+@region_silo_test(stable=True)
 class WebhookTest(APITestCase):
     def test_get(self):
 
@@ -64,7 +65,7 @@ class WebhookTest(APITestCase):
         assert response.status_code == 401
 
 
-@region_silo_test
+@region_silo_test(stable=True)
 class PushEventWebhookTest(APITestCase):
     @patch("sentry.integrations.github.client.get_jwt")
     def test_simple(self, mock_get_jwt):
@@ -85,12 +86,13 @@ class PushEventWebhookTest(APITestCase):
         )
 
         future_expires = datetime.now().replace(microsecond=0) + timedelta(minutes=5)
-        integration = Integration.objects.create(
-            external_id="12345",
-            provider="github",
-            metadata={"access_token": "1234", "expires_at": future_expires.isoformat()},
-        )
-        integration.add_organization(project.organization, self.user)
+        with exempt_from_silo_limits():
+            integration = Integration.objects.create(
+                external_id="12345",
+                provider="github",
+                metadata={"access_token": "1234", "expires_at": future_expires.isoformat()},
+            )
+            integration.add_organization(project.organization, self.user)
 
         response = self.client.post(
             path=url,
@@ -141,13 +143,14 @@ class PushEventWebhookTest(APITestCase):
         options.set("github-app.webhook-secret", secret)
 
         future_expires = datetime.now().replace(microsecond=0) + timedelta(minutes=5)
-        integration = Integration.objects.create(
-            provider="github",
-            external_id="12345",
-            name="octocat",
-            metadata={"access_token": "1234", "expires_at": future_expires.isoformat()},
-        )
-        integration.add_organization(project.organization, self.user)
+        with exempt_from_silo_limits():
+            integration = Integration.objects.create(
+                provider="github",
+                external_id="12345",
+                name="octocat",
+                metadata={"access_token": "1234", "expires_at": future_expires.isoformat()},
+            )
+            integration.add_organization(project.organization, self.user)
 
         Repository.objects.create(
             organization_id=project.organization.id,
@@ -218,12 +221,13 @@ class PushEventWebhookTest(APITestCase):
         )
 
         future_expires = datetime.now().replace(microsecond=0) + timedelta(minutes=5)
-        integration = Integration.objects.create(
-            external_id="12345",
-            provider="github",
-            metadata={"access_token": "1234", "expires_at": future_expires.isoformat()},
-        )
-        integration.add_organization(project.organization, self.user)
+        with exempt_from_silo_limits():
+            integration = Integration.objects.create(
+                external_id="12345",
+                provider="github",
+                metadata={"access_token": "1234", "expires_at": future_expires.isoformat()},
+            )
+            integration.add_organization(project.organization, self.user)
 
         org2 = self.create_organization()
         project2 = self.create_project(organization=org2, name="bar")
@@ -236,12 +240,13 @@ class PushEventWebhookTest(APITestCase):
         )
 
         future_expires = datetime.now().replace(microsecond=0) + timedelta(minutes=5)
-        integration = Integration.objects.create(
-            external_id="99",
-            provider="github",
-            metadata={"access_token": "1234", "expires_at": future_expires.isoformat()},
-        )
-        integration.add_organization(org2, self.user)
+        with exempt_from_silo_limits():
+            integration = Integration.objects.create(
+                external_id="99",
+                provider="github",
+                metadata={"access_token": "1234", "expires_at": future_expires.isoformat()},
+            )
+            integration.add_organization(org2, self.user)
 
         response = self.client.post(
             path=url,
@@ -270,7 +275,7 @@ class PushEventWebhookTest(APITestCase):
         assert len(commit_list) == 0
 
 
-@region_silo_test
+@region_silo_test(stable=True)
 class PullRequestEventWebhook(APITestCase):
     def test_opened(self):
         project = self.project  # force creation
@@ -280,13 +285,14 @@ class PullRequestEventWebhook(APITestCase):
         options.set("github-app.webhook-secret", secret)
 
         future_expires = datetime.now().replace(microsecond=0) + timedelta(minutes=5)
-        integration = Integration.objects.create(
-            provider="github",
-            external_id="12345",
-            name="octocat",
-            metadata={"access_token": "1234", "expires_at": future_expires.isoformat()},
-        )
-        integration.add_organization(project.organization, self.user)
+        with exempt_from_silo_limits():
+            integration = Integration.objects.create(
+                provider="github",
+                external_id="12345",
+                name="octocat",
+                metadata={"access_token": "1234", "expires_at": future_expires.isoformat()},
+            )
+            integration.add_organization(project.organization, self.user)
 
         repo = Repository.objects.create(
             organization_id=project.organization.id,
@@ -333,13 +339,14 @@ class PullRequestEventWebhook(APITestCase):
         options.set("github-app.webhook-secret", secret)
 
         future_expires = datetime.now().replace(microsecond=0) + timedelta(minutes=5)
-        integration = Integration.objects.create(
-            provider="github",
-            external_id="12345",
-            name="octocat",
-            metadata={"access_token": "1234", "expires_at": future_expires.isoformat()},
-        )
-        integration.add_organization(project.organization, self.user)
+        with exempt_from_silo_limits():
+            integration = Integration.objects.create(
+                provider="github",
+                external_id="12345",
+                name="octocat",
+                metadata={"access_token": "1234", "expires_at": future_expires.isoformat()},
+            )
+            integration.add_organization(project.organization, self.user)
 
         repo = Repository.objects.create(
             organization_id=project.organization.id,
@@ -382,13 +389,14 @@ class PullRequestEventWebhook(APITestCase):
         options.set("github-app.webhook-secret", secret)
 
         future_expires = datetime.now().replace(microsecond=0) + timedelta(minutes=5)
-        integration = Integration.objects.create(
-            provider="github",
-            external_id="12345",
-            name="octocat",
-            metadata={"access_token": "1234", "expires_at": future_expires.isoformat()},
-        )
-        integration.add_organization(project.organization, self.user)
+        with exempt_from_silo_limits():
+            integration = Integration.objects.create(
+                provider="github",
+                external_id="12345",
+                name="octocat",
+                metadata={"access_token": "1234", "expires_at": future_expires.isoformat()},
+            )
+            integration.add_organization(project.organization, self.user)
 
         repo = Repository.objects.create(
             organization_id=project.organization.id,

+ 48 - 43
tests/sentry/integrations/github_enterprise/test_webhooks.py

@@ -13,9 +13,10 @@ from fixtures.github_enterprise import (
 )
 from sentry.models import Commit, CommitAuthor, Integration, PullRequest, Repository
 from sentry.testutils import APITestCase
-from sentry.testutils.silo import region_silo_test
+from sentry.testutils.silo import exempt_from_silo_limits, region_silo_test
 
 
+@region_silo_test(stable=True)
 class WebhookTest(APITestCase):
     def test_get(self):
         url = "/extensions/github-enterprise/webhook/"
@@ -100,7 +101,7 @@ class WebhookTest(APITestCase):
         assert response.status_code == 204
 
 
-@region_silo_test
+@region_silo_test(stable=True)
 class PushEventWebhookTest(APITestCase):
     @pytest.mark.skip(reason="Host has been taken down")
     @patch("sentry.integrations.github_enterprise.client.get_jwt")
@@ -191,16 +192,17 @@ class PushEventWebhookTest(APITestCase):
             "verify_ssl": True,
         }
 
-        integration = Integration.objects.create(
-            provider="github_enterprise",
-            external_id="35.232.149.196:12345",
-            name="octocat",
-            metadata={
-                "domain_name": "35.232.149.196/baxterthehacker",
-                "installation": {"id": "2", "private_key": "private_key", "verify_ssl": True},
-            },
-        )
-        integration.add_organization(project.organization, self.user)
+        with exempt_from_silo_limits():
+            integration = Integration.objects.create(
+                provider="github_enterprise",
+                external_id="35.232.149.196:12345",
+                name="octocat",
+                metadata={
+                    "domain_name": "35.232.149.196/baxterthehacker",
+                    "installation": {"id": "2", "private_key": "private_key", "verify_ssl": True},
+                },
+            )
+            integration.add_organization(project.organization, self.user)
 
         Repository.objects.create(
             organization_id=project.organization.id,
@@ -340,7 +342,7 @@ class PushEventWebhookTest(APITestCase):
         assert len(commit_list) == 0
 
 
-@region_silo_test
+@region_silo_test(stable=True)
 class PullRequestEventWebhook(APITestCase):
     @patch("sentry.integrations.github_enterprise.webhook.get_installation_metadata")
     def test_opened(self, mock_get_installation_metadata):
@@ -356,16 +358,17 @@ class PullRequestEventWebhook(APITestCase):
             "verify_ssl": True,
         }
 
-        integration = Integration.objects.create(
-            provider="github_enterprise",
-            external_id="35.232.149.196:234",
-            name="octocat",
-            metadata={
-                "domain_name": "35.232.149.196/baxterthehacker",
-                "installation": {"id": "2", "private_key": "private_key", "verify_ssl": True},
-            },
-        )
-        integration.add_organization(project.organization, self.user)
+        with exempt_from_silo_limits():
+            integration = Integration.objects.create(
+                provider="github_enterprise",
+                external_id="35.232.149.196:234",
+                name="octocat",
+                metadata={
+                    "domain_name": "35.232.149.196/baxterthehacker",
+                    "installation": {"id": "2", "private_key": "private_key", "verify_ssl": True},
+                },
+            )
+            integration.add_organization(project.organization, self.user)
 
         repo = Repository.objects.create(
             organization_id=project.organization.id,
@@ -413,16 +416,17 @@ class PullRequestEventWebhook(APITestCase):
             "verify_ssl": True,
         }
 
-        integration = Integration.objects.create(
-            provider="github_enterprise",
-            external_id="35.232.149.196:234",
-            name="octocat",
-            metadata={
-                "domain_name": "35.232.149.196/baxterthehacker",
-                "installation": {"id": "2", "private_key": "private_key", "verify_ssl": True},
-            },
-        )
-        integration.add_organization(project.organization, self.user)
+        with exempt_from_silo_limits():
+            integration = Integration.objects.create(
+                provider="github_enterprise",
+                external_id="35.232.149.196:234",
+                name="octocat",
+                metadata={
+                    "domain_name": "35.232.149.196/baxterthehacker",
+                    "installation": {"id": "2", "private_key": "private_key", "verify_ssl": True},
+                },
+            )
+            integration.add_organization(project.organization, self.user)
 
         repo = Repository.objects.create(
             organization_id=project.organization.id,
@@ -468,16 +472,17 @@ class PullRequestEventWebhook(APITestCase):
             "verify_ssl": True,
         }
 
-        integration = Integration.objects.create(
-            provider="github_enterprise",
-            external_id="35.232.149.196:234",
-            name="octocat",
-            metadata={
-                "domain_name": "35.232.149.196/baxterthehacker",
-                "installation": {"id": "2", "private_key": "private_key", "verify_ssl": True},
-            },
-        )
-        integration.add_organization(project.organization, self.user)
+        with exempt_from_silo_limits():
+            integration = Integration.objects.create(
+                provider="github_enterprise",
+                external_id="35.232.149.196:234",
+                name="octocat",
+                metadata={
+                    "domain_name": "35.232.149.196/baxterthehacker",
+                    "installation": {"id": "2", "private_key": "private_key", "verify_ssl": True},
+                },
+            )
+            integration.add_organization(project.organization, self.user)
 
         repo = Repository.objects.create(
             organization_id=project.organization.id,