Adds package: puppet for proxying the Puppet Forge API (forgeapi.puppet.com).
- remote/puppet.py: rewrites absolute forge URLs and relative /v3/files/
paths in JSON responses to absolute proxy URLs; g10k uses
url.ResolveReference so absolute file_uri values override the base
entirely, meaning tarball downloads go straight to the proxy
- config.py: registers built-in mutable patterns for v3/modules/ and
v3/releases (module metadata pages)
- artifact/proxy.py: dispatches to puppet.resolve_content for package:
puppet remotes
- 9 new tests covering mutable detection, URL rewriting (relative and
absolute), content-type, tarball pass-through, and pattern blocking
Client configuration (g10k):
- config file: forge_base_url: https://artifacts.example.com/api/v1/remote/puppet-forge
- Puppetfile: forge.baseUrl https://artifacts.example.com/api/v1/remote/puppet-forge
Closes#35
## Summary
- Wraps `handler.merge(...)` in `await asyncio.to_thread(...)` so the CPU-bound YAML parse/merge/dump runs in the thread pool instead of blocking the event loop
- Change is at the generic `handle()` dispatch site — applies to all current and future `_VirtualHandler` implementations without modification
- Also fixes a pre-existing bug in `examples/single-file/remotes.yaml` where `base_url` and `package` keys were merged onto a single line, preventing `docker-compose up` from starting the app
## Measured performance gain
19-member `helm-all` virtual repo, single uvicorn worker, cache miss (38s merge):
| | Concurrent `/health` latency |
|---|---|
| Before (blocking) | **37,721ms** for first request (stalled) |
| After (thread pool) | **8–63ms** for all requests |
## Test plan
- [x] 278 unit tests pass (`make test`)
- [x] Live concurrency test: cache miss merge started in background, 5 concurrent `/health` checks measured — all <65ms
- [x] Baseline comparison: same test with blocking call — first health check stalled 37.7s
Reviewed-on: #38
Repository types now live under dedicated top-level keys instead of a
shared remotes: block distinguished by a type field:
remotes: caching proxy remotes (no type field needed)
virtuals: virtual merged-index repositories
locals: local upload repositories
Routes for local repos move from /api/v1/remote/ to /api/v1/local/.
config.py gains get_virtual_config() and get_local_config() lookups.
Root endpoint now reports all three sections. Drop root conf.d/ (was
an exact duplicate of examples/conf.d-method/).
Reviewed-on: #31
Adds a new virtual repo type that merges indexes from multiple member remotes
of the same package type. Currently supports helm (index.yaml merge with URL
rewriting). Member fetches run in parallel; merged index is Redis-cached at
min(mutable_ttl) across members.
Reviewed-on: #30
examples/single-file/remotes.yaml — original monolithic config
examples/conf.d-method/ — one yaml per remote (alpine, github, pypi)
docker-compose updated to mount from examples/single-file/.