Files
metacrypt/internal/grpcserver/engine.go
Kyle Isom 2336bf5061 Add buf lint/breaking targets and fix proto naming violations
- Add buf.yaml with STANDARD lint rules and FILE-level breaking change detection
- Add proto-lint Makefile target (buf lint + buf breaking --against master)
- Add lint Makefile target (golangci-lint) and include it in all
- Fix proto target: use module= option so protoc writes to gen/ not proto/
- engine.proto: rename rpc Request→Execute and message types accordingly
- acme.proto: drop redundant ACME prefix from SetConfig/ListAccounts/ListOrders messages
- policy.proto: add CreatePolicyResponse/GetPolicyResponse wrappers instead of returning PolicyRule directly from multiple RPCs
- Update grpcserver and webserver/client.go to match renamed types

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 10:27:52 -07:00

113 lines
3.2 KiB
Go

package grpcserver
import (
"context"
"errors"
"strings"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/structpb"
pb "git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v1"
"git.wntrmute.dev/kyle/metacrypt/internal/engine"
)
type engineServer struct {
pb.UnimplementedEngineServiceServer
s *GRPCServer
}
func (es *engineServer) Mount(ctx context.Context, req *pb.MountRequest) (*pb.MountResponse, error) {
if req.Name == "" || req.Type == "" {
return nil, status.Error(codes.InvalidArgument, "name and type are required")
}
var config map[string]interface{}
if req.Config != nil {
config = req.Config.AsMap()
}
if err := es.s.engines.Mount(ctx, req.Name, engine.EngineType(req.Type), config); err != nil {
es.s.logger.Error("grpc: mount engine", "name", req.Name, "type", req.Type, "error", err)
switch {
case errors.Is(err, engine.ErrMountExists):
return nil, status.Error(codes.AlreadyExists, err.Error())
case errors.Is(err, engine.ErrUnknownType):
return nil, status.Error(codes.InvalidArgument, err.Error())
default:
return nil, status.Error(codes.Internal, err.Error())
}
}
return &pb.MountResponse{}, nil
}
func (es *engineServer) Unmount(ctx context.Context, req *pb.UnmountRequest) (*pb.UnmountResponse, error) {
if req.Name == "" {
return nil, status.Error(codes.InvalidArgument, "name is required")
}
if err := es.s.engines.Unmount(ctx, req.Name); err != nil {
if errors.Is(err, engine.ErrMountNotFound) {
return nil, status.Error(codes.NotFound, err.Error())
}
return nil, status.Error(codes.Internal, err.Error())
}
return &pb.UnmountResponse{}, nil
}
func (es *engineServer) ListMounts(_ context.Context, _ *pb.ListMountsRequest) (*pb.ListMountsResponse, error) {
mounts := es.s.engines.ListMounts()
pbMounts := make([]*pb.MountInfo, 0, len(mounts))
for _, m := range mounts {
pbMounts = append(pbMounts, &pb.MountInfo{
Name: m.Name,
Type: string(m.Type),
MountPath: m.MountPath,
})
}
return &pb.ListMountsResponse{Mounts: pbMounts}, nil
}
func (es *engineServer) Execute(ctx context.Context, req *pb.ExecuteRequest) (*pb.ExecuteResponse, error) {
if req.Mount == "" || req.Operation == "" {
return nil, status.Error(codes.InvalidArgument, "mount and operation are required")
}
ti := tokenInfoFromContext(ctx)
engReq := &engine.Request{
Operation: req.Operation,
Path: req.Path,
Data: nil,
}
if req.Data != nil {
engReq.Data = req.Data.AsMap()
}
if ti != nil {
engReq.CallerInfo = &engine.CallerInfo{
Username: ti.Username,
Roles: ti.Roles,
IsAdmin: ti.IsAdmin,
}
}
resp, err := es.s.engines.HandleRequest(ctx, req.Mount, engReq)
if err != nil {
st := codes.Internal
switch {
case errors.Is(err, engine.ErrMountNotFound):
st = codes.NotFound
case strings.Contains(err.Error(), "forbidden"):
st = codes.PermissionDenied
case strings.Contains(err.Error(), "not found"):
st = codes.NotFound
}
return nil, status.Error(st, err.Error())
}
pbData, err := structpb.NewStruct(resp.Data)
if err != nil {
return nil, status.Error(codes.Internal, "failed to encode response")
}
return &pb.ExecuteResponse{Data: pbData}, nil
}