diff --git a/src/mobu/dependencies/github.py b/src/mobu/dependencies/github.py index a6455436..9158ff7d 100644 --- a/src/mobu/dependencies/github.py +++ b/src/mobu/dependencies/github.py @@ -5,27 +5,73 @@ import yaml from ..models.github import GitHubConfig +from ..models.user import User +from ..services.github_ci.ci_manager import CiManager +from .context import ContextDependency + +__all__ = ["GitHubConfigDependency", "CiManagerDependency"] class GitHubConfigDependency: """Holds the config for GitHub app integration, loaded from a file.""" def __init__(self) -> None: - self._config: GitHubConfig | None = None + self.config: GitHubConfig def __call__(self) -> GitHubConfig: return self.config - @property - def config(self) -> GitHubConfig: - if not self._config: - raise RuntimeError("GitHubConfigDependency not initialized") - return self._config - def initialize(self, path: Path) -> None: - self._config = GitHubConfig.model_validate( + self.config = GitHubConfig.model_validate( yaml.safe_load(path.read_text()) ) +class CiManagerDependency: + """A process-global object to manage background CI workers. + + It is important to close this when Mobu shuts down to make sure that + GitHub PRs that use the mobu CI app functionality don't have stuck + check runs. + """ + + def __init__(self) -> None: + self.ci_manager: CiManager + + def __call__(self) -> CiManager: + return self.ci_manager + + async def initialize( + self, base_context: ContextDependency, users: list[User] + ) -> None: + self.ci_manager = CiManager( + users=users, + http_client=base_context.process_context.http_client, + gafaelfawr_storage=base_context.process_context.gafaelfawr, + logger=base_context.process_context.logger, + ) + + async def aclose(self) -> None: + await self.ci_manager.aclose() + + +class MaybeCiManagerDependency: + """Try to return a CiManager, but don't blow up if it's not there. + + Used in external routes that return info about mobu, and may be called on + installations that do not have the github ci functionality enabled. + """ + + def __init__(self, dep: CiManagerDependency) -> None: + self.dep = dep + + def __call__(self) -> CiManager | None: + try: + return self.dep.ci_manager + except AttributeError: + return None + + github_config_dependency = GitHubConfigDependency() +ci_manager_dependency = CiManagerDependency() +maybe_ci_manager_dependency = MaybeCiManagerDependency(ci_manager_dependency) diff --git a/src/mobu/factory.py b/src/mobu/factory.py index 0b20f89c..58c92e8e 100644 --- a/src/mobu/factory.py +++ b/src/mobu/factory.py @@ -37,9 +37,11 @@ class ProcessContext: def __init__(self, http_client: AsyncClient) -> None: self.http_client = http_client - logger = structlog.get_logger("mobu") - gafaelfawr = GafaelfawrStorage(http_client, logger) - self.manager = FlockManager(gafaelfawr, http_client, logger) + self.logger = structlog.get_logger("mobu") + self.gafaelfawr = GafaelfawrStorage(self.http_client, self.logger) + self.manager = FlockManager( + self.gafaelfawr, self.http_client, self.logger + ) async def aclose(self) -> None: """Clean up a process context. diff --git a/src/mobu/handlers/github_ci_app.py b/src/mobu/handlers/github_ci_app.py new file mode 100644 index 00000000..46d8b1c4 --- /dev/null +++ b/src/mobu/handlers/github_ci_app.py @@ -0,0 +1,150 @@ +"""Github webhook handlers for CI app.""" + +import asyncio +from typing import Annotated + +from fastapi import APIRouter, Depends, HTTPException +from gidgethub import routing +from gidgethub.sansio import Event +from safir.github.webhooks import GitHubCheckRunEventModel +from safir.slack.webhook import SlackRouteErrorHandler + +from ..config import config +from ..constants import GITHUB_WEBHOOK_WAIT_SECONDS +from ..dependencies.context import RequestContext, anonymous_context_dependency +from ..dependencies.github import ( + ci_manager_dependency, + github_config_dependency, +) +from ..models.github import GitHubCheckSuiteEventModel, GitHubConfig +from ..services.github_ci.ci_manager import CiManager + +__all__ = ["api_router"] + +api_router = APIRouter(route_class=SlackRouteErrorHandler) +"""Registers incoming HTTP GitHub webhook requests""" + + +gidgethub_router = routing.Router() +"""Registers handlers for specific GitHub webhook payloads""" + + +@api_router.post( + "/webhook", + summary="GitHub CI webhooks", + description="Receives webhook events from the GitHub mobu CI app.", + status_code=202, +) +async def post_webhook( + context: Annotated[RequestContext, Depends(anonymous_context_dependency)], + github_config: Annotated[GitHubConfig, Depends(github_config_dependency)], + ci_manager: Annotated[CiManager, Depends(ci_manager_dependency)], +) -> None: + """Process GitHub webhook events for the mobu CI GitHubApp. + + Rejects webhooks from organizations that are not explicitly allowed via the + mobu config. This should be exposed via a Gafaelfawr anonymous ingress. + """ + webhook_secret = config.github_ci_app.webhook_secret + body = await context.request.body() + event = Event.from_http( + context.request.headers, body, secret=webhook_secret + ) + + owner = event.data.get("organization", {}).get("login") + if owner not in github_config.accepted_github_orgs: + context.logger.debug( + "Ignoring GitHub event for unaccepted org", + owner=owner, + accepted_orgs=github_config.accepted_github_orgs, + ) + raise HTTPException( + status_code=403, + detail=( + "Mobu is not configured to accept webhooks from this GitHub" + " org." + ), + ) + + # Bind the X-GitHub-Delivery header to the logger context; this + # identifies the webhook request in GitHub's API and UI for + # diagnostics + context.rebind_logger(github_app="ci", github_delivery=event.delivery_id) + context.logger.debug("Received GitHub webhook", payload=event.data) + # Give GitHub some time to reach internal consistency. + await asyncio.sleep(GITHUB_WEBHOOK_WAIT_SECONDS) + await gidgethub_router.dispatch( + event=event, context=context, ci_manager=ci_manager + ) + + +@gidgethub_router.register("check_suite", action="requested") +async def handle_check_suite_requested( + event: Event, context: RequestContext, ci_manager: CiManager +) -> None: + """Start a run for any check suite request with an associated PR.""" + context.rebind_logger( + github_webhook_event_type="check_suite", + github_webhook_action="requested", + ) + em = GitHubCheckSuiteEventModel.model_validate(event.data) + if not bool(em.check_suite.pull_requests): + context.logger.debug("Ignoring; no associated pull requests") + return + + await ci_manager.enqueue( + installation_id=em.installation.id, + repo_name=em.repository.name, + repo_owner=em.repository.owner.login, + ref=em.check_suite.head_sha, + ) + + context.logger.info("github ci webhook handled") + + +@gidgethub_router.register("check_suite", action="rerequested") +async def handle_check_suite_rerequested( + event: Event, context: RequestContext, ci_manager: CiManager +) -> None: + """Start a run for any check suite re-request with an associated PR.""" + context.rebind_logger( + github_webhook_event_type="check_suite", + github_webhook_action="rerequested", + ) + em = GitHubCheckSuiteEventModel.model_validate(event.data) + if not bool(em.check_suite.pull_requests): + context.logger.debug("Ignoring; no associated pull requests") + return + + await ci_manager.enqueue( + installation_id=em.installation.id, + repo_name=em.repository.name, + repo_owner=em.repository.owner.login, + ref=em.check_suite.head_sha, + ) + + context.logger.info("github ci webhook handled") + + +@gidgethub_router.register("check_run", action="rerequested") +async def handle_check_run_rerequested( + event: Event, context: RequestContext, ci_manager: CiManager +) -> None: + """Start a run for any check run re-request with an associated PR.""" + context.rebind_logger( + github_webhook_event_type="check_run", + github_webhook_action="rerequested", + ) + em = GitHubCheckRunEventModel.model_validate(event.data) + if not bool(em.check_run.pull_requests): + context.logger.debug("Ignoring; no associated pull requests") + return + + await ci_manager.enqueue( + installation_id=em.installation.id, + repo_name=em.repository.name, + repo_owner=em.repository.owner.login, + ref=em.check_run.head_sha, + ) + + context.logger.info("github ci webhook handled") diff --git a/src/mobu/handlers/github_refresh_app.py b/src/mobu/handlers/github_refresh_app.py index ca2feeb1..8231899e 100644 --- a/src/mobu/handlers/github_refresh_app.py +++ b/src/mobu/handlers/github_refresh_app.py @@ -40,11 +40,6 @@ async def post_webhook( Rejects webhooks from organizations that are not explicitly allowed via the mobu config. This should be exposed via a Gafaelfawr anonymous ingress. """ - if not config.github_refresh_app.enabled: - raise HTTPException( - status_code=404, - detail="GitHub refresh app not enabled in this environment", - ) webhook_secret = config.github_refresh_app.webhook_secret body = await context.request.body() event = Event.from_http( diff --git a/src/mobu/main.py b/src/mobu/main.py index 0d0995b2..23522ed3 100644 --- a/src/mobu/main.py +++ b/src/mobu/main.py @@ -23,11 +23,13 @@ from .asyncio import schedule_periodic from .config import config -from .dependencies.context import context_dependency +from .dependencies.context import ContextDependency, context_dependency from .dependencies.github import ( + ci_manager_dependency, github_config_dependency, ) from .handlers.external import external_router +from .handlers.github_ci_app import api_router as github_ci_app_router from .handlers.github_refresh_app import ( api_router as github_refresh_app_router, ) @@ -44,6 +46,7 @@ async def base_lifespan(app: FastAPI) -> AsyncIterator[ContextDependency]: raise RuntimeError("MOBU_ENVIRONMENT_URL was not set") if not config.gafaelfawr_token: raise RuntimeError("MOBU_GAFAELFAWR_TOKEN was not set") + await context_dependency.initialize() await context_dependency.process_context.manager.autostart() @@ -56,6 +59,32 @@ async def base_lifespan(app: FastAPI) -> AsyncIterator[ContextDependency]: app.state.periodic_status.cancel() +@asynccontextmanager +async def github_ci_app_lifespan( + base_context: ContextDependency, +) -> AsyncIterator[None]: + """Set up and tear down the GitHub CI app functionality.""" + if not config.github_config_path: + raise RuntimeError("MOBU_GITHUB_CONFIG_PATH was not set") + if not config.github_ci_app.webhook_secret: + raise RuntimeError("MOBU_GITHUB_CI_APP_WEBHOOK_SECRET was not set") + if not config.github_ci_app.private_key: + raise RuntimeError("MOBU_GITHUB_CI_APP_PRIVATE_KEY was not set") + if not config.github_ci_app.id: + raise RuntimeError("MOBU_GITHUB_CI_APP_ID was not set") + + github_config_dependency.initialize(config.github_config_path) + await ci_manager_dependency.initialize( + base_context=base_context, + users=github_config_dependency.config.users, + ) + await ci_manager_dependency.ci_manager.start() + + yield + + await ci_manager_dependency.aclose() + + @asynccontextmanager async def github_refresh_app_lifespan() -> AsyncIterator[None]: """Set up and tear down the GitHub refresh app functionality.""" @@ -79,6 +108,10 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]: """ async with AsyncExitStack() as stack: base_context = await stack.enter_async_context(base_lifespan(app)) + if config.github_ci_app.enabled: + await stack.enter_async_context( + github_ci_app_lifespan(base_context) + ) if config.github_refresh_app.enabled: await stack.enter_async_context(github_refresh_app_lifespan()) @@ -106,6 +139,10 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]: app.include_router(internal_router) app.include_router(external_router, prefix=config.path_prefix) +if config.github_ci_app.enabled: + app.include_router( + github_ci_app_router, prefix=f"{config.path_prefix}/github/ci" + ) if config.github_refresh_app.enabled: app.include_router( github_refresh_app_router, diff --git a/src/mobu/models/github.py b/src/mobu/models/github.py index 88c31c19..d799adef 100644 --- a/src/mobu/models/github.py +++ b/src/mobu/models/github.py @@ -1,8 +1,17 @@ """GitHub app integration models. + +Some of these could probably make their way back into Safir. """ + +import safir.github.models +import safir.github.webhooks from pydantic import BaseModel, Field, field_validator +from .user import User + __all__ = [ + "GitHubCheckSuiteEventModel", + "GitHubCheckSuiteModel", "GitHubConfig", ] @@ -10,6 +19,19 @@ class GitHubConfig(BaseModel): """Config for the GitHub CI app funcionality.""" + users: list[User] = Field( + [], + title="Environment users for CI jobs to run as.", + description=( + "Must be prefixed with 'bot-', like all mobu users. In " + " environments without Firestore, users have to be provisioned" + " by environment admins, and their usernames, uids, and guids must" + " be specified here. In environments with firestore, only " + " usernames need to be specified, but you still need to explicitly" + " specify as many users as needed to get the amount of concurrency" + " that you want." + ), + ) accepted_github_orgs: list[str] = Field( [], title="GitHub organizations to accept webhook requests from.", @@ -18,3 +40,31 @@ class GitHubConfig(BaseModel): " this list will get a 403 response." ), ) + + @field_validator("users") + @classmethod + def check_bot_user(cls, v: list[User]) -> list[User]: + bad = [u.username for u in v if not u.username.startswith("bot-")] + if any(bad): + raise ValueError( + f"All usernames must start with 'bot-'. These don't: {bad}" + ) + return v + + +class GitHubCheckSuiteModel( + safir.github.models.GitHubCheckSuiteModel, +): + """Adding ``pull_requests`` field to the existing check suite model.""" + + pull_requests: list[safir.github.models.GitHubCheckRunPrInfoModel] = ( + Field() + ) + + +class GitHubCheckSuiteEventModel( + safir.github.webhooks.GitHubCheckSuiteEventModel, +): + """Overriding ``check_suite`` to add ``pull_requests``.""" + + check_suite: GitHubCheckSuiteModel = Field() diff --git a/tests/conftest.py b/tests/conftest.py index f38dc38d..3961e512 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,6 +4,7 @@ from collections.abc import AsyncIterator, Iterator from contextlib import asynccontextmanager +from importlib import reload from pathlib import Path from textwrap import dedent from unittest.mock import DEFAULT, patch @@ -63,6 +64,9 @@ def _configure() -> Iterator[None]: @pytest.fixture def _enable_github_ci_app(tmp_path: Path) -> Iterator[None]: """Enable the GitHub CI app functionality. + + We need to reload the main module here because including the router is done + conditionally on module import. """ github_config = tmp_path / "github_config.yaml" github_config.write_text( @@ -81,11 +85,13 @@ def _enable_github_ci_app(tmp_path: Path) -> Iterator[None]: config.github_ci_app.webhook_secret = TEST_GITHUB_CI_APP_SECRET config.github_ci_app.private_key = TEST_GITHUB_CI_APP_PRIVATE_KEY config.github_config_path = github_config + reload(main) yield config.github_ci_app = GitHubCiApp() config.github_config_path = None + reload(main) @pytest.fixture diff --git a/tests/data/github_webhooks/check_run_rerequested.tmpl.json b/tests/data/github_webhooks/check_run_rerequested.tmpl.json new file mode 100644 index 00000000..7271704b --- /dev/null +++ b/tests/data/github_webhooks/check_run_rerequested.tmpl.json @@ -0,0 +1,312 @@ +{ + "action": "rerequested", + "check_run": { + "id": 26270489904, + "name": "Mobu (https://data-dev.lsst.cloud/)", + "node_id": "CR_kwDOL_KXns8AAAAGHdfdMA", + "head_sha": "$ref", + "external_id": "", + "url": "https://api.github.com/repos/$owner/$repo/check-runs/26270489904", + "html_url": "https://github.com/$owner/$repo/runs/26270489904", + "details_url": "https://github.org/$owner/mobu", + "status": "completed", + "conclusion": "failure", + "started_at": "2024-06-15T19:58:32Z", + "completed_at": "2024-06-15T19:58:57Z", + "output": { + "title": "Mobu (https://data-dev.lsst.cloud/)", + "summary": "Running these notebooks via Mobu:\n* simple_notebook_1.ipynb\n* somedir/simple_notebook_2.ipynb", + "text": "Mobu stopped, try re-running this check.", + "annotations_count": 0, + "annotations_url": "https://api.github.com/repos/$owner/$repo/check-runs/26270489904/annotations" + }, + "check_suite": { + "id": 24945993370, + "node_id": "CS_kwDOL_KXns8AAAAFzuWmmg", + "head_branch": "main", + "head_sha": "$ref", + "status": "queued", + "conclusion": null, + "url": "https://api.github.com/repos/$owner/$repo/check-suites/24945993370", + "before": "344f5d120954228511a579af8162f354d3464d12", + "after": "$ref", + "pull_requests": [ + { + "url": "https://api.github.com/repos/$owner/$repo/pulls/1", + "id": 1927184480, + "number": 1, + "head": { + "ref": "some-branch", + "sha": "4d00c17640a8186932edd0688ff5d2434e75ad21", + "repo": { + "id": 804427678, + "url": "https://api.github.com/repos/$owner/$repo", + "name": "$repo" + } + }, + "base": { + "ref": "main", + "sha": "64b07c7d33f780e60526d8b2eac70f69848fe5ec", + "repo": { + "id": 804427678, + "url": "https://api.github.com/repos/$owner/$repo", + "name": "$repo" + } + } + } + ], + "app": { + "id": 913231, + "slug": "mobu-ci-data-dev-lsst-cloud", + "node_id": "A_kwHOAJsB4M4ADe9P", + "owner": { + "login": "$owner", + "id": 10158560, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEwMTU4NTYw", + "avatar_url": "https://avatars.githubusercontent.com/u/10158560?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/$owner", + "html_url": "https://github.com/$owner", + "followers_url": "https://api.github.com/users/$owner/followers", + "following_url": "https://api.github.com/users/$owner/following{/other_user}", + "gists_url": "https://api.github.com/users/$owner/gists{/gist_id}", + "starred_url": "https://api.github.com/users/$owner/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/$owner/subscriptions", + "organizations_url": "https://api.github.com/users/$owner/orgs", + "repos_url": "https://api.github.com/users/$owner/repos", + "events_url": "https://api.github.com/users/$owner/events{/privacy}", + "received_events_url": "https://api.github.com/users/$owner/received_events", + "type": "Organization", + "site_admin": false + }, + "name": "mobu CI (data-dev.lsst.cloud)", + "description": "", + "external_url": "https://github.org/$owner/mobu", + "html_url": "https://github.com/apps/mobu-ci-data-dev-lsst-cloud", + "created_at": "2024-06-04T20:54:12Z", + "updated_at": "2024-06-05T17:54:57Z", + "permissions": { + "checks": "write", + "contents": "read", + "metadata": "read" + }, + "events": [ + "check_run", + "check_suite", + "push" + ] + }, + "created_at": "2024-06-15T19:58:30Z", + "updated_at": "2024-06-15T20:03:36Z" + }, + "app": { + "id": 913231, + "slug": "mobu-ci-data-dev-lsst-cloud", + "node_id": "A_kwHOAJsB4M4ADe9P", + "owner": { + "login": "$owner", + "id": 10158560, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEwMTU4NTYw", + "avatar_url": "https://avatars.githubusercontent.com/u/10158560?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/$owner", + "html_url": "https://github.com/$owner", + "followers_url": "https://api.github.com/users/$owner/followers", + "following_url": "https://api.github.com/users/$owner/following{/other_user}", + "gists_url": "https://api.github.com/users/$owner/gists{/gist_id}", + "starred_url": "https://api.github.com/users/$owner/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/$owner/subscriptions", + "organizations_url": "https://api.github.com/users/$owner/orgs", + "repos_url": "https://api.github.com/users/$owner/repos", + "events_url": "https://api.github.com/users/$owner/events{/privacy}", + "received_events_url": "https://api.github.com/users/$owner/received_events", + "type": "Organization", + "site_admin": false + }, + "name": "mobu CI (data-dev.lsst.cloud)", + "description": "", + "external_url": "https://github.org/$owner/mobu", + "html_url": "https://github.com/apps/mobu-ci-data-dev-lsst-cloud", + "created_at": "2024-06-04T20:54:12Z", + "updated_at": "2024-06-05T17:54:57Z", + "permissions": { + "checks": "write", + "contents": "read", + "metadata": "read" + }, + "events": [ + "check_run", + "check_suite", + "push" + ] + }, + "pull_requests": [ + { + "url": "https://api.github.com/repos/$owner/$repo/pulls/1", + "id": 1927184480, + "number": 1, + "head": { + "ref": "some-branch", + "sha": "4d00c17640a8186932edd0688ff5d2434e75ad21", + "repo": { + "id": 804427678, + "url": "https://api.github.com/repos/$owner/$repo", + "name": "$repo" + } + }, + "base": { + "ref": "main", + "sha": "64b07c7d33f780e60526d8b2eac70f69848fe5ec", + "repo": { + "id": 804427678, + "url": "https://api.github.com/repos/$owner/$repo", + "name": "$repo" + } + } + } + ] + }, + "repository": { + "id": 804427678, + "node_id": "R_kgDOL_KXng", + "name": "$repo", + "full_name": "$owner/$repo", + "private": false, + "owner": { + "login": "$owner", + "id": 10158560, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEwMTU4NTYw", + "avatar_url": "https://avatars.githubusercontent.com/u/10158560?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/$owner", + "html_url": "https://github.com/$owner", + "followers_url": "https://api.github.com/users/$owner/followers", + "following_url": "https://api.github.com/users/$owner/following{/other_user}", + "gists_url": "https://api.github.com/users/$owner/gists{/gist_id}", + "starred_url": "https://api.github.com/users/$owner/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/$owner/subscriptions", + "organizations_url": "https://api.github.com/users/$owner/orgs", + "repos_url": "https://api.github.com/users/$owner/repos", + "events_url": "https://api.github.com/users/$owner/events{/privacy}", + "received_events_url": "https://api.github.com/users/$owner/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/$owner/$repo", + "description": "Stuff with which to test mobu", + "fork": false, + "url": "https://api.github.com/repos/$owner/$repo", + "forks_url": "https://api.github.com/repos/$owner/$repo/forks", + "keys_url": "https://api.github.com/repos/$owner/$repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/$owner/$repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/$owner/$repo/teams", + "hooks_url": "https://api.github.com/repos/$owner/$repo/hooks", + "issue_events_url": "https://api.github.com/repos/$owner/$repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/$owner/$repo/events", + "assignees_url": "https://api.github.com/repos/$owner/$repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/$owner/$repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/$owner/$repo/tags", + "blobs_url": "https://api.github.com/repos/$owner/$repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/$owner/$repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/$owner/$repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/$owner/$repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/$owner/$repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/$owner/$repo/languages", + "stargazers_url": "https://api.github.com/repos/$owner/$repo/stargazers", + "contributors_url": "https://api.github.com/repos/$owner/$repo/contributors", + "subscribers_url": "https://api.github.com/repos/$owner/$repo/subscribers", + "subscription_url": "https://api.github.com/repos/$owner/$repo/subscription", + "commits_url": "https://api.github.com/repos/$owner/$repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/$owner/$repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/$owner/$repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/$owner/$repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/$owner/$repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/$owner/$repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/$owner/$repo/merges", + "archive_url": "https://api.github.com/repos/$owner/$repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/$owner/$repo/downloads", + "issues_url": "https://api.github.com/repos/$owner/$repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/$owner/$repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/$owner/$repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/$owner/$repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/$owner/$repo/labels{/name}", + "releases_url": "https://api.github.com/repos/$owner/$repo/releases{/id}", + "deployments_url": "https://api.github.com/repos/$owner/$repo/deployments", + "created_at": "2024-05-22T15:08:46Z", + "updated_at": "2024-06-15T20:00:28Z", + "pushed_at": "2024-06-15T20:00:26Z", + "git_url": "git://github.com/$owner/$repo.git", + "ssh_url": "git@github.com:$owner/$repo.git", + "clone_url": "https://github.com/$owner/$repo.git", + "svn_url": "https://github.com/$owner/$repo", + "homepage": null, + "size": 73, + "stargazers_count": 0, + "watchers_count": 0, + "language": "Jupyter Notebook", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + + ], + "visibility": "public", + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "main", + "custom_properties": { + + } + }, + "organization": { + "login": "$owner", + "id": 10158560, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEwMTU4NTYw", + "url": "https://api.github.com/orgs/$owner", + "repos_url": "https://api.github.com/orgs/$owner/repos", + "events_url": "https://api.github.com/orgs/$owner/events", + "hooks_url": "https://api.github.com/orgs/$owner/hooks", + "issues_url": "https://api.github.com/orgs/$owner/issues", + "members_url": "https://api.github.com/orgs/$owner/members{/member}", + "public_members_url": "https://api.github.com/orgs/$owner/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/10158560?v=4", + "description": "Rubin Observatory Science Quality and Reliability Engineering team" + }, + "sender": { + "login": "someone", + "id": 330402, + "node_id": "MDQ6VXNlcjMzMDQwMg==", + "avatar_url": "https://avatars.githubusercontent.com/u/330402?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/", + "html_url": "https://github.com/someone", + "followers_url": "https://api.github.com/users/someone/followers", + "following_url": "https://api.github.com/users/someone/following{/other_user}", + "gists_url": "https://api.github.com/users/someone/gists{/gist_id}", + "starred_url": "https://api.github.com/users/someone/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/someone/subscriptions", + "organizations_url": "https://api.github.com/users/someone/orgs", + "repos_url": "https://api.github.com/users/someone/repos", + "events_url": "https://api.github.com/users/someone/events{/privacy}", + "received_events_url": "https://api.github.com/users/someone/received_events", + "type": "User", + "site_admin": false + }, + "installation": { + "id": 51531298, + "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uNTE1MzEyOTg=" + } +} diff --git a/tests/data/github_webhooks/check_run_rerequested_no_prs.tmpl.json b/tests/data/github_webhooks/check_run_rerequested_no_prs.tmpl.json new file mode 100644 index 00000000..f288b338 --- /dev/null +++ b/tests/data/github_webhooks/check_run_rerequested_no_prs.tmpl.json @@ -0,0 +1,267 @@ +{ + "action": "rerequested", + "check_run": { + "id": 26270489904, + "name": "Mobu (https://data-dev.lsst.cloud/)", + "node_id": "CR_kwDOL_KXns8AAAAGHdfdMA", + "head_sha": "$ref", + "external_id": "", + "url": "https://api.github.com/repos/$owner/$repo/check-runs/26270489904", + "html_url": "https://github.com/$owner/$repo/runs/26270489904", + "details_url": "https://github.org/$owner/mobu", + "status": "completed", + "conclusion": "failure", + "started_at": "2024-06-15T19:58:32Z", + "completed_at": "2024-06-15T19:58:57Z", + "output": { + "title": "Mobu (https://data-dev.lsst.cloud/)", + "summary": "Running these notebooks via Mobu:\n* simple_notebook_1.ipynb\n* somedir/simple_notebook_2.ipynb", + "text": "Mobu stopped, try re-running this check.", + "annotations_count": 0, + "annotations_url": "https://api.github.com/repos/$owner/$repo/check-runs/26270489904/annotations" + }, + "check_suite": { + "id": 24945993370, + "node_id": "CS_kwDOL_KXns8AAAAFzuWmmg", + "head_branch": "main", + "head_sha": "$ref", + "status": "queued", + "conclusion": null, + "url": "https://api.github.com/repos/$owner/$repo/check-suites/24945993370", + "before": "344f5d120954228511a579af8162f354d3464d12", + "after": "$ref", + "pull_requests": [ + ], + "app": { + "id": 913231, + "slug": "mobu-ci-data-dev-lsst-cloud", + "node_id": "A_kwHOAJsB4M4ADe9P", + "owner": { + "login": "$owner", + "id": 10158560, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEwMTU4NTYw", + "avatar_url": "https://avatars.githubusercontent.com/u/10158560?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/$owner", + "html_url": "https://github.com/$owner", + "followers_url": "https://api.github.com/users/$owner/followers", + "following_url": "https://api.github.com/users/$owner/following{/other_user}", + "gists_url": "https://api.github.com/users/$owner/gists{/gist_id}", + "starred_url": "https://api.github.com/users/$owner/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/$owner/subscriptions", + "organizations_url": "https://api.github.com/users/$owner/orgs", + "repos_url": "https://api.github.com/users/$owner/repos", + "events_url": "https://api.github.com/users/$owner/events{/privacy}", + "received_events_url": "https://api.github.com/users/$owner/received_events", + "type": "Organization", + "site_admin": false + }, + "name": "mobu CI (data-dev.lsst.cloud)", + "description": "", + "external_url": "https://github.org/$owner/mobu", + "html_url": "https://github.com/apps/mobu-ci-data-dev-lsst-cloud", + "created_at": "2024-06-04T20:54:12Z", + "updated_at": "2024-06-05T17:54:57Z", + "permissions": { + "checks": "write", + "contents": "read", + "metadata": "read" + }, + "events": [ + "check_run", + "check_suite", + "push" + ] + }, + "created_at": "2024-06-15T19:58:30Z", + "updated_at": "2024-06-15T20:03:36Z" + }, + "app": { + "id": 913231, + "slug": "mobu-ci-data-dev-lsst-cloud", + "node_id": "A_kwHOAJsB4M4ADe9P", + "owner": { + "login": "$owner", + "id": 10158560, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEwMTU4NTYw", + "avatar_url": "https://avatars.githubusercontent.com/u/10158560?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/$owner", + "html_url": "https://github.com/$owner", + "followers_url": "https://api.github.com/users/$owner/followers", + "following_url": "https://api.github.com/users/$owner/following{/other_user}", + "gists_url": "https://api.github.com/users/$owner/gists{/gist_id}", + "starred_url": "https://api.github.com/users/$owner/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/$owner/subscriptions", + "organizations_url": "https://api.github.com/users/$owner/orgs", + "repos_url": "https://api.github.com/users/$owner/repos", + "events_url": "https://api.github.com/users/$owner/events{/privacy}", + "received_events_url": "https://api.github.com/users/$owner/received_events", + "type": "Organization", + "site_admin": false + }, + "name": "mobu CI (data-dev.lsst.cloud)", + "description": "", + "external_url": "https://github.org/$owner/mobu", + "html_url": "https://github.com/apps/mobu-ci-data-dev-lsst-cloud", + "created_at": "2024-06-04T20:54:12Z", + "updated_at": "2024-06-05T17:54:57Z", + "permissions": { + "checks": "write", + "contents": "read", + "metadata": "read" + }, + "events": [ + "check_run", + "check_suite", + "push" + ] + }, + "pull_requests": [ + + ] + }, + "repository": { + "id": 804427678, + "node_id": "R_kgDOL_KXng", + "name": "$repo", + "full_name": "$owner/$repo", + "private": false, + "owner": { + "login": "$owner", + "id": 10158560, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEwMTU4NTYw", + "avatar_url": "https://avatars.githubusercontent.com/u/10158560?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/$owner", + "html_url": "https://github.com/$owner", + "followers_url": "https://api.github.com/users/$owner/followers", + "following_url": "https://api.github.com/users/$owner/following{/other_user}", + "gists_url": "https://api.github.com/users/$owner/gists{/gist_id}", + "starred_url": "https://api.github.com/users/$owner/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/$owner/subscriptions", + "organizations_url": "https://api.github.com/users/$owner/orgs", + "repos_url": "https://api.github.com/users/$owner/repos", + "events_url": "https://api.github.com/users/$owner/events{/privacy}", + "received_events_url": "https://api.github.com/users/$owner/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/$owner/$repo", + "description": "Stuff with which to test mobu", + "fork": false, + "url": "https://api.github.com/repos/$owner/$repo", + "forks_url": "https://api.github.com/repos/$owner/$repo/forks", + "keys_url": "https://api.github.com/repos/$owner/$repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/$owner/$repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/$owner/$repo/teams", + "hooks_url": "https://api.github.com/repos/$owner/$repo/hooks", + "issue_events_url": "https://api.github.com/repos/$owner/$repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/$owner/$repo/events", + "assignees_url": "https://api.github.com/repos/$owner/$repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/$owner/$repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/$owner/$repo/tags", + "blobs_url": "https://api.github.com/repos/$owner/$repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/$owner/$repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/$owner/$repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/$owner/$repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/$owner/$repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/$owner/$repo/languages", + "stargazers_url": "https://api.github.com/repos/$owner/$repo/stargazers", + "contributors_url": "https://api.github.com/repos/$owner/$repo/contributors", + "subscribers_url": "https://api.github.com/repos/$owner/$repo/subscribers", + "subscription_url": "https://api.github.com/repos/$owner/$repo/subscription", + "commits_url": "https://api.github.com/repos/$owner/$repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/$owner/$repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/$owner/$repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/$owner/$repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/$owner/$repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/$owner/$repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/$owner/$repo/merges", + "archive_url": "https://api.github.com/repos/$owner/$repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/$owner/$repo/downloads", + "issues_url": "https://api.github.com/repos/$owner/$repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/$owner/$repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/$owner/$repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/$owner/$repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/$owner/$repo/labels{/name}", + "releases_url": "https://api.github.com/repos/$owner/$repo/releases{/id}", + "deployments_url": "https://api.github.com/repos/$owner/$repo/deployments", + "created_at": "2024-05-22T15:08:46Z", + "updated_at": "2024-06-15T20:00:28Z", + "pushed_at": "2024-06-15T20:00:26Z", + "git_url": "git://github.com/$owner/$repo.git", + "ssh_url": "git@github.com:$owner/$repo.git", + "clone_url": "https://github.com/$owner/$repo.git", + "svn_url": "https://github.com/$owner/$repo", + "homepage": null, + "size": 73, + "stargazers_count": 0, + "watchers_count": 0, + "language": "Jupyter Notebook", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + + ], + "visibility": "public", + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "main", + "custom_properties": { + + } + }, + "organization": { + "login": "$owner", + "id": 10158560, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEwMTU4NTYw", + "url": "https://api.github.com/orgs/$owner", + "repos_url": "https://api.github.com/orgs/$owner/repos", + "events_url": "https://api.github.com/orgs/$owner/events", + "hooks_url": "https://api.github.com/orgs/$owner/hooks", + "issues_url": "https://api.github.com/orgs/$owner/issues", + "members_url": "https://api.github.com/orgs/$owner/members{/member}", + "public_members_url": "https://api.github.com/orgs/$owner/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/10158560?v=4", + "description": "Rubin Observatory Science Quality and Reliability Engineering team" + }, + "sender": { + "login": "someone", + "id": 330402, + "node_id": "MDQ6VXNlcjMzMDQwMg==", + "avatar_url": "https://avatars.githubusercontent.com/u/330402?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/", + "html_url": "https://github.com/someone", + "followers_url": "https://api.github.com/users/someone/followers", + "following_url": "https://api.github.com/users/someone/following{/other_user}", + "gists_url": "https://api.github.com/users/someone/gists{/gist_id}", + "starred_url": "https://api.github.com/users/someone/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/someone/subscriptions", + "organizations_url": "https://api.github.com/users/someone/orgs", + "repos_url": "https://api.github.com/users/someone/repos", + "events_url": "https://api.github.com/users/someone/events{/privacy}", + "received_events_url": "https://api.github.com/users/someone/received_events", + "type": "User", + "site_admin": false + }, + "installation": { + "id": 51531298, + "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uNTE1MzEyOTg=" + } +} diff --git a/tests/data/github_webhooks/check_suite_requested.tmpl.json b/tests/data/github_webhooks/check_suite_requested.tmpl.json new file mode 100644 index 00000000..aa2e3b17 --- /dev/null +++ b/tests/data/github_webhooks/check_suite_requested.tmpl.json @@ -0,0 +1,243 @@ +{ + "action": "requested", + "check_suite": { + "id": 24835094650, + "node_id": "CS_kwDOL_KXns8AAAAFyEl4eg", + "head_branch": "main", + "head_sha": "$ref", + "status": "queued", + "conclusion": null, + "url": "https://api.github.com/repos/$owner/$repo/check-suites/24835094650", + "before": "89562c4813ff37d50a3bbf2e72e63b0f3252ba26", + "after": "$ref", + "pull_requests": [ + { + "url": "https://api.github.com/repos/$owner/$repo/pulls/1", + "id": 1927184480, + "number": 1, + "head": { + "ref": "some-branch", + "sha": "4d00c17640a8186932edd0688ff5d2434e75ad21", + "repo": { + "id": 804427678, + "url": "https://api.github.com/repos/$owner/$repo", + "name": "$repo" + } + }, + "base": { + "ref": "main", + "sha": "64b07c7d33f780e60526d8b2eac70f69848fe5ec", + "repo": { + "id": 804427678, + "url": "https://api.github.com/repos/$owner/$repo", + "name": "$repo" + } + } + } + ], + "app": { + "id": 913231, + "slug": "mobu-ci-data-dev-lsst-cloud", + "node_id": "A_kwHOAJsB4M4ADe9P", + "owner": { + "login": "$owner", + "id": 123, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEwMTU4NTYw", + "avatar_url": "https://avatars.githubusercontent.com/u/some-user?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/$owner", + "html_url": "https://github.com/$owner", + "followers_url": "https://api.github.com/users/$owner/followers", + "following_url": "https://api.github.com/users/$owner/following{/other_user}", + "gists_url": "https://api.github.com/users/$owner/gists{/gist_id}", + "starred_url": "https://api.github.com/users/$owner/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/$owner/subscriptions", + "organizations_url": "https://api.github.com/users/$owner/orgs", + "repos_url": "https://api.github.com/users/$owner/repos", + "events_url": "https://api.github.com/users/$owner/events{/privacy}", + "received_events_url": "https://api.github.com/users/$owner/received_events", + "type": "Organization", + "site_admin": false + }, + "name": "mobu CI (data-dev.lsst.cloud)", + "description": "", + "external_url": "https://github.org/$owner/mobu", + "html_url": "https://github.com/apps/mobu-ci-data-dev-lsst-cloud", + "created_at": "2024-06-04T20:54:12Z", + "updated_at": "2024-06-05T17:54:57Z", + "permissions": { + "checks": "write", + "contents": "read", + "metadata": "read" + }, + "events": [ + "check_run", + "check_suite", + "push" + ] + }, + "created_at": "2024-06-12T19:24:54Z", + "updated_at": "2024-06-12T19:24:54Z", + "rerequestable": true, + "runs_rerequestable": true, + "latest_check_runs_count": 0, + "check_runs_url": "https://api.github.com/repos/$owner/$repo/check-suites/24835094650/check-runs", + "head_commit": { + "id": "$ref", + "tree_id": "d618b4f86e6593696f76331b35b19d5c2e3e1816", + "message": "refresh yvzxhSNRqX41x", + "timestamp": "2024-06-12T19:24:52Z", + "author": { + "name": "Mister Mistoffelees", + "email": "aloof@theroof.com" + }, + "committer": { + "name": "Mister Mistoffelees", + "email": "aloof@theroof.com" + } + } + }, + "repository": { + "id": 804427678, + "node_id": "R_kgDOL_KXng", + "name": "$repo", + "full_name": "$owner/$repo", + "private": false, + "owner": { + "login": "$owner", + "id": 123, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEwMTU4NTYw", + "avatar_url": "https://avatars.githubusercontent.com/u/123?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/$owner", + "html_url": "https://github.com/$owner", + "followers_url": "https://api.github.com/users/$owner/followers", + "following_url": "https://api.github.com/users/$owner/following{/other_user}", + "gists_url": "https://api.github.com/users/$owner/gists{/gist_id}", + "starred_url": "https://api.github.com/users/$owner/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/$owner/subscriptions", + "organizations_url": "https://api.github.com/users/$owner/orgs", + "repos_url": "https://api.github.com/users/$owner/repos", + "events_url": "https://api.github.com/users/$owner/events{/privacy}", + "received_events_url": "https://api.github.com/users/$owner/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/$owner/$repo", + "description": "Stuff with which to test mobu", + "fork": false, + "url": "https://api.github.com/repos/$owner/$repo", + "forks_url": "https://api.github.com/repos/$owner/$repo/forks", + "keys_url": "https://api.github.com/repos/$owner/$repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/$owner/$repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/$owner/$repo/teams", + "hooks_url": "https://api.github.com/repos/$owner/$repo/hooks", + "issue_events_url": "https://api.github.com/repos/$owner/$repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/$owner/$repo/events", + "assignees_url": "https://api.github.com/repos/$owner/$repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/$owner/$repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/$owner/$repo/tags", + "blobs_url": "https://api.github.com/repos/$owner/$repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/$owner/$repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/$owner/$repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/$owner/$repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/$owner/$repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/$owner/$repo/languages", + "stargazers_url": "https://api.github.com/repos/$owner/$repo/stargazers", + "contributors_url": "https://api.github.com/repos/$owner/$repo/contributors", + "subscribers_url": "https://api.github.com/repos/$owner/$repo/subscribers", + "subscription_url": "https://api.github.com/repos/$owner/$repo/subscription", + "commits_url": "https://api.github.com/repos/$owner/$repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/$owner/$repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/$owner/$repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/$owner/$repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/$owner/$repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/$owner/$repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/$owner/$repo/merges", + "archive_url": "https://api.github.com/repos/$owner/$repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/$owner/$repo/downloads", + "issues_url": "https://api.github.com/repos/$owner/$repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/$owner/$repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/$owner/$repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/$owner/$repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/$owner/$repo/labels{/name}", + "releases_url": "https://api.github.com/repos/$owner/$repo/releases{/id}", + "deployments_url": "https://api.github.com/repos/$owner/$repo/deployments", + "created_at": "2024-05-22T15:08:46Z", + "updated_at": "2024-06-12T19:24:22Z", + "pushed_at": "2024-06-12T19:24:53Z", + "git_url": "git://github.com/$owner/$repo.git", + "ssh_url": "git@github.com:$owner/$repo.git", + "clone_url": "https://github.com/$owner/$repo.git", + "svn_url": "https://github.com/$owner/$repo", + "homepage": null, + "size": 72, + "stargazers_count": 0, + "watchers_count": 0, + "language": "Jupyter Notebook", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + + ], + "visibility": "public", + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "main", + "custom_properties": { + + } + }, + "organization": { + "login": "$owner", + "id": 123, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEwMTU4NTYw", + "url": "https://api.github.com/orgs/$owner", + "repos_url": "https://api.github.com/orgs/$owner/repos", + "events_url": "https://api.github.com/orgs/$owner/events", + "hooks_url": "https://api.github.com/orgs/$owner/hooks", + "issues_url": "https://api.github.com/orgs/$owner/issues", + "members_url": "https://api.github.com/orgs/$owner/members{/member}", + "public_members_url": "https://api.github.com/orgs/$owner/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/123?v=4", + "description": "Rubin Observatory Science Quality and Reliability Engineering team" + }, + "sender": { + "login": "some-login", + "id": 456, + "node_id": "MDQ6VXNlcjMzMDQwMg==", + "avatar_url": "https://avatars.githubusercontent.com/u/456?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/some-login", + "html_url": "https://github.com/some-login", + "followers_url": "https://api.github.com/users/some-login/followers", + "following_url": "https://api.github.com/users/some-login/following{/other_user}", + "gists_url": "https://api.github.com/users/some-login/gists{/gist_id}", + "starred_url": "https://api.github.com/users/some-login/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/some-login/subscriptions", + "organizations_url": "https://api.github.com/users/some-login/orgs", + "repos_url": "https://api.github.com/users/some-login/repos", + "events_url": "https://api.github.com/users/some-login/events{/privacy}", + "received_events_url": "https://api.github.com/users/some-login/received_events", + "type": "User", + "site_admin": false + }, + "installation": { + "id": $installation_id, + "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uNTE1MzEyOTg=" + } +} diff --git a/tests/data/github_webhooks/check_suite_requested_no_prs.tmpl.json b/tests/data/github_webhooks/check_suite_requested_no_prs.tmpl.json new file mode 100644 index 00000000..201b414e --- /dev/null +++ b/tests/data/github_webhooks/check_suite_requested_no_prs.tmpl.json @@ -0,0 +1,220 @@ +{ + "action": "requested", + "check_suite": { + "id": 24835094650, + "node_id": "CS_kwDOL_KXns8AAAAFyEl4eg", + "head_branch": "main", + "head_sha": "$ref", + "status": "queued", + "conclusion": null, + "url": "https://api.github.com/repos/$owner/$repo/check-suites/24835094650", + "before": "89562c4813ff37d50a3bbf2e72e63b0f3252ba26", + "after": "$ref", + "pull_requests": [ + ], + "app": { + "id": 913231, + "slug": "mobu-ci-data-dev-lsst-cloud", + "node_id": "A_kwHOAJsB4M4ADe9P", + "owner": { + "login": "$owner", + "id": 123, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEwMTU4NTYw", + "avatar_url": "https://avatars.githubusercontent.com/u/some-user?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/$owner", + "html_url": "https://github.com/$owner", + "followers_url": "https://api.github.com/users/$owner/followers", + "following_url": "https://api.github.com/users/$owner/following{/other_user}", + "gists_url": "https://api.github.com/users/$owner/gists{/gist_id}", + "starred_url": "https://api.github.com/users/$owner/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/$owner/subscriptions", + "organizations_url": "https://api.github.com/users/$owner/orgs", + "repos_url": "https://api.github.com/users/$owner/repos", + "events_url": "https://api.github.com/users/$owner/events{/privacy}", + "received_events_url": "https://api.github.com/users/$owner/received_events", + "type": "Organization", + "site_admin": false + }, + "name": "mobu CI (data-dev.lsst.cloud)", + "description": "", + "external_url": "https://github.org/$owner/mobu", + "html_url": "https://github.com/apps/mobu-ci-data-dev-lsst-cloud", + "created_at": "2024-06-04T20:54:12Z", + "updated_at": "2024-06-05T17:54:57Z", + "permissions": { + "checks": "write", + "contents": "read", + "metadata": "read" + }, + "events": [ + "check_run", + "check_suite", + "push" + ] + }, + "created_at": "2024-06-12T19:24:54Z", + "updated_at": "2024-06-12T19:24:54Z", + "rerequestable": true, + "runs_rerequestable": true, + "latest_check_runs_count": 0, + "check_runs_url": "https://api.github.com/repos/$owner/$repo/check-suites/24835094650/check-runs", + "head_commit": { + "id": "$ref", + "tree_id": "d618b4f86e6593696f76331b35b19d5c2e3e1816", + "message": "refresh yvzxhSNRqX41x", + "timestamp": "2024-06-12T19:24:52Z", + "author": { + "name": "Mister Mistoffelees", + "email": "aloof@theroof.com" + }, + "committer": { + "name": "Mister Mistoffelees", + "email": "aloof@theroof.com" + } + } + }, + "repository": { + "id": 804427678, + "node_id": "R_kgDOL_KXng", + "name": "$repo", + "full_name": "$owner/$repo", + "private": false, + "owner": { + "login": "$owner", + "id": 123, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEwMTU4NTYw", + "avatar_url": "https://avatars.githubusercontent.com/u/123?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/$owner", + "html_url": "https://github.com/$owner", + "followers_url": "https://api.github.com/users/$owner/followers", + "following_url": "https://api.github.com/users/$owner/following{/other_user}", + "gists_url": "https://api.github.com/users/$owner/gists{/gist_id}", + "starred_url": "https://api.github.com/users/$owner/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/$owner/subscriptions", + "organizations_url": "https://api.github.com/users/$owner/orgs", + "repos_url": "https://api.github.com/users/$owner/repos", + "events_url": "https://api.github.com/users/$owner/events{/privacy}", + "received_events_url": "https://api.github.com/users/$owner/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/$owner/$repo", + "description": "Stuff with which to test mobu", + "fork": false, + "url": "https://api.github.com/repos/$owner/$repo", + "forks_url": "https://api.github.com/repos/$owner/$repo/forks", + "keys_url": "https://api.github.com/repos/$owner/$repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/$owner/$repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/$owner/$repo/teams", + "hooks_url": "https://api.github.com/repos/$owner/$repo/hooks", + "issue_events_url": "https://api.github.com/repos/$owner/$repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/$owner/$repo/events", + "assignees_url": "https://api.github.com/repos/$owner/$repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/$owner/$repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/$owner/$repo/tags", + "blobs_url": "https://api.github.com/repos/$owner/$repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/$owner/$repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/$owner/$repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/$owner/$repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/$owner/$repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/$owner/$repo/languages", + "stargazers_url": "https://api.github.com/repos/$owner/$repo/stargazers", + "contributors_url": "https://api.github.com/repos/$owner/$repo/contributors", + "subscribers_url": "https://api.github.com/repos/$owner/$repo/subscribers", + "subscription_url": "https://api.github.com/repos/$owner/$repo/subscription", + "commits_url": "https://api.github.com/repos/$owner/$repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/$owner/$repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/$owner/$repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/$owner/$repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/$owner/$repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/$owner/$repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/$owner/$repo/merges", + "archive_url": "https://api.github.com/repos/$owner/$repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/$owner/$repo/downloads", + "issues_url": "https://api.github.com/repos/$owner/$repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/$owner/$repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/$owner/$repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/$owner/$repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/$owner/$repo/labels{/name}", + "releases_url": "https://api.github.com/repos/$owner/$repo/releases{/id}", + "deployments_url": "https://api.github.com/repos/$owner/$repo/deployments", + "created_at": "2024-05-22T15:08:46Z", + "updated_at": "2024-06-12T19:24:22Z", + "pushed_at": "2024-06-12T19:24:53Z", + "git_url": "git://github.com/$owner/$repo.git", + "ssh_url": "git@github.com:$owner/$repo.git", + "clone_url": "https://github.com/$owner/$repo.git", + "svn_url": "https://github.com/$owner/$repo", + "homepage": null, + "size": 72, + "stargazers_count": 0, + "watchers_count": 0, + "language": "Jupyter Notebook", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + + ], + "visibility": "public", + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "main", + "custom_properties": { + + } + }, + "organization": { + "login": "$owner", + "id": 123, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEwMTU4NTYw", + "url": "https://api.github.com/orgs/$owner", + "repos_url": "https://api.github.com/orgs/$owner/repos", + "events_url": "https://api.github.com/orgs/$owner/events", + "hooks_url": "https://api.github.com/orgs/$owner/hooks", + "issues_url": "https://api.github.com/orgs/$owner/issues", + "members_url": "https://api.github.com/orgs/$owner/members{/member}", + "public_members_url": "https://api.github.com/orgs/$owner/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/123?v=4", + "description": "Rubin Observatory Science Quality and Reliability Engineering team" + }, + "sender": { + "login": "some-login", + "id": 456, + "node_id": "MDQ6VXNlcjMzMDQwMg==", + "avatar_url": "https://avatars.githubusercontent.com/u/456?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/some-login", + "html_url": "https://github.com/some-login", + "followers_url": "https://api.github.com/users/some-login/followers", + "following_url": "https://api.github.com/users/some-login/following{/other_user}", + "gists_url": "https://api.github.com/users/some-login/gists{/gist_id}", + "starred_url": "https://api.github.com/users/some-login/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/some-login/subscriptions", + "organizations_url": "https://api.github.com/users/some-login/orgs", + "repos_url": "https://api.github.com/users/some-login/repos", + "events_url": "https://api.github.com/users/some-login/events{/privacy}", + "received_events_url": "https://api.github.com/users/some-login/received_events", + "type": "User", + "site_admin": false + }, + "installation": { + "id": $installation_id, + "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uNTE1MzEyOTg=" + } +} diff --git a/tests/data/github_webhooks/check_suite_rerequested.tmpl.json b/tests/data/github_webhooks/check_suite_rerequested.tmpl.json new file mode 100644 index 00000000..c62240b1 --- /dev/null +++ b/tests/data/github_webhooks/check_suite_rerequested.tmpl.json @@ -0,0 +1,243 @@ +{ + "action": "rerequested", + "check_suite": { + "id": 24835094650, + "node_id": "CS_kwDOL_KXns8AAAAFyEl4eg", + "head_branch": "main", + "head_sha": "$ref", + "status": "queued", + "conclusion": null, + "url": "https://api.github.com/repos/$owner/$repo/check-suites/24835094650", + "before": "89562c4813ff37d50a3bbf2e72e63b0f3252ba26", + "after": "$ref", + "pull_requests": [ + { + "url": "https://api.github.com/repos/$owner/$repo/pulls/1", + "id": 1927184480, + "number": 1, + "head": { + "ref": "some-branch", + "sha": "4d00c17640a8186932edd0688ff5d2434e75ad21", + "repo": { + "id": 804427678, + "url": "https://api.github.com/repos/$owner/$repo", + "name": "$repo" + } + }, + "base": { + "ref": "main", + "sha": "64b07c7d33f780e60526d8b2eac70f69848fe5ec", + "repo": { + "id": 804427678, + "url": "https://api.github.com/repos/$owner/$repo", + "name": "$repo" + } + } + } + ], + "app": { + "id": 913231, + "slug": "mobu-ci-data-dev-lsst-cloud", + "node_id": "A_kwHOAJsB4M4ADe9P", + "owner": { + "login": "$owner", + "id": 123, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEwMTU4NTYw", + "avatar_url": "https://avatars.githubusercontent.com/u/some-user?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/$owner", + "html_url": "https://github.com/$owner", + "followers_url": "https://api.github.com/users/$owner/followers", + "following_url": "https://api.github.com/users/$owner/following{/other_user}", + "gists_url": "https://api.github.com/users/$owner/gists{/gist_id}", + "starred_url": "https://api.github.com/users/$owner/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/$owner/subscriptions", + "organizations_url": "https://api.github.com/users/$owner/orgs", + "repos_url": "https://api.github.com/users/$owner/repos", + "events_url": "https://api.github.com/users/$owner/events{/privacy}", + "received_events_url": "https://api.github.com/users/$owner/received_events", + "type": "Organization", + "site_admin": false + }, + "name": "mobu CI (data-dev.lsst.cloud)", + "description": "", + "external_url": "https://github.org/$owner/mobu", + "html_url": "https://github.com/apps/mobu-ci-data-dev-lsst-cloud", + "created_at": "2024-06-04T20:54:12Z", + "updated_at": "2024-06-05T17:54:57Z", + "permissions": { + "checks": "write", + "contents": "read", + "metadata": "read" + }, + "events": [ + "check_run", + "check_suite", + "push" + ] + }, + "created_at": "2024-06-12T19:24:54Z", + "updated_at": "2024-06-12T19:24:54Z", + "rerequestable": true, + "runs_rerequestable": true, + "latest_check_runs_count": 0, + "check_runs_url": "https://api.github.com/repos/$owner/$repo/check-suites/24835094650/check-runs", + "head_commit": { + "id": "$ref", + "tree_id": "d618b4f86e6593696f76331b35b19d5c2e3e1816", + "message": "refresh yvzxhSNRqX41x", + "timestamp": "2024-06-12T19:24:52Z", + "author": { + "name": "Mister Mistoffelees", + "email": "aloof@theroof.com" + }, + "committer": { + "name": "Mister Mistoffelees", + "email": "aloof@theroof.com" + } + } + }, + "repository": { + "id": 804427678, + "node_id": "R_kgDOL_KXng", + "name": "$repo", + "full_name": "$owner/$repo", + "private": false, + "owner": { + "login": "$owner", + "id": 123, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEwMTU4NTYw", + "avatar_url": "https://avatars.githubusercontent.com/u/123?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/$owner", + "html_url": "https://github.com/$owner", + "followers_url": "https://api.github.com/users/$owner/followers", + "following_url": "https://api.github.com/users/$owner/following{/other_user}", + "gists_url": "https://api.github.com/users/$owner/gists{/gist_id}", + "starred_url": "https://api.github.com/users/$owner/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/$owner/subscriptions", + "organizations_url": "https://api.github.com/users/$owner/orgs", + "repos_url": "https://api.github.com/users/$owner/repos", + "events_url": "https://api.github.com/users/$owner/events{/privacy}", + "received_events_url": "https://api.github.com/users/$owner/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/$owner/$repo", + "description": "Stuff with which to test mobu", + "fork": false, + "url": "https://api.github.com/repos/$owner/$repo", + "forks_url": "https://api.github.com/repos/$owner/$repo/forks", + "keys_url": "https://api.github.com/repos/$owner/$repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/$owner/$repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/$owner/$repo/teams", + "hooks_url": "https://api.github.com/repos/$owner/$repo/hooks", + "issue_events_url": "https://api.github.com/repos/$owner/$repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/$owner/$repo/events", + "assignees_url": "https://api.github.com/repos/$owner/$repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/$owner/$repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/$owner/$repo/tags", + "blobs_url": "https://api.github.com/repos/$owner/$repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/$owner/$repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/$owner/$repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/$owner/$repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/$owner/$repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/$owner/$repo/languages", + "stargazers_url": "https://api.github.com/repos/$owner/$repo/stargazers", + "contributors_url": "https://api.github.com/repos/$owner/$repo/contributors", + "subscribers_url": "https://api.github.com/repos/$owner/$repo/subscribers", + "subscription_url": "https://api.github.com/repos/$owner/$repo/subscription", + "commits_url": "https://api.github.com/repos/$owner/$repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/$owner/$repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/$owner/$repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/$owner/$repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/$owner/$repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/$owner/$repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/$owner/$repo/merges", + "archive_url": "https://api.github.com/repos/$owner/$repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/$owner/$repo/downloads", + "issues_url": "https://api.github.com/repos/$owner/$repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/$owner/$repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/$owner/$repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/$owner/$repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/$owner/$repo/labels{/name}", + "releases_url": "https://api.github.com/repos/$owner/$repo/releases{/id}", + "deployments_url": "https://api.github.com/repos/$owner/$repo/deployments", + "created_at": "2024-05-22T15:08:46Z", + "updated_at": "2024-06-12T19:24:22Z", + "pushed_at": "2024-06-12T19:24:53Z", + "git_url": "git://github.com/$owner/$repo.git", + "ssh_url": "git@github.com:$owner/$repo.git", + "clone_url": "https://github.com/$owner/$repo.git", + "svn_url": "https://github.com/$owner/$repo", + "homepage": null, + "size": 72, + "stargazers_count": 0, + "watchers_count": 0, + "language": "Jupyter Notebook", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + + ], + "visibility": "public", + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "main", + "custom_properties": { + + } + }, + "organization": { + "login": "$owner", + "id": 123, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEwMTU4NTYw", + "url": "https://api.github.com/orgs/$owner", + "repos_url": "https://api.github.com/orgs/$owner/repos", + "events_url": "https://api.github.com/orgs/$owner/events", + "hooks_url": "https://api.github.com/orgs/$owner/hooks", + "issues_url": "https://api.github.com/orgs/$owner/issues", + "members_url": "https://api.github.com/orgs/$owner/members{/member}", + "public_members_url": "https://api.github.com/orgs/$owner/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/123?v=4", + "description": "Rubin Observatory Science Quality and Reliability Engineering team" + }, + "sender": { + "login": "some-login", + "id": 456, + "node_id": "MDQ6VXNlcjMzMDQwMg==", + "avatar_url": "https://avatars.githubusercontent.com/u/456?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/some-login", + "html_url": "https://github.com/some-login", + "followers_url": "https://api.github.com/users/some-login/followers", + "following_url": "https://api.github.com/users/some-login/following{/other_user}", + "gists_url": "https://api.github.com/users/some-login/gists{/gist_id}", + "starred_url": "https://api.github.com/users/some-login/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/some-login/subscriptions", + "organizations_url": "https://api.github.com/users/some-login/orgs", + "repos_url": "https://api.github.com/users/some-login/repos", + "events_url": "https://api.github.com/users/some-login/events{/privacy}", + "received_events_url": "https://api.github.com/users/some-login/received_events", + "type": "User", + "site_admin": false + }, + "installation": { + "id": $installation_id, + "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uNTE1MzEyOTg=" + } +} diff --git a/tests/data/github_webhooks/check_suite_rerequested_no_prs.tmpl.json b/tests/data/github_webhooks/check_suite_rerequested_no_prs.tmpl.json new file mode 100644 index 00000000..d8163c62 --- /dev/null +++ b/tests/data/github_webhooks/check_suite_rerequested_no_prs.tmpl.json @@ -0,0 +1,220 @@ +{ + "action": "rerequested", + "check_suite": { + "id": 24835094650, + "node_id": "CS_kwDOL_KXns8AAAAFyEl4eg", + "head_branch": "main", + "head_sha": "$ref", + "status": "queued", + "conclusion": null, + "url": "https://api.github.com/repos/$owner/$repo/check-suites/24835094650", + "before": "89562c4813ff37d50a3bbf2e72e63b0f3252ba26", + "after": "$ref", + "pull_requests": [ + ], + "app": { + "id": 913231, + "slug": "mobu-ci-data-dev-lsst-cloud", + "node_id": "A_kwHOAJsB4M4ADe9P", + "owner": { + "login": "$owner", + "id": 123, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEwMTU4NTYw", + "avatar_url": "https://avatars.githubusercontent.com/u/some-user?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/$owner", + "html_url": "https://github.com/$owner", + "followers_url": "https://api.github.com/users/$owner/followers", + "following_url": "https://api.github.com/users/$owner/following{/other_user}", + "gists_url": "https://api.github.com/users/$owner/gists{/gist_id}", + "starred_url": "https://api.github.com/users/$owner/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/$owner/subscriptions", + "organizations_url": "https://api.github.com/users/$owner/orgs", + "repos_url": "https://api.github.com/users/$owner/repos", + "events_url": "https://api.github.com/users/$owner/events{/privacy}", + "received_events_url": "https://api.github.com/users/$owner/received_events", + "type": "Organization", + "site_admin": false + }, + "name": "mobu CI (data-dev.lsst.cloud)", + "description": "", + "external_url": "https://github.org/$owner/mobu", + "html_url": "https://github.com/apps/mobu-ci-data-dev-lsst-cloud", + "created_at": "2024-06-04T20:54:12Z", + "updated_at": "2024-06-05T17:54:57Z", + "permissions": { + "checks": "write", + "contents": "read", + "metadata": "read" + }, + "events": [ + "check_run", + "check_suite", + "push" + ] + }, + "created_at": "2024-06-12T19:24:54Z", + "updated_at": "2024-06-12T19:24:54Z", + "rerequestable": true, + "runs_rerequestable": true, + "latest_check_runs_count": 0, + "check_runs_url": "https://api.github.com/repos/$owner/$repo/check-suites/24835094650/check-runs", + "head_commit": { + "id": "$ref", + "tree_id": "d618b4f86e6593696f76331b35b19d5c2e3e1816", + "message": "refresh yvzxhSNRqX41x", + "timestamp": "2024-06-12T19:24:52Z", + "author": { + "name": "Mister Mistoffelees", + "email": "aloof@theroof.com" + }, + "committer": { + "name": "Mister Mistoffelees", + "email": "aloof@theroof.com" + } + } + }, + "repository": { + "id": 804427678, + "node_id": "R_kgDOL_KXng", + "name": "$repo", + "full_name": "$owner/$repo", + "private": false, + "owner": { + "login": "$owner", + "id": 123, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEwMTU4NTYw", + "avatar_url": "https://avatars.githubusercontent.com/u/123?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/$owner", + "html_url": "https://github.com/$owner", + "followers_url": "https://api.github.com/users/$owner/followers", + "following_url": "https://api.github.com/users/$owner/following{/other_user}", + "gists_url": "https://api.github.com/users/$owner/gists{/gist_id}", + "starred_url": "https://api.github.com/users/$owner/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/$owner/subscriptions", + "organizations_url": "https://api.github.com/users/$owner/orgs", + "repos_url": "https://api.github.com/users/$owner/repos", + "events_url": "https://api.github.com/users/$owner/events{/privacy}", + "received_events_url": "https://api.github.com/users/$owner/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/$owner/$repo", + "description": "Stuff with which to test mobu", + "fork": false, + "url": "https://api.github.com/repos/$owner/$repo", + "forks_url": "https://api.github.com/repos/$owner/$repo/forks", + "keys_url": "https://api.github.com/repos/$owner/$repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/$owner/$repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/$owner/$repo/teams", + "hooks_url": "https://api.github.com/repos/$owner/$repo/hooks", + "issue_events_url": "https://api.github.com/repos/$owner/$repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/$owner/$repo/events", + "assignees_url": "https://api.github.com/repos/$owner/$repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/$owner/$repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/$owner/$repo/tags", + "blobs_url": "https://api.github.com/repos/$owner/$repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/$owner/$repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/$owner/$repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/$owner/$repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/$owner/$repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/$owner/$repo/languages", + "stargazers_url": "https://api.github.com/repos/$owner/$repo/stargazers", + "contributors_url": "https://api.github.com/repos/$owner/$repo/contributors", + "subscribers_url": "https://api.github.com/repos/$owner/$repo/subscribers", + "subscription_url": "https://api.github.com/repos/$owner/$repo/subscription", + "commits_url": "https://api.github.com/repos/$owner/$repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/$owner/$repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/$owner/$repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/$owner/$repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/$owner/$repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/$owner/$repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/$owner/$repo/merges", + "archive_url": "https://api.github.com/repos/$owner/$repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/$owner/$repo/downloads", + "issues_url": "https://api.github.com/repos/$owner/$repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/$owner/$repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/$owner/$repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/$owner/$repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/$owner/$repo/labels{/name}", + "releases_url": "https://api.github.com/repos/$owner/$repo/releases{/id}", + "deployments_url": "https://api.github.com/repos/$owner/$repo/deployments", + "created_at": "2024-05-22T15:08:46Z", + "updated_at": "2024-06-12T19:24:22Z", + "pushed_at": "2024-06-12T19:24:53Z", + "git_url": "git://github.com/$owner/$repo.git", + "ssh_url": "git@github.com:$owner/$repo.git", + "clone_url": "https://github.com/$owner/$repo.git", + "svn_url": "https://github.com/$owner/$repo", + "homepage": null, + "size": 72, + "stargazers_count": 0, + "watchers_count": 0, + "language": "Jupyter Notebook", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + + ], + "visibility": "public", + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "main", + "custom_properties": { + + } + }, + "organization": { + "login": "$owner", + "id": 123, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEwMTU4NTYw", + "url": "https://api.github.com/orgs/$owner", + "repos_url": "https://api.github.com/orgs/$owner/repos", + "events_url": "https://api.github.com/orgs/$owner/events", + "hooks_url": "https://api.github.com/orgs/$owner/hooks", + "issues_url": "https://api.github.com/orgs/$owner/issues", + "members_url": "https://api.github.com/orgs/$owner/members{/member}", + "public_members_url": "https://api.github.com/orgs/$owner/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/123?v=4", + "description": "Rubin Observatory Science Quality and Reliability Engineering team" + }, + "sender": { + "login": "some-login", + "id": 456, + "node_id": "MDQ6VXNlcjMzMDQwMg==", + "avatar_url": "https://avatars.githubusercontent.com/u/456?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/some-login", + "html_url": "https://github.com/some-login", + "followers_url": "https://api.github.com/users/some-login/followers", + "following_url": "https://api.github.com/users/some-login/following{/other_user}", + "gists_url": "https://api.github.com/users/some-login/gists{/gist_id}", + "starred_url": "https://api.github.com/users/some-login/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/some-login/subscriptions", + "organizations_url": "https://api.github.com/users/some-login/orgs", + "repos_url": "https://api.github.com/users/some-login/repos", + "events_url": "https://api.github.com/users/some-login/events{/privacy}", + "received_events_url": "https://api.github.com/users/some-login/received_events", + "type": "User", + "site_admin": false + }, + "installation": { + "id": $installation_id, + "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uNTE1MzEyOTg=" + } +} diff --git a/tests/handlers/github_ci_app_test.py b/tests/handlers/github_ci_app_test.py new file mode 100644 index 00000000..5ef67aa0 --- /dev/null +++ b/tests/handlers/github_ci_app_test.py @@ -0,0 +1,204 @@ +"""Test the github ci app webhook handler.""" + +import hashlib +import hmac +from dataclasses import dataclass +from pathlib import Path +from string import Template + +import pytest +import respx +from httpx import AsyncClient +from pytest_mock import MockerFixture + +from mobu.services.github_ci.ci_manager import CiManager + +from ..support.constants import TEST_GITHUB_CI_APP_SECRET +from ..support.gafaelfawr import mock_gafaelfawr + + +@dataclass +class GitHubRequest: + payload: str + headers: dict[str, str] + + +def webhook_request( + event: str, + action: str, + owner: str = "org1", + repo: str = "repo1", + ref: str = "abc123", + *, + with_prs: bool = True, +) -> GitHubRequest: + """Build a GitHub webhook request and headers with the right hash.""" + data_path = Path(__file__).parent.parent / "data" / "github_webhooks" + suffix = "_no_prs" if not with_prs else "" + template = (data_path / f"{event}_{action}{suffix}.tmpl.json").read_text() + payload = Template(template).substitute( + owner=owner, + repo=repo, + ref=ref, + installation_id=123, + ) + + # https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries#python-example + hash_object = hmac.new( + TEST_GITHUB_CI_APP_SECRET.encode(), + msg=payload.encode(), + digestmod=hashlib.sha256, + ) + sig = "sha256=" + hash_object.hexdigest() + + headers = { + "Accept": "*/*", + "Content-Type": "application/json", + "User-Agent": "GitHub-Hookshot/c9d6c0a", + "X-GitHub-Delivery": "d2d3c948-1d61-11ef-848a-c578f23615c9", + "X-GitHub-Event": event, + "X-GitHub-Hook-ID": "479971864", + "X-GitHub-Hook-Installation-Target-ID": "1", + "X-GitHub-Hook-Installation-Target-Type": "integration", + "X-Hub-Signature-256": sig, + } + + return GitHubRequest(payload=payload, headers=headers) + + +@pytest.mark.asyncio +async def test_not_enabled( + client: AsyncClient, + anon_client: AsyncClient, + respx_mock: respx.Router, +) -> None: + mock_gafaelfawr(respx_mock) + request = webhook_request( + event="check_suite", + action="requested", + ) + response = await anon_client.post( + "/mobu/github/ci/webhook", + headers=request.headers, + content=request.payload, + ) + + assert response.status_code == 404 + + +@pytest.mark.asyncio +@pytest.mark.usefixtures("_enable_github_ci_app") +async def test_unacceptable_org( + client: AsyncClient, + anon_client: AsyncClient, + respx_mock: respx.Router, + mocker: MockerFixture, +) -> None: + mock_gafaelfawr(respx_mock) + request = webhook_request( + event="check_suite", + action="requested", + owner="nope", + ) + + mock_func = mocker.patch.object( + CiManager, + "enqueue", + ) + response = await anon_client.post( + "/mobu/github/ci/webhook", + headers=request.headers, + content=request.payload, + ) + + assert response.status_code == 403 + assert not mock_func.called + + +@pytest.mark.asyncio +@pytest.mark.usefixtures("_enable_github_ci_app") +@pytest.mark.parametrize( + "gh_request", + [ + webhook_request( + event="check_suite", + action="requested", + ), + webhook_request( + event="check_suite", + action="rerequested", + ), + webhook_request( + event="check_run", + action="rerequested", + ), + ], +) +async def test_should_enqueue( + client: AsyncClient, + anon_client: AsyncClient, + respx_mock: respx.Router, + mocker: MockerFixture, + gh_request: GitHubRequest, +) -> None: + mock_gafaelfawr(respx_mock) + + mock_func = mocker.patch.object( + CiManager, + "enqueue", + ) + + response = await anon_client.post( + "/mobu/github/ci/webhook", + headers=gh_request.headers, + content=gh_request.payload, + ) + + assert response.status_code == 202 + assert mock_func.called + + +@pytest.mark.asyncio +@pytest.mark.usefixtures("_enable_github_ci_app") +@pytest.mark.parametrize( + "gh_request", + [ + webhook_request( + event="check_suite", + action="requested", + with_prs=False, + ), + webhook_request( + event="check_suite", + action="rerequested", + with_prs=False, + ), + webhook_request( + event="check_run", + action="rerequested", + with_prs=False, + ), + ], +) +async def test_should_ignore( + client: AsyncClient, + anon_client: AsyncClient, + respx_mock: respx.Router, + mocker: MockerFixture, + gh_request: GitHubRequest, +) -> None: + mock_gafaelfawr(respx_mock) + + mock_func = mocker.patch.object( + CiManager, + "enqueue", + ) + + response = await anon_client.post( + "/mobu/github/ci/webhook", + headers=gh_request.headers, + content=gh_request.payload, + ) + + assert response.status_code == 202 + assert not mock_func.called