Skip to content

Commit

Permalink
generalize proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
akosyakov committed Oct 12, 2023
1 parent 60c4d3f commit af7ef05
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 42 deletions.
29 changes: 15 additions & 14 deletions components/dashboard/src/api/workspace-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,29 @@
* See License.AGPL.txt in the project root for license information.
*/

import { CallOptions, PromiseClient } from "@bufbuild/connect";
import { CallOptions, PromiseClient, ConnectError, Code } from "@bufbuild/connect";
import { PartialMessage } from "@bufbuild/protobuf";
import { WorkspaceService } from "@gitpod/public-api/lib/gitpod/experimental/v2/workspace_connectweb";
import { GetWorkspaceRequest, GetWorkspaceResponse } from "@gitpod/public-api/lib/gitpod/experimental/v2/workspace_pb";
import { getGitpodService } from "../service/service";
import { converter, getServiceClient } from "../service/public-api";

class WorkspaceClient implements PromiseClient<typeof WorkspaceService> {
async getWorkspace(request: GetWorkspaceRequest, options?: CallOptions): Promise<GetWorkspaceResponse> {
// if public api is disable then
const result = await getGitpodService().server.getWorkspace(request.id);
// conversion to Public API tpypes
// otherwise use real public api
return new GetWorkspaceResponse();
async getWorkspace(
request: PartialMessage<GetWorkspaceRequest>,
options?: CallOptions,
): Promise<GetWorkspaceResponse> {
if (!request.id) {
throw new ConnectError("id is required", Code.InvalidArgument);
}
const info = await getGitpodService().server.getWorkspace(request.id);
const workspace = converter.toWorkspace(info);
const result = new GetWorkspaceResponse();
result.item = workspace;
return result;
}
}

export function getWorkspaceClient(): PromiseClient<typeof WorkspaceService> {
const w = window as any;
const _gp = w._gp || (w._gp = {});
let service = _gp[WorkspaceService.typeName];
if (!service) {
service = _gp[WorkspaceService.typeName] = new WorkspaceClient();
}
return service;
return getServiceClient(WorkspaceService, () => new WorkspaceClient());
}
81 changes: 74 additions & 7 deletions components/dashboard/src/service/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,30 @@
* See License.AGPL.txt in the project root for license information.
*/

import { createPromiseClient } from "@bufbuild/connect";
import { Code, ConnectError, PromiseClient, createPromiseClient } from "@bufbuild/connect";
import { createConnectTransport } from "@bufbuild/connect-web";
import { MethodKind, ServiceType } from "@bufbuild/protobuf";
import { TeamMemberInfo, TeamMemberRole } from "@gitpod/gitpod-protocol";
import { PublicAPIConverter } from "@gitpod/gitpod-protocol/lib/public-api-converter";
import { Project as ProtocolProject, Team as ProtocolTeam } from "@gitpod/gitpod-protocol/lib/teams-projects-protocol";
import { HelloService } from "@gitpod/public-api/lib/gitpod/experimental/v1/dummy_connectweb";
import { OIDCService } from "@gitpod/public-api/lib/gitpod/experimental/v1/oidc_connectweb";
import { ProjectsService } from "@gitpod/public-api/lib/gitpod/experimental/v1/projects_connectweb";
import { Project } from "@gitpod/public-api/lib/gitpod/experimental/v1/projects_pb";
import { TeamsService } from "@gitpod/public-api/lib/gitpod/experimental/v1/teams_connectweb";
import { Team, TeamMember, TeamRole } from "@gitpod/public-api/lib/gitpod/experimental/v1/teams_pb";
import { TokensService } from "@gitpod/public-api/lib/gitpod/experimental/v1/tokens_connectweb";
import { ProjectsService } from "@gitpod/public-api/lib/gitpod/experimental/v1/projects_connectweb";
import { WorkspacesService } from "@gitpod/public-api/lib/gitpod/experimental/v1/workspaces_connectweb";
import { OIDCService } from "@gitpod/public-api/lib/gitpod/experimental/v1/oidc_connectweb";
import { getMetricsInterceptor } from "@gitpod/public-api/lib/metrics";
import { Team } from "@gitpod/public-api/lib/gitpod/experimental/v1/teams_pb";
import { TeamMemberInfo, TeamMemberRole } from "@gitpod/gitpod-protocol";
import { TeamMember, TeamRole } from "@gitpod/public-api/lib/gitpod/experimental/v1/teams_pb";
import { Project } from "@gitpod/public-api/lib/gitpod/experimental/v1/projects_pb";
import { getExperimentsClient } from "../experiments/client";

const transport = createConnectTransport({
baseUrl: `${window.location.protocol}//${window.location.host}/public-api`,
interceptors: [getMetricsInterceptor()],
});

export const converter = new PublicAPIConverter();

export const helloService = createPromiseClient(HelloService, transport);
export const teamsService = createPromiseClient(TeamsService, transport);
export const personalAccessTokensService = createPromiseClient(TokensService, transport);
Expand Down Expand Up @@ -120,3 +124,66 @@ export function projectToProtocol(project: Project): ProtocolProject {
},
};
}

const clients = new Map<string, PromiseClient<ServiceType>>();

export function getServiceClient<T extends ServiceType>(
type: T,
jsonRPCClientProvider: () => PromiseClient<T>,
): PromiseClient<T> {
let client: PromiseClient<T> = clients.get(type.typeName) as any;
if (client) {
return client;
}
client = createServiceClient(type, jsonRPCClientProvider());
clients.set(type.typeName, client);
return client;
}

function createServiceClient<T extends ServiceType>(type: T, jsonRPCClient: PromiseClient<T>): PromiseClient<T> {
return new Proxy(jsonRPCClient, {
get(grpcClient, prop) {
// TODO(ak) remove after migration
async function resolveClient(): Promise<PromiseClient<T>> {
const isEnabled = await getExperimentsClient().getValueAsync("dashboard_public_api_v2_enabled", false, {
//TODO(ak) user
});
if (isEnabled) {
return grpcClient;
}
return jsonRPCClient;
}
return (...args: any[]) => {
const method = type.methods[prop as string];
if (!method) {
throw new ConnectError("unimplemented", Code.Unimplemented);
}

// TODO(ak) default timeouts

if (method.kind === MethodKind.Unary || method.kind === MethodKind.ClientStreaming) {
return (async () => {
try {
const client = await resolveClient();
const result = await Reflect.apply(client[prop as any], client, args);
return result;
} catch (e) {
throw converter.toError(e);
}
})();
}
return (async function* () {
try {
const client = await resolveClient();
const generator = Reflect.apply(client[prop as any], client, args) as AsyncGenerator<any>;
for await (const item of generator) {
yield item;
}
} catch (e) {
throw converter.toError(e);
}
})();
};
},
});
}
2 changes: 2 additions & 0 deletions components/gitpod-protocol/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
"exit": true
},
"dependencies": {
"@bufbuild/connect": "^0.13.0",
"@gitpod/public-api": "0.1.5",
"@types/react": "17.0.32",
"abort-controller-x": "^0.4.0",
"ajv": "^6.5.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,6 @@
*/

import { Code, ConnectError } from "@bufbuild/connect";
import {
CommitContext,
ConfigurationIdeConfig,
EnvVarWithValue,
PortProtocol,
WithEnvvarsContext,
WorkspaceContext,
WorkspaceInfo,
WorkspaceInstancePort,
} from "@gitpod/gitpod-protocol";
import { ApplicationError, ErrorCode, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
import {
AdmissionLevel,
EditorReference,
Expand All @@ -30,10 +19,13 @@ import {
WorkspaceStatus,
} from "@gitpod/public-api/lib/gitpod/experimental/v2/workspace_pb";
import { injectable } from "inversify";
import { ApplicationError, ErrorCode, ErrorCodes } from "./messaging/error";
import { CommitContext, EnvVarWithValue, WithEnvvarsContext, WorkspaceContext, WorkspaceInfo } from "./protocol";
import { ConfigurationIdeConfig, PortProtocol, WorkspaceInstancePort } from "./workspace-instance";

// TODO(ak) integration testing with stub services
@injectable()
export class PublicAPIConverter {
// TODO(ak) integration testing with stub services
toWorkspace(info: WorkspaceInfo) {
const workspace = new Workspace();

Expand Down Expand Up @@ -72,7 +64,10 @@ export class PublicAPIConverter {
}

toError(reason: unknown): ConnectError {
if (reason instanceof ApplicationError) {
if (reason instanceof ConnectError) {
return reason;
}
if (reason instanceof ApplicationError || ApplicationError.hasErrorCode(reason)) {
if (reason.code === ErrorCodes.NOT_FOUND) {
return new ConnectError(reason.message, Code.NotFound);
}
Expand Down
14 changes: 7 additions & 7 deletions components/server/src/api/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,28 @@
import { Code, ConnectError, ConnectRouter, HandlerContext, ServiceImpl } from "@bufbuild/connect";
import { expressConnectMiddleware } from "@bufbuild/connect-express";
import { MethodKind, ServiceType } from "@bufbuild/protobuf";
import { PublicAPIConverter } from "@gitpod/gitpod-protocol/lib/public-api-converter";
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
import { HelloService } from "@gitpod/public-api/lib/gitpod/experimental/v1/dummy_connectweb";
import { StatsService } from "@gitpod/public-api/lib/gitpod/experimental/v1/stats_connectweb";
import { TeamsService as TeamsServiceDefinition } from "@gitpod/public-api/lib/gitpod/experimental/v1/teams_connectweb";
import { UserService as UserServiceDefinition } from "@gitpod/public-api/lib/gitpod/experimental/v1/user_connectweb";
import { WorkspaceService } from "@gitpod/public-api/lib/gitpod/experimental/v2/workspace_connectweb";
import express from "express";
import * as http from "http";
import { inject, injectable, interfaces } from "inversify";
import { AddressInfo } from "net";
import { performance } from "perf_hooks";
import { v4 } from "uuid";
import { isFgaChecksEnabled } from "../authorization/authorizer";
import { grpcServerHandled, grpcServerHandling, grpcServerStarted } from "../prometheus-metrics";
import { SessionHandler } from "../session-handler";
import { LogContextOptions, runWithContext, wrapAsyncGenerator } from "../util/log-context";
import { APIHelloService as HelloServiceAPI } from "./hello-service-api";
import { APIStatsService as StatsServiceAPI } from "./stats";
import { APITeamsService as TeamsServiceAPI } from "./teams";
import { APIUserService as UserServiceAPI } from "./user";
import { WorkspaceServiceAPI as WorkspaceServiceAPI } from "./workspace-service-api";
import { LogContextOptions, wrapAsyncGenerator, runWithContext } from "../util/log-context";
import { v4 } from "uuid";
import { performance } from "perf_hooks";
import { WorkspaceService } from "@gitpod/public-api/lib/gitpod/experimental/v2/workspace_connectweb";
import { isFgaChecksEnabled } from "../authorization/authorizer";
import { PublicAPIConverter } from "./public-api-converter";
import { WorkspaceServiceAPI } from "./workspace-service-api";

function service<T extends ServiceType>(type: T, impl: ServiceImpl<T>): [T, ServiceImpl<T>] {
return [type, impl];
Expand Down
3 changes: 2 additions & 1 deletion components/server/src/api/workspace-service-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { WorkspaceService as WorkspaceServiceInterface } from "@gitpod/public-ap
import { GetWorkspaceRequest, GetWorkspaceResponse } from "@gitpod/public-api/lib/gitpod/experimental/v2/workspace_pb";
import { inject, injectable } from "inversify";
import { WorkspaceService } from "../workspace/workspace-service";
import { PublicAPIConverter } from "./public-api-converter";
import { PublicAPIConverter } from "@gitpod/gitpod-protocol/lib/public-api-converter";

@injectable()
export class WorkspaceServiceAPI implements ServiceImpl<typeof WorkspaceServiceInterface> {
Expand All @@ -19,6 +19,7 @@ export class WorkspaceServiceAPI implements ServiceImpl<typeof WorkspaceServiceI
@inject(PublicAPIConverter)
private readonly apiConverter: PublicAPIConverter;

// TODO(ak) cancellation
async getWorkspace(req: GetWorkspaceRequest, context: HandlerContext): Promise<GetWorkspaceResponse> {
const info = await this.workspaceService.getWorkspace(context.user.id, req.id);
const response = new GetWorkspaceResponse();
Expand Down

0 comments on commit af7ef05

Please sign in to comment.