Files
mcr/internal/server/admin_gc.go
Kyle Isom dddc66f31b Phases 5, 6, 8: OCI pull/push paths and admin REST API
Phase 5 (OCI pull): internal/oci/ package with manifest GET/HEAD by
tag/digest, blob GET/HEAD with repo membership check, tag listing with
OCI pagination, catalog listing. Multi-segment repo names via
parseOCIPath() right-split routing. DB query layer in
internal/db/repository.go.

Phase 6 (OCI push): blob uploads (monolithic and chunked) with
uploadManager tracking in-progress BlobWriters, manifest push
implementing full ARCHITECTURE.md §5 flow in a single SQLite
transaction (create repo, upsert manifest, populate manifest_blobs,
atomic tag move). Digest verification on both blob commit and manifest
push-by-digest.

Phase 8 (admin REST): /v1 endpoints for auth (login/logout/health),
repository management (list/detail/delete), policy CRUD with engine
reload, audit log listing with filters, GC trigger/status stubs.
RequireAdmin middleware, platform-standard error format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 18:25:18 -07:00

67 lines
1.6 KiB
Go

package server
import (
"net/http"
"sync"
"github.com/google/uuid"
)
// GCLastRun records the result of the last GC run.
type GCLastRun struct {
StartedAt string `json:"started_at"`
CompletedAt string `json:"completed_at,omitempty"`
BlobsRemoved int `json:"blobs_removed"`
BytesFreed int64 `json:"bytes_freed"`
}
// GCState tracks the current state of garbage collection.
type GCState struct {
mu sync.Mutex
Running bool `json:"running"`
LastRun *GCLastRun `json:"last_run,omitempty"`
}
type gcStatusResponse struct {
Running bool `json:"running"`
LastRun *GCLastRun `json:"last_run,omitempty"`
}
type gcTriggerResponse struct {
ID string `json:"id"`
}
// AdminTriggerGCHandler handles POST /v1/gc.
func AdminTriggerGCHandler(state *GCState) http.HandlerFunc {
return func(w http.ResponseWriter, _ *http.Request) {
state.mu.Lock()
if state.Running {
state.mu.Unlock()
writeAdminError(w, http.StatusConflict, "garbage collection already running")
return
}
state.Running = true
state.mu.Unlock()
// GC engine is Phase 9 -- for now, just mark as running and return.
// The actual GC goroutine will be wired up in Phase 9.
gcID := uuid.New().String()
writeJSON(w, http.StatusAccepted, gcTriggerResponse{ID: gcID})
}
}
// AdminGCStatusHandler handles GET /v1/gc/status.
func AdminGCStatusHandler(state *GCState) http.HandlerFunc {
return func(w http.ResponseWriter, _ *http.Request) {
state.mu.Lock()
resp := gcStatusResponse{
Running: state.Running,
LastRun: state.LastRun,
}
state.mu.Unlock()
writeJSON(w, http.StatusOK, resp)
}
}