QueryResolver enables superusers and project admins to query a resolver within a project
(ctx context.Context, req *runtimev1.QueryResolverRequest)
| 16 | |
| 17 | // QueryResolver enables superusers and project admins to query a resolver within a project |
| 18 | func (s *Server) QueryResolver(ctx context.Context, req *runtimev1.QueryResolverRequest) (*runtimev1.QueryResolverResponse, error) { |
| 19 | // Validate permissions |
| 20 | claims := auth.GetClaims(ctx, req.InstanceId) |
| 21 | switch req.Resolver { |
| 22 | case "metrics", "metrics_sql": |
| 23 | // As a special case, we allow metrics resolvers for users with ReadMetrics permission (i.e. all users) |
| 24 | if !claims.Can(runtime.ReadMetrics) { |
| 25 | return nil, status.Error(codes.PermissionDenied, "not allowed to query metrics") |
| 26 | } |
| 27 | default: |
| 28 | // Other resolvers require ReadResolvers permission (i.e. project admin) |
| 29 | if !claims.Can(runtime.ReadResolvers) { |
| 30 | return nil, status.Error(codes.PermissionDenied, "only project admins can query resolvers") |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | // Resolver should exist |
| 35 | initializer, ok := runtime.ResolverInitializers[req.Resolver] |
| 36 | if !ok { |
| 37 | return nil, status.Errorf(codes.NotFound, "no resolver found of type %q", req.Resolver) |
| 38 | } |
| 39 | |
| 40 | // Inject limit into the props. |
| 41 | // Note: Not all resolvers support `limit` being passed here, but it's better than nothing. |
| 42 | // In case the resolver does not apply the limit, we fall back to applying it when reading the results later in this handler. |
| 43 | props := req.ResolverProperties.AsMap() |
| 44 | if req.Limit != 0 { |
| 45 | props["limit"] = req.Limit |
| 46 | } |
| 47 | |
| 48 | // Initialize the resolver |
| 49 | resolver, err := initializer(ctx, &runtime.ResolverOptions{ |
| 50 | Runtime: s.runtime, |
| 51 | InstanceID: req.InstanceId, |
| 52 | Properties: props, |
| 53 | Args: req.ResolverArgs.AsMap(), |
| 54 | Claims: claims, |
| 55 | ForExport: false, |
| 56 | }) |
| 57 | if err != nil { |
| 58 | return nil, mapGRPCErrorWithFallback(err, codes.InvalidArgument) |
| 59 | } |
| 60 | defer resolver.Close() |
| 61 | |
| 62 | // Query the resolver |
| 63 | res, err := resolver.ResolveInteractive(ctx) |
| 64 | if err != nil { |
| 65 | return nil, mapGRPCErrorWithFallback(err, codes.InvalidArgument) |
| 66 | } |
| 67 | defer res.Close() |
| 68 | |
| 69 | data := make([]*structpb.Struct, 0) |
| 70 | count := 0 |
| 71 | for { |
| 72 | // Break if we've reached the limit (when limit > 0) |
| 73 | if req.Limit > 0 && count >= int(req.Limit) { |
| 74 | break |
| 75 | } |