From cf55a19acf3c0fab7c94301969f056dba7d372d5 Mon Sep 17 00:00:00 2001 From: DevITWay Date: Sat, 31 Jan 2026 07:53:19 +0000 Subject: [PATCH] docs: sync CHANGELOG and OpenAPI with actual implementation - Fix CHANGELOG: add missing versions v0.2.4-v0.2.12 - Implement GET /v2/_catalog endpoint for Docker repository listing - Add missing OpenAPI endpoints: - Docker: PUT manifest, POST/PATCH/PUT blob uploads, HEAD blob - Maven: PUT artifact upload - Cargo: GET metadata, GET download (was completely undocumented) - Metrics: GET /metrics - Update OpenAPI version to 0.2.12 --- CHANGELOG.md | 179 +++++++++++++++++++++--- nora-registry/src/openapi.rs | 198 +++++++++++++++++++++++++-- nora-registry/src/registry/docker.rs | 21 +++ 3 files changed, 368 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f5218c..53a27e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,18 +4,14 @@ All notable changes to NORA will be documented in this file. --- -## [0.3.0] - 2026-01-30 +## [0.2.12] - 2026-01-30 ### Added #### Configurable Rate Limiting - Rate limits now configurable via `config.toml` and environment variables -- New config section `[rate_limit]` with 6 parameters: - - `auth_rps` / `auth_burst` - Authentication endpoints (brute-force protection) - - `upload_rps` / `upload_burst` - Upload endpoints (Docker push, etc.) - - `general_rps` / `general_burst` - General API endpoints +- New config section `[rate_limit]` with parameters: `auth_rps`, `auth_burst`, `upload_rps`, `upload_burst`, `general_rps`, `general_burst` - Environment variables: `NORA_RATE_LIMIT_{AUTH|UPLOAD|GENERAL}_{RPS|BURST}` -- Rate limit configuration logged at startup #### Secrets Provider Architecture - Trait-based secrets management (`SecretsProvider` trait) @@ -23,14 +19,78 @@ All notable changes to NORA will be documented in this file. - Protected secrets with `zeroize` (memory zeroed on drop) - Redacted Debug impl prevents secret leakage in logs - New config section `[secrets]` with `provider` and `clear_env` options -- Foundation for future AWS Secrets Manager, Vault, K8s integration + +#### Docker Image Metadata +- Support for image metadata retrieval + +#### Documentation +- Bilingual onboarding guide (EN/RU) + +--- + +## [0.2.11] - 2026-01-26 + +### Added +- Internationalization (i18n) support +- PyPI registry proxy +- UI improvements + +--- + +## [0.2.10] - 2026-01-26 ### Changed -- Rate limiting functions now accept `&RateLimitConfig` parameter -- Improved error messages with `.expect()` instead of `.unwrap()` +- Dark theme applied to all UI pages + +--- + +## [0.2.9] - 2026-01-26 + +### Changed +- Version bump release + +--- + +## [0.2.8] - 2026-01-26 + +### Added +- Dashboard endpoint added to OpenAPI documentation + +--- + +## [0.2.7] - 2026-01-26 + +### Added +- Dynamic version display in UI sidebar + +--- + +## [0.2.6] - 2026-01-26 + +### Added + +#### Dashboard Metrics +- Global stats panel: downloads, uploads, artifacts, cache hit rate, storage +- Extended registry cards with artifact count, size, counters +- Activity log (last 20 events) + +#### UI +- Dark theme (bg: #0f172a, cards: #1e293b) + +--- + +## [0.2.5] - 2026-01-26 ### Fixed -- Rate limiting was hardcoded in v0.2.0, now user-configurable +- Docker push/pull: added PATCH endpoint for chunked uploads + +--- + +## [0.2.4] - 2026-01-26 + +### Fixed +- Rate limiting: health/metrics endpoints now exempt +- Increased upload rate limits for Docker parallel requests --- @@ -82,7 +142,6 @@ All notable changes to NORA will be documented in this file. - JSON error responses with request_id support ### Changed - - `StorageError` now uses `thiserror` derive macro - `TokenError` now uses `thiserror` derive macro - Storage wrapper validates keys before delegating to backend @@ -90,7 +149,6 @@ All notable changes to NORA will be documented in this file. - Body size limit set to 100MB default via `DefaultBodyLimit` ### Dependencies Added - - `thiserror = "2"` - typed error handling - `tower_governor = "0.8"` - rate limiting - `governor = "0.10"` - rate limiting backend @@ -98,7 +156,6 @@ All notable changes to NORA will be documented in this file. - `wiremock = "0.6"` (dev) - HTTP mocking for S3 tests ### Files Added - - `src/validation.rs` - input validation module - `src/migrate.rs` - storage migration module - `src/error.rs` - application error types @@ -110,7 +167,6 @@ All notable changes to NORA will be documented in this file. ## [0.1.0] - 2026-01-24 ### Added - - Multi-protocol support: Docker Registry v2, Maven, npm, Cargo, PyPI - Web UI dashboard - Swagger UI (`/api-docs`) @@ -125,7 +181,6 @@ All notable changes to NORA will be documented in this file. - Graceful shutdown (SIGTERM/SIGINT) - Backup/restore commands ---- --- # Журнал изменений (RU) @@ -134,6 +189,96 @@ All notable changes to NORA will be documented in this file. --- +## [0.2.12] - 2026-01-30 + +### Добавлено + +#### Настраиваемый Rate Limiting +- Rate limits настраиваются через `config.toml` и переменные окружения +- Новая секция `[rate_limit]` с параметрами: `auth_rps`, `auth_burst`, `upload_rps`, `upload_burst`, `general_rps`, `general_burst` +- Переменные окружения: `NORA_RATE_LIMIT_{AUTH|UPLOAD|GENERAL}_{RPS|BURST}` + +#### Архитектура Secrets Provider +- Trait-based управление секретами (`SecretsProvider` trait) +- ENV provider по умолчанию (12-Factor App паттерн) +- Защищённые секреты с `zeroize` (память обнуляется при drop) +- Redacted Debug impl предотвращает утечку секретов в логи +- Новая секция `[secrets]` с опциями `provider` и `clear_env` + +#### Docker Image Metadata +- Поддержка получения метаданных образов + +#### Документация +- Двуязычный onboarding guide (EN/RU) + +--- + +## [0.2.11] - 2026-01-26 + +### Добавлено +- Поддержка интернационализации (i18n) +- PyPI registry proxy +- Улучшения UI + +--- + +## [0.2.10] - 2026-01-26 + +### Изменено +- Тёмная тема применена ко всем страницам UI + +--- + +## [0.2.9] - 2026-01-26 + +### Изменено +- Релиз с обновлением версии + +--- + +## [0.2.8] - 2026-01-26 + +### Добавлено +- Dashboard endpoint добавлен в OpenAPI документацию + +--- + +## [0.2.7] - 2026-01-26 + +### Добавлено +- Динамическое отображение версии в сайдбаре UI + +--- + +## [0.2.6] - 2026-01-26 + +### Добавлено + +#### Dashboard Metrics +- Глобальная панель статистики: downloads, uploads, artifacts, cache hit rate, storage +- Расширенные карточки реестров с количеством артефактов, размером, счётчиками +- Лог активности (последние 20 событий) + +#### UI +- Тёмная тема (bg: #0f172a, cards: #1e293b) + +--- + +## [0.2.5] - 2026-01-26 + +### Исправлено +- Docker push/pull: добавлен PATCH endpoint для chunked uploads + +--- + +## [0.2.4] - 2026-01-26 + +### Исправлено +- Rate limiting: health/metrics endpoints теперь исключены +- Увеличены лимиты upload для параллельных Docker запросов + +--- + ## [0.2.0] - 2026-01-25 ### Добавлено @@ -182,7 +327,6 @@ All notable changes to NORA will be documented in this file. - JSON-ответы об ошибках с поддержкой request_id ### Изменено - - `StorageError` теперь использует макрос `thiserror` - `TokenError` теперь использует макрос `thiserror` - Storage wrapper валидирует ключи перед делегированием backend @@ -190,7 +334,6 @@ All notable changes to NORA will be documented in this file. - Лимит размера body установлен в 100MB через `DefaultBodyLimit` ### Добавлены зависимости - - `thiserror = "2"` - типизированная обработка ошибок - `tower_governor = "0.8"` - rate limiting - `governor = "0.10"` - backend для rate limiting @@ -198,7 +341,6 @@ All notable changes to NORA will be documented in this file. - `wiremock = "0.6"` (dev) - HTTP-мокирование для S3 тестов ### Добавлены файлы - - `src/validation.rs` - модуль валидации ввода - `src/migrate.rs` - модуль миграции хранилища - `src/error.rs` - типы ошибок приложения @@ -210,7 +352,6 @@ All notable changes to NORA will be documented in this file. ## [0.1.0] - 2026-01-24 ### Добавлено - - Мульти-протокольная поддержка: Docker Registry v2, Maven, npm, Cargo, PyPI - Web UI дашборд - Swagger UI (`/api-docs`) diff --git a/nora-registry/src/openapi.rs b/nora-registry/src/openapi.rs index 70006b4..74d183f 100644 --- a/nora-registry/src/openapi.rs +++ b/nora-registry/src/openapi.rs @@ -15,7 +15,7 @@ use crate::AppState; #[openapi( info( title = "Nora", - version = "0.2.10", + version = "0.2.12", description = "Multi-protocol package registry supporting Docker, Maven, npm, Cargo, and PyPI", license(name = "MIT"), contact(name = "DevITWay", url = "https://github.com/getnora-io/nora") @@ -25,6 +25,7 @@ use crate::AppState; ), tags( (name = "health", description = "Health check endpoints"), + (name = "metrics", description = "Prometheus metrics"), (name = "dashboard", description = "Dashboard & Metrics API"), (name = "docker", description = "Docker Registry v2 API"), (name = "maven", description = "Maven Repository API"), @@ -37,18 +38,30 @@ use crate::AppState; // Health crate::openapi::health_check, crate::openapi::readiness_check, + // Metrics + crate::openapi::prometheus_metrics, // Dashboard crate::openapi::dashboard_metrics, - // Docker + // Docker - Read crate::openapi::docker_version, crate::openapi::docker_catalog, crate::openapi::docker_tags, - crate::openapi::docker_manifest, - crate::openapi::docker_blob, + crate::openapi::docker_manifest_get, + crate::openapi::docker_blob_head, + crate::openapi::docker_blob_get, + // Docker - Write + crate::openapi::docker_manifest_put, + crate::openapi::docker_blob_upload_start, + crate::openapi::docker_blob_upload_patch, + crate::openapi::docker_blob_upload_put, // Maven - crate::openapi::maven_artifact, + crate::openapi::maven_artifact_get, + crate::openapi::maven_artifact_put, // npm crate::openapi::npm_package, + // Cargo + crate::openapi::cargo_metadata, + crate::openapi::cargo_download, // PyPI crate::openapi::pypi_simple, crate::openapi::pypi_package, @@ -258,6 +271,8 @@ pub struct ActivityEntry { // ============ Path Operations (documentation only) ============ +// -------------------- Health -------------------- + /// Health check endpoint #[utoipa::path( get, @@ -282,6 +297,23 @@ pub async fn health_check() {} )] pub async fn readiness_check() {} +// -------------------- Metrics -------------------- + +/// Prometheus metrics endpoint +/// +/// Returns metrics in Prometheus text format for scraping. +#[utoipa::path( + get, + path = "/metrics", + tag = "metrics", + responses( + (status = 200, description = "Prometheus metrics", content_type = "text/plain") + ) +)] +pub async fn prometheus_metrics() {} + +// -------------------- Dashboard -------------------- + /// Dashboard metrics and activity /// /// Returns comprehensive metrics including downloads, uploads, cache statistics, @@ -296,6 +328,8 @@ pub async fn readiness_check() {} )] pub async fn dashboard_metrics() {} +// -------------------- Docker Registry v2 - Read Operations -------------------- + /// Docker Registry version check #[utoipa::path( get, @@ -325,7 +359,7 @@ pub async fn docker_catalog() {} path = "/v2/{name}/tags/list", tag = "docker", params( - ("name" = String, Path, description = "Repository name") + ("name" = String, Path, description = "Repository name (e.g., 'alpine' or 'library/nginx')") ), responses( (status = 200, description = "Tag list", body = DockerTags), @@ -341,14 +375,30 @@ pub async fn docker_tags() {} tag = "docker", params( ("name" = String, Path, description = "Repository name"), - ("reference" = String, Path, description = "Tag or digest") + ("reference" = String, Path, description = "Tag or digest (sha256:...)") ), responses( (status = 200, description = "Manifest content"), (status = 404, description = "Manifest not found") ) )] -pub async fn docker_manifest() {} +pub async fn docker_manifest_get() {} + +/// Check if blob exists +#[utoipa::path( + head, + path = "/v2/{name}/blobs/{digest}", + tag = "docker", + params( + ("name" = String, Path, description = "Repository name"), + ("digest" = String, Path, description = "Blob digest (sha256:...)") + ), + responses( + (status = 200, description = "Blob exists, Content-Length header contains size"), + (status = 404, description = "Blob not found") + ) +)] +pub async fn docker_blob_head() {} /// Get blob #[utoipa::path( @@ -364,7 +414,79 @@ pub async fn docker_manifest() {} (status = 404, description = "Blob not found") ) )] -pub async fn docker_blob() {} +pub async fn docker_blob_get() {} + +// -------------------- Docker Registry v2 - Write Operations -------------------- + +/// Push manifest +#[utoipa::path( + put, + path = "/v2/{name}/manifests/{reference}", + tag = "docker", + params( + ("name" = String, Path, description = "Repository name"), + ("reference" = String, Path, description = "Tag or digest") + ), + responses( + (status = 201, description = "Manifest created, Docker-Content-Digest header contains digest"), + (status = 400, description = "Invalid manifest") + ) +)] +pub async fn docker_manifest_put() {} + +/// Start blob upload +/// +/// Initiates a resumable blob upload. Returns a Location header with the upload URL. +#[utoipa::path( + post, + path = "/v2/{name}/blobs/uploads/", + tag = "docker", + params( + ("name" = String, Path, description = "Repository name") + ), + responses( + (status = 202, description = "Upload started, Location header contains upload URL") + ) +)] +pub async fn docker_blob_upload_start() {} + +/// Upload blob chunk (chunked upload) +/// +/// Uploads a chunk of data to an in-progress upload session. +#[utoipa::path( + patch, + path = "/v2/{name}/blobs/uploads/{uuid}", + tag = "docker", + params( + ("name" = String, Path, description = "Repository name"), + ("uuid" = String, Path, description = "Upload session UUID") + ), + responses( + (status = 202, description = "Chunk accepted, Range header indicates bytes received") + ) +)] +pub async fn docker_blob_upload_patch() {} + +/// Complete blob upload +/// +/// Finalizes the blob upload. Can include final chunk data in the body. +#[utoipa::path( + put, + path = "/v2/{name}/blobs/uploads/{uuid}", + tag = "docker", + params( + ("name" = String, Path, description = "Repository name"), + ("uuid" = String, Path, description = "Upload session UUID"), + ("digest" = String, Query, description = "Expected blob digest (sha256:...)") + ), + responses( + (status = 201, description = "Blob created"), + (status = 400, description = "Digest mismatch or missing") + ) +)] +pub async fn docker_blob_upload_put() {} + +// -------------------- Maven -------------------- /// Get Maven artifact #[utoipa::path( @@ -379,7 +501,24 @@ pub async fn docker_blob() {} (status = 404, description = "Artifact not found, trying upstream proxies") ) )] -pub async fn maven_artifact() {} +pub async fn maven_artifact_get() {} + +/// Upload Maven artifact +#[utoipa::path( + put, + path = "/maven2/{path}", + tag = "maven", + params( + ("path" = String, Path, description = "Artifact path") + ), + responses( + (status = 201, description = "Artifact uploaded"), + (status = 500, description = "Storage error") + ) +)] +pub async fn maven_artifact_put() {} + +// -------------------- npm -------------------- /// Get npm package metadata #[utoipa::path( @@ -387,7 +526,7 @@ pub async fn maven_artifact() {} path = "/npm/{name}", tag = "npm", params( - ("name" = String, Path, description = "Package name") + ("name" = String, Path, description = "Package name (e.g., 'lodash' or '@scope/package')") ), responses( (status = 200, description = "Package metadata (JSON)"), @@ -396,6 +535,41 @@ pub async fn maven_artifact() {} )] pub async fn npm_package() {} +// -------------------- Cargo -------------------- + +/// Get Cargo crate metadata +#[utoipa::path( + get, + path = "/cargo/api/v1/crates/{crate_name}", + tag = "cargo", + params( + ("crate_name" = String, Path, description = "Crate name") + ), + responses( + (status = 200, description = "Crate metadata (JSON)"), + (status = 404, description = "Crate not found") + ) +)] +pub async fn cargo_metadata() {} + +/// Download Cargo crate +#[utoipa::path( + get, + path = "/cargo/api/v1/crates/{crate_name}/{version}/download", + tag = "cargo", + params( + ("crate_name" = String, Path, description = "Crate name"), + ("version" = String, Path, description = "Crate version") + ), + responses( + (status = 200, description = "Crate file (.crate)"), + (status = 404, description = "Crate version not found") + ) +)] +pub async fn cargo_download() {} + +// -------------------- PyPI -------------------- + /// PyPI Simple index #[utoipa::path( get, @@ -422,6 +596,8 @@ pub async fn pypi_simple() {} )] pub async fn pypi_package() {} +// -------------------- Auth / Tokens -------------------- + /// Create API token #[utoipa::path( post, diff --git a/nora-registry/src/registry/docker.rs b/nora-registry/src/registry/docker.rs index 2e4e5b8..c460965 100644 --- a/nora-registry/src/registry/docker.rs +++ b/nora-registry/src/registry/docker.rs @@ -47,6 +47,7 @@ static UPLOAD_SESSIONS: std::sync::LazyLock>>> = pub fn routes() -> Router> { Router::new() .route("/v2/", get(check)) + .route("/v2/_catalog", get(catalog)) // Single-segment name routes (e.g., /v2/alpine/...) .route("/v2/{name}/blobs/{digest}", head(check_blob)) .route("/v2/{name}/blobs/{digest}", get(download_blob)) @@ -87,6 +88,26 @@ async fn check() -> (StatusCode, Json) { (StatusCode::OK, Json(json!({}))) } +/// List all repositories in the registry +async fn catalog(State(state): State>) -> Json { + let keys = state.storage.list("docker/").await; + + // Extract unique repository names from paths like "docker/{name}/manifests/..." + let mut repos: Vec = keys + .iter() + .filter_map(|k| { + k.strip_prefix("docker/") + .and_then(|rest| rest.split('/').next()) + .map(String::from) + }) + .collect(); + + repos.sort(); + repos.dedup(); + + Json(json!({ "repositories": repos })) +} + async fn check_blob( State(state): State>, Path((name, digest)): Path<(String, String)>,