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) Request(ctx context.Context, req *pb.EngineRequest) (*pb.EngineResponse, 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.EngineResponse{Data: pbData}, nil }