Files
windy-git/api/app/providers/registry.py
Grant Whitmer ce54d488f2 G1: stop probing the tunnel from inside a container
cloudflared binds 127.0.0.1:2000 on the HOST. This process runs in a container
whose only route to the host is the bridge gateway (172.17.0.1), where nothing
is listening — so the check was permanently red regardless of what the tunnel
was actually doing.

Binding the metrics endpoint wider would have fixed the probe and made a
metrics bind failure capable of taking down ingress. That is a worse trade than
losing one row on a dashboard.

The check is not silently dropped: /health/full now carries a 'not_checked_here'
map naming the tunnel and where its health actually lives (systemd
windygit-tunnel). An observer should never have to wonder whether a missing
check means healthy or means forgotten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 14:37:53 -04:00

113 lines
3.8 KiB
Python

"""Concrete providers: Gitea, R2, Eternitas, Postgres, tunnel.
Each is a real probe against the real dependency (I-8). None of them has a mock
mode. If you find yourself adding one, add it behind `configured` returning False
instead — an unconfigured provider is honest; a mock provider is a liar with a
green light.
"""
from __future__ import annotations
import httpx
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncEngine
from api.app.config import Settings
from api.app.providers.base import ProbeResult, Provider
_TIMEOUT = httpx.Timeout(5.0, connect=3.0)
class GiteaProvider(Provider):
"""Gitea is a COMPONENT behind an API membrane, never a merged tree (I-1)."""
name = "gitea"
def __init__(self, settings: Settings) -> None:
self._s = settings
@property
def configured(self) -> bool:
return self._s.gitea_configured
async def probe(self) -> ProbeResult:
async with httpx.AsyncClient(timeout=_TIMEOUT) as client:
r = await client.get(
f"{self._s.gitea_base_url}/api/v1/version",
headers={"Authorization": f"token {self._s.gitea_admin_token}"},
)
if r.status_code != 200:
return ProbeResult(False, f"gitea /api/v1/version -> {r.status_code}", True)
return ProbeResult(True, f"gitea {r.json().get('version', '?')}", True)
class R2Provider(Provider):
"""I-3: LFS, releases, artifacts, archives. NEVER git object stores."""
name = "r2"
def __init__(self, settings: Settings) -> None:
self._s = settings
@property
def configured(self) -> bool:
return self._s.r2_configured
async def probe(self) -> ProbeResult:
# HEAD the bucket via the S3 endpoint. boto3 is sync, so we keep the
# probe to a plain reachability check here and let G4 wire the signed
# client; a 400/403 still proves the endpoint is real and answering.
async with httpx.AsyncClient(timeout=_TIMEOUT) as client:
r = await client.head(f"{self._s.r2_endpoint_url}/{self._s.r2_bucket_lfs}")
reachable = r.status_code < 500
return ProbeResult(
ok=r.status_code in (200, 400, 403),
detail=f"r2 endpoint -> {r.status_code}",
reachable=reachable,
)
class EternitasProvider(Provider):
"""The one issuer. Every agent identity in the ecosystem terminates here."""
name = "eternitas"
def __init__(self, settings: Settings) -> None:
self._s = settings
@property
def configured(self) -> bool:
return self._s.eternitas_configured
async def probe(self) -> ProbeResult:
async with httpx.AsyncClient(timeout=_TIMEOUT) as client:
r = await client.get(f"{self._s.eternitas_base_url}/health")
return ProbeResult(r.status_code == 200, f"eternitas /health -> {r.status_code}", True)
class DatabaseProvider(Provider):
"""Postgres is truth. Gitea's own DB is a component's private state."""
name = "db"
def __init__(self, engine: AsyncEngine | None) -> None:
self._engine = engine
@property
def configured(self) -> bool:
return self._engine is not None
async def probe(self) -> ProbeResult:
assert self._engine is not None
async with self._engine.connect() as conn:
await conn.execute(text("SELECT 1"))
return ProbeResult(True, "postgres reachable", True)
# TunnelProvider was removed deliberately. See the note in main.py: cloudflared
# binds 127.0.0.1:2000 on the HOST, and this process runs in a container whose
# only route to the host is the bridge gateway (172.17.0.1), where nothing is
# listening. Binding the metrics endpoint wider would fix the probe and make a
# metrics bind failure able to take down ingress -- a worse trade than losing
# one row on a dashboard. The tunnel is supervised by systemd instead.