GitHub status now point back to a dedicagted page
It automatically redirects with the build homepage, or show a page with button to start/stop the build.
This commit is contained in:
parent
24d9be3b0c
commit
bc2740029d
8 changed files with 83 additions and 13 deletions
|
|
@ -2,8 +2,9 @@ RUNBOAT_SUPPORTED_REPOS=["OCA/mis-builder", "shopinvader/odoo-shopinvader", "OCA
|
|||
RUNBOAT_API_ADMIN_USER="admin"
|
||||
RUNBOAT_API_ADMIN_PASSWD="admin"
|
||||
RUNBOAT_BUILD_NAMESPACE=runboat-builds
|
||||
RUNBOAT_BUILD_DOMAIN=runboat.odoo-community.org
|
||||
RUNBOAT_BUILD_DOMAIN=runboat-builds.odoo-community.org
|
||||
RUNBOAT_BUILD_ENV={"PGHOST": "postgres14.runboat-builds-db", "PGPORT": "5432", "PGUSER": "runboat-build"}
|
||||
RUNBOAT_BUILD_SECRET_ENV={"PGPASSWORD": "..."}
|
||||
RUNBOAT_GITHUB_TOKEN=
|
||||
RUNBOAT_LOG_CONFIG=log-config.yaml
|
||||
RUNBOAT_BASE_URL=https://runboat.odoo-community.org
|
||||
|
|
|
|||
|
|
@ -16,11 +16,12 @@ ENV RUNBOAT_SUPPORTED_REPOS='["OCA/mis-builder", "shopinvader/odoo-shopinvader",
|
|||
ENV RUNBOAT_API_ADMIN_USER="admin"
|
||||
ENV RUNBOAT_API_ADMIN_PASSWD="admin"
|
||||
ENV RUNBOAT_BUILD_NAMESPACE=runboat-builds
|
||||
ENV RUNBOAT_BUILD_DOMAIN=runboat.example.com
|
||||
ENV RUNBOAT_BUILD_DOMAIN=runboat-builds.example.com
|
||||
ENV RUNBOAT_BUILD_ENV='{"PGHOST": "postgres14.runboat-builds-db", "PGPORT": "5432", "PGUSER": "runboat-build"}'
|
||||
ENV RUNBOAT_BUILD_SECRET_ENV='{"PGPASSWORD": "..."}'
|
||||
ENV RUNBOAT_GITHUB_TOKEN=
|
||||
ENV RUNBOAT_LOG_CONFIG=/etc/runboat-log-config.yaml
|
||||
ENV RUNBOAT_BASE_URL=https://runboat.example.com
|
||||
|
||||
ENV KUBECONFIG=/run/kubeconfig
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ Runboat has the following main components:
|
|||
accessible;
|
||||
- when the initializaiton job fails, flag the deployment as failed;
|
||||
- when there are too many deployments started, stop the oldest started;
|
||||
- when there are too many deployments, deleted the oldest created;
|
||||
- when there are too many deployments, delete the oldest created;
|
||||
- when a deployment is deleted, run a cleanp job to drop the database and delete
|
||||
all kubernetes resources associated with the deployment.
|
||||
|
||||
|
|
@ -114,6 +114,8 @@ actually deploy. It expects the following to hold true:
|
|||
- `runboat/pr`: the pull request number if this build is for a pull request;
|
||||
- `runboat/git-commit`: the commit sha.
|
||||
|
||||
- the home page of a running build is exposed at `http://{build_slug}.{build_domain}`.
|
||||
|
||||
During the lifecycle of a build, the controller does the following on the deployed
|
||||
resources:
|
||||
|
||||
|
|
@ -139,9 +141,6 @@ MVP:
|
|||
- look at other TODO in code to see if anything important remains
|
||||
- basic UI (single page with a combo box to select repo and show builds by branch/pr,
|
||||
with start/stop buttons)
|
||||
- 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,
|
||||
or to a build details page with action buttons (start, stop, view log, etc)
|
||||
- secure github webhooks
|
||||
- deployment and more load testing
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
from fastapi import FastAPI
|
||||
|
||||
from . import __version__, api, controller, k8s, webhooks
|
||||
from . import __version__, api, controller, k8s, webhooks, webui
|
||||
|
||||
app = FastAPI(
|
||||
title="Runboat", description="Runbot on Kubernetes ☸️", version=__version__
|
||||
)
|
||||
app.include_router(api.router, prefix="/api/v1")
|
||||
app.include_router(webhooks.router)
|
||||
app.include_router(api.router, prefix="/api/v1", tags=["api"])
|
||||
app.include_router(webhooks.router, tags=["webhooks"])
|
||||
app.include_router(webui.router, tags=["webui"])
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
|
|
|
|||
|
|
@ -132,6 +132,18 @@ class Build(BaseModel):
|
|||
def link(self) -> str:
|
||||
return f"http://{self.slug}.{settings.build_domain}"
|
||||
|
||||
@property
|
||||
def repo_link(self) -> str:
|
||||
link = f"https://github.com/{self.repo}"
|
||||
if self.pr:
|
||||
return f"{link}/pull/{self.pr}"
|
||||
else:
|
||||
return f"{link}/tree/{self.target_branch}"
|
||||
|
||||
@property
|
||||
def live_link(self) -> str:
|
||||
return f"{settings.base_url}/builds/{self.name}?live"
|
||||
|
||||
@classmethod
|
||||
async def deploy(
|
||||
cls, repo: str, target_branch: str, pr: int | None, git_commit: str
|
||||
|
|
@ -178,7 +190,7 @@ class Build(BaseModel):
|
|||
self.repo,
|
||||
self.git_commit,
|
||||
GitHubStatusState.pending,
|
||||
target_url=None,
|
||||
target_url=self.live_link,
|
||||
)
|
||||
elif self.status in (BuildStatus.stopped, BuildStatus.stopping):
|
||||
_logger.info(f"Starting {self} that was last scaled on {self.last_scaled}.")
|
||||
|
|
@ -246,7 +258,7 @@ class Build(BaseModel):
|
|||
self.repo,
|
||||
self.git_commit,
|
||||
GitHubStatusState.pending,
|
||||
target_url=None,
|
||||
target_url=self.live_link,
|
||||
)
|
||||
|
||||
async def on_initialize_succeeded(self) -> None:
|
||||
|
|
@ -260,7 +272,7 @@ class Build(BaseModel):
|
|||
self.repo,
|
||||
self.git_commit,
|
||||
GitHubStatusState.success,
|
||||
target_url=self.link,
|
||||
target_url=self.live_link,
|
||||
)
|
||||
|
||||
async def on_initialize_failed(self) -> None:
|
||||
|
|
@ -274,7 +286,7 @@ class Build(BaseModel):
|
|||
self.repo,
|
||||
self.git_commit,
|
||||
GitHubStatusState.failure,
|
||||
target_url=None,
|
||||
target_url=self.live_link,
|
||||
)
|
||||
|
||||
async def on_cleanup_started(self) -> None:
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ class Settings(BaseSettings):
|
|||
github_token: Optional[str]
|
||||
# The file with the python logging configuration to use for the runboat controller.
|
||||
log_config: Optional[str]
|
||||
# The base url where the runboat UI and API is exposed on internet.
|
||||
# Used to generate backlinks in GitHub statuses
|
||||
base_url: str = "http://localhost:8000"
|
||||
|
||||
class Config:
|
||||
env_prefix = "RUNBOAT_"
|
||||
|
|
|
|||
24
src/runboat/webui.py
Normal file
24
src/runboat/webui.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Request, status
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
from .controller import controller
|
||||
from .models import BuildStatus
|
||||
|
||||
router = APIRouter()
|
||||
templates = Jinja2Templates(directory=Path(__file__).parent / "webui")
|
||||
|
||||
|
||||
@router.get("/builds/{name}", response_class=HTMLResponse)
|
||||
async def build(request: Request, name: str, live: Optional[str] = None):
|
||||
build = controller.db.get(name)
|
||||
if not build:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND)
|
||||
if live is not None and build.status == BuildStatus.started:
|
||||
return RedirectResponse(url=build.link)
|
||||
return templates.TemplateResponse(
|
||||
"build.html", {"request": request, "build": build}
|
||||
)
|
||||
29
src/runboat/webui/build.html
Normal file
29
src/runboat/webui/build.html
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Runboat build {{ build.name }} for {{ build.repo }}</title>
|
||||
<script>
|
||||
build_name = "{{ build.name }}";
|
||||
function start() {
|
||||
fetch(`/api/v1/builds/${build_name}/start`, options={method: 'POST'})
|
||||
}
|
||||
function stop() {
|
||||
fetch(`/api/v1/builds/${build_name}/stop`, options={method: 'POST'})
|
||||
}
|
||||
</script>
|
||||
<body>
|
||||
<p>Repo: {{ build.repo }}</p>
|
||||
{% if build.pr %}
|
||||
<p>PR: <a href="{{ build.repo_link }}">{{ build.pr }}</a> to {{ build.target_branch }}</p>
|
||||
{% else %}
|
||||
<p>Branch: <a href="{{ build.repo_link }}">{{ build.target_branch }}</a></p>
|
||||
{% endif %}
|
||||
<p>Commit: {{ build.git_commit }}</p>
|
||||
<p>Status: {{ build.status }}</p>
|
||||
{% if build.status == 'started' %}
|
||||
<p><a href="{{ build.live_link }}">=> live</a></p>
|
||||
<button onclick="stop()">stop</button>
|
||||
{% else %}
|
||||
<button onclick="start()">start</button>
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in a new issue