feat: expose deployment build identity
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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}"]'
|
||||
|
||||
@@ -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
@@ -146,7 +146,7 @@
|
||||
- [ ] Добавить безопасный административный экспорт диагностики без персональных данных.
|
||||
- [ ] Контролировать рост PostgreSQL и MinIO с порогами предупреждений.
|
||||
- [ ] Добавить фоновую проверку битых исходных ссылок с соблюдением лимитов источников.
|
||||
- [ ] Показывать commit SHA/версию в readiness и административной диагностике.
|
||||
- [x] Показывать версию и commit SHA в readiness и защищённой административной диагностике (7 сентября 2026).
|
||||
- [x] Добавить публичную `/status` без внутренних адресов, секретов и текстов ошибок (7 сентября 2026).
|
||||
- [ ] После появления сервера подключить privacy-friendly аналитику без cookies либо собственные агрегированные счётчики.
|
||||
- [ ] Зафиксировать нагрузочный бюджет и проверить p95 публичных API на целевом сервере.
|
||||
|
||||
Reference in New Issue
Block a user