Files
mcr/internal/grpcserver/server.go
Kyle Isom d5580f01f2 Migrate module path from kyle/ to mc/ org
All import paths updated to git.wntrmute.dev/mc/. Bumps mcdsl to v1.2.0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:05:59 -07:00

116 lines
3.3 KiB
Go

// Package grpcserver implements the MCR gRPC admin API server.
//
// It delegates TLS, logging, and auth/admin interceptors to the mcdsl
// grpcserver package. MCR-specific business logic lives in the service
// handler files (registry.go, policy.go, audit.go, admin.go).
package grpcserver
import (
"log"
"log/slog"
"net"
"sync"
mcdslauth "git.wntrmute.dev/mc/mcdsl/auth"
mcdslgrpc "git.wntrmute.dev/mc/mcdsl/grpcserver"
pb "git.wntrmute.dev/mc/mcr/gen/mcr/v1"
"git.wntrmute.dev/mc/mcr/internal/db"
"git.wntrmute.dev/mc/mcr/internal/gc"
"git.wntrmute.dev/mc/mcr/internal/policy"
)
// AuditFunc is a callback for recording audit events. It follows the same
// signature as db.WriteAuditEvent but without an error return -- audit
// failures should not block request processing.
type AuditFunc func(eventType, actorID, repository, digest, ip string, details map[string]string)
// Deps holds the dependencies injected into the gRPC server.
type Deps struct {
DB *db.DB
Authenticator *mcdslauth.Authenticator
Engine PolicyReloader
AuditFn AuditFunc
Collector *gc.Collector
}
// PolicyReloader can reload policy rules from a store.
type PolicyReloader interface {
Reload(store policy.RuleStore) error
}
// GCStatus tracks the current state of garbage collection for the gRPC server.
type GCStatus struct {
mu sync.Mutex
running bool
lastRun *gcLastRun
}
type gcLastRun struct {
StartedAt string
CompletedAt string
BlobsRemoved int
BytesFreed int64
}
// Server wraps a mcdsl grpcserver.Server with MCR-specific configuration.
type Server struct {
srv *mcdslgrpc.Server
deps Deps
gcStatus *GCStatus
}
// New creates a configured gRPC server that delegates TLS setup, logging,
// and auth/admin interceptors to the mcdsl grpcserver package.
//
// If certFile or keyFile is empty, TLS is skipped (for testing only).
func New(certFile, keyFile string, deps Deps, logger *slog.Logger) (*Server, error) {
srv, err := mcdslgrpc.New(certFile, keyFile, deps.Authenticator, methodMap(), logger, nil)
if err != nil {
return nil, err
}
// The JSON codec is registered globally via init() in gen/mcr/v1/codec.go.
// The client must use grpc.ForceCodecV2(mcrv1.JSONCodec{}) to match.
_ = pb.JSONCodec{} // ensure the gen/mcr/v1 init() runs (codec registration)
gcStatus := &GCStatus{}
s := &Server{srv: srv, deps: deps, gcStatus: gcStatus}
// Register all services.
pb.RegisterRegistryServiceServer(srv.GRPCServer, &registryService{
db: deps.DB,
collector: deps.Collector,
gcStatus: gcStatus,
auditFn: deps.AuditFn,
})
pb.RegisterPolicyServiceServer(srv.GRPCServer, &policyService{
db: deps.DB,
engine: deps.Engine,
auditFn: deps.AuditFn,
})
pb.RegisterAuditServiceServer(srv.GRPCServer, &auditService{
db: deps.DB,
})
pb.RegisterAdminServiceServer(srv.GRPCServer, &adminService{})
return s, nil
}
// Serve starts the gRPC server on the given listener.
func (s *Server) Serve(lis net.Listener) error {
log.Printf("grpc server listening on %s", lis.Addr())
return s.srv.GRPCServer.Serve(lis)
}
// GracefulStop gracefully stops the gRPC server.
func (s *Server) GracefulStop() {
s.srv.Stop()
}
// GRPCServer returns the underlying grpc.Server for testing.
func (s *Server) GRPCServer() *mcdslgrpc.Server {
return s.srv
}