Commit 12e8c11
authored
admin: read-only S3 bucket endpoints (P2 slice 1) (#658)
P2 slice 1 of
[docs/design/2026_04_24_proposed_admin_dashboard.md](https://github.com/bootjp/elastickv/blob/main/docs/design/2026_04_24_proposed_admin_dashboard.md).
Read-only S3 admin endpoints land first so the SPA's S3List and S3Detail
pages stop hitting 404. Write paths (POST/PUT/DELETE/ACL) ship in slice
2 together with AdminForward integration; until then they reply 405.
## Summary
- **`*adapter.S3Server.AdminListBuckets` / `AdminDescribeBucket`** —
SigV4-bypass read methods. Share `loadBucketMetaAt` + the
metadata-prefix scan with the SigV4 path, so a SigV4 `listBuckets` and
the admin dashboard cannot drift.
- **`internal/admin/buckets_source.go`** — `BucketsSource` interface +
`BucketSummary` DTO + sentinel errors (`ErrBucketsForbidden` /
`ErrBucketsNotLeader` / `ErrBucketsNotFound` /
`ErrBucketsAlreadyExists`). `ErrBucketsForbidden` is wired in this
slice; the others are reserved for slice 2 to keep the error vocabulary
additive.
- **`internal/admin/s3_handler.go`** — `S3Handler` with `handleList`
(paginated) + `handleDescribe` (404 for missing). Sub-paths under
`/buckets/{name}/` (the future `/acl`) return 404 here so a SPA bug
pointed at this build cannot accidentally hit the describe path with a
`"{name}/acl"` string.
- **Shared `list_pagination.go`** — centralises base64url cursor + limit
parsing/clamping so the Dynamo and S3 handlers cannot diverge on
validation rules. Drops the now-redundant `parseDynamoListLimit` /
`decodeDynamoNextToken` / `encodeDynamoNextToken` from
`dynamo_handler.go` in favour of `parseListPaginationParams` /
`decodeListNextToken` / `encodeListNextToken`.
- **`apiRouteTable` in `server.go`** — bundles the precomposed
middleware chains and dispatches by URL prefix. `buildAPIMux`'s body
went from 13 cyclomatic branches to 6, leaving headroom for the next
resource family (SQS queues, etc.) to land without another refactor.
- **Production wiring** — `main_s3.go`'s `startS3Server` now returns
`*adapter.S3Server`; `runtimeServerRunner` stores it;
`startAdminFromFlags` accepts it and threads `bucketsBridge` →
`admin.BucketsSource` → `ServerDeps.Buckets`.
## What is NOT in this PR
- `AdminCreateBucket` / `AdminPutBucketAcl` / `AdminDeleteBucket` on the
adapter and their HTTP/AdminForward counterparts. Slice 2.
- `RoleStore` plumb-through for S3. Slice 2 (read-only is fine for any
authenticated session today; the write paths will need it).
- `forwarded_from` audit trail for S3 admin writes. Slice 2 — needs the
`pb.AdminOperation` enum extended.
- The merge-freeze-acceptable changes to `proto/admin_forward.proto`.
Slice 2.
## Test plan
- [x] `go build ./...`
- [x] `go vet ./...`
- [x] `golangci-lint run` (admin + main + adapter packages: 0 issues)
- [x] `go test ./internal/admin/ -count=1 -race` — 13 new handler tests
pass
- [x] `go test . -count=1 -race` — main package passes
- [x] `go test -run "TestS3Server_Admin" ./adapter/ -count=1 -race` — 4
new adapter tests pass
- [ ] `go test ./adapter/` times out at 120s due to a pre-existing flake
(verified earlier on PR #648 against `main` — unrelated to this branch)
- [ ] End-to-end smoke against a 3-node cluster — needs the next slice +
the SPA PR (#649) to merge before there's anything for the SPA to render
## Acceptance criteria coverage (Section 4.1)
| Endpoint | This PR |
|---|---|
| `GET /admin/api/v1/s3/buckets` | ✓ |
| `POST /admin/api/v1/s3/buckets` | ⏳ slice 2 |
| `GET /admin/api/v1/s3/buckets/{name}` | ✓ |
| `PUT /admin/api/v1/s3/buckets/{name}/acl` | ⏳ slice 2 |
| `DELETE /admin/api/v1/s3/buckets/{name}` | ⏳ slice 2 |
## Self-review (5 lenses)
1. **Data loss**: No FSM / Raft / Pebble path changes.
`AdminListBuckets` / `AdminDescribeBucket` are read-only and use the
same `ScanAt` / `loadBucketMetaAt` as the SigV4 path.
2. **Concurrency**: No new shared state. The handler holds only the
immutable `BucketsSource` interface; the bridge holds only the immutable
`*S3Server`. `pinReadTS` is the same pattern the SigV4 path uses for
snapshot stability.
3. **Performance**: One additional metadata scan per `GET
/admin/api/v1/s3/buckets`, identical to the SigV4 listBuckets.
Pagination caps at 1000 buckets per page (silent clamp on oversize). No
hot-path changes.
4. **Data consistency**: Read-only; no commit-ts ordering risk. Admin
reads use the same lease-read window as the SigV4 path.
5. **Test coverage**: 13 handler tests + 4 adapter tests + the existing
dynamo/cluster test surfaces still pass after the shared-helper
refactor. The pagination cursor round-trip test pins the wire format so
a future cursor-encoding change cannot ship without breaking the test.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Release Notes
* **New Features**
* Added S3 bucket admin API endpoints to list all buckets and retrieve
detailed bucket information, including creation timestamps.
* Implemented pagination support with cursor-based next_token for
browsing large bucket collections.
* Added proper authorization checks and error handling for admin bucket
operations (403 for forbidden, 404 for not found, 500 for errors).
<!-- end of auto-generated comment: release notes by coderabbit.ai -->15 files changed
Lines changed: 1270 additions & 127 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
0 commit comments