Skip to content

Commit

Permalink
Migrate parts of gPRC workspaceService (#19129)
Browse files Browse the repository at this point in the history
* Migrate parts of WorkspaceService

* bump version
  • Loading branch information
mustard-mh authored Nov 29, 2023
1 parent 56b3dd5 commit d6dcda8
Show file tree
Hide file tree
Showing 18 changed files with 1,869 additions and 303 deletions.
2 changes: 1 addition & 1 deletion components/dashboard/src/data/setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import * as SSHClasses from "@gitpod/public-api/lib/gitpod/v1/ssh_pb";
// This is used to version the cache
// If data we cache changes in a non-backwards compatible way, increment this version
// That will bust any previous cache versions a client may have stored
const CACHE_VERSION = "9";
const CACHE_VERSION = "10";

export function noPersistence(queryKey: QueryKey): QueryKey {
return [...queryKey, "no-persistence"];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,26 @@
import { useQuery } from "@tanstack/react-query";
import { getGitpodService } from "../../service/service";
import { GetDefaultWorkspaceImageResult } from "@gitpod/gitpod-protocol";
import { GetWorkspaceDefaultImageResponse } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
import { workspaceClient } from "../../service/public-api";

export const useDefaultWorkspaceImageQuery = (workspaceId?: string) => {
return useQuery<GetDefaultWorkspaceImageResult>({
queryKey: ["default-workspace-image", { workspaceId }],
staleTime: 1000 * 60 * 10, // 10 minute
queryFn: async () => {
// without `workspaceId` getDefaultWorkspaceImage will return org setting and if not set fallback to installation
return await getGitpodService().server.getDefaultWorkspaceImage({ workspaceId });
},
});
};

export const useWorkspaceDefaultImageQuery = (workspaceId: string) => {
return useQuery<GetWorkspaceDefaultImageResponse>({
queryKey: ["default-workspace-image-v2", { workspaceId }],
staleTime: 1000 * 60 * 10, // 10 minute
queryFn: async () => {
return await workspaceClient.getWorkspaceDefaultImage({ workspaceId });
},
});
};
80 changes: 80 additions & 0 deletions components/dashboard/src/service/json-rpc-workspace-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ import {
WatchWorkspaceStatusResponse,
ListWorkspacesRequest,
ListWorkspacesResponse,
GetWorkspaceDefaultImageRequest,
GetWorkspaceDefaultImageResponse,
GetWorkspaceEditorCredentialsRequest,
GetWorkspaceEditorCredentialsResponse,
GetWorkspaceOwnerTokenRequest,
GetWorkspaceOwnerTokenResponse,
SendHeartBeatRequest,
SendHeartBeatResponse,
WorkspacePhase_Phase,
GetWorkspaceDefaultImageResponse_Source,
} from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
import { converter } from "./public-api";
import { getGitpodService } from "./service";
Expand Down Expand Up @@ -160,4 +170,74 @@ export class JsonRpcWorkspaceClient implements PromiseClient<typeof WorkspaceSer
result.workspace = workspace.workspace;
return result;
}

async getWorkspaceDefaultImage(
request: PartialMessage<GetWorkspaceDefaultImageRequest>,
_options?: CallOptions | undefined,
): Promise<GetWorkspaceDefaultImageResponse> {
if (!request.workspaceId) {
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "workspaceId is required");
}
const response = await getGitpodService().server.getDefaultWorkspaceImage({
workspaceId: request.workspaceId,
});
const result = new GetWorkspaceDefaultImageResponse();
result.defaultWorkspaceImage = response.image;
switch (response.source) {
case "installation":
result.source = GetWorkspaceDefaultImageResponse_Source.INSTALLATION;
break;
case "organization":
result.source = GetWorkspaceDefaultImageResponse_Source.ORGANIZATION;
break;
}
return result;
}

async sendHeartBeat(
request: PartialMessage<SendHeartBeatRequest>,
_options?: CallOptions | undefined,
): Promise<SendHeartBeatResponse> {
if (!request.workspaceId) {
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "workspaceId is required");
}
const workspace = await this.getWorkspace({ workspaceId: request.workspaceId });
if (
!workspace.workspace?.status?.phase ||
workspace.workspace.status.phase.name !== WorkspacePhase_Phase.RUNNING
) {
throw new ApplicationError(ErrorCodes.PRECONDITION_FAILED, "workspace is not running");
}
await getGitpodService().server.sendHeartBeat({
instanceId: workspace.workspace.status.instanceId,
wasClosed: request.disconnected === true,
});
return new SendHeartBeatResponse();
}

async getWorkspaceOwnerToken(
request: PartialMessage<GetWorkspaceOwnerTokenRequest>,
_options?: CallOptions | undefined,
): Promise<GetWorkspaceOwnerTokenResponse> {
if (!request.workspaceId) {
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "workspaceId is required");
}
const ownerToken = await getGitpodService().server.getOwnerToken(request.workspaceId);
const result = new GetWorkspaceOwnerTokenResponse();
result.ownerToken = ownerToken;
return result;
}

async getWorkspaceEditorCredentials(
request: PartialMessage<GetWorkspaceEditorCredentialsRequest>,
_options?: CallOptions | undefined,
): Promise<GetWorkspaceEditorCredentialsResponse> {
if (!request.workspaceId) {
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "workspaceId is required");
}
const credentials = await getGitpodService().server.getIDECredentials(request.workspaceId);
const result = new GetWorkspaceEditorCredentialsResponse();
result.editorCredentials = credentials;
return result;
}
}
10 changes: 6 additions & 4 deletions components/dashboard/src/service/service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { GitpodHostUrl } from "@gitpod/gitpod-protocol/lib/util/gitpod-host-url"
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
import { IDEFrontendDashboardService } from "@gitpod/gitpod-protocol/lib/frontend-dashboard-service";
import { RemoteTrackMessage } from "@gitpod/gitpod-protocol/lib/analytics";
import { helloService } from "./public-api";
import { helloService, workspaceClient } from "./public-api";
import { getExperimentsClient } from "../experiments/client";
import { ConnectError, Code } from "@connectrpc/connect";
import { instrumentWebSocket } from "./metrics";
Expand Down Expand Up @@ -237,7 +237,9 @@ export class IDEFrontendService implements IDEFrontendDashboardService.IServer {
const [user, listener, ideCredentials] = await Promise.all([
this.service.server.getLoggedInUser(),
this.service.listenToInstance(this.workspaceID),
this.service.server.getIDECredentials(this.workspaceID),
workspaceClient
.getWorkspaceEditorCredentials({ workspaceId: this.workspaceID })
.then((resp) => resp.editorCredentials),
]);
this.user = user;
this.ideCredentials = ideCredentials;
Expand Down Expand Up @@ -299,8 +301,8 @@ export class IDEFrontendService implements IDEFrontendDashboardService.IServer {
}

private activeHeartbeat(): void {
if (this.instanceID) {
this.service.server.sendHeartBeat({ instanceId: this.instanceID });
if (this.workspaceID) {
workspaceClient.sendHeartBeat({ workspaceId: this.workspaceID });
}
}

Expand Down
12 changes: 9 additions & 3 deletions components/dashboard/src/start/StartPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import { useDocumentTitle } from "../hooks/use-document-title";
import gitpodIcon from "../icons/gitpod.svg";
import { gitpodHostUrl } from "../service/service";
import { VerifyModal } from "./VerifyModal";
import { useDefaultWorkspaceImageQuery } from "../data/workspaces/default-workspace-image-query";
import { useWorkspaceDefaultImageQuery } from "../data/workspaces/default-workspace-image-query";
import { GetWorkspaceDefaultImageResponse_Source } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";

export enum StartPhase {
Checking = 0,
Expand Down Expand Up @@ -133,9 +134,14 @@ function StartError(props: { error: StartWorkspaceError }) {
}

function WarningView(props: { workspaceId?: string; showLatestIdeWarning?: boolean; error?: StartWorkspaceError }) {
const { data: imageInfo } = useDefaultWorkspaceImageQuery(props.workspaceId);
const { data: imageInfo } = useWorkspaceDefaultImageQuery(props.workspaceId ?? "");
let useWarning: "latestIde" | "orgImage" | undefined = props.showLatestIdeWarning ? "latestIde" : undefined;
if (props.error && props.workspaceId && imageInfo?.source === "organization") {
if (
props.error &&
props.workspaceId &&
imageInfo &&
imageInfo.source === GetWorkspaceDefaultImageResponse_Source.ORGANIZATION
) {
useWarning = "orgImage";
}
return (
Expand Down
19 changes: 14 additions & 5 deletions components/dashboard/src/start/StartWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ import Alert from "../components/Alert";
import { workspaceClient, workspacesService } from "../service/public-api";
import { watchWorkspaceStatus } from "../data/workspaces/listen-to-workspace-ws-messages";
import { Button } from "@podkit/buttons/Button";
import { GetWorkspaceRequest, StartWorkspaceRequest, StartWorkspaceResponse, Workspace, WorkspacePhase_Phase } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
import {
GetWorkspaceRequest,
StartWorkspaceRequest,
StartWorkspaceResponse,
Workspace,
WorkspacePhase_Phase,
} from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
import { PartialMessage } from "@bufbuild/protobuf";

const sessionId = v4();
Expand Down Expand Up @@ -575,10 +581,13 @@ export default class StartWorkspace extends React.Component<StartWorkspaceProps,
{
title: "Connect via SSH",
onClick: async () => {
const ownerToken = await getGitpodService().server.getOwnerToken(
this.props.workspaceId,
);
this.setState({ isSSHModalVisible: true, ownerToken });
const response = await workspaceClient.getWorkspaceOwnerToken({
workspaceId: this.props.workspaceId,
});
this.setState({
isSSHModalVisible: true,
ownerToken: response.ownerToken,
});
},
},
{
Expand Down
6 changes: 3 additions & 3 deletions components/dashboard/src/workspaces/WorkspaceOverflowMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import { ItemFieldContextMenu } from "../components/ItemsList";
import { useStopWorkspaceMutation } from "../data/workspaces/stop-workspace-mutation";
import { useToggleWorkspacedPinnedMutation } from "../data/workspaces/toggle-workspace-pinned-mutation";
import { useToggleWorkspaceSharedMutation } from "../data/workspaces/toggle-workspace-shared-mutation";
import { getGitpodService } from "../service/service";
import ConnectToSSHModal from "./ConnectToSSHModal";
import { DeleteWorkspaceModal } from "./DeleteWorkspaceModal";
import { useToast } from "../components/toasts/Toasts";
import { RenameWorkspaceModal } from "./RenameWorkspaceModal";
import { AdmissionLevel, Workspace, WorkspacePhase_Phase } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
import { workspaceClient } from "../service/public-api";

type WorkspaceEntryOverflowMenuProps = {
info: Workspace;
Expand All @@ -42,8 +42,8 @@ export const WorkspaceEntryOverflowMenu: FunctionComponent<WorkspaceEntryOverflo

//TODO: shift this into ConnectToSSHModal
const handleConnectViaSSHClick = useCallback(async () => {
const ot = await getGitpodService().server.getOwnerToken(workspace.id);
setOwnerToken(ot);
const response = await workspaceClient.getWorkspaceOwnerToken({ workspaceId: workspace.id });
setOwnerToken(response.ownerToken);
setSSHModalVisible(true);
}, [workspace.id]);

Expand Down
60 changes: 60 additions & 0 deletions components/public-api/gitpod/v1/workspace.proto
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ service WorkspaceService {
// StartWorkspace starts an existing workspace.
// If the specified workspace is not in stopped phase, this will return the workspace as is.
rpc StartWorkspace(StartWorkspaceRequest) returns (StartWorkspaceResponse) {}

// GetWorkspaceDefaultImage returns the default workspace image of specified
// workspace.
rpc GetWorkspaceDefaultImage(GetWorkspaceDefaultImageRequest) returns (GetWorkspaceDefaultImageResponse) {}

// SendHeartBeat sends a heartbeat to activate the workspace
rpc SendHeartBeat(SendHeartBeatRequest) returns (SendHeartBeatResponse) {}

// GetWorkspaceOwnerToken returns an owner token of workspace.
rpc GetWorkspaceOwnerToken(GetWorkspaceOwnerTokenRequest) returns (GetWorkspaceOwnerTokenResponse) {}

// GetWorkspaceEditorCredentials returns an credentials that is used in editor
// to encrypt and decrypt secrets
rpc GetWorkspaceEditorCredentials(GetWorkspaceEditorCredentialsRequest) returns (GetWorkspaceEditorCredentialsResponse) {}
}

message GetWorkspaceRequest {
Expand Down Expand Up @@ -158,6 +172,52 @@ message StartWorkspaceResponse {
Workspace workspace = 1;
}

message GetWorkspaceDefaultImageRequest {
// workspace_id specifies the workspace to get default image
string workspace_id = 1;
}

message GetWorkspaceDefaultImageResponse {
enum Source {
SOURCE_UNSPECIFIED = 0;
SOURCE_INSTALLATION = 1;
SOURCE_ORGANIZATION = 2;
}

string default_workspace_image = 1;

Source source = 2;
}

message SendHeartBeatRequest {
// workspace_id specifies the workspace to send heartbeat
//
// +required
string workspace_id = 1;

// disconnected indicates if the editor connection is disconnected.
// If set to true, the workspace will be stopped after Timeout.disconnected.
bool disconnected = 2;
}

message SendHeartBeatResponse {}

message GetWorkspaceOwnerTokenRequest {
string workspace_id = 1;
}

message GetWorkspaceOwnerTokenResponse {
string owner_token = 1;
}

message GetWorkspaceEditorCredentialsRequest {
string workspace_id = 1;
}

message GetWorkspaceEditorCredentialsResponse {
string editor_credentials = 1;
}

// +resource get workspace
message Workspace {
string id = 1;
Expand Down
2 changes: 1 addition & 1 deletion components/public-api/go/v1/user.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit d6dcda8

Please sign in to comment.