// 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, ®istryService{ 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 }