feat(api): add repository quota enforcement middleware (#3923)

Adds a configurable maximum repository count per registry instance.
When maxRepos is set on StorageConfig, manifest pushes that would create
a new repository beyond the limit are rejected with HTTP 429
TOOMANYREQUESTS. Pushes to existing repositories are always allowed.

Implemented as an always-available feature in pkg/api (not a build-tag
extension). MaxRepos is a field on StorageConfig, enabled when > 0.

- repoQuotaMiddleware on the dist-spec router intercepts manifest PUTs.
  New-repo pushes are serialized with a sync.Mutex to prevent concurrent
  requests from exceeding the limit.
- Adds CountRepos(ctx) to the MetaDB interface with efficient
  implementations: BoltDB (Stats().KeyN), Redis (HLen), DynamoDB
  (Scan with Select=COUNT).
- Config.IsQuotaEnabled() added, wired into controller.go metaDB init.
- Four integration tests (enforcement, concurrency, disabled,
  unconfigured) and backend-specific CountRepos tests for BoltDB, Redis,
  and DynamoDB.

Signed-off-by: Bachir Khiati <bachir.khiati@gmail.com>
This commit is contained in:
Bachir Khiati
2026-04-13 23:18:34 +03:00
committed by GitHub
parent 82947e801e
commit ba8575d960
18 changed files with 598 additions and 3 deletions
+12
View File
@@ -29,6 +29,7 @@ var (
type StorageConfig struct {
RootDirectory string
MaxRepos int
Dedupe bool
RemoteCache bool
GC bool
@@ -1144,6 +1145,17 @@ func (c *Config) IsRetentionEnabled() bool {
return c.isRetentionEnabledInternal()
}
func (c *Config) IsQuotaEnabled() bool {
if c == nil {
return false
}
c.mu.RLock()
defer c.mu.RUnlock()
return c.Storage.MaxRepos > 0
}
// IsCompatEnabled checks if compatibility mode is enabled.
func (c *Config) IsCompatEnabled() bool {
if c == nil {