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) and Step 4 (#591691) - Step 3 (error page) integrates into this middleware for rendering responses ## Context Parent issue: https://gitlab.com/gitlab-org/gitlab/-/issues/590009 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 existing `ReadOnly` middleware) - [ ] 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_regex` and `route_hash` for accurate matching - For API routes, extract from route params (`:namespace_id`, `:group_id`, `:id`) - [ ] 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-After` header 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.rb` or 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)
task