diff --git a/src/sentry/integrations/github/tasks/sync_repos.py b/src/sentry/integrations/github/tasks/sync_repos.py index 06740e1c7a497d..c008433b1308b8 100644 --- a/src/sentry/integrations/github/tasks/sync_repos.py +++ b/src/sentry/integrations/github/tasks/sync_repos.py @@ -246,7 +246,7 @@ def github_repo_sync_beat() -> None: name="github_repo_sync", schedule_key="github-repo-sync-beat", queryset=OrganizationIntegration.objects.filter( - integration__provider="github", + integration__provider__in=["github", "github_enterprise"], integration__status=ObjectStatus.ACTIVE, status=ObjectStatus.ACTIVE, ), diff --git a/src/sentry/integrations/github_enterprise/webhook.py b/src/sentry/integrations/github_enterprise/webhook.py index 61c3b59ef649bb..20adf4f2a91c1c 100644 --- a/src/sentry/integrations/github_enterprise/webhook.py +++ b/src/sentry/integrations/github_enterprise/webhook.py @@ -21,6 +21,7 @@ from sentry.integrations.github.webhook import ( GitHubWebhook, InstallationEventWebhook, + InstallationRepositoriesEventWebhook, IssuesEventWebhook, PullRequestEventWebhook, PushEventWebhook, @@ -106,6 +107,12 @@ class GitHubEnterpriseInstallationEventWebhook(GitHubEnterpriseWebhook, Installa pass +class GitHubEnterpriseInstallationRepositoriesEventWebhook( + GitHubEnterpriseWebhook, InstallationRepositoriesEventWebhook +): + pass + + class GitHubEnterprisePushEventWebhook(GitHubEnterpriseWebhook, PushEventWebhook): pass @@ -348,6 +355,7 @@ class GitHubEnterpriseWebhookEndpoint(GitHubEnterpriseWebhookBase): "push": GitHubEnterprisePushEventWebhook, "pull_request": GitHubEnterprisePullRequestEventWebhook, "installation": GitHubEnterpriseInstallationEventWebhook, + "installation_repositories": GitHubEnterpriseInstallationRepositoriesEventWebhook, "issues": GitHubEnterpriseIssuesEventWebhook, } diff --git a/tests/sentry/integrations/github/tasks/test_sync_repos.py b/tests/sentry/integrations/github/tasks/test_sync_repos.py index f6f224b0c737be..bb1f14f74fb51a 100644 --- a/tests/sentry/integrations/github/tasks/test_sync_repos.py +++ b/tests/sentry/integrations/github/tasks/test_sync_repos.py @@ -8,11 +8,12 @@ from sentry.constants import ObjectStatus from sentry.integrations.github.integration import GitHubIntegrationProvider from sentry.integrations.github.tasks.sync_repos import sync_repos_for_org +from sentry.integrations.github_enterprise.integration import GitHubEnterpriseIntegrationProvider from sentry.integrations.models.organization_integration import OrganizationIntegration from sentry.models.auditlogentry import AuditLogEntry from sentry.models.repository import Repository from sentry.silo.base import SiloMode -from sentry.testutils.cases import IntegrationTestCase +from sentry.testutils.cases import IntegrationTestCase, TestCase from sentry.testutils.silo import assume_test_silo_mode, assume_test_silo_mode_of, control_silo_test @@ -218,3 +219,44 @@ def test_rate_limited_raises_for_retry(self, _: MagicMock) -> None: with self.feature("organizations:github-repo-auto-sync"), pytest.raises(RetryTaskError): sync_repos_for_org(self.oi.id) + + +@control_silo_test +class SyncReposForOrgGHETestCase(TestCase): + @patch("sentry.integrations.github.client.GitHubBaseClient.get_repos") + def test_creates_new_repos_for_ghe(self, mock_get_repos: MagicMock) -> None: + GitHubEnterpriseIntegrationProvider().setup() + + integration = self.create_integration( + organization=self.organization, + external_id="35.232.149.196:12345", + provider="github_enterprise", + metadata={ + "domain_name": "35.232.149.196/testorg", + "installation_id": "12345", + "installation": { + "id": "2", + "private_key": "private_key", + "verify_ssl": True, + }, + }, + ) + oi = OrganizationIntegration.objects.get( + organization_id=self.organization.id, integration=integration + ) + + mock_get_repos.return_value = [ + {"id": 1, "full_name": "testorg/repo1"}, + {"id": 2, "full_name": "testorg/repo2"}, + ] + + with self.feature( + ["organizations:github-repo-auto-sync", "organizations:github-repo-auto-sync-apply"] + ): + sync_repos_for_org(oi.id) + + with assume_test_silo_mode(SiloMode.CELL): + repos = Repository.objects.filter(organization_id=self.organization.id).order_by("name") + + assert len(repos) == 2 + assert repos[0].provider == "integrations:github_enterprise" diff --git a/tests/sentry/integrations/github_enterprise/test_webhooks.py b/tests/sentry/integrations/github_enterprise/test_webhooks.py index cbf5456cd32cfe..849e25261f56bc 100644 --- a/tests/sentry/integrations/github_enterprise/test_webhooks.py +++ b/tests/sentry/integrations/github_enterprise/test_webhooks.py @@ -14,6 +14,9 @@ PULL_REQUEST_OPENED_EVENT_EXAMPLE, PUSH_EVENT_EXAMPLE_INSTALLATION, ) +from sentry.integrations.github_enterprise.webhook import ( + GitHubEnterpriseInstallationRepositoriesEventWebhook, +) from sentry.integrations.services.integration import integration_service from sentry.models.commit import Commit from sentry.models.commitauthor import CommitAuthor @@ -253,6 +256,66 @@ def test_missing_signature_fail_without_option_set(self, mock_installation: Magi assert b"Missing headers X-Hub-Signature-256 or X-Hub-Signature" in response.content +@patch("sentry.integrations.github_enterprise.client.get_jwt") +@patch("sentry.integrations.github_enterprise.webhook.get_installation_metadata") +class InstallationRepositoriesEventWebhookTest(APITestCase): + def setUp(self) -> None: + self.url = "/extensions/github-enterprise/webhook/" + self.metadata = { + "url": "35.232.149.196", + "id": "2", + "name": "test-app", + "webhook_secret": "b3002c3e321d4b7880360d397db2ccfd", + "private_key": "private_key", + "verify_ssl": True, + } + + @patch( + "sentry.integrations.github.tasks.sync_repos_on_install_change.sync_repos_on_install_change.apply_async" + ) + def test_handler_dispatches_task_with_ghe_provider( + self, + mock_apply_async: MagicMock, + mock_get_installation_metadata: MagicMock, + mock_get_jwt: MagicMock, + ) -> None: + """Verify the GHE handler looks up integrations with the correct provider.""" + mock_get_jwt.return_value = "" + mock_get_installation_metadata.return_value = self.metadata + + integration = self.create_integration( + external_id="35.232.149.196:12345", + organization=self.project.organization, + provider="github_enterprise", + metadata={ + "domain_name": "35.232.149.196/testorg", + "installation_id": "12345", + "installation": { + "id": "2", + "private_key": "private_key", + "verify_ssl": True, + }, + }, + ) + + handler = GitHubEnterpriseInstallationRepositoriesEventWebhook() + handler( + event={ + "installation": {"id": 12345}, + "action": "added", + "repositories_added": [{"id": 1, "full_name": "testorg/repo", "private": False}], + "repositories_removed": [], + "repository_selection": "selected", + "sender": {"id": 1, "login": "testuser"}, + }, + host="35.232.149.196", + ) + + mock_apply_async.assert_called_once() + kwargs = mock_apply_async.call_args[1]["kwargs"] + assert kwargs["integration_id"] == integration.id + + @patch("sentry.integrations.github_enterprise.client.get_jwt") @patch("sentry.integrations.github_enterprise.webhook.get_installation_metadata") class PushEventWebhookTest(APITestCase):