Compare commits

...

1 commit

Author SHA1 Message Date
7d61782a2c feat: add configurable forge base URLs for Forgejo/Gitea support
Add RUNBOAT_FORGE_API_BASE_URL and RUNBOAT_FORGE_WEB_BASE_URL settings so
runboat can be used with Forgejo, Gitea, or any GitHub-compatible forge,
not just github.com. GitHub remains the default for full backwards
compatibility.

- RUNBOAT_FORGE_API_BASE_URL: API base URL (default: https://api.github.com)
- RUNBOAT_FORGE_WEB_BASE_URL: web UI base URL (default: https://github.com)

The webhook signature verification (X-Hub-Signature-256) and payload
structure are already compatible with Forgejo/Gitea.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 18:49:13 -04:00
4 changed files with 21 additions and 13 deletions

View file

@ -16,8 +16,10 @@ Runboat has the following main components:
- An in-memory database of deployed builds, with their current status. - An in-memory database of deployed builds, with their current status.
- A REST API to list builds and trigger new deployments as well as start, stop, redeploy - A REST API to list builds and trigger new deployments as well as start, stop, redeploy
or undeploy builds. or undeploy builds.
- A GitHub webhook to automatically trigger new builds on pushes to branches and pull - A git forge webhook to automatically trigger new builds on pushes to branches and pull
requests of supported repositories and branches (configured via regular expressions). requests of supported repositories and branches (configured via regular expressions).
GitHub is the default; Forgejo/Gitea and other GitHub-compatible forges are supported
via `RUNBOAT_FORGE_API_BASE_URL` and `RUNBOAT_FORGE_WEB_BASE_URL`.
- A controller that performs the following tasks: - A controller that performs the following tasks:
- monitor deployments in a kubernetes namespaces to maintain the in-memory database; - monitor deployments in a kubernetes namespaces to maintain the in-memory database;

View file

@ -13,7 +13,7 @@ _logger = logging.getLogger(__name__)
async def _github_request(method: str, url: str, json: Any = None) -> Any: async def _github_request(method: str, url: str, json: Any = None) -> Any:
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
full_url = f"https://api.github.com{url}" full_url = f"{settings.forge_api_base_url}{url}"
headers = { headers = {
"Accept": "application/vnd.github.v3+json", "Accept": "application/vnd.github.v3+json",
} }
@ -21,7 +21,7 @@ async def _github_request(method: str, url: str, json: Any = None) -> Any:
headers["Authorization"] = f"token {settings.github_token}" headers["Authorization"] = f"token {settings.github_token}"
response = await client.request(method, full_url, headers=headers, json=json) response = await client.request(method, full_url, headers=headers, json=json)
if response.status_code == 404: if response.status_code == 404:
raise NotFoundOnGitHub(f"GitHub URL not found: {full_url}.") raise NotFoundOnGitHub(f"Forge URL not found: {full_url}.")
response.raise_for_status() response.raise_for_status()
return response.json() return response.json()

View file

@ -141,23 +141,22 @@ class Build(BaseModel):
@property @property
def repo_target_branch_link(self) -> str: def repo_target_branch_link(self) -> str:
return ( return (
f"https://github.com/{self.commit_info.repo}" f"{settings.forge_web_base_url}/{self.commit_info.repo}"
f"/tree/{self.commit_info.target_branch}" f"/src/branch/{self.commit_info.target_branch}"
) )
@property @property
def repo_pr_link(self) -> str | None: def repo_pr_link(self) -> str | None:
if not self.commit_info.pr: if not self.commit_info.pr:
return None return None
return f"https://github.com/{self.commit_info.repo}/pull/{self.commit_info.pr}" return f"{settings.forge_web_base_url}/{self.commit_info.repo}/pulls/{self.commit_info.pr}"
@property @property
def repo_commit_link(self) -> str: def repo_commit_link(self) -> str:
link = f"https://github.com/{self.commit_info.repo}" link = f"{settings.forge_web_base_url}/{self.commit_info.repo}"
if self.commit_info.pr: if self.commit_info.pr:
return ( return (
f"{link}/pull/{self.commit_info.pr}" f"{link}/pulls/{self.commit_info.pr}"
f"/commits/{self.commit_info.git_commit}"
) )
else: else:
return f"{link}/commit/{self.commit_info.git_commit}" return f"{link}/commit/{self.commit_info.git_commit}"
@ -418,4 +417,4 @@ class Repo(BaseModel):
@property @property
def link(self) -> str: def link(self) -> str:
return f"https://github.com/{self.name}" return f"{settings.forge_web_base_url}/{self.name}"

View file

@ -70,10 +70,17 @@ class Settings(BaseSettings):
build_default_kubefiles_path: Annotated[ build_default_kubefiles_path: Annotated[
Path | None, BeforeValidator(validate_path) Path | None, BeforeValidator(validate_path)
] = None ] = None
# The token to use for the GitHub api calls (to query branches and pull requests, # The base URL of the git forge API (e.g. https://api.github.com for GitHub,
# and report build statuses). # or https://git.example.com/api/v1 for Forgejo/Gitea).
# Defaults to the GitHub API for backwards compatibility.
forge_api_base_url: str = "https://api.github.com"
# The base URL of the git forge web UI (e.g. https://github.com for GitHub,
# or https://git.example.com for Forgejo/Gitea).
forge_web_base_url: str = "https://github.com"
# The token to use for the git forge API calls (to query branches and pull
# requests, and report build statuses).
github_token: str | None = None github_token: str | None = None
# The secret used to verify GitHub webhook signatures # The secret used to verify git forge webhook signatures (X-Hub-Signature-256).
github_webhook_secret: bytes | None = None github_webhook_secret: bytes | None = None
# The file with the python logging configuration to use for the runboat controller. # The file with the python logging configuration to use for the runboat controller.
log_config: str | None = None log_config: str | None = None