Step 2: Create namespace-scoped read-only middleware
Summary
Create middleware that blocks write HTTP requests (POST/PATCH/PUT/DELETE) for namespaces in maintenance state. This is the core enforcement mechanism for TLG read-only mode on web and REST API routes.
POC approach: This will be part of a single POC MR (does not need to merge to master). Maintenance mode will be toggled via Rails console.
Dependencies
- Depends on: Step 1 (#591688) — state machine transitions
- Parallel with: Step 3 (#591690 (closed)) and Step 4 (#591691 (closed))
- Step 3 (error page) integrates into this middleware for rendering responses
Context
Parent issue: #590009 (closed)
The existing Gitlab::Middleware::ReadOnly (lib/gitlab/middleware/read_only.rb) blocks writes at the instance level. We need a similar mechanism scoped to individual namespaces.
Tasks
-
Create
Gitlab::Middleware::NamespaceReadOnly(or extend existingReadOnlymiddleware) -
Implement namespace resolution from request paths:
- Parse path to extract top-level namespace segment (strip known prefixes like
/api/v4/,/groups/, etc.) - Use
Gitlab::PathRegex.full_namespace_route_regexandroute_hashfor accurate matching - For API routes, extract from route params (
:namespace_id,:group_id,:id)
- Parse path to extract top-level namespace segment (strip known prefixes like
-
Look up root namespace and check
effective_state == :maintenance - Implement caching strategy for namespace state lookups (request-store + short Redis TTL to avoid per-request DB hits)
-
Return 503 with maintenance message and
Retry-Afterheader for blocked requests -
Reuse existing allowlist pattern from
ReadOnly::Controller(internal routes, sessions, sidekiq admin, etc.) - Register middleware in the Rack stack
- Add specs covering: write blocking, read passthrough, allowlisted routes, cache behavior, nested namespace resolution
Key Files
-
lib/gitlab/middleware/read_only.rb(reference pattern) -
lib/gitlab/middleware/read_only/controller.rb(reference: allowlists, route_hash, error responses) -
ee/lib/ee/gitlab/middleware/read_only/controller.rb(reference: 503 maintenance response pattern) -
lib/gitlab/path_regex.rb(namespace path parsing) -
config/initializers/rack_attack.rbor middleware registration location
Design Decisions
- Option A (Recommended): New standalone middleware — cleaner separation, doesn't couple with instance-level read-only
-
Option B: Extend existing
ReadOnly::Controller#read_only?— simpler but mixes concerns
Effort Estimate
Medium (3-5 days)
Edited by Chen Zhang