feat: expose deployment build identity

This commit is contained in:
ik
2026-09-07 18:43:20 +07:00
parent ef8355904c
commit 02b67741f8
9 changed files with 26 additions and 4 deletions
+2
View File
@@ -1,4 +1,6 @@
DATABASE_URL=postgresql+psycopg://rf4:rf4_local@localhost:5432/rf4_spotter
APP_VERSION=0.1.0
APP_REVISION=dev
PUBLIC_API_URL=http://localhost:8000
API_INTERNAL_URL=http://api:8000
ADMIN_TOKEN=change-me-in-production
+2
View File
@@ -8,6 +8,8 @@ POSTGRES_USER=rf4
POSTGRES_PASSWORD=replace-with-long-random-value
# URL-encode special characters from POSTGRES_PASSWORD in this URL.
DATABASE_URL=postgresql+psycopg://rf4:replace-with-url-encoded-password@db:5432/rf4_spotter
APP_VERSION=0.1.0
APP_REVISION=replace-with-git-commit-sha
ADMIN_TOKEN=replace-with-at-least-32-random-characters
RATE_LIMIT_SECRET=replace-with-at-least-32-random-characters
+2 -2
View File
@@ -83,10 +83,10 @@ docker compose up --build
- сайт: <http://localhost:4321>;
- OpenAPI: <http://localhost:8000/docs>;
- liveness API: <http://localhost:8000/health>;
- readiness PostgreSQL, MinIO и импорта: <http://localhost:8000/ready>;
- readiness PostgreSQL, MinIO и импорта с версией/revision сборки: <http://localhost:8000/ready>;
- консоль MinIO: <http://localhost:9001>.
Контейнер API сам выполняет `alembic upgrade head`, затем идемпотентный seed. PostgreSQL хранит данные в именованном volume `postgres_data`, а MinIO — в `minio_data`. Compose ожидает readiness PostgreSQL и MinIO перед API, а API-контейнер проверяет `/ready`. Официальный импорт по умолчанию необязателен; при включённом scheduler установите `OFFICIAL_IMPORT_REQUIRED=true`, тогда отсутствующий, неуспешный или просроченный запуск сделает readiness отрицательным.
Контейнер API сам выполняет `alembic upgrade head`, затем идемпотентный seed. PostgreSQL хранит данные в именованном volume `postgres_data`, а MinIO — в `minio_data`. Compose ожидает readiness PostgreSQL и MinIO перед API, а API-контейнер проверяет `/ready`. Версия и commit SHA задаются через `APP_VERSION`/`APP_REVISION`; те же значения доступны администратору в `/api/v1/admin/diagnostics`. Официальный импорт по умолчанию необязателен; при включённом scheduler установите `OFFICIAL_IMPORT_REQUIRED=true`, тогда отсутствующий, неуспешный или просроченный запуск сделает readiness отрицательным.
API и scheduler пишут по одной JSON-записи на событие. HTTP-лог содержит только сгенерированный `request_id`, метод, путь без query string, статус и длительность; IP, заголовок авторизации и пользовательский payload не журналируются. `X-Request-ID` возвращается клиенту. Стандартный access-log Uvicorn отключён. Уровень управляется `LOG_LEVEL`.
+2
View File
@@ -4,6 +4,8 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
deployment_environment: str = "development"
app_version: str = "0.1.0"
app_revision: str = "dev"
database_url: str = "postgresql+psycopg://rf4:rf4_local@localhost:5432/rf4_spotter"
admin_token: str = "change-me-in-production"
s3_endpoint_url: str = "http://localhost:9000"
+6 -1
View File
@@ -89,7 +89,7 @@ def ready(db: Db) -> JSONResponse:
)
return JSONResponse(
status_code=200 if is_ready else 503,
content={"status": "ready" if is_ready else "not_ready", "components": components},
content={"status": "ready" if is_ready else "not_ready", "version": settings.app_version, "revision": settings.app_revision, "components": components},
)
@@ -257,6 +257,11 @@ def _admin(authorization: Annotated[str | None, Header()] = None) -> str:
return "admin"
@app.get("/api/v1/admin/diagnostics")
def admin_diagnostics(_: Annotated[str, Depends(_admin)]) -> dict[str, str]:
return {"version": settings.app_version, "revision": settings.app_revision, "environment": settings.deployment_environment}
@app.get("/api/v1/imports", response_model=list[ImportRunOut])
def imports(db: Db, limit: int = Query(20, ge=1, le=100), offset: int = Query(0, ge=0)) -> list[OfficialRecordImport]:
return list(db.scalars(select(OfficialRecordImport).order_by(OfficialRecordImport.started_at.desc(), OfficialRecordImport.id.desc()).offset(offset).limit(limit)))
+7
View File
@@ -85,6 +85,13 @@ def test_liveness_does_not_probe_dependencies() -> None:
assert response.headers["Cross-Origin-Opener-Policy"] == "same-origin"
def test_admin_diagnostics_exposes_build_identity_only_to_admin() -> None:
assert client.get("/api/v1/admin/diagnostics").status_code == 401
response = client.get("/api/v1/admin/diagnostics", headers={"Authorization": "Bearer change-me-in-production"})
assert response.status_code == 200
assert response.json() == {"version": "0.1.0", "revision": "dev", "environment": "development"}
def test_spot_detail_and_catches() -> None:
spot_id = client.get("/api/v1/activity").json()[0]["spot_id"]
detail = client.get(f"/api/v1/spots/{spot_id}")
+2
View File
@@ -105,6 +105,8 @@ services:
restart: unless-stopped
environment:
DEPLOYMENT_ENVIRONMENT: production
APP_VERSION: ${APP_VERSION:-0.1.0}
APP_REVISION: ${APP_REVISION:-unknown}
DATABASE_URL: ${DATABASE_URL:?Set DATABASE_URL}
ADMIN_TOKEN: ${ADMIN_TOKEN:?Set ADMIN_TOKEN}
CORS_ORIGINS: '["https://${SITE_DOMAIN:?Set SITE_DOMAIN}"]'
+2
View File
@@ -31,6 +31,8 @@ docker run --rm caddy:2.10.2-alpine caddy hash-password --plaintext 'ОТДЕЛ
./deploy/preflight.sh
```
Перед сборкой запишите текущий `git rev-parse --short HEAD` в `APP_REVISION` файла `.env.production`, чтобы `/ready` однозначно показывал развёрнутый commit.
```bash
docker compose --env-file .env.production -f compose.production.yaml config --quiet
docker compose --env-file .env.production -f compose.production.yaml build
+1 -1
View File
@@ -146,7 +146,7 @@
- [ ] Добавить безопасный административный экспорт диагностики без персональных данных.
- [ ] Контролировать рост PostgreSQL и MinIO с порогами предупреждений.
- [ ] Добавить фоновую проверку битых исходных ссылок с соблюдением лимитов источников.
- [ ] Показывать commit SHA/версию в readiness и административной диагностике.
- [x] Показывать версию и commit SHA в readiness и защищённой административной диагностике (7 сентября 2026).
- [x] Добавить публичную `/status` без внутренних адресов, секретов и текстов ошибок (7 сентября 2026).
- [ ] После появления сервера подключить privacy-friendly аналитику без cookies либо собственные агрегированные счётчики.
- [ ] Зафиксировать нагрузочный бюджет и проверить p95 публичных API на целевом сервере.