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
This commit is contained in:
Stéphane Bidoul 2021-11-20 14:22:32 +01:00
parent dc6d75c94a
commit f173688935
No known key found for this signature in database
GPG key ID: BCAB2555446B5B92
4 changed files with 77 additions and 11 deletions

View file

@ -17,3 +17,5 @@ root:
loggers: loggers:
kubernetes.client.rest: kubernetes.client.rest:
level: INFO level: INFO
sse_starlette.sse:
level: INFO

View file

@ -80,8 +80,33 @@ async def repos() -> list[models.Repo]:
response_model=list[Build], response_model=list[Build],
response_model_exclude_none=True, response_model_exclude_none=True,
) )
async def builds(repo: Optional[str] = None) -> list[models.Build]: async def builds(
return list(controller.db.search(repo)) 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( @router.post(
@ -151,21 +176,21 @@ async def log(name: str) -> str:
@router.post("/builds/{name}/start") @router.post("/builds/{name}/start")
async def start(name: str) -> None: async def start_build(name: str) -> None:
"""Start the deployment.""" """Start the deployment."""
build = await _build_by_name(name) build = await _build_by_name(name)
await build.start() await build.start()
@router.post("/builds/{name}/stop") @router.post("/builds/{name}/stop")
async def stop(name: str) -> None: async def stop_build(name: str) -> None:
"""Stop the deployment.""" """Stop the deployment."""
build = await _build_by_name(name) build = await _build_by_name(name)
await build.stop() await build.stop()
@router.delete("/builds/{name}", dependencies=[Depends(authenticated)]) @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.""" """Delete the deployment and drop the database."""
build = await _build_by_name(name) build = await _build_by_name(name)
await build.undeploy() await build.undeploy()
@ -177,12 +202,16 @@ class BuildEventSource:
request: Request, request: Request,
repo: str | None = None, repo: str | None = None,
target_branch: str | None = None, target_branch: str | None = None,
branch: str | None = None,
pr: int | None = None,
build_name: str | None = None, build_name: str | None = None,
): ):
self.queue: asyncio.Queue[str] = asyncio.Queue() self.queue: asyncio.Queue[str] = asyncio.Queue()
self.request = request self.request = request
self.repo = repo self.repo = repo
self.target_branch = target_branch self.target_branch = target_branch
self.branch = branch
self.pr = pr
self.build_name = build_name self.build_name = build_name
controller.db.register_listener(self) controller.db.register_listener(self)
@ -195,13 +224,21 @@ class BuildEventSource:
return return
if self.target_branch and build.target_branch != self.target_branch: if self.target_branch and build.target_branch != self.target_branch:
return 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: if self.build_name and build.name != self.build_name:
return return
self.queue.put_nowait(self._serialize(event, build)) self.queue.put_nowait(self._serialize(event, build))
async def events(self) -> AsyncGenerator[str, None]: async def events(self) -> AsyncGenerator[str, None]:
for build in controller.db.search( 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) yield self._serialize(models.BuildEvent.modified, build)
while True: while True:
@ -217,11 +254,15 @@ class BuildEventSource:
@router.get("/build-events") @router.get("/build-events")
async def eventsource_endpoint( async def build_events(
request: Request, request: Request,
repo: Optional[str] = None, repo: Optional[str] = None,
target_branch: Optional[str] = None, target_branch: Optional[str] = None,
branch: Optional[str] = None,
pr: Optional[int] = None,
build_name: Optional[str] = None, build_name: Optional[str] = None,
) -> EventSourceResponse: ) -> 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()) return EventSourceResponse(event_source.events())

View file

@ -13,6 +13,9 @@ class BuildListener(Protocol):
... ...
NoPr = 0
class BuildsDb: class BuildsDb:
"""An in-memory database of builds. """An in-memory database of builds.
@ -192,17 +195,29 @@ class BuildsDb:
self, self,
repo: str | None = None, repo: str | None = None,
target_branch: str | None = None, target_branch: str | None = None,
branch: str | None = None,
pr: int | None = None,
name: str | None = None, name: str | None = None,
) -> Iterator[Build]: ) -> Iterator[Build]:
query = "SELECT * FROM builds " query = "SELECT * FROM builds "
where = [] where = []
params = [] params: list[str | int] = []
if repo: if repo:
where.append("repo=?") where.append("repo=?")
params.append(repo.lower()) params.append(repo.lower())
if target_branch: if target_branch:
where.append("target_branch=?") where.append("target_branch=?")
params.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: if name:
where.append("name=?") where.append("name=?")
params.append(name) params.append(name)

View file

@ -1,7 +1,7 @@
from typing import Optional from typing import Optional
from fastapi import APIRouter, HTTPException, Response, status from fastapi import APIRouter, HTTPException, Response, status
from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.responses import RedirectResponse
from .controller import controller from .controller import controller
from .models import BuildStatus from .models import BuildStatus
@ -9,7 +9,15 @@ from .models import BuildStatus
router = APIRouter() 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: async def build(name: str, live: Optional[str] = None) -> Response:
build = controller.db.get(name) build = controller.db.get(name)
if not build: if not build: