From f17368893537db933bd1f9b9cc132ea7c4812fe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sat, 20 Nov 2021 14:22:32 +0100 Subject: [PATCH] Various api improvements - tweak function names for better swagger labels - more build query options - more build-events query options - allow undeploying several builds at once --- log-config.yaml | 2 ++ src/runboat/api.py | 57 +++++++++++++++++++++++++++++++++++++------- src/runboat/db.py | 17 ++++++++++++- src/runboat/webui.py | 12 ++++++++-- 4 files changed, 77 insertions(+), 11 deletions(-) diff --git a/log-config.yaml b/log-config.yaml index ceba42f..78f5c00 100644 --- a/log-config.yaml +++ b/log-config.yaml @@ -17,3 +17,5 @@ root: loggers: kubernetes.client.rest: level: INFO + sse_starlette.sse: + level: INFO diff --git a/src/runboat/api.py b/src/runboat/api.py index d8479e7..e832330 100644 --- a/src/runboat/api.py +++ b/src/runboat/api.py @@ -80,8 +80,33 @@ async def repos() -> list[models.Repo]: response_model=list[Build], response_model_exclude_none=True, ) -async def builds(repo: Optional[str] = None) -> list[models.Build]: - return list(controller.db.search(repo)) +async def builds( + repo: Optional[str] = None, + target_branch: Optional[str] = None, + branch: Optional[str] = None, + pr: Optional[int] = None, +) -> list[models.Build]: + return list( + controller.db.search( + repo=repo, target_branch=target_branch, branch=branch, pr=pr + ) + ) + + +@router.delete( + "/builds", + dependencies=[Depends(authenticated)], +) +async def undeploy_builds( + repo: Optional[str] = None, + target_branch: Optional[str] = None, + branch: Optional[str] = None, + pr: Optional[int] = None, +) -> None: + for build in controller.db.search( + repo=repo, target_branch=target_branch, branch=branch, pr=pr + ): + await build.undeploy() @router.post( @@ -151,21 +176,21 @@ async def log(name: str) -> str: @router.post("/builds/{name}/start") -async def start(name: str) -> None: +async def start_build(name: str) -> None: """Start the deployment.""" build = await _build_by_name(name) await build.start() @router.post("/builds/{name}/stop") -async def stop(name: str) -> None: +async def stop_build(name: str) -> None: """Stop the deployment.""" build = await _build_by_name(name) await build.stop() @router.delete("/builds/{name}", dependencies=[Depends(authenticated)]) -async def delete(name: str) -> None: +async def undeploy_build(name: str) -> None: """Delete the deployment and drop the database.""" build = await _build_by_name(name) await build.undeploy() @@ -177,12 +202,16 @@ class BuildEventSource: request: Request, repo: str | None = None, target_branch: str | None = None, + branch: str | None = None, + pr: int | None = None, build_name: str | None = None, ): self.queue: asyncio.Queue[str] = asyncio.Queue() self.request = request self.repo = repo self.target_branch = target_branch + self.branch = branch + self.pr = pr self.build_name = build_name controller.db.register_listener(self) @@ -195,13 +224,21 @@ class BuildEventSource: return if self.target_branch and build.target_branch != self.target_branch: return + if self.branch and (build.target_branch != self.branch or build.pr): + return + if self.pr and build.pr != self.pr: + return if self.build_name and build.name != self.build_name: return self.queue.put_nowait(self._serialize(event, build)) async def events(self) -> AsyncGenerator[str, None]: for build in controller.db.search( - self.repo, self.target_branch, self.build_name + repo=self.repo, + target_branch=self.target_branch, + branch=self.branch, + pr=self.pr, + name=self.build_name, ): yield self._serialize(models.BuildEvent.modified, build) while True: @@ -217,11 +254,15 @@ class BuildEventSource: @router.get("/build-events") -async def eventsource_endpoint( +async def build_events( request: Request, repo: Optional[str] = None, target_branch: Optional[str] = None, + branch: Optional[str] = None, + pr: Optional[int] = None, build_name: Optional[str] = None, ) -> EventSourceResponse: - event_source = BuildEventSource(request, repo, target_branch, build_name) + event_source = BuildEventSource( + request, repo, target_branch, branch, pr, build_name + ) return EventSourceResponse(event_source.events()) diff --git a/src/runboat/db.py b/src/runboat/db.py index c7b3937..b1331d2 100644 --- a/src/runboat/db.py +++ b/src/runboat/db.py @@ -13,6 +13,9 @@ class BuildListener(Protocol): ... +NoPr = 0 + + class BuildsDb: """An in-memory database of builds. @@ -192,17 +195,29 @@ class BuildsDb: self, repo: str | None = None, target_branch: str | None = None, + branch: str | None = None, + pr: int | None = None, name: str | None = None, ) -> Iterator[Build]: query = "SELECT * FROM builds " where = [] - params = [] + params: list[str | int] = [] if repo: where.append("repo=?") params.append(repo.lower()) if target_branch: where.append("target_branch=?") params.append(target_branch) + if branch: + where.append("target_branch=?") + params.append(branch) + where.append("pr IS NULL") + if pr is not None: + if pr == NoPr: + where.append("pr IS NULL") + else: + where.append("pr=?") + params.append(pr) if name: where.append("name=?") params.append(name) diff --git a/src/runboat/webui.py b/src/runboat/webui.py index ea1dd3e..29a5f6b 100644 --- a/src/runboat/webui.py +++ b/src/runboat/webui.py @@ -1,7 +1,7 @@ from typing import Optional from fastapi import APIRouter, HTTPException, Response, status -from fastapi.responses import HTMLResponse, RedirectResponse +from fastapi.responses import RedirectResponse from .controller import controller from .models import BuildStatus @@ -9,7 +9,15 @@ from .models import BuildStatus router = APIRouter() -@router.get("/builds/{name}", response_class=HTMLResponse) +@router.get("/builds", response_class=RedirectResponse) +async def builds(repo: str, target_branch: Optional[str] = None) -> Response: + url = f"/webui/builds.html?repo={repo}" + if target_branch: + url += f"&target_branch={target_branch}" + return RedirectResponse(url=url) + + +@router.get("/builds/{name}", response_class=RedirectResponse) async def build(name: str, live: Optional[str] = None) -> Response: build = controller.db.get(name) if not build: