Report build statuses to GitHub
This commit is contained in:
parent
968f94c1e7
commit
903a05fc0e
3 changed files with 65 additions and 6 deletions
|
|
@ -122,7 +122,6 @@ resources:
|
|||
|
||||
Prototype (min required to open the project):
|
||||
|
||||
- report build status to github
|
||||
- plug it on a bunch of OCA and shopinvader repos to test load
|
||||
- basic tests
|
||||
|
||||
|
|
@ -142,6 +141,8 @@ More:
|
|||
|
||||
- shiny UI
|
||||
- websocket stream of build changes, for a dynamic UI
|
||||
- better target_url in GitHub status: instead of providing the link to the ingress,
|
||||
provide a link to the build, which redirects to the ingress if the build is started
|
||||
- handle PR close (delete all builds for PR)
|
||||
- handle branch delete (delete all builds for branch)
|
||||
- create builds for all supported repos on startup (goes with sticky branches)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import logging
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
|
@ -6,8 +8,10 @@ import httpx
|
|||
from .exceptions import NotFoundOnGitHub
|
||||
from .settings import settings
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
async def _github_get(url: str) -> Any:
|
||||
|
||||
async def _github_request(method: str, url: str, json: Any = None) -> Any:
|
||||
async with httpx.AsyncClient() as client:
|
||||
full_url = f"https://api.github.com{url}"
|
||||
headers = {
|
||||
|
|
@ -15,7 +19,8 @@ async def _github_get(url: str) -> Any:
|
|||
}
|
||||
if settings.github_token:
|
||||
headers["Authorization"] = f"token {settings.github_token}"
|
||||
response = await client.get(full_url, headers=headers)
|
||||
_logger.debug("%s %s", method, full_url)
|
||||
response = await client.request(method, full_url, headers=headers, json=json)
|
||||
if response.status_code == 404:
|
||||
raise NotFoundOnGitHub(f"GitHub URL not found: {full_url}.")
|
||||
response.raise_for_status()
|
||||
|
|
@ -30,7 +35,7 @@ class BranchInfo:
|
|||
|
||||
|
||||
async def get_branch_info(repo: str, branch: str) -> BranchInfo:
|
||||
branch_data = await _github_get(f"/repos/{repo}/git/ref/heads/{branch}")
|
||||
branch_data = await _github_request("GET", f"/repos/{repo}/git/ref/heads/{branch}")
|
||||
return BranchInfo(
|
||||
repo=repo,
|
||||
name=branch,
|
||||
|
|
@ -47,10 +52,32 @@ class PullInfo:
|
|||
|
||||
|
||||
async def get_pull_info(repo: str, pr: int) -> PullInfo:
|
||||
pr_data = await _github_get(f"/repos/{repo}/pulls/{pr}")
|
||||
pr_data = await _github_request("GET", f"/repos/{repo}/pulls/{pr}")
|
||||
return PullInfo(
|
||||
repo=repo,
|
||||
number=pr,
|
||||
head_sha=pr_data["head"]["sha"],
|
||||
target_branch=pr_data["base"]["ref"],
|
||||
)
|
||||
|
||||
|
||||
class GitHubStatusState(str, Enum):
|
||||
error = "error"
|
||||
failure = "failure"
|
||||
pending = "pending"
|
||||
success = "success"
|
||||
|
||||
|
||||
async def notify_status(
|
||||
repo: str, sha: str, state: GitHubStatusState, target_url: str | None
|
||||
) -> None:
|
||||
# https://docs.github.com/en/rest/reference/repos#create-a-commit-status
|
||||
await _github_request(
|
||||
"POST",
|
||||
f"/repos/{repo}/statuses/{sha}",
|
||||
json={
|
||||
"state": state,
|
||||
"target_url": target_url,
|
||||
"context": "ci/runboat",
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ from typing import Optional
|
|||
from kubernetes.client.models.v1_deployment import V1Deployment
|
||||
from pydantic import BaseModel
|
||||
|
||||
from . import k8s
|
||||
from . import github, k8s
|
||||
from .build_images import get_build_image
|
||||
from .github import GitHubStatusState
|
||||
from .settings import settings
|
||||
from .utils import slugify
|
||||
|
||||
|
|
@ -151,6 +152,12 @@ class Build(BaseModel):
|
|||
image,
|
||||
)
|
||||
await k8s.deploy(deployment_vars)
|
||||
await github.notify_status(
|
||||
repo,
|
||||
git_commit,
|
||||
GitHubStatusState.pending,
|
||||
target_url=None,
|
||||
)
|
||||
|
||||
async def start(self) -> None:
|
||||
"""Start build if init succeeded, or reinitialize if failed."""
|
||||
|
|
@ -167,6 +174,12 @@ class Build(BaseModel):
|
|||
_logger.info(f"Marking failed {self} for reinitialization.")
|
||||
await k8s.delete_job(self.name, job_kind=k8s.DeploymentMode.initialize)
|
||||
await self._patch(init_status=BuildInitStatus.todo, desired_replicas=0)
|
||||
await github.notify_status(
|
||||
self.repo,
|
||||
self.git_commit,
|
||||
GitHubStatusState.pending,
|
||||
target_url=None,
|
||||
)
|
||||
elif self.status in (BuildStatus.stopped, BuildStatus.stopping):
|
||||
_logger.info(f"Starting {self} that was last scaled on {self.last_scaled}.")
|
||||
await self._patch(desired_replicas=1)
|
||||
|
|
@ -229,6 +242,12 @@ class Build(BaseModel):
|
|||
return
|
||||
_logger.info(f"Initialization job started for {self}.")
|
||||
await self._patch(init_status=BuildInitStatus.started, desired_replicas=0)
|
||||
await github.notify_status(
|
||||
self.repo,
|
||||
self.git_commit,
|
||||
GitHubStatusState.pending,
|
||||
target_url=None,
|
||||
)
|
||||
|
||||
async def on_initialize_succeeded(self) -> None:
|
||||
if self.init_status == BuildInitStatus.succeeded:
|
||||
|
|
@ -237,6 +256,12 @@ class Build(BaseModel):
|
|||
return
|
||||
_logger.info(f"Initialization job succeded for {self}, starting.")
|
||||
await self._patch(init_status=BuildInitStatus.succeeded, desired_replicas=1)
|
||||
await github.notify_status(
|
||||
self.repo,
|
||||
self.git_commit,
|
||||
GitHubStatusState.success,
|
||||
target_url=self.link,
|
||||
)
|
||||
|
||||
async def on_initialize_failed(self) -> None:
|
||||
if self.init_status == BuildInitStatus.failed:
|
||||
|
|
@ -245,6 +270,12 @@ class Build(BaseModel):
|
|||
return
|
||||
_logger.info(f"Initialization job failed for {self}.")
|
||||
await self._patch(init_status=BuildInitStatus.failed, desired_replicas=0)
|
||||
await github.notify_status(
|
||||
self.repo,
|
||||
self.git_commit,
|
||||
GitHubStatusState.failure,
|
||||
target_url=None,
|
||||
)
|
||||
|
||||
async def on_cleanup_started(self) -> None:
|
||||
_logger.info(f"Cleanup job started for {self}.")
|
||||
|
|
|
|||
Loading…
Reference in a new issue